iOS In-App Purchase
In-App Purchase
前言
Apple 规定,发布的 App 中包含非实物类型的、功能类型的收费项目时,必须使用 IAP 方式来让用户支付,而不能使用三方支付如支付宝、微信支付。
面向用户的付费服务应当是所有用户都可以付费购买的,而不应该设置只支持部分用户购买。
在我们 App 中,用户可以免费申请开通会员,会员有期限,并且产品提出会员可申请条件是该用户付费 >= x万。Apple 审核人员不予通过该会员申请方式,所以在此增加通过订阅型内购产品来满足此需求。
由于 App 内购其他事宜配置不由本人控制,所以此篇不包含财务、协议、银行等相关内容。
前期准备
请先阅读 App 内购买项目配置流程
商品
在 iTunes Connect 后台可以配置内购支持的商品,对应的操作步骤可能需要一些必要的用户职能。对应的商品分为四类:
- 消耗型项目
只可使用一次的产品,使用之后即失效,必须再次购买。
示例:钓鱼 App 中的鱼食。 - 非消耗型项目
只需购买一次,不会过期或随着使用而减少的产品。
示例:游戏 App 的赛道。 - 自动续期订阅
允许用户在固定时间段内购买动态内容的产品。除非用户选择取消,否则此类订阅会自动续期。
示例:每月订阅提供流媒体服务的 App。 - 非续期订阅
允许用户购买有时限性服务的产品。此 App 内购买项目的内容可以是静态的。此类订阅不会自动续期。
示例:为期一年的已归档文章目录订阅。
首先要分清楚自己 App 中的产品需要的是哪类商品,可同时存在多类商品 。自动续费订阅型内购可以分组创建商品。
商品的商品 ID 唯一,不可重复,也不可使用删除过的商品 ID 。
购买测试
测试包以及 TestFlight 包可以通过配置沙箱技术技术员账号,使用测试账号进行测试。
账号在 iTunes Connect -> 用户和访问 -> 沙箱技术 中进行配置,配置的用户名和邮箱不必使用真实邮箱,不需要验证码之类的验证步骤。
自定续费订阅的商品在测试时有对应时限,可参考上文提到的内购配置文章中的内容。
基本流程
购买结果支持本地校验以及服务器校验,通常服务器校验更为安全可靠一些。
本地校验
- 读取存放于 bundle 中注册好的商品 ID 组合
- 通过商品 ID 向 App Store 获取对应商品
- App Store 返回商品
- 展示给用户
- 用户选择某个商品
- 向 App Store 发起支付请求
- App Store 完成交易
- 获取交易凭证,向用户发放商品
服务器校验
- 向服务器请求商品 ID
- 服务器返回
- 通过商品 ID 向 App Store 获取对应商品
- App Store 返回商品
- 展示给用户
- 用户选择某个商品
- 向 App Store 发起支付请求
- App Store 完成交易
- 获取交易凭证,将凭证发送至服务器
- 服务器向 App Store 确认凭证
- App Store 返回是否凭证有效
- 服务器拿到结果,通知客户端,并且下发商品
- 客户端展示给用户
StoreKit
内购开发大概涉及到以下类的调用:
SKProductsRequest
负责内购商品请求,创建带有商品 ID 的请求,并且发送,以代理来接受请求的结束、成功、失败。SKProductsRequestDelegate
商品请求的代理:
1 | // required |
SKProductsResponse
AppStore 的响应数据,里面包含两个列表(NSArray):
1、经过验证有效的商品,products
2、无法被识别的商品信息:invalidProductIdentifiers
商品的 ID 拼写错误、商品标记为不可售,或是商品还没有通过 App Store 审核,都会造成商品无法被识别。SKProduct
包含了在App Store上注册的商品的本地化信息。通常我们不去SKPayment
发起支付时,新建一个SKPayment
对象,传入对应的商品SKProduct
,之后将payment
添加至SKPaymentQueue
这样一个支付队列中。SKPaymentQueue
SKPaymentmentQueue
包含所有的请求商品,该队列用以和 App Store 之间进行通信。 当新的支付对象被添加到队列中的时候, Store Kit 向 App Store 发送请求。 Store Kit 将会弹出对话框询问用户是否确定购买。SKPaymentTransaction
添加至支付队列的支付请求都会被一个持久保存的SKPaymentTransaction
对象来存放它,每当支付的状态更改时,对应的transaction
被更新。更新通知到SKPaymentTransactionObserver
对应的监听者。交易状态需要手动结束,否则只要有observer
存在,每次启动 App 后都会收到状态更新。SKPaymentTransactionObserver
通过创建观察者SKPaymentTransactionObserver
来获取更新的信息,该观察者的主要职责是:检查完成的交易,交付购买的内容,和把完成后的交易对象从队列中移除。
观察者应当在程序一启动时就指定,因为每次程序重启,没有结束的交易都会被继续执行(如还没有收到成功或者失败回调的交易),如果不是在程序刚启动时就指定好观察者,某些交易状态可能被丢失。
开发实现
交易准备
- StoreKit.framework
首先肯定要引入 StoreKit.framework。
- 获取商品 ID
商品 ID 一般存于后台而非本地,以便随时获取最新的商品列表。一般在应用启动之后就会先请求商品,填充好所有的商品信息模型。
- 检测是否可以支付
用户可以禁用在程序内部支付的功能。在发送支付请求之前,程序应该检查该功能是否被开启。在 StoreKit 中有方法可以检测:
1 | if([SKPaymentQueue canMakePayments]) { |
- 获取商品信息
通过SKProductsRequest
对象来向 App Store 请求商品,传入商品 ID,并且通过代理方法取得成功、失败、完成的相关回调。
发起请求
1 | - (void)fetchProductInformationForIds:(NSArray *)productIds { |
代理接收
1 | - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { |
1 | - (void)handleProduct { |
- 展示商品
需要自己展示内购商品列表,通过 AppStore 给到的商品信息分别自定义展示。
- 添加 Observer
交易状态的变换完全由observer
来通知到程序,实现SKPaymentTransactionObserver
的对应方法来接受接收状态:
1 | [[SKPaymentQueue defaultQueue] addTransactionObserver:[StoreObserver sharedInstance]]; |
required 方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for(SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
break;
// The purchase was successful
case SKPaymentTransactionStatePurchased: {
// Check whether the purchased product has content hosted with Apple.
if(transaction.downloads && transaction.downloads.count > 0) {
[self completeTransaction:transaction forStatus:IAPDownloadStarted];
} else {
[self completeTransaction:transaction forStatus:IAPPurchaseSucceeded];
}
}
break;
// There are restored products
case SKPaymentTransactionStateRestored: {
// Send a IAPDownloadStarted notification if it has
if(transaction.downloads && transaction.downloads.count > 0) {
[self completeTransaction:transaction forStatus:IAPDownloadStarted];
} else {
[self completeTransaction:transaction forStatus:IAPRestoredSucceeded];
}
}
break;
// The transaction failed
case SKPaymentTransactionStateFailed: {
[self completeTransaction:transaction forStatus:IAPPurchaseFailed];
}
break;
default:
break;
}
}
}
- 处理交易逻辑
根据不同的状态处理交易
1 | - (void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(NSInteger)status { |
产生购买
- 用户点击购买
当用户点击某个商品时,程序应该生成一个 payment,并且添加至支付队列中:
1 | - (void)buy:(SKProduct *)product { |
- 获取收据
如果用户正常购买,客户端可以拿到 Apple 的支付凭证,这个凭证是否有效需要自己判断,有上文提到的本地校验和服务器校验,通常我们选择服务器校验,某些越狱手机可以仿造支付凭证,本地校验存在风险。
1 | - (void)getPaymentVoucher { |
- 服务器校验
1 | - (void)postReceiptData:(NSData *)data transid:(NSString *)transid transAction:(SKPaymentTransaction *)transAction { |
- 完成交易
完成交易需要手动调用,否则重启 App 都会收到交易状态:
1 | [[SKPaymentQueue defaultQueue] finishTransaction:transAction]; |