1.苹果iTunes Connect内购产品信息录入。

1)创建app内购买项目(Create New),选择类型:

1.消耗型项目

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

2.非消耗型项目

对于非消耗型 App 内购买项目,用户仅需要购买一次。不会过期或随使用而减少的服务通常为非消耗型项目,例如游戏App 的新跑道。

3.自动续订订阅

通过自动续订订阅,用户可以购买指定时间期限内的更新和动态内容。除非用户取消选择,否则订阅(例如杂志订阅等)会自动续订。

4.免费订阅

通过免费订阅,开发者可以将免费订阅内容放入“报刊杂志”。用户注册免费订阅后,该订阅内容将会出现在与该用户Apple ID 关联的所有设备上。请注意,免费订阅不会过期,并且仅在支持报刊杂志功能的 App 中提供。

5.非续订订阅

非续订订阅允许有时限性的营销服务。对于 App 内购买项目中的限时访问内容,就需使用非续订订阅。例如,导航App 中语音导航功能的一周订阅,或者年度订阅已存档的视频或音频的在线目录。

一定要根据自己应用的情况选择正确,不然会被App Store审核团队拒绝。应用内的虚拟币要采用消耗型的,有固定时限的会员选择自动续订订阅。也可以只选择虚拟币充值自己后台购买的情况解决会员问题。

2)生成共享密钥

共享密钥是在您联系我们的服务器获取 App 内购买项目收据时使用的唯一代码。没有共享密钥,您将无法在沙箱技术模式下测试自动续订 App 内购买项目。另外,共享密钥不能在 App Store 使用。

注:无论与哪个 App 相关联,您的所有自动续订订阅都将使用同一共享密钥。

此共享密钥用于后台服务器验证用户购买项目的凭证,生成新密要服务器也立即改变验证密钥。共享密钥在验证自动续订订阅类型项目的时候必须需要。

3)内购项目的状态

A) Pending Developer Approval – Your in apppurchase has been created but has not been tested in a sandbox environment andapproved by you.

B) Approved By Developer – Your in apppurchase has been tested in a sandbox environment and has been approved by you.

C) Waiting For Review – You have submittedyour in app purchase to be reviewed by Apple.

D) In Review – Your in app purchase iscurrently being reviewed by Apple no edits can be made.

E) Developer Action Required – In app purchasedetail changes that you submitted have been rejected. You are required to takeaction to edit the detail information or cancel the request to change thedetail information before this in app purchase can be reviewed again.(内购项目详情界面会提示那个地方出现了问题,稍微修改一下再次提交就行了)

F) Ready for Sale – Apple has approved your inapp purchase to go live on the App Store with its associated application. Thein app purchase must be cleared for sale in iTunes Connect to be Ready forSale.

G) Rejected – Apple has rejected your in apppurchase during the review process. If you have not already been contacted byApple with more information about your rejection, you may inquire through theContact Us section of iTunes Connect. A rejected in app purchase cannot bereinstated. You must create a new in app purchase if you still wish for it tobe sold.

H) Developer Removed from Sale – You havemarked your in app purchase as not cleared for sale in iTunes Connect.

2.app端程序代码编写(代码仅供参考)

#pragma mark - 支付以单利的形式展开

+(PurchasesObject*)SharePurchases

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

if (_purchase == nil) {

_purchase = [[super alloc]init];

[[SKPaymentQueue defaultQueue]addTransactionObserver:_purchase];

}

});

return _purchase;

}

+(id)allocWithZone:(struct _NSZone *)zone

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

if (_purchase == nil) {

_purchase = [[super allocWithZone:zone]init];

}

});

return _purchase;

}

+(id)alloc

{

return _purchase;

}

#pragma mark -支付钻石会员

_alter = [[UIAlertView alloc]initWithTitle:@游客模式购买仅限当前设备使用所购买的权限,推荐您登录购买 message:nil delegate:self cancelButtonTitle:@取消 otherButtonTitles:@登陆购买(推荐),@游客模式购买 ,nil];

//游客购买很重要,会被AppStore审核团队拒绝。

#pragma mark -开始支付,根据录入内购项目的产品id去AppStore请求产品信息。

if ([SKPaymentQueue canMakePayments]) {

NSSet * set = [NSSet setWithArray:@[ProductID]];

SKProductsRequest * request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];

request.delegate = self;

[request start];

}

else

{

NSLog(@无权限购买);

}

#pragma mark -SKProductsRequestDelegate 获取appstroe产品信息

- (void)productsRequest:(SKProductsRequest *)requestdidReceiveResponse:(SKProductsResponse *)response {

self.mySelfView.userInteractionEnabled = NO;

[AFTools showHUD:@获取产品信息 atView:self.mySelfView];

NSLog(@-----------收到产品反馈信息--------------);

NSArray *myProduct = response.products;

if (myProduct.count == 0) {

[AFTools alertWithTitle:@购买失败 message:@无法获取产品信息];

NSLog(@无法获取产品信息,购买失败。);

return;

}

NSLog(@产品products==%@,myProduct);

NSLog(@产品id==%@,response.invalidProductIdentifiers);

NSLog(@产品数量==========%lu,(unsigned long)myProduct.count);

for(SKProduct *product in myProduct){

// SKMutablePayment

NSLog(@SKProduct描述信息%@, [product description]);

NSLog(@产品标题 %@ , product.localizedTitle);

NSLog(@产品描述信息: %@ , product.localizedDescription);

NSLog(@价格: %@ , product.price);

NSLog(@Product id: %@ , product.productIdentifier);

SKMutablePayment *MpayMent = [SKMutablePayment paymentWithProduct:product];

NSLog(@===%@,MpayMent.requestData);

if ([[SKPaymentQueue defaultQueue]respondsToSelector:@selector(addPayment:)]){

[[SKPaymentQueue defaultQueue] addPayment:MpayMent];

}

else

{

}

}

}

#pragmamark-SKPaymentTransactionObserver支付结果

- (void)paymentQueue:(SKPaymentQueue *)queueupdatedTransactions:(NSArray *)transactions

{

for (SKPaymentTransaction *transaction in transactions)

{

switch (transaction.transactionState)

{

case SKPaymentTransactionStatePurchased://交易完成

NSLog(@交易完成transactionIdentifier= %@, transaction.transactionIdentifier);

[self completeTransaction:transaction];

break;

case SKPaymentTransactionStateFailed://交易失败

[self failedTransaction:transaction];

NSLog(@交易失败);

break;

case SKPaymentTransactionStateRestored://已经购买过该商品

[self restoreTransaction:transaction];

NSLog(@已买过商品);

break;

case SKPaymentTransactionStatePurchasing://商品添加进列表

NSLog(@商品添加进列表);

break;

default:

break;

}

}

}

- (void)paymentQueue:(SKPaymentQueue *)queueremovedTransactions:(NSArray *)transactions

{

NSLog(@---------------移除-------------);

}

- (void)paymentQueue:(SKPaymentQueue *)queuerestoreCompletedTransactionsFailedWithError:(NSError *)error

{

NSLog(@---------------重复支付失败-------------);

}

- (void)completeTransaction:(SKPaymentTransaction *)transaction {

NSLog(@-------------------支付完成--------------------);

[self commitSeversSucceeWithTransaction:transaction];

}

-(void)restoreTransaction: (SKPaymentTransaction *)transaction

{

NSLog(@----------------重复支付-----------------);

[self commitSeversSucceeWithTransaction:transaction];

}

- (void)commitSeversSucceeWithTransaction:(SKPaymentTransaction *)transaction

{

NSString * productIdentifier = transaction.payment.productIdentifier;

NSString *transactionReceiptString= nil;

//系统IOS7.0以上获取支付验证凭证的方式应该改变,切验证返回的数据结构也不一样了。

if(IOSSystemVersion>=7.0)

{

NSURLRequest*appstoreRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle]appStoreReceiptURL]];

NSError *error = nil;

NSData * receiptData = [NSURLConnection sendSynchronousRequest:appstoreRequestreturningResponse:nil error:&error];

transactionReceiptString = [receiptDatabase64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

}

else

{

NSData * receiptData = transaction.transactionReceipt;

transactionReceiptString = [receiptDatabase64EncodedString];

}

// 向自己的服务器验证购买凭证

}

- (void)failedTransaction:(SKPaymentTransaction *)transaction {

[[SKPaymentQueue defaultQueue] finishTransaction: transaction];

}

最好在客户端上键一个数据库,跟踪订单的状态,防止用户订单在某个环节出现问题时无法寻找到订单进行二次处理。

去AppStore请求数据时有时候会出现错误,你可以iTunes connect里的connect us去给他们写邮件反馈问题。但是大部分时间你等等就能解决了,对就是什么也不做等着。也许那一天他就好了。

3.后台服务器验证

IOS 内支付有两种模式:

1) 内置模式

2) 服务器模式

内置模式的流程可以简单的总结为以下几步:

1) app从app store 获取产品信息

2) 用户选择需要购买的产品

3) app发送支付请求到app store

4) app store 处理支付请求,并返回transaction信息

5) app将购买的内容展示给用户

服务器模式的主要流程如下所示:

1) app从服务器获取产品标识列表

2) app从app store 获取产品信息

3) 用户选择需要购买的产品

4) app 发送 支付请求到app store

5) app store 处理支付请求,返回transaction信息

6) app 将transaction receipt 发送到服务器

7) 服务器收到收据后发送到app stroe验证收据的有效性

8) app store 返回收据的验证结果

9) 根据app store 返回的结果决定用户是否购买成功

上述两种模式的不同之处主要在于:交易的收据验证,内建模式没有专门去验证交易收据,而服务器模式会使用独立的服务器去验证交易收据。内建模式简单快捷,但容易被破解。服务器模式流程相对复杂,但相对安全。

开发之初,苹果方就很负责的告知:我们的服务器不稳定。真正开发之后,发现苹果方果然是很负责的,不仅是不稳定,而且足够慢。app store server验证一个收据需要3-6s时间。

1.用户能否忍受3-6s的等待时间

2.如果app store server 宕机,如何确保成功付费的用户能够得到正常服务。

对于第一个问题,我们有理由相信用户完全无法忍受,所以采用异步验证的方式,服务器收到客户端的请求后,就将请求放到MCQ中去处理。

对于第二个问题,由于苹果人员很负责人的告知:我们的服务器不稳定,所以不排除收据验证超时的情况。对于验证超时的收据,保存到数据库中并标记为验证超时,定时任务每隔一定的时间去app store验证,确保能够获取收据的验证结果。

在开发过程中,需要测试应用是否能够正常的进行支付,但是又不能进行实际的支付,因此需要使用苹果提供的sandbox Store测试。Store Kit不能在iOS模拟器中使用,测试Store必须在真机上进行。

在sandbox中验证receipt

https://sandbox.itunes.apple.com/verifyReceipt

在生产环境中验证receipt

https://buy.itunes.apple.com/verifyReceipt

在实际开发过程中,服务器端通过issandbox字段标识客户端传递的收据是沙盒环境中的收据还是生产环境中的收据。在提交苹果审核前,沙盒测试均无问题。提交苹果审核后,被告知购买失败,审核未通过。通过查询日志发现,客户端发送的交易收据为沙盒收据,但是issandbox字段却标识为生产环境。

结论:苹果审核app时,仍然在沙盒环境下测试。但是客户端同事在app提交苹果审核时,将issandbox字段写死,设置为生产环境。这样就导致沙盒收据发送到https://buy.itunes.apple.com/verifyReceipt去验证。

那么如何自动的识别收据是否是sandbox receipt呢?

识别沙盒环境下收据的方法有两种:

1.根据收据字段 environment = sandbox。

2.根据收据验证接口返回的状态码

如果status=21007,则表示当前的收据为沙盒环境下收据, t进行验证。

苹果反馈的状态码;

21000App Store无法读取你提供的JSON数据
21002 收据数据不符合格式
21003 收据无法被验证
21004 你提供的共享密钥和账户的共享密钥不一致
21005 收据服务器当前不可用
21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
21008 收据信息是产品环境中使用,但却被发送到测试环境中验证

先生产验证后测试验证,可以避免来回切换接口的麻烦。测试验证只要用你自己申请的测试appid的时候才会用到,用户不会拥有测试appid,所以不会走到测试验证这一步。即使生产验证出错,应该也不回返回21007状态吗。测试验证通过的用户名,和充值金额最好用数据库记录下来,方便公司资金核对。

iOS应用内付费(IAP)开发步骤的更多相关文章

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

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

  2. Cocos2d-x使用iOS游戏内付费IAP(C++篇)

    本文章转载 http://www.ityran.com/archives/5515.非本人原创! 前期准备 设备与账号 在开始编码之前我们需要准备测试环境. IAP只能真机测试,准备一台iOS设备是必 ...

  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. 【iOS开发必收藏】详解iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程!【2012-12-11日更新获取”产品付费数量等于0的问题”】

    转的别人的 看到很多童鞋问到,为什么每次都返回数量等于0?? 其实有童鞋已经找到原因了,原因是你在 ItunesConnect 里的 “Contracts, Tax, and Banking”没有完成 ...

  9. iOS开发支付篇-内购(IAP)

    一,前言 经典文章参考: . http://yimouleng.com/2015/12/17/ios-AppStore/ 内购流程 . http://www.jianshu.com/p/b199a46 ...

随机推荐

  1. IntelliJ IDEA JDK配置

    1.JDK环境 目前大多数IDE都没有集成JDK环境,IDEA也是一样,在使用IDEA之前首先要安装JDK,并且配置环境变量.与其他IDE不同之处在于,IDEA不会自动匹配系统的JDK环境,编译时会提 ...

  2. openldap+php-ldap操作

    一.基础知识首先,如果您对LDAP 不认识,建议先看看[原]LDAP服务介绍一文.本文以Linux 下常用的OpenLDAP为例说明.LDAP 以数方式存放数据,每个节点可存放属性或作为下面节点的父节 ...

  3. 《The Swift Programming Language》的笔记-第24页

    The Swift Programming Language读书笔记学习笔记 第24页 本页主要内容有两个:打印输出和怎样在swift凝视代码 1 怎样打印变量和常量的值? 使用println函数,细 ...

  4. scrollHeight、clientHeight、offsetHeight、scrollTop等的定义以及图解

    开发中经常遇到使用scrollHeight.scrollTop.clientHeight.offsetHeight等的情况,网上有众多关于这些属性的解释,但是并不全面和直观,现在将这些属性结合图例整理 ...

  5. linux more less cat

    在使用和维护Linux系统时,常常需要查看文件的相关内容,那么如何才能做到呢?下面小编就以CentOS6.4系统为例演示查看文件内容的几种常用的方法. 工具/原料   CentOS6.4 查看文件内容 ...

  6. Laravel5.1 登录和注册

    关于登录和注册 Laravel自带了一套组件实现了这一功能,我们只需要实现简单的视图即可. AuthController是专门管理用户注册和登录的. PassWordController是重置密码用的 ...

  7. 解决Ajax请求跨域问题

    from:https://blog.csdn.net/wang379275614/article/details/53333775 上篇文章提到,由于浏览器的同源策略,使得,AJAX请求只能发给同源的 ...

  8. iOS 状态栏更改为白色

    如果觉得在iOS 7启动期间状态栏黑色不合你意,以下方法可改变Status bar style成白色 在工程的plist添加 Status bar style,改变style值 默认是Gray sty ...

  9. c# Log4net的结构

    log4net 有四种主要的组件,分别是Logger(记录器), Repository(库), Appender(附着器)以及 Layout(布局).

  10. mongoDB之find()

    一.find方法 db.collection_name.find();查询 查询所有结果 1) db.users.find();类似于select * from users; 指定返回那些列(键) 2 ...