移動端支付系統如何設計有效地防重失效機制?

tianxiaoxu發表於2018-08-31

目前在網際網路應用的大部分支付場景中,對接支付寶、微信移動支付產品這樣需要使用者參與支付流程的支付方式已經變得非常普遍,類似的還有PC端銀行網銀支付;而通過繫結使用者銀行卡、對接銀行卡快捷支付通道直接扣款的支付方式,雖然還在電商、保險、網際網路金融、租房等行業被廣泛應用,但是隨著微信錢包、支付寶錢包這類移動網際網路支付方式的興起,使用者規模的迅速增長,再加上使用者銀行卡資訊保安、直連銀行通道關閉等因素使用者市場份額正在逐步減少。

實際上,這種需要客戶端參與支付流程的方式相比銀行卡快捷支付直接扣款這類支付方式,在支付系統的流程及訂單結構等設計上是存在較大差異的,其中訂單的防重失效機制的設計更是一個比較棘手的問題。

參與過支付系統開發或在業務系統中開發支付功能的同學可能會遇到類似這樣的業務需求。

使用者在外賣網站或 App 上購買了點了一份外賣,並通過微信支付進行付款,系統在收到使用者支付完成的訊息後,提示使用者付款成功並派單給餐館?

初看這個問題,可能很多同學都會有疑問,這不是一個很簡單的支付流程嗎?大部分支付場景不都是這樣的麼?

其實是這樣的,作為正常的支付流程來講,上述場景並沒有什麼問題,在整個系統鏈執行穩定的情況下,可能大部分參與者並不會有什麼感覺;但是,作為一個具備專業精神的小碼農來說,還是有很多異常場景需要考慮的,不然就可能會因為系統流程上設計的缺陷而給公司和使用者體驗造成比較大的傷害。

那麼上述需求中,會有什麼樣的異常場景呢?與支付系統防重失效機制的設計有什麼關聯?

我們可以先來看一下以上場景在系統流程中的執行情況「需要放大檢視」。

在上面的流程中,雖然從使用者角度看可能只是幾秒鐘的事情,但實際上整個系統鏈是經歷了一個比較長的呼叫過程,具體如下。

● 使用者在點外賣的過程中選擇微信支付後,App會將支付請求傳送給外賣後臺系統,如果在整個外賣平臺中,支付系統是一個獨立的系統,則外賣業務後臺服務會在生成業務訂單後將支付請求傳送給獨立的“支付系統”進行處理;

● 此時支付系統作為獨立的中間系統會處理外賣平臺業務後臺傳送過來的支付請求,記錄其業務訂單號並生成對應支付系統自身的支付流水號,並對支付流水進行狀態初始化(這裡涉及一個業務訂單號&支付訂單號如何匹配問題,會在後面的討論中闡述);

● 支付系統呼叫微信統一下單介面進行預支付(這裡的操作方式就是類網銀式的支付方式,先進行預支付然後由使用者跳到站外進行支付),並同步得到微信支付返回的預支付訂單資訊,支付系統此時需要更新支付訂單為pending狀態表示處於預支付狀態;

● 然後支付系統將預支付資訊同步給呼叫方—外賣業務後臺,外賣業務後臺再同步給外賣App;

● 外賣App會根據預支付訂單資訊通過客戶端支付SDK喚起微信支付客戶端,由使用者操作微信支付客戶端直接向微信支付發起付款動作,需要注意的是,此時呼叫鏈已經轉移到了站外,實際上此時使用者是否支付或是否支付成功,無論是支付系統還是外賣系統及App本身都是無法直接感知到支付結果的,需要逐層回撥;

● 微信支付會通過迴圈呼叫的方式主動將支付結果回撥給支付系統,再由支付系統回撥給外賣業務系統,最後在使用者直接感知前由App主動查詢外賣業務系統訂單支付狀態,同時提示使用者支付成功或支付處理中這樣的資訊;

從上面的流程可以瞭解到,實際上大家平時在通過 App 購物時支付的一瞬間是經歷了很複雜的流程。那麼,不知道在支付的過程中有沒有這樣的體驗?

在點外賣後付款了,微信也提示支付成功了,但是外賣App卻始終不顯示點餐成功?即使選擇重新支付也提示支付中,不允許重複支付?或者選擇重新支付以後外賣App顯示也顯示點餐成功了,但是之前支付的錢卻不見了,只能打客服投訴,各種麻煩?

上述問題,在目前支付流程的設計上是必然會發生的,目前作者所在的公司也有類似的問題,雖然這種問題發生的概率可能不是特別高,但是絕對是破壞使用者體驗以及增加了客服的工作量,處理得是否得當是衡量一套支付系統是否強大的核心指標之一。

那麼怎樣的設計才能很好地解決此類問題呢?

從流程上看使用者選擇微信支付並喚起微信錢包付款後,實際上外賣平臺支付系統已經感知不到系統的狀態了,也就說此時使用者是否完成了支付,平臺是無法同步感知的,只能依賴於微信的主動通知回撥,一般來說目前主流的支付公司都有一套完整的商戶通知邏輯,會在支付完成後實時通知到商戶。

但是很多時候會有多種因素導致這種通知被延遲,比較常見的因素主要有網路、自身平臺系統服務當機、第三方渠道通知服務故障等。

也就說會有使用者支付了點外賣的錢,系統卻沒有實時顯示支付成功的問題,也就是我們常說的短時掉單問題;或者使用者沒有及時支付,重新付款時卻會被提示“支付中請勿重複提交”,也就是支付防重問題。

對於掉單問題的處理,可以根據業務場景進行考慮,但無論是哪種方案,越快速補償業務越能夠有效地提升使用者體驗,減少系統異常處理流程,讓防重機制更加靈敏,在避免重複支付問題的同時提高支付成功率。

另外,是否允許使用者重複支付,如何利用衝正機制有效提高使用者體驗的同時快速保障使用者權益,也是需要在整體方案中進行考慮的方面。

根據業務時效性要求不同,大致有兩種方案:

一、非同步補償機制

具體來說,就是支付流程按照正常的流程走,通過採用旁掛定時的方式掃描系統中一定時間策略範圍內的pengding狀態的訂單,通過微信提供的訂單查詢介面主動輪詢,一旦支付狀態查詢到終態即刻觸發系統回撥,完成支付訂單及業務邏輯的補償;

另一方面,如果pengding狀態訂單通過輪詢方式沒有查詢到最終狀態則需要設定一定的重複輪詢策略,例如5分鐘、10分鐘、20分鐘、1小時、3小時、8小時、24小時這樣,並在超過策略規定的時間及輪詢次數後將支付流水更新為失效終態,並提供訂單查詢介面供業務平臺完成自身業務訂單邏輯的更新。

  系統示意圖如下:

通過旁掛式的方式,支付主流程會變的相對簡單,只需要考慮正常的收單場景,對於很多業務實時性不太高的支付場景,這種方式也夠用。但對於業務實時性要求非常高,並且對使用者體驗有極致要求的場景來說,這種方式顯然也是存在明顯問題的。

我們還是拿點外賣這件事來說,外賣後臺在接收到使用者通過App傳送的點餐支付請求後生成外賣訂單並將支付請求傳送給平臺支付系統,支付系統一般來說會首先進行訂單防重判斷,即已經發起過的成功支付/支付中的請求不被允許發起第二次,支付成功的交易不允許重複發起。

但是等待支付或支付失敗的交易很多公司內部支付系統都會被要求允許發起二次付款,在外賣點餐環節,如果使用者點餐了但是並沒有立刻進行支付或者支付由於某種原因失敗了,是可以重新發起付款的,在這種情況下,支付系統就會面臨一個問題,由於不知道在進行預支付後使用者是否完成了支付,對於是否應該繼續讓使用者發起支付請求,防重邏輯就會變的遲鈍,如果允許使用者支付則可能出現重複扣款的問題,不允許則會影響使用者體驗,為了讓整個機制變得合理,所以需要依賴於上述系統的補償機制來進行回盤或失效處理。

這裡會遇到以下三種情況:

1、使用者最終未支付,則系統安裝一定的輪詢機制進行後續的訂單失效處理即可;

2、使用者完成了支付,支付系統遲遲收不到微信的回撥,通過逐步輪詢的方式系統也會進行後續的訂單回撥補償;但這裡的問題是,如果非同步補償系統對訂單的輪詢不夠及時(在支付訂單量比較大的情況下,通過定時輪詢的方式在時效性上較差),那麼就會導致一個比較尷尬的情況,使用者付完了錢,但是外賣訂單很長時間顯示未支付,無法進行派單,在輪詢補償系統完成回撥後觸發派單操作,但往往很可能已經過了飯點,並且很可能使用者已經觸發了申訴流程,外賣平臺需要進行退款操作(增加客服工作量);

3、則是使用者當時並未及時支付,在訂單失效前的某個時間,使用者可能會選擇重新付款,因為此時支付系統訂單並未失效,會處於支付中狀態,觸發防重機制,無法再次發起付款;

對於2、3兩種情況,如果需要很好的滿足業務要求,就要提高支付系統時效性,提高訂單防重失效、快速回盤的處理時間。要達到這樣的效果,往往單純的依賴旁掛式的處理方案是很難達到的,而是需要讓實時支付流程的設計變得更加智慧和靈敏。

二、實時支付流程優化設計

為解決上面的問題我們需要在實時支付流程中加入異常優化機制,從整個流程的設計上去解決,讓整個支付系統變得更加智慧和靈敏,雖然這種方式看似讓支付主流程變得複雜了很多,但從優化使用者體驗、提高系統靈敏度的角度看,這種複雜度是值得的並且是可以通過技術細節遮蔽的。

那麼具體應該怎樣去設計這樣的流程?

詳細如圖所示「需點選放大」。

如上圖所示,支付系統接收到前端發起的支付請求,系統首先需要進行防重判斷,這裡為了有效地防止併發請求,採用Codis鎖的方式,即一筆業務支付訂單請求傳送到支付系統後首先獲取Codis全域性鎖,如果存在鎖則說明訂單正常被處理/未被正常處理,此時我們需要進行鎖更新時間判斷,如果鎖的更新時間與系統當前時間差<=10s(可根據業務場景進行動態調整,即10s內同一筆商戶訂單號的支付請求不允許被多次發起),則很有可能此時系統正在處理這筆支付請求,應該正常進行防重處理;

相反,如果獲取的Codis鎖的更新時間與系統當前時間差>10s,則此時會存在兩種情況,一種就是這筆支付訂單沒有被正常支付,是應該被允許重新發起支付的;另一種可能則是使用者可能支付成功了,只是渠道在支付結果回撥的過程中出了問題導致系統掉單。這兩種情況混在一起,系統並不能立刻識別出到底屬於何種情況。

這個問題是一個非常普遍和典型的問題,幾乎很多公司都會遇到。

此時,支付系統有兩種選擇,一種選擇是執行嚴格的防重策略,即要求所有對接支付平臺的業務系統每次呼叫支付請求都必須生成不同的商戶訂單號,支付系統對於同一個訂單號無論支付成功與否都不允許將此商戶支付單號重複傳送給支付平臺,這種方案與第三方支付公司的介面約定一致。

這種防重策略粗暴簡單,本質上是將邏輯的複雜性傳到給了業務系統,也會讓業務變的難受,如果支付平臺在後期經歷過重建,需要推動業務線切換的話,也往往會招致業務系統的反對。

那麼如何讓支付平臺本身來遮蔽這種複雜的細節,讓業務儘可能無感知?

兩個訂單號

● 商戶訂單號:業務系統發起的向支付系統發起支付請求是生成的在商戶系統中唯一標識一筆訂單的標記。

● 支付訂單號:業務系統向支付系統發起支付請求後,支付平臺本身生成的系統唯一標識一筆支付流水的標記,並且是支付系統與第三方支付渠道互動的唯一流水標識。

為了達到以上目標需要在支付系統內部採用 1:3「舉例」的訂單模型,即 1 筆業務訂單號可以對應支付系統3筆支付訂單流水,並且每筆支付流水允許被髮起的條件是上筆支付流水資料庫訂單狀態是未支付成功,並且需要在當前這筆支付流水重新生成後將上筆支付流水放入訂單動態實效佇列,進行快速失效處理。

之所以採用以上方式,原因在於超過3次時間間隔超過30s「策略可以根據業務實踐進行動態調整」還未完成支付的情況,系統基本可以認定屬於惡意點選行為,可以直接拒絕此筆業務訂單重新發起支付了。

需要動態將上筆支付訂單快速置為實效的原因在於,我們需要在內部設定一個邏輯「如果支付訂單處於實效狀態並在後面接收到了第三方支付成功的回撥,則需要系統自動發起該筆支付訂單的原路退款邏輯,並確保該筆訂單不會被通知到商戶側」。這種現象之所以出現,在於我們為了提高系統的實時性允許了少量重複扣款的情況發生,並進行了自動衝正邏輯。

當然,在細節的處理上我們是在當前流水發起前對上筆流水已經進行了一輪訂單實時查詢,如果結果為支付成功,則此次請求會直接返回支付成功「或者,也可以提示已經支付成功,App主動查詢支付系統的訂單狀態來完成回盤」。

如果當前訂單再次預支付成功,在同步返回預支付結果前需要更新 Codis 中訂單鎖的時間及發起次數。同時,在接受到第三方正常的支付成功回撥後完成訂單狀態更新及商戶通知後消除 Codis 鎖。

上述策略,為解決防重 & 二次支付問題提供了一種方案,當然還有很多細節的程式碼邏輯是需要考慮完善的,例如,實時查詢超時的策略、退款的觸發時間、使用者提示等。

此外,如果使用者不再選擇再次發起付款,系統中的存量訂單也需要通過文中早些時候介紹過的非同步補償機制逐步將進行失效處理「具體策略機制可參考圖示及之前的概述」,只是如果在非同步補償機制過程中發現掉單的訂單,是否正常回盤或自動給使用者退款,就需要具體情況具體分析了。

本文作者:無敵碼農

本文來自雲棲社群合作伙伴“Java面試那些事兒”

原文連結:https://yq.aliyun.com/articles/632492?spm=a2c4e.11153940.bloghomeflow.300.5181291aJVRxrc

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31137683/viewspace-2213346/,如需轉載,請註明出處,否則將追究法律責任。

相關文章