一、Agreements, Tax, and Banking Information (填写协议、税务和银行卡信息)
http://www.jianshu.com/p/f7bff61e0b31 IOS内购IAP,设置及使用&填写协议、税务
二、Certificates, Identifiers & Profiles (证书设置)
安装好项目证书后,进入Capabilities 设置界面,将In-App-Purchase 开关打开就OK.
三、iTunes Connect (添加沙箱测试技术员、添加应用内购商品信息)
1、iTunes Connect —>用户和职能 —>沙盒测试技术员:添加沙盒测试账号
2、我的App—>准提交的项目—>功能—>App内购买项目:添加内购商品信息
【注】提交审核前需要上传购买界面截图,供苹果审核
四、代码实现
// // AppleIAPService.h // OneMate // // Created by ChenWenHan on 2017/8/30. // Copyright ? 2017年 OneMate1314. All rights reserved. // #import #import "PayProtocol.h" typedef NS_ENUM(NSInteger, IAPPurchaseStatus) { IAPPurchaseFailed, // Indicates that the purchase was unsuccessful IAPPurchaseSucceeded, // Indicates that the purchase was successful IAPRestoredFailed, // Indicates that restoring products was unsuccessful IAPRestoredSucceeded, // Indicates that restoring products was successful }; @interface AppleIAPService : NSObject + (AppleIAPService *)sharedInstance; @end // // AppleIAPService.m // OneMate // // Created by ChenWenHan on 2017/8/30. // Copyright ? 2017年 OneMate1314. All rights reserved. // #import "AppleIAPService.h" #import #import "SkyApiManager.h" @interface AppleIAPService()@property (nonatomic, copy) MsgBlock resultBlock; @end @implementation AppleIAPService //类装载的时候系统调用该方法 + (void)load { [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidFinishLaunchingNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { [AppleIAPService sharedInstance]; }]; } + (AppleIAPService *)sharedInstance { static dispatch_once_t onceToken; static AppleIAPService * sharedInstance; dispatch_once(&onceToken, ^{ sharedInstance = [[AppleIAPService alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } return self; } -(void)purchase:(NSString *)product_id resultBlock:(MsgBlock)resultBlock{ self.resultBlock = resultBlock; if ([SKPaymentQueue canMakePayments]) {//用户允许支付 NSSet *set = [NSSet setWithObjects:product_id, nil]; SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set]; request.delegate = self; [request start]; } else { NSError *error = [NSError errorWithDomain:@"IAP" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"检查是否允许支付功能或者该设备是否支持支付." }]; if(self.resultBlock) self.resultBlock(nil,error); } } #pragma mark - SKProductsRequestDelegate - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSArray *products = response.products; if (products.count == 0) { NSError *error = [NSError errorWithDomain:@"IAP" code:-2 userInfo:@{ NSLocalizedDescriptionKey : @"商品信息无效,请联系客服。" }]; if(self.resultBlock) self.resultBlock(nil,error); return; } SKProduct *product = products.firstObject; NSLog(@"请求购买:%@",product.productIdentifier); SKPayment * payment = [SKPayment paymentWithProduct:product]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } - (void)requestDidFinish:(SKRequest *)request{ NSLog(@"%s",__FUNCTION__); } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{ NSLog(@"%s:%@",__FUNCTION__,error.localizedDescription); if(self.resultBlock) self.resultBlock(nil,error); } #pragma mark - SKPaymentTransactionObserver //监听购买结果 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { //NSLog(@"transaction_id:%@",transaction.transactionIdentifier); switch (transaction.transactionState) { case SKPaymentTransactionStatePurchasing: // 0 购买事务进行中 break; case SKPaymentTransactionStatePurchased: // 1 交易完成 [self completeTransaction:transaction forStatus:IAPPurchaseSucceeded]; break; case SKPaymentTransactionStateFailed: // 2 交易失败 [self completeTransaction:transaction forStatus:IAPPurchaseFailed]; break; case SKPaymentTransactionStateRestored: // 3 恢复交易成功:从用户的购买历史中恢复了交易 [self completeTransaction:transaction forStatus:IAPRestoredSucceeded]; break; default: [[SKPaymentQueue defaultQueue] finishTransaction:transaction];//结束支付事务 break; } } } // Sent when transactions are removed from the queue (via finishTransaction:). - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions { for(SKPaymentTransaction *transaction in transactions){ NSLog(@"%@ was removed from the payment queue.", transaction.payment.productIdentifier); } } // Sent when an error is encountered while adding transactions from the user's purchase history back to the queue. - (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error { NSLog(@"%s error:%@",__FUNCTION__,error.localizedDescription); } // Sent when all transactions from the user's purchase history have successfully been added back to the queue. - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { NSLog(@"%s",__FUNCTION__); } #pragma mark Complete transaction -(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(IAPPurchaseStatus)status { //Do not send any notifications when the user cancels the purchase if (transaction.error.code != SKErrorPaymentCancelled){ // Notify the user switch (status) { case IAPPurchaseSucceeded: case IAPRestoredSucceeded: { [self uploadReceipt:transaction]; } break; case IAPPurchaseFailed: case IAPRestoredFailed: default: { if(self.resultBlock) self.resultBlock(nil,transaction.error); [[SKPaymentQueue defaultQueue] finishTransaction:transaction];//结束支付事务 } break; } }else{ NSError *error = [NSError errorWithDomain:@"IAP" code:-3 userInfo:@{ NSLocalizedDescriptionKey : @"已取消支付。" }]; if(self.resultBlock) self.resultBlock(nil,error); // Remove the transaction from the queue for purchased and restored statuses [[SKPaymentQueue defaultQueue] finishTransaction:transaction];//结束支付事务 } } /** 上传支付凭证到后台 @param transaction 支付事务 */ //附加:官方文档:向苹果校验支付结果https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html -(void)uploadReceipt:(SKPaymentTransaction *)transaction { NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; NSData *receipt = [NSData dataWithContentsOfURL:receiptURL]; if (!receipt) { /* No local receipt -- handle the error. */ NSLog(@"receipt 本地数据不存在"); return; } NSString *base64_receipt = [receipt base64EncodedStringWithOptions:0]; NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; [params setObject:base64_receipt forKey:@"receipt"]; [params setObject:transaction.transactionIdentifier forKey:@"transaction_id"]; wSelf(self); [SkyApiManager fetch:params url:kUrlIAPSuccessNofity requester:nil block:^(id result, NSError *error) { if (!error) { NSInteger code = [[result objectForKey:kResponseCode] integerValue]; NSString *msg = [result objectForKey:kResponseMsg]; MessageObj *msgObj = [[MessageObj alloc] init]; msgObj.code = code; msgObj.msg = msg; if(wSelf.resultBlock) wSelf.resultBlock(msgObj,nil); [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; }else{ if(wSelf.resultBlock) wSelf.resultBlock(nil,error); } }]; } - (void)dealloc { NSLog(@"%s销毁",__FUNCTION__); [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; } @end
五、receipt-data 支付凭证校验
https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html 官方文档:向苹果校验支付凭证
服务器端开发处理流程
http://blog.csdn.net/teng_ontheway/article/details/47023119 漏单处理
苹果反馈的状态码;
21000App Store无法读取你提供的JSON数据
21002 收据数据不符合格式
21003 收据无法被验证
21004 你提供的共享密钥和账户的共享密钥不一致
21005 收据服务器当前不可用
21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证 【请求sandbox校验支付凭证】
21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
六、注意事项&附加资源
https://developer.apple.com/library/content/samplecode/sc1991/Introduction/Intro.html#//apple_ref/doc/uid/DTS40014726-Intro-DontLinkElementID_2 StoreKitSuite 官方Demo
个人碰到的坑:
类装载的时候实例化[AppleIAPService sharedInstance];重复注册[[SKPaymentQueue defaultQueue] addTransactionObserver:self];导致重复接监听支付结果- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;最终导致重复向后台服务器上传相同的支付凭证。??
http://blog.csdn.net/darling_shadow/article/details/51538267 [iOS]应用内支付(内购)的个人开发过程及坑!
http://www.jianshu.com/p/86ac7d3b593a
http://blog.csdn.net/shenjie12345678/article/details/40978977
苹果IAP支付时序图:
苹果IAP支付时序图.png
图片来源:https://www.processon.com/view/link/598c062be4b02e9a26eed69a