当前位置:   article > 正文

SpringBoot对接小程序微信支付_springboot实现小程序支付

springboot实现小程序支付

目录

前言

一、准备工作

2.1、企业微信小程序开通

2.1.1、获取开发者ID

2.1.2、开通支付功能

2.1.3、关联商户号

2.2、企业商户号的开通

2.2.1、获取商户号mch_id

2.2.2、获取商户API密钥mch_key

二、整体流程

三、后端项目搭建

3.1、统一下单

3.2、支付支付回调

3.3、问题排查

3.4、统一下单和订单查询


前言

项目采用SpringBoot

微信支付有两个版本:V3 和 V2,本文的接入版本为V2

API V2 和 API V3 的区别

1、接口请求参数不同

2、API V2 调用流程

在微信v2接口中,只有涉及资金流出或获取重要信息才会使用证书,比如退款、企业付款和下载资金账单等。

3、API V3 调用流程

(1)证书序列号

每个证书都有一个由CA颁发的唯一编号,即证书序列号。如果读取到的序列号是10进制整形需要转换为大写的16进制。

(2)平台证书

微信支付平台证书是指由微信支付负责申请的,包含微信支付平台标识、公钥信息的证书。

a、不同的商户,对应的微信支付平台证书是不一样的。

b、微信支付APIv3使用微信支付的平台私钥进行应答签名,商户使用平台证书中的公钥进行验签。

c、微信平台证书会周期性更换,商户应实现定期更新平台证书的逻辑实现平台证书平滑切换,即:定期调用该接口,间隔时间小于12 小时,参考获取平台证书接口。目前已知的微信更换平台证书的场景:

  • 证书到期后,必须更换。(目前是五年)
  • 证书到期前,例行更换。(每年一次)

d、旧证书过期前10天生成新证书,旧证书过期前5天至过期当天,新证书开始逐步放量用于应答和回调的签名。为了保证更换过程中不影响API的使用,请求和应答的HTTP头部中包括证书序列号,以声明签名或者加密所用的密钥对和证书。

一、准备工作

微信小程序

微信支付

  • 微信小程序账号:要认证、获取appid、生成secret、开通支付、关联商户号
  • 微信商户平台账号:要认证、获取商户号mch_id、获取商户API密钥mch_key、APPID授权、配置支付接口

2.1、企业微信小程序开通

2.1.1、获取开发者ID

  • 获取appid:小程序的身份证明
  • 获取secret:小程序的唯一凭证密钥

2.1.2、开通支付功能

2.1.3、关联商户号

2.2、企业商户号的开通

2.2.1、获取商户号mch_id

2.2.2、获取商户API密钥mch_key

二、整体流程

商户系统与微信支付系统主要交互:

1、JSAPI支付、APP支付、H5支付、Native支付、付款码支付、小程序支付【微信支付API列表

2、小程序内调用登录接口,获取到用户的openid【小程序登录API

3、商户server调用支付统一下单【统一下单API

4、小程序支付成功回调notity_url(业务逻辑处理)【支付结果通知

5、商户server查询支付结果,如未收到支付通知的情况,商户后台系统可调用【查询订单API

三、后端项目搭建

3.1、统一下单

1、导入相关依赖 pom.yml

  1. <!-- 微信支付 SDK -->
  2. <dependency>
  3. <groupId>com.github.wxpay</groupId>
  4. <artifactId>wxpay-sdk</artifactId>
  5. <version>0.0.3</version>
  6. </dependency>

2、文件配置微信公众号的基础信息 application.yml

  1. # 微信支付配置 notifyUrl:微信支付异步回调地址
  2. pay:
  3. appId: #应用id
  4. apiKey: #商户私钥key
  5. mchId: #商户id
  6. appSecret: #小程序密钥
  7. notifyUrl: #支付回调地址

3、设置配置文件 WxPayConfig.java

  1. import lombok.Data;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.stereotype.Component;
  5. /**
  6. * 微信支付配置
  7. * @author lf
  8. * @date 2023/8/30
  9. */
  10. @Data
  11. @Component
  12. @Configuration
  13. @ConfigurationProperties(prefix = "pay")
  14. public class WxPayConfig {
  15. /**
  16. * 微信公众号appid
  17. */
  18. private String appId;
  19. /**
  20. * 公众号设置的API v2密钥
  21. */
  22. private String apiKey;
  23. /**
  24. * 微信商户平台 商户id
  25. */
  26. private String mchId;
  27. /**
  28. *小程序密钥
  29. */
  30. private String appSecret;
  31. /**
  32. * 小程序支付异步回调地址
  33. */
  34. private String notifyUrl;
  35. }

4、微信支付预下单实体类 WxChatPay.java

  1. import lombok.Data;
  2. import lombok.experimental.Accessors;
  3. import java.math.BigDecimal;
  4. /**
  5. * 微信支付预下单实体类
  6. */
  7. @Data
  8. @Accessors(chain = true)
  9. public class WeChatPay {
  10. /**
  11. * 返回状态码 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
  12. */
  13. public String return_code;
  14. /**
  15. * 返回信息 当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
  16. */
  17. private String return_msg;
  18. /**
  19. * 公众账号ID 调用接口提交的公众账号ID
  20. */
  21. private String appid;
  22. /**
  23. * 商户号 调用接口提交的商户号
  24. */
  25. private String mch_id;
  26. /**
  27. * api密钥 详见:https://pay.weixin.qq.com/index.php/extend/employee
  28. */
  29. private String api_key;
  30. /**
  31. * 设备号 自定义参数,可以为请求支付的终端设备号等
  32. */
  33. private String device_info;
  34. /**
  35. * 随机字符串 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的随机字符串
  36. */
  37. private String nonce_str;
  38. /**
  39. * 签名 微信返回的签名值,详见签名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
  40. */
  41. private String sign;
  42. /**
  43. * 签名类型
  44. */
  45. private String sign_type;
  46. /**
  47. * 业务结果 SUCCESS SUCCESS/FAIL
  48. */
  49. private String result_code;
  50. /**
  51. * 错误代码 当result_code为FAIL时返回错误代码,详细参见下文错误列表
  52. */
  53. private String err_code;
  54. /**
  55. * 错误代码描述 当result_code为FAIL时返回错误描述,详细参见下文错误列表
  56. */
  57. private String err_code_des;
  58. /**
  59. * 交易类型 JSAPI JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 说明详见;https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
  60. */
  61. private String trade_type;
  62. /**
  63. * 预支付交易会话标识 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
  64. */
  65. private String prepay_id;
  66. /**
  67. * 二维码链接 weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 trade_type=NATIVE时有返回,此url用于生成支付二维码,然后提供给用户进行扫码支付。注意:code_url的值并非固定,使用时按照URL格式转成二维码即可
  68. */
  69. private String code_url;
  70. /**
  71. * 商品描述 商品简单描述,该字段请按照规范传递,具体请见 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
  72. */
  73. private String body;
  74. /**
  75. * 商家订单号 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
  76. */
  77. private String out_trade_no;
  78. /**
  79. * 标价金额 订单总金额,单位为分,详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
  80. */
  81. private String total_fee;
  82. /**
  83. * 终端IP 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
  84. */
  85. private String spbill_create_ip;
  86. /**
  87. * 通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
  88. */
  89. private String notify_url;
  90. /**
  91. * 子商户号 sub_mch_id 非必填(商户不需要传入,服务商模式才需要传入) 微信支付分配的子商户号
  92. */
  93. private String sub_mch_id;
  94. /**
  95. * 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
  96. */
  97. private String attach;
  98. /**
  99. * 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
  100. */
  101. private String out_refund_no;
  102. /**
  103. * 退款总金额,单位为分,只能为整数,可部分退款。详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=4_2
  104. */
  105. private String refund_fee;
  106. /**
  107. * 退款原因 若商户传入,会在下发给用户的退款消息中体现退款原因 注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
  108. */
  109. private String refund_desc;
  110. /**
  111. * 交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟
  112. */
  113. private String time_expire;
  114. /**
  115. * 用户标识 trade_type=JSAPI,此参数必传,用户在主商户appid下的唯一标识。openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid。下单前需要调用【网页授权获取用户信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口获取到用户的Openid。
  116. */
  117. private String openid;
  118. /**
  119. * 时间戳
  120. */
  121. private String time_stamp;
  122. /**
  123. * 会员类型
  124. */
  125. private String memberShipType;
  126. }

5、微信支付API地址 WeChatPayUrlContants.java

  1. /**
  2. * 微信支付API地址
  3. * @author lf
  4. * @date 2023/8/30
  5. */
  6. public class WeChatPayUrlConstants {
  7. /**
  8. * 统一下单预下单接口url
  9. */
  10. public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder";
  11. /**
  12. * 订单状态查询接口URL
  13. */
  14. public static final String Orderquery = "https://api.mch.weixin.qq.com/pay/orderquery";
  15. /**
  16. * 订单申请退款
  17. */
  18. public static final String Refund = "https://api.mch.weixin.qq.com/secapi/pay/refund";
  19. /**
  20. * 付款码 支付
  21. */
  22. public static final String MicroPay = "https://api.mch.weixin.qq.com/pay/micropay";
  23. /**
  24. * 微信网页授权 获取“code”请求地址
  25. */
  26. public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";
  27. /**
  28. * 微信网页授权 获取“code” 回调地址
  29. */
  30. public static final String GainCodeRedirect_uri = "http://i5jmxe.natappfree.cc/boss/WeChatPayMobile/SkipPage.html";
  31. }

 6、预下单成功之后返回结果 OrderReturnInfo.java

  1. import lombok.Data;
  2. @Data
  3. public class OrderReturnInfo {
  4. private String return_code;
  5. private String return_msg;
  6. private String result_code;
  7. private String appid;
  8. private String mch_id;
  9. private String nonce_str;
  10. private String sign;
  11. private String prepay_id;
  12. private String trade_type;
  13. }

7、查询订单返回的实体类 QueryReturnInfo.java

  1. import lombok.Data;
  2. /**
  3. * 查询订单返回实体类
  4. * @author lf
  5. * @date 2023/9/1
  6. */
  7. @Data
  8. public class QueryReturnInfo {
  9. private String return_code;
  10. private String return_msg;
  11. private String result_code;
  12. private String err_code;
  13. private String err_code_des;
  14. private String appid;
  15. private String mch_id;
  16. private String nonce_str;
  17. private String sign;
  18. private String prepay_id;
  19. private String trade_type;
  20. private String device_info;
  21. private String openid;
  22. private String is_subscribe;
  23. private String trade_state;
  24. private String bank_type;
  25. private int total_fee;
  26. private int settlement_total_fee;
  27. private String fee_type;
  28. private int cash_fee;
  29. private String cash_fee_type;
  30. private int coupon_fee;
  31. private int coupon_count;
  32. private String coupon_type_$n;
  33. private String coupon_id_$n;
  34. private String transaction_id;
  35. private String out_trade_no;
  36. private String time_end;
  37. private String trade_state_desc;
  38. }

8、签名实体类 SignInfo.java

  1. /**
  2. * 签名实体类
  3. * @author lf
  4. * @date 2023/9/1
  5. */
  6. @Data
  7. public class SignInfo {
  8. private String appId;//小程序ID
  9. private String timeStamp;//时间戳
  10. private String nonceStr;//随机串
  11. @XStreamAlias("package")
  12. private String repay_id;
  13. private String signType;//签名方式
  14. public String getAppId() {
  15. return appId;
  16. }
  17. public void setAppId(String appId) {
  18. this.appId = appId;
  19. }
  20. public String getTimeStamp() {
  21. return timeStamp;
  22. }
  23. public void setTimeStamp(String timeStamp) {
  24. this.timeStamp = timeStamp;
  25. }
  26. public String getNonceStr() {
  27. return nonceStr;
  28. }
  29. public void setNonceStr(String nonceStr) {
  30. this.nonceStr = nonceStr;
  31. }
  32. public String getRepay_id() {
  33. return repay_id;
  34. }
  35. public void setRepay_id(String repay_id) {
  36. this.repay_id = repay_id;
  37. }
  38. public String getSignType() {
  39. return signType;
  40. }
  41. public void setSignType(String signType) {
  42. this.signType = signType;
  43. }
  44. }

9、Http工具类 HttpRequest.java

  1. /**
  2. * Http工具类
  3. * @author lf
  4. * @date 2023/9/1
  5. */
  6. public class HttpRequest {
  7. //连接超时时间,默认10秒
  8. private static final int socketTimeout = 10000;
  9. //传输超时时间,默认30秒
  10. private static final int connectTimeout = 30000;
  11. /**
  12. * post请求
  13. *
  14. * @throws IOException
  15. * @throws ClientProtocolException
  16. * @throws NoSuchAlgorithmException
  17. * @throws KeyStoreException
  18. * @throws KeyManagementException
  19. * @throws UnrecoverableKeyException
  20. */
  21. public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException {
  22. HttpPost httpPost = new HttpPost(url);
  23. //解决XStream对出现双下划线的bug
  24. XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
  25. xStreamForRequestPostData.alias("xml", xmlObj.getClass());
  26. //将要提交给API的数据对象转换成XML格式数据Post给API
  27. String postDataXML = xStreamForRequestPostData.toXML(xmlObj);
  28. //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
  29. StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
  30. httpPost.addHeader("Content-Type", "text/xml");
  31. httpPost.setEntity(postEntity);
  32. //设置请求器的配置
  33. RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
  34. httpPost.setConfig(requestConfig);
  35. HttpClient httpClient = HttpClients.createDefault();
  36. HttpResponse response = httpClient.execute(httpPost);
  37. HttpEntity entity = response.getEntity();
  38. String result = EntityUtils.toString(entity, "UTF-8");
  39. return result;
  40. }
  41. /**
  42. * 自定义证书管理器,信任所有证书
  43. *
  44. * @author pc
  45. */
  46. public static class MyX509TrustManager implements X509TrustManager {
  47. @Override
  48. public void checkClientTrusted(
  49. java.security.cert.X509Certificate[] arg0, String arg1)
  50. throws CertificateException {
  51. }
  52. @Override
  53. public void checkServerTrusted(
  54. java.security.cert.X509Certificate[] arg0, String arg1)
  55. throws CertificateException {
  56. }
  57. @Override
  58. public java.security.cert.X509Certificate[] getAcceptedIssuers() {
  59. return null;
  60. }
  61. }
  62. }

10、微信签名 SignUtils.java

  1. /**
  2. * 微信签名
  3. * @author lf
  4. * @date 2023/9/1
  5. */
  6. public class SignUtils {
  7. /**
  8. * 签名算法
  9. *
  10. * @param o 要参与签名的数据对象
  11. * @return 签名
  12. * @throws IllegalAccessException
  13. */
  14. public static String getSign(Object o) throws IllegalAccessException {
  15. ArrayList<String> list = new ArrayList<String>();
  16. Class cls = o.getClass();
  17. Field[] fields = cls.getDeclaredFields();
  18. for (Field f : fields) {
  19. f.setAccessible(true);
  20. if (f.get(o) != null && f.get(o) != "") {
  21. String name = f.getName();
  22. XStreamAlias anno = f.getAnnotation(XStreamAlias.class);
  23. if (anno != null) {
  24. name = anno.value();
  25. }
  26. list.add(name + "=" + f.get(o) + "&");
  27. }
  28. }
  29. int size = list.size();
  30. String[] arrayToSort = list.toArray(new String[size]);
  31. Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
  32. StringBuilder sb = new StringBuilder();
  33. for (int i = 0; i < size; i++) {
  34. sb.append(arrayToSort[i]);
  35. }
  36. String result = sb.toString();
  37. result += "key=" + Configure.getKey();
  38. System.out.println("签名数据:" + result);
  39. result = MD5.MD5Encode(result).toUpperCase();
  40. return result;
  41. }
  42. public static String getSign(Map<String, Object> map) {
  43. ArrayList<String> list = new ArrayList<String>();
  44. for (Map.Entry<String, Object> entry : map.entrySet()) {
  45. if (entry.getValue() != "") {
  46. list.add(entry.getKey() + "=" + entry.getValue() + "&");
  47. }
  48. }
  49. int size = list.size();
  50. String[] arrayToSort = list.toArray(new String[size]);
  51. Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
  52. StringBuilder sb = new StringBuilder();
  53. for (int i = 0; i < size; i++) {
  54. sb.append(arrayToSort[i]);
  55. }
  56. String result = sb.toString();
  57. result += "key=" + Configure.getKey();
  58. //Util.log("Sign Before MD5:" + result);
  59. result = MD5.MD5Encode(result).toUpperCase();
  60. //Util.log("Sign Result:" + result);
  61. return result;
  62. }
  63. }

11、MD5加密工具类 MD5.java

  1. import java.security.MessageDigest;
  2. /**
  3. * MD5加密工具类
  4. * @author lf
  5. * @date 2023/9/1
  6. */
  7. public class MD5 {
  8. private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
  9. "8", "9", "a", "b", "c", "d", "e", "f"};
  10. /**
  11. * 转换字节数组为16进制字串
  12. *
  13. * @param b 字节数组
  14. * @return 16进制字串
  15. */
  16. public static String byteArrayToHexString(byte[] b) {
  17. StringBuilder resultSb = new StringBuilder();
  18. for (byte aB : b) {
  19. resultSb.append(byteToHexString(aB));
  20. }
  21. return resultSb.toString();
  22. }
  23. /**
  24. * 转换byte到16进制
  25. *
  26. * @param b 要转换的byte
  27. * @return 16进制格式
  28. */
  29. private static String byteToHexString(byte b) {
  30. int n = b;
  31. if (n < 0) {
  32. n = 256 + n;
  33. }
  34. int d1 = n / 16;
  35. int d2 = n % 16;
  36. return hexDigits[d1] + hexDigits[d2];
  37. }
  38. /**
  39. * MD5编码
  40. *
  41. * @param origin 原始字符串
  42. * @return 经过MD5加密之后的结果
  43. */
  44. public static String MD5Encode(String origin) {
  45. String resultString = null;
  46. try {
  47. resultString = origin;
  48. MessageDigest md = MessageDigest.getInstance("MD5");
  49. resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
  50. } catch (Exception e) {
  51. e.printStackTrace();
  52. }
  53. return resultString;
  54. }
  55. }

12、微信支付配置类 Configure.java

  1. /**
  2. * 微信支付配置类
  3. * @author lf
  4. * @date 2023/9/1
  5. */
  6. public class Configure {
  7. /**
  8. * 商户支付秘钥
  9. */
  10. private static String key = "";
  11. public static String getKey() {
  12. return key;
  13. }
  14. public static void setKey(String key) {
  15. Configure.key = key;
  16. }
  17. }

13、Controller层,PayparameterVO可以不需要,我这里是因为业务需要,处理业务逻辑

  1. /**
  2. * 小程序支付下单接口
  3. * @return 返回结果
  4. */
  5. @ApiOperation("小程序支付功能")
  6. @PostMapping("/pay")
  7. public AjaxResult wxPay(@RequestBody PayParameterVO payParameterVO){
  8. Map payHistory = wxPayInfoService.insertPayRecord(payParameterVO);
  9. return success("success",payHistory);
  10. }
  11. /**
  12. * 查询订单
  13. */
  14. @ApiOperation("订单查询")
  15. @PostMapping("/wx/query")
  16. public AjaxResult orderQuery(@RequestParam("out_trade_no") String out_trade_no) {
  17. Map query = wxPayInfoService.orderQuery(out_trade_no);
  18. return success("success", query);
  19. }

14、业务接口层 WxPayInfoService.java

  1. import com.ruoyi.ai.doamin.PayParameterVO;
  2. import java.util.Map;
  3. /**
  4. * 微信小程序支付-业务接口层
  5. * @author lf
  6. * @date 2023/8/31
  7. */
  8. public interface WxPayInfoService {
  9. /**
  10. * 插入订单记录
  11. */
  12. Map insertPayRecord(PayParameterVO payParameterVO);
  13. /**
  14. * 查询订单
  15. * @param out_trade_no 订单号
  16. * @return 返回结果
  17. */
  18. Map orderQuery(String out_trade_no);
  19. }

15、业务接口实现层 WxPayInfoServiceImpl.java

  1. import cn.hutool.core.util.ObjectUtil;
  2. import com.github.wxpay.sdk.WXPayConstants;
  3. import com.github.wxpay.sdk.WXPayUtil;
  4. import com.ruoyi.ai.doamin.PayParameterVO;
  5. import com.ruoyi.ai.service.WxPayInfoService;
  6. import com.ruoyi.common.config.WxPayConfig;
  7. import com.ruoyi.common.constant.WeChatPayUrlConstants;
  8. import com.ruoyi.common.core.domain.pay.WeChatPay;
  9. import com.ruoyi.common.utils.pay.HttpRequest;
  10. import com.ruoyi.common.utils.pay.SignUtils;
  11. import com.ruoyi.common.utils.pay.entity.OrderReturnInfo;
  12. import com.ruoyi.common.utils.pay.entity.QueryReturnInfo;
  13. import com.ruoyi.common.utils.pay.entity.SignInfo;
  14. import com.thoughtworks.xstream.XStream;
  15. import lombok.extern.slf4j.Slf4j;
  16. import org.springframework.stereotype.Service;
  17. import org.springframework.transaction.annotation.Transactional;
  18. import javax.annotation.Resource;
  19. import java.math.BigDecimal;
  20. import java.text.DecimalFormat;
  21. import java.util.*;
  22. /**
  23. * 微信小程序支付-业务接口实现层
  24. * @author lf
  25. * @date 2023/8/31
  26. */
  27. @Service
  28. @Slf4j
  29. public class WxPayInfoServiceImpl implements WxPayInfoService {
  30. @Resource
  31. private WxPayConfig payProperties;
  32. private static final DecimalFormat df = new DecimalFormat("#");
  33. /**
  34. * 插入订单记录
  35. * @param payParameterVO 用户ID 会员套餐ID
  36. * @return 返回结果
  37. */
  38. @Override
  39. @Transactional
  40. public Map insertPayRecord(PayParameterVO payParameterVO) {
  41. //接收返回的参数
  42. Map<String, Object> map = new HashMap<>();
  43. String title = "koko测试点数";
  44. //金额 * 100 以分为单位
  45. BigDecimal fee = BigDecimal.valueOf(1);
  46. BigDecimal RMB = new BigDecimal(100);
  47. BigDecimal totalFee = fee.multiply(RMB);
  48. try {
  49. WeChatPay weChatPay = new WeChatPay();
  50. weChatPay.setAppid(payProperties.getAppId());
  51. weChatPay.setMch_id(payProperties.getMchId());
  52. weChatPay.setNonce_str(getRandomStringByLength(32));
  53. weChatPay.setBody(title);
  54. weChatPay.setOut_trade_no(getRandomStringByLength(32));
  55. weChatPay.setTotal_fee( df.format(Double.parseDouble(String.valueOf(totalFee))));
  56. weChatPay.setSpbill_create_ip("127.0.0.1");
  57. weChatPay.setNotify_url(payProperties.getNotifyUrl());
  58. weChatPay.setTrade_type("JSAPI");
  59. //这里直接使用当前用户的openid
  60. weChatPay.setOpenid("oOKq*******xj8o");
  61. weChatPay.setSign_type("MD5");
  62. //生成签名
  63. String sign = SignUtils.getSign(weChatPay);
  64. weChatPay.setSign(sign);
  65. String result = HttpRequest.sendPost(WeChatPayUrlConstants.Uifiedorder, weChatPay);
  66. System.out.println(result);
  67. //将返回结果从xml格式转换为map格式
  68. Map<String, String> wxResultMap = WXPayUtil.xmlToMap(result);
  69. if (ObjectUtil.isNotEmpty(wxResultMap.get("return_code")) && wxResultMap.get("return_code").equals("SUCCESS")){
  70. if (wxResultMap.get("result_code").equals("FAIL")){
  71. map.put("msg", "统一下单失败");
  72. map.put("status",500);
  73. map.put("data", wxResultMap.get("err_code_des"));
  74. return map;
  75. }
  76. }
  77. XStream xStream = new XStream();
  78. xStream.alias("xml", OrderReturnInfo.class);
  79. OrderReturnInfo returnInfo = (OrderReturnInfo) xStream.fromXML(result);
  80. // 二次签名
  81. if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) {
  82. SignInfo signInfo = new SignInfo();
  83. signInfo.setAppId(payProperties.getAppId());
  84. long time = System.currentTimeMillis() / 1000;
  85. signInfo.setTimeStamp(String.valueOf(time));
  86. signInfo.setNonceStr(WXPayUtil.generateNonceStr());
  87. signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id());
  88. signInfo.setSignType("MD5");
  89. //生成签名
  90. String sign1 = SignUtils.getSign(signInfo);
  91. Map<String, String> payInfo = new HashMap<>();
  92. payInfo.put("timeStamp", signInfo.getTimeStamp());
  93. payInfo.put("nonceStr", signInfo.getNonceStr());
  94. payInfo.put("package", signInfo.getRepay_id());
  95. payInfo.put("signType", signInfo.getSignType());
  96. payInfo.put("paySign", sign1);
  97. map.put("status", 200);
  98. map.put("msg", "统一下单成功!");
  99. map.put("data", payInfo);
  100. //预下单成功,处理业务逻辑
  101. //****************************//
  102. // 业务逻辑结束 回传给小程序端唤起支付
  103. return map;
  104. }
  105. map.put("status", 500);
  106. map.put("msg", "统一下单失败!");
  107. map.put("data", null);
  108. return map;
  109. } catch (Exception e) {
  110. e.printStackTrace();
  111. }
  112. return null;
  113. }
  114. /**
  115. * 查询订单
  116. * @param out_trade_no 订单号
  117. * @return 返回结果
  118. */
  119. @Override
  120. public Map orderQuery(String out_trade_no){
  121. Map<String, Object> map = new HashMap<>();
  122. try {
  123. WeChatPay weChatPay = new WeChatPay();
  124. weChatPay.setAppid(payProperties.getAppId());
  125. weChatPay.setMch_id(payProperties.getMchId());
  126. weChatPay.setNonce_str(WXPayUtil.generateNonceStr());
  127. weChatPay.setOut_trade_no(out_trade_no);
  128. //order.setSign_type("MD5");
  129. //生成签名
  130. String sign = SignUtils.getSign(weChatPay);
  131. weChatPay.setSign(sign);
  132. String result = HttpRequest.sendPost(WXPayConstants.ORDERQUERY_URL, weChatPay);
  133. System.out.println(result);
  134. XStream xStream = new XStream();
  135. xStream.alias("xml", QueryReturnInfo.class);
  136. QueryReturnInfo returnInfo = (QueryReturnInfo) xStream.fromXML(result);
  137. map.put("status", 500);
  138. map.put("msg", "统一下单失败!");
  139. map.put("data", returnInfo);
  140. return map;
  141. } catch (Exception e) {
  142. e.printStackTrace();
  143. }
  144. return null;
  145. }
  146. /**
  147. * 获取一定长度的随机字符串
  148. *
  149. * @param length 指定字符串长度
  150. * @return 一定长度的字符串
  151. */
  152. public static String getRandomStringByLength(int length) {
  153. String base = "abcdefghijklmnopqrstuvwxyz0123456789";
  154. Random random = new Random();
  155. StringBuffer sb = new StringBuffer();
  156. for (int i = 0; i < length; i++) {
  157. int number = random.nextInt(base.length());
  158. sb.append(base.charAt(number));
  159. }
  160. return sb.toString();
  161. }
  162. }

3.2、支付支付回调

  1. /**
  2. * 微信小程序支付成功回调
  3. * @param request 请求
  4. * @param response 响应
  5. * @return 返回结果
  6. * @throws Exception 异常处理
  7. */
  8. @RequestMapping("/weixin/callback")
  9. public String callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
  10. System.out.println("接口已被调用");
  11. ServletInputStream inputStream = request.getInputStream();
  12. String notifyXml = StreamUtils.inputStream2String(inputStream, "utf-8");
  13. System.out.println(notifyXml);
  14. // 解析返回结果
  15. Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyXml);
  16. // 判断支付是否成功
  17. if ("SUCCESS".equals(notifyMap.get("result_code"))) {
  18. //支付成功时候,处理业务逻辑
  19. System.out.println("支付成功");
  20. System.out.println("<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
  21. + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ");
  22. /**
  23. * 注意
  24. * 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了
  25. * return返回的结果一定是这种格式,当result_code返回的结果是SUCCESS时,则不进行调用了
  26. * 如果不返回下面的格式,业务逻辑会出现回调多次的情况,我就遇到过这种情况。
  27. */
  28. return "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
  29. + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
  30. }
  31. // 创建响应对象:微信接收到校验失败的结果后,会反复的调用当前回调函数
  32. Map<String, String> returnMap = new HashMap<>();
  33. returnMap.put("return_code", "FAIL");
  34. returnMap.put("return_msg", "");
  35. String returnXml = WXPayUtil.mapToXml(returnMap);
  36. response.setContentType("text/xml");
  37. System.out.println("校验失败");
  38. return returnXml;
  39. }

注:如果能正常走预支付的接口,而没有处理回调接口的业务逻辑。

1、返回的xml格式是否正确,V2会比对xml返回的数据格式是否一致

2、回调的接口是否允许外网访问,并且接口不能携带任何参数,如果框架存在token验证时,则需要关闭支付回调的token验证。

3、如果以上都没问题,却还是没走回调,可以看看这篇文档【接收不到回调排查指引

3.3、问题排查

1、在与前端联调时,如果出现如下问题,看看预下单的packeage参数是否正确,必须严格按照如下格式:

package: "prepay_id=wx*********0000";

2、在与前端联调时,如果出现如下问题,看看签名的工具类是否能够验证通过【签名校验

3.4、统一下单和订单查询

1、统一下单

2、订单查询-支付成功

3、订单查询-支付失败

 4、预下单成功并且支付成功

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/169282?site
推荐阅读
相关标签
  

闽ICP备14008679号