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

                
                
                    com.github.wxpay
                    wxpay-sdk
                    0.0.3
                

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

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

        3、设置配置文件 WxPayConfig.java

        import lombok.Data;
        import org.springframework.boot.context.properties.ConfigurationProperties;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.stereotype.Component;
        /**
         * 微信支付配置
         * @author lf
         * @date 2023/8/30
         */
        @Data
        @Component
        @Configuration
        @ConfigurationProperties(prefix = "pay")
        public class WxPayConfig {
            /**
             * 微信公众号appid
             */
            private String appId;
            /**
             * 公众号设置的API v2密钥
             */
            private String apiKey;
            /**
             * 微信商户平台 商户id
             */
            private String mchId;
            /**
             *小程序密钥
             */
            private String appSecret;
            /**
             * 小程序支付异步回调地址
             */
            private String notifyUrl;
        }

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

        import lombok.Data;
        import lombok.experimental.Accessors;
        import java.math.BigDecimal;
        /**
         * 微信支付预下单实体类
         */
        @Data
        @Accessors(chain = true)
        public class WeChatPay {
            /**
             * 返回状态码  此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
             */
            public String return_code;
            /**
             * 返回信息 当return_code为FAIL时返回信息为错误原因 ,例如 签名失败 参数格式校验错误
             */
            private String return_msg;
            /**
             * 公众账号ID 调用接口提交的公众账号ID
             */
            private String appid;
            /**
             * 商户号 调用接口提交的商户号
             */
            private String mch_id;
            /**
             * api密钥 详见:https://pay.weixin.qq.com/index.php/extend/employee
             */
            private String api_key;
            /**
             * 设备号  自定义参数,可以为请求支付的终端设备号等
             */
            private String device_info;
            /**
             * 随机字符串    5K8264ILTKCH16CQ2502SI8ZNMTM67VS   微信返回的随机字符串
             */
            private String nonce_str;
            /**
             * 签名 微信返回的签名值,详见签名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
             */
            private String sign;
            /**
             * 签名类型
             */
            private String sign_type;
            /**
             * 业务结果 SUCCESS SUCCESS/FAIL
             */
            private String result_code;
            /**
             * 错误代码 当result_code为FAIL时返回错误代码,详细参见下文错误列表
             */
            private String err_code;
            /**
             * 错误代码描述 当result_code为FAIL时返回错误描述,详细参见下文错误列表
             */
            private String err_code_des;
            /**
             * 交易类型 JSAPI JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 说明详见;https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
             */
            private String trade_type;
            /**
             * 预支付交易会话标识 微信生成的预支付会话标识,用于后续接口调用中使用,该值有效期为2小时
             */
            private String prepay_id;
            /**
             * 二维码链接     weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 trade_type=NATIVE时有返回,此url用于生成支付二维码,然后提供给用户进行扫码支付。注意:code_url的值并非固定,使用时按照URL格式转成二维码即可
             */
            private String code_url;
            /**
             * 商品描述  商品简单描述,该字段请按照规范传递,具体请见 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
             */
            private String body;
            /**
             * 商家订单号 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
             */
            private String out_trade_no;
            /**
             * 标价金额 订单总金额,单位为分,详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2
             */
            private String total_fee;
            /**
             * 终端IP 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP
             */
            private String spbill_create_ip;
            /**
             * 通知地址 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
             */
            private String notify_url;
            /**
             * 子商户号 sub_mch_id 非必填(商户不需要传入,服务商模式才需要传入) 微信支付分配的子商户号
             */
            private String sub_mch_id;
            /**
             * 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
             */
            private String attach;
            /**
             * 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
             */
            private String out_refund_no;
            /**
             * 退款总金额,单位为分,只能为整数,可部分退款。详见支付金额 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=4_2
             */
            private String refund_fee;
            /**
             * 退款原因 若商户传入,会在下发给用户的退款消息中体现退款原因 注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
             */
            private String refund_desc;
            /**
             * 交易结束时间 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟
             */
            private String time_expire;
            /**
             * 用户标识 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。
             */
            private String openid;
            /**
             * 时间戳
             */
            private String time_stamp;
            /**
             * 会员类型
             */
            private String memberShipType;
        }

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

        /**
         * 微信支付API地址
         * @author lf
         * @date 2023/8/30
         */
        public class WeChatPayUrlConstants {
            /**
             * 统一下单预下单接口url
             */
            public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder";
            /**
             * 订单状态查询接口URL
             */
            public static final String Orderquery = "https://api.mch.weixin.qq.com/pay/orderquery";
            /**
             * 订单申请退款
             */
            public static final String Refund = "https://api.mch.weixin.qq.com/secapi/pay/refund";
            /**
             * 付款码 支付
             */
            public static final String MicroPay = "https://api.mch.weixin.qq.com/pay/micropay";
            /**
             * 微信网页授权 获取“code”请求地址
             */
            public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";
            /**
             * 微信网页授权 获取“code” 回调地址
             */
            public static final String GainCodeRedirect_uri = "http://i5jmxe.natappfree.cc/boss/WeChatPayMobile/SkipPage.html";
        }

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

        import lombok.Data;
        @Data
        public class OrderReturnInfo {
            private String return_code;
            private String return_msg;
            private String result_code;
            private String appid;
            private String mch_id;
            private String nonce_str;
            private String sign;
            private String prepay_id;
            private String trade_type;
        }

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

        import lombok.Data;
        /**
         * 查询订单返回实体类
         * @author lf
         * @date 2023/9/1
         */
        @Data
        public class QueryReturnInfo {
            private String return_code;
            private String return_msg;
            private String result_code;
            private String err_code;
            private String err_code_des;
            private String appid;
            private String mch_id;
            private String nonce_str;
            private String sign;
            private String prepay_id;
            private String trade_type;
            private String device_info;
            private String openid;
            private String is_subscribe;
            private String trade_state;
            private String bank_type;
            private int total_fee;
            private int settlement_total_fee;
            private String fee_type;
            private int cash_fee;
            private String cash_fee_type;
            private int coupon_fee;
            private int coupon_count;
            private String coupon_type_$n;
            private String coupon_id_$n;
            private String transaction_id;
            private String out_trade_no;
            private String time_end;
            private String trade_state_desc;
        }

        8、签名实体类 SignInfo.java

        /**
         * 签名实体类
         * @author lf
         * @date 2023/9/1
         */
        @Data
        public class SignInfo {
            private String appId;//小程序ID
            private String timeStamp;//时间戳
            private String nonceStr;//随机串
            @XStreamAlias("package")
            private String repay_id;
            private String signType;//签名方式
            public String getAppId() {
                return appId;
            }
            public void setAppId(String appId) {
                this.appId = appId;
            }
            public String getTimeStamp() {
                return timeStamp;
            }
            public void setTimeStamp(String timeStamp) {
                this.timeStamp = timeStamp;
            }
            public String getNonceStr() {
                return nonceStr;
            }
            public void setNonceStr(String nonceStr) {
                this.nonceStr = nonceStr;
            }
            public String getRepay_id() {
                return repay_id;
            }
            public void setRepay_id(String repay_id) {
                this.repay_id = repay_id;
            }
            public String getSignType() {
                return signType;
            }
            public void setSignType(String signType) {
                this.signType = signType;
            }
        }

        9、Http工具类 HttpRequest.java

        /**
         * Http工具类
         * @author lf
         * @date 2023/9/1
         */
        public class HttpRequest {
            //连接超时时间,默认10秒
            private static final int socketTimeout = 10000;
            //传输超时时间,默认30秒
            private static final int connectTimeout = 30000;
            /**
             * post请求
             *
             * @throws IOException
             * @throws ClientProtocolException
             * @throws NoSuchAlgorithmException
             * @throws KeyStoreException
             * @throws KeyManagementException
             * @throws UnrecoverableKeyException
             */
            public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException {
                HttpPost httpPost = new HttpPost(url);
                //解决XStream对出现双下划线的bug
                XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
                xStreamForRequestPostData.alias("xml", xmlObj.getClass());
                //将要提交给API的数据对象转换成XML格式数据Post给API
                String postDataXML = xStreamForRequestPostData.toXML(xmlObj);
                //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
                StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
                httpPost.addHeader("Content-Type", "text/xml");
                httpPost.setEntity(postEntity);
                //设置请求器的配置
                RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
                httpPost.setConfig(requestConfig);
                HttpClient httpClient = HttpClients.createDefault();
                HttpResponse response = httpClient.execute(httpPost);
                HttpEntity entity = response.getEntity();
                String result = EntityUtils.toString(entity, "UTF-8");
                return result;
            }
            /**
             * 自定义证书管理器,信任所有证书
             *
             * @author pc
             */
            public static class MyX509TrustManager implements X509TrustManager {
                @Override
                public void checkClientTrusted(
                        java.security.cert.X509Certificate[] arg0, String arg1)
                        throws CertificateException {
                }
                @Override
                public void checkServerTrusted(
                        java.security.cert.X509Certificate[] arg0, String arg1)
                        throws CertificateException {
                }
                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            }
        }

        10、微信签名 SignUtils.java

        /**
         * 微信签名
         * @author lf
         * @date 2023/9/1
         */
        public class SignUtils {
            /**
             * 签名算法
             *
             * @param o 要参与签名的数据对象
             * @return 签名
             * @throws IllegalAccessException
             */
            public static String getSign(Object o) throws IllegalAccessException {
                ArrayList list = new ArrayList();
                Class cls = o.getClass();
                Field[] fields = cls.getDeclaredFields();
                for (Field f : fields) {
                    f.setAccessible(true);
                    if (f.get(o) != null && f.get(o) != "") {
                        String name = f.getName();
                        XStreamAlias anno = f.getAnnotation(XStreamAlias.class);
                        if (anno != null) {
                            name = anno.value();
                        }
                        list.add(name + "=" + f.get(o) + "&");
                    }
                }
                int size = list.size();
                String[] arrayToSort = list.toArray(new String[size]);
                Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
                StringBuilder sb = new StringBuilder();
                for (int i = 0; i  
        

        11、MD5加密工具类 MD5.java

        import java.security.MessageDigest;
        /**
         * MD5加密工具类
         * @author lf
         * @date 2023/9/1
         */
        public class MD5 {
            private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
                    "8", "9", "a", "b", "c", "d", "e", "f"};
            /**
             * 转换字节数组为16进制字串
             *
             * @param b 字节数组
             * @return 16进制字串
             */
            public static String byteArrayToHexString(byte[] b) {
                StringBuilder resultSb = new StringBuilder();
                for (byte aB : b) {
                    resultSb.append(byteToHexString(aB));
                }
                return resultSb.toString();
            }
            /**
             * 转换byte到16进制
             *
             * @param b 要转换的byte
             * @return 16进制格式
             */
            private static String byteToHexString(byte b) {
                int n = b;
                if (n  
        

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

        /**
         * 微信支付配置类
         * @author lf
         * @date 2023/9/1
         */
        public class Configure {
            /**
             * 商户支付秘钥
             */
            private static String key = "";
            public static String getKey() {
                return key;
            }
            public static void setKey(String key) {
                Configure.key = key;
            }
        }

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

            /**
             * 小程序支付下单接口
             * @return 返回结果
             */
            @ApiOperation("小程序支付功能")
            @PostMapping("/pay")
            public AjaxResult wxPay(@RequestBody PayParameterVO payParameterVO){
                Map payHistory = wxPayInfoService.insertPayRecord(payParameterVO);
                return success("success",payHistory);
            }
            /**
             * 查询订单
             */
            @ApiOperation("订单查询")
            @PostMapping("/wx/query")
            public AjaxResult orderQuery(@RequestParam("out_trade_no") String out_trade_no) {
               Map query = wxPayInfoService.orderQuery(out_trade_no);
               return success("success", query);
            }

        14、业务接口层 WxPayInfoService.java

        import com.ruoyi.ai.doamin.PayParameterVO;
        import java.util.Map;
        /**
         * 微信小程序支付-业务接口层
         * @author lf
         * @date 2023/8/31
         */
        public interface WxPayInfoService {
            /**
             * 插入订单记录
             */
            Map insertPayRecord(PayParameterVO payParameterVO);
            /**
             * 查询订单
             * @param out_trade_no 订单号
             * @return 返回结果
             */
            Map orderQuery(String out_trade_no);
        }

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

        import cn.hutool.core.util.ObjectUtil;
        import com.github.wxpay.sdk.WXPayConstants;
        import com.github.wxpay.sdk.WXPayUtil;
        import com.ruoyi.ai.doamin.PayParameterVO;
        import com.ruoyi.ai.service.WxPayInfoService;
        import com.ruoyi.common.config.WxPayConfig;
        import com.ruoyi.common.constant.WeChatPayUrlConstants;
        import com.ruoyi.common.core.domain.pay.WeChatPay;
        import com.ruoyi.common.utils.pay.HttpRequest;
        import com.ruoyi.common.utils.pay.SignUtils;
        import com.ruoyi.common.utils.pay.entity.OrderReturnInfo;
        import com.ruoyi.common.utils.pay.entity.QueryReturnInfo;
        import com.ruoyi.common.utils.pay.entity.SignInfo;
        import com.thoughtworks.xstream.XStream;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.stereotype.Service;
        import org.springframework.transaction.annotation.Transactional;
        import javax.annotation.Resource;
        import java.math.BigDecimal;
        import java.text.DecimalFormat;
        import java.util.*;
        /**
         * 微信小程序支付-业务接口实现层
         * @author lf
         * @date 2023/8/31
         */
        @Service
        @Slf4j
        public class WxPayInfoServiceImpl implements WxPayInfoService {
            @Resource
            private WxPayConfig payProperties;
            private static final DecimalFormat df = new DecimalFormat("#");
            /**
             * 插入订单记录
             * @param payParameterVO 用户ID 会员套餐ID
             * @return 返回结果
             */
            @Override
            @Transactional
            public Map insertPayRecord(PayParameterVO payParameterVO) {
                    //接收返回的参数
                    Map map = new HashMap();
                    String title = "koko测试点数";
                    //金额 * 100 以分为单位
                    BigDecimal fee = BigDecimal.valueOf(1);
                    BigDecimal RMB = new BigDecimal(100);
                    BigDecimal totalFee = fee.multiply(RMB);
                    try {
                        WeChatPay weChatPay = new WeChatPay();
                        weChatPay.setAppid(payProperties.getAppId());
                        weChatPay.setMch_id(payProperties.getMchId());
                        weChatPay.setNonce_str(getRandomStringByLength(32));
                        weChatPay.setBody(title);
                        weChatPay.setOut_trade_no(getRandomStringByLength(32));
                        weChatPay.setTotal_fee( df.format(Double.parseDouble(String.valueOf(totalFee))));
                        weChatPay.setSpbill_create_ip("127.0.0.1");
                        weChatPay.setNotify_url(payProperties.getNotifyUrl());
                        weChatPay.setTrade_type("JSAPI");
                        //这里直接使用当前用户的openid
                        weChatPay.setOpenid("oOKq*******xj8o");
                        weChatPay.setSign_type("MD5");
                        //生成签名
                        String sign = SignUtils.getSign(weChatPay);
                        weChatPay.setSign(sign);
                        String result = HttpRequest.sendPost(WeChatPayUrlConstants.Uifiedorder, weChatPay);
                        System.out.println(result);
                        //将返回结果从xml格式转换为map格式
                        Map wxResultMap = WXPayUtil.xmlToMap(result);
                        if (ObjectUtil.isNotEmpty(wxResultMap.get("return_code")) && wxResultMap.get("return_code").equals("SUCCESS")){
                            if (wxResultMap.get("result_code").equals("FAIL")){
                                map.put("msg", "统一下单失败");
                                map.put("status",500);
                                map.put("data", wxResultMap.get("err_code_des"));
                                return map;
                            }
                        }
                        XStream xStream = new XStream();
                        xStream.alias("xml", OrderReturnInfo.class);
                        OrderReturnInfo returnInfo = (OrderReturnInfo) xStream.fromXML(result);
                        // 二次签名
                        if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) {
                            SignInfo signInfo = new SignInfo();
                            signInfo.setAppId(payProperties.getAppId());
                            long time = System.currentTimeMillis() / 1000;
                            signInfo.setTimeStamp(String.valueOf(time));
                            signInfo.setNonceStr(WXPayUtil.generateNonceStr());
                            signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id());
                            signInfo.setSignType("MD5");
                            //生成签名
                            String sign1 = SignUtils.getSign(signInfo);
                            Map payInfo = new HashMap();
                            payInfo.put("timeStamp", signInfo.getTimeStamp());
                            payInfo.put("nonceStr", signInfo.getNonceStr());
                            payInfo.put("package", signInfo.getRepay_id());
                            payInfo.put("signType", signInfo.getSignType());
                            payInfo.put("paySign", sign1);
                            map.put("status", 200);
                            map.put("msg", "统一下单成功!");
                            map.put("data", payInfo);
                            //预下单成功,处理业务逻辑
                            //****************************//
                            // 业务逻辑结束 回传给小程序端唤起支付
                            return map;
                        }
                        map.put("status", 500);
                        map.put("msg", "统一下单失败!");
                        map.put("data", null);
                        return map;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                return null;
            }
            /**
             * 查询订单
             * @param out_trade_no 订单号
             * @return 返回结果
             */
            @Override
            public Map orderQuery(String out_trade_no){
                Map map = new HashMap();
                try {
                    WeChatPay weChatPay = new WeChatPay();
                    weChatPay.setAppid(payProperties.getAppId());
                    weChatPay.setMch_id(payProperties.getMchId());
                    weChatPay.setNonce_str(WXPayUtil.generateNonceStr());
                    weChatPay.setOut_trade_no(out_trade_no);
                    //order.setSign_type("MD5");
                    //生成签名
                    String sign = SignUtils.getSign(weChatPay);
                    weChatPay.setSign(sign);
                    String result = HttpRequest.sendPost(WXPayConstants.ORDERQUERY_URL, weChatPay);
                    System.out.println(result);
                    XStream xStream = new XStream();
                    xStream.alias("xml", QueryReturnInfo.class);
                    QueryReturnInfo returnInfo = (QueryReturnInfo) xStream.fromXML(result);
                    map.put("status", 500);
                    map.put("msg", "统一下单失败!");
                    map.put("data", returnInfo);
                    return map;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
            /**
             * 获取一定长度的随机字符串
             *
             * @param length 指定字符串长度
             * @return 一定长度的字符串
             */
            public static String getRandomStringByLength(int length) {
                String base = "abcdefghijklmnopqrstuvwxyz0123456789";
                Random random = new Random();
                StringBuffer sb = new StringBuffer();
                for (int i = 0; i  
        

        3.2、支付支付回调

            /**
             * 微信小程序支付成功回调
             * @param request 请求
             * @param response 响应
             * @return 返回结果
             * @throws Exception 异常处理
             */
            @RequestMapping("/weixin/callback")
            public String callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
                System.out.println("接口已被调用");
                ServletInputStream inputStream = request.getInputStream();
                String notifyXml = StreamUtils.inputStream2String(inputStream, "utf-8");
                System.out.println(notifyXml);
                // 解析返回结果
                Map notifyMap = WXPayUtil.xmlToMap(notifyXml);
                // 判断支付是否成功
                if ("SUCCESS".equals(notifyMap.get("result_code"))) {
                    //支付成功时候,处理业务逻辑
                    System.out.println("支付成功");
                    System.out.println("" + ""
                            + "" + " ");
                    
                    /**
                     * 注意
                     * 因为微信回调会有八次之多,所以当第一次回调成功了,那么我们就不再执行逻辑了
                     * return返回的结果一定是这种格式,当result_code返回的结果是SUCCESS时,则不进行调用了
                     * 如果不返回下面的格式,业务逻辑会出现回调多次的情况,我就遇到过这种情况。
                     */
                    return "" + ""
                                + "" + " ";
                }
                // 创建响应对象:微信接收到校验失败的结果后,会反复的调用当前回调函数
                Map returnMap = new HashMap();
                returnMap.put("return_code", "FAIL");
                returnMap.put("return_msg", "");
                String returnXml = WXPayUtil.mapToXml(returnMap);
                response.setContentType("text/xml");
                System.out.println("校验失败");
                return returnXml;
            }

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

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

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

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

        3.3、问题排查

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

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

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

        3.4、统一下单和订单查询

        1、统一下单

        2、订单查询-支付成功

        3、订单查询-支付失败

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


文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复:表情:
评论列表 (暂无评论,0人围观)

还没有评论,来说两句吧...

目录[+]

取消
微信二维码
微信二维码
支付宝二维码