????iOS內(nèi)購這塊的開發(fā)一直比較麻煩,除了各種購買選項的問題,最惡心的問題就是丟單問題。
????丟單就是iOS內(nèi)購過程中付了錢,但是App沒有發(fā)貨的問題。要解決丟單問題,先要梳理一下整個購買的過程:
- 調(diào)用服務(wù)器接口創(chuàng)建訂單
- 調(diào)用內(nèi)購的api完成購買
- 獲取receipt、transactionIdentifier發(fā)送給服務(wù)器
- 本地服務(wù)器拿到receipt向蘋果發(fā)起驗證,并回調(diào)結(jié)果給App
以上就是整個購買的過程,現(xiàn)在我們根據(jù)每一步分析下可能出問題的點
1. 調(diào)用服務(wù)器接口創(chuàng)建訂單
這一步的操作就是讓服務(wù)器創(chuàng)建當(dāng)前購買商品的訂單,返回結(jié)果失敗或者成功,這里基本不會出問題,成功就繼續(xù)接下來的流程,失敗的話,客戶端返回失敗的提醒就行。
2. 調(diào)用內(nèi)購的api完成購買
絕大多數(shù)的問題都出在這里,在這里說一下我遇見的坑
- 客戶在付完款之后,直接殺死了app。
- 在支付過程中,若當(dāng)前賬戶里的錢不夠,且沒有綁定銀行卡、微信或者支付寶,會觸發(fā)一個綁定機(jī)制,但是用戶在點擊去綁定時候,
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions,會返回一個失敗的transaction,當(dāng)用戶完成支付,又會返回一個成功的transaction。由于我們之前使用RMStore這個第三方插件,他在處理回調(diào)的時候有這樣一個機(jī)制,每次發(fā)起購買RMStore都會把回調(diào)的block存儲到RMAddPaymentParameters這個對象里面,當(dāng)?shù)谝淮五e誤回調(diào)返回時,會把RMAddPaymentParameters里面的block取出來并執(zhí)行,執(zhí)行之后,持有這個對象的數(shù)組會把這個對象移除,所以當(dāng)?shù)诙纬晒Φ幕卣{(diào)返回時,再想去執(zhí)行block,但是持有這個block的對象已經(jīng)被移除了,導(dǎo)致了非常多的丟單。
對于第一個問題,內(nèi)購的api有一個監(jiān)聽機(jī)制,在每次app剛啟動的時候調(diào)用[[SKPaymentQueue defaultQueue] addTransactionObserver:self];方法,- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions,會回調(diào)所有未執(zhí)行[[SKPaymentQueue defaultQueue] finishTransaction:tran];的transaction,這個時候再去和服務(wù)器進(jìn)行訂單的驗證。這里還有一個很麻煩的問題,在上個流程中,調(diào)用服務(wù)器創(chuàng)建了訂單,會返回一個訂單號,驗證的時候一般都是把訂單號,以及receipt,transactionid一起發(fā)送給服務(wù)器,但是這里是拿不到訂單號的。針對這個問題,網(wǎng)上有人用applicationUsername這個字段去存入訂單號,但是也有一些人在使用這個字段的時候出現(xiàn)了Bug(App殺死后,獲取這個字段的值為空,參見下面的引用),為了保險,這里還是不用字段。我的解決方案是在用戶創(chuàng)建完訂單之后,存儲當(dāng)前的訂單信息,用戶token等等,當(dāng)購買完之后,再去取這個訂單信息,完成驗證,刪除訂單,這個方案的核心是永遠(yuǎn)只存儲一個訂單,若在購買的時候有未完成的訂單,必須先驗證之前的訂單,驗證完之后,再去發(fā)起新的購買,這樣就能規(guī)避多個transaction不能匹配訂單號的問題;
????對于第二個問題,這個問題好解決,不用這個第三方就行,自己造個輪子,基本的購買邏輯很簡單,沒必要用這個插件。當(dāng)- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions返回的transaction的state為failure時候,finish這個transaction,state為purchsed的時候,去驗證。
????這里還有一個問題,finishTransaction:的調(diào)用時機(jī),一定要在與服務(wù)器完成驗證之后,再去調(diào)用這個方法,因為在與服務(wù)器的驗證的過程中,也是會出現(xiàn)錯誤的,如果再購買成功回調(diào)之后就調(diào)用這個方法,驗證錯誤的訂單也被finish了,下次去取transaction的時候,就取不到了,這樣就造成了丟單問題。
3. 獲取receipt、transactionIdentifier發(fā)送給服務(wù)器
????這里的邏輯基本就是自己的了,跟蘋果內(nèi)購關(guān)系不大,把訂單、receipt等信息發(fā)給服務(wù)器就行,唯一需要注意的問題就是網(wǎng)絡(luò)錯誤,這個時候我會做一個輪循,在發(fā)生錯誤的時候,輪循幾次,超過這個次數(shù),認(rèn)為驗證失敗,等到用戶購買下一個商品、或者app重新啟動,會重新驗證這個訂單(當(dāng)然,這個情況是極少的!為了嚴(yán)謹(jǐn),做了以上處理)
4. 本地服務(wù)器拿到receipt向蘋果發(fā)起驗證,并回調(diào)結(jié)果給App
????終于到最后一步了,這里有一個地方需要注意一下,就是本地服務(wù)器與蘋果服務(wù)器驗證的方式,之前我們公司出了個問題,在調(diào)用/verifyReceip這個接口去驗證,蘋果會返回一個數(shù)據(jù)status,它們認(rèn)為status等于0就是完成購買了,但是這個字段的含義并非如此
For iOS 7 style app receipts, the status code is reflects the status of the app receipt as a whole. For example, if you send a valid app receipt that contains an expired subscription, the response is 0 because the receipt as a whole is valid.
????它只是反映這個receipt是不是完整的,并不能證明這次購買完成。正確的做法應(yīng)該是服務(wù)器拿到此次的transaction和receipt之后,解析receipt,拿當(dāng)前的transaction和receipt里的transaction數(shù)組去比對,若數(shù)組中有這個transaction對應(yīng)的transactionid,則認(rèn)為購買成功。
????蘋果服務(wù)器返回的錯誤碼中,我們只需留意21005即可,它代表驗證服務(wù)器當(dāng)前不可用(當(dāng)然這種情況是級級級小的!為了嚴(yán)謹(jǐn)),處理的方式和網(wǎng)絡(luò)錯誤的處理方式是一樣的。
