微信支付-电商收付通开发-04.支付

合单支付

合单支付是指可以在一个订单中包含多个商家的多个商品,一次性支付。

  • 关于JSAPI、APP、小程序等支付类型的区别:https://pay.weixin.qq.com/wiki/doc/api/sl.html
    我们是在小程序中嵌入支付功能的,最开始用的是APP支付一直提示“支付场景非法”,换成JSAPI支付后才成功调出支付确认框并成功支付。
    在这里插入图片描述

  • 如果是公众号支付、扫码支付和H5支付还需要配置支付域名回调链接,请参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
    在这里插入图片描述

  • 在申请开通电商收付通时如果选择的是平台代扣手续费,那么建议在正式支付分账前向手续费账户充值至少100块钱,否则可能会导致分账失败;

  • 回调通知地址必须为域名并且为https协议,微信会以POST方式访问URL,以json格式通过body携带数据;

预支付

此处的测试方法只设置了一个子单,即只在一家店铺下了一个商品的订单;

    /**
     * 预支付
     */
    @Test
    public void prepay() {
        JSONObject reqJsonObject = new JSONObject();
        //合单商户appid
        reqJsonObject.put("combine_appid", WxPayConfig.merchantAppId);
        //合单商户
        reqJsonObject.put("combine_mchid", WxPayConfig.merchantId);
        //合单商户订单号
        reqJsonObject.put("combine_out_trade_no", "202005260952_test001");
        //回调地址
        reqJsonObject.put("notify_url", WxPayConfig.callback_notify_pay_address);

        //支付者
        JSONObject combine_payer_info = new JSONObject();
        combine_payer_info.put("openid", "oaKTs4vq93MDDmCFKQO123456789");
        reqJsonObject.put("combine_payer_info", combine_payer_info);

        //子单信息数组
        JSONArray sub_orders = new JSONArray();
        //子单信息
        JSONObject sub_order = new JSONObject();
        //子单商户号,必须与发起方appid有绑定关系
        sub_order.put("mchid", WxPayConfig.merchantId);
        //附加数据,在查询API和支付通知中原样返回
        sub_order.put("attach", "订单号为202005260952_test001");
        //子单商户订单号
        sub_order.put("out_trade_no", "202005260952_test001");
        //二级商户号
        sub_order.put("sub_mchid", WxPayConfig.testMerchantId);
        //商品描述
        sub_order.put("description", "高端茶叶500g装");

        //子单信息-订单金额
        JSONObject amount = new JSONObject();
        //标价金额,子单金额,单位为分
        amount.put("total_amount", 100);
        //标价币种
        amount.put("currency", WxPayConfig.currency_cny);
        sub_order.put("amount", amount);
        //子单信息-结算信息
        JSONObject settle_info = new JSONObject();
        //是否指定分账,true:是,false:否
        settle_info.put("profit_sharing", true);
        sub_order.put("settle_info", settle_info);
        sub_orders.set(0, sub_order);
        reqJsonObject.put("sub_orders", sub_orders);

        System.out.println("请求的body:" + reqJsonObject + "\n\n");

        String headerToken = WxPayUtils.getHeaderAuthorization("POST",
                HttpUrl.parse(WxPayConfig.transactions_prepay_app_url), reqJsonObject.toJSONString());
        System.out.println("请求接口携带的 header:  \n" + headerToken + "\n\n");

        Map<String, String> headersMap = new HashMap<>();
        headersMap.put("User-Agent", WxPayConfig.userAgent);
        headersMap.put("Accept", "application/json");
        headersMap.put("Authorization", headerToken);
        headersMap.put("Wechatpay-Serial", WxPayConfig.api_v3_cert_serial_no);

        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqJsonObject.toJSONString());
        ResponseAndStatusAndHeaders response = ClientUtil.getAndPostJson("POST", WxPayConfig.transactions_prepay_app_url, requestBody, headersMap);
        if (response.getStatus() != Status.SUCCESS || response.getResponseData() == null) {
            LogUtil.printErrorLog(LogUtil.log_front_wxpay + "合单下单接口请求错误,返回信息为:\n" + response.toString() + "\n请求的参数为:\n" + reqJsonObject + "\n\n");
            return;
        }

        System.out.println(response);

        JSONObject jsonObject = JSON.parseObject(response.getResponseData().toString());
        String prepay_id = jsonObject.get("prepay_id").toString();
        System.out.println("合单下单成功,预支付id为:" + prepay_id);
        //{"prepay_id":"up_wx26112728792815875842ded51824410800"}
    }

调起支付

后端处理好数据获得到预支付id后,需要将各种支付参数进行签名计算后的值返回给前端,由小程序方调起支付框(也就是输入支付密码的界面)。

    /**
     * 继续支付
     * @param requestJsonStr 接受的参数
     * @return 操作结果
     */
    @PostMapping("/continuePay")
    public ResultBO continuePay(@RequestBody String requestJsonStr) {
        JSONObject requestJson = JSONObject.parseObject(requestJsonStr);
        String orderNo = requestJson.getString("orderNo");

        //是否为有效订单
        MallOrderDO mallOrder = mallOrderService.getByOrderNo(orderNo);
        if (mallOrder == null) {
            return Results.result(HttpStatusEnum.PARAMTER_ERROE, "订单不存在");
        }
        if (mallOrder.getOrderStatus() != 1 || StrUtil.isBlank(mallOrder.getWxPrepayId())) {
            return Results.result(HttpStatusEnum.PARAMTER_ERROE, "该订单已经支付过了,请勿重复支付");
        }

        JSONObject payObject = WxPayUtils.getPayObject(mallOrder.getWxPrepayId());

        JSONObject data = new JSONObject();
        data.put("payObject", payObject);
        return Results.success(HttpStatusEnum.SUCCESS, data);
    }

支付回调通知

  1. 微信回调通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
  2. 回调处理必须校验签名,避免出现恶意调用攻击。
  3. 使用APIv3密钥对回调数据中的ciphertext进行AEAD_AES_256_GCM算法解密,得到原文。
  4. 回调处理必须检查订单状态,避免重复处理。
  5. 处理回调通知后做好日志记录,当回调正常接受返回的HTTP状态码为200或204,当回调处理失败返回的HTTP状态码应为500
    /**
     * 微信支付回调通知
     */
    @PostMapping("payCallBack")
    public JSONObject callBackNotify(@RequestBody String requestJsonStr, @RequestHeader HttpHeaders headers, HttpServletResponse response) {
        LogUtil.printErrorLog(LogUtil.log_front_wxpay + "微信支付结果通知接受成功,header为:" + headers + "\n body为:" + requestJsonStr);

        //返回通知的应答报文,code(32):SUCCESS为清算机构接收成功;message(64):错误原因
        JSONObject responseJson = new JSONObject();
        responseJson.put("code", "FAIL");
        //支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

        if (!headers.containsKey("Wechatpay-Serial") || !headers.containsKey("Wechatpay-Timestamp")
                || !headers.containsKey("Wechatpay-Nonce") || !headers.containsKey("Wechatpay-Signature")) {
            LogUtil.printErrorLog(LogUtil.log_front_wxpay + "回调请求header缺失");
            responseJson.put("message", "回调请求header缺失");
            return responseJson;
        }

        String WechatpaySerial = headers.getFirst("Wechatpay-Serial");//平台证书序列号
        String WechatpayTimestamp = headers.getFirst("Wechatpay-Timestamp");//应答时间戳
        String WechatpayNonce = headers.getFirst("Wechatpay-Nonce");//应答随机串
        String WechatpaySignature = headers.getFirst("Wechatpay-Signature"); //应答签名
        if (!WechatpaySerial.equals(WxPayConfig.api_v3_cert_serial_no)) {
            LogUtil.printErrorLog(LogUtil.log_front_wxpay + "回调请求证书序列化不一致");
            responseJson.put("message", "回调请求证书序列化不一致");
            return responseJson;
        }

        //获取签名串,验签
        String srcData = WechatpayTimestamp + "\n" + WechatpayNonce + "\n" + requestJsonStr + "\n";
        if (!WxPayUtils.verify(srcData, WechatpaySignature)) {
            LogUtil.printErrorLog(LogUtil.log_front_wxpay + "验签失败");
            responseJson.put("message", "验签失败");
            return responseJson;
        }

        JSONObject requestJson = JSONObject.parseObject(requestJsonStr);
        //通知的类型,支付成功通知的类型为 TRANSACTION.SUCCESS
        String event_type = requestJson.get("event_type").toString();
        //通知数据
        JSONObject resource = JSONObject.parseObject(requestJson.get("resource").toString());
        //数据密文,Base64编码后的开启/停用结果数据密文
        String ciphertext = resource.get("ciphertext").toString();
        String associated_data = resource.get("associated_data").toString();
        String nonce = resource.get("nonce").toString();

        //对密文串进行解密
        String verify = WxPayUtils.getNotifyData(associated_data, nonce, ciphertext);
        JSONObject paySuccess = JSONObject.parseObject(verify);
        JSONArray sub_orders = JSON.parseArray(paySuccess.get("sub_orders").toString());
        JSONObject sub_order = JSONObject.parseObject(sub_orders.get(0).toString());
        //微信子单订单号
        String wxOrderNo = sub_order.get("transaction_id").toString();
        if (StrUtil.isBlank(wxOrderNo)) {
            responseJson.put("message", "微信支付成功,未接受到微信订单号");
            return responseJson;
        }
        //合单商户订单号
        String orderNo = paySuccess.get("combine_out_trade_no").toString();
        if (StrUtil.isBlank(orderNo)) {
            responseJson.put("message", "微信支付成功,未接受到合单商户订单号");
            return responseJson;
        }

        MallOrderDO mallOrder = mallOrderService.getByOrderNo(orderNo);
        if (mallOrder == null) {
            responseJson.put("message", "操作失败,未查询到合单商户订单号");
            return responseJson;
        }

        //支付失败
        if (!event_type.equals("TRANSACTION.SUCCESS")) {
            boolean result = mallOrderService.payFail(orderNo, wxOrderNo);
            if (result) {
                LogUtil.printErrorLog(LogUtil.log_front_wxpay + "支付结果通知:支付失败,处理成功。订单号为:" + orderNo);
                response.setStatus(HttpServletResponse.SC_OK);
                responseJson.put("code", "SUCCESS");
                responseJson.put("message", "微信支付失败,商户处理成功");
            } else {
                LogUtil.printErrorLog(LogUtil.log_front_wxpay + "支付结果通知:支付失败,处理失败。订单号为:" + orderNo);
                responseJson.put("message", "微信支付失败,商户处理失败");
            }
            return responseJson;
        }

        //支付成功
        boolean result = mallOrderService.paySuccess(orderNo, wxOrderNo);
        if (result) {
            LogUtil.printErrorLog(LogUtil.log_front_wxpay + "支付结果通知:支付成功,处理成功。订单号为:" + orderNo);
            response.setStatus(HttpServletResponse.SC_OK);
            responseJson.put("code", "SUCCESS");
            responseJson.put("message", "微信支付成功,商户处理成功");
        } else {
            LogUtil.printErrorLog(LogUtil.log_front_wxpay + "支付结果通知:支付成功,处理失败。订单号为:" + orderNo);
            responseJson.put("message", "微信支付成功,商户处理失败");
        }
        return responseJson;
    }

查询合单订单

  • 我们系统设定用户下订单后一定时间内(24h)未付款会自动取消订单,为防止状态不同步,在正式取消订单前调用合单查询接口,确认最终支付状态。
  • 当超时(4h)未接受到支付结果通知时,手动调用查询接口确认最终支付状态。

    /**
     * 合单查询订单
     * @param orderNo 订单号
     * @return 支付是否成功
     */
    @Override
    public Map<String, String> queryOrder(String orderNo) {
        Map<String, String> resultMap = new HashMap<>();

        Map<String, String> headersMap = WxPayUtils.getHeaderMap("GET", WxPayConfig.transactions_order_query_url + orderNo, "");
        ResponseAndStatusAndHeaders response = ClientUtil.getAndPostJson("GET", WxPayConfig.transactions_order_query_url + orderNo, null, headersMap);
        if (response.getStatus() != Status.SUCCESS || response.getResponseData() == null) {
            LogUtil.printErrorLog(LogUtil.log_front_wxpay + "合单查询订单接口请求错误,返回信息为:\n" + response.toString() + ",请求的参数:\n" + orderNo);
            return null;
        }

        JSONObject jsonObject = JSON.parseObject(response.getResponseData().toString());
        /*{
            "combine_appid": "wx4fcb17899329a2a1",
            "combine_mchid": "1586786671",
            "combine_out_trade_no": "20200528807139",
            "combine_payer_info": {
                "openid": "oaKTs4vq93MDDmCFKQO123456789"
            },
            "scene_info": {
                "device_id": ""
            },
            "sub_orders": [{
                "amount": {
                    "payer_amount": 100,
                    "payer_currency": "CNY",
                    "total_amount": 100
                },
                "attach": "商户平台订单号为20200528807139",
                "bank_type": "CFT",
                "mchid": "1586786671",
                "out_trade_no": "20200528807139",
                "sub_mchid": "1596741381",
                "success_time": "2020-05-28T22:11:13+08:00",
                "trade_state": "SUCCESS",
                "trade_type": "JSAPI",
                "transaction_id": "4325300104202005287616306099"
            }]
        }*/

        JSONArray subOrders = JSON.parseArray(jsonObject.get("sub_orders").toString());
        JSONObject subOrder = JSON.parseObject(subOrders.get(0).toString());
        //交易状态,SUCCESS:支付成功
        //REFUND:转入退款
        //NOTPAY:未支付
        //CLOSED:已关闭
        //USERPAYING:用户支付中
        //PAYERROR:支付失败(其他原因,如银行返回失败)
        String trade_state = subOrder.getString("trade_state");
        String transaction_id = subOrder.getString("transaction_id");

        resultMap.put("trade_state", trade_state);
        resultMap.put("transaction_id", transaction_id);
        return resultMap;
    }

参考链接

微信电商收付通支付接口文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_1.shtml
微信API-v3回调报文解密文档:https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值