技術資訊

                      錢被扣了,但是訂單未成功!支付掉單異常如何解決

                      TIME:2020-10-14

                      前言

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

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

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

                      但是可惜沒有人能做到這一點,所以為了處理異??赡軐е碌膯栴},我們不得不需要加上很多額外的設計,用來應對這些異常。

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

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

                      掉單異常

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

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

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

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

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

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

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

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

                      外部掉單

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

                      增加超時時間

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

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

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

                      接收異步通知

                      第二個辦法,接收渠道異步回執通知信息。

                      一般來說,現在支付渠道接口我們都可以上送一個異步回調地址,當渠道端處理成功,將會把成功信息通知到這個回調地址上。

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

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

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

                      1. 對于異步請求信息,一定需要對通知內容進行簽名驗證,并校驗返回的訂單金額是否與商戶側的訂單金額一致,防止數據泄漏導致出現“假通知”,造成資金損失。
                      2. 異步通知將會發送多次,所以異步通知處理需要冪等。

                      掉單查詢

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

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

                      若查詢成功或者明確失?。ū热缬唵尾淮嬖诘龋?,可以更新訂單狀態,并且刪除掉單表記錄。

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

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

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

                      對賬

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

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

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

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

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

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

                      內部掉單異常

                      支付公司內部訂單關系

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

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

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

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

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

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

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

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

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

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

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

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

                      內部掉單異常的原因

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

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

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

                      內部掉單異常解決辦法

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

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

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

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

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

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

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

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

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

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

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

                      總結

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

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

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

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

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

                      上一篇

                      請問Pytest測試框架可以使用關鍵字驅動嗎?

                      下一篇

                      傳統電商將被淘汰,未來3-5年想抓住新機會:就要學會私域電商