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个接口:

  1. 发起products information请求,并等待数据回来。

  2. 获取每个product的information。

  3. 请求购买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];}

要点如下:

  1. 转换C++的数组为Object-c的数组。

  2. 新建一个SKProductsRequest,用product identifiers来初始化。

  3. iAPProductsRequestDelegate是内部抽象的一个桥接Object-c类,用来接受StoreKit的回调,并转换到C++的回调。

  4. 一切准备就绪,启动request。

下面我们看下iAPProductsRequestDelegate是如何桥接的。
声明protocol:SKProductsRequestDelegate,
在interface里面定义了一个iosiap,引用到C++对象实例。

@interface iAPProductsRequestDelegate : NSObject<SKProductsRequestDelegate>@property (nonatomic, assign) IOSiAP *iosiap;@end

实现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

解析如下:

  1. 收到响应,解析出每个product information,再转换为C++数据存储起来。

  2. 请求结束通知。

  3. 请求失败通知,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

要点如下:

  1. payment的状态更新,这里有四个状态,我们一一做了映射。

  • IOSIAP_PAYMENT_PURCHASING不需要做任何处理。

  • IOSIAP_PAYMENT_PURCHAED这个消息里面,游戏需要把金币交付给玩家。

  • IOSIAP_PAYMENT_FAILED则可能需要UI提示错误信息。

  • IOSIAP_PAYMENT_RESTORED,consumble类型的IAP是没有这个消息的。

除了IOSIAP_PAYMENT_PURCHASING消息,其他消息在通知完上层游戏逻辑后,都需要finishTransaction处理。removedTransactions实则是由finishTransaction触发的回调,我们依然要把这个消息映射到上层。

测试

不少只使用C++的用户反馈不知道如何使用接口,这里给一个伪代码供参考

首先要让调用的类继承IOSiAPDelegate,重写三个消息函数。
IOSiAP的初始化很简单,记得把delegate设置为调用IOSiAP_Bridge。其他逻辑参考伪代码注释。

class IOSiAP_Bridge : public IOSiAPDelegate{public:IOSiAP_Bridge();~IOSiAP_Bridge();IOSiAP *iap;virtual void onRequestProductsFinish(void);virtual void onRequestProductsError(int code);virtual void onPaymentEvent(std::string &identifier, IOSiAPPaymentEvent event, int quantity);};IOSiAP_Bridge::IOSiAP_Bridge(){iap = new IOSiAP();iap->delegate = this;}IOSiAP_Bridge::~IOSiAP_Bridge(){delete iap;}IOSiAP_Bridge:: requestProducts(){iap->requestProducts(identifiers);}void IOSiAP_Bridge::onRequestProductsFinish(void){//必须在onRequestProductsFinish后才能去请求iAP产品数据。IOSProduct *product = iap->iOSProductByIdentifier(identifier);// 然后可以发起付款请求。iap->paymentWithProduct(product, quantity);}void IOSiAP_Bridge::onRequestProductsError(int code){//这里requestProducts出错了,不能进行后面的所有操作。}void IOSiAP_Bridge::onPaymentEvent(std::string &identifier, IOSiAPPaymentEvent event, int quantity){if (event == IOSIAP_PAYMENT_PURCHAED) {//付款成功了,可以吧金币发给玩家了。}//其他状态依情况处理掉。}

Where to Go

你可以在这里获取到本文的源码,把工程放到Cocos2d-x 3.0 beta下的projects目录下即可运行使用。

这里没有提及接口测试,我们将在下一章JSB篇中讲解。

Cocos2d-x使用iOS游戏内付费IAP(C++篇)相关推荐

  1. iOS应用内付费(IAP)开发步骤列表

    iOS应用内付费(IAP)开发步骤列表 前两天和服务端同事一起,完成了应用内付费(以下简称IAP, In app purchase)的开发工作.步骤繁多,在此把开发步骤列表整理如下.因为只是步骤列表, ...

  2. iOS应用内付费详解

    Himi  原创, 欢迎转载,转载请在明显处注明! 谢谢. 原文地址:http://blog.csdn.net/xiaominghimi/article/details/6937097 //--201 ...

  3. iOS应用内支付(IAP)的那些坑

    我们在今年春节后上线了新的在线智能题库:猿题库.猿题库现在推出了公务员考试行测和申论2个产品,均包括web, iOS和Android三个平台.这次我们尝试做一个收费的产品,所以在iOS端集成了应用内支 ...

  4. iOS应用内支付(IAP)的注意事项

    来源:http://blog.csdn.net/xinruiios/article/details/9289573 IAP的全称是In-App Purchase,应用内付费.这种业务模式允许用户免费下 ...

  5. IOS游戏上架 玩家iap充值 base64码发到苹果验证收据 返回值里面没有 in_app 段的奇怪问题.

    这几天我们的IOS游戏上架了.然后收到了很多的用户充值.但是itunesconnet上面却只有2个人确实是交了钱.肯定有人骗了我们. 先介绍下我们的验证流程: 手机发起充值->购买成功-> ...

  6. 如何使得客户端和服务器端完美配合做IOS应用内付费

    配置Developer.apple.com 登录到Developer.apple.com,然后进行以下步骤: 为应用建立建立一个不带通配符的App ID 用该App ID生成和安装相应的Provisi ...

  7. Unity IOS游戏内好评

    在开发时,所有评分请求都会通过,也就是说每次请求评分,评分对话框都会显示,但无法提交评分.在 Testflight 中,请求都不会被通过,所以如果 Testflight 测试时评分对话框没有正确显示, ...

  8. IOS 应用内支付(IAP)接口使用说明

    群里看到一个关于ios支付回调问题如下 问题: 箭头函数的回调没有执行 解决办法: 不要着急改成箭头函数, 先按官网复制代码跑通流程 为什么用箭头函数有这个bug呢? 文档说了 : 服务端要做好防重校 ...

  9. iOS 应用内购(iAP)

    国内银行CNAPS CODE 查询 http://weekend.blog.163.com/blog/static/746895820127961346724/ 在iTunesConnect创建内购商 ...

最新文章

  1. java 回滚异常_回滚事务并关闭抛出异常的连接
  2. vs2005打开vs2008
  3. CSDN 首页的第一屏 广告/新闻 比率 = 40 : 2
  4. 造大专计算机学历,广昌县职业技术学校计算机应用专业助您 掌握一技之长获大专学历...
  5. python与html5搭建聊天室_html5 websocket 新版协议聊天室 服务端(python版)
  6. 云小课|聊一聊DRS的数据过滤特性
  7. 使用PADDING-TOP:(PERCENTAGE)实现响应式背景图片
  8. 如何让 Pages 文字分为两栏或更多栏?
  9. linux 服务器长ping 加时间戳
  10. sql server2008密钥,sql server 2008 r2 序列号密钥
  11. 千兆2光4电工业级光纤自愈环网交换机ERPS环网交换机
  12. CommandName与CommandArgument
  13. 冯仑《企业领导最容易犯的十大错误》
  14. 手机中的计算摄影-超广角畸变校正
  15. 算法(Java)——双指针
  16. imageview设置资源图片
  17. Zabbix_agent的主动式和被动式
  18. 计算机音乐文爱图片,CG/贺敬轩《文爱》[FLAC/MP3-320K]
  19. Win10系统自带功能,提高办公效率
  20. 友华改设备标识命令_电信路由器密码怎么修改,路由器牌子是友华通信

热门文章

  1. UVA11462年龄排序
  2. hdu3415单调队列
  3. hdu4411 经典费用里建图
  4. 【错误记录】NDK 配置错误 ( C/C++ debug|arm64-v8a : Could not get version from cmake.dir path )
  5. 【Android 性能优化】布局渲染优化 ( CPU 渲染优化 | 减少布局的嵌套 | 测量布局绘制时间 | OnFrameMetricsAvailableListener | 布局渲染优化总结 )
  6. 【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )
  7. 【C++ 语言】线程 ( 线程创建方法 | 线程标识符 | 线程属性 | 线程属性初始化 | 线程属性销毁 | 分离线程 | 线程调度策略 | 线程优先级 | 线程等待 )
  8. 进程 互斥锁、队列与管道、生产者消费者模型
  9. 设计模式-Decorator模式
  10. JVM 参数及各部分含义(转)