微信支付功能的计划实现与关键实践(UniApp+Java)全代码 ...

打印 上一主题 下一主题

主题 1999|帖子 1999|积分 6001

微信支付功能的计划实现与关键实践(UniApp+Java)全代码

感觉本篇对你有资助可以关注一下我的微信公众号(深入浅出谈java),会不定期更新知识和面试资料、本领!!!

概述

在移动互联网时代,支付功能已成为应用开辟的焦点能力之一。本文将以 UniApp前端+Java后端技术栈为例,体系解析微信支付功能的计划实现与关键实践,为开辟者提供从技术架构到安全防护的全景视角。
微信支付功能是跨平台应用(UniApp前端 + Java后端)与微信支付体系对接的焦点模块,实现用户从下单到支付完成的闭环流程。支持多种支付场景(如APP支付、小步伐支付、H5支付),确保生意业务安全、实时性和数据一致性
对于支付体系,公司一般会对其举行独立,确保服务的安全、可靠、稳固。因此支付体系是现代互联网的关键焦点体系。本篇实现基本功能,提供思绪,部门代码不严谨,可以自行优化
微信支付流程图

大致如下:流程图中和流程步骤 描述的2、3 步举行了调换,不受影响,但是发起可以先创建订单

流程步骤

1、用户提交订单请求


  • 行为主体:用户(通过UniApp前端操作)
  • 动作描述:用户在UniApp中选择商品或服务,点击支付按钮,前端将订单信息(如商品ID、数目、金额等)发送至Java后端。
2、生成业务订单


  • 行为主体:Java后端
  • 动作描述

    • 后端接收订单请求后,验证数据合法性(如金额、商品库存等)。
    • 在数据库中生成唯一业务订单号(out_trade_no),并记录订单状态为「待支付」

3、调用微信统一下单API


  • 行为主体:Java后端 → 微信支付平台
  • 动作描述

    • 后端构造统一下单请求参数(包罗商户号、订单号、金额、回调地址等)。
    • 使用商户API密钥生成签名(保障请求安全性)。
    • 向微信支付平台发送HTTP请求,调用统一下单接口(URL: https://api.mch.weixin.qq.com/pay/unifiedorder)。

4、接收预支付生意业务单响应


  • 行为主体:微信支付平台 → Java后端
  • 动作描述

    • 微信验证请求参数和签名,确认无误后生成预支付生意业务单。
    • 返回XML格式响应数据,包含关键字段:

      • prepay_id(预支付生意业务标识,用于后续支付)
      • return_code(通讯状态码,如SUCCESS/FAIL)
      • result_code(业务结果码,如SUCCESS/FAIL)


5、返回支付参数至前端


  • 行为主体:Java后端 → UniApp前端
  • 动作描述

    • 后端解析微信返回的prepay_id,重新构造前端支付参数(需二次签名)。
    • 返回JSON数据给UniApp,包含:

      • appId(微信应用ID)
      • timeStamp(时间戳)
      • nonceStr(随机字符串)
      • package(固定值Sign=WXPay)
      • signType(签名类型,通常为MD5或HMAC-SHA256)
      • paySign(最终支付签名)


6、调起微信支付界面


  • 行为主体:用户(UniApp前端) → 微信客户端
  • 动作描述

    • UniApp通过uni.requestPayment API,传入后端返回的支付参数。
    • 微信客户端(APP/小步伐)根据参数拉起支付界面,用户确认金额并输入密码。

7、用户完成支付


  • 行为主体:微信支付平台 → 用户
  • 动作描述

    • 微信验证支付密码和账户余额,扣款成功后,向用户展示支付结果页面(成功/失败)。

8、异步通知支付结果


  • 行为主体:微信支付平台 → Java后端
  • 动作描述

    • 微信通过POST请求调用后端预设的notify_url(需公网可访问)。
    • 推送XML格式回调数据,包含:

      • out_trade_no(商户订单号)
      • transaction_id(微信支付单号)
      • total_fee(实际支付金额)
      • result_code(支付结果,如SUCCESS/FAIL)


9、处理回调并响应微信


  • 行为主体:Java后端 → 微信支付平台
  • 动作描述

    • 后端接收回调数据后:

      • 验证签名防止伪造请求
      • 检查订单金额与业务体系是否一致
      • 更新订单状态为「已支付」(需做幂等处理,制止重复更新)

    • 返回XML响应(必须包含),告知微信已正确处理。

10、通知前端最闭幕果


  • 行为主体:Java后端 → UniApp前端
  • 动作描述

    • 若前端未实时感知支付结果(如用户关闭页面),可通过两种方式同步:

      • 轮询查询:前端定期请求后端订单状态接口
      • WebSocket推送:后端主动推送支付结果

    • 更新前端界面显示支付成功/失败状态。

账号预备工作

申请微信小步伐账号

1、开辟小步伐的第一步,你需要拥有一个小步伐账号,因此先申请小步伐账号
小步伐注册地址:小步伐

2、信息填好,举行注册,就会产生AppID、AppSecret

小步伐的 AppID 相称于小步伐平台的一个身份证,后续你会在很多地方要用到 AppID (注意这里要区别于服务号或订阅号的 AppID)
微信支付 官网开通商户支付能力

微信支付官网:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式
1、找到接入微信支付,举行绑定注册
注册需要营业执照、法人信息,按照要求填写即可

2、注册微信支付商户号

3、填写须要信息举行注册

4、申请证书和AIPv3秘钥,v2现在有镌汰趋势,不需要申请v2,直接v3走起。

5、下载和保存好 秘钥及证书(PS:一定要好好保存)

6、获取商户号

接入实现

服务端代码

1、导入maven依赖
  1.         
  2.         <dependency>
  3.             <groupId>com.github.wxpay</groupId>
  4.             <artifactId>wxpay-sdk</artifactId>
  5.             <version>0.0.3</version>
  6.         </dependency>
  7.         <dependency>
  8.             <groupId>com.thoughtworks.xstream</groupId>
  9.             <artifactId>xstream</artifactId>
  10.             <version>1.4.20</version>
  11.             <scope>compile</scope>
  12.         </dependency>
  13. <dependency>
  14.     <groupId>com.github.wechatpay-apiv3</groupId>
  15.     <artifactId>wechatpay-apache-httpclient</artifactId>
  16.     <version>0.4.7</version>
  17. </dependency>
复制代码
2、设置商户信息
  1. # 微信支付配置
  2. pay:
  3.   appId: xxx #应用id
  4.   mchId: xxx #商户id
  5.   notifyUrl: https://服务器ip或对应域名/wxpay/weixin/callback #支付回调地址
复制代码
3、实体类代码

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

SignUtils:签名工具类
  1. @Slf4j
  2. public class SignUtils {
  3.         /**
  4.          * 签名算法
  5.          *
  6.          * @param o 要参与签名的数据对象
  7.          * @return 签名
  8.          * @throws IllegalAccessException
  9.          */
  10.         public static String getSign(Object o) throws IllegalAccessException {
  11.             ArrayList<String> list = new ArrayList<String>();
  12.             Class cls = o.getClass();
  13.             Field[] fields = cls.getDeclaredFields();
  14.             for (Field f : fields) {
  15.                 f.setAccessible(true);
  16.                 if (f.get(o) != null && f.get(o) != "") {
  17.                     String name = f.getName();
  18.                     XStreamAlias anno = f.getAnnotation(XStreamAlias.class);
  19.                     if (anno != null) {
  20.                         name = anno.value();
  21.                     }
  22.                     list.add(name + "=" + f.get(o) + "&");
  23.                 }
  24.             }
  25.             int size = list.size();
  26.             String[] arrayToSort = list.toArray(new String[size]);
  27.             Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
  28.             StringBuilder sb = new StringBuilder();
  29.             for (int i = 0; i < size; i++) {
  30.                 sb.append(arrayToSort[i]);
  31.             }
  32.             String result = sb.toString();
  33.             result += "key=" + Configure.getKey();
  34.             log.info("签名数据:" + result);
  35.             result = MD5.MD5Encode(result).toUpperCase();
  36.             return result;
  37.         }
  38.         public static String getSign(Map<String, Object> map) {
  39.             ArrayList<String> list = new ArrayList<String>();
  40.             for (Map.Entry<String, Object> entry : map.entrySet()) {
  41.                 if (entry.getValue() != "") {
  42.                     list.add(entry.getKey() + "=" + entry.getValue() + "&");
  43.                 }
  44.             }
  45.             int size = list.size();
  46.             String[] arrayToSort = list.toArray(new String[size]);
  47.             Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
  48.             StringBuilder sb = new StringBuilder();
  49.             for (int i = 0; i < size; i++) {
  50.                 sb.append(arrayToSort[i]);
  51.             }
  52.             String result = sb.toString();
  53.             result += "key=" + Configure.getKey();
  54.             //Util.log("Sign Before MD5:" + result);
  55.             result = MD5.MD5Encode(result).toUpperCase();
  56.             //Util.log("Sign Result:" + result);
  57.             return result;
  58.         }
  59.     }
复制代码
MD5:MD5 加密工具类
  1. public class MD5 {
  2.     private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
  3.             "8", "9", "a", "b", "c", "d", "e", "f"};
  4.     /**
  5.      * 转换字节数组为16进制字串
  6.      *
  7.      * @param b 字节数组
  8.      * @return 16进制字串
  9.      */
  10.     public static String byteArrayToHexString(byte[] b) {
  11.         StringBuilder resultSb = new StringBuilder();
  12.         for (byte aB : b) {
  13.             resultSb.append(byteToHexString(aB));
  14.         }
  15.         return resultSb.toString();
  16.     }
  17.     /**
  18.      * 转换byte到16进制
  19.      *
  20.      * @param b 要转换的byte
  21.      * @return 16进制格式
  22.      */
  23.     private static String byteToHexString(byte b) {
  24.         int n = b;
  25.         if (n < 0) {
  26.             n = 256 + n;
  27.         }
  28.         int d1 = n / 16;
  29.         int d2 = n % 16;
  30.         return hexDigits[d1] + hexDigits[d2];
  31.     }
  32.     /**
  33.      * MD5编码
  34.      *
  35.      * @param origin 原始字符串
  36.      * @return 经过MD5加密之后的结果
  37.      */
  38.     public static String MD5Encode(String origin) {
  39.         String resultString = null;
  40.         try {
  41.             resultString = origin;
  42.             MessageDigest md = MessageDigest.getInstance("MD5");
  43.             resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
  44.         } catch (Exception e) {
  45.             e.printStackTrace();
  46.         }
  47.         return resultString;
  48.     }
  49. }
复制代码
MapToObject:map 转化对象工具类
  1. public class MapToObject {
  2.     /**
  3.      * Map数据转为java对象
  4.      * @param map map数据
  5.      * @param targetType 对象类型
  6.      * @return
  7.      * @param <T>
  8.      * @throws IllegalAccessException
  9.      * @throws InstantiationException
  10.      */
  11.     public static <T> T convertMapToObject(Map<String, String> map, Class<T> targetType) throws IllegalAccessException, InstantiationException {
  12.         T targetObject = targetType.newInstance();
  13.         for (Map.Entry<String, String> entry : map.entrySet()) {
  14.             String key = entry.getKey();
  15.             String value = entry.getValue();
  16.             try {
  17.                 // 使用反射获取字段
  18.                 Field field = targetType.getDeclaredField(key);
  19.                 // 设置字段可访问(如果是私有字段)
  20.                 field.setAccessible(true);
  21.                 // 获取字段的类型
  22.                 Class<?> fieldType = field.getType();
  23.                 // 将字符串值转换为字段类型
  24.                 Object convertedValue = convertStringToType(value, fieldType);
  25.                 // 设置字段的值
  26.                 field.set(targetObject, convertedValue);
  27.             } catch (NoSuchFieldException e) {
  28.                 // 处理字段不存在的异常
  29.                 e.printStackTrace();
  30.             }
  31.         }
  32.         return targetObject;
  33.     }
  34.     private static Object convertStringToType(String value, Class<?> targetType) {
  35.         if (targetType == int.class || targetType == Integer.class) {
  36.             return Integer.parseInt(value);
  37.         }
  38.         // 添加其他可能的类型转换逻辑,例如 double、float、Date 等
  39.         // 默认情况下,返回字符串值
  40.         return value;
  41.     }
  42. }
复制代码
HttpRequest:请求工具类
  1. public class HttpRequest {
  2.     //连接超时时间,默认10秒
  3.     private static final int socketTimeout = 10000;
  4.     //传输超时时间,默认30秒
  5.     private static final int connectTimeout = 30000;
  6.     /**
  7.      * post请求
  8.      *
  9.      * @throws IOException
  10.      * @throws ClientProtocolException
  11.      * @throws NoSuchAlgorithmException
  12.      * @throws KeyStoreException
  13.      * @throws KeyManagementException
  14.      * @throws UnrecoverableKeyException
  15.      */
  16.     public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException {
  17.         HttpPost httpPost = new HttpPost(url);
  18.         //解决XStream对出现双下划线的bug
  19.         XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
  20.         xStreamForRequestPostData.alias("xml", xmlObj.getClass());
  21.         //将要提交给API的数据对象转换成XML格式数据Post给API
  22.         String postDataXML = xStreamForRequestPostData.toXML(xmlObj);
  23.         //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
  24.         StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
  25.         httpPost.addHeader("Content-Type", "text/xml");
  26.         httpPost.setEntity(postEntity);
  27.         //设置请求器的配置
  28.         RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
  29.         httpPost.setConfig(requestConfig);
  30.         HttpClient httpClient = HttpClients.createDefault();
  31.         HttpResponse response = httpClient.execute(httpPost);
  32.         HttpEntity entity = response.getEntity();
  33.         String result = EntityUtils.toString(entity, "UTF-8");
  34.         return result;
  35.     }
  36.     /**
  37.      * 自定义证书管理器,信任所有证书
  38.      *
  39.      * @author pc
  40.      */
  41.     public static class MyX509TrustManager implements X509TrustManager {
  42.         @Override
  43.         public void checkClientTrusted(
  44.                 java.security.cert.X509Certificate[] arg0, String arg1)
  45.                 throws CertificateException {
  46.         }
  47.         @Override
  48.         public void checkServerTrusted(
  49.                 java.security.cert.X509Certificate[] arg0, String arg1)
  50.                 throws CertificateException {
  51.         }
  52.         @Override
  53.         public java.security.cert.X509Certificate[] getAcceptedIssuers() {
  54.             return null;
  55.         }
  56.     }
  57. }
复制代码
5、设置类代码

WxPayConfig:微信支付设置
  1. @Data
  2. @Component
  3. @Configuration
  4. @ConfigurationProperties(prefix = "pay")
  5. public class WxPayConfig {
  6.     /**
  7.      * 微信小程序appid
  8.      */
  9.     private String appId;
  10.     /**
  11.      * 小程序设置的API v2密钥
  12.      */
  13.     private String apiKey;
  14.     /**
  15.      * 微信商户平台 商户id
  16.      */
  17.     private String mchId;
  18.     /**
  19.      *小程序密钥
  20.      */
  21.     private String appSecret;
  22.     /**
  23.      * 小程序支付异步回调地址
  24.      */
  25.     private String notifyUrl;
  26. }
复制代码
Configure:商户支付秘钥
  1. public class Configure {
  2.     /**
  3.      * 商户支付秘钥
  4.      */
  5.     @Getter
  6.     private static String key = "此处填写秘钥";
  7.     public static void setKey(String key) {
  8.         Configure.key = key;
  9.     }
  10. }
复制代码
6、常量类代码

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

Controller 层
  1. @Slf4j
  2. @RestController
  3. @RequestMapping("/system/wxpay")
  4. public class WxPayController {
  5.     @Autowired
  6.     private WxPayInfoService wxPayInfoService;
  7.     /**
  8.      * 小程序支付下单接口
  9.      *
  10.      * @return 返回结果
  11.      */
  12.     @ApiOperation("小程序支付功能")
  13.     @PostMapping("/pay")
  14.     public InvokeResult wxPay(@RequestBody PayParameterVO payParameterVO) {
  15.         PayParameterVO parameterVO = new PayParameterVO();
  16.         parameterVO.setWxOpenId(payParameterVO.getWxOpenId());
  17.         parameterVO.setPrice("1");
  18.         parameterVO.setGoodsTitle("测试支付商品");
  19.         HashMap<String, String> payHistory = wxPayInfoService.insertPayRecord(parameterVO);
  20.         return InvokeResultBuilder.success(payHistory);
  21.     }
  22.     /**
  23.      * 查询订单
  24.      */
  25.     @ApiOperation("订单查询")
  26.     @PostMapping("/wx/query")
  27.     public InvokeResult orderQuery(@RequestParam("out_trade_no") String out_trade_no) {
  28.         QueryReturnInfo queryReturnInfo = wxPayInfoService.orderQuery(out_trade_no);
  29. //        return InvokeResultBuilder.success(queryReturnInfo.getTrade_state_desc(), queryReturnInfo);
  30.         return InvokeResultBuilder.success(queryReturnInfo);
  31.     }
  32.     /**
  33.      * 微信小程序支付成功回调
  34.      *
  35.      * @param request  请求
  36.      * @param response 响应
  37.      * @return 返回结果
  38.      * @throws Exception 异常处理
  39.      */
  40.     @RequestMapping("/weixin/callback")
  41.     public String callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
  42.         log.info("接收到微信支付回调信息");
  43.         String notifyXml = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
  44.         // 解析返回结果
  45.         Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyXml);
  46.         // 判断支付是否成功
  47.         if ("SUCCESS".equals(notifyMap.get("result_code"))) {
  48.             //支付成功时候,处理业务逻辑
  49.             wxPayInfoService.payCallbackSuccess(notifyMap);
  50.             //返回处理成功的格式数据,避免微信重复回调
  51.             return "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
  52.                     + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
  53.         }
  54.         // 创建响应对象:微信接收到校验失败的结果后,会反复的调用当前回调函数
  55.         Map<String, String> returnMap = new HashMap<>();
  56.         returnMap.put("return_code", "FAIL");
  57.         returnMap.put("return_msg", "");
  58.         String returnXml = WXPayUtil.mapToXml(returnMap);
  59.         response.setContentType("text/xml");
  60.         System.out.println("校验失败");
  61.         return returnXml;
  62.     }
  63. }
复制代码
接口层
  1. public interface  WxPayInfoService {
  2.     /**
  3.      * 创建统一支付订单
  4.      */
  5.     HashMap<String, String> insertPayRecord(PayParameterVO payParameterVO);
  6.     /**
  7.      * 查询订单
  8.      * @param out_trade_no 订单号
  9.      * @return 返回结果
  10.      */
  11.     QueryReturnInfo orderQuery(String out_trade_no);
  12.     /**
  13.      * 微信小程序支付成功回调
  14.      * @param notifyMap
  15.      */
  16.     void payCallbackSuccess(Map<String, String> notifyMap);
  17. }
复制代码
实现层
  1. @Slf4j
  2. @Service
  3. public class WxPayInfoServiceImpl implements WxPayInfoService {
  4.     //这是注入的业务处理类
  5. //    @Autowired
  6. //    private IFPayOrderService ifPayOrderService;
  7.     @Resource
  8.     private WxPayConfig payProperties;
  9.     private static final DecimalFormat df = new DecimalFormat("#");
  10.     /**
  11.      * 创建统一支付订单
  12.      *
  13.      * @param payParameterVO 商品信息
  14.      * @return 返回结果
  15.      */
  16.     @Override
  17.     @Transactional
  18.     public HashMap<String, String> insertPayRecord(PayParameterVO payParameterVO) {
  19.         String title = payParameterVO.getGoodsTitle();
  20.         //金额 * 100 以分为单位
  21.         BigDecimal fee = BigDecimal.valueOf(1);
  22.         BigDecimal RMB = new BigDecimal(payParameterVO.getPrice());
  23.         BigDecimal totalFee = fee.multiply(RMB);
  24.         try {
  25.             WeChatPay weChatPay = new WeChatPay();
  26.             weChatPay.setAppid(payProperties.getAppId());
  27.             weChatPay.setMch_id(payProperties.getMchId());
  28.             weChatPay.setNonce_str(getRandomStringByLength(32));
  29.             weChatPay.setBody(title);
  30.             weChatPay.setOut_trade_no(getRandomStringByLength(32));
  31.             weChatPay.setTotal_fee(df.format(Double.parseDouble(String.valueOf(totalFee))));
  32.             // 获取当前服务器IP地址
  33. //            weChatPay.setSpbill_create_ip(IpUtils());
  34.             weChatPay.setNotify_url(payProperties.getNotifyUrl());
  35.             weChatPay.setTrade_type("JSAPI");
  36.             //这里直接使用当前用户的openid
  37.             weChatPay.setOpenid(payParameterVO.getWxOpenId());
  38.             weChatPay.setSign_type("MD5");
  39.             //生成签名
  40.             String sign = SignUtils.getSign(weChatPay);
  41.             weChatPay.setSign(sign);
  42.             log.info("订单号:" + weChatPay.getOut_trade_no());
  43.             //向微信发送下单请求
  44.             String result = HttpRequest.sendPost(WeChatPayUrlConstants.Uifiedorder, weChatPay);
  45.             //将返回结果从xml格式转换为map格式
  46.             Map<String, String> wxResultMap = WXPayUtil.xmlToMap(result);
  47.             if (StringUtils.isNotEmpty(wxResultMap.get("return_code")) && wxResultMap.get("return_code").equals("SUCCESS")) {
  48.                 if (wxResultMap.get("result_code").equals("FAIL")) {
  49.                     log.error("微信统一下单失败!");
  50.                     return null;
  51.                 }
  52.             }
  53.             OrderReturnInfo returnInfo = MapToObject.convertMapToObject(wxResultMap, OrderReturnInfo.class);
  54.             // 二次签名
  55.             if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) {
  56.                 SignInfo signInfo = new SignInfo();
  57.                 signInfo.setAppId(payProperties.getAppId());
  58.                 long time = System.currentTimeMillis() / 1000;
  59.                 signInfo.setTimeStamp(String.valueOf(time));
  60.                 signInfo.setNonceStr(WXPayUtil.generateNonceStr());
  61.                 signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id());
  62.                 signInfo.setSignType("MD5");
  63.                 //生成签名
  64.                 String sign1 = SignUtils.getSign(signInfo);
  65.                 HashMap<String, String> payInfo = new HashMap<>();
  66.                 payInfo.put("timeStamp", signInfo.getTimeStamp());
  67.                 payInfo.put("nonceStr", signInfo.getNonceStr());
  68.                 payInfo.put("package", signInfo.getRepay_id());
  69.                 payInfo.put("signType", signInfo.getSignType());
  70.                 payInfo.put("paySign", sign1);
  71.                 payInfo.put("placeOrderJsonMsg", JSON.toJSONString(weChatPay));
  72.                 payInfo.put("orderNum", weChatPay.getOut_trade_no());
  73.                 // 业务逻辑结束 回传给小程序端唤起支付
  74.                 return payInfo;
  75.             }
  76.             return null;
  77.         } catch (Exception e) {
  78.             log.error(e.getMessage());
  79.         }
  80.         return null;
  81.     }
  82.     /**
  83.      * 查询订单
  84.      *
  85.      * @param out_trade_no 订单号
  86.      * @return 返回结果
  87.      */
  88.     @Override
  89.     public QueryReturnInfo orderQuery(String out_trade_no) {
  90.         try {
  91.             WeChatPay weChatPay = new WeChatPay();
  92.             weChatPay.setAppid(payProperties.getAppId());
  93.             weChatPay.setMch_id(payProperties.getMchId());
  94.             weChatPay.setNonce_str(WXPayUtil.generateNonceStr());
  95.             weChatPay.setOut_trade_no(out_trade_no);
  96.             //order.setSign_type("MD5");
  97.             //生成签名
  98.             String sign = SignUtils.getSign(weChatPay);
  99.             weChatPay.setSign(sign);
  100.             //向微信发送查询订单详情请求
  101.             String result = HttpRequest.sendPost(WXPayConstants.ORDERQUERY_URL, weChatPay);
  102.             Map<String, String> xmlToMap = WXPayUtil.xmlToMap(result);
  103.             // 将 Map 转换为对象
  104.             return MapToObject.convertMapToObject(xmlToMap, QueryReturnInfo.class);
  105.         } catch (Exception e) {
  106.             log.error("查询支付订单失败:[{}]", e.getMessage());
  107.         }
  108.         return null;
  109.     }
  110.     /**
  111.      * 微信小程序支付成功回调
  112.      *
  113.      * @param notifyMap 回调Map数据
  114.      */
  115.     @Override
  116.     public void payCallbackSuccess(Map<String, String> notifyMap) {
  117.         //保存相关支付数据
  118.         try {
  119.             QueryReturnInfo queryReturnInfo = MapToObject.convertMapToObject(notifyMap, QueryReturnInfo.class);
  120.             log.info("支付回调信息:" + queryReturnInfo);
  121.             //处理回调信息,此处根据自己的项目业务进行处理
  122. //            ifPayOrderService.receivePayCallback(queryReturnInfo);
  123.         } catch (Exception e) {
  124.             throw new RuntimeException(e);
  125.         }
  126.     }
  127.     /**
  128.      * 获取一定长度的随机字符串
  129.      *
  130.      * @param length 指定字符串长度
  131.      * @return 一定长度的字符串
  132.      */
  133.     public static String getRandomStringByLength(int length) {
  134.         String base = "abcdefghijklmnopqrstuvwxyz0123456789";
  135.         Random random = new Random();
  136.         StringBuilder sb = new StringBuilder();
  137.         for (int i = 0; i < length; i++) {
  138.             int number = random.nextInt(base.length());
  139.             sb.append(base.charAt(number));
  140.         }
  141.         return sb.toString();
  142.     }
  143. }
复制代码
前端焦点代码

支付方法
  1. goPay () {
  2.       console.log('goPay');
  3.       const that = this
  4.       //向服务器发送下单请求(服务端会返回预下单信息)
  5.       uni.request({
  6.         url: common.api_base_url + "/system/wxpay/pay",
  7.         header: {
  8.           "Content-Type": "application/json",
  9.           "Authorization": "Bearer " + uni.getStorageSync('token'),
  10.         },
  11.         method: 'POST',
  12.         data: {
  13.           goodsId: that.nowGoodsId,
  14.         },
  15.         success(res) {
  16.           console.log("下单信息结果",res)
  17.           if (res.data.code == 200){
  18.             //成功,调用微信支付接口进行支付()
  19.             uni.requestPayment({
  20.               provider: 'wxpay',
  21.               timeStamp: res.data.data.timeStamp,
  22.               nonceStr:  res.data.data.nonceStr,
  23.               package:  res.data.data.package,
  24.               signType: res.data.data.signType,
  25.               paySign:  res.data.data.paySign,
  26.               // appId: app.globalData.appid,
  27.               success: function (ress) {
  28.                 console.log("支付完成:",ress)
  29.                 //支付成功后,查询订单情况,或者2 秒自动跳转到其他页面
  30.                 uni.showToast({
  31.                   title: '支付成功',
  32.                   duration: 2000
  33.                 });
  34.               },
  35.               fail: function (err) {
  36.                 uni.showToast({
  37.                   title: '支付失败!',err,
  38.                   icon:"none",
  39.                   duration: 2000
  40.                 });
  41.               }
  42.             });
  43.           }else {
  44.             //弹出下单失败的提示
  45.             uni.showToast({
  46.               title:res.data.msg,
  47.               icon:"none"
  48.             });
  49.           }
  50.         }
  51.       })
  52.     },
复制代码
效果图

前端传递参数,后端生成预订单,返回前端,前端唤醒支付页面,随后支付即可,再调用订单状态查询接口,对差别状态的订单举行自己的业务逻辑判断。

最后文章有啥不对,欢迎大佬在评论区辅导!!!
如果感觉对你有资助就点赞推荐或者关注一下吧!!!
****

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王國慶

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表