接上一篇笔记
沃尔玛卖一支牙膏的流程是: 1.把商家的牙膏放到货柜上 2. 让用户自由选择 3.用户去收银台刷信用卡 4. 刷卡器交给用户,等待银行确认刷卡信息,如果返回付款确认信息,让用户拿走牙膏。
内置付费已经走完了前面的三步,用户要一手交钱了,我们也要准备一手交货喽。(只收钱不办事儿在App Store是行不通的,写软件易,建国家难,且写且珍惜。)
这一步的流程图如下:
处理支付信息 (Processes payment)
再回到沃尔玛购买牙膏的场景,当刷信用卡的时候,整个操作流程大体如下:
对于银联的直联商户,流程如下:
1、刷卡信息(包括磁道和密码)由POS机具受理后通过收单机构送往银联的收单系统。
2、银联收单系统将报文通过银联核心交换平台送到信用卡的发卡银行,根据交易指令,在发卡银行的对应的卡片账户进行扣款。
3、银联核心交换系统收到扣款成功的返回后,将交易结果原路返回到POS终端上。
4、当天晚上11点,清算信息开始批量处理。
5、T+1日,各行在人行的头寸账户根据银联的清算文件(指令)将资金进行划拨,即交易资金从信用卡的发卡银行转移到商户的收单银行。
6、收单银行将资金转入商户的具体清算账户(也可以由银联直接转入)。
就扮演的角色而言,有持卡人、商户、收单机构(为商户提供服务的银行或机构)、转接清算机构(银联、VISA等卡组织)、发卡机构(信用卡银行)
(以上答案为知乎网友周宇的解答,链接在此)
在内置付费购买环节中,App Store在此处也扮演了银联收单系统的角色,App Store会把扣款成功的信息返回给“售货员”, 这里的“售货员”是我们的一段代码,名字叫做transaction queue observer。这个“售货员”放在哪里有程序员自己来决定,大体上有两个地方比较好:
-
对于非常小型的App, 可以放在 app delegate中
2.对大部分的Apps, 单独弄一个类,和其它与Store有关的代码放在一起就很不错
这个名叫observer的"售货员"必须要"签署"SKPaymentTransactionObserver协议才能完成工作。- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
{
/ 放一个“售货员” */
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
签署了"SKPaymentTransactionObserver协议的“售货员”必须遵从协议中的要求——执行paymentQueue:updatedTransactions:这个函数。工作的职责是: 当交易状态(The Status of a Transaction)有任何的变化, 都要调用这个操作。操作的具体细节需要我们来完成。
交易的四种主要状态以及采取相应的行动:
- SKPaymentTransactionStatePurchasing: 购买中,此时可更新UI来展现购买的过程
SKPaymentTransactionStateFailed: 购买错误,此时要根据错误的代码给用户相应的提示
SKPaymentTransactionStatePurchased: 购买成功,此时要提供给用户相应的内容
-
SKPaymentTransactionStateRestored: 恢复已购产品,此时需要将已经购买的商品恢复给用户
(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
// Call the appropriate custom method.
case SKPaymentTransactionStatePurchased: // 购买成功
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed: // 购买失败
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: // 恢复已购
[self restoreTransaction:transaction];
default:
break;
}
}
}(void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSString * productIdentifier = transaction.payment.productIdentifier;
NSString * receipt = [transaction.transactionReceipt base64EncodedString];
if ([productIdentifier length] > 0) {
// 向自己的服务器验证购买凭证
}
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}(void)failedTransaction:(SKPaymentTransaction *)transaction {
if(transaction.error.code != SKErrorPaymentCancelled) {
NSLog(@"购买失败");
} else {
NSLog(@"用户取消交易");
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}(void)restoreTransaction:(SKPaymentTransaction *)transaction {
// 恢复已经购买的产品
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
保存好购物凭证(Persisting the Purchase)
现实中,购物以后要给个发票或者购物小票。在这里,也需要这么做,永久存储交易记录。这样做至少有两个用处:
- 程序启动以后,检查购买记录,让已购的功能生效。
- 当用户需要恢复已购功能的时候, 可以读取这个记录。
保存购物凭证的方法有如下几种:
- 对于非消耗(non-consumable) 品, 并且iOS 7以上,可以使用app receipt来记录
- 对于非消耗(non-consumable)品,但是是iOS7以下,可以使用User Defaults system 或者 iCloud来记录
- 对于消耗品(consumable), 因为不能在不同设备上同步,因此不需要做永久记录(有种强拆的感觉啊!)
将Value/Key保存在User Defaults 或者 iCloud中
#if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif
[storage setBool:YES forKey:@"enable_rocket_car"];
[storage setObject:@15 forKey:@"highest_unlocked_level"];
[storage synchronize];
将Receipt保存在User Defaults 或者 iCloud中
#if USE_ICLOUD_STORAGE
NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
#else
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
#endif
NSData *newReceipt = transaction.transactionReceipt;
NSArray *savedReceipts = [storage arrayForKey:@"receipts"];
if (!receipts) {
// Storing the first receipt
[storage setObject:@[newReceipt] forKey:@"receipts"];
} else {
// Adding another receipt
NSArray *updatedReceipts = [savedReceipts arrayByAddingObject:newReceipt];
[storage setObject:updatedReceipts forKey:@"receipts"];
}
[storage synchronize];
解锁功能 Unlocking App Functionality
当用户购买成功以后,就需要对相应的产品功能进行解锁, 当使用Receipt的时候,代码应该类似于下面的样子
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
// Custom method to work with receipts
BOOL rocketCarEnabled = [self receipt:receiptData includesProductID:@"com.example.rocketCar"];
当使用Key:Value来存储的时候, 代码应该类似于下面的样子:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"];
在程序中写下如下相应的代码,判断是否可以使用高级一点的功能 :)
if (rocketCarEnabled) {
// Use the rocket car.
} else {
// Use the regular car.
}
解锁资源Delivering Associated Content
如果购买是有关资源的,比如更多的声音,更多的图片,更多的素材等等,可以有三种方式来处理这种情况:
- (Local Content) 内置一些热门资源(预期会大卖的资源),不要太大,顶多几M左右即可。
- (Apple-hosted Content) 使用Apple提供的Apple-hosted服务,这样可以保证App的尺寸较为精简。支持iOS 6以上。
- 使用自己的服务器。
结束交易 Finishing the Transaction
这里没什么好讲的,就是结束交易了。
SKPaymentTransaction *transaction = <# The current payment #>;
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
需要注意的一点是,在交易结束之前,不要调用这个函数,会让Apple-hosted Content没法下载,因为在下载Apple-hosted内容之前,返回的transaction有一个SKDownload属性,如果贸然调用了此函数,有可能会导致下载中断,以及潜在的其它问题。