本文章转载 http://www.ityran.com/archives/5515。非本人原创!

前期准备

设备与账号

在开始编码之前我们需要准备测试环境。

  • 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 SKProduct
void *skTransactionObserver;// object-c TransactionObserver
std::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 nothing
IOSIAP_PAYMENT_PURCHAED,// need unlock App Functionality
IOSIAP_PAYMENT_FAILED,// remove waiting on UI, tall user payment was failed
IOSIAP_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 requestProduct
virtual void onRequestProductsFinish(void) = 0;
virtual void onRequestProductsError(int code) = 0;
// for payment
virtual 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 *)request
didReceiveResponse:(SKProductsResponse *)response
{
// release old
if (_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 valid
bool 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 string
NSNumberFormatter *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 restorable
event = 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是没有这个消息的。
  2. 除了IOSIAP_PAYMENT_PURCHASING消息,其他消息在通知完上层游戏逻辑后,都需要finishTransaction处理。
  3. 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) {
//付款成功了,可以吧金币发给玩家了。
}
//其他状态依情况处理掉。
}

这样就可以完成C++加IOS的内购IAP了!!!本人在ios7.1测试可以用!!

需要注意的地方是:payment的状态更新 IOSIAP_PAYMENT_FAILED这个是用于用户支付失败以及取消支付的时候反馈的,需要写逻辑!

本文章转载 http://www.ityran.com/archives/5515。非本人原创!

Cocos2d-x使用iOS游戏内付费IAP(C++篇)的更多相关文章

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

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

  2. iOS应用内付费(IAP)开发步骤

    1.苹果iTunes Connect内购产品信息录入. 1)创建app内购买项目(Create New),选择类型: 1.消耗型项目 对于消耗型 App 内购买项目,用户每次下载时都必须进行购买.一次 ...

  3. [转]iOS 应用内付费(IAP)开发步骤

    FROM : http://blog.csdn.net/xiaoxiangzhu660810/article/details/17434907 参考文章链接: (1)http://mobile.51c ...

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

    本文转载至 http://blog.devtang.com/2013/04/07/tricks-in-iap/ 前言 udacity 中的在线课程 <How to build a startup ...

  5. iOS 应用内付费(IAP)开发步骤

    折腾好几天,原来是税务信息没有填写,哎...  国内就是好啊,没有这些麻烦的事情...  :) 等24小时,等税务的审核结果...     有结论了 才能测试内购.... 如果税务信息没有填写完毕,p ...

  6. IOS应用内支付IAP从零开始详解

    前言 什么是IAP,即in-app-purchase 这几天一直在搞ios的应用内购,查了很多博客,发现几乎没有一篇博客可以完整的概括出所有的点,为了防止大伙多次查阅资料,所以写了这一篇博客,希望大家 ...

  7. 苹果内付费 IAP

    创建app内购买项目 消耗型项目:对于消耗型App内购买项目,用户每次下载时都必须进行购买.一次性服务通常属于消耗型项目,例如钓鱼App 中的鱼饵. 非消耗型项目:对于非消耗型App内购买项目,用户仅 ...

  8. unity 嵌入 百度分享 与 游戏内购物 iap

    原地址:http://blog.csdn.net/u012085988/article/details/18268869 最近老板让在unity项目里实现分享与内购功能,还要ios和android两个 ...

  9. <转>iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程!

    原文地址:http://blog.csdn.net/xiaominghimi/article/details/6937097 //——2012-12-11日更新   获取"产品付费数量等于0 ...

随机推荐

  1. lintcode :sort letters by case字符大小写排序

    题目 字符大小写排序 给定一个只包含字母的字符串,按照先小写字母后大写字母的顺序进行排序. 您在真实的面试中是否遇到过这个题? Yes 样例 给出"abAcD",一个可能的答案为& ...

  2. lintcode :Valid Palindrome 有效回文串

    题目: 有效回文串 给定一个字符串,判断其是否为一个回文串.只包含字母和数字,忽略大小写. 样例 "A man, a plan, a canal: Panama" 是一个回文. & ...

  3. DOS命令 Net config server Net config workstation

    DOS命令 Net config 作用:显示当前运行的可配置服务,或显示并修改某项服务的设置. 格式:net conifg service options 参数:(1)键入不带参数的net conif ...

  4. 47. Permutations II

    题目: Given a collection of numbers that might contain duplicates, return all possible unique permutat ...

  5. swift:自动引用计数ARC

    Swift自动引用计数:ARC    原文链接:https://numbbbbb.gitbooks.io/-the-swift-programming-language-/content/chapte ...

  6. 5、处理模型数据ModelAndView、Map、Model以及@SessionAttributes注解

    Spring MVC提供了以下几种途径输出模型数据 —— ModelAndView: 处理方法返回值类型为ModelAndView时,方法体即可通过该对象添加模型数据.数据会添加到request域中. ...

  7. Spring IoC — 基于XML的配置

    1.属性注入 注意点: 1)如果类中显示定义了一个带参的构造函数,则一定还要显示提供一个无参构造函数,否则使用属性注入时将抛出异常. 2)JavaBean关于属性命名的特殊规范.Spring只会检查B ...

  8. Struts2+JSON+JQUERY DEMO

    看到别人用了Struts2和JSON,自己也想练练手.记录下练习过程中遇到的问题,以便参考. 使用Maven新建项目: 先挂上pom.xml <project xmlns="http: ...

  9. Java开发之单例设计模式

    设计模式之单例模式: 一.单例模式实现特点:①单例类在整个应用程序中只能有一个实例(通过私有无参构造器实现):②单例类必须自己创建这个实例并且可供其他对象访问(通过静态公开的访问权限修饰的getIns ...

  10. yeoman开始项目

    使用 yeoman 构建项目之前,你需要安装这两个环境:node,ruby. 为什么需要使用node?因为我们需要使用grunt自动化工具,而grunt工具则是依赖node. 为什么需要使用ruby? ...