关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言
支付是业务系统比较常见功能,而支付回调是串联业务系统的关键。你有没有遇到这样的问题:
- 每个业务单独一套回支付调,导致支付的混乱,无法统一管理
- 公用的支付回调,里面堆积了所有的业务逻辑,让支付变的很重
- 多套测试环境,但是支付回调只能配置一个环境,测支付的时只能将代码发布到配置了支付回调的环境上。
虽然上面的问题也不算是大问题,但是秉着是问题就应该解决的态度,我们以微信支付回调为例,一起唠唠解决方案。
02 问题背景
单一的支付本身没有问题,但是随着业务模块的增多,支付的场景也变多,支付渠道也随之增加,支付就需要抽象成一个独立的系统,专门处理支付以及回调。
2.1 独立的支付回调
这种方式有好有坏,相互独立互不影响。但是随着相同渠道支付的增多,冗余的代码就会变多,系统就会变的混乱。

冗余代码不说,微信支付的JSAPI支付需要配置授权目录,最多5个。这个也就直接限制支付回调地址的配置。

2.2 支付回调冗余业务
这中当时自然是最常用的方式之一,为了统一支付,将回调统一到一个地方,然后统一处理各自业务的逻辑。

这样的问题,导致所有的业务逻辑代码都堆积在支付模块里。一旦需要发布,所有的支付回调都会跟着收到影响。
2.3 多套环境不互通
我们生产环境经常遇到的是同一套多节点部署,用于负载均衡,平摊单节点访问的压力。这种情况支付回调配置任意一台即可,没有什么特别的。
这里要说的是,线下为了应对快读迭代的业务,会部署多套测试环境。每一套测试环境包含了一整套业务代码。各个测试环境之间共享数据库,但是代码相互隔离。

这种环境下,支付回调又该如何配置呢?
03 核心设计
带着上面的问题,看看有没有什么好的设计方案呢?
简单来说,支付的回调分类两种:同步回调和异步回到。因为同步回调阻塞业务,异步回调应用更加广泛。这两种姑且称之为被动回调。
还有一种就简单了,就是根据支付方的查询接口,直接查询当前订单的订单状态。
2.1 异步回调
在设计异步回调之前,我们先来考虑一下回调接口是否属于高并发场景?
我们知道异步回调是有时间间隔的:

如果出现网络抖动,可能出现重复的通知。如果订单量大的情况下,当属高并发场景。
所以我们处理需要增加锁的控制,保证系统的幂等性。我们看看有道友就遇到这样的问题,在微信开放社区提问:

为了保证统一回调,我们可以将回调地址固定位一个,回调回来之后再调用其他业务系统完成业务即可。
MQ解耦
调用业务系统,通过RPC调用或者微服务调用,无法解耦,可以采用中间件(MQ)的方式,将主要参数推送到消息中,然后在各自的业务系统重监听消息,消费消息即可。

MQ可以使用广播的方式通知到每一个业务端,这个就完美解决了上面你那的三个问题。
java
@PostMapping("/pay/callback")
public String handleCallback(HttpServletRequest request) {
String rawBody = request.getReader().lines().collect(Collectors.joining());
// 1. 验签
if (!wechatPayService.verifySignature(rawBody,
request.getHeader("Wechatpay-Signature"))) {
return "FAIL";
}
// 2. 解析 + 幂等落库
WxPayCallbackNotifyRequest callback = parseXml(rawBody);
if (!saveCallbackRecord(callback)) {
return "SUCCESS"; // 已处理过
}
// 3. 投递 MQ(这条消息会被所有订阅的机器消费)
messageQueue.publish("wechat.pay.callback", callback);
// 4. 快速返回
return "SUCCESS";
}
HTTP广播
如果系统没有接入MQ,HTTP调用无疑也是一种比较优雅的做法。但是需要注意的是,我们需要在支付流水中记录真正回调的业务路径,用来通知业务系统修改对应的逻辑。

这里需要注意的是:notifyUrl在生成流水时,就需要记录真正的业务回调地址。
java
@PostMapping("/pay/callback")
public String handleCallback(HttpServletRequest request) {
String rawBody = request.getReader().lines().collect(Collectors.joining());
WxPayCallbackNotifyRequest callback = parseXml(rawBody);
// 幂等落库
if (!saveCallbackRecord(callback)) {
return "SUCCESS";
}
// 同步通知各业务机器(异步并行)
String notifyUrl = "http://machine-b/pay/callback";
CompletableFuture.runAsync(() -> notifySystem(notifyUrl, callback));
return "SUCCESS";
}
private void notifySystem(String url, WxPayCallbackNotifyRequest callback) {
try {
restTemplate.postForObject(url, callback, String.class);
} catch (Exception e) {
// 记录失败,进入重试队列
retryQueue.add(new RetryTask(url, callback));
}
}
这种方式可以解决问题中的前两个,但是处理类似2.3的问题,就需要将业务回调地址变成可配置的,方可解决多环境的问题。
2.2 主动查询
主动查询作为一种补偿机制使用。订单长时间没有收到回调,就可以通过订单查询的方式查询订单结果。

因为异步回调不保证一定调用成功,主动查询也是一种必要的设计手段。

主动查询不依赖支付回调,没有环境要求,更加通用。
2.3 完整设计
完整的支付回到,一定要考虑异步回调和主动查询的接才能保证系统的健壮性。

04 小结
多环境下,每次支付回调的调试都让人心烦。一劳永逸的方案,可以直接躺平了......
老铁们,你们的支付回调是怎么玩的?