Cocos2d-x使用iOS游戏内付费IAP(C++篇)
source file url: http://www.tairan.com/archives/5515
Cocos2d-x使用iOS游戲內付費IAP(C++篇)
前期準備
設備與賬號
在開始編碼之前我們需要準備測試環境。
IAP只能真機測試,準備一臺iOS設備是必須的。
真機調試與IAP沙盒(SandBox)測試需要IDP(IOS Developer Program)賬號。
MAC開發機一臺.
本文不涉及IDP申請流程和真機調試設置,重點解析IAP相關的設置。
新建IAP付費條目
新建app ID
登錄iOS Dev Center, 點擊“Certificates, Identifiers & Profiles->Identifiers->App IDs”,切換到App IDs界面,再點擊“+”新建用于測試的AppID,默認設置”In-App-Purchase”已開啟,如下圖所示:
創建發布程序
無IAP的iOS App的真機測試是不需要下面的步驟的,而有IAP的則不同,需要先建立發布程序,設置好IAP信息才能測試相關的功能。
登錄iTunes Connect, 切換到“Manage Your Apps ”,點擊“Add New App”新建一個待發布程序, Bundle ID選擇剛才創建的App ID。
接下來的程序信息界面可隨意填寫,截圖可使用符合大小要求的假圖,先保證能創建成功、可測試,等到需要正式提交審核的時候再修改成最終截圖。
為發布程序新建IAP付費項目
點擊剛才創建完成的App進入“App Information”界面,再點擊“Manager In-App Purchases”進入IAP管理界面。
我們點擊左上角的“Create New”來新建一個IAP付費項目,接下來的Select Type界面會有5中IAP類型可供選擇。如圖:
前兩種是主類型:
游戲中使用得最多的就是“購買游戲幣”了,我們這里只關注Consumable類型,可多次購買。
更多其他類型的信息可查詢StoreKitGuide.pdf。
選擇“Consumable”,進入詳細信息設置界面。
Product ID全服唯一,起個自己覺得舒服的名稱, 一般建議:Bundle ID + IAP description.
Language需要至少一種,選擇“English”,方便測試。
當完成IAP付費項目的新建后,回到“Manager In-App Purchases”界面,可以看到下面的信息。
你可以隨時修改已存在的項目,即使在游戲上線后也能修改(Product ID除外),這樣可以在不發布新程序的情況下,做一些促銷活動。
新建IAP付費測試賬號
IAP的測試至關重要,你肯定不想給錢測試,被蘋果扣掉30%。蘋果的SandBox提供了一整套測試相關的服務。依然在iTunes Connect中設置。
點擊“Manage Users->Test User”進入測試賬號添加界面,點擊左上交的“Add New User”,填入Email等信息。
Note:Email地址必須是未注冊過Apple ID的email,注冊過的無法使用。
Select iTunes Store必須選“United States”,錯選為中國區不能測試不要怪我沒提醒。
到此,前期準備工作都已完成,你也許需要等待幾個小時讓iTunes Connect設置生效,以便代碼能獲取到IAP信息,接下來我們正式進入代碼階段。
IAP的C++封裝
新建項目
使用tool下的create_project.py創建項目,注意project ID 必須填寫為上面我們申請的APP ID,這樣真機調試才能取到我們設置的IAP信息。
C++開發的游戲,付費點直接使用Object-c的IAP接口會有諸多不便,在StoreKit基礎上再封裝一層C++接口會方便很多。新建 IOSiAP.h和IOSiAP.mm兩個文件,加入到Xcode工程。mm文件為C++和Object-c混編文件,可在里面實現兩種語言的互相調用。
IAP付費流程與接口抽象
如下圖所示:
首先,IAP付費首先需要客戶端發起請求,獲取服務器上的IAP條目信息。之所用需要這個步驟,是因為iTunes Connect后臺可以修改付費條目的價格、說明等信息。
然后,客戶端根據獲取到的IAP條目信息展示UI,當用戶點擊支付后發起payment請求。
最后,等待payment的回調響應。如果成功,游戲幣增加;如果失敗,UI提示給用戶。
從付費流程,我們可以看出需要3個接口:
發起products information請求,并等待數據回來。
獲取每個product的information。
請求購買product,并等待響應。
具體在IOSiAP.h中的抽象如下:
class?IOSiAP{public:IOSiAP();~IOSiAP();void?requestProducts(std::vector?<std::string>?&productIdentifiers);IOSProduct?*iOSProductByIdentifier(std::string?&identifier);void?paymentWithProduct(IOSProduct?*iosProduct,?int?quantity?=?1);IOSiAPDelegate?*delegate;//?===??internal?use?for?object-c?class?===void?*skProducts;//?object-c?SKProductvoid?*skTransactionObserver;//?object-c?TransactionObserverstd::vector<IOSProduct?*>?iOSProducts;};其中的identifier是IAP付費項目的“Product ID”。
IOSProduct是一個簡單的數據類,存放Product information。
class?IOSProduct{public:std::string?productIdentifier;std::string?localizedTitle;std::string?localizedDescription;std::string?localizedPrice;//?has?be?localed,?just?display?it?on?UI.bool?isValid;int?index;//internal?use?:?index?of?skProducts};IOSiAPDelegate是消息回調通知類,由具體的調用者來實現。
typedef?enum?{IOSIAP_PAYMENT_PURCHASING,//?just?notify,?UI?do?nothingIOSIAP_PAYMENT_PURCHAED,//?need?unlock?App?FunctionalityIOSIAP_PAYMENT_FAILED,//?remove?waiting?on?UI,?tall?user?payment?was?failedIOSIAP_PAYMENT_RESTORED,//?need?unlock?App?Functionality,?consumble?payment?No?need?to?care?about?this.IOSIAP_PAYMENT_REMOVED,//?remove?waiting?on?UI}?IOSiAPPaymentEvent;class?IOSiAPDelegate{public:virtual?~IOSiAPDelegate()?{}//?for?requestProductvirtual?void?onRequestProductsFinish(void)?=?0;virtual?void?onRequestProductsError(int?code)?=?0;//?for?paymentvirtual?void?onPaymentEvent(std::string?&identifier,?IOSiAPPaymentEvent?event)?=?0;};其中的前兩個消息是requestProducts()的消息回調,最后一個是payment的回調。而payment又分5種狀態。
requestProducts的實現
首先我們要包含StoreKit的頭文件
#import?<StoreKit/StoreKit.h>然后,需要把StoreKit.framework加入到工程里面,如下圖:
requestProducts的具體實現如下:
void?IOSiAP::requestProducts(std::vector?<std::string>?&productIdentifiers){//?1.NSMutableSet?*set?=?[NSMutableSet?setWithCapacity:productIdentifiers.size()];std::vector?<std::string>::iterator?iterator;for?(iterator?=?productIdentifiers.begin();?iterator?!=?productIdentifiers.end();?iterator++)?{[set?addObject:[NSString?stringWithUTF8String:(*iterator).c_str()]];}//?2.SKProductsRequest?*productsRequest?=?[[SKProductsRequest?alloc]?initWithProductIdentifiers:set];//?3.iAPProductsRequestDelegate?*delegate?=?[[iAPProductsRequestDelegate?alloc]?init];delegate.iosiap?=?this;productsRequest.delegate?=?delegate;//?4.[productsRequest?start];}要點如下:
轉換C++的數組為Object-c的數組。
新建一個SKProductsRequest,用product identifiers來初始化。
iAPProductsRequestDelegate是內部抽象的一個橋接Object-c類,用來接受StoreKit的回調,并轉換到C++的回調。
一切準備就緒,啟動request。
下面我們看下iAPProductsRequestDelegate是如何橋接的。
聲明protocol:SKProductsRequestDelegate,
在interface里面定義了一個iosiap,引用到C++對象實例。
實現SKProductsRequestDelegate的協議接口。
@implementation?iAPProductsRequestDelegate//?1.-?(void)productsRequest:(SKProductsRequest?*)requestdidReceiveResponse:(SKProductsResponse?*)response{//?release?oldif?(_iosiap->skProducts)?{[(NSArray?*)(_iosiap->skProducts)?release];}//?record?new?product_iosiap->skProducts?=?[response.products?retain];for?(int?index?=?0;?index?<?[response.products?count];?index++)?{SKProduct?*skProduct?=?[response.products?objectAtIndex:index];//?check?is?validbool?isValid?=?true;for?(NSString?*invalidIdentifier?in?response.invalidProductIdentifiers)?{NSLog(@"invalidIdentifier:%@",?invalidIdentifier);if?([skProduct.productIdentifier?isEqualToString:invalidIdentifier])?{isValid?=?false;break;}}IOSProduct?*iosProduct?=?new?IOSProduct;iosProduct->productIdentifier?=?std::string([skProduct.productIdentifier?UTF8String]);iosProduct->localizedTitle?=?std::string([skProduct.localizedTitle?UTF8String]);iosProduct->localizedDescription?=?std::string([skProduct.localizedDescription?UTF8String]);//?locale?price?to?stringNSNumberFormatter?*formatter?=?[[NSNumberFormatter?alloc]?init];[formatter?setFormatterBehavior:NSNumberFormatterBehavior10_4];[formatter?setNumberStyle:NSNumberFormatterCurrencyStyle];[formatter?setLocale:skProduct.priceLocale];NSString?*priceStr?=?[formatter?stringFromNumber:skProduct.price];[formatter?release];iosProduct->localizedPrice?=?std::string([priceStr?UTF8String]);iosProduct->index?=?index;iosProduct->isValid?=?isValid;_iosiap->iOSProducts.push_back(iosProduct);}}//?2.-?(void)requestDidFinish:(SKRequest?*)request{_iosiap->delegate->onRequestProductsFinish();[request.delegate?release];[request?release];}//?3.-?(void)request:(SKRequest?*)request?didFailWithError:(NSError?*)error{NSLog(@"%@",?error);_iosiap->delegate->onRequestProductsError([error?code]);}@end解析如下:
收到響應,解析出每個product information,再轉換為C++數據存儲起來。
請求結束通知。
請求失敗通知,2和3不會同時出現。
iOSProductByIdentifier的實現
iOSProductByIdentifier的實現簡單很多,在上一個步驟中我們已存儲了請求回來的數據,現在只需要查找出對應的數據返回即可。
IOSProduct?*IOSiAP::iOSProductByIdentifier(std::string?&identifier){std::vector?<IOSProduct?*>::iterator?iterator;for?(iterator?=?iOSProducts.begin();?iterator?!=?iOSProducts.end();?iterator++)?{IOSProduct?*iosProduct?=?*iterator;if?(iosProduct->productIdentifier?==?identifier)?{return?iosProduct;}}return?nullptr;}paymentWithProduct的實現
paymentWithProduct有兩個參數,第一個參數是由iOSProductByIdentifier獲取的IOSProduct實例,第二個參數是購買數量,本文只涉及Consumable類型的IAP,所以需要這個參數。
void?IOSiAP::paymentWithProduct(IOSProduct?*iosProduct,?int?quantity){SKProduct?*skProduct?=?[(NSArray?*)(skProducts)?objectAtIndex:iosProduct->index];SKMutablePayment?*payment?=?[SKMutablePayment?paymentWithProduct:skProduct];payment.quantity?=?quantity;[[SKPaymentQueue?defaultQueue]?addPayment:payment];}SKMutablePayment是異步請求,和requestProducts一樣自定義了一個叫iAPTransactionObserver的Object-c類來實現橋接。
@implementation?iAPTransactionObserver//?1.-?(void)paymentQueue:(SKPaymentQueue?*)queue?updatedTransactions:(NSArray?*)transactions{for?(SKPaymentTransaction?*transaction?in?transactions)?{std::string?identifier([transaction.payment.productIdentifier?UTF8String]);IOSiAPPaymentEvent?event;switch?(transaction.transactionState)?{case?SKPaymentTransactionStatePurchasing:event?=?IOSIAP_PAYMENT_PURCHASING;return;case?SKPaymentTransactionStatePurchased:event?=?IOSIAP_PAYMENT_PURCHAED;break;case?SKPaymentTransactionStateFailed:event?=?IOSIAP_PAYMENT_FAILED;NSLog(@"==ios?payment?error:%@",?transaction.error);break;case?SKPaymentTransactionStateRestored://?NOTE:?consumble?payment?is?NOT?restorableevent?=?IOSIAP_PAYMENT_RESTORED;break;}_iosiap->delegate->onPaymentEvent(identifier,?event,?transaction.payment.quantity);//?2.if?(event?!=?IOSIAP_PAYMENT_PURCHASING)?{[[SKPaymentQueue?defaultQueue]?finishTransaction:?transaction];}}}//?3.-?(void)paymentQueue:(SKPaymentQueue?*)queue?removedTransactions:(NSArray?*)transactions{for?(SKPaymentTransaction?*transaction?in?transactions)?{std::string?identifier([transaction.payment.productIdentifier?UTF8String]);_iosiap->delegate->onPaymentEvent(identifier,?IOSIAP_PAYMENT_REMOVED,?transaction.payment.quantity);}}@end要點如下:
payment的狀態更新,這里有四個狀態,我們一一做了映射。
IOSIAP_PAYMENT_PURCHASING不需要做任何處理。
IOSIAP_PAYMENT_PURCHAED這個消息里面,游戲需要把金幣交付給玩家。
IOSIAP_PAYMENT_FAILED則可能需要UI提示錯誤信息。
IOSIAP_PAYMENT_RESTORED,consumble類型的IAP是沒有這個消息的。
測試
不少只使用C++的用戶反饋不知道如何使用接口,這里給一個偽代碼供參考
首先要讓調用的類繼承IOSiAPDelegate,重寫三個消息函數。
IOSiAP的初始化很簡單,記得把delegate設置為調用IOSiAP_Bridge。其他邏輯參考偽代碼注釋。
Where to Go
你可以在這里獲取到本文的源碼,把工程放到Cocos2d-x 3.0 beta下的projects目錄下即可運行使用。
這里沒有提及接口測試,我們將在下一章JSB篇中講解。
總結
以上是生活随笔為你收集整理的Cocos2d-x使用iOS游戏内付费IAP(C++篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sqrt(int x) leetcode
- 下一篇: 软件测试中排错的基本方法