如何防止訂單重複支付?

在進行線上支付的時候,有可能會出現訂單重複支付的情況,要如何防止呢?本文作者對此進行了分析,一起來看一下吧。

如何防止訂單重複支付?

想必大家對線上支付都不陌生,今天和大家聊聊如何防止訂單重複支付。

01 看看訂單支付流程

我們來看看,電商訂單支付的簡要流程:

如何防止訂單重複支付?

訂單錢包支付流程

從下單/計算開始:

1)下單/結算

這一步雖然不是直接的支付起點,但是支付相關的金額等等資訊都來自結算,此時訂單的狀態是未支付。

2)申請支付

使用者選擇申請支付,客戶端呼叫支付服務,此時在系統內產生一筆支付流水,這筆流水的狀態是未支付。

3)發起支付

支付服務呼叫三方支付,通常這種錢包類的支付,在發起支付這一步,會響應一些支付的連結,客戶端會對連結進行對應的處理。

4)錢包支付

使用者進行支付,通常是透過對應的錢包進行的,大家可以回憶一下自己在購物中,支付的過程,不同的端,對錢包支付的處理是不太一樣的。

如何防止訂單重複支付?

京東PC端支付頁

APP端: 在國內,購物大部分都是在APP端,產品經理會想法設法把使用者帶到APP,為什麼我的示例圖都用京東,不用淘寶呢?因為我拿UC開啟淘寶,會直接跳轉APP。

APP端的錢包支付,我們應該都非常熟悉,一般是拉起錢包,支付。

如何防止訂單重複支付?

APP支付

WAP端:手機的網頁站,WAP端的支付一般是直接拉起對應的錢包,如果拉起錢包失敗,就跳轉介面。

如何防止訂單重複支付?

京東支付WAP端

PC端:PC端,通常是開啟收銀臺,展示一個二維碼,透過錢包掃碼支付,下面是京東的微信支付掃碼頁。

5)支付回撥

使用者完成支付後,三方支付平臺,會回撥商戶,通知支付結果。

6)同步訂單狀態

支付服務在確認支付完成後,會向訂單服務同步支付的結果,訂單服務變更訂單的狀態,由未支付—待發貨,客戶端透過輪詢、長連線,或者服務端主動推送的方式,在介面上變更訂單狀態。

我們再從支付流水的角度看一下支付狀態的變化:

如何防止訂單重複支付?

支付狀態變化

從未支付,到有支付結果的終態,中間還有一箇中間狀態——支付中。

使用者透過開啟錢包—完成支付—支付回撥,這段時間的支付流水就處於支付中。為什麼要花這麼多篇幅來講支付的業務流程、互動過程呢?因為我認為,防止訂單的重複支付,不止是技術上的問題,也是業務和產品上的問題。

02 為什麼訂單會重複支付

1. 未防重導致的重複支付

我們可以看到PC端支付,是掃描二維碼,這些二維碼,就是對應相應的支付流水,假如使用者重複點選支付,如果不做防重的話,會生成兩筆支付流水,也就是兩個不同的二維碼,要是使用者分別掃了兩個不同的支付碼,那麼毫無疑問,就會產生重複支付。

2. 掉單導致的重複支付

“我明明付款了,為什麼我的訂單還沒支付呢?”

這就是所謂的“掉單”:

外部掉單:三方支付的支付狀態沒有同步或者沒有及時同步到商城,這叫外部掉單。

內部掉單:支付服務的狀態沒有同步到訂單,或者客戶端沒有及時獲取到訂單狀態,這叫內部掉單。

使用者一看,自己付了款,結果商城裡訂單還未付款,但是又特別想要,可能就會再下一單,這樣就重複支付了。

3. 多渠道導致的重複支付

我們國內支付的體驗還是非常快捷的,大家可能沒有感覺,如果瞭解過海外支付的可能瞭解,很多支付的渠道,消耗的時間非常長。

比如使用者保羅選擇了一種支付方式Boleto,結果支付的網點離保羅他們村太遠了,保羅又選擇了Paypal支付,保羅去趕集的時候,又順手去網點把Boleto的這一筆支付了,結果就重複支付了。

這種情況大家可能很少遇到,我們可以用美團下一個單,先開啟微信支付,不要支付啊,接著回到美團,開啟支付寶,用支付寶支付完成後,用微信接著支付,大家猜猜,兩筆支付是不是都能成功?答案是可以。

如何防止訂單重複支付?

美團多渠道支付

04 如何防止訂單重複支付

1. 加鎖

不管是申請支付、還是支付回撥,都應該以訂單維度加鎖,防止併發下的重複操作。

加鎖,毫無疑問,也是分散式鎖,通常我們會選擇Redis分散式鎖。

如何防止訂單重複支付?

加鎖

2. 快取結果

申請支付成功,支付回撥成功,都應該快取結果。

再申請支付,收到成功回撥的時候,都應該先去檢查支付的狀態。

如何防止訂單重複支付?

3. 支付中流水取消

假如說,使用者重複支付了,再次申請支付的時候,如果已經申請支付成功了,那麼這筆支付肯定是要拒絕的。

但是,要是已經存在的這筆流水還在支付中呢?——我們不確定它是成功還是失敗,肯定是不能拒絕支付的,因為可能使用者支付失敗了,但是狀態還沒同步,這樣肯定是不行的。

所以,我們可以取消掉正在支付中的流水,再進行支付。

如何防止訂單重複支付?

支付中流水取消

4. 已支付流水退款

現在又有新的問題了,假如發起支付的時候,有流水正在支付中,如果第三方支付平臺不支援取消支付,或者使用者新的支付是透過不同的渠道,我們希望儘可能提高使用者的支付成功率,怎麼辦呢?

我們可以在發起支付的時候,訂單還在支付中的情況下,允許使用者發起多筆支付,在支付回撥的時候,檢查使用者是否已經有成功流水,對後來的流水進行退款處理。

如何防止訂單重複支付?

支付回撥

當然,退款是個很危險的操作,畢竟錢退了,可就很難追回來,一定要做好風險的控制。

5. 主動輪詢&重試防止掉單

1)主動輪詢防止外部掉單

如果因為故障沒有收到回撥,或者沒有及時收到回撥,就可能會發生所謂的外部掉單。

防止外部掉單的關鍵,就在於,不能傻傻地只等三方的回撥通知,而要主動去查詢,使用者發起支付的3s之後,就可以發起輪詢了,直到拿到支付流水的最終狀態,主動輪詢,一般可以這麼實現:

如何防止訂單重複支付?

輪詢

①定時任務輪詢

使用定時任務,掃描表中支付中的流水,主動查詢支付的狀態,定時任務的實現方式有很多,執行緒池、排程框架、分散式排程框架等等。

定時任務輪詢的缺點有兩個:

對資料庫有一些壓力,觀察監控,會發現定時任務掃表的時候,有時候會造成資料庫的一些“峰刺”

不便調整頻率,實際上,使用者發起一筆支付之後,一般都會在10s-1min中完成支付,越往後,使用者完成支付,所以輪詢梯度進行,會更合理一些,輪詢的間隔可以設定成類似這種:3s、10s、30s、3min……

②延時訊息輪詢

另外一種方式就是使用延時訊息,使用者發起支付之後,傳送一個延時訊息,消費到延時訊息之後,查詢流水支付狀態,沒有拿到最終狀態,就再發一個延時訊息。延時訊息的好處是對資料庫的壓力沒有那麼大,輪詢的梯度也可以進行控制,缺點是實現起來複雜一些,而且要維護訊息佇列。

2)同步+非同步防止內部掉單

支付服務在收到非同步通知回撥、或者主動輪詢到流水的最終狀態後,要通知訂單服務支付流水的變化,訂單服務同步更新訂單的狀態,這個過程要儘可能保證通知成功,可以採用同步+非同步的方式。

同步呼叫:支付服務呼叫訂單服務的通知介面,有可能會因為網路等等的原因失敗,也可以重試,但是根據經驗,如果網路出現一些波動,重試很可能也會失敗。

非同步通知:支付服務還應該傳送一個支付成功的訊息,訂單服務可以利用訊息佇列的重試機制,來儘可能保證支付狀態的同步。

這裡還有一個問題,客戶端如何同步這個狀態?因為可能服務端更新了訂單狀態,但是客戶端的介面上還是未支付,得使用者主動重新整理一下,才能拿到最新的狀態,這樣明顯是不太合適的。

服務端、客戶端的狀態同步,無非就拉和推:

拉:很簡單,就是客戶端在使用者跳回訂單狀態頁的時候,輪詢一會,如果使用者完成支付,通常很短時間就能獲取到狀態的變更,當然這種方式對客戶端的效能會有一些影響,而且很出現狀態同步“漏網之魚”的情況。

推:推的實現有些麻煩,Web通常是用Websocket,對APP端的推送,一般採用第三方的推送平臺。

6. 客戶端支付儘可能不外跳

不管從產品的角度,還是技術的角度,客戶端發起支付這一步,其實應該儘可能地不要外跳,PC端使用支付服務生成的支付碼,而不是跳轉;移動端網頁、APP在應用內展示支付頁,當然這個是由第三方支付平臺決定的。

如何防止訂單重複支付?

在UC內內嵌支付寶

不知道大家留意到了沒有,現在的支付寶,已經做到了不用拉起錢包,在應用內就可以完成支付,這個對於商家的意義還是比較大的,對使用者體驗、支付成功率,都有正面的作用,相信以國內的內卷程度,其它支付供應商,一定會“跟進”的。

好了,關於如何防止重複支付,就講到這裡。對於支付,老三也只是初窺門徑,希望各位大佬不吝指教。

參考:

[1] 服務端如何防止重複支付

本文由 @三分惡 原創釋出於人人都是產品經理。未經許可,禁止轉載

題圖來自Unsplash,基於CC0協議

該文觀點僅代表作者本人,人人都是產品經理平臺僅提供資訊儲存空間服務。

頂部