專案背景
最近由於專案業務原因,需要為系統設計虛擬幣的充值及消費功能。公司內已經有成熟的支付閘道器服務,所以重點變成了如何設計專案內虛擬幣的充值流程,讓整個充值流程都實現冪等,確保使用者的虛擬幣餘額不會重複增加或扣減。
商品購買及支付流程
- 使用者購買商品,商戶後臺請求生成支付訂單並返回相關資訊到客戶端。
- 客戶端根據返回的資訊喚起支付SDK,使用者確認支付。
- 使用者完成支付後,支付系統會非同步通知商戶後臺支付結果。
- 商戶後臺接收支付回撥,在回撥介面內完成自己的業務邏輯。
- 客戶端在支付完成後延時一定時間從商戶後臺查詢支付結果,此時若尚未接收到支付回撥,可主動同步支付結果(保底策略)。
支付寶支付流程和微信支付類似,此處省略。正常情況下支付回撥都會在毫秒級別進行通知回撥。
虛擬幣充值流程
虛擬幣充值流程會巢狀支付回撥流程中。若虛擬幣沒有完成完整的業務流程,支付系統會進行重試。因此業務流程需要支援冪等。
在實踐過程遇到以下問題並最終得到解決:
- 如何支援訂單按使用者維度分表?支付回撥資訊只包括訂單ID資訊,在這種情況下一般只能根據訂單ID進行分表。考慮到訂單會越來越多,我們一開始就把訂單按使用者維度進行分表。一般情況下按使用者維度的查詢是很多的,而單純按訂單維度的查詢會比較少。所以在預下單的時候把使用者ID資訊寫入attach附加資訊,支付回撥時會攜帶上原先的附加資訊,這樣就可以知道使用者及訂單ID資訊,完成後續操作。
在後來的優化中,訂單ID生成時預留低位段儲存使用者訂單表ID資訊,這樣完全不依賴附加資訊進行傳遞,在使用者進行自動扣費授權時的回撥通知也可以適用。
- 虛擬幣如何做事務操作?若只有使用者虛擬幣的數量資訊,很容易會出現錯誤的重複操作。因此需要流水錶配合進行事務操作,當流水錶已經有相同的記錄時說明當前操作是重複的,需要回滾虛擬幣數值。通過資料庫的單機事務即可實現虛擬幣的正確變更,支援重入。
- 如何做虛擬幣的版本控制?我們虛擬幣每次變更都會對應一個版本號,在對虛擬幣的併發操作時一般都是通過判斷version是否符合預期時才進行資料變更。這個和樂觀鎖的控制類似,可是在這種情況下容易出現死鎖,尤其是資料庫效能差更容易觸發。因此不再嚴格判斷version版本號,只需要變更後的虛擬幣數量不小於0即可,所有符合這個條件的變更都視為有效變更。這個情景適合不用版本號,只更新是做資料安全校驗,適合庫存模型,效能更高。
update table_xxx set avai_amount=avai_amount+:deltaAmount where user_id=:userId and avai_amount+:deltaAmount >
= 0複製程式碼
大多數情況下只有虛擬幣消費才會出現併發修改,因此我們只需要嚴格控制虛擬幣不出現餘額不足以扣除的情況。
蘋果內購虛擬幣充值流程
使用者在應用內購買商品時,客戶端可以獲取到使用者ID、交易憑證receipt和交易ID等資訊。整體購買流程和Android端差異比較大,因為對receipt驗證流程參考Android下單流程做了拆解,更容易做到重入。
- 客戶端獲取到充值列表;
- 客戶端支付成功後提交交易憑證receipt給服務端驗證,服務端建立對應的憑證和訂單記錄,更新狀態,完成充值;
蘋果內購注意事項
- 如何避免receipt被重複使用?iOS客戶端支付成功後能獲取到transactionId和receipt資訊,兩者唯一對應。因此服務端在驗證receipt有效後,可建立對應的transaction記錄,根據transactionId進行分表,transactionId作為唯一鍵。這樣可避免receipt被重複使用。
- transaction和訂單記錄的對映關係?訂單記錄包含渠道transactionId資訊,這樣在建立transaction記錄後也可以唯一繫結到訂單資訊,避免重複建立訂單。
設計總結
在設計訂單系統時冪等性是首要考慮的問題,需要嚴格保證金額的準確性,不能給使用者多扣款或多打款。一般情況下我們通過資料庫單機事務和冪等重試等方式提高訂單系統的健壯性。