当前位置:   article > 正文

【第三方支付】微信支付总结_微信支付回调

微信支付回调

一、流程总结

1.1 技术概览

1.2 官方业务流程时序图

在这里插入图片描述

1.3 支付流程

用户在请求支付的时候,服务端就要向微信支付发起统一下单接口:这里就会填入商户内部订单的编号、付款金额、通知地址等,统一下单接口就会返回调起支付的必要参数。服务器就需要将这个参数返回给客户端。

微信服务器验证正确后会自动通知微信弹出输入密码框。

用户在输入了支付密码之后,微信支付系统将会根据你在统一下单接口时候填入的通知地址,返回一些数据信息。服务端在处理了这些信息之后,就要返回微信支付系统需要的返回参数。

流程,如下所示:
在这里插入图片描述

1.4 退款流程

用户在发起退款申请,服务器就调起申请退款接口,输入退款结果通知

推荐使用异步通知进行处理:因为微信退款产生,并不是即时到帐,通常出现这个提示之后的几分钟之内,钱款就会到账。
请添加图片描述

二、支付注意事项

  1. 可以利用好统一下单接口的附加数据 attach,方便回调处理
  2. 在微信回调的时候,还需要验证信息
  • 得到的订单编号中能否查询相应的订单
  • 该通知是否已经处理过
  • 校验返回的订单金额是否与商户的订单金额一致
  1. 订单总金额,单位为
  2. 当且仅当 return_code 与 result_code 为 SUCCESS 时,才是付款成功
  3. 回调地址:必须为外网可访问的url,不能携带参数。 公网域名必须为https
  4. 支付成功才会有异步支付结果通知下发,支付失败没有,回调中的result_code为fail:有可能是商户出现问题
  5. 支付金额,应该是根据订单编号查询数据库中的支付金额进行支付申请

三、退款注意事项

  1. 退款结果通知验证
  • 当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过;如果没有处理过再进行处理,如果处理过直接返回结果成功。
  1. 当且仅当 return_code 与 result_code 为 SUCCESS 时,才是退款成功
  2. “退款异常”:
    当用户使用银行卡支付时,微信支付首先原路退款到银行卡,当银行卡状态不正常或银行卡错误时,微信支付会优先转退用户微信零钱,仅当用户微信零钱也注销,才会转入“退款异常”状态。此时可选择“其它方式退款”,手动录入用户的银行信息完成退款
  3. “退款关闭”:
    余额不足等其他情况会导致退款关闭
  4. 退款金额与支付金额相似

四、代码

4.1 依赖导入

        <dependency>
            <groupId>com.github.javen205</groupId>
            <artifactId>IJPay-WxPay</artifactId>
            <version>2.8.0</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

4.2 配置文件

wx:
  miniApp:
    appId:
    appSecret:
  pay:
    mchId: 
    machSerialNumber: 
    mchSecret: 
    # 商户证书文件路径
    # 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
    cert: apiclient_cert.p12
    # 请参考“回调通知注意事项” https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_8&index=6
    doMain:
  android:
    appId:
    appSecret: 
  ios:
    appId: 
    appSecret:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4.3 配置类

@ConfigurationProperties(prefix = "wx")
@Component
@Data
public class WxProperties {

    private MiniApp miniApp;

    private Pay pay;

    private Android android;

    private Ios ios;

    @Data
    public static class MiniApp{
        private String appId;
        private String appSecret;
    }

    @Data
    public static class Pay{
        private String mchId;
        private String machSerialNumber;
        private String mchSecret;
        private String cert;
        private String doMain;
    }

    @Data
    public static class Ios{
        private String appId;
        private String appSecret;
    }

    @Data
    public static class Android{
        private String appId;
        private String appSecret;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

4.4 业务层

1. 微信支付

1) 用户统一下单
    /**
     * 付款订单的预支付会话标识
     * <p> 此接口对应的是 V2 的版本
     * 1. 检查订单是否能够付款
     * 2. 处理微信商户平台返回的参数
     * 3. 设置订单付款状态
     */
   @Transactional(rollbackFor = Exception.class)
    public Object prepay(Integer originType) {
        UnifiedOrderModel.UnifiedOrderModelBuilder unifiedOrderModelBuilder = UnifiedOrderModel.builder();
        // 商户号
        unifiedOrderModelBuilder.mch_id(wxProperties.getPay().getMchId());
        // 商品描述: 订单编号
        unifiedOrderModelBuilder.body("");
        // 订单编号:订单中获取
        unifiedOrderModelBuilder.out_trade_no("");
        // 价格单位:(分) 整数
        unifiedOrderModelBuilder.total_fee("");
        // 附加数据
        unifiedOrderModelBuilder.attach("");
        // 随机字符串
        unifiedOrderModelBuilder.nonce_str(WxPayKit.generateStr());
        //支付的ip地址
        unifiedOrderModelBuilder.spbill_create_ip(ServletUtil.getClientIP(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()));
        // 支付回调地址
        unifiedOrderModelBuilder.notify_url(wxProperties.getPay().getDoMain() + "/other/wx/notifyUrl");
		/*
         * 订单有效期为15min。用户在14min59s唤起支付,在支付界面停留了2s(此时,已经过了交易期限),不能够让用户在进行微信支付
         * 就需要设置交易的开始时间以及结束时间
         */
        DateTime orderCreateTime = DateUtil.date();
        // 交易开始时间
        unifiedOrderModelBuilder.time_start(DateUtil.format(orderCreateTime, DatePattern.PURE_DATETIME_PATTERN));
        // 交易结束时间 : 15分钟
        unifiedOrderModelBuilder.time_expire(DateUtil.format(DateUtil.offsetMinute(orderCreateTime, 15), DatePattern.PURE_DATETIME_PATTERN));
        if (originType == 0) {
            // 小程序appId
            unifiedOrderModelBuilder.appid(wxProperties.getMiniApp().getAppId());
            // 小程序中的openId(ios以及android是没有的)
            unifiedOrderModelBuilder.openid("");
            // 交易方式
            unifiedOrderModelBuilder.trade_type(TradeType.JSAPI.getTradeType());
        } else if (originType == 1) {
            // 安卓appId
            unifiedOrderModelBuilder.appid(wxProperties.getAndroid().getAppId());
            // 交易方式
            unifiedOrderModelBuilder.trade_type(TradeType.APP.getTradeType());
        } else if (originType == 2) {
            // ios的appId
            unifiedOrderModelBuilder.appid(wxProperties.getIos().getAppId());
            // 交易方式
            unifiedOrderModelBuilder.trade_type(TradeType.APP.getTradeType());
        }
        Map<String, String> requestParams = unifiedOrderModelBuilder.build().createSign(wxProperties.getPay().getMchSecret(), SignType.MD5);
        logger.info("统一下单请求参数:" + requestParams);
        // 向微信发送统一下单请求
        String resultXml = WxPayApi.pushOrder(false, requestParams);
        logger.info("统一下单请求参数:" + resultXml);
        // 将获取结果集xml变为Map
        Map<String, String> resultMap = WxPayKit.xmlToMap(resultXml);
        String returnCode = resultMap.get("return_code");
        String returnMsg = resultMap.get("return_msg");
        // 检查是否连接到微信支付
        if (!WxPayKit.codeIsOk(returnCode)) {
            return returnMsg;
        }
        // 检查交易是否成功
        String resultCode = resultMap.get("result_code");
        if (!WxPayKit.codeIsOk(resultCode)) {
            return returnMsg;
        }
        // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回
        Map<String, String> returnParams = null;
        if (originType == 0) {
            returnParams = WxPayKit.miniAppPrepayIdCreateSign(wxProperties.getMiniApp().getAppId(), resultMap.get("prepay_id"), wxProperties.getPay().getMchSecret(), SignType.MD5);
            logger.info("小程序调起支付的参数: " + returnParams);
        } else if (originType == 1) {
            returnParams = WxPayKit.appPrepayIdCreateSign(wxProperties.getAndroid().getAppId(), wxProperties.getPay().getMchId(), resultMap.get("prepay_id"), wxProperties.getPay().getMchSecret(), SignType.MD5);
            logger.info("Android调起支付的参数: " + returnParams);
        } else if (originType == 2) {
            returnParams = WxPayKit.appPrepayIdCreateSign(wxProperties.getIos().getAppId(), wxProperties.getPay().getMchId(), resultMap.get("prepay_id"), wxProperties.getPay().getMchSecret(), SignType.MD5);
            logger.info("IOS调起支付的参数: " + returnParams);
        }
        /*
         * 数据库处理:设置状态为未支付、设置支付方式等业务处理
         */
        return returnParams;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
2) 支付回调通知:Post

支付结果通知详细的注意事项以及提醒

    public Object payNotify(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> analysisMap = WxPayKit.xmlToMap(HttpKit.readData(request));
        logger.info("支付结果通知:" + analysisMap);
        // 验证支付
        if (WxPayKit.verifyNotify(analysisMap, wxProperties.getPay().getMchSecret(), SignType.MD5)) {

            // 与微信通讯是否成功
            if (WxPayKit.codeIsOk(analysisMap.get("return_code"))) {
                Map<String, String> resultParams = new HashMap<>();
                resultParams.put("return_code", "SUCCESS");
                resultParams.put("return_msg", "OK");

                //支付成功
                if (WxPayKit.codeIsOk(analysisMap.get("result_code"))) {

                    // !得到的订单编号中能否查询相应的订单 !

                    // !校验返回的订单金额是否与商户的订单金额一致 !

                    // !该通知是否已经处理过 !

                    /*
                     * 数据库的业务处理
                     */
                    return WxPayKit.toXml(resultParams);
                } else {
                    // 交易失败
                    // ! 用户输错密码 银行卡余额不足 不走微信支付结果通知接口的失败 !
                    log.info("微信支付交易失败");
                    return WxPayKit.toXml(resultParams);
                }
            }else {
                log.info("微信回调:微信通讯失败: {}", analysisMap.get("return_msg"));
            }
        }
        log.info("非法微信回调");
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

2. 微信退款

1) 用户申请退款
    /**
     * @param orderNo  订单编号
     * @param originType  订单来源
     */
    public Object refund(String orderNo,Integer originType) {
        /*
         * 查看能否搜寻到该订单
         */

        /*
         * 查看订单是否能够退款
         */

        // 微信退款
        String appid = null;
        // 0.小程序  1.安卓  2.iOS
        if (originType == 0) {
            appid = wxProperties.getMiniApp().getAppId();
        } else if (originType == 1) {
            appid = wxProperties.getAndroid().getAppId();
        } else {
            appid = wxProperties.getIos().getAppId();
        }
        Map<String, String> params = RefundModel.builder()
                .appid(appid)
                .mch_id(wxProperties.getPay().getMchId())
                .nonce_str(WxPayKit.generateStr())
                .out_trade_no(orderNo)
                .out_refund_no("R" + orderNo.substring(1))
                .total_fee("")
                .refund_fee("")
                .notify_url(wxProperties.getPay().getDoMain() + "/wx/refundUrl")
                .build()
                .createSign(wxProperties.getPay().getMchSecret(), SignType.MD5);
        Map<String, String> resultMap = null;
        try {
            resultMap = WxPayKit.xmlToMap(WxPayApi.orderRefund(false, params, new ClassPathResource("apiclient_cert.p12").getURL().getPath(), wxProperties.getPay().getMchId()));
        } catch (IOException e) {
            return new Exception("微信调起支付退款异常" + e.getMessage());
        }
        String returnCode = resultMap.get("return_code");
        String returnMsg = resultMap.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode)) {
            log.info("微信退款失败:{}", returnMsg);
            return "微信退款失败";
        }
        log.info("微信退款成功");
        return "微信退款成功";
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
2) 退款回调通知
    public String refundUrl(HttpServletRequest request){
        String xmlMsg = HttpKit.readData(request);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
        log.info("退款通知:{}", params);

        String returnCode = params.get("return_code");
        if (WxPayKit.codeIsOk(returnCode)) {
            String reqInfo = params.get("req_info");
            Map<String, String> decryptData = WxPayKit.xmlToMap(WxPayKit.decryptData(reqInfo, wxProperties.getPay().getMchSecret()));
            log.info("退款通知解密后的数据:{}", decryptData);

            Map<String, String> xml = new HashMap<String, String>(2);
            xml.put("return_code", "SUCCESS");
            xml.put("return_msg", "OK");
            // 退款是否成功
            if (WxPayKit.codeIsOk(decryptData.get("refund_status"))) {
                /*
                 * 当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,
                 * 如果没有处理过再进行处理,如果处理过直接返回结果成功。
                 */
                return WxPayKit.toXml(xml);
            }else {
                if (("CHANGE".equals(decryptData.get("refund_status")))) {
                    /*
                     * “退款异常”:当用户使用银行卡支付时,当银行卡状态不正常或银行卡错误时,又退回用户微信零钱失败
                     */
                    log.info("微信退款发生退款异常");
                } else {
                    /*
                     * “退款关闭”:余额不足或者其他情况
                     */
                    log.info("微信退款发生退款关闭异常");
                }
                return WxPayKit.toXml(xml);
            }
        }
        log.info("微信退款回调错误:{}", params.get("return_msg"));
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

3. 查询订单

    /**
     * 微信支付订单的查询
     *
     * @param orderNo    订单号
     * @param originType 订单来源 0.小程序  1.安卓  2.iOS
     */
    public Map<String, String> orderQuery(String orderNo, Integer originType) {
        String appid = null;
        // 0.小程序  1.安卓  2.iOS
        if (originType == 0) {
            appid = wxProperties.getMiniApp().getAppId();
        } else if (originType == 1) {
            appid = wxProperties.getAndroid().getAppId();
        } else {
            appid = wxProperties.getIos().getAppId();
        }
        Map<String, String> params = OrderQueryModel.builder()
                .appid(appid)
                .mch_id(wxProperties.getPay().getMchId())
                .out_trade_no(orderNo)
                .nonce_str(WxPayKit.generateStr())
                .build()
                .createSign(wxProperties.getPay().getMchSecret(), SignType.MD5);
        Map<String, String> resultMap = WxPayKit.xmlToMap(WxPayApi.orderQuery(params));
        String return_code = resultMap.get("return_code");
        String return_msg = resultMap.get("return_msg");
        if (!WxPayKit.codeIsOk(return_code)) {
            log.info("查询微信支付订单失败:{}", return_msg);
        }
        log.info("查询微信支付订单:{}", resultMap);
        return resultMap;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

4. 查询退款

    /**
     * 微信退款订单的查询
     *
     * @param orderNo    订单号
     * @param originType 订单来源 0.小程序  1.安卓  2.iOS
     */
    public Map<String, String> refundQuery(String orderNo, Integer originType) {
        String appid = null;
        // 0.小程序  1.安卓  2.iOS
        if (originType == 0) {
            appid = wxProperties.getMiniApp().getAppId();
        } else if (originType == 1) {
            appid = wxProperties.getAndroid().getAppId();
        } else {
            appid = wxProperties.getIos().getAppId();
        }
        Map<String, String> params = RefundQueryModel.builder()
                .appid(appid)
                .mch_id(wxProperties.getPay().getMchId())
                .out_trade_no(orderNo)
                .nonce_str(WxPayKit.generateStr())
                .build()
                .createSign(wxProperties.getPay().getMchSecret(), SignType.MD5);
        Map<String, String> resultMap = WxPayKit.xmlToMap(WxPayApi.orderRefundQuery(false, params));
        String returnCode = resultMap.get("return_code");
        String returnMsg = resultMap.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode)) {
            log.info("查询退款信息失败:{}", returnMsg);
        }
        log.info("查询退款信息:{}", resultMap);
        return resultMap;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/132294
推荐阅读
相关标签
  

闽ICP备14008679号