iOS內置付費開發筆記

>>>  技術話題—商業文明的嶄新時代  >>> 簡體     傳統

內置付費到底有多流行? 我截取了2014年5月12日的榜單大約70%左右的App都使用了內置付費的功能。本文向您介紹iOS開發內置付費流程。

一、概述

內置付費到底有多流行?
我截取了2014年5月12日的榜單,紅色為包含內置付費的App,藍色是不包含內置付費的app。只需要看看顏色,滿眼一片紅。大約70%左右的App都使用了內置付費的功能。 
內置付費的工作流程
其實和所有的購物 并無二致,以沃爾瑪商城為例:
1. 沃爾瑪商城賣什么?
2. 把貨物運放到貨架上供客戶挑選。
3. 沃爾瑪收款。
 
App Store上也是如此:
1. Apple 允許賣什么 ?
2. 軟件上架任由用戶挑選。
3. Apple收款。
 
還有一點非常相似,沃爾瑪需要收商家的進店費,上架費,店慶費... Apple則收取30%的分成。
 
Apple允許賣什么類型的內置付費產品?
四類:
1. Non-Consumable: 比如游戲中的關卡, 購買以后隨意玩任意多次; 比如美國的房子,70年后還是你的。
2. Consumable: 比如游戲中的金幣, 消耗品;比如汽油, 消耗品。用光了就得去再去買。
3. Auto-Renewable Subscriptions: 暫略下不表。
4. Free Subscriptions: 暫略下不表。
 
注冊商品信息(Create iTunes Connect record)
在沃爾瑪賣東西,必須先要登記賣的是西瓜,還是芝麻。在Apple也是如此,先要去登記一下。
 
注冊的具體位置見下圖:
 
Apple的開發文檔在此
 
非常重要但不再詳細展開的內容如下:
1. 填寫稅務,銀行等信息 (詳見蘋果官方文檔)。
2. 創建測試用戶 (詳見蘋果官方文檔)。
 
二、獲取產品信息
 
編程步驟
購物的第一步是讓用戶看到商品,裝潢門面比什么都重要!顯示商品要盡量迅速(App Store的查詢速度普遍要2-3秒,要不要建立自己的服務器?),不能顯示缺貨的商品(看到心儀的產品,商家告訴你缺貨, 多么痛的領悟!),Store UI要人性,價格顯示要到位...
 
Apple官網給出的流程圖如下:
 
第一步 在工程中引入storekit.framework并且在文件中

  1. #import <StoreKit/StoreKit.h> 
 
第二步 product identifier存在哪里?
* 可以保存在app bundle中。
* 也可以保存在自己的服務器上。
 
以下是Apple給出的存儲在本地的一個例子(product_ids.plist)。

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" 
  3. "http://www.apple.com/DTDs/PropertyList-1.0.dtd"
  4. <plist version="1.0"
  5. <array> 
  6.   <string>com.example.level1</string> 
  7.   <string>com.example.level2</string> 
  8.   <string>com.example.rocket_car</string> 
  9. </array> 
  10. </plist> 
 
如果使用自己的服務器, 可以傳輸JSON格式的文件, Apple同樣給出了相關的例子

  1.   "com.example.level1"
  2.   "com.example.level2"
  3.   "com.example.rocket_car" 
 
第三步 讀取本地product_ids.plist文件

  1. NSURL *url = [[NSBundle mainBundle] URLForResource:@"product_ids" withExtension:@"plist"]; 
  2. NSArray *productIdentifiers = [NSArray arrayWithContentsOfURL:url]; 
 
第四步 根據Product IDs從App Store獲取產品的信息

  1. - (void)validateProductIdentifiers:(NSArray *)productIdentifiers{ 
  2.     SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIdentifiers]]; 
  3.     productsRequest.delegate = self; 
  4.     [productsRequest start]; 
  5.   
  6. // SKProductsRequestDelegate protocol method 
  7. - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ 
  8.     self.products = response.products; 
  9.     for (NSString *invalidIdentifier in response.invalidProductIdentifiers) { 
  10.     // Handle any invalid product identifiers. 處理有效的ProductIdentifiers, 缺貨的,錯誤的不能有! 
  11.     } 
  12.     [self displayStoreUI]; // Custom method 顯示Store的UI 
 
第五步 顯示購買界面UI
5.1 判斷用戶是否關閉了內置付費,如果關閉了,就提示一下

  1. if ([SKPaymentQueue canMakePayments]) { 
  2.    [self displayStoreUI]; // Custom method 
  3. else { 
  4.     NSLog(@"用戶禁止應用內付費購買."); 
 
5.2 購買界面UI上價格要顯示的清楚明了

  1. NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 
  2. [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; 
  3. [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; 
  4. [numberFormatter setLocale:product.priceLocale]; 
  5. NSString *formattedPrice = [numberFormatter stringFromNumber:product.price]; 
 
三、請求付款 Requesting Payment
當用戶點擊購買按鈕以后,會向App Store發起一個購買操作。下圖是Apple文檔中的示意圖(圖中橙色部分):
 
第一步: 創建Payment Request

  1. SKProduct *product = <# Product returned by a products request #>;  
  2. SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; 
  3. payment.quantity = 2; //數量 
 
第二步: 提交Payment Request

  1. [[SKPaymentQueue defaultQueue] addPayment:payment]; 
 
四、解鎖產品
 
沃爾瑪賣一支牙膏的流程是:
1.把商家的牙膏放到貨柜上
2. 讓用戶自由選擇
3.用戶去收銀臺刷信用卡
4. 刷卡器交給用戶,等待銀行確認刷卡信息,如果返回付款確認信息,讓用戶拿走牙膏。
 
內置付費已經走完了前面的三步,用戶要一手交錢了,我們也要準備一手交貨嘍。(只收錢不辦事兒在App Store是行不通的,寫軟件易,建國家難,且寫且珍惜。)
 
這一步的流程圖如下:
 
處理支付信息 (Processes payment)
再回到沃爾瑪購買牙膏的場景,當刷信用卡的時候,整個操作流程大體如下: 
注:以上答案為知乎網友周宇的解答。
 
在內置付費購買環節中,App Store在此處也扮演了銀聯收單系統的角色,App Store會把扣款成功的信息返回給“售貨員”, 這里的“售貨員”是我們的一段代碼,名字叫做transaction queue observer。這個“售貨員”放在哪里有程序員自己來決定,大體上有兩個地方比較好:
1. 對于非常小型的App, 可以放在 app delegate中
2.對大部分的Apps, 單獨弄一個類,和其它與Store有關的代碼放在一起就很不錯。
 
這個名叫observer的"售貨員"必須要"簽署SKPaymentTransactionObserver協議才能完成工作。

  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
  2.     /* 放一個“售貨員” */ 
  3.     [[SKPaymentQueue defaultQueue] addTransactionObserver:observer]; 
 
簽署了SKPaymentTransactionObserver協議的“售貨員”必須遵從協議中的要求——執行paymentQueue:updatedTransactions: 這個函數。工作的職責是: 當交易狀態(The Status of a Transaction)有任何的變化, 都要調用這個操作。操作的具體細節需要我們來完成。
 
交易的四種主要狀態以及采取相應的行動:
1. SKPaymentTransactionStatePurchasing: 購買中,此時可更新UI來展現購買的過程。
2. SKPaymentTransactionStateFailed: 購買錯誤,此時要根據錯誤的代碼給用戶相應的提示。
3. SKPaymentTransactionStatePurchased: 購買成功,此時要提供給用戶相應的內容。
4. SKPaymentTransactionStateRestored: 恢復已購產品,此時需要將已經購買的商品恢復給用戶。
 

  1. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions 
  2.     for (SKPaymentTransaction *transaction in transactions) { 
  3.         switch (transaction.transactionState) { 
  4.             // Call the appropriate custom method. 
  5.             case SKPaymentTransactionStatePurchased: // 購買成功 
  6.                 [self completeTransaction:transaction]; 
  7.                 break
  8.             case SKPaymentTransactionStateFailed: // 購買失敗 
  9.                 [self failedTransaction:transaction]; 
  10.                 break
  11.             case SKPaymentTransactionStateRestored: // 恢復已購 
  12.                 [self restoreTransaction:transaction]; 
  13.             default
  14.                 break
  15.         } 
  16.     } 
  17. - (void)completeTransaction:(SKPaymentTransaction *)transaction 
  18.     NSString * productIdentifier = transaction.payment.productIdentifier; 
  19.     NSString * receipt = [transaction.transactionReceipt base64EncodedString]; 
  20.     if ([productIdentifier length] > 0) { 
  21.     // 向自己的服務器驗證購買憑證 
  22.     } 
  23.     // Remove the transaction from the payment queue. 
  24.     [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
  25.   
  26. - (void)failedTransaction:(SKPaymentTransaction *)transaction { 
  27.      if(transaction.error.code != SKErrorPaymentCancelled) { 
  28.         NSLog(@"購買失敗"); 
  29.      } else { 
  30.       NSLog(@"用戶取消交易"); 
  31.      } 
  32.      [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
  33.   
  34. - (void)restoreTransaction:(SKPaymentTransaction *)transaction { 
  35.    // 恢復已經購買的產品 
  36.    [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
 
保存好購物憑證(Persisting the Purchase)
現實中,購物以后要給個發票或者購物小票。在這里,也需要這么做,永久存儲交易記錄。這樣做至少有兩個用處:
1. 程序啟動以后,檢查購買記錄,讓已購的功能生效。
2. 當用戶需要恢復已購功能的時候, 可以讀取這個記錄。
 
保存購物憑證的方法有如下幾種:
1. 對于非消耗(non-consumable) 品, 并且iOS 7以上,可以使用app receipt來記錄
2. 對于非消耗(non-consumable)品,但是是iOS7以下,可以使用User Defaults system 或者 iCloud來記錄
3. 對于消耗品(consumable), 因為不能在不同設備上同步,因此不需要做永久記錄(有種強拆的感覺啊!)
 
將Value/Key保存在User Defaults 或者 iCloud中

  1. #if USE_ICLOUD_STORAGE 
  2. NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore]; 
  3. #else 
  4. NSUserDefaults *storage = [NSUserDefaults standardUserDefaults]; 
  5. #endif 
  6.   
  7. [storage setBool:YES forKey:@"enable_rocket_car"]; 
  8. [storage setObject:@15 forKey:@"highest_unlocked_level"]; 
  9. [storage synchronize]; 
  10.   
  11. 將Receipt保存在User Defaults 或者 iCloud中 
  12. #if USE_ICLOUD_STORAGE 
  13. NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore]; 
  14. #else 
  15. NSUserDefaults *storage = [NSUserDefaults standardUserDefaults]; 
  16. #endif 
  17.   
  18. NSData *newReceipt = transaction.transactionReceipt; 
  19. NSArray *savedReceipts = [storage arrayForKey:@"receipts"]; 
  20. if (!receipts) { 
  21.     // Storing the first receipt 
  22.     [storage setObject:@[newReceipt] forKey:@"receipts"]; 
  23. else { 
  24.     // Adding another receipt 
  25.     NSArray *updatedReceipts = [savedReceipts arrayByAddingObject:newReceipt]; 
  26.     [storage setObject:updatedReceipts forKey:@"receipts"]; 
  27. [storage synchronize]; 
 
解鎖功能 Unlocking App Functionality
當用戶購買成功以后,就需要對相應的產品功能進行解鎖, 當使用Receipt的時候,代碼應該類似于下面的樣子:

  1. NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; 
  2. NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; 
  3. // Custom method to work with receipts 
  4.   
  5. BOOL rocketCarEnabled = [self receipt:receiptData includesProductID:@"com.example.rocketCar"]; 
 
當使用Key:Value來存儲的時候, 代碼應該類似于下面的樣子:

  1. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 
  2. BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"]; 
 
在程序中寫下如下相應的代碼,判斷是否可以使用高級一點的功能:

  1. if (rocketCarEnabled) { 
  2.     // Use the rocket car. 
  3. else { 
  4.     // 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
這里沒什么好講的,就是結束交易了。

  1. SKPaymentTransaction *transaction = <# The current payment #>; 
  2. [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; 
 
需要注意的一點是,在交易結束之前,不要調用這個函數,會讓Apple-hosted Content沒法下載,因為在下載Apple-hosted內容之前,返回的transaction有一個SKDownload屬性,如果貿然調用了此函數,有可能會導致下載中斷,以及潛在的其它問題。
 
來源:簡書(作者:超級馬里奧

網載 2014-07-14 15:53:17

[新一篇] 在程序員的眼里,用戶是這樣使用他們開發的軟件的

[舊一篇] 【特別推薦】12款最佳的網站速度和性能測試工具
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表