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

合单支付

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

预支付

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

    /**
     * 预支付
     */
    @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

微信支付电商收付提供了全面的二级商户资金管理解决方案,其中分账功能是关键。为确保规性与账期控制,首先要确保二级商户已过接口入驻成为微信支付的二级商户。在电商收付的分账操作中,电商平台可以根据预设的业务规则,如按比例分账或按订分账等方式,将交易款项分配给相应的二级商户。 参考资源链接:[微信支付V3电商收付:解决二清问题](https://wenku.csdn.net/doc/64532619ea0840391e774c14?spm=1055.2569.3001.10343) 分账比例可以预先在系统中设置,最高可达30%,并且在资金解冻后,可以完成对二级商户的账期管理。操作时,需要过调用相应的API接口进行,确保分账信息的准确性和及时性。例如,可以使用分账API,传入相关商户号和分账金额,系统会自动处理分账到对应商户账户。 此外,账期控制方面,电商平台可以利用支付功能整多个二级商户的订进行统一支付操作,资金会直接冻结在各自的商户账户中。平台可自行设定冻结资金的解冻规则,例如,按照实际账期解冻,以满足账期控制的要求。 为避免账期控制失效,建议不要过分依赖限制提现来控制账期。二级商户仍然可以微信支付官方渠道申请提现。因此,电商平台应确保其账期管理规则与提现政策相协调,以免影响资金流的正常运转。 最后,建议电商平台定期审查和优化分账规则,确保账期管理符最新的监管要求和商业协议,同时保障二级商户的法权益。使用电商收付的分账和账期管理功能,可以有效地解决二清问题,并确保电商平台的资金流规、透明和安全。 参考资源链接:[微信支付V3电商收付:解决二清问题](https://wenku.csdn.net/doc/64532619ea0840391e774c14?spm=1055.2569.3001.10343)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值