第一印象覺得In-App Purchase(簡稱IAP)非常簡單。Apple提供的大量文檔應(yīng)該讓開發(fā)者很快熟悉地熟悉。那么,為什么在你的應(yīng)用中集成IAP特性就如此令人生厭呢?
?這是因為在開發(fā)過程中不可避免會出現(xiàn)一些錯誤。而但這些錯誤發(fā)生的時候,你就抓瞎了。雖然Apple提供了有關(guān)IAP的大量文檔,但他們并未提及集成IAP的詳細(xì)步驟。而且對StoreKit集成過程中出現(xiàn)的問題也沒有一個核對清單。另外對于為什么諸如產(chǎn)品ID非法之類的問題也沒有提供NSError之類的對象來告訴你原因。
在試用了各種可能的解決方案后,你只能身心疲憊,彷徨無助。
為了提高你的效率和減少你的痛苦,我覺定利用此文來介紹一下實現(xiàn)IAP的詳細(xì)步驟。本文很詳細(xì),有點長。甚至可能太長了,但不像Apple的文檔,它提供了為實現(xiàn)IAP的每一個步驟。
廢話少說,我們直入主題吧。
概況
IAP能正常工作的秘訣:分成兩個步驟:
創(chuàng)建及提取產(chǎn)品描述購買產(chǎn)?品 第一個步驟是你可能遇到問題的部分。一旦你在代碼中成功地獲取了產(chǎn)品描述,編寫購買產(chǎn)品的代碼不過是小菜一碟。
我們先看看步驟1。
創(chuàng)建及提取產(chǎn)品描述
下面是有關(guān)創(chuàng)建產(chǎn)品及提取其描述的非常粗略的步驟:
創(chuàng)建唯一的App ID生成及安裝新的provisioning profile文件在Xcode中更新 bundle ID 及 code signing profile如果還沒做的話,請在iTunes Connect中提交有關(guān)你程序的 metadata如果還沒做的話,請在iTunes Connect中提交你程序的二進(jìn)制碼為IAP添加新產(chǎn)品編寫提取產(chǎn)品描述的代碼等待幾小時 提取產(chǎn)品描述的代碼非常簡單,但其他步驟則很容易錯。
注意: 為提取產(chǎn)品描述,你并不需要在iTunes Connect中創(chuàng)建IAP測試用戶。
1. 創(chuàng)建唯一的App ID
為支持IAP,你的App ID不能包括通配符(“*”)。為確定你的App Id是否包括通配符,請登錄http://developer.apple.com/iphone,在 iPhone Developer Program Portal中選擇左邊菜單中的 “App IDs”檢查你的 App ID。
下面是一個唯一的App ID:
7DW89RZKLY.com.runmonster.runmonsterfree
下面不是一個唯一的 App ID:
7DW89RZKLY.com.runmonster.*?
如果你還沒有一個唯一的App ID,按如下步驟創(chuàng)建一個:
在developer portal中的 App IDs 部分,選擇“New App ID”填寫下列信息: - Display name(顯示名): 選取一個不同的App ID的名稱。你不能編輯或刪除舊的App ID,所以你必須為你的App ID提供一個新名稱以避免混淆。
- Prefix(前綴): 生成一個新的前綴,或者如果你的程序是通過Keychain Services API分享數(shù)據(jù)的系列程序中之一的話,則選用已存在的前綴。
- Suffix(后綴): com.companyname.appname (這是通用格式 – 注意沒有使用通配符)。
按 “Save”按 App ID旁的“Configure” 鏈接選取 “Enable In App Purchase”選擇框按“Done” 2. 創(chuàng)建一個新的Provisioning Profile文件
在創(chuàng)建了新的App ID后,你需要生成一個指向這個App ID的新provisioning profile。
下面就是令人痛苦的生成和安裝新provisioning profile的詳細(xì)步驟:
在 iPhone Developer Portal中, 選擇左邊的Provisioning部分確保你處于Development 標(biāo)簽下, 按下右上角的 “New Profile”填入所需信息并指向你剛創(chuàng)建的唯一的App ID如果你在Actions條目下看到 “Pending”,那么請按下“Development”標(biāo)簽標(biāo)題進(jìn)行刷新點擊 “Download” 下載新的profile文件將profile文件拖入到Dock中Xcode圖標(biāo)上進(jìn)行安裝如果你想在硬盤上保存provisioning profile,那么你可以按如下步驟手工安裝profile: 在Xcode中, 選擇 Window > Organizer選擇左邊 “Provisioning Profiles” 分類Ctrl-按下profile > Reveal in Finder將新profile拖入到 profile Finder 窗口 3. 更新Xcode 設(shè)置
在Xcode中安裝了 profile 文件后,你需要對使用此provisiong profile的項目進(jìn)行一些編輯工作:
編輯項目 .plist 文件使其 Bundle ID 與 App ID 匹配。忽略ID開始部分的字母數(shù)字序列。例如,在Developer Portal中你的App ID為“7DW89RZKLY.com.runmonster.runmonsterfree”,那么在Bundle ID中你只需輸入“com.runmonster.runmonsterfree” 。編輯項目的 target 信息以使用新的provisioning profile: 選取 Project > Edit Active Target選取頂部“Build” 標(biāo)簽選取需要的 configuration (通常為 Debug)在Code Signing Identity中選擇新的provisioning profile在Code Signing Identity之下的行中(可能名為 Any iPhone OS Device)選擇新的provisioning profile 4. 添加你的應(yīng)用程序
如果你的程序已經(jīng)發(fā)表到App Store了,那么可以略過此步驟。
在你將產(chǎn)品添加到 iTunes Connect之前,你必須添加此產(chǎn)品所需的程序。如果你的程序還沒有100%完成也無需擔(dān)心,你可以先提交具有部分?jǐn)?shù)據(jù)的程序,最后再提交真實的程序。
注意: 只有 SKU 和 version(版本)部分是以后不可修改的
登錄到 http://developer.apple.com/iphone點擊右邊鏈接進(jìn)入 iTunes Connect - 注意:你必須先登錄到developer.apple.com,否則會有不測發(fā)生(譯者注:具體是什么不測我也不太清楚,膽大的請自己試一下)
在 iTunes Connect主頁點擊 “Manage Your Applications”在右上角點擊“Create New Application”填寫程序所需的一切信息。當(dāng)要求程序二進(jìn)制碼時,請選擇稍后上傳選項。 5. 提交程序二進(jìn)制碼
Apple的文檔中沒有任何地方提及詳情,但它卻是必須的步驟。要成功測IAP功能,你必須提交程序的二進(jìn)制碼。即使你的程序還沒有100%完成,你仍然需要提交二進(jìn)制碼。然而,你也可以立即擯棄你的二進(jìn)制碼,使其不會進(jìn)入審核階段。
下面這些步驟非常關(guān)鍵,我可是因為少做了某些步驟而度過了一段非常痛苦的時間:
生成App Store發(fā)布版程序 - 如果你不知怎么做,請在 iPhone Developer Portal 中點擊左方的 Distribution標(biāo)簽,并選擇 “Prepare App” 標(biāo)簽。然后,根據(jù)藍(lán)色鏈接的指示:
- 獲取iPhone發(fā)行許可證
- 創(chuàng)建并下載在App Store發(fā)行所需的iPhone Distribution Provisioning Profile
- 在Xcode中生成程序的發(fā)行版
在iTunes Connect中進(jìn)入程序頁選擇 “Upload Binary”上傳.zip壓縮程序如果你的程序還沒有100%完成以進(jìn)行審核,那么請點擊iTunes Connect中你程序首頁中的 “Reject Binary”鏈接。程序的狀態(tài)應(yīng)該更新為 “Developer Rejected”. 不用擔(dān)心,由于程序的狀態(tài)是“Developer Rejected”,Apple是不會對其進(jìn)行審核的。你可以在任何時候提交程序的新版本并使其狀態(tài)為“Developer Rejected”,這不會對以后程序正式提交的等待時間有任何影響。
6. 添加產(chǎn)品
完成了以上所有步驟后,我們最終可以向iTunes Connect中添加產(chǎn)品了。
確保登錄到 http://developer.apple.com/iphone進(jìn)入 iTunes Connect 主頁點擊 “Manage Your in App Purchases” 鏈接點擊 “Create New”選擇你的程序填寫下列產(chǎn)品信息: - Reference Name(參考名稱): 產(chǎn)品的通用名稱。比如,我使用的是 “Pro Upgrade”。此名稱是不允許進(jìn)行編輯的,它不會顯示于App Store中。
- Product ID(產(chǎn)品ID): 你產(chǎn)品的唯一id。通常格式是 com.company.appname.product,但它可以說任何形式。它并不要求以程序的App ID作為前綴。
- Type(類型): 有三種選擇
- Non-consumable(非消耗品): 僅需付費一次 (例如你希望將出現(xiàn)從免費版升級為專業(yè)版)
- Consumable(消耗品): 每次下載都需要付費
- Subscription(預(yù)訂): 循環(huán)反復(fù)
- Price Tier(價格等級): 產(chǎn)品價格。參見不同等級的價格列表。
- Cleared for Sale(等待銷售): 一定要選取此項,否則的話,測試時會發(fā)生非法產(chǎn)品ID的錯誤。
- Language to Add(增加的語言): 選一項。下列兩項將出現(xiàn):
- Displayed Name(顯示名稱): 用戶看到的產(chǎn)品名稱。比如我選擇 “Upgrade to Pro”。
- Description(描述): 對產(chǎn)品進(jìn)行描述。此處輸入的文本將與Displayed Name 及 Price 一起在你代碼中提取 SKProduct時出現(xiàn)。
- Screenshot(截屏): 展示你產(chǎn)品的截屏。盡管屏幕上會顯示“提交截屏?xí)|發(fā)產(chǎn)品審核過程”之類的文字(個人拙見,這是非常糟糕的設(shè)計),你還是可以安全地提交截屏而不會使產(chǎn)品進(jìn)入審核過程。存儲后,選擇“Submit with app binary” (隨程序二進(jìn)制碼一起提交)選項。是產(chǎn)品與程序二進(jìn)制綁定在一起,所以在你最后正式提交100%完成的程序二進(jìn)制碼時,產(chǎn)品也會隨之提交。
點擊 “Save”? 7. 編寫代碼
下面我們開始編寫代碼對剛加入到iTunes Connect中的產(chǎn)品信息進(jìn)行提取。我訪問產(chǎn)品數(shù)據(jù),我們需要使用 StoreKit framework。
注意: StoreKit 無法在模擬器上工作。你必須在真機(jī)上進(jìn)行測試。
1.添加 StoreKit framework 到你的項目中。2.添加SKProduct引用到你的 .h 文件中: ??#import?<StoreKit/StoreKit.h> ??#define?kInAppPurchaseManagerProductsFetchedNotification?@"kInAppPurchaseManagerProductsFetchedNotification" ??@interface?InAppPurchaseManager?:?NSObject?<SKProductsRequestDelegate> ?{ ?????SKProduct?*proUpgradeProduct; ?????SKProductsRequest?*productsRequest; ?}? 注意: InAppPurchaseManager 是一個單例類,它處理程序中所有IAP任務(wù)。它是本文中的示例程序。
3.產(chǎn)品請求,并在相應(yīng).m文件中實現(xiàn)代理協(xié)議: ??-?(void)requestProUpgradeProductData ?{ ?????NSSet?*productIdentifiers?=?[NSSet?setWithObject:@"com.runmonster.runmonsterfree.upgradetopro"?]; ?????productsRequest?=?[[SKProductsRequest?alloc]?initWithProductIdentifiers:productIdentifiers]; ?????productsRequest.delegate?=?self; ?????[productsRequest?start]; ????? ??????} ??#pragma?mark?- ?#pragma?mark?SKProductsRequestDelegate?methods ??-?(void)productsRequest:(SKProductsRequest?*)request?didReceiveResponse:(SKProductsResponse?*)response ?{ ?????NSArray?*products?=?response.products; ?????proUpgradeProduct?=?[products?count]?==?1???[[products?firstObject]?retain]?:?nil; ?????if?(proUpgradeProduct) ?????{ ?????????NSLog(@"Product?title:?%@"?,?proUpgradeProduct.localizedTitle); ?????????NSLog(@"Product?description:?%@"?,?proUpgradeProduct.localizedDescription); ?????????NSLog(@"Product?price:?%@"?,?proUpgradeProduct.price); ?????????NSLog(@"Product?id:?%@"?,?proUpgradeProduct.productIdentifier); ?????} ????? ?????for?(NSString?*invalidProductId?in?response.invalidProductIdentifiers) ?????{ ?????????NSLog(@"Invalid?product?id:?%@"?,?invalidProductId); ?????} ????? ??????????[productsRequest?release]; ????? ?????[[NSNotificationCenter?defaultCenter]?postNotificationName:kInAppPurchaseManagerProductsFetchedNotification?object:self?userInfo:nil]; ?}? 上面代碼有幾點需要注意:
- 指定產(chǎn)品id時,你必須使用完整產(chǎn)品id。例如,上例中使用 “com.runmonster.runmonsterfree.upgradetopro”。僅使用 “upgradetopro” 將不會正常工作。
- 如果在productsRequest:didReceiveResponse:中response.products 為 nil,而你的產(chǎn)品id出現(xiàn)于 response.invalidProductIdentifers 數(shù)組中時,那么請做好心理準(zhǔn)備開始一場徒勞的搜索戰(zhàn)吧。 StoreKit API沒有提供任何幫助,也沒有任何指示關(guān)于為什么你的id是無效的。很可愛,不是嗎?
- SKProduct類提供了有關(guān)程序標(biāo)題和描述的本地化版本,但是價格則沒有本地化版本。下面是針對此疏忽提供的代碼:
??#import?<Foundation/Foundation.h> ?#import?<StoreKit/StoreKit.h> ??@interface?SKProduct?(LocalizedPrice) ??@property?(nonatomic,?readonly)?NSString?*localizedPrice; ??@end? ??#import?"SKProduct+LocalizedPrice.h" ??@implementation?SKProduct?(LocalizedPrice) ??-?(NSString?*)localizedPrice ?{ ?????NSNumberFormatter?*numberFormatter?=?[[NSNumberFormatter?alloc]?init]; ?????[numberFormatter?setFormatterBehavior:NSNumberFormatterBehavior10_4]; ?????[numberFormatter?setNumberStyle:NSNumberFormatterCurrencyStyle]; ?????[numberFormatter?setLocale:self.priceLocale]; ?????NSString?*formattedString?=?[numberFormatter?stringFromNumber:self.price]; ?????[numberFormatter?release]; ?????return?formattedString; ?} ??@end? 加入上述代碼,測試一下。你應(yīng)該在控制臺窗口中看見產(chǎn)品信息了。然而更大的可能是,你得到了一個無效的產(chǎn)品id。我下一篇文章將介紹怎樣對這個問題進(jìn)行調(diào)試。但是,下面的步驟8有可能是阻礙你前進(jìn)的障礙。
8. 等待幾小時
遵循了上述所有步驟,但是你的產(chǎn)品仍然是無效的?你是否兩次,三次,四次不懈努力地確認(rèn)你是否遵循了上面提到的每個步驟?你是否已經(jīng)對網(wǎng)上IAP信息少得可憐而感到絕望?
那么,你應(yīng)該等待。
你的產(chǎn)品要進(jìn)入iTunes Connect使得Apple準(zhǔn)備好沙箱環(huán)境需要一些時間。對于我而言,我是經(jīng)過了無數(shù)次產(chǎn)品無效錯誤的絕望。而在24小時后,我沒有修改任何一行代碼,但產(chǎn)品id變?yōu)橛行АN艺J(rèn)為要使產(chǎn)品發(fā)布到Apple的網(wǎng)絡(luò)系統(tǒng)需要幾個小時的時間,但如果你有時間的話,你可以像我一樣等上24個小時。
購買產(chǎn)品
至此你應(yīng)該已經(jīng)成功地獲取了 SKProduct 描述。比較而言,支持購買產(chǎn)品相對簡單些。僅需下面三個步驟:
編寫代碼支持事務(wù)(transaction)在iTunes Connect中添加程序測試用戶在設(shè)備中登錄你的 iTunes Store 帳號購買測試 我們從編寫支持事務(wù)所需代碼開始。
1. 編寫代碼支持事務(wù)
首先注意:你將負(fù)責(zé)開發(fā)產(chǎn)品購買的用戶界面。StoreKit 未提供任何與用戶界面相關(guān)的元素。如果你希望你的購買用戶界面與App Store一樣,那么你要自己完成。
下面所有代碼都是有關(guān)事務(wù)處理的后臺部分。這是一個單獨的類只有一條簡單的API以供外部類(比如view controller)調(diào)用進(jìn)行購買。如果你找到將其集成到你程序的購買部分的方法,那么我推薦你使用類似方案。
首先,需要遵循 SKPaymentTransactionObserver 協(xié)議:
???#define?kInAppPurchaseManagerTransactionFailedNotification?@"kInAppPurchaseManagerTransactionFailedNotification" ?#define?kInAppPurchaseManagerTransactionSucceededNotification?@"kInAppPurchaseManagerTransactionSucceededNotification" ??… ??@interface?InAppPurchaseManager?:?NSObject?<SKProductsRequestDelegate,?SKPaymentTransactionObserver> ?{ ?????… ?} ???-?(void)loadStore; ?-?(BOOL)canMakePurchases; ?-?(void)purchaseProUpgrade; ??@end? 上面我們定義了兩個新的notification,它們將作為購買事務(wù)的結(jié)果被發(fā)送。在上例中我們?nèi)匀皇褂门c獲取產(chǎn)品描述同一個InAppPurchaseManager類。
??#define?kInAppPurchaseProUpgradeProductId?@"com.runmonster.runmonsterfree.upgradetopro" ??… ??#pragma?- ?#pragma?Public?methods ?????-?(void)loadStore ?{ ??????????[[SKPaymentQueue?defaultQueue]?addTransactionObserver:self]; ????? ??????????[self?requestProUpgradeProductData]; ?} ?????-?(BOOL)canMakePurchases ?{ ?????return?[SKPaymentQueue?canMakePayments]; ?} ?????-?(void)purchaseProUpgrade ?{ ?????SKPayment?*payment?=?[SKPayment?paymentWithProductIdentifier:kInAppPurchaseProUpgradeProductId]; ?????[[SKPaymentQueue?defaultQueue]?addPayment:payment]; ?} ??#pragma?- ?#pragma?Purchase?helpers ?????-?(void)recordTransaction:(SKPaymentTransaction?*)transaction ?{ ?????if?([transaction.payment.productIdentifier?isEqualToString:kInAppPurchaseProUpgradeProductId]) ?????{ ??????????????????[[NSUserDefaults?standardUserDefaults]?setValue:transaction.transactionReceipt?forKey:@"proUpgradeTransactionReceipt"?]; ?????????[[NSUserDefaults?standardUserDefaults]?synchronize]; ?????} ?} ?????-?(void)provideContent:(NSString?*)productId ?{ ?????if?([productId?isEqualToString:kInAppPurchaseProUpgradeProductId]) ?????{ ??????????????????[[NSUserDefaults?standardUserDefaults]?setBool:YES?forKey:@"isProUpgradePurchased"?]; ?????????[[NSUserDefaults?standardUserDefaults]?synchronize]; ?????} ?} ?????-?(void)finishTransaction:(SKPaymentTransaction?*)transaction?wasSuccessful:(BOOL)wasSuccessful ?{ ??????????[[SKPaymentQueue?defaultQueue]?finishTransaction:transaction]; ????? ?????NSDictionary?*userInfo?=?[NSDictionary?dictionaryWithObjectsAndKeys:transaction,?@"transaction"?,?nil]; ?????if?(wasSuccessful) ?????{ ??????????????????[[NSNotificationCenter?defaultCenter]?postNotificationName:kInAppPurchaseManagerTransactionSucceededNotification?object:self?userInfo:userInfo]; ?????} ?????else?????{ ??????????????????[[NSNotificationCenter?defaultCenter]?postNotificationName:kInAppPurchaseManagerTransactionFailedNotification?object:self?userInfo:userInfo]; ?????} ?} ?????-?(void)completeTransaction:(SKPaymentTransaction?*)transaction ?{ ?????[self?recordTransaction:transaction]; ?????[self?provideContent:transaction.payment.productIdentifier]; ?????[self?finishTransaction:transaction?wasSuccessful:YES]; ?} ?????-?(void)restoreTransaction:(SKPaymentTransaction?*)transaction ?{ ?????[self?recordTransaction:transaction.originalTransaction]; ?????[self?provideContent:transaction.originalTransaction.payment.productIdentifier]; ?????[self?finishTransaction:transaction?wasSuccessful:YES]; ?} ?????-?(void)failedTransaction:(SKPaymentTransaction?*)transaction ?{ ?????if?(transaction.error.code?!=?SKErrorPaymentCancelled) ?????{ ??????????????????[self?finishTransaction:transaction?wasSuccessful:NO]; ?????} ?????else?????{ ??????????????????[[SKPaymentQueue?defaultQueue]?finishTransaction:transaction]; ?????} ?} ??#pragma?mark?- ?#pragma?mark?SKPaymentTransactionObserver?methods ?????-?(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]; ?????????????????break; ?????????????default: ?????????????????break; ?????????} ?????} ?}? ?要測試上面的新代碼,你還需要編寫調(diào)用 loadStore, canMakePurchases 以及 purchaseProUpgrade 方法的代碼。
有關(guān)上述代碼的詳細(xì)解釋,請參考官方 In App Purchase Programming Guide (IAP編程指南)
上述代碼有幾個部分是針對我的程序的。例如,在 provideContent:中,NSUserDefaults 中的@”isProUpgradePurchased” BOOL 字段被設(shè)定為 YES。程序的其他部分將檢查此BOOL值以確定是否需要啟動專業(yè)版功能。如果你正好也要實現(xiàn)免費升級專業(yè)版的功能,那么你可以使用同樣的方法。
2. 添加測試用戶
為測試上述代碼,你需要在 iTunes Connect 中創(chuàng)建測試用戶以對IAP功能進(jìn)行測試。你可以使用測試帳號購買產(chǎn)品而不被Apple收取費用。
按以下步驟創(chuàng)建測試用戶:
登錄到 http://developer.apple.com/iphone進(jìn)入 iTunes Connect選擇iTunes Connect首頁中的 “Manage Users”選擇 “In App Purchase Test User”選擇 “Add New User”填入用戶信息. 所有信息都不必是合法的。建議使用虛假簡短的email地址及簡短的密碼。選擇 “Save” 測試時你需要輸入這些email地址和密碼。
3. 在你的設(shè)備中退出登錄
在進(jìn)行程序購買功能測試前,你必須在你的設(shè)備中退出iTunes Store。遵循以下步驟:
打開Settings App點擊 “Store” 行點擊 “Sign Out”? 4. 購買測試
現(xiàn)在,終于可以開始進(jìn)行IAP功能的測試了。測試很簡單:
運行你設(shè)備中的程序進(jìn)行購買當(dāng)程序提示輸入用戶名和密碼時,輸入?yún)?shù)用戶的信息 如果你使用同一賬戶進(jìn)行購買時,系統(tǒng)將提示你已經(jīng)購買了此產(chǎn)品。按“Yes”就可以再次下載此產(chǎn)品。
總結(jié)
實現(xiàn)IAP功能比想象的要復(fù)雜許多。我可是經(jīng)過無數(shù)痛苦的經(jīng)歷才完成我的程序。希望能夠幫助其他開發(fā)者減輕他們的痛苦。
原文見:In App Purchases: A Full Walkthrough
本文轉(zhuǎn)自:IAP(程序內(nèi)購買): 完全攻略
總結(jié)
以上是生活随笔為你收集整理的IAP(程序内购买): 完全攻略的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。