向App Store请求支付(Requesting a Payment from the App Store)
详细讲图中高亮的由APP与App Store完成的第二阶段
创建支付请求
当用户选择一个要购买的商品时,使用对应的SKProduct对象创建支付请求并且有需要的话设置数量,如下所示。商品数组中的商品对象由APP的商品请求返回的,在Fetching Product Information from the App Store中所提到的。
SKProduct *product = <# Product returned by a products request. #>;
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];payment.quantity = 2;
提交支付请求
通过将请求加到支付队列中来向App Store提交支付请求。如果向队列中添加支付对象超过一次,会向App Store提交多次,每次都会让用户支付并请求你的APP下发商品。
[[SKPaymentQueue defaultQueue] addPayment:payment];
对于你的APP提交的每个请求,会接收到对应的交易来处理。更多关于交易和支付队列的信息,查看Processing a Transaction。
对于自动订阅,你可能提交一个支付请求,并为您确定有资格接收offe的用户提供订阅要约。更多对的信息,查看Implementing Promotional Offers in Your App。
处理交易
注册一个交易队列观察者来接受并且处理来自App Store的交易的更新。
主要解释在最后阶段你的APP和App Store执行的步骤。
App Store在处理交易请求之后通知transaction queueobserver。然后你的APP记录购买的相关信息,以便后来的启动,下载购买的内容,将交易标记为结束。
监视队列中的交易
交易队列扮演了一个重要的角色,使你的APP与App Store通过storeKit框架交流。你将工作添加到App Store需要按照它执行的队列中,例如一个要处理的支付请求。当交易的状态改变时,例如一个支付交易成功了,StoreKit通知app的交易队列观察者。你决定哪个类扮演观察者,在非常小型的apps中,你可以在APPdelegate中处理storekit的逻辑,包括观察交易队列。在大多数APP中,创建一个单独处理观察逻辑和其他的app商店逻辑的单独的类。观察者必须符合SKPayment
通过增加观察者,您的应用程序不需要不断轮询其活动事务的状态。您的APP为支付请求使用交易队列,来下载苹果存储的内容,并发现订阅已经自动更新了。
通常在APP启动后立即注册交易队列观察者,如下所示,更多指导信息,查看Setting Up the Transaction Observer for the Payment Queue。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
/* ... */
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
}
如果APP把一个交易标记为关闭失败了,StoreKit在每次APP启动之后都通知观察者直到交易关闭。
Table 1 订单状态和对应的动作
Status
Action to take in your app
[`Transaction
State
Purchasing`](developer.apple.com/documentati…)
Update your UI to reflect the in-progress status, and wait to be called again.
更新UI来表示进行中的状态,等待再次调用
[`Transaction
State
Deferred`](developer.apple.com/documentati…)
Update your UI to reflect the deferred status, and wait to be called again.
更新UI表示推迟的状态,等待再次调用
[`Transaction
State
Failed`](developer.apple.com/documentati…)
Use the value of the error property to present a message to the user. For a list of error constants, see SKError
Domain.
使用error属性展示给用户一个信息,关于error常量的列表,查看SKError Domain
[`Transaction
State
Purchased`](developer.apple.com/documentati…)
Provide the purchased functionality, typically by unlocking features or delivering content.
提供购买的功能,通常为解锁功能或者下发内容
[`Transaction
State
Restored`](developer.apple.com/documentati…)
Restore the previously purchased functionality.
恢复之前购买的功能
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{ for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { // Call the appropriate custom method for the transaction state. case SKPaymentTransactionStatePurchasing: [self showTransactionAsInProgress:transaction deferred:NO]; break; case SKPaymentTransactionStateDeferred: [self showTransactionAsInProgress:transaction deferred:YES]; break; case SKPaymentTransactionStateFailed: [self failedTransaction:transaction]; break; case SKPaymentTransactionStatePurchased: [self completeTransaction:transaction]; break; case SKPaymentTransactionStateRestored: [self restoreTransaction:transaction]; break; default: NSLog(@"Unexpected transaction state %@", @(transaction.transactionState)); break; } }}
更新app的ui来表示交易状态
当等待的时候保持APP用户界面是最新的,交易队列观察者可以实现SKPaymentTransactionObserver协议中可选的方法:
- paymentQueue:removedTransactions:方法会在StoreKit将交易从队列中移除的时候调用。在APP中,从UI中移除对应的内容。
- paymentQueueRestoreCompletedTransactionsFinished:和paymentQueue:restoreCompletedTransactionsFailedWithError:两方法会在恢复交易结束后调用,取决于是否会有错误。
对于成功的交易操作,APP应该验证与交易关联的凭证来确认用户购买的项目并且解锁对应的内容。对于服务端验证凭证更多的信息,查看Validating Receipts with the App Store。
持久化购买
APP持久化策略取决于商品的类型
- 非消耗品和自动订阅,使用APP的凭证。如果APP凭证获取不到,使用用户系统或者iCloud来保留一个持久的记录
- 对于非自动续费订阅,使用iCloud或者自己的服务器来保持持久的记录
- 对于消耗品,APP更新内部状态来表示购买。确保更新状态是支持状态保存的对象的一部分(ios)或者你通过APP启动来手动保存状态(ios和Macos)
当你使用用户默认系统或者iCloud时,APP可以保存一个值,如数字、布尔值或者交易凭证的拷贝。在macOS中,用户可以通过默认命令编辑用户默认系统。存储一个凭证需要更多地程序逻辑,但是防止保存记录被破坏。
当通过iCloud保存时,APP持久记录是通过设备同步的,但是你的APP需要负责在其他设备上下载相关的内容。
使用APP凭证持久化购买
APP凭证包括一个用户购买的记录,由apple签名加密的。对于更多的信息,查看Choosing a Receipt Validation Technique。
在他们支付时与消耗品相关的信息会被添加到凭证中,保留在凭证中直到关闭交易。在结束交易后,这个信息会在凭证下次更新的时候移除,例如用户下次购买时。
其他种类购买的信息会在商品支付时添加到凭证中,并且永久的保留在凭证中。
在User Defaults or iCloud中持久化一个值
#if USE_ICLOUD_STORAGENSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];#elseNSUserDefaults *storage = [NSUserDefaults standardUserDefaults];#endif[storage setBool:YES forKey:@"enable_rocket_car"];[storage setObject:@15 forKey:@"highest_unlocked_level"];
使用自己的服务器持久化购买
与证书或者标识一起将凭证发送给服务器,所以你可以追踪属于特定用户的凭证。例如,让用户用用户名和密码来在服务器标识自己。不要使用UIDevice的identifierForVendor。不同的设备有不同的值,所以不能用它来标记恢复不同设备上同一用户的购买。
解锁购买的内容
一旦购买完成,你要负责确定你通过编码使用户可以获取到内容。
确定购买内容
对于一个使APP有某种功能的内购商品,像高价的订阅,设置一个布尔值使代码路径可行并且如果有需要的话更新用户界面。考虑在你的APP中产生的交易的持久化记录来决定解锁的功能。你的APP必须更新这个布尔值无论用户何时完成交易和APP启动时。关于生成持久记录的信息,查看Persisting a Purchase。
例如,使用APP凭证,代码可能看起来像下面这样:
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];// Custom method to work with receiptsBOOL rocketCarEnabled = [self receipt:receiptData includesProductID:@"com.example.rocketCar"];
或者使用用户默认系统:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"];
在定义了布尔值变量后,使用购买信息来使APP中合适的代码路径可行:
if (rocketCarEnabled) {
// Use the rocket car.
} else {
// Use the regular car.
}
下发相关内容
你的APP必须下发所有与购买商品相关的内容给用户。例如,在一个音乐APP中购买了乐器,需要下发所需的声音文件使用户可以使用这个乐器。你可以将内容内嵌在APP的包中或者如果有需要的话下载。每种方式有自己的优点和缺点。
小文件内嵌在APP中,特别是如果你预料大多数用户购买的产品。在用户购买之后可以APP包中的内容立即被使用。不过,添加或者更新APP包中的内容,你必须提交一个新版本的APP。
只有在需要的时候下载大文件。从APP包中分离的内容使你的APP的初始下载变小,例如,一个游戏APP在他的APP包中包含第一阶段然后让用户在购买之后下载其他的等级。假设你的APP从服务器上获取商品标识列表,并且信息不是硬编码在APP包中的,你不需要重新提交APP来增加或者更新APP下载的内容。
加载本地内容
NSURL *url = [[NSBundle mainBundle] URLForResource:@"rocketCar" withExtension:@"plist"];[self loadVehicleAtURL:url];
从apple的服务器下载保存的内容
大多数APP使用苹果保存的内容来下载文件。会用xcode中内购内容对象创建一个苹果保存的内容包,并且提交到苹果商店connect。
如果你需要支持老版本的iOS或者跨平台共享你的服务器设施,你可能要选择使用自己的服务器设备保存内容。
当用户购买了有苹果保存内容的商品后,传递给交易队列观察者的交易包含了一个SKDownload对象,是你可以下载相关的信息。
要下载内容,将交易的downloads属性中的下载对象通过startDownlads:方法加到交易队列中。如果downloads属性是nil,表示这个交易没有苹果保存的内容。不想下载APP,下载内容不会自动为一个确定的尺寸大的内容请求WiFi连接。避免用户没有明确的动作来使用蜂窝网络来下载大体积的文件。
可以使用On-Demand Resources(ODR)来在APP中下载数据。ODR是一个苹果保存服务,一旦通过APP凭证验证了用户的购买你可以用来为用户存储内购数据。对比SKDownload的优势是ODR不需要你调用恢复交易和要求用户认证苹果保存的下载内容
实现交易队列观察则的方法来响应下载状态的改变,例如在UI中更新进度。如果失败了,使用errror属性的信息把错误展示给用户。
确保可以优雅的处理错误。例如,如果设备在下载期间耗尽了存储空间,给用户选项放弃已下载的部分或者过会儿空间充足的时候恢复下载。
在关闭交易之前下载全部苹果保存的内容。在交易完成之后,他的下载对象变得不可用。
在iOS,你的APP可以管理下载的文件。StoreKit框架将这些文件保存在Caches文件夹中,备份的标记没有设置。在下载完成之后,你APP需要负责将这些文件移动到合适的位置。对于那些在设备磁盘空间用尽的时候可以删除的文件留在Caches文件夹中。不然,将文件移动到Documents文件夹并且将标签设置为从用户备份中排除。
NSError *error;BOOL success = [URL setResourceValue:[NSNumber numberWithBool:YES]
forKey:NSURLIsExcludedFromBackupKey
error:&error];
if (!success) { /* Handle error... */ }
从自己的服务器下载内容
和其他的你APP和服务器之间交流一样,下载的细节和过程你自己负责。通讯至少组合了下面的步骤:
- APP发送凭证给服务器并且请求内容
- 服务器验证凭证来确定已经购买过的内容
- 假设凭证有效,服务器返回给你的APP内容
关闭交易
结束交易来完成购买流程
关闭交易告知StoreKit购买需要的所有事情完成了。未完成的交易保存在队列中直到他们完成,所以你应该在你的APP启动的每次都添加交易队列观察者。你的APP必须要关闭交易,不管他是成功还是失败
关闭交易前做好下列事情:
-
持久化购买
-
下载相关的内容
-
更新APP的UI,用户可以获取商品
SKPaymentTransaction *transaction = <# The current transaction #>;[ [SKPaymentQueue defaultQueue] finishTransaction:transaction];
在交易真正完成之前不要关闭就交易并且尝试使用其他的机制来追踪未完成的交易。StoreKit不是为这种方式使用来设计的,这样做会让你的APP下载不了苹果保存的内容,并可能引起其他问题。