由于苹果服务器返回很慢,并且一般我们都会有一个自己的商店界面,就不必向苹果服务器去请求商品列表了。 购买时传要购买商品的ID(在App Store Connect 创建的产品ID)就可以了。
/// 请求苹果的服务器能够销售的商品 /// @param products 【产品ID】 - (void)requestProductsWithProductArray:(NSArray *)products { NSSet *set = [[NSSet alloc] initWithArray:products]; // "异步"请求苹果能否销售 SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set]; request.delegate = self; // 启动请求 [request start]; }拿到了苹果服务器返回的可购买商品后,下单进行购买。
/// 下单购买商品 /// @param productID 商品ID - (void)buyProduct:(NSString *)productID { // 从自己的商品列表中取出要购买的商品 SKProduct *product = self.productDict[productID]; // 要购买的产品生成单据 SKPayment *payment = [SKPayment paymentWithProduct:product]; // 加入队列准备付款购买 [[SKPaymentQueue defaultQueue] addPayment:payment]; }// 发送网络POST请求,对购买凭据进行验证 // 沙盒测试 https://sandbox.itunes.apple.com/verifyReceipt // 正式环境 https://buy.itunes.apple.com/verifyReceipt
当SKPaymentTransactionStatePurchased 完成交易时,我们需要验证一下。 a、拿到收据上传到自己的服务器进行验证。
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 从沙盒中获取到购买凭据 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];b、如果是单机就本地向苹果服务器请求验证。
/// 验证购买凭据 /// @param ProductID 商品ID - (void)verifyPruchaseWithID:(NSString *)ProductID { // 验证凭据,获取到苹果返回的交易凭据 // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 从沙盒中获取到购买凭据 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; // 发送网络POST请求,对购买凭据进行验证 //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt //In the real environment, use https://buy.itunes.apple.com/verifyReceipt // Create a POST request with the receipt data. NSURL *url = [NSURL URLWithString:checkURL]; NSLog(@"checkURL:%@",checkURL); // 国内访问苹果服务器比较慢,timeoutInterval需要长一点 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0f]; request.HTTPMethod = @"POST"; NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; request.HTTPBody = payloadData; // 提交验证请求,并获得官方的验证JSON结果 [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSData *result = data; // 官方验证结果为空 if (result == nil) { //NSLog(@"验证失败"); //验证失败,通知代理 [self.delegate IAPToolCheckFailedWithProductID:ProductID andInfo:result]; } NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil]; //NSLog(@"RecivedVerifyPruchaseDict:%@", dict); if (dict != nil) { // 验证成功,通知代理 [self.delegate IAPToolBoughtProductSuccessedWithProductID:ProductID andInfo:dict receipt:encodeStr]; } else { //验证失败,通知代理 [self.delegate IAPToolCheckFailedWithProductID:ProductID andInfo:result]; } }] resume]; }非消耗型项目一定要有恢复商品的功能,不会被拒绝哦~
/// 恢复商品 - (void)restorePurchase { // 恢复已经完成的所有交易.(仅限永久有效商品) [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; }封装内购工具类 FSInAppPurchaseTool.h
#import <Foundation/Foundation.h> #import <StoreKit/StoreKit.h> NS_ASSUME_NONNULL_BEGIN /// 内购工具的代理 @protocol FSInAppPurchaseToolDelegate <NSObject> /// 系统错误 -(void)IAPToolSysWrong; /// 可购买商品 /// @param products 商品数组 -(void)IAPToolGotProducts:(NSMutableArray *)products; /// 购买成功 /// @param productID 购买成功的商品ID /// @param infoDic 官方验证结果 /// @param receipt 凭证 -(void)IAPToolBoughtProductSuccessedWithProductID:(NSString *)productID andInfo:(nullable NSDictionary *)infoDic receipt:(nonnull NSString *)receipt; /// 取消购买 /// @param productID 商品ID -(void)IAPToolCanceldWithProductID:(NSString *)productID; /// 购买成功,开始验证购买 /// @param productID 商品ID -(void)IAPToolBeginCheckingdWithProductID:(NSString *)productID; /// 验证失败 /// @param productID 商品ID /// @param infoData 官方验证结果 -(void)IAPToolCheckFailedWithProductID:(NSString *)productID andInfo:(NSData *)infoData; /// 恢复了已购买的商品(永久性商品) /// @param productID 商品ID -(void)IAPToolRestoredProductID:(NSString *)productID; /// 恢复了已购买的商品(永久性商品) /// @param products 商品信息 /// @param receipt 凭证 -(void)IAPToolPaymentQueueRestoreCompletedTransactionsFinished:(NSArray *)products receipt:(nonnull NSString *)receipt; @optional /// 连接itunes store 错误 /// @param error 错误信息 -(void)IAPToolPaymentQueueRestoreCompletedTransactionsFailedWithError:(NSString *)error; @end #pragma mark -FSInAppPurchaseTool /// 内购工具 @interface FSInAppPurchaseTool : NSObject @property (nonatomic, weak) id <FSInAppPurchaseToolDelegate> delegate; /// 购买完后是否在iOS端向服务器验证一次,默认为YES @property (nonatomic, assign) BOOL CheckAfterPay; + (FSInAppPurchaseTool *)defaultTool; /// 询问苹果的服务器能够销售哪些商品 /// @param products 商品ID的数组 - (void)requestProductsWithProductArray:(NSArray *)products; /// 用户决定购买商品 /// @param productID 商品ID - (void)buyProduct:(NSString *)productID; /// 恢复商品(仅限永久有效商品) - (void)restorePurchase; @end NS_ASSUME_NONNULL_ENDFSInAppPurchaseTool.m
#import "FSInAppPurchaseTool.h" /// 苹果服务器购买凭据进行验证 #ifdef DEBUG #define checkURL @"https://sandbox.itunes.apple.com/verifyReceipt" #else #define checkURL @"https://buy.itunes.apple.com/verifyReceipt" #endif @interface FSInAppPurchaseTool ()<SKPaymentTransactionObserver,SKProductsRequestDelegate> /// 商品字典 @property(nonatomic,strong)NSMutableDictionary *productDict; @end @implementation FSInAppPurchaseTool static FSInAppPurchaseTool *storeTool; + (FSInAppPurchaseTool *)defaultTool{ if(!storeTool){ storeTool = [FSInAppPurchaseTool new]; [storeTool setup]; } return storeTool; } #pragma mark 初始化 - (void)setup{ self.CheckAfterPay = YES; // 设置购买队列的监听器 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } #pragma mark 请求苹果的服务器能够销售的商品 /// 请求苹果的服务器能够销售的商品 /// @param products 产品 - (void)requestProductsWithProductArray:(NSArray *)products { // 能够销售的商品 NSSet *set = [[NSSet alloc] initWithArray:products]; // "异步"请求苹果能否销售 SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set]; request.delegate = self; // 启动请求 [request start]; } #pragma mark - SKProductsRequestDelegate /// 获取请求结果,把商品加入到自己的商品列表 /// @param request 请求 /// @param response 返回结果 - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { if (self.productDict == nil) { self.productDict = [NSMutableDictionary dictionaryWithCapacity:response.products.count]; } NSMutableArray *productArray = [NSMutableArray array]; for (SKProduct *product in response.products) { //NSLog(@"%@", product.productIdentifier); [self.productDict setObject:product forKey:product.productIdentifier]; [productArray addObject:product]; } // 通知代理 [self.delegate IAPToolGotProducts:productArray]; } #pragma mark - 下单购买商品 /// 下单购买商品 /// @param productID 商品ID - (void)buyProduct:(NSString *)productID { // 从自己的商品列表中取出要购买的商品 SKProduct *product = self.productDict[productID]; // 要购买的产品生成单据 SKPayment *payment = [SKPayment paymentWithProduct:product]; // 加入队列准备付款购买 [[SKPaymentQueue defaultQueue] addPayment:payment]; } #pragma mark - SKPaymentTransaction Observer #pragma mark 购买队列状态变化,判断购买状态是否成功 /// 当交易数组发生更改(添加或状态更改)时发送。客户端应该检查事务的状态并适当地完成 /// @param queue 队列 /// @param transactions 交易 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { // 处理结果 for (SKPaymentTransaction *transaction in transactions) { NSLog(@"队列状态变化 %@", transaction); // 如果收据状态是购买完成 switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: { // 完成交易 //NSLog(@"购买完成 %@", transaction.payment.productIdentifier); if (self.CheckAfterPay) { // 需要向苹果服务器验证一下 // 通知代理 [self.delegate IAPToolBeginCheckingdWithProductID:transaction.payment.productIdentifier]; // 验证购买凭据 [self verifyPruchaseWithID:transaction.payment.productIdentifier]; } else { // 不需要向苹果服务器验证 // 验证凭据,获取到苹果返回的交易凭据 // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 从沙盒中获取到购买凭据 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; // 通知代理 [self.delegate IAPToolBoughtProductSuccessedWithProductID:transaction.payment.productIdentifier andInfo:nil receipt:encodeStr]; } // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } break; case SKPaymentTransactionStateRestored: { // 恢复购买 //NSLog(@"恢复成功 :%@", transaction.payment.productIdentifier); // 通知代理 [self.delegate IAPToolRestoredProductID:transaction.payment.productIdentifier]; // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } break; case SKPaymentTransactionStateFailed: { // 加入队列之前取消或失败 // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; //NSLog(@"交易失败"); [self.delegate IAPToolCanceldWithProductID:transaction.payment.productIdentifier]; } break; case SKPaymentTransactionStatePurchasing: { // 交易被添加到服务器队列中 NSLog(@"正在购买"); } break; default: { NSLog(@"state:%ld",(long)transaction.transactionState); // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } break; } } } /// 当用户购买历史中的所有事务都已成功添加回队列时发送。 /// @param queue 队列 - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { NSMutableArray *products = [[NSMutableArray alloc] init]; NSLog(@"received restored transactions: %lu", (unsigned long)queue.transactions.count); for (SKPaymentTransaction *transaction in queue.transactions) { [products addObject:transaction.payment.productIdentifier]; } NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; [self.delegate IAPToolPaymentQueueRestoreCompletedTransactionsFinished:products receipt:encodeStr]; } /// 当事务从队列中移除时发送(通过finishTransaction:) /// @param queue 队列 /// @param transactions 交易 - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray<SKPaymentTransaction *> *)transactions API_AVAILABLE(ios(3.0), macos(10.7)) { } /// 在将用户购买历史记录中的事务添加回队列时遇到错误时发送 /// @param queue 队列 /// @param error 错误信息 - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error API_AVAILABLE(ios(3.0), macos(10.7)) { NSDictionary *userinfo = [[NSDictionary alloc] initWithDictionary:error.userInfo]; if(userinfo) { NSString *str = userinfo[NSLocalizedDescriptionKey]; if (str.length) { if ([self.delegate respondsToSelector:@selector(IAPToolPaymentQueueRestoreCompletedTransactionsFailedWithError:)]) { [self.delegate IAPToolPaymentQueueRestoreCompletedTransactionsFailedWithError:str]; } } } else { if ([self.delegate respondsToSelector:@selector(IAPToolPaymentQueueRestoreCompletedTransactionsFailedWithError:)]) { [self.delegate IAPToolPaymentQueueRestoreCompletedTransactionsFailedWithError:@""]; } } } #pragma mark - 恢复商品 /// 恢复商品 - (void)restorePurchase { // 恢复已经完成的所有交易.(仅限永久有效商品) [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } #pragma mark 验证购买凭据 /// 验证购买凭据 /// @param ProductID 商品ID - (void)verifyPruchaseWithID:(NSString *)ProductID { // 验证凭据,获取到苹果返回的交易凭据 // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 从沙盒中获取到购买凭据 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; // 发送网络POST请求,对购买凭据进行验证 //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt //In the real environment, use https://buy.itunes.apple.com/verifyReceipt // Create a POST request with the receipt data. NSURL *url = [NSURL URLWithString:checkURL]; NSLog(@"checkURL:%@",checkURL); // 国内访问苹果服务器比较慢,timeoutInterval需要长一点 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0f]; request.HTTPMethod = @"POST"; NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; request.HTTPBody = payloadData; // 提交验证请求,并获得官方的验证JSON结果 [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSData *result = data; // 官方验证结果为空 if (result == nil) { //NSLog(@"验证失败"); //验证失败,通知代理 [self.delegate IAPToolCheckFailedWithProductID:ProductID andInfo:result]; } NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil]; //NSLog(@"RecivedVerifyPruchaseDict:%@", dict); if (dict != nil) { // 验证成功,通知代理 [self.delegate IAPToolBoughtProductSuccessedWithProductID:ProductID andInfo:dict receipt:encodeStr]; } else { //验证失败,通知代理 [self.delegate IAPToolCheckFailedWithProductID:ProductID andInfo:result]; } }] resume]; } @end应用内购工具类
// 创建单例 { FSInAppPurchaseTool *IAPTool = [FSInAppPurchaseTool defaultTool]; IAPTool.delegate = self; } /// 请求store产品 /// @param payno 服务器订单号 /// @param pid store 产品id - (void)startApplePayWithPayno:(NSString*)payno PID:(NSString*)pid { if (!payno.length) { ALERT(@"下订单失败,请稍后操作!"); return; } if (!pid.length) { ALERT(@"请稍后操作!"); return; } [self showHUD]; [[FSInAppPurchaseTool defaultTool] requestProductsWithProductArray:@[pid]]; } /// 恢复购买 - (IBAction)onRestoreButtonClick:(id)sender { [self showHUD]; //检查是否有恢复购买项 [[FSInAppPurchaseTool defaultTool] restorePurchase]; } #pragma mark -------- FSInAppPurchaseToolDelegate /// 苹果返回可购买的商品,前往购买 /// @param products 商品数组【SKProduct】 -(void)IAPToolGotProducts:(NSMutableArray *)products { NSLog(@"GotProducts:%@",products); [self hideHUD]; if (products.count) { SKProduct *product = products[0]; [self showHUD]; [[FSInAppPurchaseTool defaultTool] buyProduct:product.productIdentifier]; } // for (SKProduct *product in products){ // NSLog(@"localizedDescription:%@\nlocalizedTitle:%@\nprice:%@\npriceLocale:%@\nproductID:%@", // product.localizedDescription, // product.localizedTitle, // product.price, // product.priceLocale, // product.productIdentifier); // NSLog(@"--------------------------"); // } } /// 支付失败/取消 /// @param productID 商品id -(void)IAPToolCanceldWithProductID:(NSString *)productID { NSLog(@"canceld:%@",productID); [self hideHUD]; } /// 支付成功 本地向苹果服务器进行验证 (CheckAfterPay为YES 执行此步骤) /// @param productID 商品id -(void)IAPToolBeginCheckingdWithProductID:(NSString *)productID { FSLog(@"BeginChecking:%@",productID); [self hideHUD]; } /// 支付成功,拿到收据传到自己的服务器进行验证 (CheckAfterPay为NO 执行此步骤,为YES时,验证成功也会执行) /// @param productID 商品id /// @param infoDic 验证结果 /// @param receipt 收据 -(void)IAPToolBoughtProductSuccessedWithProductID:(NSString *)productID andInfo:(nullable NSDictionary *)infoDic receipt:(nonnull NSString *)receipt { FSLog(@"BoughtSuccessed:%@",productID); FSLog(@"successedInfo:%@",infoDic); [self hideHUD]; NSString *payno = self.payinfo[productID]; if (payno.length) { // 请求自己服务器进行验证 [self requestPortCheckApplePay:receipt Payno:payno]; } else { FSLog(@"订单号有误"); } } /// 支付成功了,但向苹果服务器验证失败了 /// @param productID 商品id /// @param infoData 验证结果 -(void)IAPToolCheckFailedWithProductID:(NSString *)productID andInfo:(NSData *)infoData { FSLog(@"CheckFailed:%@",productID); [self hideHUD]; } /// 挨个返回已购买的商品(仅限永久有效商品) /// @param productID 商品id -(void)IAPToolRestoredProductID:(NSString *)productID { FSLog(@"Restored:%@",productID); } /// 返回所有已购买的记录 /// @param products 商品数组 /// @param receipt 收据 -(void)IAPToolPaymentQueueRestoreCompletedTransactionsFinished:(NSArray *)products receipt:(NSString *)receipt { FSLog(@"Restored Finished%@",products); [self hideHUD]; self.restoreProducts = products; self.restoreReceipt = receipt; if (products.count) { /// 有购买记录就显示恢复购买 self.restoreButton.hidden = NO; if (self.restoreReceipt.length) { // 向自己服务器请求恢复购买 [self requestPortRestore:self.restoreReceipt]; } } else { ALERT(@"没有购买记录,无需购买恢复"); } } /// 恢复购买失败 (登录appleid 弹窗 取消) - (void)IAPToolPaymentQueueRestoreCompletedTransactionsFailedWithError:(NSString *)error { FSLog(@"Restored Failed%@",error); [self hideHUD]; } //内购系统错误了 -(void)IAPToolSysWrong { FSLog(@"SysWrong"); [self hideHUD]; }iOS App Store Connect 内购详解.