iOS 内购(IAP) 简单总结

iOS 内购(IAP) 简单总结

1 IAP详细规则

IAP(In-App Purchase),是指苹果App Store的应用内购买,是苹果为APP内购买虚拟商品或服务提供的一套交易系统。

1.1适用范围

在APP内需要付费使用的产品功能或虚拟商品、服务。例如在斗鱼上充鱼翅、在视频APP上冲会员、在王者农药里买皮肤等....这些购买的商品或服务只能在APP内消费使用的适用IAP。反之,在京东或淘宝买东西、在滴滴上打车等...这些购买的商品或服务只能在APP外使用的情况是不适用IAP的。

1.2 IAP类型

IAP是一套商品交易系统,而非简单的支付系统。每一个购买项目都需要在App的itunes connect后台创建一个商品,提交给苹果审核,审核通过后,购买项目才会生效。

在创建IAP商品时,主要有四中类型:

1.2.1 Consumable products (该类型适用于可多次购买的消耗型项目,如游戏道具、虚拟币等。)

1.2.2 Non-consumable products (该类型适用于一次购买永久有效的项目,如电子书、游戏关卡等。

该类型项目支持跨设备同步和本地restore,比如说,用户在某个App中购买了一本书,可在所有相同Apple ID设备的App中免费获取这本书,而不要需要借助App本身的帐号体系,即使在App中删除了这本书,也可免费重新获取。)

1.2.3 Auto-renewable subscriptions (该类型适用于自动续费的订阅项目,如Apple Music的按月订阅,用户购买后会每月自动续费,直到用户手动取消或者开发者下架IAP项目。

类似Non-consumable products,该类型也支持跨设备同步和本地restore机制。)

1.2.4 Non-renewable subscriptions (该类型适用于固定有效期的非自动续费项目,如云音乐的会员和一些视频App的会员。没有跨设备同步和本地restore机制,用户可以多次购买。)

IAPType.png

*********特别说明本文侧重讲解 消耗性项目 的流程*********

2 IAP设计开发要点

2.1 开发之前需要 先向AppStore提交资料,填写协议、税务和银行业务 具体流程可参考 http://www.jianshu.com/p/cb1c8b4ba2c0

2.2 然后需要先在itunes connect后台创建IAP商品,并按规范填写product id、商品名称、价格、截图等信息。

如果App当前版本支持新增的IAP项目,可不用发版直接提交IAP审核。如果需要App新功能配合,则需要和App版本一起提交。

《In-App Purchase Configuration Guide for iTunes Connect》详细介绍了IAP的创建和提交流程:https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/iTunesConnectInAppPurchase_Guide/Chapters/CreatingInAppPurchaseProducts.html#//apple_ref/doc/uid/TP40013727-CH3-SW1

注意点:

2.2.1尽量不要删除已创建的IAP

已创建的IAP除了product id之外的所有信息都可以修改,如果删除了一个IAP,将无法再创建一个相同product id的IAP,也意味着该product id永久失效。而product id一般有特定的命名规则,用来标示App内的购买项目,如果命名规则下有某个product id永久失效,可能会导致整个product id命名规则都要修改,掉进坑里~

2.2.2 注意区分reference name和display name

eference name是给开发者自己看的,display name会在IAP支付流程的确认购买系统弹窗中展示给用户,而且不能随意修改(修改需要重新提交IAP审核),所以命名的时候要弄清楚。

2.3 IAP支付流程

IAP的支付模式分为客户端校验和服务端校验两种模式,客户端校验模式因为容易伪造支付凭证,安全性比较低,一般只有简单的单机APP才会使用,大部分APP都会采用服务端校验模式。

不同的IAP支付流程也会有一些小差异,主要是因为restore机制,下面是最常用的Consumable products和Non-renewable subscriptions类型的支付流程(服务端校验模式)

1.用户准备购买某个项目时,App客户端通过product id向苹果API请求支付信息

2.手机系统弹窗验证用户的Apple ID(可能需要输入Apple ID密码或验证touch ID)

3.Apple ID验证完成后,苹果API向App客户端返回用户将要支付的价格和货币单位

4.手机系统弹窗提示用户确认将要购买的内容和价格,用户点击确认购买

5.App客户端获得苹果API返回的支付成功通知以及支付凭据,向App服务端请求校验支付凭据

6.App服务端拿到客户端的支付凭据,再向苹果服务器请求校验支付凭据(避免一些越狱插件伪造客户端支付凭据)

7.App服务端校验支付凭据成功,通知App客户端

8.App收到支付凭据校验成功通知,代表用户付费成功,再处理后续业务逻辑

3 具体的代码实现

3.1 想要让用户购买我们的商品,首先我们得有个商品的展示界面。上面我们已经在itunes connect后台创建过我们的商品。创建过的商品都有一个唯一的产品标识 ProductID。通过ProductID我们可以拿到商品的具体信息。

3.1.1 商品的请求

- (void)fetchProductInformationForIds:(NSArray *)productIds{

//产品ID可以以Plist的方式放在本地APP,也可以放在本地的服务器上。不过最好是放在本地服务器上,当后的产品有变化时,就不用升级我们的APP了

SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIds]];

request.delegate = self;

[request start];

SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIds]];

request.delegate = self;

[request start];

}

//苹果的服务器通过此方法向我们返回商品信息

#pragma mark - SKProductsRequestDelegate

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{`

if (response.products.count > 0) { //有效产品

self.availableProducts = [NSMutableArray arrayWithArray:response.products];

}

if (response.invalidProductIdentifiers.count > 0) {//无效产品标示

self.invalidProductIds = [NSMutableArray arrayWithArray:response.invalidProductIdentifiers];

}

self.status = IAPProductRequestResponse;

[[NSNotificationCenter defaultCenter] postNotificationName:IAPProductRequestNotification object:self];

}

//产品请求失败会调用此方法

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{

self.status = IAPRequestFailed;

[[NSNotificationCenter defaultCenter] postNotificationName:IAPProductRequestNotification object:self];

NSLog(@"Product Request Status: %@",error.localizedDescription);

}

//然后就是产品的展示了

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{

if (self.itemArr.count > 0) {

SKProduct *product = self.itemArr[indexPath.row];

cell.textLabel.text = product.localizedTitle;

cell.detailTextLabel.text = [NSString stringWithFormat:@"%@元",product.price];

}

}

3.2.2 商品的购买

- (void)buy:(SKProduct *)product{

SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];

// 记录购买者ID 后面取到传给服务器

NSString *userID = @"user";

payment.applicationUsername =userID;

payment.quantity = 1;//购买一次

//将商品添加到购买队列

[[SKPaymentQueue defaultQueue] addPayment:payment];

}

//当用户点击完购买,此时付款队列中有了交易,会调用下面的方法。

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{

for (SKPaymentTransaction * transaction in transactions) {

switch (transaction.transactionState) {

case SKPaymentTransactionStatePurchasing: //交易正在被添加到付款队列

NSLog(@"交易正在被添加到付款队列");

break;

case SKPaymentTransactionStateDeferred: //最终状态未确定

[self completeTransaction:transaction forStatus:IAPPurchaseFailed];

NSLog(@"最终状态未确定");

break;

case SKPaymentTransactionStatePurchased: //购买成功

NSLog(@"购买成功");

[self completeTransaction:transaction forStatus:IAPPurchaseSucceeded];

break;

case SKPaymentTransactionStateRestored: //已经购买过该商品

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];//消耗型不支持恢复

NSLog(@"已经购买过该商品");

break;

case SKPaymentTransactionStateFailed: //交易失败

NSLog(@"交易失败");

[self completeTransaction:transaction forStatus:IAPPurchaseFailed];

break;

default:

break;

}

}

}

//检查交易状态,做出相应操作

- (void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status{

self.status = status;

NSString *detail = nil;

if (transaction.error != nil) {

switch (transaction.error.code) {

case SKErrorUnknown:

NSLog(@"SKErrorUnknown");

detail = @"未知的错误,请稍后重试。";

break;

case SKErrorClientInvalid:

NSLog(@"SKErrorClientInvalid");

detail = @"当前苹果账户无法购买商品(如有疑问,可以询问苹果客服)";

break;

case SKErrorPaymentCancelled:

NSLog(@"SKErrorPaymentCancelled");

detail = @"订单已取消";

break;

case SKErrorPaymentInvalid:

NSLog(@"SKErrorPaymentInvalid");

detail = @"订单无效(如有疑问,可以询问苹果客服)";

break;

case SKErrorPaymentNotAllowed:

NSLog(@"SKErrorPaymentNotAllowed");

detail = @"当前苹果设备无法购买商品(如有疑问,可以询问苹果客服)";

break;

case SKErrorStoreProductNotAvailable:

NSLog(@"SKErrorStoreProductNotAvailable");

detail = @"当前商品不可用";

break;

default:

NSLog(@"No Match Found for error");

detail = @"未知错误";

break;

}

NSLog(@"detail == %@",transaction.error.localizedDescription);

}

if (status == IAPPurchaseSucceeded) {

/***************此处有坑,需特别注意*****************/

//由于网络问题等种种原因,即使用户已经付款成功,客户端也可能一时半会收不到苹果API的支付成功通知,也无法主动向苹果API请求查询支付状态,只能被动等待通知。

//因此有些情况下,客户端会延迟收到支付成功的通知(可能是过了几分钟,也有可能是下次打开App的时候),针对这种情况,需要做好两件事:

//1. 客户端本地保存所有支付结果未确认的交易信息,并设置一个监听进程,在收到支付成功的信息后,继续处理这笔交易的后续流程。极端情况下,用户在交易结果未确认的情况下删除App,保存在App本地数据库中的交易信息也会丢失,因此,更好的方案是把交易信息存到iOS系统的keychain里面

//2.当本地存在支付结果未确认的交易信息时,在交互上提示用户可能需要等待支付结果,避免用户重复付款

//获得交易 凭证

NSURL *receipturl = [[NSBundle mainBundle] appStoreReceiptURL];

NSData *receiptData = [NSData dataWithContentsOfURL:receipturl];

NSLog(@"receiptData == %@",receiptData);

//获取购买者标识

NSLog(@"payment.applicationUsername == %@",transaction.payment.applicationUsername);

//本次交易的唯一标识符

MyLog(@"transaction.transactionIdentifier == %@",transaction.transactionIdentifier);

//将 1、交易凭证 2、购买者标识 3、购买的产品类型(ProductID)4、本次交易的唯一标识符 保存到本地(keyChain)

// [LGJKeyChainTools setObject:receiptStr forService:@"user" account:transaction.transactionIdentifier ];

//向本地服务器发送请求 传送 1、交易凭证 2、购买者标识 3、购买的产品类型(ProductID)4、本次交易的唯一标识符,本地服务器向苹果服务器发送验证验证交易凭证请求。如果凭证有效,则发放产品,删除保存到本地的相应信息,如果无效则提示相应的错误提示,也要删除保存到本地的相应信息。

}

//无论什么状态都应该将本次交易做结束处理,否则下次购买会出现问题。

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

//发送通知

[[NSNotificationCenter defaultCenter] postNotificationName:IAPPurchaseNotification object:self];

//在客户端向服务端轮询结果时,为了避免用户在支付结果页面等待过久,交互层面上可以先结束支付流程(经过一定超时时间),同时提示用户需要等待支付结果,避免用户重复付款

}

Demo下载地址 https://github.com/234313037/MyINAppPurchasesDemo

最后 感谢 http://bbs.netease.im/read-tid-730

相关推荐

日本吉祥物是什麼?深入剖析萌力無限的在地文化符號與行銷奇蹟
【学党史·我打卡】什么是“四清”运动?
365bet网址

【学党史·我打卡】什么是“四清”运动?

📅 01-02 👁️ 2103
堂哥的老婆叫什麼?探討華人家庭中的稱謂及其文化意義
完美365体育ios下载

堂哥的老婆叫什麼?探討華人家庭中的稱謂及其文化意義

📅 07-15 👁️ 8560