In App Purchase Programming Guide
购买程序向导
Adding a Store to Your Application
向你的应用程序中添加商店
This chapter provides guided steps for adding a store to your application.
这个章节介绍了向你的应用程序中添加商店的详细步骤。
The Step-By-Step Process
When you set up the project, make sure to link toStoreKit.framework. You can then add a store by following these steps:
当安装工程时,确保链接到StoreKit.framework.,然后就可以按照下面的步骤添加一个商店到你的应用程序。
(1)Decide what products you wish to deliver with your application.
首先确定你想把哪个产品交付到你的应用程序
There are limitations on the types of features you can offer. Store Kit does not allow your application to patch itself or download additional code. Products must either work with existing code in your application or must be implemented using data files delivered from a remote server. If you wish to add a feature that requires changes to your source code, you need to ship an updated version of your application.
在你能提供的功能的类型上有一些限制。商店包(Store Kit)不允许你的应用软件为自己打补丁或者下载附加的源码。产品必须同在你的应用程序或者通过远程服务器下载的数据文件来运行。如果你想为产品加上一些特色,则需要更改你的源码,你需要为你的应用程序发送一个更新版本。
(2)Register product information for each product with iTunes Connect.
为每个和iTunes Connect关联的产品注册产品信息。
You revisit this step every time you want to add a new product to your application’s store. Every product requires a unique product identifier string. The App Store uses this string to look up product information and to process payments. Product identifiers are specific to your iTunes Connect account and are registered with iTunes Connect in a way similar to how you registered your application.
当你每一次向你的应用程序添加一个新的产品时你要重复这一步。每一个产品需要一个唯一的产品标识符字符串。App Store用这个字符串来查看产品信息和处理付款。产品标识符是特定于你的iTunes Connect帐户,并且它和iTunes Connect注册时类似于你注册你的应用程序。
The process to create and register product information is described in the iTunes Connect Developer Guide.
更新产品的过程需要生成并注册产品信息,这些信息在iTunes Connect Developer Guide.中有描述。
(3)Determine whether payments can be processed.
确定是否可以处理付款。
A user can disable the ability to make purchases inside applications. Your application should check to see whether payments can be purchased before queuing new payment requests. Your application might do this before displaying a store to the user (as shown here) or it may defer this check until the user actually attempts to purchase an item. The latter allows the user to see items that they could purchase when payments are enabled.
用户可以禁用在程序内购买这个功能。你的应用程序应该在新的付款请求排队之前确认付款是否可以购买。你的应用程序也可以在把商店显示给用户之前来确认是否可以购买或者在用户实际企图购买一个项目时确认是否可以购买,当付款被启用时后者可以使用户看见他们能购买的项目。
if ([[SKPaymentQueue defaultQueue] canMakePayments]) |
{ |
... // Display a store to the user. //显示一个商店给用户 |
} |
else |
{ |
... // Warn the user that purchases are disabled. //警告用户不能交易 |
} |
(4)Retrieve information about products.
检索有关产品的信息
Your application creates a SKProductsRequest object and initializes it with a set of product identifiers for the items you wish to sell, attaches adelegate to the request, and then starts it. The response holds the localized product information for all valid product identifiers.
你的应用程序创建了一个SKProductsRequest对象,并用你想卖的一系列项目的产品标识符来初始化这个SKProductsRequest对象。附加一个delegate到这个request,然后开始。响应信息中有所有的有效地产品标识符的本地化产品信息。
- (void) requestProductData |
{ |
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject: kMyFeatureIdentifier]]; |
request.delegate = self; |
[request start]; |
} |
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response |
{ |
NSArray *myProduct = response.products; |
// populate UI // 填充界面 |
[request autorelease]; |
} |
(5)Add a user interface that displays products to the user.
添加一个用户界面来显示产品给用户
Store Kit does not provide user interface classes. The look and feel of how you offer products to your customers is up to you!
商店包(Store Kit)不提供用户界面类。你提供的产品的界面风格由你来定制。
(6)Register a transaction observer with the payment queue.
用付费队列来注册一个交易观察者。
Your application should instantiate a transaction observer and add it as an observer of the payment queue.
你的应用程序应实例化一个交易观察者,并把它以付费队列的观察者的身份添加上去。
MyStoreObserver *observer = [[MyStoreObserver alloc] init]; |
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer]; Your application should add the observer when your application launches. The App Store remembers queued transactions even if your application exited before completing all transactions. Adding an observer during initialization ensures that all previously queued transactions are seen by your application. 你的应用程序在启动时就应添加一个交易观察者。即使你的应用程序在完成所有的交易之前就退出,App Store也能够记住放在队列中的交易。在初始化的过程中就添加一个交易观察者,就能够保证你的应用程序能看见之前没有执行完的所有放在队列中的交易。 |
(7)Implement thepaymentQueue:updatedTransactions: method on MyStoreObserver.
在MyStoreObserver上实现paymentQueue:updatedTransactions:
The observer’s paymentQueue:updatedTransactions: method is called whenever new transactions are created or updated.
观察者的paymentQueue:updatedTransactions:方法在新的交易被创建或更新时就会被调用。
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions |
{ |
for (SKPaymentTransaction *transaction in transactions) |
{ |
switch (transaction.transactionState) |
{ |
case SKPaymentTransactionStatePurchased: |
[self completeTransaction:transaction]; |
break; |
case SKPaymentTransactionStateFailed: |
[self failedTransaction:transaction]; |
break; |
case SKPaymentTransactionStateRestored: |
[self restoreTransaction:transaction]; |
default: |
break; |
} |
} |
} |
(8)Your observer provides the product when the user successfully purchases an item.
当用户成功的购买了一个项目时,你的观察者就会为你提供刚购买的产品。
- (void) completeTransaction: (SKPaymentTransaction *)transaction |
{ |
// Your application should implement these two methods. // 你的应用程序应该 实现这两个方法 |
[self recordTransaction: transaction]; |
[self provideContent: transaction.payment.productIdentifier]; |
// Remove the transaction from the payment queue. //从付费队列中删除交易 |
[[SKPaymentQueue defaultQueue] finishTransaction: transaction]; |
} |
A successful transaction includes a transactionIdentifier property and atransactionReceipt property that record the details of the processed payment. Your application is not required to do anything with this information. You may wish to record this information to establish an audit trail for the transaction. If your application uses a server to deliver content, the receipt can be sent to your server and validated by the App Store.
一个成功的交易应该包括一个transactionIdentifier属性和transactionReceipt属性,后者用于记录处理过的付费的详细细节。你的应用程序不要求对这个信息作任何操作。你可能希望通过建立一个交易的审计跟踪(audit trail)来记录这个信息。如果你的应用程序使用服务器来传输内容(content),收据(receipt)就会被发送到你的服务器,并被App Store鉴定是否有效。
It is critical that your application take whatever steps are necessary to provide the product that the user purchased. Payment has already been collected, so the user expects to receive the new purchase. See“Feature Delivery” for suggestions on how you might implement this.
你的应用程序操作一些必要步骤,而这些步骤是可以提供用户购买过的产品的,这是很重要的。付费已经被收集起来,因此用户唯一期望的就是收到新的购买。你可以通过看“Feature Delivery””来了解你如何实现这些的建议。
Once you’ve delivered the product, your application must call finishTransaction: to complete the transaction. When you callfinishTransaction:, the transaction is removed from the payment queue. To ensure that products are not lost, your application should deliver the product before callingfinishTransaction:.
一旦你已经发出了产品,那你的应用程序就必须调用finishTransaction:来完成交易。当你调用了finishTransaction:,交易就从付费队列中删除掉。为了确保产品不会丢失,你的应用程序应在调用finishTransaction:之前就把产品发出去。
(9)Finish the transaction for a restored purchase.
为一个恢复的购买请求完成交易
- (void) restoreTransaction: (SKPaymentTransaction *)transaction |
{ |
[self recordTransaction: transaction]; |
[self provideContent: transaction.originalTransaction.payment.productIdentifier]; |
[[SKPaymentQueue defaultQueue] finishTransaction: transaction]; |
} |
This routine is similar to that for a purchased item. A restored purchase provides a new transaction, including a different transaction identifier and receipt. You can save this information separately as part of any audit trail if you desire. However, when it comes time to complete the transaction, you’ll want to recover the original transaction that holds the actual payment object and use its product identifier.
这个过程同购买一个项目类似。一个被恢复的购买提供了一个新的交易,它包括一个不同的交易标识符和收据(receipt)。你也可以把这个信息单独作为跟踪审计的一部分存储起来。尽管如此,当交易完成时,你要恢复持有实际支付对象和使用其产品标识符的原始的交易。
(10)Finish the transaction for a failed purchase.
完成一个购买失败的交易。
- (void) failedTransaction: (SKPaymentTransaction *)transaction |
{ |
if (transaction.error.code != SKErrorPaymentCancelled) |
{ |
// Optionally, display an error here. |
} |
[[SKPaymentQueue defaultQueue] finishTransaction: transaction]; |
} |
Usually a transaction fails because the user decided not to purchase the item. Your application can read theerror field on a failed transaction to learn exactly why the transaction failed.
通常由于用户决定不购买这个项目而导致一个交易失败。你的应用程序可以通过读取购买失败的交易中的error字段来了解交易失败的原因。
The only requirement for a failed purchase is that your application remove it from the queue. If your application chooses to put up an dialog displaying the error to the user, you should avoid presenting an error when the user cancels a purchase.
对于购买失败的唯一要求就是你的应用程序从队列中移除它。因此如果你的程序通过一个对话框来向用户显示错误,那么你应该避免当用户取消购买时提出一个错误。
(11)With all the infrastructure in place, you can finish the user interface. When the user selects an item in the store, create a payment object and add it to the payment queue.
你可以用所有提供的基础设施(infrastructure)完成用户界面。当用户选择了商店中的一个项目时,就创建一个交易对象,并把它加到交易队列中。
SKPayment *payment = [SKPayment paymentWithProductIdentifier:kMyFeatureIdentifier]; |
[[SKPaymentQueue defaultQueue] addPayment:payment]; |
If your store offers the ability to purchase more than one of a product, you can create a single payment and set the quantity property.
如果你的商店能够让用户购买多个商品的话,你就可以仅仅创建一个交易然后设置数量(quantity)属性就可以了
SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier:kMyFeatureIdentifier]; |
payment.quantity = 3; |
[[SKPaymentQueue defaultQueue] addPayment:payment]; |
Where to Go Next
The code provided in these steps is best used for the built-in product model. If your application uses a server to deliver content, you are responsible for designing and implementing the protocols used to communicate between your iPhone application and your server. Your server should also verify receipts before delivering products to your application.
在这些过程中提供的代码能够很好的用于基于产品的模式。如果你的应用程序使用服务器发送内容,你就必须设计并实现用于在你的iPhone程序和你的服务器之间通信的协议。你的服务器也同样需要在把产品发送到你的应用程序之前对收据进行验证。
3.以下英文说明主要过程:
Create and Fetch a Product Description
- Create a unique App ID
- Generate and install a new provisioning profile
- Update the bundle ID and code signing profile in Xcode
- If you haven’t already, submit your application metadata in iTunes Connect
- If you haven’t already, submit your application binary in iTunes Connect
- Add a new product for in-app purchase
- Write code for fetching the product description //待完
- Wait a few hours
4.IAP(In App Purchase)
IAP的全称是In App Purchase,应用内付费。这种业务模式允许用户免费下载试用,对应用内提供的商品选择消费,比如购买游戏道具,购买游戏等级等等。相比完全收费的应用而言,应用内付费给用户试用的机会,不会让优秀的应用因为缺乏用户的认知而丧失消费者;而且对于开发商,也不需要为了让用户试用而单独发布一款免费的精简版本。
商品(profuct):
![](https://i-blog.csdnimg.cn/blog_migrate/151211969ebc2fb9e7584b812b495d10.jpeg)
iTunesConnect创建应用:
登陆iTunesConnect,创建一个新的应用,即使该应用尚未开发,也可以用一些假的文字和图片来代替,创建好之后切记要点击Ready to Upload binary将应用的状态变为Waiting for upload。
管理In-App Purchase商品
在应用列表中点击新创建的应用图标,进入应用首页,在右面的一行按钮中选择Manage In-App Purchase,进入内付费商品管理页面。通过点击左上角的Create New按钮可以进入商品页面选择创建一个新的商品。页面中显示的四种商品分别是我在本文介绍过的四种商品,消耗型商品(Consumable),非消耗型商品(Non-Consumable),自动重置型订阅(Auto-Renewable Subscriptions),非自动重置型订阅(Non-Renewing Subscription)。
以消耗型商品为例,点击Select进入创建页面。Reference Name是商品名字,这不是最终用户会看到的名字,而是会在内付费管理的商品列表中显示的字符,类似于变量名。
Product ID是商品的唯一标识,这个ID十分重要,在编写应用程序的时候会用它来识别改商品。
接下来是为不同的语言定义该商品的显示名称,最终用户看到的就是这个名称。定义好名称后是为商品定价以及上传缩略图,这个商品就算是定义完了。如下图,定义完成的商品会显示在内付费管理的商品列表中。每一个内付费商品的创建和修改都需要提交审核,但这里需要注意的是,在一个新的应用版本内创建的内付费商品,必须和这个应用版本一起提交审核,而在该应用版本通过审核之后再为它创建的内付费商品,可以通过这个列表中的Ready to submit按钮来提交。
![](https://i-blog.csdnimg.cn/blog_migrate/5638bc5c03c825c809dc42a2537cdfc4.png)
刚刚创建好的内付费应用,已经可以用来调试了。
使用测试帐号调试应用
苹果应用商店是一个交易环境,任何用户可以在这个环境内购买应用,但如果要测试正在开发过程中的应用内付费,我们不能在真正的苹果商店里进行。苹果给开发者提供了一个用于调试购买行为的测试沙箱,它完全复制了应用商店的交易环境,但在沙箱环境中我们不能用平常的苹果帐号,而是需要用测试帐号。(只要有一个app id,就可以添加其商品,并且进行测试。)
在iTunesConnect的首页可以点击Manage Users进入用户管理页面,然后选择Test User来创建测试帐号。根据苹果开发者的最新谢意,创建测试帐号必须使用一个真实的Email地址,而且密码必须是符合规范的,测试账号需要在邮件里激活后才可以使用。这里创建的帐号可以用来购买开发过程中的应用内付费,但必须记住,测试帐号不能用来登陆真正的应用商店并在产品环境中进行购买行为,否则你的iTunes帐号将有可能被停用。(测试账号可以通过itnunes connect来添加,账号信息随便添就行。这里要注意,这个账号只能用于我们应用的沙盒测试,不要用于正常商品的购买(比如买个已上架的应用里面的商品),否则苹果会禁用这个账号。)
当我们确认购买一个商品,我们会获取一个SKPaymentTransaction对象,里面的transactionReceipt是验证信息(就是一组json字符串),我们对其进行base64加密,然后按照苹果规定的格式(具体可以参考文档)发送到验证地址就可以了。验证成功后,app store返回的信息里面包含购买商品的具体信息,可以用于对账。
购买商品后,我们本地的交易队列中会有一个新的对象,这个交易队列是保存在本地硬盘上的,除非我们调用finishTransaction,否则交易对象不会删除。而程序开启时(这里要注意一下,下面会针对这个做详细说明)如果交易队列不为空,则ios会通知我们交易队列状态更新,我们就要根据交易对象的状态进行处理。
SKPaymentTransactionStatePurchased 交易成功,这时已经扣完钱,我们要保证将商品发送给用户
SKPaymentTransactionStateFailed 交易失败,原因很多(可以通过SKPaymentTransaction.error.code来查看具体失败原因),最常见的是SKErrorPaymentCancelled(用户取消交易),或是未输入合法的itunes id
SKPaymentTransactionStateRestored 非消耗性商品已经购买过,这时我们要按交易成功来处理。
如果交易失败,我们可以直接将交易从交易队列中移除。如果成功,则要发起验证,等待验证结果来进行处理。其结果无非三种,验证成功、验证非法、验证错误。成功和非法我们都要讲交易对象从交易队列中移除,验证错误则可能是验证服务器出现故障,我们不应该删除该交易对象,待程序重新开启后,再一次进行验证,直到成功或者失败。
============================================================================================
In App Purchase为创建产品提供了一种通用的机制,如何操作将由你负责。当你设计程序的时候,有以下几点需要注意:
1. 你必须提供电子类产品和服务。不要使用In App Purchase 去出售实物和实际服务。
2. 不能提供代表中介货币的物品,因为让用户知晓他们购买的商品和服务是很重要的。
![Store Kit Guide(In App Purchase)翻译 已完结 - Alex - 梦想千里远,分秒追逐近](http://cc.cocimg.com/bbs/attachment/Fid_6/6_15122_8352222472ba3b9.png)
使用这终方式,要提供另外的服务器将产品发送给程序。 服务器交付适用于订阅、内容类商品和服务,因为商品可以作为数据发送,而不需改动程序束。 例如,一个游戏提供的新的内容(关卡等)。 Store Kit不会对服务器端的设计和交互做出定义,这方面工作需要你来完成。 而且,Store Kit不提供验证用户身份的机制,你需要来设计。 如果你的程序需要以上功能,例如,纪录特定用户的订阅计划, 你需要自己来设计和实现。
图1-3 展示了服务器类型的购买过程。
![Store Kit Guide(In App Purchase)翻译 已完结 - Alex - 梦想千里远,分秒追逐近](http://cc.cocimg.com/bbs/attachment/Fid_6/6_15122_e33abafd4bd5f2b.png)
1. 程序向服务器发送请求,获得一份产品列表。
2. 服务器返回包含产品标识符的列表。
3. 程序向App Store发送请求,得到产品的信息。
4. App Store返回产品信息。
5. 程序把返回的产品信息显示给用户(App的store界面)
6. 用户选择某个产品
7. 程序向App Store发送支付请求
8. App Store处理支付请求并返回交易完成信息。
9. 程序从信息中获得数据,并发送至服务器。
10. 服务器纪录数据,并进行审(我们的)查。
11. 服务器将数据发给App Store来验证该交易的有效性。
12. App Store对收到的数据进行解析,返回该数据和说明其是否有效的标识。
13. 服务器读取返回的数据,确定用户购买的内容。
14. 服务器将购买的内容传递给程序。
Apple建议在服务器端存储产品标识,而不要将其存储在plist中。 这样就可以在不升级程序的前提下添加新的产品。
在服务器模式下, 你的程序将获得交易(transaction)相关的信息,并将它发送给服务器。服务器可以验证收到的数据,并将其解码以确定需要交付的内容。 这个流程将在“验证store收据”一节讨论。
对于服务器模式,我们有安全性和可靠性方面的顾虑。 你应该测试整个环境来避免威胁。《Secure Coding Guide》文档中有相关的提示说明。
虽然非消耗性商品可以用内置模式来恢复,订阅类商品必须通过服务器来恢复。你要负责纪录订阅信息、恢复数据。
消耗类商品也可以通过服务器方式来纪录。例如,由服务器提供的一项服务, 你可能需要用户在多个设备上重新获得结果。
(这段翻译的比较生硬,因为我个人也没有机会把各种类型的服务跑一遍,后续会检查并修改。希望大家一起来看看,欢迎补充。)
要在程序内部显示“商店”,需要从App Store得到信息来购建界面。 本章详细讲解如何从App Store获取产品信息。
向App Store发送请求
Store Kit提供了从App Store上请求数据的通用机制。 程序可以创建并初始化一个request对象, 为其附加delegate, 然后启动请求过程。请求将被发送到App Store,在那里被处理。 处理完成时, request对象的delegate方法将被异步调用,以获得请求的结果。 图2-1显示了请求的数据模型。
![Store Kit Guide(In App Purchase)翻译 已完结 - Alex - 梦想千里远,分秒追逐近](http://cc.cocimg.com/bbs/attachment/Fid_6/6_15122_d677b4d63d03654.png)
如果程序在请求期间退出,则需要重新发送请求。
下面讲解请求过程中用到的类:
SKRequest
SKRequest为request的抽象根类。
SKRequestDelegate
SKRequestDelegate是一个protocol, 实现用以处理请求结果的方法,比如请求成功,或请求失败。
发送获得产品信息的请求
程序使用products request来获得产品的信息。 要完成这一过程,程序需创建一个request对象,其中会包含一个产品标识的列表。之前提到过,你的程序既可以内置产品列表,又可以通过外部服务器来获得。
当发送请求时,产品标识会传送到App Store,App Store将会返回本地化信息(这些信息事先已经在iTunes Connect中设置好了),你将使用这些信息来购建内置商店的界面(显示商品名,描述,等等)。 图2-2显示了请求的过程。
![Store Kit Guide(In App Purchase)翻译 已完结 - Alex - 梦想千里远,分秒追逐近](http://cc.cocimg.com/bbs/attachment/Fid_6/6_15122_76131e6f071f739.png)
SKProductsRequest
用来请求商品的信息。 创建时,我们将需要显示的商品列表加入该对象。
SKProductsRequestDelegate
该protocol定义了处理App Store响应的方法。
SKProductsResponse
SKProductsResponse对象为App Store返回的响应信息。里面包含两个列表(当然是NSArray了):一是经过验证有效的商品,
@property(nonatomic, readonly) NSArray *products
另外一个是无法被识别的商品信息:
@property(nonatomic, readonly) NSArray * invalidProductIdentifiers
有几种原因将造成商品标识无法被识别,如拼写错误(当然),被标记为不可出售(unavailable for sale),或是对商品信息的改变没有传送到所有App Store的服务器。(这个原因不是很清楚,再议)。
SKProduct
SKProduct对象包含了在App Store上注册的商品的本地化信息。
当用户准备购买商品时,程序向App Store请求支付信息,然后App Store将会创建持久化的交易信息,并继续处理支付流程,即使用户重启程序,这个过程亦是如此。App Store同步待定交易的列表到程序中,并在交易状态发生改变时向程序发送更新的数据。
收集支付信息
要收集支付信息, 你的程序可以创建一个payment的对象,将它放到支付队列中,如图3-1所示。
![Store Kit Guide(In App Purchase)翻译 已完结 - Alex - 梦想千里远,分秒追逐近](http://cc.cocimg.com/bbs/attachment/Fid_6/6_15122_c3de50c8f88b9bc.png)
1. 一个SKPayment的对象,包含了"Sword"的商品标识,并且制定购买数量为1。
2. 使用addPayment:方法将SKPayment的对象添加到SKPaymentQueue里。
3. SKPaymentmentQueue包含的所有请求商品,
4. 使用SKPaymentTransactionObserver的paymentQueue: updatedTransactions: 方法来检测所有完成的购买,并发送购买的商品。
5. 最后,使用finishTransaction:方法完成交易。
当payment的对象被添加到支付队列中的时候, 会创建一个持久保存的transaction对象来存放它。 当支付被处理后,transaction被更新。 程序中将实现一个观察者(observer)对象来获取transaction更新的消息。 观察者应该为用户提供购买的商品,然后将transaction从队列中移除。
下面介绍在购买过程中用到的几个类:
SKPayment
要收集支付信息,先要了解一下支付对象。 支付对象包含了商品的标识(identifier)和要购买商品的数量(quantity)(数量可选)。你可以把同一个支付对象重复放入支付队列,,每一次这样的动作都相当于一次独立的支付请求。
用户可以在Settings程序中禁用购买的功能。 因此在请求支付之前,程序应该首先检查支付是否可以被处理。 调用SKPaymentQueue的canMakePayments方法来检查。
SKPaymentQueue
支付队列用以和App Store之间进行通信。 当新的支付对象被添加到队列中的时候, Store Kit向App Store发送请求。 Store Kit将会弹出对话框询问用户是否确定购买。 完成的交易将会返回给程序的observer对象。
SKPaymentTransaction
transaction对象在每次添加新的payment到队列中的时候被创建。 transaction对象包含了一些属性,可以让程序确定当前的交易状态。
程序可以从支付队列那里得到一份审核中的交易列表,但更常用的做法还是等待支付队列告知交易状态的更新。
SKPaymentTransactionObserver
在程序中实现SKPaymentTransactionObserver的协议,然后把它作为SKPaymentQueue对象的观察者。该观察者的主要职责是:检查完成的交易,交付购买的内容,和把完成后的交易对象从队列中移除。
在程序一启动,就应该为支付队列指定对应的观察者对象,而不是等到用户想要购买商品的时候。 Transaction对象在程序退出时不会丢失。程序重启时, Store Kit继续执行未完成的交易。 在程序初始化的时候添加观察者对象,可以保证所有的交易都被程序接收(也就时说,如果有未完成的transaction,如果程序重启,就重新开始了,如果稍候再添加观察者,就可能会漏掉部分交易的信息)。
恢复交易信息(Transactions)
当transaction被处理并从队列移除之后,正常情况下,程序就再也看不到它们了。 如果你的程序提供的是非消耗性的或是订阅类的商品,就必须提供restore的功能,使用户可以在其他设备上重新存储购买信息。
Store Kit提供内建的功能来重新存储非消耗商品的交易信息。 调用SKPaymentQueue的restoreCompletedTransactions的方法来重新存储。对于那些之前已经完成交易的非消耗性商品,Apple Store生成新的,用于恢复的交易信息。 它包含了原始的交易信息。你的程序可以拿到这个信息,然后继续为购买的功能解锁。 当之前所有的交易都被恢复时, 就会调用观察者对象的paymentQueueRestoreCompletedTransactionsFinished方法。
如果用户试图购买已经买过的非消耗性商品,程序会收到一个常规的交易信息,而不是恢复的交易信息。但是用户不会被再次收费。程序 应把这类交易和原始的交易同等对待。
订阅类服务和消耗类商品不会被Store Kit自动恢复。 要恢复这些商品,你必须在用户购买这些商品时,在你自己的服务器上记录这些交易信息, 并且为用户的设备提供恢复交易信息的机制。
本章为添加购买功能的指导
详细流程:
准备工作当然是添加StoreKit.framework了。
然后是具体的步骤:
1. 决定在程序内出售的商品的类型。
之前提到过,程序内可以出售的新feature类型是有限制的。 Store Kit不允许我们下载新的代码。 你的商品要么可以通过当前的代码工作(bundle类型),要么可以通过服务器下载(当然,这里下载的为数据文件,代码是不可以的)。 如果要修改源代码,就只能老实的升级了。
2. 通过iTunes Connect注册商品
每次添加新商品的时候都需要执行这一步骤。 每个商品都需要一个唯一的商品标识。 App Store通过这个标识来查找商品信息并处理支付流程。 注册商品标识的方法和注册程序的方法类似。
要了解如何创建和注册商品信息,请参考“iTunes Connect Developer Guide”文档。
3. 检测是否可以进行支付
用户可以禁用在程序内部支付的功能。在发送支付请求之前,程序应该检查该功能是否被开启。程序可在显示商店界面之前就检查该设置(没启用就不显示商店界面了),也可以在用户发送支付请求前再检查,这样用户就可以看到可购买的商品列表了。
例子:
if([SKPaymentQueue canMakePayments])
{
...//Display a store to the user
}
else
{
...//Warn the user that purchases are disabled.
}
4. 获得商品的信息
程序创建SKProductsRequest对象,用想要出售的商品的标识来初始化, 然后附加上对应的委托对象。 该请求的响应包含了可用商品的本地化信息。
//这里发送请求
- (void)requestProductData
{
SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:
[NSSet setWithObject: kMyFeatureIdentifier]];
request.delegate = self;
[request start];
}
//这个是响应的delegate方法
- (void)productsRequest: (SKProductsRequest *)request
didReceiveResponse: (SKProductsResponse *)response
{
NSArray *myProduct = response.products;
//生成商店的UI
[request autorelease];
}
5. 添加一个展示商品的界面
Store Kit不提供界面的类。 这个界面需要我们自己来设计并实现。
6. 为支付队列(payment queue)注册一个观察者对象
你的程序需要初始化一个transaction observer对象并把它指定为payment queue的观察者。
上代码:
MyStoreObserver *observer = [[MyStoreObserver alloc]init];
[[SKPaymentQueue defaultQueue]addTransactionObserver: observer];
应该在程序启动的时候就添加好观察者,原因前面说过,重启后程序会继续上次未完的交易,这时就添加观察者对象就不会漏掉之前的交易信息。
7. 在MyStoreObserver类中执行paymentQueue: updatedTransactions: 方法。
这个方法会在有新的交易被创建,或者交易被更新的时候被调用。
- (void)paymentQueue: (SKPaymentQueue *)queue updatedTransactions: (NSArray *)transactions
{
for(SKPaymentTransaction * transaction in transactions)
{
switch(transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction: transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction: transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction: transaction];
default:
break;
}
}
}
上面的函数针对不同的交易返回状态,调用对应的处理函数。
8. 观察者对象在用户成功购买一件商品时,提供相应的内容,以下是在交易成功后调用的方法
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
//你的程序需要实现这两个方法
[self recordTransaction: transaction];
[self provideContent: transaction.payment.productIdentifier];
//将完成后的交易信息移出队列
[[SKPaymentQueue defaultQueue]finishTransaction: transaction];
}
交易成功的信息包含transactionIdentifier和transactionReceipt的属性。其中,transactionReceipt记录了支付的详细信息,这个信息可以帮助你跟踪、审(我们的)查交易,如果你的程序是用服务器来交付内容,transactionReceipt可以被传送到服务器,然后通过App Store验证交易。(之前提到的server模式,可以参考以前的图)
9. 如果交易是恢复过来的(restore),我们用这个方法来处理:
- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
[self recordTransaction: transaction];
[self provideContent: transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
这个过程完成购买的过程类似。 恢复的购买内容提供一个新的交易信息,这个信息包含了新的transaction的标识和receipt数据。 如果需要的话,你可以把这些信息单独保存下来,供追溯审(我们的)查之用。但更多的情况下,在交易完成时,你可能需要覆盖原始的transaction数据,并使用其中的商品标识。
10. 交易过程失败的话,我们调用如下的方法:
- (void)failedTransaction: (SKPaymentTransaction *)transaction
{
if(transaction.error.code != SKErrorPaymentCancelled)
{
//在这类显示除用户取消之外的错误信息
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
通常情况下,交易失败的原因是取消购买商品的流程。 程序可以从error中读出交易失败的详细信息。
显示错误信息不是必须的,但在上面的处理方法中,需要将失败的交易从支付队列中移除。 一般来说,我们用一个对话框来显示错误信息,这时就应避免将用户取消购买这个error显示出来。
11. 组织好程序内“商店”的UI。当用户选择一件商品时, 创建一个支付对象,并放到队列中。
SKPayment *payment = [SKPayment paymentWithProductIdentifier: kMyFeatureIdentifier];
[[SKPaymentQueue defaultQueue] addPayment: payment];
如果你的商店支持选择同一件商品的数量,你可以设置支付对象的quantity属性
SKMutablePayment *payment = [SKMutablePayment paymentWithProductIdentifier: kMyFeatureIdentifier];
payment.quantity = 3;
[[SKPaymentQueue defaultQueue] addPayment: payment];
下一步:
本章中所示代码可用于内置型商品模式(Built-in)。 如果你的程序要使用服务器来发布商品,你需要负责设计和执行iPhone程序和你的服务器之间的通信。服务器应该验证数据并为程序提供内容。
验证store的收据
使用服务器来交付内容,我们还需要做些额外的工作来验证从Store Kit发送的收据信息。
重要信息:来自Store的收据信息的格式是专用的。 你的程序不应直接解析这类数据。可使用如下的机制来取出其中的信息。
验证App Store返回的收据信息
当交易完成时,Store Kit告知payment observer这个消息,并返回完成的transaction。 SKPaymentTransaction的transactionReceipt属性就包含了一个经过签名的收据信息,其中记录了交易的关键信息。你的服务器要负责提交收据信息来确定其有效性,并保证它未经过篡改。 这个过程中,信息被以JSON数据格式发送给App Store,App Store也以JSON的格式返回数据。
(大家可以先了解一下JSON的格式)
验证收据的过程:
1. 从transaction的transactionReceipt属性中得到收据的数据,并以base64方式编码。
2. 创建JSON对象,字典格式,单键值对,键名为"receipt-data", 值为上一步编码后的数据。效果为:
{
"receipt-data" : "(编码后的数据)"
}
3. 发送HTTP POST的请求,将数据发送到App Store,其地址为:
https://buy.itunes.apple.com/verfyReceipt
4. App Store的返回值也是一个JSON格式的对象,包含两个键值对, status和receipt:
{
"status" : 0,
"receipt" : { … }
}
如果status的值为0, 就说明该receipt为有效的。 否则就是无效的。
App Store的收据
发送给App Store的收据数据是通过对transaction中对应的信息编码而创建的。 当App Store验证收据时, 将从其中解码出数据,并以"receipt"的键返回。 返回的响应信息是JSON格式,被包含在SKPaymentTransaction的对象中(transactionReceipt属性)。Server可通过这些值来了解交易的详细信息。 Apple建议只发送receipt数据到服务器并使用receipt数据验证和获得交易详情。 因为App Store可验证收据信息,返回信息,保证信息不被篡改,这种方式比同时提交receipt和transaction的数据要安全。(这段得再看看)
表5-1为交易信息的所有键,很多的键都对应SKPaymentTransaction的属性。
备注:一些键取决于你的程序是链接到App Store还是测试用的Sandbox环境。更多关于sandbox的信息,请查看"Testing a Store"一章。
Table 5-1 购买信息的键:
键名 描述
quantity 购买商品的数量。对应SKPayment对象中的quantity属性
product_id 商品的标识,对应SKPayment对象的productIdentifier属性。
transaction_id 交易的标识,对应SKPaymentTransaction的transactionIdentifier属性
purchase_date 交易的日期,对应SKPaymentTransaction的transactionDate属性
original_-transaction_id 对于恢复的transaction对象,该键对应了原始的transaction标识
original_purchase_-date 对于恢复的transaction对象,该键对应了原始的交易日期
app_item_id App Store用来标识程序的字符串。一个服务器可能需要支持多个server的支付功能,可以用这个标识来区分程序。链接sandbox用来测试的程序的不到这个值,因此该键不存在。
version_external_-identifier 用来标识程序修订数。该键在sandbox环境下不存在
bid iPhone程序的bundle标识
bvrs iPhone程序的版本号
测试Store功能
开发过程中,我们需要测试支付功能以保证其工作正常。然而,我们不希望在测试时对用户收费。 Apple提供了sandbox的环境供我们测试。
备注:Store Kit在模拟器上无法运行。 当在模拟器上运行Store Kit的时候,访问payment queue的动作会打出一条警告的log。测试store功能必须在真机上进行。
Sandbox环境
使用Sandbox环境的话,Store Kit并没有链接到真实的App Store,而是链接到专门的Sandbox环境。 SandBox的内容和App Store一致,只是它不执行真实的支付动作。 它会返回交易成功的信息。 Sandbox使用专门的iTunes Connect测试 账户。不能使用正式的iTunes Connect账户来测试。
要测试程序,需要创建一个专门的测试账户。你至少需要为程序的每个区域创建至少一个测试账户。详细信息,请查看iTunes Connect Developer Guide文档。
在Sandbox环境中测试
步骤:
1. 在测试的iPhone上退出iTunes账户
Settings中可能会记录之前登录的账户,进入并退出。
重要信息:不能在Settings 程序中通过测试账户登录。
2. 运行程序
当你在程序的store中购买商品后,Store kit提示你去验证交易。用测试账户登录,并批准支付。 这样虚拟的交易就完成了。
在Sandbox中验证收据
验证的URL不同了:
NSURL *sandboxStoreURL = [[NSURL alloc]initWithString:
@"https://sandbox.itunes.apple.com/verifyReceipt"];