一、微信支付(简单贴一下)
/** 发起微信支付 */ public Map wechatPay(String openid, HttpServletRequest request, String outTradeNo, Integer totalFee) { //生成的随机32位字符串 String nonce_str = RandomString.generateRandomString(32, RandomString.POSSIBLE_CHARS); //商品金额 单位分 将原来的金额*100 元->分 Integer total_fee = (totalFee); //商品名称 String packageName = "杭家订单:" + outTradeNo; //获取客户端的ip地址 String spbill_create_ip = getIpAddress(request); //生成商户订单号 String out_trade_no = outTradeNo; //组装参数,用户生成统一下单接口的签名 Map<String, String> packageParams = new HashMap<String, String>(); packageParams.put("appid", weChatConfig.appId); packageParams.put("mch_id", weChatConfig.mchId); packageParams.put("nonce_str", nonce_str); packageParams.put("body", packageName); packageParams.put("out_trade_no", out_trade_no);//商户订单号 packageParams.put("total_fee", String.valueOf(total_fee));//支付金额,这边需要转成字符串类型,否则后面的签名会失败 packageParams.put("spbill_create_ip", spbill_create_ip); packageParams.put("notify_url", weChatConfig.notifyUrl);//支付成功后的回调地址 packageParams.put("trade_type", weChatConfig.tradetype);//支付方式 packageParams.put("openid", openid); String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 //MD5运算生成签名,这里是第一次签名,用于调用统一下单接口 String mysign = PayUtil.sign(prestr, weChatConfig.key, "utf-8").toUpperCase(); //拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去 String xml = "<xml>" + "<appid>" + weChatConfig.appId + "</appid>" + "<body><![CDATA[" + packageName + "]]></body>" + "<mch_id>" + weChatConfig.mchId + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<notify_url>" + weChatConfig.notifyUrl + "</notify_url>" + "<openid>" + openid + "</openid>" + "<out_trade_no>" + out_trade_no + "</out_trade_no>" + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>" + "<total_fee>" + total_fee + "</total_fee>" + "<trade_type>" + weChatConfig.tradetype + "</trade_type>" + "<sign>" + mysign + "</sign>" + "</xml>"; //调用统一下单接口,并接受返回的结果 String result = PayUtil.httpRequest(weChatConfig.payUrl, "POST", xml); System.out.println("调试模式 返回XML数据:" + result); // 将解析结果存储在HashMap中 Map map = PayUtil.doXMLParse(result); String return_code = (String) map.get("return_code");//返回状态码 String return_msg = (String) map.get("return_msg");//返回状态信息 //返回给移动端需要的参数 Map<String, Object> response = new HashMap<>(); if ("SUCCESS".equals(return_code)) { // 业务结果 String prepay_id = (String) map.get("prepay_id");//返回的预付单信息 String sign = (String) map.get("sign");//微信返回的签名值 response.put("sign", sign); response.put("signType", weChatConfig.signtype); response.put("nonceStr", nonce_str); response.put("package", "prepay_id=" + prepay_id); Long timeStamp = System.currentTimeMillis() / 1000; response.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误 String stringSignTemp = "appId=" + weChatConfig.appId + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id + "&signType=" + weChatConfig.signtype + "&timeStamp=" + timeStamp; //再次签名,这个签名用于小程序端调用wx.requesetPayment方法 String paySign = PayUtil.sign(stringSignTemp, weChatConfig.key, "utf-8").toUpperCase(); log.info("=======================第二次签名:" + paySign + "====================="); response.put("paySign", paySign); response.put("totalFee",total_fee); /**业务代码*/ } else { throw new BaseException(ResultEnum.WECHAT_PAY_ERROR, return_msg); } response.put("appid", weChatConfig.appId); return response; } /** 支付回调 */ @Override @Transactional public String wechatPayCallBack(HttpServletRequest request, HttpServletResponse response) { try { String line = null; BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream())); StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line); } br.close(); //sb为微信返回的xml String notityXml = sb.toString(); String resXml = ""; log.info("接收到的报文:" + notityXml); Map map = PayUtil.doXMLParse(notityXml); String returnCode = (String) map.get("return_code"); if ("SUCCESS".equals(returnCode)) { //验证签名是否正确 Map<String, String> validParams = PayUtil.paraFilter(map); //回调验签时需要去除sign和空值参数 String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 String sign = PayUtil.sign(validStr, weChatConfig.key, "utf-8").toUpperCase();//拼装生成服务器端验证的签名 //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等 if (sign.equals(map.get("sign"))) { log.info("sign == sigin 签名通过"); /**此处添加自己的业务逻辑代码start*/ String outTradeNo = (String) map.get("out_trade_no"); String transactionId = (String) map.get("transaction_id"); String ip = getIpAddress(request); // 支付回调处理 Boolean check = orderService.wechatPayCallBack(outTradeNo,transactionId,ip); if(check){ // 通知微信服务器已经支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { // 通知微信服务器已经支付失败 resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[FAIL]]></return_msg>" + "</xml> "; } /**此处添加自己的业务逻辑代码end**/ } } else { resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; } log.info(resXml); log.info("微信支付回调数据结束"); BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); return resXml; } catch (Exception e) { return null; } }二、微信退款
1.主要代码:
/** 微信退款 */ @Override public Map<String, String> wechatRefund(int refundMoney, int totalMoney, String out_trade_no) { //生成的随机32位字符串 String nonce_str = RandomString.generateRandomString(32, RandomString.POSSIBLE_CHARS); String out_refund_no = SnowflakeIdWorker.getId();//商户退款单号 //签名算法 SortedMap<Object, Object> paramMap = new TreeMap<Object, Object>(); paramMap.put("appid", weChatConfig.appId); paramMap.put("mch_id", weChatConfig.mchId); paramMap.put("nonce_str", nonce_str); paramMap.put("out_trade_no", out_trade_no); paramMap.put("out_refund_no", out_refund_no); paramMap.put("total_fee", String.valueOf(totalMoney)); paramMap.put("refund_fee", String.valueOf(refundMoney)); paramMap.put("notify_url", weChatConfig.refundNotifyUrl); String sign = PayUtil.createSign(weChatConfig.key,paramMap); //获取最终待发送的数据 String requestXml = "<xml>" + "<appid>" + weChatConfig.appId + "</appid>" + "<mch_id>" + weChatConfig.mchId + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<out_trade_no>" + out_trade_no + "</out_trade_no>" + "<out_refund_no>" + out_refund_no + "</out_refund_no>" + "<total_fee>" + String.valueOf(totalMoney) + "</total_fee>" + "<refund_fee>" + String.valueOf(refundMoney) + "</refund_fee>" + "<notify_url>" + weChatConfig.refundNotifyUrl + "</notify_url>" + "<sign>" + sign + "</sign>" + "</xml>"; //定义本函数返回值: Map<String,String> returnMap = new HashMap<>(); try { //建立连接并发送数据 String result = PayUtil.WeixinSendPostToRefund(weChatConfig.refundUrl,requestXml, weChatConfig.mchId); //解析返回的xml Map<String,String> resultMap = PayUtil.doXMLParse(result); //退款返回标志码 String return_code = resultMap.get("return_code").toString(); String result_code = resultMap.get("result_code").toString(); if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){ //code... returnMap.put("status","success"); returnMap.put("msg","发起微信退款成功"); returnMap.put("transactionId",resultMap.get("transaction_id")); returnMap.put("outTradeNo",resultMap.get("out_trade_no")); returnMap.put("outRefundNo",resultMap.get("out_refund_no")); return returnMap; }else if(return_code.equals("SUCCESS") && result_code.equals("FAIL")){ returnMap.put("status","fail"); returnMap.put("msg","微信原路返款失败!"); return returnMap; }else{ returnMap.put("status","fail"); returnMap.put("msg","微信原路返款失败!"); return returnMap; } }catch (Exception e){ e.printStackTrace(); returnMap.put("status","fail"); returnMap.put("msg",e.getMessage()); return returnMap; } }2.证书使用
微信退款是需要证书的,怎么获取证书,微信支付文档比任何人说得都详细
/** 微信退款--向微信端发送post请求 */ public static String WeixinSendPostToRefund(String url,String xmlObj,String mch_id) throws Exception{ ClassPathResource classPathResource = new ClassPathResource("apiclient_cert.p12");//证书路径(此处我用的相对路径,你也可以考虑安全性,放在项目外) InputStream instream = classPathResource.getInputStream(); KeyStore keyStore = KeyStore.getInstance("PKCS12"); try { keyStore.load(instream, mch_id.toCharArray()); } catch (Exception e) { e.printStackTrace(); } finally { instream.close(); } SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mch_id.toCharArray()).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); String result = ""; try { HttpPost httpPost = new HttpPost(url); HttpEntity xmlData = new StringEntity((String) xmlObj, "text/xml", "iso-8859-1"); httpPost.setEntity(xmlData); CloseableHttpResponse response = httpclient.execute(httpPost); try { HttpEntity entity = response.getEntity(); result = EntityUtils.toString(entity, "UTF-8"); System.out.println(response.getStatusLine()); EntityUtils.consume(entity); } finally { response.close(); } } finally { httpclient.close(); } //去除空格 return result.replaceAll(" ", ""); }3.代码中引用的方法
生成随机字符串:你可以用微信提供的javaSDK,其中有这个,而且map转xml和xml转map等都有
商户订单号:我用的SnowflakeIdWorker,这个随你,只要唯一就行了
sign签名:
/** * 微信退款--sign签名 */ public static String createSign(String secretKey, SortedMap<Object, Object> parameters) { StringBuffer sb = new StringBuffer(); Set<Map.Entry<Object, Object>> es = parameters.entrySet(); Iterator<Map.Entry<Object, Object>> it = es.iterator(); while (it.hasNext()) { Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + secretKey); String sign = MD5Utils.MD5(sb.toString()).toUpperCase(); parameters.put("sign", sign); return sign; }三、微信退款回调
首先不用多说,在外网地址上开放一个接口
1.主要代码
/** 微信退款回调 */ @Override @Transactional(rollbackFor = Exception.class) public String wechatRefundCallback(HttpServletRequest request, HttpServletResponse response) { log.info("微信回调开始啦!!!!"); try { BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream())); String line = null; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line); } br.close(); //sb为微信返回的xml Map resultMap = PayUtil.doXMLParse(sb.toString()); if ("SUCCESS".equals(resultMap.get("return_code"))){//通信成功 String req_info = String.valueOf(resultMap.get("req_info")); String resultXml = AESUtil.decryptData(req_info,weChatConfig.key); Map<String,Object> reqInfoMap = PayUtil.doXMLParse(resultXml);//code.... String resultStr = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; return resultStr; } }catch (Exception e){ e.printStackTrace(); } throw new RuntimeException("微信回调失败"); }2.解密方法
AESUtil
import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;public class AESUtil { /** 密钥算法 */ private static final String ALGORITHM = "AES"; /** 加解密算法/工作模式/填充方式 */ private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding"; /** AES加密 */ public static String encryptData(String data,String password) throws Exception { // 创建密码器 Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING); SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM); // 初始化 cipher.init(Cipher.ENCRYPT_MODE, key); return Base64Util.encode(cipher.doFinal(data.getBytes())); } /** AES解密 */ public static String decryptData(String base64Data,String password) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING); SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key); byte[] decode = Base64Util.decode(base64Data); byte[] doFinal = cipher.doFinal(decode); return new String(doFinal,"utf-8"); }}Base64Util
import java.util.Base64;public class Base64Util { /** 解码 */ public static byte[] decode(String encodedText){ final Base64.Decoder decoder = Base64.getDecoder(); return decoder.decode(encodedText); } /** 编码 */ public static String encode(byte[] data){ final Base64.Encoder encoder = Base64.getEncoder(); return encoder.encodeToString(data); }}MD5Utils
/** 普通MD5 */ public static String MD5(String input) { MessageDigest md5 = null; try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { return "check jdk"; } catch (Exception e) { e.printStackTrace(); return ""; } char[] charArray = input.toCharArray(); byte[] byteArray = new byte[charArray.length]; for (int i = 0; i < charArray.length; i++) { byteArray[i] = (byte) charArray[i]; } byte[] md5Bytes = md5.digest(byteArray); StringBuffer hexValue = new StringBuffer(); for (int i = 0; i < md5Bytes.length; i++) { int val = ((int) md5Bytes[i]) & 0xff; if (val < 16) { hexValue.append("0"); } hexValue.append(Integer.toHexString(val)); } return hexValue.toString(); }ps:很多教程说要替换jre的两个jar包,我这里没照这一步,在AES解密的时候
SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM);
这步将商户key加密串转成小写了,这样就解决了"JAVA运行环境默认不允许256位密钥的AES加解密""的问题
有啥不懂得,或者我贴的代码有啥缺漏的,可以留言
参考:https://blog.csdn.net/zhangxing52077/article/details/80269999













