內置付費到底有多流行? 我截取了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也是如此,先要去登記一下。
注冊的具體位置見下圖:
非常重要但不再詳細展開的內容如下:
1. 填寫稅務,銀行等信息 (詳見蘋果
官方文檔)。
二、獲取產品信息
編程步驟
購物的第一步是讓用戶看到商品,裝潢門面比什么都重要!顯示商品要盡量迅速(App Store的查詢速度普遍要2-3秒,要不要建立自己的服務器?),不能顯示缺貨的商品(看到心儀的產品,商家告訴你缺貨, 多么痛的領悟!),Store UI要人性,價格顯示要到位...
Apple官網給出的流程圖如下:
第一步 在工程中引入storekit.framework并且在文件中
-
#import <StoreKit/StoreKit.h>
第二步 product identifier存在哪里?
* 可以保存在app bundle中。
* 也可以保存在自己的服務器上。
以下是Apple給出的存儲在本地的一個例子(product_ids.plist)。
-
<?xml version="1.0" encoding="UTF-8"?>
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
-
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-
<plist version="1.0">
-
<array>
-
<string>com.example.level1</string>
-
<string>com.example.level2</string>
-
<string>com.example.rocket_car</string>
-
</array>
-
</plist>
如果使用自己的服務器, 可以傳輸JSON格式的文件, Apple同樣給出了相關的例子
-
[
-
"com.example.level1",
-
"com.example.level2",
-
"com.example.rocket_car"
-
]
第三步 讀取本地product_ids.plist文件
-
NSURL *url = [[NSBundle mainBundle] URLForResource:@"product_ids" withExtension:@"plist"];
-
NSArray *productIdentifiers = [NSArray arrayWithContentsOfURL:url];
第四步 根據Product IDs從App Store獲取產品的信息
-
- (void)validateProductIdentifiers:(NSArray *)productIdentifiers{
-
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIdentifiers]];
-
productsRequest.delegate = self;
-
[productsRequest start];
-
}
-
-
-
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
-
self.products = response.products;
-
for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
-
-
}
-
[self displayStoreUI];
-
}
第五步 顯示購買界面UI
5.1 判斷用戶是否關閉了內置付費,如果關閉了,就提示一下
-
if ([SKPaymentQueue canMakePayments]) {
-
[self displayStoreUI];
-
} else {
-
NSLog(@"用戶禁止應用內付費購買.");
-
}
5.2 購買界面UI上價格要顯示的清楚明了
-
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
-
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
-
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
-
[numberFormatter setLocale:product.priceLocale];
-
NSString *formattedPrice = [numberFormatter stringFromNumber:product.price];
三、請求付款 Requesting Payment
當用戶點擊購買按鈕以后,會向App Store發起一個購買操作。下圖是Apple文檔中的示意圖(圖中橙色部分):
第一步: 創建Payment Request
-
SKProduct *product = <# Product returned by a products request #>;
-
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
-
payment.quantity = 2;
第二步: 提交Payment Request
-
[[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有關的代碼放在一起就很不錯。
-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
-
{
-
-
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
-
}
交易的四種主要狀態以及采取相應的行動:
1. SKPaymentTransactionStatePurchasing: 購買中,此時可更新UI來展現購買的過程。
2. SKPaymentTransactionStateFailed: 購買錯誤,此時要根據錯誤的代碼給用戶相應的提示。
3. SKPaymentTransactionStatePurchased: 購買成功,此時要提供給用戶相應的內容。
4. SKPaymentTransactionStateRestored: 恢復已購產品,此時需要將已經購買的商品恢復給用戶。
-
- (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;
-
}
-
}
-
}
-
- (void)completeTransaction:(SKPaymentTransaction *)transaction
-
{
-
NSString * productIdentifier = transaction.payment.productIdentifier;
-
NSString * receipt = [transaction.transactionReceipt base64EncodedString];
-
if ([productIdentifier length] > 0) {
-
-
}
-
-
[[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. 程序啟動以后,檢查購買記錄,讓已購的功能生效。
2. 當用戶需要恢復已購功能的時候, 可以讀取這個記錄。
保存購物憑證的方法有如下幾種:
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) {
-
-
[storage setObject:@[newReceipt] forKey:@"receipts"];
-
} else {
-
-
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];
-
-
-
BOOL rocketCarEnabled = [self receipt:receiptData includesProductID:@"com.example.rocketCar"];
當使用Key:Value來存儲的時候, 代碼應該類似于下面的樣子:
-
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
-
BOOL rocketCarEnabled = [defaults boolForKey:@"enable_rocket_car"];
在程序中寫下如下相應的代碼,判斷是否可以使用高級一點的功能:
-
if (rocketCarEnabled) {
-
-
} else {
-
-
}
解鎖資源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屬性,如果貿然調用了此函數,有可能會導致下載中斷,以及潛在的其它問題。
網載 2014-07-14 15:53:17