官方文档
微信官方提供的SDK和demo,下载地址
springboot整合微信支付(完整)
springboot 使用 RestTemplate 携带 微信API证书发送请求实现企业付款到零钱/提现/微信退款等场景
这个是别人把官方的SDK放到了Maven仓库里,也可以使用

  1. <dependency>
  2. <groupId>com.github.tedzhdz</groupId>
  3. <artifactId>wxpay-sdk</artifactId>
  4. <version>3.0.10</version>
  5. </dependency>

第一步:把官方提供的SDK复制到自己的项目中

image.png
这个时候,里面有地方会缺少依赖,我们需要引入对应的依赖。一般就缺少httpclient依赖,

  1. <!--微信支付请求里需要用到的依赖,是http请求相关的-->
  2. <dependency>
  3. <groupId>org.apache.httpcomponents</groupId>
  4. <artifactId>httpclient</artifactId>
  5. </dependency>

第二步,根据要求生成XML的请求体参数

1.定义一个JAVA实体类

  1. package com.tj.qywx.domain;
  2. import lombok.Data;
  3. import java.io.Serializable;
  4. /**
  5. * 向员工付款的XML数据字段
  6. */
  7. @Data
  8. public class WxPayManXml implements Serializable {
  9. //商户订单号
  10. private String partner_trade_no;
  11. //用户openid
  12. private String userid;
  13. //金额
  14. private Integer amount;
  15. //向员工付款说明信息。必填
  16. private String desc;
  17. //审批编号
  18. private String approval_number;
  19. //项目名称
  20. private String act_name;
  21. private static final long serialVersionUID = 1L;
  22. }

2、定义map填入请求需要的数据

  1. //1.定义Map
  2. String mchId = baseNumvarService.getMchId();
  3. HashMap<String, String> xmlMap = MapUtils.newHashMap();
  4. xmlMap.put("appid", baseNumvarService.getCorpid());
  5. xmlMap.put("mch_id", mchId);
  6. xmlMap.put("nonce_str", WXPayUtil.generateNonceStr());
  7. xmlMap.put("partner_trade_no", wxPayManXml.getPartner_trade_no());
  8. xmlMap.put("openid", wxUserlistService.getOpenidByUserid(wxPayManXml.getUserid()));
  9. xmlMap.put("amount", String.valueOf(wxPayManXml.getAmount()));
  10. xmlMap.put("desc", wxPayManXml.getDesc());
  11. xmlMap.put("ww_msg_type", "APPROVAL_MSG");
  12. //进行企业微信签名(不是所有字段都需要参与签名,根据文档要求)
  13. log.info("企业微信签名的原始数据:{}", xmlMap);
  14. //微信官方工具默认最后拼接的是“key”,但是企业微信签名算法最后需要拼接的是“secret”,这个bug有点坑,这个地方自己要特殊处理一下
  15. String workwxSign = MyWXPayUtils.getSignByMD5(xmlMap, baseNumvarService.getSecretPay(), "secret");
  16. xmlMap.put("workwx_sign", workwxSign);
  17. xmlMap.put("act_name", wxPayManXml.getAct_name());
  18. xmlMap.put("approval_number", wxPayManXml.getApproval_number());
  19. xmlMap.put("spbill_create_ip", "192.168.0.1");
  20. //进行微信支付签名
  21. log.info("微信支付签名的原始数据:{}", xmlMap);
  22. String sign = WXPayUtil.generateSignature(xmlMap, baseNumvarService.getAPIv2());
  23. xmlMap.put("sign", sign);

处理签名的方法(微信官方提供的sdk里面有些小坑,要注意)

  1. /**
  2. * 签名算法,
  3. * 第一步: 对参数按照key=value的格式,并按照参数名ASCII字典序排序如下
  4. * 第二步:拼接企业微信支付应用secret(参见企业微信管理端支付应用页面):
  5. *
  6. * @param data
  7. * @param keyValue
  8. * @param key
  9. * @return
  10. */
  11. public static String getSignByMD5(final Map<String, String> data, String keyValue, String key) {
  12. if (key == null) {
  13. key = "key";
  14. }
  15. //排序
  16. Set<String> keySet = data.keySet();
  17. String[] keyArray = keySet.toArray(new String[keySet.size()]);
  18. Arrays.sort(keyArray);
  19. StringBuilder sb = new StringBuilder();
  20. for (String k : keyArray) {
  21. if (k.equals(WXPayConstants.FIELD_SIGN)) {
  22. continue;
  23. }
  24. if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
  25. sb.append(k).append("=").append(data.get(k).trim()).append("&");
  26. }
  27. sb.append(key).append("=").append(keyValue);
  28. System.out.println("微信签名组合sb = " + sb);
  29. //md5加密转大写
  30. return DigestUtils.md5DigestAsHex(sb.toString().getBytes()).toUpperCase();
  31. }

3、注意,这个里面WXPayUtil是官方提供的SDK里工具类,

参考:微信签名的算法文档

  1. //进行企业微信签名(不是所有字段都需要参与签名,根据文档要求)
  2. log.info("企业微信签名的原始数据:{}", xmlMap);
  3. //微信官方工具默认最后拼接的是“key”,但是企业微信签名算法最后需要拼接的是“secret”,这个bug有点坑,这个地方自己要特殊处理一下
  4. String workwxSign = MyWXPayUtils.getSignByMD5(xmlMap, baseNumvarService.getSecretPay(), "secret");
  5. xmlMap.put("workwx_sign", workwxSign);
  6. //进行微信支付签名
  7. log.info("微信支付签名的原始数据:{}", xmlMap);
  8. String sign = WXPayUtil.generateSignature(xmlMap, baseNumvarService.getAPIv2());
  9. xmlMap.put("sign", sign);

4、把Map转成XML字符串,使用的还是微信官方提供的工具类

  1. //2.Map转成XML
  2. String PostXml = WXPayUtil.mapToXml(xmlMap);

5、执行请求,再吧请求结果转换成map,方便使用

  1. //3.请求地址
  2. String url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/paywwsptrans2pocket";
  3. // 执行请求
  4. String xmlString = MyWXPayUtils.requestWithCert(mchId, this.path, url, PostXml);
  5. log.info("付款后返回的Xml信息:{}", xmlString);
  6. //4.xml转map
  7. Map<String, String> resultMap = WXPayUtil.xmlToMap(xmlString);
  8. log.info("付款后返回的转换后的map信息:{}", resultMap);

附录:

http请求封装

  1. /**
  2. * 微信小程序退款订单(需要双向证书)--微信支付需要双向证书
  3. * @param mchId 商户号
  4. * @param certPath 证书文件夹路径,全路径
  5. * @param url 请求地址
  6. * @param data xml请求参数数据
  7. * @return
  8. * @throws Exception
  9. */
  10. public static String requestWithCert(String mchId, String certPath, String url, String data) throws Exception {
  11. //小程序退款需要调用双向证书的认证
  12. CloseableHttpClient httpClient = readCertificate(mchId, certPath);
  13. try {
  14. HttpPost httpost = new HttpPost(url); // 设置响应头信息
  15. httpost.addHeader("Connection", "keep-alive");
  16. httpost.addHeader("Accept", "*/*");
  17. httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
  18. httpost.addHeader("Host", "api.mch.weixin.qq.com");
  19. httpost.addHeader("X-Requested-With", "XMLHttpRequest");
  20. httpost.addHeader("Cache-Control", "max-age=0");
  21. httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
  22. httpost.setEntity(new StringEntity(data, "UTF-8"));
  23. CloseableHttpResponse response = httpClient.execute(httpost);
  24. try {
  25. HttpEntity entity = response.getEntity();
  26. String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
  27. EntityUtils.consume(entity);
  28. return jsonStr;
  29. } finally {
  30. response.close();
  31. }
  32. } catch (Exception e) {
  33. throw new RuntimeException(e);
  34. } finally {
  35. httpClient.close();
  36. }
  37. }

读取P12双向证书

  1. /**
  2. * 读取p12双向证书
  3. *
  4. * @param mchId 商户号,主要作为密码使用
  5. * @param certPath 证书文件位置
  6. * @return
  7. * @throws Exception
  8. */
  9. @PostConstruct
  10. private static CloseableHttpClient readCertificate(String mchId, String certPath) throws Exception {
  11. /**
  12. * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
  13. */
  14. KeyStore keyStore = KeyStore.getInstance("PKCS12");
  15. //P12文件目录 证书路径,这里需要你自己修改,linux下还是windows下的根路径
  16. String filepath = certPath;
  17. System.out.println("filepath->" + filepath);
  18. FileInputStream instream = new FileInputStream(filepath + "apiclient_cert.p12");
  19. try {
  20. keyStore.load(instream, mchId.toCharArray());//这里写密码..默认是你的MCHID(商户号)
  21. } finally {
  22. instream.close();
  23. }
  24. // Trust own CA and all self-signed certs
  25. SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();//这里也是写密码的
  26. // Allow TLSv1 protocol only
  27. SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
  28. return HttpClients.custom().setSSLSocketFactory(sslsf).build();
  29. }

1、企业微信支付请求带P12证书双向认证

参考:
企业微信支付请求带P12证书双向认证