網路上關於內購支付的文章大都千篇一律,講述的是如何建立內購專案,最後再把內購發起的相關程式碼直接寫上就完事了。但是,在實際開發中,關於內購的坑卻多的讓人抓狂,比如常見的掉單,重複充值等情形。有的使用者反饋我明明釦過錢了,為何沒有充值?有的卻是使用者付了一次款,卻給衝了兩次+的額度,無論哪種情況,對開發者而言都是災難,一方面是使用者體驗,一方面是公司虧損。我之前也寫過一篇關於內購的文章內購支付踩過的坑以及自己的解決途徑,最近有朋友問到過,回看後發現有些地方的表述過於混亂,所以決定重新整理一篇內購的支付流程和期間需要注意的一些地方,當然這個只是我在開發中遇到的問題,如果有其他問題和策略,歡迎補充。
接下來我先講兩種常見的流程和思路,以及列舉將會出現的問題,最後再來講一下我自己開發的流程和應對策略
1. 驗證放在客戶端
我剛接手手中專案的時候,由於之前是外包做的,在內購這塊顯然沒有那麼上心,只求完成功能即可,後來查閱訂單和使用者反饋,發現出現很多丟單的事情,下面我大致陳述下這種策略的流程:
1.1 流程
1.判斷是否允許內購
2.根據商品id請求商品的具體資訊
3.商品資訊請求成功,建立支付交易,發起支付,之後就走的是系統提供的觀察者
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
4.如果支付不成功,結束交易
5.支付成功,結束交易,獲取憑證,本地進行驗證(具體驗證方法,我這裡不再貼出程式碼,網上有一大堆)
6.等待校驗結果,成功則調取充值介面,否則不充值
1.2 可能存在的問題
看了上述流程,我們會發現,前面的還好,驗證的過程卻完全不可控,比如,校驗失敗,再比如校驗回來了,充值介面卻沒有來得及呼叫,或者充值介面還沒回來等,使用者退出了介面或者app。由於在支付成功的時候就結束了交易,這個時候若是校驗結果沒有返回或者校驗結果回來了充值沒有順利進行,那麼這個單子就丟了,對使用者而言這就是損失。再有,放在客戶端有個問題,憑證可以偽造,如果有人擷取請求,偽造憑證,那就可以肆無忌憚地進行充值,對公司而言這是損失,所以這種方案不用想肯定錯漏百出。
2.支付在客戶端,校驗和充值在服務端
基於第一種方案,第二種方案是要好一些的,而且我下面提到的第三種也是在第二種的基礎上改進的。
2.1 流程
1.判斷是否允許內購
2.根據商品id請求商品的具體資訊
3.商品資訊請求成功,建立支付交易,發起支付,之後就走的是系統提供的觀察者
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
4.支付失敗,結束交易
5.支付成功,取出憑證上傳給服務端,服務端進行驗證並充值,這些步驟完成後返回給客戶端,客戶端結束交易。
這麼做看似沒有什麼毛病,如果按照正常流程的話的確是對的。但是都是有意外的,出現意外的點:
- 服務端接到的憑證還有可能是偽造的
- 服務端驗證是和蘋果伺服器打交道,不可能那麼快,如果這個時候使用者退出了app,那麼交易就沒有結束,下次啟動依然會執行上傳憑證的後續操作,由於每次獲取的憑證和時間戳有關,所以服務端在判斷憑證重複性上來說也是不安全的,這就可能造成重複充值的情況
3.本文要介紹的方案,基於第二種方案的改進
由於要對交易進行判重,如果憑藉蘋果那邊返回的資料進行判斷,就等著被坑死吧,網上一開始以為沒有問題的applicationUserName
也可能抽風給你來個nil,而且在查賬的時候根本查無可查,所以最好的做法是判斷邏輯都用我們自己的資料,下面我列舉一下我的流程,所有的操作都從點選購買按鈕開始:
3.1 流程
- 向伺服器請求建立訂單介面,這個訂單是伺服器那邊生成的,用來確定每次請求訂單的唯一性,也是用來對賬的標識。
- 進入內購支付流程
- 根據商品ID請求商品資訊
- 商品資訊請求成功,發起交易
- 支付沒有成功,結束交易
- 支付成功,先判斷交易的
transactionIdentifier
是否存在於本地,如果存在,則此項交易已經無效,直接結束交易,如果不存在,則走驗證邏輯
- 驗證邏輯:取出沙盒中的憑證,將憑證上傳給伺服器,要帶上之前生成的訂單id,伺服器接收失敗,什麼也不做。接收成功,將交易的
transactionIdentifier
存到本地同時結束交易,後面的驗證都在伺服器了。
- 伺服器驗證過程:伺服器會維護一個訂單id列表,此訂單id的初始狀態為未驗證,當接收到客戶端傳的憑證後,將此訂單id狀態置為驗證中。如果驗證成功,將訂單id狀態設定為無效,同時前去充值,充值結果返回給客戶端。
有人會問,客戶端如果這個時候退出了呢?這個也沒關係,因為從客戶端上傳憑證給服務端成功後,就沒有客戶端什麼事兒了,所有的校驗和驗證,充值邏輯都有服務端把控。
做的好一點的,為了讓使用者知曉充值成功,服務端在充值完成後,可以定向給使用者發一條推送通知。同時客戶端上傳憑證成功後,給使用者一個充值延遲到賬的提示。
下面模擬幾種意外情況,由於支付工具類做的是單例,這裡只模擬退出app的情況,不用再模擬退出支付頁面的情況:
-
使用者在上傳憑證中退出app,這個很好理解,什麼也不會發生,交易依然存在於蘋果伺服器快取中,使用者下次啟動介面,會從快取中查詢是否有未完成交易,如果有,會繼續執行支付成功後的邏輯,不用擔心掉單的情形
-
上傳憑證成功了,如果服務端驗證失敗呢?這個可不在客戶端控制範圍內,而且客戶端已經做了結束交易和移除交易id的操作,那麼有沒有可能丟單呢?下面我具體說一下: 前面講過,服務端有一個訂單id標識每一筆支付交易,接收了憑證後也會立即根據訂單id存起來,如果驗證失敗,伺服器會用定時器做輪詢驗證操作,只要是有效的訂單id,就會去進行驗證,前面已經知道,唯有校驗成功,訂單id才會無效,所以這保證了憑證一定會去驗證,而且最終一定會驗證通過,這樣就極大地減少了丟單的概率,而由於服務端存在嚴謹的判斷邏輯,所以重複充值的情況在正常邏輯下是不會存在的(排除有使用者惡意退款的情況,這個也非重複充值,而是另外一種情況,由於蘋果噁心的保密機制,我們無法獲取到使用者退款的詳情,所以也就無法應對這種惡意行為)
結束語:總體而言就這些,由於程式碼涉及到具體的業務和介面,我這裡只用語言進行陳述,有描述的不到位的情況,還請在留言區指出,有更好的思路的也請在留言區交流。內購這塊一直是一個巨坑,我很希望有朝一日能徹底填平,這樣對大家都好。