iOS In-App Purchase 內購之 問題總結
1、內購流程
- 1、在 AppStore 中建立相應的物品,建立內購沙盒測試賬號
- 2、客戶端從後臺獲取相應的物品 ID (當然也可以再客戶端寫死,但後期擴充套件性就受限制了)
- 3、依據相應的物品 ID 請求商品的相關資訊
- 4、依據商品資訊建立訂單請求交易
- 5、依據返回的訂單狀態處理交易結果
- 6、請求後臺再次驗證訂單狀態
- 7、依據後臺返回結果處理相關邏輯
2、建立內購物品以及沙盒測試賬號
3、客戶端編寫相關程式碼
- 再這裡我把和支付相關的邏輯都抽取到了一個單例中,在最後貼上個人梳理的相關程式碼大家一起學習
4、做內購過程中遇到的坑
-
1、內購沙盒測試賬號在支付成功後,再次購買相同 ID 的物品,會提示如下內容的彈窗。
您已購買此 App 內購買專案。此專案將免費恢復。
解決方法:在使用
[[SKPaymentQueue defaultQueue] addPayment:payment];
將支付資訊新增進蘋果的支付佇列後,蘋果會自動完成後續的購買請求,在使用者購買成功或者點選取消購買的選項後回撥
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction;
方法返回響應的結果資訊,在該方法內除了得到響應的支付資訊編寫自身的業務的程式碼外還要記得呼叫
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
方法通知蘋果的支付佇列該交易已經完成,否者就會已發起相同 ID 的商品購買就會有此專案將免費恢復的提示。
-
2、每次啟動一個新的內購支付流程,剛發起的時候系統就會呼叫
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction;
這個方法,結果擾亂一部分的支付業務邏輯在 SKPaymentQueue 被啟動並且添加了 addObserver之後,如果其判斷到有未完成的交易,會主動呼叫paymentQueue updatedTransactions 這個方法來繼續完成相關的交易流程,所以如果在上面那種情況下得到結果後不去呼叫 finish 介面,下次重新開啟支付流程就會檢查未完成的支付並呼叫該介面。
解決方法:
1.在得到支付結果後及時呼叫 finish 方法
2.新增一個是否是新發起的支付流程的條件,在條件符合的情況下才觸發應用的相關邏輯的程式碼
(PS:在拿到蘋果的支付結果憑據的時候最好在客戶端做一份持久化的資料備份,等待後臺驗證完成後再清除掉,避免出現驗證中間出現問題導致使用者支付成功但後臺相關的增值處理沒有完成導致使用者金錢損失的問題)
-
3、如何區分購買物品的是 沙盒測試賬號 還是 真實賬號
後臺再驗證支付憑據的時候要區分是沙盒測試賬號購買的還是使用者真實賬號購買的,所以在傳憑據的時候還需要告訴後臺當前購買的賬號性質。
解決方法:通過在配置檔案中定義相關的巨集定義並結合 Debug 與 Release 的編譯環境確定相關的引數
[objc] view plain copy print?
- // 蘋果內購是否為沙盒測試賬號,開啟就代表為沙盒測試賬號,注意上線時註釋掉
- #define APPSTORE_ASK_TO_BUY_IN_SANDBOX 1
- // 生成訂單引數,注意沙盒測試賬號與線上正式蘋果賬號的驗證途徑不一樣,要給後臺標明
- NSNumber *sandbox;
- #if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
- sandbox = @(0);
- #else
- sandbox = @(1);
- #endif
個人沒有找到相關的方法可以打完包後動態的檢測購買物品的賬號性質,希望知道的朋友分享一下,感謝 ^_^
5、iOS7 客戶端驗證的訂單狀態
-
蘋果在iOS7提升了購買憑據的安全性,可以直接單獨在客戶端完成訂單正確性的驗證,但是處於金錢考慮,購買完成後,建議還是要做憑據的後臺驗證工作。
[objc] view plain copy print?- #pragma mark 客戶端驗證購買憑據
- - (void)verifyTransactionResult
- {
- // 驗證憑據,獲取到蘋果返回的交易憑據
- // appStoreReceiptURL iOS7.0增加的,購買交易完成後,會將憑據存放在該地址
- NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
- // 從沙盒中獲取到購買憑據
- NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
- // 傳輸的是BASE64編碼的字串
- /**
- BASE64 常用的編碼方案,通常用於資料傳輸,以及加密演算法的基礎演算法,傳輸過程中能夠保證資料傳輸的穩定性
- BASE64是可以編碼和解碼的
- */
- NSDictionary *requestContents = @{
- @"receipt-data": [receipt base64EncodedStringWithOptions:0]
- };
- NSError *error;
- // 轉換為 JSON 格式
- NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
- options:0
- error:&error];
- // 不存在
- if (!requestData) { /* ... Handle error ... */ }
- // 傳送網路POST請求,對購買憑據進行驗證
- NSString *verifyUrlString;
- #if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
- verifyUrlString = @"https://sandbox.itunes.apple.com/verifyReceipt";
- #else
- verifyUrlString = @"https://buy.itunes.apple.com/verifyReceipt";
- #endif
- // 國內訪問蘋果伺服器比較慢,timeoutInterval 需要長一點
- NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[[NSURL alloc] initWithString:verifyUrlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
- [storeRequest setHTTPMethod:@"POST"];
- [storeRequest setHTTPBody:requestData];
- // 在後臺對列中提交驗證請求,並獲得官方的驗證JSON結果
- NSOperationQueue *queue = [[NSOperationQueue alloc] init];
- [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
- completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
- if (connectionError) {
- NSLog(@"連結失敗");
- } else {
- NSError *error;
- NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
- if (!jsonResponse) {
- NSLog(@"驗證失敗");
- }
- // 比對 jsonResponse 中以下資訊基本上可以保證資料安全
- /*
- bundle_id
- application_version
- product_id
- transaction_id
- */
- NSLog(@"驗證成功");
- }
- }];
- }
6、內購驗證憑據返回結果狀態碼說明
-
蘋果反饋的狀態碼:
[objc] view plain copy print?- 21000 App Store無法讀取你提供的JSON資料
- 21002 收據資料不符合格式
- 21003 收據無法被驗證
- 21004 你提供的共享金鑰和賬戶的共享金鑰不一致
- 21005 收據伺服器當前不可用
- 21006 收據是有效的,但訂閱服務已經過期。當收到這個資訊時,解碼後的收據資訊也包含在返回內容中
- 21007 收據資訊是測試用(sandbox),但卻被髮送到產品環境中驗證
- 21008 收據資訊是產品環境中使用,但卻被髮送到測試環境中驗證