这是第二篇
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或者下面留言给我!!