Cocos 2d-X Lua 游戏添加苹果内购(二) OC和Lua交互代码详解
这是第二篇
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或者下面留言给我!!
Cocos 2d-X Lua 游戏添加苹果内购(一) 图文详解准备流程
Cocos 2d-X Lua 游戏添加苹果内购(二) OC和Lua交互代码详解的更多相关文章
- Cocos 2d-X Lua 游戏添加苹果内购(一) 图文详解准备流程
事前准备 最近给游戏添加了苹果的内购,这一块的东西也是刚刚做完,总结一下,其实这里不管是游戏还是我们普通的App添加内购这一块的东西都是差不多的,多出来的部分就是我们Lua和OC的交互的部分,以前刚开 ...
- 添加“返回顶部”小图标按钮的JS(JavaScript)代码详解
如何给自己的网站添加方便快捷的"返回顶部"小图标按钮呢?如下图: JS源代码: /** * JavaScript脚本实现回到页面顶部示例 * @param acceleration ...
- 苹果内购服务器验证之receipt返回多组in_app思考
最近有部分用户反映,苹果内购充值失败,经过测试总结有几个关键点出现问题 1.app购买成功苹果没有返回票据,属于票据遗漏(取决于苹果服务器的响应状况),只能客户端进行监听刷新等处理 2.app连续购买 ...
- iOS:苹果内购实践
iOS 苹果的内购 一.介绍 苹果规定,凡是虚拟的物品(例如:QQ音乐的乐币)进行交易时,都必须走苹果的内购通道,苹果要收取大约30%的抽成,所以不允许接入第三方的支付方式(微信.支付宝等),当然开发 ...
- iOS开发苹果内购的介绍与实现
1.iOS开发苹果内购的介绍 1.1 介绍 苹果规定,凡是虚拟的物品(例如:QQ音乐的乐币)进行交易时,都必须走苹果的内购通道,苹果要收取大约30%的抽成,所以不允许接入第三方的支付方式(微信.支付宝 ...
- 苹果内购和 Apple Pay
作者:CC老师_MissCC链接:http://www.jianshu.com/p/e3bc47e81785來源:简书 苹果内购 1.什么是内购? 如果你购买的商品,是在本app中使用和消耗的,就一定 ...
- apicloud含有微信支付。支付宝支付和苹果内购的代码
apicloud含有微信支付.支付宝支付和苹果内购的代码 <!DOCTYPE html> <html> <head> <meta charset=" ...
- 苹果IOS内购二次验证返回state为21002的坑
项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...
- IOS,苹果内购和添加广告
内购——应用内购买 通过苹果应用程序商店有三种主要赚钱的方式: 直接收费(与国内大部分用户的消费习惯相悖) 广告(降低用户体验 应用程序名称带Lite可以添加广告) O2O -> Online推 ...
随机推荐
- 201521123106 《Java程序设计》第13周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...
- 201521123079《java程序设计》第13周学习总结
1. 本周学习总结 1.以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. (1)netassist可以用来链接IP端口 (2)accept方法可以用来监听端口,当没有客户端连接 ...
- 201521123031 《Java程序设计》第13周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...
- Srping - bean的依赖注入(Dependency injection)
目录 1 概述 2 两种基本的依赖注入方式 2.1 构造函数方式 2.2Setter方式 3 其他依赖注入功能 3.1 <ref/>标签引用不同范围的bean 3.2 内部bean 3.3 ...
- java线程池相关知识点总结
Android中常见到的很多通用组件一般都离不开"池"的概念,如各种图片加载库,网络请求库,即使Android的消息传递机制中的Meaasge当使用Meaasge.obtain() ...
- Tiled Editor 图块的两种导入方式
一.图块集图块的导入. 打开或者创建地图后,新建 新图块. 弹出新图块面板 图块类型选择 "基于图块集图块",一定要选择"嵌入地图",否则需要另存为其他类型的文 ...
- iOS蓝牙心得
1.获取蓝牙mac地址 因为安卓不能得到uuid,所以,在要同步的时候要将uuid转换成mac地址,下面是转换方法 [peripheral discoverServices:@[[CBUUID UUI ...
- 【转载】关于api-ms-win-crt-runtimel1-1-0.dll缺失的解决方案
关于api-ms-win-crt-runtimel1-1-0.dll缺失的解决方案 目录 关于api-ms-win-crt-runtimel1-1-0dll缺失的解决方案 目录 安装VC redite ...
- Java钉钉开发_01_开发前的准备
源码已上传GitHub:传送门 一.准备事项 1.1 一个能在公网上访问的项目: 参见:Java微信开发_02_本地服务器映射外网 1.2 一个钉钉账号 去注册 1.3 创建一个应用 登录钉钉后台 ...
- 记一次使用快速幂与Miller-Rabin的大素数生成算法
大家都知道RSA的加密的安全性就是能够找到一个合适的大素数,而现在判断大素数的办法有许多,比如Fermat素性测试或者Miller-Rabin素性测试,而这里我用了Miller-Rabin素性测试的算 ...