iOS内置付费开发笔记四(解锁产品)

上一篇笔记

沃尔玛卖一支牙膏的流程是: 1.把商家的牙膏放到货柜上 2. 让用户自由选择 3.用户去收银台刷信用卡 4. 刷卡器交给用户,等待银行确认刷卡信息,如果返回付款确认信息,让用户拿走牙膏。

内置付费已经走完了前面的三步,用户要一手交钱了,我们也要准备一手交货喽。(只收钱不办事儿在App Store是行不通的,写软件易,建国家难,且写且珍惜。)

这一步的流程图如下:


Delivering Products
Delivering Products

处理支付信息 (Processes payment)

再回到沃尔玛购买牙膏的场景,当刷信用卡的时候,整个操作流程大体如下:

对于银联的直联商户,流程如下:
1、刷卡信息(包括磁道和密码)由POS机具受理后通过收单机构送往银联的收单系统。
2、银联收单系统将报文通过银联核心交换平台送到信用卡的发卡银行,根据交易指令,在发卡银行的对应的卡片账户进行扣款。
3、银联核心交换系统收到扣款成功的返回后,将交易结果原路返回到POS终端上。
4、当天晚上11点,清算信息开始批量处理。
5、T+1日,各行在人行的头寸账户根据银联的清算文件(指令)将资金进行划拨,即交易资金从信用卡的发卡银行转移到商户的收单银行。
6、收单银行将资金转入商户的具体清算账户(也可以由银联直接转入)。
就扮演的角色而言,有持卡人、商户、收单机构(为商户提供服务的银行或机构)、转接清算机构(银联、VISA等卡组织)、发卡机构(信用卡银行)
(以上答案为知乎网友周宇的解答,链接在此)

在内置付费购买环节中,App Store在此处也扮演了银联收单系统的角色,App Store会把扣款成功的信息返回给“售货员”, 这里的“售货员”是我们的一段代码,名字叫做transaction queue observer。这个“售货员”放在哪里有程序员自己来决定,大体上有两个地方比较好:

  1. 对于非常小型的App, 可以放在 app delegate中
    2.对大部分的Apps, 单独弄一个类,和其它与Store有关的代码放在一起就很不错
    这个名叫observer的"售货员"必须要"签署"SKPaymentTransactionObserver协议才能完成工作。

    • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
      {
      /
      放一个“售货员” */
      [[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
      }

签署了"SKPaymentTransactionObserver协议的“售货员”必须遵从协议中的要求——执行paymentQueue:updatedTransactions:这个函数。工作的职责是: 当交易状态(The Status of a Transaction)有任何的变化, 都要调用这个操作。操作的具体细节需要我们来完成。

交易的四种主要状态以及采取相应的行动:

  1. SKPaymentTransactionStatePurchasing: 购买中,此时可更新UI来展现购买的过程
  1. SKPaymentTransactionStateFailed: 购买错误,此时要根据错误的代码给用户相应的提示

  2. SKPaymentTransactionStatePurchased: 购买成功,此时要提供给用户相应的内容

  3. 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)

现实中,购物以后要给个发票或者购物小票。在这里,也需要这么做,永久存储交易记录。这样做至少有两个用处:

  • 程序启动以后,检查购买记录,让已购的功能生效。
  • 当用户需要恢复已购功能的时候, 可以读取这个记录。
    保存购物凭证的方法有如下几种:
  1. 对于非消耗(non-consumable) 品, 并且iOS 7以上,可以使用app receipt来记录
  2. 对于非消耗(non-consumable)品,但是是iOS7以下,可以使用User Defaults system 或者 iCloud来记录
  3. 对于消耗品(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

如果购买是有关资源的,比如更多的声音,更多的图片,更多的素材等等,可以有三种方式来处理这种情况:

  1. (Local Content) 内置一些热门资源(预期会大卖的资源),不要太大,顶多几M左右即可。
  2. (Apple-hosted Content) 使用Apple提供的Apple-hosted服务,这样可以保证App的尺寸较为精简。支持iOS 6以上。
  3. 使用自己的服务器。

结束交易 Finishing the Transaction

这里没什么好讲的,就是结束交易了。

SKPaymentTransaction *transaction = <# The current payment #>;
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

需要注意的一点是,在交易结束之前,不要调用这个函数,会让Apple-hosted Content没法下载,因为在下载Apple-hosted内容之前,返回的transaction有一个SKDownload属性,如果贸然调用了此函数,有可能会导致下载中断,以及潜在的其它问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,814评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,124评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,814评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,924评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,815评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,562评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,944评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,582评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,859评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,881评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,700评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,493评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,943评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,115评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,413评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,978评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,182评论 2 339

推荐阅读更多精彩内容