之前自己在項目中做了內購,但忘了寫博客。
今天回憶流程的時候看這哥們博客寫的很詳細,我就自己木有寫了,直接轉載了啦
引自https://www.jianshu.com/p/d90aeca518a8
iOS的內購流程如下
- 通過產品ID獲取產品信息列表
- 添加監聽
- 把產品包裝成SKPayment(支付)發送給蘋果服務器
- 蘋果服務器購買成功后會回調監聽方法,根據蘋果服務器返回信息判斷是否購買成功。
- 購買失敗或已經購買過該商品則注銷交易。如果購買成功,此時可以向自家服務器發送購買成功的消息,並通過后台向蘋果服務器發送驗證,然后注銷交易。
一般而言,這就是iOS內購的基本過程,看似很簡單,但是其實實際操作起來,還是比較麻煩的,因為要考慮到各種意外情況。
下面講一講iOS內購的具體過程
1.獲取產品信息列表
if ([SKPaymentQueue canMakePayments]) { NSSet *IDSet = [NSSet setWithArray:proID]; SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:IDSet]; productsRequest.delegate = self; [productsRequest start]; } else { NSLog(@"用戶禁止付費"); }
delegate是指SKProductsRequestDelegate
首先判斷用戶是否禁止付費,如果沒有禁止付費,就想蘋果服務器請求產品信息。
請求的信息會在SKProductsRequestDelegate的方法中返回
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSLog(@"%i", response.products.count); NSArray *myProducts = response.products; if (0 == myProducts.count) { NSLog(@"無法獲取產品信息列表"); } else { self.products = [myProducts sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { SKProduct *pro1 = (SKProduct *)obj1; SKProduct *pro2 = (SKProduct *)obj2; return pro1.price.integerValue < pro2.price.integerValue ? NSOrderedAscending : NSOrderedDescending; }];; for (SKProduct *pro in myProducts) { NSLog(@"%@", [pro localizedTitle]); NSLog(@"%@", [pro localizedDescription]); NSLog(@"%@", [pro price]); NSLog(@"%@", [pro.priceLocale objectForKey:NSLocaleCurrencySymbol]); NSLog(@"%@", [pro.priceLocale objectForKey:NSLocaleCurrencyCode]); NSLog(@"%@", [pro productIdentifier]); } } }
拿到產品信息以后可以進行排序處理,因為請求的時候發送的產品ID是裝在一個NSSet中的,所以返回的產品信息也是亂序的,這里需要注意一下。
2.內購監聽
拿到產品信息以后要設置監聽,因為當你點擊購買和購買后蘋果服務器會通過監聽方法通知應用。
- (void)startObserver { if (!self.isObserver) { [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; NSLog(@"開始監聽 ------ 內購"); self.isObserver = YES; } } - (void)stopObserver { if (self.isObserver) { [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; NSLog(@"移除監聽 ------ 內購"); self.isObserver = NO; } }
監聽方法和移除監聽的方法一起送上,isObserver是一個判斷是否已經監聽的BOOL數據。
我的建議是將監聽方法和移除監聽的方法都在AppDelegate中執行。當App啟動時(- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions)開始監聽,當App被關閉時(- (void)applicationWillTerminate:(UIApplication *)application)移除監聽。至於原因,后面會提到。
3.實現監聽方法
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions { NSLog(@"調用了幾次這個方法?"); SKPaymentTransaction *transaction = transactions.lastObject; switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: { NSLog(@"購買完成,向自己的服務器驗證 ---- %@", transaction.payment.applicationUsername); NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]]; NSString *receipt = [data base64EncodedStringWithOptions:0]; [self buySuccessWithReceipt:receipt transaction:transaction]; } break; case SKPaymentTransactionStateFailed: { NSLog(@"交易失敗"); [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } break; case SKPaymentTransactionStateRestored: { NSLog(@"已經購買過該商品"); [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } break; case SKPaymentTransactionStatePurchasing: { NSLog(@"商品添加進列表"); } break; default: { NSLog(@"這是什么情況啊?"); } break; } }
有的同學可能會疑惑,transaction.payment.applicationUsername中的這個applicationUsername屬性是干嘛的,先不要急,關於這個屬性我們會在后面提到,現在先記住這個點就好。
NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]]; NSString *receipt = [data base64EncodedStringWithOptions:0]; [self buySuccessWithReceipt:receipt transaction:transaction];
關於這三句,要注意,receipt是剛才交易的清單,如果后台需要進行二次驗證,需要用到這個數據。
至於最后一句,則是購買成功后向自家的服務器發送的請求。
發送內購請求
完成上面的代碼后,就可以進行發送內購請求的部分啦
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; payment.applicationUsername = [AppManager sharedInstance].userId.stringValue; [[SKPaymentQueue defaultQueue] addPayment:payment];
內購請求很簡單,就是用請求道的產品信息SKProduct創建一個“內購支付”SKPayment,然后添加進支付隊列。
以上就是iOS內購的全部核心代碼。接下來要講的,就是一些內購中可能出現的坑和如何跳過這些坑。
細心的同學應該已經發現,在發送內購請求的代碼部分,我們又一次見到了applicationUsername這個屬性,並將用戶的id賦值給了它,那么,這是用來干什么的呢?
答案是:在某些極端情況下,可能出現在發送內購請求的用戶和內購成功后通知自家后台的用戶可能不是同一個用戶的情況(真是奇葩的用戶。。。。但是沒辦法,用戶就是上帝嘛。。。)這種情況下,為payment綁定一個appUsername就可以讓這次payment有一個固定的發起者,這樣當這次payment在蘋果后台支付成功后,我們就可以通過監聽的回調,將這個發起者的唯一標識符上傳給自家后台,使得這次購買能找到一個合適的主人。就算用戶在購買的過程中切換賬號或者退出,也能夠讓這次充值驗證成功。
既然說到了極端情況,那么我們不如更進一步,讓情況更極端一點,那就是當購買請求發送后,直到向蘋果后台購買成功的這段時間,如果程序崩潰了!或者程序被用戶關閉了!怎么辦?!
這種情況下,我們的App自然也就無法進行監聽的回調,自然也就無法把購買成功的消息發送給自家后台,用戶也就拿不到自己的充值啦。情況很糟糕,但是!不用擔心,我們有辦法解決。
還記得上邊提到的注銷交易的方法嗎?沒錯就是:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction]
利用這個特性,我們可以將完成購買后注銷方法放到我們向自家后台發送交易成功后調用。
