蘋果內購小結 - iOS
此篇針對 iOS 支付進行一次小結,很久沒碰這塊了,有些方法 Apple 官方也進行了優化,故也將隨之進行更新.
首先,code 部分將分為兩部分,一部分在 appdelegate 中,另一部分單獨封裝在了一個類中執行,需要使用的地方呼叫的介面方法.
其次,大體支付流程為獲取到充值價格訂單列表後,選擇對應的價格後向 Apple 發起支付請求,接收到 Apple 支付回撥後,根據結果處理相關邏輯,最後將處理完成的結果反饋至使用者.
其過程中會分為幾個環節來處理:
若支付失敗則執行異常處理並將最後處理結果資訊反饋至使用者;
若支付成功則對支付憑證校驗,此篇文章中的校驗過程分為兩部分,先是由客戶端自行校驗,若校驗成功則將相關使用者資訊和支付憑證傳送至服務端進行二次校驗;
其中,客戶端優先進行交易憑證校驗,校驗失敗則將校驗的異常處理資訊反饋至使用者;反之,校驗成功則再次將相關使用者資訊和本次支付憑證資料一併傳送至服務端進行二次校驗,最終將雙重驗證後的結果資訊反饋至使用者,從而為了避免刷單的情況.
最後,文章中具體處理邏輯中可能會因為需求的不同與實際有些小的出入,但大體流程應該是一致的,也會對應新增相應的註釋,若存在不清楚的地方可以帖子下方留言溝通交流.
大致支付流程:
1.蘋果APP(商家)
2.告訴蘋果Store伺服器要賣的商品
3.蘋果稽核完(告訴你是否可以賣)
4.使用者(買商品)
5.蘋果APP(商家)
6.開發票給(使用者)
7.使用者(拿著發票去蘋果Store伺服器付款)
8.付款成功(使用者在APP裡獲得服務商品)
注:如果要模擬測試內購,需要用真機才可以測試
憑證校驗地址:
開發環境: https://sandbox.itunes.apple.com/verifyReceipt
生產環境: https://buy.itunes.apple.com/verifyReceipt
憑證校驗異常 code 參照碼:
內購驗證憑據返回結果狀態碼說明(status 狀態)
21000 App Store無法讀取你提供的JSON資料
21002 收據資料不符合格式
21003 收據無法被驗證
21004 你提供的共享金鑰和賬戶的共享金鑰不一致
21005 收據伺服器當前不可用
21006 收據是有效的,但訂閱服務已經過期。當收到這個資訊時,解碼後的收據資訊也包含在返回內容中
21007 收據資訊是測試用(sandbox),但卻被髮送到產品環境中驗證
21008 收據資訊是產品環境中使用,但卻被髮送到測試環境中驗證
Code 如下:
/* 支付_管理類(Apple Pay) */ #import <Foundation/Foundation.h> #import <StoreKit/StoreKit.h> //充值金額型別 typedef NS_ENUM(NSInteger, buyCoinsTag) { IAP0p20=20, IAP1p100, IAP4p600, IAP9p1000, IAP24p6000, }; @import WebKit; @interface PaymentManager : NSObject <SKPaymentTransactionObserver, SKProductsRequestDelegate> { /** 購買型別*/ int buyType; } /** 產品 ID*/ @property (nonatomic, strong) NSString *productID; /** Init*/ + (PaymentManager *)manager; #pragma mark - 方法相關 /** 判斷當前環境是否支援支付購買*/ - (void)buyInfo:(NSDictionary *)buyDic AndViewController:(UIViewController *)vc AndSn:(NSString *)sn; /** 校驗交易憑證 - App Store (Plan A) 該方法以客戶端為基準: 若客戶端校驗結果失敗,則伺服器不再進行二次校驗; 若客戶端校驗結果成功,則伺服器再次進行二次校驗. @param transaction 交易事物 */ - (void)verifyTransaction:(SKPaymentTransaction *)transaction; /** 交易失敗*/ - (void)failedTransaction:(SKPaymentTransaction *)transaction; @end
/* 1.蘋果APP(商家) 2.告訴蘋果Store伺服器要賣的商品 3.蘋果稽核完(告訴你是否可以賣) 4.使用者(買商品) 5.蘋果APP(商家) 6.開發票給(使用者) 7.使用者(拿著發票去蘋果Store伺服器付款) 8.付款成功(使用者在APP裡獲得服務商品) 注:如果要模擬測試內購,需要用真機才可以測試 */ #import "PaymentManager.h" #import <objc/runtime.h> #import "GTMBase64.h" #import "RequestToolManager.h" //在內購專案中創的商品單號 #define ProductID_IAP0p20 @"***此處與實際內購價格配置表為準***" //20 #define ProductID_IAP1p100 @"***此處與實際內購價格配置表為準***" //100 #define ProductID_IAP4p600 @"***此處與實際內購價格配置表為準***" //600 #define ProductID_IAP9p1000 @"***此處與實際內購價格配置表為準***" //1000 #define ProductID_IAP24p6000 @"***此處與實際內購價格配置表為準***" //6000 #define PaySucceed @"充值成功" #define PayFailed @"充值失敗" #define PayException @"訂單發生異常,請聯絡客服" #define RequestError @"支付成功,等待驗證" /* AppStore增加了驗證內購(In App Purchasement)的方法, 就是蘋果提供一個url地址: 當購買成功時, 會得到蘋果返回的一個收據(receipt), 蘋果推薦的方法是將收據發給開發者的 server 服務端, 由 server 服務端向上述地址發起請求(post http)訊息, 進行驗證, 蘋果將校驗結果返回.此次交易憑證是真購買憑證還是偽購買憑證. 開發環境地址: https://sandbox.itunes.apple.com/verifyReceipt 生成環境地址: https://buy.itunes.apple.com/verifyReceipt */ #define SANDBOX_VERIFY_RECEIPT_URL [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"] #define APP_STORE_VERIFY_RECEIPT_URL [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"] #ifdef DEBUG #define VERIFY_RECEIPT_URL SANDBOX_VERIFY_RECEIPT_URL #else #define VERIFY_RECEIPT_URL APP_STORE_VERIFY_RECEIPT_URL #endif @interface PaymentManager () <SKPaymentTransactionObserver,SKProductsRequestDelegate, SKRequestDelegate> /** 訂單編號*/ @property (nonatomic, strong) NSString *tradeNo; @end @implementation PaymentManager #pragma mark - Init + (PaymentManager *)manager { static id sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; } #pragma mark - ************************************ 支付初始化 /** Pay Method 支付初始化 具體方法介紹詳見上面註釋中連線地址 注:切記繫結 <SKPaymentTransactionObserver> 事件,設定購買佇列的監聽器,實時監聽跟蹤訂單狀態,避免傳送丟單的意外,即 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; @param buyDic 支付所對應的產品資訊 @param vc 當前 VC 控制元件 @param sn 使用者資訊 */ - (void)buyInfo:(NSDictionary *)buyDic AndViewController:(UIViewController *)vc AndSn:(NSString *)sn { //判斷網路是否可用 if (k_NetWorkIsReachable) {// 可用 self.productID = [NSString stringWithFormat:@"%@", [buyDic objectForKey:@"productId"]]; // 獲取訂單編號 NSMutableDictionary *dicParameter = [NSMutableDictionary dictionary]; [dicParameter setValue:@"Apple Pay" forKey:@"payType"]; [dicParameter setValue:sn forKey:@"user"]; [dicParameter setValue:@"iPhone" forKey:@"deviceType"]; [dicParameter setValue:self.productID forKey:@"productId"]; kWeakSelf(self); [[HttpRequestManager shareInstance] PayPOST:URL_ApplePay parameters:dicParameter isEncipherment:YES success:^(id responseObject) { NSDictionary *result = [NSDictionary dictionaryWithDictionary:responseObject]; // 訂單編號 NSString *tradeNo = [NSString stringWithFormat:@"%@", [result objectForKey:@"tradeNo"]]; //設定購買佇列的監聽器 [[SKPaymentQueue defaultQueue] addTransactionObserver:weakself]; //判斷當前是否可支付 if ([SKPaymentQueue canMakePayments]) { NSLog(@"允許程式內付費購買"); //請求產品資料 [weakself fetchProductInformationForIds:weakself.productID AndTradeNo:tradeNo]; } else { NSLog(@"不允許程式內付費購買"); // Callback //[MBProgressHUD showError:@"請開啟手機內付費購買功能" toView:vc.view]; [self hudAlertMessage:@"請開啟手機內付費購買功能"]; } } failure:^(NSError *error) { NSLog(@"buyInfo: %@",error); // Callback //[MBProgressHUD showError:@"服務異常,請重新嘗試" toView:vc.view]; [self hudAlertMessage:@"服務異常,請重新嘗試"]; }]; } else { // Callback //[MBProgressHUD showError:@"暫無網路" toView:vc.view]; [self hudAlertMessage:@"暫無網路"]; } } /** 獲取對應的產品資料資訊 @param productIds 產品 id @param tradeNo 訂單編號 */ - (void)fetchProductInformationForIds:(NSString *)productIds AndTradeNo:(NSString *)tradeNo { NSLog(@"------------請求對應的產品資訊------------"); self.tradeNo = tradeNo; NSArray *product = [[NSArray alloc] initWithObjects:productIds, nil]; //為該產品識別符號建立一個集合 NSSet *nsSet = [NSSet setWithArray:product]; //建立該產品請求物件,並將上面的集合進行初始化它 SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsSet]; request.delegate = self; //向 App Store 發起請求 [request start]; } #pragma mark - ************************************ 交易處理中 /** 獲取 App Store 產品反饋資訊 注:設定請求協議代理: <SKProductsRequestDelegate> @param request 請求 @param response 應用結果 */ - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSLog(@"------------收到產品反饋訊息------------"); /** 當前產品資訊*/ NSArray *myProduct = response.products; if (0 == myProduct.count) { NSLog(@"------------暫無商品------------"); // Callback //[MBProgressHUD showError:@"App Store支付異常,請重新嘗試" toView:vc.view]; [self hudAlertMessage:@"App Store支付異常,請重新嘗試"]; return; } else { NSLog(@"------------預購商品------------"); SKProduct *p = nil; for (SKProduct *product in myProduct) { NSLog(@"*** 產品資訊相關[product info] ***\n1.描述資訊(SKProduct): %@\n2.產品標題(Title): %@\n3.產品描述資訊(Description): %@\n4.產品價格(Price): %@\n5.產品 id(Product id): %@", product, product.localizedTitle, product.localizedDescription, product.price, product.productIdentifier); if([product.productIdentifier isEqualToString:self.productID]){ p = product; } } if (p != nil) { // 將要購買的產品 SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:p];// The product is available, let's submit a payment request to the queue NSLog(@"------------傳送購買請求------------"); // 發起準備購買流程(非同步) [[SKPaymentQueue defaultQueue] addPayment:payment]; } } } #pragma mark - ************************************ 交易校驗中 /** 校驗交易憑證 - App Store 該方法以客戶端為基準: 若客戶端校驗結果失敗,則伺服器不再進行二次校驗; 若客戶端校驗結果成功,則伺服器再次進行二次校驗. @param transaction 交易事物 */ - (void)verifyTransaction:(SKPaymentTransaction *)transaction { /* 使用如下方法獲取購買憑證也 ok,則需要對 data 進行判空操作, NSData *transactionReceipt = [PaymentManager receiptDataFromTransaction:transaction]; // 若 data 為空則執行如下方法 SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil]; receiptRefreshRequest.delegate = self; [receiptRefreshRequest start]; return; */ // 從沙盒中獲取到購買憑據 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) { NSData *transactionReceipt = [NSData dataWithContentsOfURL:receiptURL]; NSString *encodeStr = [transactionReceipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//轉化為base64字串 /* 驗證自動訂閱的有效 receipt 示例 { "receipt-data" : "...", "password" : "..." } */ // 拼接請求資料 NSString *bodyStr = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSData *bodyData = [bodyStr dataUsingEncoding:NSUTF8StringEncoding]; // 建立請求,驗證憑證,蘋果伺服器比較坑,建議超時時長設定稍稍長一些 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:VERIFY_RECEIPT_URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f]; request.HTTPBody = bodyData; request.HTTPMethod = @"POST"; NSError *error = nil; // 建立連線併發送同步請求,獲得官方的驗證JSON結果 NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; if (error) { NSLog(@"App Store 驗證購買過程中發生錯誤,錯誤資訊: %@",error.localizedDescription); // Callback //[MBProgressHUD showError:@"網路請求超時,請重試" toView:vc.view]; [self hudAlertMessage:@"網路請求超時,請重試"]; } else { NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:&error]; /* 內購驗證憑據返回結果狀態碼說明(status 狀態) 21000 App Store無法讀取你提供的JSON資料 21002 收據資料不符合格式 21003 收據無法被驗證 21004 你提供的共享金鑰和賬戶的共享金鑰不一致 21005 收據伺服器當前不可用 21006 收據是有效的,但訂閱服務已經過期。當收到這個資訊時,解碼後的收據資訊也包含在返回內容中 21007 收據資訊是測試用(sandbox),但卻被髮送到產品環境中驗證 21008 收據資訊是產品環境中使用,但卻被髮送到測試環境中驗證 */ NSString *status = [NSString stringWithFormat:@"%@", [result objectForKey:@"status"]]; if (0 == [status intValue]) { NSLog(@"App Store 驗證購買 --- 成功"); NSDictionary *dicReceipt = [NSDictionary dictionaryWithDictionary:[result objectForKey:@"receipt"]]; NSArray *arrInApp = [dicReceipt objectForKey:@"in_app"]; // 注:此處 in_app 欄位中資料可能為多個,需進行迴圈 NSMutableDictionary *dicInAppReult = [NSMutableDictionary dictionary]; for (NSDictionary *dict in arrInApp) { /** 產品標識*/ NSString *product_id = [NSString stringWithFormat:@"%@", [dict objectForKey:@"product_id"]]; /** 事物標識*/ NSString *transaction_id = [NSString stringWithFormat:@"%@", [dict objectForKey:@"transaction_id"]]; [dicInAppReult setValue:product_id forKey:transaction_id]; } NSString *bundle_id = [NSString stringWithFormat:@"%@", [dicReceipt objectForKey:@"bundle_id"]]; NSString *application_version = [NSString stringWithFormat:@"%@", [dicReceipt objectForKey:@"application_version"]]; if ([bundle_id isEqualToString:kGetBundleId] && [application_version isEqualToString:kAppBundle] && [dicInAppReult.allKeys containsObject:transaction.transactionIdentifier] && [[dicInAppReult objectForKey:transaction.transactionIdentifier] isEqualToString:transaction.payment.productIdentifier] ) { NSLog(@"App Store 憑證驗證 --- 成功"); // 交易成功且憑證驗證成功向服務端提交憑證進行處理 [self commitSeversSucceeWithTransaction:transaction]; } else { NSLog(@"App Store 憑證驗證 --- 失敗"); // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; // Callback //[MBProgressHUD showError:PayFailed toView:vc.view]; [self hudAlertMessage:PayFailed]; } } else { NSLog(@"App Store 憑證驗證 --- 失敗: %@", error.localizedDescription); // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; // Callback //[MBProgressHUD showError:PayFailed toView:vc.view]; [self hudAlertMessage:PayFailed]; } } } else { SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil]; receiptRefreshRequest.delegate = self; [receiptRefreshRequest start]; return; } } /** 獲取交易憑證 @param transaction 交易事物 @return 結果集 */ + (NSData *)receiptDataFromTransaction:(SKPaymentTransaction *)transaction { NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) { // 從沙盒中獲取到購買憑據 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; if (!receiptData) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" if ([transaction respondsToSelector:@selector(transactionReceipt)]) { //Works in iOS3 - iOS8, deprected since iOS7, actual deprecated (returns nil) since receiptData = transaction.transactionReceipt; } #pragma clang diagnostic pop } return receiptData; } else { return nil; } } #pragma mark - ************************************ 交易成功 - 向公司伺服器驗證購買憑證 /** 交易成功 - 向公司伺服器驗證購買憑證 status:0:訂單開始,1:充值成功,2:充值失敗 @param transaction 交易事務 */ - (void)commitSeversSucceeWithTransaction:(SKPaymentTransaction *)transaction { NSLog(@"------------交易成功向公司伺服器驗證購買憑證------------"); #pragma mark - 交易憑證相關 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; if (![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) { // 取 receipt 的時候要判空,如果檔案不存在,就要從蘋果伺服器重新重新整理下載 receipt 了 // SKReceiptRefreshRequest 重新整理的時候,需要使用者輸入 Apple ID,同時需要網路狀態良好 SKReceiptRefreshRequest *receiptRefreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil]; receiptRefreshRequest.delegate = self; [receiptRefreshRequest start]; return; } NSData *data = [NSData dataWithContentsOfURL:receiptURL]; /** 交易憑證*/ NSString *receipt_data = [data base64EncodedStringWithOptions:0]; /** 事務識別符號(交易編號) 交易編號(必傳:防止越獄下內購被破解,校驗 in_app 引數)*/ NSString *transaction_id = transaction.transactionIdentifier; // 此處忽略,純好奇心所驅,一個神奇的 data,拆不出來 ... 你贏了 // NSString * test1 = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; // NSString * test2 = [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; // NSError *error; // NSDictionary * test3 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error]; // 判空相關 if (receipt_data == nil) { receipt_data = @""; } if (transaction_id == nil) { transaction_id = @""; } NSLog(@"交易憑證:\n%@", receipt_data); NSLog(@"事務識別符號(交易編號):\n%@", transaction_id); NSLog(@"產品識別符號(內購產品編號) --- productIdentifier:\n%@", transaction.payment.productIdentifier); // NSLog(@"交易日期\nDate: %@,Date(String): %@", transaction_date, strTransaction_date); // NSLog(@"事物狀態:\n%@", transaction_state); NSMutableDictionary *dicParameter = [NSMutableDictionary dictionary]; [dicParameter setValue:kAppBundle forKey:@"appBundle"]; [dicParameter setValue:transaction_id forKey:@"transactionId"];// 查明交易識別符號(防止越獄下內購被破解,校驗 in_app 引數) [dicParameter setValue:receipt_data forKey:@"receiptData"];// 收到的收據,即收據證明 transactionReceipt kWeakSelf(self); // Request [[HttpRequestManager shareInstance] PayPOST:URL_ApplePay parameters:dicParameter isEncipherment:NO success:^(id responseObject) { NSDictionary *result = [NSDictionary dictionaryWithDictionary:responseObject]; NSString *status = [NSString stringWithFormat:@"%@", [result objectForKey:@"payStatus"]]; NSLog(@"Pay Callback Result: %@", result); // Callback if ([status isEqualToString:@"success"]) {// 成功 // Callback //[MBProgressHUD showError:PaySucceed toView:vc.view]; [self hudAlertMessage:PaySucceed]; } else { // Callback //[MBProgressHUD showError:PayException toView:vc.view]; [self hudAlertMessage:PayException]; } // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } failure:^(NSError *error) { NSLog(@"commitSeversSucceeWithTransaction: %@", error.localizedDescription); // Callback //[MBProgressHUD showError:RequestError toView:vc.view]; [self hudAlertMessage:RequestError]; }]; } #pragma mark - ************************************ 交易失敗相關 /* 內購驗證憑據返回結果狀態碼說明 21000 App Store無法讀取你提供的JSON資料 21002 收據資料不符合格式 21003 收據無法被驗證 21004 你提供的共享金鑰和賬戶的共享金鑰不一致 21005 收據伺服器當前不可用 21006 收據是有效的,但訂閱服務已經過期。當收到這個資訊時,解碼後的收據資訊也包含在返回內容中 21007 收據資訊是測試用(sandbox),但卻被髮送到產品環境中驗證 21008 收據資訊是產品環境中使用,但卻被髮送到測試環境中驗證 */ #pragma mark - 交易失敗 -> 彈出錯誤資訊 Error - (void)failedTransaction:(SKPaymentTransaction *)transaction { NSLog(@"*** 交易失敗 Error code: (%d)",transaction.error.code); if (transaction.error != nil) { switch (transaction.error.code) { case SKErrorUnknown: NSLog(@"SKErrorUnknown --- 未知的錯誤,您可能正在使用越獄手機"); break; case SKErrorClientInvalid: NSLog(@"SKErrorClientInvalid --- 當前蘋果賬戶無法購買商品(如有疑問,可以詢問蘋果客服)"); break; case SKErrorPaymentCancelled: NSLog(@"SKErrorPaymentCancelled --- 訂單已取消"); break; case SKErrorPaymentInvalid: NSLog(@"SKErrorPaymentInvalid --- 訂單無效(如有疑問,可以詢問蘋果客服)"); break; case SKErrorPaymentNotAllowed: NSLog(@"SKErrorPaymentNotAllowed --- 當前蘋果裝置無法購買商品(如有疑問,可以詢問蘋果客服)"); break; case SKErrorStoreProductNotAvailable: NSLog(@"SKErrorStoreProductNotAvailable --- 當前商品不可用"); break; default: NSLog(@"No Match Found for error -- 未知錯誤"); break; } } // Callback //[MBProgressHUD showError:PayFailed toView:vc.view]; [self hudAlertMessage:PayFailed]; // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } -(void)requestDidFinish:(SKRequest *)request { NSLog(@"------------反饋資訊結束------------"); } #pragma mark - 交易失敗 - 請求失敗 -> 彈出錯誤資訊 Error - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { NSLog(@"------------彈出錯誤資訊------------"); // Callback //[MBProgressHUD showError:error.localizedDescription toView:vc.view]; [self hudAlertMessage:error.localizedDescription]; } #pragma mark - 交易恢復處理 - (void)restoreTransaction:(SKPaymentTransaction *)transaction { // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } #pragma mark - 完成付款佇列恢復完成交易 - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentTransaction *)transaction { NSLog(@"****** 完成付款佇列恢復完成交易: %@", transaction.transactionIdentifier); /* NSMutableArray *productIDsToRestore = From the user ; SKPaymentTransaction *transaction = Current transaction ; if ([productIDsToRestore containsObject:transaction.transactionIdentifier]) { // Re-download the Apple-hosted content, then finish the transaction // and remove the product identifier from the array of product IDs. } else { [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } */ } #pragma mark - 付款佇列 - (void)paymentQueue:(SKPaymentQueue *)paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error { NSLog(@"------------付款佇列(當從使用者的購買歷史記錄向佇列新增事務時遇到錯誤時傳送)------------\n%@", error.localizedDescription); } #pragma mark - 購買交易 - (void)purchasedTransaction:(SKPaymentTransaction *)transaction { NSLog(@"------------購買交易------------"); NSArray *transactions =[[NSArray alloc] initWithObjects:transaction, nil]; [self paymentQueue:[SKPaymentQueue defaultQueue] updatedTransactions:transactions]; } /** 提示框 @param msg 提示語 */ - (void)hudAlertMessage:(NSString *)msg { UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"" message:msg delegate:nil cancelButtonTitle:NSLocalizedString(@"確定",nil) otherButtonTitles:nil]; [alerView show]; } #pragma mark - ************************************ Connection delegate - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { NSLog(@"connection delegate --- %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ switch([(NSHTTPURLResponse *)response statusCode]) { case 200: case 206: break; case 304: break; case 400: break; case 404: break; case 416: break; case 403: break; case 401: case 500: break; default: break; } } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSLog(@"connection:(NSURLConnection *)connection didFailWithError:(NSError *)error: %@", error.localizedDescription); } - (void)dealloc { [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];// 解除監聽 }
AppDelegate 中 Code:
#import "AppDelegate.h" // Apple pay #import <StoreKit/StoreKit.h> #import "PaymentManager.h" - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. // 將觀察者新增到支付佇列中 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; return YES; } #pragma mark - ****************************** Apple Pay /** 監聽購買交易結果 transactions @param queue 交易佇列 @param transactions 交易事物 */ - (void)paymentQueue:(nonnull SKPaymentQueue *)queue updatedTransactions:(nonnull NSArray<SKPaymentTransaction *> *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing:// 商品新增進列表 // Transaction is being added to the server queue. NSLog(@"------------商品新增進列表------------"); /* 不用finish,繼續觀察支付佇列,等待transaction狀態改變 */ break; case SKPaymentTransactionStateDeferred: // The transaction is in the queue, but its final status is pending external action. NSLog(@"------------事務在佇列中,但其最終狀態是等待外部操作"); /* 不用finish,繼續觀察支付佇列 */ break; case SKPaymentTransactionStateFailed:// 交易失敗 // Transaction was cancelled or failed before being added to the server queue. NSLog(@"------------交易失敗: %@", transaction.error.localizedDescription); /* 檢查錯誤並根據需要處理,然後呼叫 finishTransaction */ [[PaymentManager manager] failedTransaction:transaction]; break; case SKPaymentTransactionStatePurchased:// 交易完成 // Transaction is in queue, user has been charged. Client should complete the transaction. NSLog(@"------------交易完成: %@", transaction.payment.productIdentifier); /* 分發內容給使用者,然後呼叫 finishTransaction */ [[PaymentManager manager] verifyTransaction:transaction]; break; case SKPaymentTransactionStateRestored:// 已經購買過該商品 // Transaction was restored from user's purchase history. Client should complete the transaction. NSLog(@"------------已經購買過該商品"); /* 分發內容給使用者,然後呼叫 finishTransaction */ [[PaymentManager manager] verifyTransaction:transaction]; break; default: break; } } }
以上便是此次內購支付相關小結,還望多多指點交流!