微信服务商模式分账与动账通知流程梳理

写在开头

微信支付关于分账动账,此功能基本用于服务商抽佣,二级商户分账,账期控制等用途,在开发中我们一般希望于微信分账动账回调通知处理业务逻辑,但是这个回调通知,由于微信文档的描述不清晰,造成我们明明接收地址也配置正确,代码也无误,但就是收不到回调通知,但分账的确又是分账成功了的。造成这种原因其实在于,微信分账动账通知 请求的时候要使用的api商户证书发送动账通知的时候还得使用商户对应的微信平台证书。这俩要求没有明说,这个流程对我们开发者也不透明,估计坑了很多人。

服务商模式分账

服务商模式分账要开通的权限,特别注意第四、五点:

  1. 服务商绑定下属特约商户 ,出资子商户一般是服务商下的特约商户,两者是通过特约商户绑定关系;
  2. 特约商户申请开通分账功能 ,特约商户要走审批流程申请开通分账权限;
  3. 服务商配置分账比例,服务商来配置分账比例,最大30%,即出资商户最多保留交易资金的 70%;
  4. 服务商已开通支付证书和ApiV3秘钥 ,分账时请求用的是服务商的私钥证书,验签用的是服务商的ApiV3秘钥;
  5. 收资商户已开通支付证书和ApiV3秘钥 , 发送动账通知请求的时候要使用api商户证书,还得使用商户对应的微信平台证书;接收动账通知请求后,我方进行验签用的是收资商户的ApiV3秘钥;

服务商分账资料准备清单

清单如下:

角色商户号绑定的APPID
服务商16xxxxxxxxwx20230711xx,对应appid
出资商户17xxxxxxxx分账接收方为商户时不需要,为个人时必需
收资商户18xxxxxxxx可以不需要

在这里插入图片描述

关键第一步:配置分账动账通知service_notify_url

可以参考社区文档: link.

图片:
未配置的图例
收资商户-配置好了的样子

关键第二步:分账接收方商户开通证书&&APIV3秘钥

我这里是求证微信官方技术人员,他们看后台日志帮助排查到的。回调请求发起需要商户API证书和对应商户生成的微信平台证书, 官方文档压根是没有明说这一步的,因为他们默认以为你这个商户是正常,且可以用APIV3秘钥来加签支付-分账-发起回调等等。而我们作为被回调方,也无从排查起。这一步是真的有点坑

配置链接: link
没申请过API证书的时候是这样的

微信分账序列图UML

Mermaid. 微信分账序列图:

分账出资商户 服务商 收资商户 发起一笔100元分账请求 分账请求加签时用服务商证书、ApiV3密钥 验签时用服务商微信平台证书\n(由服务商证书、ApiV3密钥申请获得) 分账请求服务商限流 300QPS 分账收资20元,分账比例20% 解冻剩余80元,分给出资商户 step1 - 配置收资商户端动账通知地址(HTTP也可以) step2 -开通收资商户个人证书、ApiV3密钥(用于动账通知请求加签) step3 -动账通知验签使用收资商户对应微信平台证书、ApiV3密钥 分账出资商户 服务商 收资商户

动账通知收不到-各种踩坑记录

一、网上答案别信
二、如果配置没问题+商户正常+HTTP通知地址是可以POSTMASN打通的一定要找官方技术排查日志

微信开放社区答案 - 按照这个排查流程也解决不了问题

https://developers.weixin.qq.com/community/develop/doc/000ece52dcc7e851c26ca8edf51400?_at=1689751992741

至于说非商户摸根本不会动账通知,这是扯淡的,别信

https://blog.csdn.net/qq_41220904/article/details/106018778

具体代码

分账动账回调接收方法

/**
     * 异步回调方法
     *
     * @param response       response
     * @param request        request
     */
    @PostMapping("/notify/profitsharing")
    public void profitsharingNotify(HttpServletResponse response, HttpServletRequest request) throws IOException {

        logger.info("进入微信代付分账异步支付回调方法");
        Map<String, Object> parameterMap = new HashMap<>(16);
        logger.info("######### start #########微信代付分账异步通知返回结果{}", parameterMap);
        if (CollectionUtil.isEmpty(parameterMap)) {
        	//从Header中取相关参数
            parameterMap = getRequestHeadToMap(request, parameterMap);
            //从Body中取完整body参数
            String requestBody = getRequestBody(request);
            parameterMap.put("requestBody",requestBody);
            logger.info("######### end #########微信代付分账异步通知返回结果{}", parameterMap);
        }
        ProfitsharingResult result = weChatWithholdProfitsharing.notifyVerify(parameterMap);
        try {
            logger.info("分账回调验签成功,验签结果为:" + result);
            profitsharingCompleteService.profitsharingComplete(result);
            logger.info("订单{} 开始订单完成处理", result.getOutTradeNo());
            if (StringUtils.isNotBlank(result.getMessage())) {
                logger.info("回写上游信息:{}", result.getMessage());
                response.getWriter().print(result.getMessage());
            }
        } catch (TradeBizException e) {
            if (TradeBizException.TRADE_ORDER_STATUS_ERROR == e.getCode()) {
                response.getWriter().print(result.getMessage());
            } else {
                logger.error("订单流水号:{}, 通道方异步通知系统异常:{}", result.getOutTradeNo(), e);
            }
        } catch (IOException e) {
            logger.error("通道方异步通知系统异常", e);
        }
    }

/**
     * 将通知参数转化为字符串
     * @param request
     * @return
     */
    protected String getRequestBody(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

分账动账接收验签代码示例 - 中间层的异常处理和出入参转换这里省略,自己写吧

// 分账动账接收验签代码示例
/**
     * 验签方法
     *
     * @param map 验签参数
     * @return true: 验签成功
     */
    public static PSNotifyResult verify(Map<String, Object> map, X509Certificate certificate, String receiveApiV3Key) {
        //微信平台传入的时间戳 - 注意解析获取的字符串均为小写
        String timestamp = Convert.toStr(map.get("wechatpay-timestamp"));
        //微信平台传入的随机字符串
        String nonce = Convert.toStr(map.get("wechatpay-nonce"));
        //微信平台传入的签名
        String signature = Convert.toStr(map.get("wechatpay-signature"));
        //微信平台传入的验签序列号
        String serial = Convert.toStr(map.get("wechatpay-serial"));
        // 初始化 RequestParam  - 微信SDK有,requestBody为验签接口获取的 HttpServletRequest流读取后的所有参数
        RequestParam requestParam = new RequestParam.Builder()
            .serialNumber(serial)
            .nonce(nonce)
            .signature(signature)
            .timestamp(timestamp)
            // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
            .signType(Convert.toStr(map.get("wechatpay-signature-type")))
            .body(Convert.toStr(map.get("requestBody")))
            .build();

        // 初始化 NotificationConfig  - 微信SDK有
        NotificationConfig rsaNotificationConfig = new RSANotificationConfig.Builder()
            // 注意这里必须是 收资商户号的ApiV3Key
            .apiV3Key(receiveApiV3Key)
            // 注意这里必须是 收资商户生成的对应平台证书
            .certificates(certificate)
            .build();
        // 初始化 NotificationParser - 微信SDK有
        NotificationParser parser = new NotificationParser(rsaNotificationConfig);
        // 自定义验签结果PSNotifyResult 
        PSNotifyResult decryptObject = parser.parse(requestParam, PSNotifyResult.class);
        log.info(" ######## 服务商模式解密的报文-PSNotifyResult对象 ######### {}", GsonUtil.toJson(decryptObject));
        return decryptObject;
    }

	/**
	 * @Desc: 分账回调通知对象
	 * @Author HEDY
	 * @Date 2023/6/28 14:12
	 */
	@Data
	@ToString
	@NoArgsConstructor
	public class PSNotifyResult {
	
	    @SerializedName("sp_mchid")
	    private String spMchid;
	
	    @SerializedName("sub_mchid")
	    private String subMchid;
	
	    @SerializedName("transaction_id")
	    private String transactionId;
	
	    @SerializedName("out_order_no")
	    private String outOrderNo;
	
	    @SerializedName("order_id")
	    private String orderId;
	
	    @SerializedName("receiver")
	    private PSNotifyReceiver receiver;
	
	    @SerializedName("success_time")
	    private String successTime;
	
	}
	/**
	 * @Desc: 分账响应-接收对象
	 * @Author HEDY
	 * @Date 2023/6/28 14:12
	 */
	@Data
	@ToString
	@NoArgsConstructor
	public class PSNotifyReceiver {
	
	    @SerializedName("type")
	    private String type;
	
	    /**
	     * MERCHANT_ID类型下对应商户号
	     */
	    @SerializedName("account")
	    private String account;
	
	
	    @SerializedName("amount")
	    private Integer amount;
	
	
	    @SerializedName("description")
	    private String description;
	}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值