錢被扣走了,但是訂單卻未成功!支付掉單異常最全解決方案

樓下小黑哥發表於2020-10-12

前言

好了,迴歸到今天的主題,今天分享一下支付系統中異常一些處理方式。

其實這些處理方式並不只是侷限於支付系統,也可以適用於其他系統,大家可以借鑑,應用到自己系統中,提高自己系統的健壯性。

異常是系統執行不可避免會發生的問題,如果一切都正常,我們的系統設計將會相當簡單。

但是可惜沒有人能做到這一點,所以為了處理異常可能導致的問題,我們不得不需要加上很多額外的設計,用來應對這些異常。

可以說系統設計中,異常處理需要我們著重思考,將會佔據我們大部分的精力。

下面我們先來看下支付系統中最常見的異常:掉單

歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn

掉單異常

一個最常見的支付平臺架構關係如下所示:

上圖我們是站在第三方支付公司支付角度,如果是自己公司的內部支付系統,那麼外部商戶這一塊其實就是公司內部一些系統,比如說訂單系統,而外部支付渠道其實就是第三方支付公司

我們以攜程為例,在其上面發起一筆訂單支付,將會經過三個系統:

  1. 攜程建立訂單,向第三方支付公司發起支付請求
  2. 第三方支付公司建立訂單,並向工行發起支付請求
  3. 工行完成扣款操作,返回第三方支付公司
  4. 第三方支付完成訂單更新並返回攜程
  5. 攜程變更訂單狀態

上面的流程,簡單如下圖所示:

在這個過程就可能會碰到,使用者工行卡已經扣款,但是攜程訂單卻還是待支付,我們通常將這種情況稱為掉單

上述掉單的場景,多數是因為③、⑤環節資訊丟失導致,這種掉單我們將其稱為外部掉單

還有一種極少數的情況,收到 ③、⑤環節返回資訊,但是在④、⑥環節內部系統更新訂單狀態失敗,從而導致丟失支付成功的資訊,這類掉單由於是內部問題,我們通常將其稱之為內部掉單

外部掉單

外部掉單是因為沒有收到對端返回資訊,這種情況極有可能是網路問題,也有可能對端處理邏輯太慢,導致我方請求超時,直接斷開了網路請求。

增加超時時間

對於這種情況,第一個最簡單的解決辦法,適當的增加超時時間

不過這裡需要注意了,在我們增加網路超時時間之後,我們可能還需要調整整個鏈路的超時時間,不然有可能導致整個鏈路內部差事從而引起內部掉單。

畫外音:對接外部渠道,一定要設定網路連線超時時間與讀取超時時間

接收非同步通知

第二個辦法,接收渠道非同步回執通知資訊。

一般來說,現在支付渠道介面我們都可以上送一個非同步回撥地址,當渠道端處理成功,將會把成功資訊通知到這個回撥地址上。

這種情況下,我們只需要接收通知資訊,然後解析,再更新內部訂單狀態。

支付系統異常處理-支付非同步通知

這種情況下,我們需要注意幾點:

  1. 對於非同步請求資訊,一定需要對通知內容進行簽名驗證,並校驗返回的訂單金額是否與商戶側的訂單金額一致,防止資料洩漏導致出現“假通知”,造成資金損失。
  2. 非同步通知將會傳送多次,所以非同步通知處理需要冪等。

掉單查詢

有的渠道可能沒有提供非同步通知的功能,只提供了訂單查詢的介面,這種情況下,我們只能使用第三種解決辦法,定時掉單查詢。

我們可以將這類超時未知的訂單的單獨儲存到掉單表,然後定時向渠道端查詢訂單的狀態。

若查詢成功或者明確失敗(比如訂單不存在等),可以更新訂單狀態,並且刪除掉單表記錄。

若查詢依舊未知,這時我們需要等待下次查詢的結果。

支付系統異常處理-定時查詢

這裡我們需要注意了,有些情況下,有可能無法查詢返回訂單的狀態,所以我們需要設定訂單查詢的最大次數,防止無限查詢浪費效能。

對賬

最後,極少數的情況下,訂單查詢與非同步通知都無法獲取的支付結果,這就還剩下最後一種兜底的解決辦法,對賬。

如果第二天渠道端給的對賬檔案有這一筆支付結果,那麼我們可以根據這個記錄更新直接更新我們內部支付記錄。

之前小黑哥寫過一篇對賬文章,感興趣的可以再看一下:聊聊對賬系統的設計方案

畫外音:穩妥一點,可以先發起查詢,然後根據查詢結果更新訂單記錄。

不過有些極端情況,查詢無法獲取結果,那麼直接更新內部記錄即可。

那如果第二天也沒有這筆記錄的結果,這種情況下,我們可以認為這筆是失敗的。如果使用者被扣款,渠道端內部將會發起退款,將支付金額返回給使用者。所以這種情況可以無需處理。

內部掉單異常

支付公司內部訂單關係

接下來我們講下內部掉單異常,首先我們來看下為什麼會發生內部掉單的異常,這其實跟我們系統架構有關。

如上圖隨所示,第三方支付公司內部表通常為支付訂單與渠道訂單這樣一種 1 比 N 的關係。

支付訂單儲存著外部商戶系統的訂單號,代表第三方支付公司內部訂單與外部商戶的訂單的關係。

而渠道訂單代表著第三方支付公司與外部渠道的關係,其實對於外部渠道系統來講,第三方支付公司就是一個外部商戶。

為什麼需要設計這種關係那?而不是使用下面這種 1 對 1 關係的那?

如果我們使用上圖 1 對1 的訂單關係,如果第一次支付支付失敗,外部商戶可能會再次使用相同訂單號對第三方支付公司發起支付。

這時如果第三方支付公司也拿相同的內部訂單去請求外部渠道系統,有可能外部渠道系統並不支援同一訂單號再次請求。

那其實我們也有其他辦法,生成一個新的內部單號,更新原有支付訂單上內部記錄,然後去請求外部渠道系統。但是這樣的話就會丟失上次支付失敗記錄,這就不利於我們做一些事後統計了。

那其實第三方支付公司也可以不支援相同的訂單號再次發起請求,但是這樣的話,就需要外部商戶重新生成的新的訂單號。

這樣的話,第三方支付公司是系統是簡單了,全部複雜度都交給了外部商戶。

但是現實的情況,很多外部商戶並不是那麼容易更換生成新的訂單號,所以一般第三方支付公司都需要支援同一外部商戶訂單號在未成功的情況下,支援重複支付。

在這種情況下,就需要我們上面的 1:N 的訂單關係圖了。

內部掉單異常的原因

當我們收到外部渠道系統的成功的返回資訊,成功更新了渠道訂單表的記錄。但是由於渠道訂單表與支付訂單表可能不是同一個資料庫,也有可能兩者並不在同一個應用中,這就有可能導致更新支付訂單表的更新失敗。

由於支付訂單是表儲存著外部商戶訂單與內部訂單關係,支付訂單未成功,所以外部商戶也無法查詢得到成功的支付結果。

此時渠道訂單表已經成功,所以上面外部掉單的方法並不適用內部掉單。

內部掉單異常解決辦法

第一種解決辦法,分散式事務。

內部掉單異常,說白就是因為支付訂單表與渠道訂單表無法使用資料庫事務保證兩者同時更新成功或失敗。

那麼這種情況下,我們其實就需要使用分散式事務了。

不過我們沒有采用這種分散式事務,一是因為之前開發的時候市面上並沒有開源成熟分散式事務框架,第二自己自己開發難度又很大。

所以對於分散式事務這一塊,並沒有什麼使用經驗。如果有使用分散式事務解決這類的問題同學,留言去可以評論一下。

第二種解決辦法,非同步補償更新。

當發生內部掉單的情況,即更新支付訂單失敗等情況,可以將這裡支付訂單儲存到一張內部掉單表。

但是這裡可能會有一個問題,我們無法保證儲存到內部掉單表這一步驟也一定成功。

所以說,我們還需要定時查詢,查詢一段時間內支付訂單未成功,而渠道訂單表已成功的支付訂單記錄,然後也將其插入到內部掉單表。

另一個系統應用,只需要定時掃描內部掉單表,將支付訂單成功,然後再刪除內部掉單記錄即可。

這裡需要注意了,當支付訂單表資料量很大之後,定時查詢可能會慢,為了防止影響主庫,所以這類查詢可以在備庫進行。

總結

今天主要介紹了支付系統中的掉單異常,這類異常往往會導致使用者實際已經被扣錢,但是商戶訂單還是等待支付的情況。

這個異常如果沒有很好處理,將會導致客戶使用者體驗很不好,還有可能收到客戶的投訴。

掉單的異常,通常可以外部系統與內部系統。而大部分的掉單都是因為外部系統導致,我們可以增加超時時間,掉單查詢,以及接受非同步通知解決 99% 的問題,剩下 1% 的掉單隻能通過次日的對賬來兜底。

內部系統導致掉單異常是典型的分散式環境資料一致性的問題,這類問題我們可以不需要追求強一致性,只要保證最終一致性即可。我們可以使用分散式事務解決這類問題,也可以定時掃描狀態不一致的訂單,然後在做批量更新。

最後,這次只是介紹支付系統中一類掉單異常,下一篇文章中,再給大家介紹一下支付系統的其他異常,敬請期待!

參考資料

  1. 知乎@天順 談談異常(一)

歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn

相關文章