一.先說下驗證方式
iOS 內購支付兩種模式: 1、內置模式 2、服務器模式
1、內置模式的流程
內置模式的流程:
1.app從app store 獲取產品信息
2.用戶選擇需要購買的產品
3.app發送支付請求到app store
4.app store 處理支付請求,並返回transaction信息
5.app將購買的內容展示給用戶
內置模式可以這樣進行本地驗單 //本地驗證 - (void)localPaymentTransactionVerify:(NSString *)environment body:(NSData *)postData transaction:(SKPaymentTransaction *)transaction{ NSURL *StoreURL = nil; if ([environment isEqualToString:@"environment=Sandbox"]) { StoreURL = [[NSURL alloc] initWithString: ITMS_SANDBOX_VERIFY_RECEIPT_URL]; }else { StoreURL = [[NSURL alloc] initWithString: ITMS_PRODUCT_VERIFY_RECEIPT_URL]; } NSLog(@"運行環境是--- %@", StoreURL); NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:StoreURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:postData]; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; NSString *product = transaction.payment.productIdentifier; NSLog(@"transaction.payment.productIdentifier++++-----%@",product); if ([product length] > 0) { NSArray *tt = [product componentsSeparatedByString:@"."]; NSString *bookid = [tt lastObject]; if([bookid length] > 0) { NSLog(@"打印bookid------%@",bookid); } } //在此做交易記錄 // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } #pragma mark connection delegate - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { //進行二次驗證; // NSLog(@" 以下是HTTP協議的監聽,若由服務器驗證,可不用這段代碼%@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); NSLog(@" 以下是HTTP協議的監聽,若由服務器驗證,可不用這段代碼%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); NSDictionary * dic=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; NSNumber *status=dic[@"status"]; NSDictionary * receiptDic=dic[@"receipt"]; // NSString *purchased=receiptDic[@"product_id"]; NSArray * tempArr=receiptDic[@"in_app"]; NSString * purchased=nil; for (int i=0 ; i<tempArr.count; i++) { NSDictionary * tempPurchase=tempArr[i]; purchased=tempPurchase[@"product_id"]; } if (status.intValue==0) { // 發送通知更改賬戶V豆的數量; // [[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchasedNotification object:purchased]; } else { NSLog(@"收據校驗失敗"); switch (status.intValue) { case 21000: NSLog(@"App Store不能讀取你提供的JSON對象"); break; case 21002: NSLog(@"receipt-data域的數據有問題"); break; case 21003: NSLog(@"receipt無法通過驗證"); break; case 21004: NSLog(@"提供的shared secret不匹配你賬號中的shared secret"); break; case 21005: NSLog(@"receipt服務器當前不可用"); break; case 21006: NSLog(@"receipt合法,但是訂閱已過期。服務器接收到這個狀態碼時,receipt數據仍然會解碼並一起發送"); break; case 21007: NSLog(@"receipt是Sandbox receipt,但卻發送至生產系統的驗證服務"); break; case 21008: NSLog(@"receipt是生產receipt,但卻發送至Sandbox環境的驗證服務"); break; default: break; } } NSLog(@"%@",dic[@"status"]); } - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ // NSLog(@" 以下是HTTP協議的監聽,若由服務器驗證,可不用這段代碼"); } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ NSLog(@" 以下是HTTP協議的監聽,若由服務器驗證,可不用這段代碼"); NSLog(@"%@+++",response); switch([(NSHTTPURLResponse *)response statusCode]) { case 200: NSLog(@"200------"); break; case 206: NSLog(@"206------"); break; case 304: NSLog(@"304------"); break; case 400: NSLog(@"400------"); break; case 404: NSLog(@"404------"); break; case 416: NSLog(@"416------"); break; case 403: NSLog(@"403------"); break; case 401: NSLog(@"401------"); case 500: NSLog(@"500------"); break; default: break; } }
2、服務器模式的流程
服務器模式的流程:
*******最重要的一點:在確認服務端收到receipt之前不要結束訂單(不要調用[[SKPaymentQueue defaultQueue] finishTransaction:transaction];)******
1.app從服務器獲取產品標識列表 2.app從app store 獲取產品信息 3.用戶選擇需要購買的產品 4.app 發送 支付請求到app store 5.app store 處理支付請求,返回transaction信息 6.app 將transaction receipt 發送到服務器 7.服務器收到收據后發送到app stroe驗證收據的有效性 8.app store 返回收據的驗證結果 9.根據app store 返回的結果決定用戶是否購買成功
服務器驗證這樣處理---在下面這個交易結束方法里進行服務器驗證;就不需要上面本地內置模式的代碼塊了 - (void)completeTransaction:(SKPaymentTransaction *)transaction{ NSLog(@"交易結束"); NSString *productID = transaction.payment.productIdentifier; //驗證購買結果 if (productID.length > 0) { //向自己的服務器驗證購買憑證 //最好將返回的數據轉換成 base64再傳給后台,后台再轉換回來;以防返回字符串中有特字符傳給后台顯示空 NSString *result=[[NSString alloc]initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding]; NSLog(@"---transactionReceipt:%@", result); NSString *environment = [self environmentForReceipt:result]; // appStoreReceiptURL iOS7.0增加的,購買交易完成后,會將憑據存放在該地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 從沙盒中獲取到購買憑據 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSLog(@"_____%@",sendString); //******這個二進制數據由服務器進行驗證***************************** NSData *postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]]; //在這里進行服務器驗證 //******本地驗證*********************************************** [self localPaymentTransactionVerify:environment body:postData transaction:transaction]; //結束交易(收到服務器的驗證之后再調用此方法---避免造成漏單) // [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } }
上述兩種模式的不同之處主要在於:交易的收據驗證,內建模式沒有專門去驗證交易收據,而服務器模式會使用獨立的服務器去驗證交易收據。內建模式簡單快捷,但容易被破解。服務器模式流程相對復雜,但相對安全。
二.再說下關於漏單的處理
*****最重要的一點:在確認服務端收到receipt之前不要結束訂單(不要調用[[SKPaymentQueue defaultQueue] finishTransaction:transaction];)
漏單:正常玩家購買了卻沒有收到物品、且自己的服務端沒有任何記錄iOS的訂單。iOS的補單是非常麻煩的,用戶提供支付的截圖中的訂單號我們又不能在itunes 或者其他地方找到相應的訂單號。 服務端需要處理一個receipt中攜帶了多個未處理的訂單,即在in-app中有多個支付記錄。 因為雖然按正常邏輯,一次只會處理一筆支付,在漏掉以前充值訂單的情況下,一個receipt,可能含有多個購買記錄,這些記錄可能就是沒有下發給用戶的,需要對receipt 的 in-app記錄逐條檢查,根據訂單記錄查看某一單是否已經下發過了。
如果 in_app 里面值為空.看下這個:https://forums.developer.apple.com/thread/8954
參考內容來源:https://www.jianshu.com/p/e7722bc578c0