這是第二篇
Cocos 2d-X Lua 游戲添加蘋果內購(一) 圖文詳解准備流程
這是前面的第一篇,詳細的說明了怎樣添加內購項目以及填寫銀行信息提交以及沙盒測試員的添加使用以及需要我們注意的東西,結果,被移除首頁了!前面第一篇的內容是這篇的基礎,前面那些不弄好,下面的商品信息你是請求不到的,這點需要大家特別注意...有需要前面提到的內容的孩子可以點擊鏈接進去自己看看!!
這篇就具體的總結我們Lua和OC交互的內容以及內購具體的代碼以及結果的測試說明:
內購部分OC的代碼實現
先自己總結一下整個支付的流程,下面的代碼部分我們也就按照這個支付流程來解讀:
最開始你首先要做的就是給你的項目添加: StoreKit.framework 框架
以及在你需要寫支付的類中導入: #import <StoreKit/StoreKit.h>
並且你還得遵守 <SKPaymentTransactionObserver,SKProductsRequestDelegate > 兩個協議,后面會實現他們相應的代理方法。
接下來你初始化了你支付類需要你初始化的東西之后,就開始判斷用戶有沒有禁止了蘋果支付,代碼如下:
// 在這里先判斷是否可以調用支付 -(void)isPay{ // 判斷用戶是否禁止了蘋果支付 if ([SKPaymentQueue canMakePayments]) { // 1.獲取產品信息列表 [self requestProductData:PRODUCTID]; }else{ self.alertTitle = @"充值失敗"; self.alertMessage = @"您禁止了支付權限!"; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 // 不能進行支付進行提示 [self.viewController presentViewController:self.alertController animated:YES completion:nil]; #else [self.viewController.view addSubview:self.alertView]; #endif } }
按照我們最開始時候的流程,接下來就需要我們去創建 SKProductsRequest 向蘋果服務器請求商品的信息,具體的代碼如下:
- (void)requestProductData:(NSString *)productId{ NSArray * productArray = [[NSArray alloc]initWithObjects:productId, nil]; NSSet * productSet = [NSSet setWithArray:productArray]; // 創建支付請求 SKProductsRequest * productRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:productSet]; productRequest.delegate = self; [productRequest start]; }
注意: 上面代碼中的 productId 就是我們剛開始在開發者后台創建新的內購產品時候的產品ID,要是不理解的強烈建議先看第一篇文章,得知道什么是產品ID。
#pragma mark -- SKRequestDelegate // 下面的方法會接收蘋果服務器返回的商品的產品信息 // Sent immediately before -requestDidFinish: - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { // 接收商品的信息 NSArray * productInfo = response.products; if (productInfo.count ==0) { NSLog(@"接收到的商品的信息為空!"); return; }else{ // 打印出商品信息 以下的信息全都是在你開發者賬號下面添加了內購項目中填寫 for (SKProduct *pro in productInfo) { NSLog(@"顯示名稱:%@", [pro localizedTitle]); NSLog(@"描述:%@", [pro localizedDescription]); NSLog(@"你設置的價格單位:%@", [pro price]); // 注意這里的單位,是用你在內購項目里面設置的定價 NSLog(@"單位:%@", [pro.priceLocale objectForKey:NSLocaleCurrencySymbol]); NSLog(@"CNY:%@", [pro.priceLocale objectForKey:NSLocaleCurrencyCode]); NSLog(@"測試商品ID:%@", [pro productIdentifier]); } } // SKProduct對象包含了在App Store上注冊的商品的本地化信息。 SKProduct *storeProduct = nil; for (SKProduct * pro in productInfo) { if ([pro.productIdentifier isEqualToString:PRODUCTID]) { storeProduct = pro; } } //創建一個支付對象,並放到隊列中 self.skMutablePayment = [SKMutablePayment paymentWithProduct:storeProduct]; //設置購買的數量 具體的交易金額就是這里的 數量 * 開發者賬號定價 if (self.payParments != 0) { //開始調用支付 self.skMutablePayment.quantity = self.payParments; [[SKPaymentQueue defaultQueue] addPayment:self.skMutablePayment]; // 開始一個內購監聽 [self startObserver]; }else{ NSLog(@"沒有設置購買的數量!!"); } }
這一步就走到我們接收到了商品的信息,接下來要做的事按照前面給的流程圖,就需要我們開啟一個內購的監聽。再給大家看到你請求到的商品的基本信息,如下:
具體的上面的內容是什么,大家可以對比上面的輸出的時候循環里面我加了它們各自的信息。
可以看到我們現在是添加了一個監控,開始監控和結束監控的代碼如下:
- (void)startObserver { if (!self.isObserver) { [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; NSLog(@"開始監聽 ------ 內購"); self.isObserver = YES; } } - (void)stopObserver { if (self.isObserver) { [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; NSLog(@"移除監聽 ------ 內購"); self.isObserver = NO; } }
上面我們就開啟了監聽,也開始了支付,中間的怎樣使用沙盒測試賬號進行購買的測試,以及過程中需要注意的事項我們已經總結過了,可以看前面的文章。
等支付完成之后,我們就可以收到來自蘋果支付結果的回調了,具體的回調處理下面的代碼中有詳細的注釋:
#pragma mark -- SKPaymentTransactionObserver //<SKPaymentTransactionObserver>千萬不要忘記綁定,代碼如下: //監聽購買結果 //[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions { SKPaymentTransaction *transaction = transactions.lastObject; switch (transaction.transactionState) { // 購買成功 case SKPaymentTransactionStatePurchased: { NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]]; // 剛才交易的內購的清單 NSString *receipt = [data base64EncodedStringWithOptions:0]; NSLog(@"剛才交易的內購的清單:%@",receipt); // 對交易結果進行二次驗證 [self verifyPruchase]; } break; // 交易失敗 case SKPaymentTransactionStateFailed: { NSLog(@"交易失敗"); // 交易失敗也要回調服務端 // 將交易從交易隊列中刪除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } break; case SKPaymentTransactionStateRestored: { NSLog(@"這是你已經購買過該商品!"); [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } break; case SKPaymentTransactionStatePurchasing: { NSLog(@"商品添加進列表"); } break; default: { NSLog(@"這是什么情況啊?"); } break; } }
接下來在按照流程走的話就到二次驗證支付結果的,其實我覺得這個過程放在移動端問題也不大,整個過程走Https,也沒有什么關鍵的數據怕數問題,不像支付寶和微信支付簽名的過程一樣那么重要,不過需要注意的是驗證這個結果得地址是分開的,也就是在開發測試階段和上線階段的地址是不同的,發送網絡POST請求,對購買憑據進行驗證:
測試驗證地址:https://sandbox.itunes.apple.com/verifyReceipt
正式驗證地址:https://buy.itunes.apple.com/verifyReceipt
下面是具體的支付結果的驗證代碼:
#pragma mark 驗證購買憑據 - (void)verifyPruchase { // 驗證憑據,獲取到蘋果返回的交易憑據 // appStoreReceiptURL iOS7.0增加的,購買交易完成后,會將憑據存放在該地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 從沙盒中獲取到購買憑據 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; // 發送網絡POST請求,對購買憑據進行驗證 //測試驗證地址:https://sandbox.itunes.apple.com/verifyReceipt //正式驗證地址:https://buy.itunes.apple.com/verifyReceipt NSURL *url = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"]; NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f]; urlRequest.HTTPMethod = @"POST"; NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; urlRequest.HTTPBody = payloadData; // 提交驗證請求,並獲得官方的驗證JSON結果 iOS9后更改了另外的一個方法 NSData *result = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:nil]; // 官方驗證結果為空 if (result == nil) { NSLog(@"交易驗證失敗"); return; } // 二次驗證返回,在這里給服務端返回驗證結果 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil]; if (dict != nil) { // 比對字典中以下信息基本上可以保證數據安全 // bundle_id , application_version , product_id , transaction_id NSLog(@"驗證成功!購買的商品的信息是:%@", dict); // 在這里回調Lua文件支付的結果 NSDictionary * receipt = dict[@"receipt"]; // 在連續的交易中,會有多筆交易產生 NSArray * in_app = receipt[@"in_app"]; if (in_app.count !=0) { for (NSDictionary * dic in in_app) { // 訂單號回調Lua NSString * transaction_id = dic[@"transaction_id"]; [self toLuaFunc:self.handlerID backMsg:[transaction_id UTF8String]]; } } }else{ // 交易不成功,回調Lua NSString * transaction_id = @" "; [self toLuaFunc:self.handlerID backMsg:[transaction_id UTF8String]]; } }
這樣整個支付其實已經算是完成了的,看的仔細的朋友應該看到請求到回調結果之后我們OC回調Lua了,在這里成功之后我們是回調了Lua,在Lua倆面利用Socket向服務器去發送一個支付的確定的結果,下面我們說說Lua和OC的相互調用。
Lua 調用 OC
下面是自己在做的一個Lua文件和OC交互的一個大概的示意圖,如下:
通過上面的一個示意圖,在對比下面的代碼,我們一句句的分析一下整個流程:
首先是第一步: Lua 文件調用 Bridge_ios 着重看一下下面幾句代碼:
-- 點擊跳轉到蘋果支付界面 function Bridge_ios.presentApplePayWithParams(payParam, callback) --quantity 價格 callback回調 local params = {quantity = payParam, scriptHandler = callback} local ok,ret = luaoc.callStaticMethod(BRIDGE_CLASS,"presentApplePayController",params) if not ok then -- 返回值 print("luaj error:"..ret) end end
其實直接調用OC的就是 luaoc.callStaticMethod 這個方法,這個方法解釋一下:
1、luaoc local luaoc = require "cocos.cocos2d.luaoc" 這個Lua引入就像OC 的#import 一樣!
2、BRIDGE_CLASS 這個表示你和OC的那個類進行交互。
3、"presentApplePayController" 接下來的這個參數,就是你OC類里面寫的類方法!
4、params 顧名思義就是參數的意思,Lua 這里傳過去的是以 表 的形式,相信懂Lua 的你也清楚,什么是表!
再說說這個參數:看這一句 local params = {quantity = payParam, scriptHandler = callback}
我們把參數寫成了表的形式,用OC理解牛把它當成一個字典!quantity 是鍵 payParam 就是你要傳的值 , scriptHandler 是鍵,callback是值,只不過它是函數,OC需要回調的函數,具體的用法我們下面說回調的時候再說。
在上面的調用中:BRIDGE_CLASS 就是 AppController。
OC 回調 Lua
前面說完了Lua 調用OC,下面接着說說 OC是怎樣回調Lua 的,具體的根據下面的代碼解釋:
// Lua 回調函數 - (void) toLuaFunc: (int)funC backMsg:(std::string)msg{ #if CC_ENABLE_SCRIPT_BINDING int handler = funC; if (-1 != handler) { auto sc = cocos2d::Director::getInstance()->getScheduler(); sc->performFunctionInCocosThread([handler, msg](){ cocos2d::LuaBridge::pushLuaFunctionById(handler); cocos2d::LuaStack *stack = cocos2d::LuaBridge::getStack(); stack->pushString(msg.c_str()); stack->executeFunction(1); cocos2d::LuaBridge::releaseLuaFunctionById(handler); }); } #endif }
最后我們一句一句的解釋一下上面這個OC回調Lua的過程:
LuaBridge::pushLuaFunctionById(handlerID); //壓入需要調用的方法id(假設方法為XG)
LuaStack *stack = LuaBridge::getStack(); //獲取lua棧
stack->pushString("oc call lua method..."); //將需要通過方法XG傳遞給lua的參數壓入lua棧,這里也就是設置OC回調給Lua的參數
stack->executeFunction(1); //根據壓入的方法id調用方法XG,並把XG方法參數傳遞給lua代碼
LuaBridge::releaseLuaFunctionById(handlerID); //最后記得釋放一下function
上面的這整個過程,再結合我們第一篇文章寫得蘋果內購的圖文詳解流程,基本上一個完整的游戲添加內購的過程就算是結束了,要是有什么問題可以在我的主頁找我的QQ或者下面留言給我!!