写在开头
微信支付关于分账动账,此功能基本用于服务商抽佣,二级商户分账,账期控制等用途,在开发中我们一般希望于微信分账动账回调通知处理业务逻辑,但是这个回调通知,由于微信文档的描述不清晰,造成我们明明接收地址也配置正确,代码也无误,但就是收不到回调通知,但分账的确又是分账成功了的。造成这种原因其实在于,微信分账动账通知 请求的时候要使用的api商户证书 , 发送动账通知的时候还得使用商户对应的微信平台证书。这俩要求没有明说,这个流程对我们开发者也不透明,估计坑了很多人。
服务商模式分账
服务商模式分账要开通的权限,特别注意第四、五点:
- 服务商绑定下属特约商户 ,出资子商户一般是服务商下的特约商户,两者是通过特约商户绑定关系;
- 特约商户申请开通分账功能 ,特约商户要走审批流程申请开通分账权限;
- 服务商配置分账比例,服务商来配置分账比例,最大30%,即出资商户最多保留交易资金的 70%;
- 服务商已开通支付证书和ApiV3秘钥 ,分账时请求用的是服务商的私钥证书,验签用的是服务商的ApiV3秘钥;
- 收资商户已开通支付证书和ApiV3秘钥 , 发送动账通知请求的时候要使用api商户证书,还得使用商户对应的微信平台证书;接收动账通知请求后,我方进行验签用的是收资商户的ApiV3秘钥;
服务商分账资料准备清单
清单如下:
角色 | 商户号 | 绑定的APPID |
---|---|---|
服务商 | 16xxxxxxxx | wx20230711xx,对应appid |
出资商户 | 17xxxxxxxx | 分账接收方为商户时不需要,为个人时必需 |
收资商户 | 18xxxxxxxx | 可以不需要 |
关键第一步:配置分账动账通知service_notify_url
可以参考社区文档: link.
图片:
关键第二步:分账接收方商户开通证书&&APIV3秘钥
我这里是求证微信官方技术人员,他们看后台日志帮助排查到的。回调请求发起需要商户API证书和对应商户生成的微信平台证书, 官方文档压根是没有明说这一步的,因为他们默认以为你这个商户是正常,且可以用APIV3秘钥来加签支付-分账-发起回调等等。而我们作为被回调方,也无从排查起。这一步是真的有点坑。
配置链接: link
微信分账序列图UML
Mermaid. 微信分账序列图:
动账通知收不到-各种踩坑记录
一、网上答案别信
二、如果配置没问题+商户正常+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;
}