項目背景
最近因爲項目業務緣由,須要爲系統設計虛擬幣的充值及消費功能。公司內已經有成熟的支付網關服務,因此重點變成了如何設計項目內虛擬幣的充值流程,讓整個充值流程都實現冪等,確保用戶的虛擬幣餘額不會重複增長或扣減。數據庫
商品購買及支付流程
- 用戶購買商品,商戶後臺請求生成支付訂單並返回相關信息到客戶端。
- 客戶端根據返回的信息喚起支付SDK,用戶確認支付。
- 用戶完成支付後,支付系統會異步通知商戶後臺支付結果。
- 商戶後臺接收支付回調,在回調接口內完成本身的業務邏輯。
- 客戶端在支付完成後延時必定時間從商戶後臺查詢支付結果,此時若還沒有接收到支付回調,可主動同步支付結果(保底策略)。
支付寶支付流程和微信支付相似,此處省略。正常狀況下支付回調都會在毫秒級別進行通知回調。安全
虛擬幣充值流程
虛擬幣充值流程會嵌套支付回調流程中。若虛擬幣沒有完成完整的業務流程,支付系統會進行重試。所以業務流程須要支持冪等。 bash
在實踐過程遇到如下問題並最終獲得解決:
- 如何支持訂單按用戶維度分表? 支付回調信息只包括訂單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記錄後也能夠惟一綁定到訂單信息,避免重複建立訂單。
設計總結
在設計訂單系統時冪等性是首要考慮的問題,須要嚴格保證金額的準確性,不能給用戶多扣款或多打款。通常狀況下咱們經過數據庫單機事務和冪等重試等方式提升訂單系統的健壯性。性能