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";
</pre><p></p><pre code_snippet_id="1612284" snippet_file_name="blog_20160316_1_6013313" name="code" class="objc">#import <StoreKit/StoreKit.h>
@interface TestViewControler() <SKPaymentTransactionObserver, SKProductsRequestDelegate>
....
- (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