通过App Store注册产品
每个你想要出售的产品都必须先通过iTunes Connect在App Store注册。你需提供产品的名称,描述,价格和其他在程序中用到的元数据。
需为产品指定唯一的标识符。当你的程序利用Store Kit和App Store通信时,会使用产品标识来取回产品的信息。如果用户购买某个商品时,程序可以用该标识来将产品标注为“已购买”。
App Store将前面提到过的产品种类简化为以下三种:
1. 消耗性商品。 该类商品在需要时被单次购买。比如,单次服务。
2. 非消耗性商品。 该类商品只需被某个用户购买一次,一旦被购买,和该用户iTunes 账户关联的设备都可以使用此商品。Store Kit为在多个设备上重新存储非消耗性商品提供了内置的支持。
3. 自动更新型订阅服务。提供内容的方式和非消耗性商品类似。但是,还是有一些区别。在iTunes Connect上创建自动更新型订阅服务的时候,需要选择订阅周期,这样,每次过期的时候,App Store会自动更新订阅服务。如果用户选择不更新订阅,则其订阅权限会被撤销。你的程序要负责验证当前的订阅是否可用,并获取新的交易收据。
4. 非自动更新型订阅服务。这是创建周期性产品的一种老的方式。可以考虑一下是否需要换成自动更新型订阅。它们之间的主要不同在于:
1) 订阅的条款并不在iTunes Connect中声明。你的程序需要负责提供这些信息给用户。通常,你需要将订阅条款加在产品描述中。
2)非自动更新型订阅可以被购买多次(和消费型商品类似),并且不能被App Store自动更新。你需要在app中自己实现更新机制。具体说来,你的app要能知道哪些订阅过期了,并且提示用户去再次购买。
3)购买过订阅服务的用户还可能使用其他设备,你必须保证将订阅内容发送给这些设备。非自动更新型订阅服务不会通过Store Kit被同步。你必须自己来实现这个功能。例如,大多数订阅服务使用server来提供,你的server需要实现标识用户和相关订阅服务的功能。
3. 订阅类。订阅类商品拥有以上两种类型的特性。和消耗性商品一样,订阅类商品可以被多次购买; 你可以在程序内部加入自己的订阅计划更新机制。 另外,订阅类商品必须提供给和某一用户关联的所有设备。In App Purchase期望订阅类商品可以通过外部服务器交付。你必须为多个设备的订阅服务提供相应的支持。
关于注册产品的详细信息,请参考iTunes Connect Developer Guide文档。
交付方式
交付机制在程序In App Purchase的设计和实现种有很重要的意义。有两种基本的模型可以用来交付产品:内置类型(Built-in model)和服务器类型(Server model)。 不管使用那种模型,你都需要维护产品列表,并保证当用户购买后,成功的交付产品。
1. 内置产品类型
使用这种模型。 需要交付的产品已经在程序内部。 这种方式通常用在一些被锁定的功能上。 也可以用来交付在程序束(App Bundle)中的内容。 该方式的一个重要的优点是你可以及时的给客户交付产品,大多数的内置产品应为非消耗性商品。
注意:In App Purchase不提供购买补丁的功能。 如果需要更改app的bundle,你必须向App Store提交新的app版本。
为了标识产品,程序要在bundle中存储产品的标识符。内置模式下,Apple建议使用plist来纪录产品的标识符。 内容类应用可以使用折衷方式很方便的添加新的内容,而不改动程序本身。(原话为: Content-driven applications can use this to add new content without modifying the source for your application,不是很懂,感觉应该是说类似是用plist来管理产品列表,因此就不需要在添加新产品的时候改动程序了。再议。。。)
当成功购买产品后,程序应将锁定的功能解锁,提供给用户。 解锁的最简单方式是修改程序偏好设置(Application Preferences)。 当用户备份手机数据的时候,程序偏好设置也会随之备份。 程序可能需要建议用户在购买产品后备份手机以免丢失购买的内容。
图1-2显示了交付内置型产品的流程。
1. 程序通过bundle存储的plist文件得到产品标识符的列表。
2. 程序向App Store发送请求,得到产品的信息。
3. App Store返回产品信息。
4. 程序把返回的产品信息显示给用户(App的store界面)
5. 用户选择某个产品
6. 程序向App Store发送支付请求
7. App Store处理支付请求并返回交易完成信息。
8. App获取信息并提供内容给用户。
下班再继续吧。
[ 此帖被leon在2011-12-13 15:36重新编辑 ]
在程序中添加Store功能
本章为添加购买功能的指导
详细流程:
准备工作当然是添加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/verifyReceipt4. 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"];
自动更新的订阅服务
In-App Purchase提供了自动更新型订阅服务的标准方式。自动更新型订阅有如下新的显著特征:
1. 当你在iTunes Connect中配置自动更新型订阅服务时,需要同时指定更新周期和其他的促销选项。
2. 自动更新型订阅服务会被自动恢复(使用Store Kit中恢复非消费型商品一样的函数)。原始的交易信息会和更新的交易信息一起发送给你的程序。详情请查看“Restoring Transactions”一节。
3. 当你的服务器向App Store验证收据(receipt),订阅服务被激活并更新时,App store会向你的app返回更新后的收据信息。
为你的商店添加自动更新型订阅服务
按以下步骤来实现自动更新型订阅服务:
1. 连接iTunes Connect网站,并创建一个共享密钥。共享密钥是一个密码,你的服务器在验证自动更新型订阅服务的时候必须提供这个密码。共享密钥为App Store的交易增加了一层保护。(详情,请参考iTunes Connect Developer Guide文档)
2. 在iTunes Connect中创建并配置新的自动更新型订阅服务商品。
3. 修改服务器端关于验证收据部分的代码,添加共享密钥到验证信息用的JSON数据中。服务器的验证代码需要可以解析App store的返回数据以判断订阅是否过期。如果订阅服务已经被用户更新,最新的收据也会返回给你的server。
设计iOS客户端
大多数情况下,iOS客户端程序应做出最小新改来支持自动更新型订阅服务。事实上,客户端程序需要做的更简单,你可以使用非消费型(non-consumable)商品的流程来做自动更新型订阅服务的事情。你的程序在不同时期会收到单独的交易信息来告知订阅已被更新。程序应该单独验证每一条收据。
验证自动更新型订阅服务的收据
验证自动更形型订阅服务的收据和之前讲到的“验证收据”的方式一致。你的程序创建一个JSON对象并把它发送给App Store。自动更新型订阅服务的JSON对象必须包含另外的参数——就是你在iTunes Connect中创建的共享密钥。
{
"receipt-data" : "(actual receipt bytes here)"
"password" : "(shared secret bytes here)"
}
返回内容包含了状态信息,用来标识收据是否验证有效。
{
"status" : 0,
"receipt" : { ... }
"latest_receipt" : "(base-64 encoded receipt)"
"latest_receipt_info" : { ... }
}
如果用户的收据是有效的,订阅被激活,则status的值为0。receipt对应的值为解码后的收据信息。如果你的服务器收到了非零值的状态码,对照表7-1查看:
表7-1 自动更新型订阅服务返回状态码
状态码 描述
21000 App Store无法读取你提供的JSON数据
21002 收据数据不符合格式
21003 收据无法被验*****br />21004 你提供的共享密钥和账户的共享密钥不一致
21005 收据服务器当前不可用
21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
21007 收据信息是测试用(sandbox),但却被发送到产品环境中验*****br />21008 收据信息是产品环境中使用,但却被发送到测试环境中验*****br />注意:在这里的非零状态码只是针对自动更新型订阅服务,不能将这些状态码用在测试其他类型产品的返回值中。
JSON数据中的receipt栏位包含了解析过的收据信息。自动更新型订阅服务包含了一些新加的信息。请参考表7-2:
表7-2 自动更新型订阅服务的信息:
键名 描述
expires_date 订阅的过期时间,显示方式是从Jan 1, 1970, 00:00:00 GMT计算到过期时间的毫秒数。这个键不包含在恢复的交易信息中。
original_transaction_id 初次购买的交易标识。所有订阅的更新和恢复交易都共享这个标识
original_purchase_date 初次购买(订阅)的日期。
purchase_date 交易的日期。对于更新订阅的交易来说,这个日期表示更新日期。如果从App Store解析的数据是最新的订阅收据,这个值表示最近更新订阅的日期。
除了receipt-Data信息外,返回内容还可能包含另外两个信息。如果用户的订阅服务被激活并更新。则latest_receipt信息会被以base-64方式编码并包含在返回内容中。解码后的新的收据信息也会在latest_expired_receipt_info包含。你的服务器可以使用新的收据来维护最新更新订阅的信息。