支付寶異步通知接口思考與實現
對接支付的思考
在業務系統中,對接支付寶支付時。關注到了支付寶的回調通知設計,針對支付寶的異步回調設計,可以抽象出兩個系統對接時,如何保證雙方的數據一致性的方案。因此針對支付寶支付系統的異步通知文檔思考如何實現這種雙方的數據一致性方案。
異步通知特性
- 客戶端程序執行完後必須返回 success。如果商戶反饋給支付寶的字符不是 success 這 7 個字符,支付寶服務器會不斷重發通知,直到超過 24 小時22 分鐘。一般情況下,25 小時以內完成 8 次通知(通知的間隔頻率一般是:4 m、10 m、10 m、1 h、2 h、6 h、15 h)
- 可以看到最核心的就是異常的補償機制,因此如何從給定示例中,提煉思考出一個合理的對接補償方案是本次思考的重點。當然,實現的方式可以有很多種,本文只針對我自己思考的方案進行一個簡單的demo實現。
梳理業務
支付接口
我們的業務接口以支付接口爲例子,提供給調用放創建支付訂單的能力,通過該接口可以發起一次付款,同時根據調用地方提供的信息,異步的通知調用方,業務的執行是否成功。
定時補償機制
系統需要考慮相關定時補償機制,有可能因爲網絡問題,異步通知沒有到位,因此需要利用相關定時任務框架,針對未通知成功的業務數據做通知補償,直到超過設置的最大閾值時間,對此訂單做特殊標記,方便後續業務處理。
demo思路
調用方
- 調用方提供兩個接口
- 一個用於提交支付請求
- 一個接口用於接受回調通知
- 用靜態變量模擬數據庫操作
服務端
- 提供一個接口用於接受調用方的請求
- 實現一個通知管理器,管理所有調用方的通知,用來進行支付狀態的通知。
- 利用靜態變量模擬數據庫的相關操作
- 核心就是考慮通知管理器的補償機制
代碼思路
- 補償機制可以採用調度框架,進行定時發送通知,當滿足一定條件時,取消這個任務即可,因此本文利用quartz說明,如何通過API的方式,動態添加一個任務去執行。
- 下面只是基於quartz的一個簡單demo。當然可以講類似的思路擴展到多個系統異步接口的對接上。
- 也可以利用不同的任務調度框架,或者直接使用spring提供的
spring-retry
重試框架來實現這個功能。
TaskManager
中抽象出兩個操作一個是 開啓任務的操作、一個是關閉任務的操作。通過任務的唯一key執行。
public interface TaskManager {
void startTask(String key);
void removeTask(String key);
}
QuartzTaskManager
是基於quartz實現的定時補償機制,我們這裏可以採用一個簡單的5s補償一次的定時器。
@Component
public class QuartzTaskManager implements TaskManager {
private static final SchedulerFactory SF = new StdSchedulerFactory();
private static final String JOB_GROUP_NAME = "NOTIFY_GROUP";
private static final String TRIGGER_GROUP_NAME = "NOTIFY_TRIGGER";
@Override
@SneakyThrows
public void startTask(String key) {
Scheduler sched = SF.getScheduler();
JobDetail jobDetail = JobBuilder.newJob(NotifyJob.class)
.withIdentity(key, JOB_GROUP_NAME).build();
//觸發器
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(key, TRIGGER_GROUP_NAME)
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever()).build();
sched.scheduleJob(jobDetail, trigger);
//啓動
if (!sched.isShutdown()) {
sched.start();
}
}
@Override
@SneakyThrows
public void removeTask(String key) {
Scheduler sched = SF.getScheduler();
TriggerKey triggerKey = new TriggerKey(key, TRIGGER_GROUP_NAME);
sched.pauseTrigger(triggerKey);
sched.unscheduleJob(triggerKey);
sched.deleteJob(new JobKey(key, JOB_GROUP_NAME));
}
}
有了這樣的操作後,我們可以抽象出一個通知管理類,組合TaskManager
來實現定時補償機制。
public interface NotifyManager {
void addNotify(String key) ;
void removeNotify(String key) ;
void executeNotify(String key);
}
@Component
@AllArgsConstructor
public class DefaultNotifyManager implements NotifyManager {
private final TaskManager taskManager;
private final IPayOrder payOrder;
@Override
public void addNotify(String key) {
taskManager.startTask(key);
}
@Override
public void removeNotify(String key) {
taskManager.removeTask(key);
}
@Override
public void executeNotify(String key) {
System.out.println("執行啊");
Order order = payOrder.getOrderById(key);
// 超過了24個小時沒成功。直接結束
if (Objects.equals(order.getNotifyStatus(), "0") &&
DateUtil.date().between(DateUtil.date(order.getOrderTime()), DateUnit.HOUR) > 24) {
removeNotify(key);
}
String notifyUrl = order.getNotifyUrl();
Map<String, Object> params = new HashMap<>();
params.put("orderId", key);
params.put("status", "success");
String post = HttpUtil.post(notifyUrl, JSONUtil.toJsonStr(params));
if ("success".equals(post)) {
// 更改狀態,去掉定時
removeNotify(key);
System.out.println("收到通知了,,關閉通知");
}
}
}
這樣,我們的通知接口在接受到用戶的請求之後,發送相關事件即可
@Async
@EventListener(TradeEvent.class)
public void tradeEvent(TradeEvent event) {
Order order = (Order) event.getSource();
// 添加一個交易通知
iPayOrder.saveOrder(order);
notifyManager.addNotify(order.getOrderId());
}
@Async
@EventListener(NotifyEvent.class)
public void notifyListener(NotifyEvent event) {
String key = (String) event.getSource();
// 執行通知
notifyManager.executeNotify(key);
}