支付系統返回碼設計及映射避坑指南
在支付系統的構建與管理中,返回碼的設計和映射是確保交易順暢進行的關鍵環節。本文將深入探討支付系統返回碼的重要性、常見問題及其解決方案,旨在幫助開發者和支付系統管理者避免潛在的陷阱,提升用戶體驗,并確保交易的準確性和安全性。
我在支付行業呆了十來年,和返回碼映射導致的線上生產問題交手無數次,有很多是因為影響用戶體驗,也有一些直接導致了線上資損,所以有必要開一篇小文聊一下。
一、返回碼還是錯誤碼
有些人喜歡用“返回碼”,有些人喜歡用“錯誤碼”。兩者本質相同,都是用于標識通信或交易的狀態。但我更傾向于使用“返回碼”,因為它不僅涵蓋錯誤狀態,還包括成功狀態,而成功并非錯誤,所以使用“返回碼”更為合適。
二、返回碼的本質
我很喜歡探尋事物的本質,那么返回碼的本質是什么?我覺得是解決2個問題:
- 標識單一系統內的業務處理結果:在一個單一系統內,如何表達業務處理的結果,比如參數不對,余額不足,還是成功等。
- 完成異構系統或應用之間的處理結果同步:在多個系統之間如何同步業務處理的結果,比如渠道的結果同步給支付平臺的網關系統,網關系統同步給支付引擎等。
三、返回碼最核心的關注點
返回碼最核心的關注點也只有2個:
- 同一系統內定義是否足夠清晰明確。減少歧義,減少誤解。
- 異構系統或應用之間的映射是否足夠準確。映射不好,輕則影響用戶體驗,重則有資損。
四、曾經碰到過的坑
踩過的坑很多,大致可以歸為以下幾類:
- 對客映射不準確,導致用戶持續重試失敗,影響用戶體驗。比如“余額不足”或“風控不過”,返回給用戶“系統異常,請重試”,有些用戶就瘋狂地重試。
- 外部渠道沒有明確成功或失敗,內部映射成明確成功或失敗,造成資損。比如:
支付同步請求渠道響應還沒有回來,發起了查詢,查詢返回“訂單不存在”,直接推進失敗,但最后銀行扣款成功。
退款同步請求渠道響應返回“系統異?!?,直接推進到失敗,但最后銀行退款成功。
3. 外部渠道有雙層返回碼,沒有做完整判斷。比如第1層只表示接口是否成功(通信層面),第2層才是表示業務是否成功,但是只判斷了接口層面,就推進了內部訂單的業務狀態。
4. 返回碼制定過于籠統或太細。
五、最佳實踐
5.1. 基本原則
- 制定統一返回碼規范:在團隊或公司層面制定統一的返回碼規范,明確各個返回碼的含義,確保各模塊一致性。
- 嚴格遵守返回碼定義:研發人員在編碼時,應嚴格按照規范返回對應的返回碼,確保返回碼與實際狀態匹配。明確成功才推進成功,明確失敗才推進失敗,其它全部按“未知”處理。
- 區分接口/通信成功與業務成功。
- 流入到平臺的(支付、充值等),謹慎映射到成功。從平臺流出的(提現,代發等),謹慎映射到失敗。
5.2. 三級返回碼體系
外部商戶對接支付平臺,支付平臺內部有自己的業務處理,同時還對接了外部的很多渠道,所以需要管理三套返回碼:
- 提供給商戶OpenAPI使用的返回碼:這塊可以直接參考微信支付、支付寶等機構的門戶網站。
- 內部各應用使用的標準返回碼:用于內部業務的處理。
- 渠道返回碼:外部渠道提供的返回碼,每個渠道都不一樣,需要映射到內部標準返回碼。
為什么需要三層?主要有3個原因:
- 內部應用使用的標準返回碼需要精確,便于內部系統運行的監控。
- 給商戶OpenAPI的返回碼需要業務語義明確,但不能過于精確。比如內部出現“卡的有效期不正確”,對外則是“卡號或持卡人或有效期不正確”,避免輪詢攻擊。
- 外部渠道返回碼不能全部一對一映射到內部,因為外部渠道太多,容易膨脹。
5.3. 商戶OpenAPI返回碼設計
這部分建議直接參考微信支付、支付寶或者ISO20022標準,這幾家代表了行業的最高水準。
一般來說最少有兩個字段:resultCode和message,一個表示碼,一個表示碼的描述。
也可以增加一個參數result,使用S,F,U表示業務狀態的成功、失敗、未知。
如果是查詢類接口,一定要明確說明是接口成功,還是業務成功。
5.4. 內部標準返回碼設計
支付平臺內部也分了不同域,建議使用一個共同的規范,比如:RS+子系統編號+錯誤級別+具體返回碼。具體如下圖所示:
說明:
- 1-2位:固定值RS,Result縮寫。
- 3-5位:子系統編號。比如001:收銀支付,002:會員等??煞奖愣ㄎ荒膫€系統出的問題。
- 6位:錯誤類或等級。比如:0:正常,1:業務級異常,2:系統級異常。
- 7-9位:各業務線自己定。比如:1xx:參數相關,2xx:數據庫相關,3xx:賬戶狀態/Token狀態相關等。
- 這樣的好處在于,每個子域或子系統既有全局的規范,又有自己的靈活性,減少溝通成本。
- 注意:上面只是寫了resultCode,還需要有message,用于描述這個碼代表什么語義。
- 核心代碼(注:使用chatGPT o1生成,請自行增刪):
public interface IResultCode {
String getResultCode();
String getMessage();
}
public enum PaymentResultCode implements IResultCode {
SUCCESS( "0000", "success"),
FAIL("2998", "fail"),
SYSTEM_ERROR("2999","system error"),
// 額度相關 11XX
INSUFFICIENT_FUND("1101", "insufficient fund"),
// 風控相關 12XX
RISK_REJECTED("1201", "risk rejected"),
// DB相關 21XX ;
private static final String PREFIX = "RS";
private static final String SYSTEM_CODE = "101";
private String codeNumber;
private String message;
@Override
public String getResultCode() {
return PREFIX + SYSTEM_CODE + codeNumber;
}
@Override
public String getMessage() {
return message;
}
PaymentResultCode(String codeNumber, String message) {
this.codeNumber = codeNumber;
this.message = message;
}
}
5.5. 渠道返回碼映射
每個渠道的返回碼都是不一樣的,所以需要設計外部渠道返回碼映射到內部標準返回碼。需要遵守幾個原則:
- 只有明確成功,才能映射到成功。
- 只有明確失敗,才能映射到失敗。比如渠道返回:訂單不存在,或者系統異常,不能直接映射到失敗,因為有可能會成功。
- 涉及個人敏感信息或內部系統敏感信息的,需要轉成模糊返回碼和描述出去,不能給最終用戶展示精確信息。比如內部一個系統宕機,不能直接把異常拋出去。有效期錯誤也需要映射成“卡號或姓名或有效期不正確”。
- 與敏感信息無關的,越準確越好,避免用戶無謂的重試。比如余額不足,就不要映射成“系統異常,請重試”。
- 查詢類接口,務必要區分是接口成功,還是業務成功。
具體的技術實現,通過使用映射表就足夠,加到緩存中,增加運算速度。如果找不到映射關系,就全部轉到一個默認的返回碼上面,同時對這個默認返回碼做監控,定期把這些沒有做映射的返回碼映射到正確的返回碼上面去。避免應該把類似“余額不足”映射成了“系統異常請重試”的場景。
5.6. 返回碼監控與告警
大部分團隊都會監控成功率,只有少數團隊會監控返回碼或定期分析返回碼。然而當交易量足夠大時,成功率的波動可能只有0.5%,很難看出異常,而如果去分析返回碼,則可以快速看出并定位問題。
一般來說,有幾個建議:
- 實時監控返回碼的突變異常。比如:最近10分鐘,某個返回碼突然增加50%,或者比明天突然增加50%等。都需要介入看看。
- 定期觀察返回碼曲線表。如果某個返回碼連續多天持續在上升,一般都是有問題的。
- 建立返回碼全鏈路映射大盤。比如渠道返回“余額不足”或“風控不通過”,映射到用戶展示“系統異常,請重試”,那就有問題。而且類似這種情況還非常常見。
- 定期分析用戶支付行為。以前在分析用戶行為時,發現同一用戶重試了20多次,最后排查發現,就是返回碼映射不準確,導致用戶無謂的重試。
六、結束語
卷用戶體驗和成功率時,往往需要于細微處見真章,而返回碼的設計和映射就是如此。做得不好,輕則影響用戶體驗,重則資損。
希望對大家在設計標準返回碼及映射時有所啟發,也歡迎點贊轉發。
這是《圖解支付系統設計與實現》專欄系列文章中的第(48)篇。歡迎和我一起深入解碼支付系統的方方面面。
作者:隱墨星辰,公眾號:隱墨星辰
本文由 @隱墨星辰 原創發布于人人都是產品經理。未經作者許可,禁止轉載
題圖來自Unsplash,基于CC0協議
該文觀點僅代表作者本人,人人都是產品經理平臺僅提供信息存儲空間服務
- 目前還沒評論,等你發揮!