iOS 应用内购买(In-App Purchase)之协议、税务和银行业务
使用IAP之前,需要签订协议,查看上面的链接。
IAP开发
添加App内购项目
登录 iTunes Connect ,选择我的app,点击要使用IAP的应用。
添加app内购买项目,这里有几种类型可选,根据你的产品选择需要的类型,比如Q币是消耗类型,QQ会员是非续订订阅等等。
虚拟货币/充值模式引入
之所以要引入虚拟货币或充值,有两个重要的原因:
1. 每一个商品都需要在 ITunes Connect 中创建一个对应的内购项目,而内购项目还需要提交审核,考虑到商品后期增删改,这是一个坑。
2. 苹果的内购项目的定价是固定的,参看这里,在列表中选择一个作为售价,这样就不一定契合我们实际商品定价,也不方便我们对商品改价促销之类。
引入充值或虚拟货币则可以解决这个问题
以虚拟货币(金币)为例,流程大致如下:
应用内购商品提供 100金币,200金币,500金币,1000金币4中类型的付费购买。
用户付费购买对应的商品(金币),用户账户里增加对应的金币数量,用户再使用金币购买应用内的实际商品(比如A课程,耗费288金币)
这样一来,ITunes Connect 中的内购项目只需要创建一次,而实际商品(课程)也可以按需增删改。
IPA购买流程
1. IOS客户端发起购买请求,购买指定产品ID对应的商品。
2. 支付成功/失败/取消后,苹果服务器返回支付结果,支付成功返回 receipt 。
3. IOS客户端获取到 receipt 后,提交到应用服务器(我们自己的服务器)验证。
4. 应用服务器将验证结果发给客户端。
IOS代码部分
// 这里产品id就是上面添加的内购项目产品id
static NSString *productID = @"100RMB";
#import@interface TestViewControler() .... - (void)viewDidLoad { // 添加购买监听 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (void) dealloc { // 移除监听 [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; } .... #pragma mark - 苹果 IAP 支付 // 开始 IAP 流程 - (void) startIAPPay { // 检测是否允许内购 if([SKPaymentQueue canMakePayments]){ [self.navigationController.view makeToastActivity:CSToastPositionCenter]; self.navigationController.view.userInteractionEnabled = NO; [btnBottom setTitle:NSLocalizedString(@"请稍等...", nil) forState:UIControlStateNormal]; btnBottom.userInteractionEnabled = NO; NSSet *nsset = [NSSet setWithArray:@[productID]]; // 请求商品 SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset]; request.delegate = self; [request start]; }else{ [[[UIAlertView alloc]initWithTitle:nil message:NSLocalizedString(@"用户禁止应用内付费购买", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"确定", nil) otherButtonTitles:nil, nil] show]; } } //收到产品返回信息 - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ if(response.products.count == 0){ // 无法获取产品信息,购买失败 [self.navigationController.view hideToastActivity]; self.navigationController.view.userInteractionEnabled = YES; [[[UIAlertView alloc]initWithTitle:nil message:NSLocalizedString(@"无法获取产品信息,请重试", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"确定", nil) otherButtonTitles:nil, nil] show]; return; } SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:response.products[0]]; payment.quantity = (NSInteger)_coursesDetailInfo.price;//购买次数=价钱 if (payment.quantity == 0) { payment.quantity = 1; } payment.applicationUsername = [NSString stringWithFormat:@"%ld", self.orderId]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } //请求失败 - (void)request:(SKRequest *)request didFailWithError:(NSError *)error { NSLog(@"商品信息请求错误:%@", error); [self.navigationController.view hideToastActivity]; self.navigationController.view.userInteractionEnabled = YES; [btnBottom setTitle:NSLocalizedString(@"立即支付", nil) forState:UIControlStateNormal]; btnBottom.userInteractionEnabled = YES; [[[UIAlertView alloc]initWithTitle:nil message:NSLocalizedString(@"无法获取产品信息,请重试", nil) delegate:nil cancelButtonTitle:NSLocalizedString(@"确定", nil) otherButtonTitles:nil, nil] show]; } - (void)requestDidFinish:(SKRequest *)request { // [self.navigationController.view hideToastActivity]; // self.navigationController.view.userInteractionEnabled = YES; } //监听购买结果 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction { for(SKPaymentTransaction *tran in transaction){ switch (tran.transactionState) { case SKPaymentTransactionStatePurchased: NSLog(@"交易完成"); [self completeTransaction:tran]; break; case SKPaymentTransactionStatePurchasing: NSLog(@"商品添加进列表"); break; case SKPaymentTransactionStateRestored: NSLog(@"已经购买过商品"); [self restoreTransaction:tran]; break; case SKPaymentTransactionStateFailed: NSLog(@"交易失败"); [self failedTransaction:tran]; break; default: break; } } } // 交易完成 - (void)completeTransaction:(SKPaymentTransaction *)transaction{ NSLog(@"交易完成,走验证通道"); NSLog(@"transaction=%@", transaction); NSString *productIdentifier = transaction.payment.productIdentifier; NSLog(@"productIdentifier=%@", productIdentifier); NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; NSData *receipt = [NSData dataWithContentsOfURL:receiptURL]; NSString *strReceipt = [receipt base64EncodedStringWithOptions:0]; NSLog(@"strReceipt=%@", strReceipt); NSString *orderId = transaction.payment.applicationUsername; NSLog(@"orderId=%@", orderId); if ([productIdentifier length] > 0) { // 向自己的服务器验证购买凭证 } // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; [self.navigationController.view hideToastActivity]; self.navigationController.view.userInteractionEnabled = YES; [self payCompleted]; } // 交易失败 - (void)failedTransaction:(SKPaymentTransaction *)transaction { [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; [self.navigationController.view hideToastActivity]; self.navigationController.view.userInteractionEnabled = YES; [btnBottom setTitle:NSLocalizedString(@"立即支付", nil) forState:UIControlStateNormal]; btnBottom.userInteractionEnabled = YES; } // 已经购买过该商品 - (void)restoreTransaction:(SKPaymentTransaction *)transaction { // 对于已购商品,处理恢复购买的逻辑 [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; [self.navigationController.view hideToastActivity]; self.navigationController.view.userInteractionEnabled = YES; [btnBottom setTitle:NSLocalizedString(@"立即支付", nil) forState:UIControlStateNormal]; btnBottom.userInteractionEnabled = YES; }
服务器验证
验证方法查看这里
测试服务器地址:https://sandbox.itunes.apple.com/verifyReceipt
正式服务器地址:https://buy.itunes.apple.com/verifyReceipt
验证时先向正式环境发起,如果返回码是 21007,说明是测试购买,再转向测试环境验证。
测试购买
进入 ITunes Connect 》用户和职能 》添加 沙箱技术测试员,然后就可以用这个测试账号发起购买了。
其它
记得开启 Capabilities > In-App Purchase