iOS 内购讲解
一.总说内购的内容
1.协议、税务和银行业务 信息填写
2.内购商品的添加
3.添加沙盒测试账号
4.内购代码的具体实现
5.内购的注意事项
二.协议、税务和银行业务 信息填写
2.1、协议、税务和银行业务 信息填写 的入口
2.2、选择申请合同类型
进入协议、税务和银行业务页面后,会有3种合同类型,如果你之前没有主动申请过去合同,那么一般你现在激活的合同只有iOS Free Application一种。
页面内容分为两块:
Request Contracts(申请合同)
Contracts In Effect(已生效合同)。
合同类型分为3种:
iOS Free Application(免费应用合同)
iOS Paid Application(付费应用合同)
iAd App NetNetwork(广告合同)
这里我们主要主要讲一下付费应用合同的申请流程。
2.3、申请iOS Paid Application合同(协议、税务和银行业务3个都要填写)
2.4、Contact Info(填写联系方式)
如果你没有添加过联系人,你需要通过Add New Contact按钮来添加一个新的联系人。然后指定联系人的职务,
职务如下:
Senior Management:高管
Financial:财务
Technical:技术支持
Legal:法务
Marketing:市场推广
如果你是独立开发者,可以全部填你自己一个人。
2.5、填写银行信息
选择你的银行账户,如果你没有,点击旁边的Add Bank Account添加一个账户。下面是添加一个账户的流程。
2.5.1、选择银行所在的国家
2.5.2、填写银行标识CNAPS Code
如果你不知道CNAPS Code是多少,可以百度搜CNAPS Code来查询,查询时会根据3个关键信息来查询,如下:
Bank Name:银行的英文名称(不能是拼音)
City:银行所在的城市英文名称(中国的城市用拼音)
Postal Code:邮编
然后在下面就会出来备选的银行,选择正确的银行后,点击next,进入下一步。
2.5.3、确认银行信息
2.5.4、填写银行账号信息
Bank Account Number:银行账号
Confirm Bank Account Number:再次输入银行账号
Account Holder Name:持卡人姓名,中文名用拼写,名在前,姓在后
Bank Account Currency:货币类型,一般国内的开发者选择CNY
2.5.5、确认所有信息
2.6.填写税务信息(这个内容比较多)
2.6.1.税务信息这一块了解不是很多,不过因为是国内开发者,可以不用太费心,税务信息分3种:
U.S Tax Forms:美国税务
Australia Tax Forms:澳大利亚税务
Canada Tax Forms:加拿大税务
2.6.2.一堆条约
我选择的是U.S Tax Forms,选择后会问你两个问题:
第1个问题如下:询问你是否是美国居民,有没有美国伙伴关系或者美国公司,如果没有直接选择No。
接下来第二个问题如下:询问你有没有在美国的商业性活动,没有也直接选No
2.6.3.然后填写你的税务信息,包括以下几点:
Individual or Organization Name:个人或者组织名称
Country of incorporation: 所在国家
Type of Beneficial Owner:受益方式,独立开发者选个人
Permanent Residence:居住地址
Mailing address:邮寄地址
Name of Person Making this Declaration:声明人
Title:头衔
2.6.4.打钩
2.6.5.澳大利亚的不要管了
2.6.6.加拿大的也不用管了
2.7.填写完成
2.8.待审核
你填写完所有资料后,合同状态就会变成Processing, 大概24小时内就会有结果。
三.内购商品的添加
3.1.创建内购商品
3.2.选择内购类型
3.2.1.消耗型商品:类似游戏中的钻石,还有现在某些APP中的货币,比如斗鱼里的鱼丸、映客里的映票。会被消耗的,要选择消耗型商品
注意:大多数的消耗型商品都是需要登录的,因为需要在数据库存余额。
需要注意的是:在登录之前,你最好不要让用户看到商品,有可能会因为这个原因被拒(大家都说看运气)
3.2.2.非消耗型商品:无法被消耗的商品,比如上文提到的视频课程,一次购买,就应该永久可以观看
注意:当你使用非消耗型商品时,你需要添加一个恢复购买的按钮
这个常见于各种游戏中,其实知道这个规定以后还是挺好理解的,非消耗型商品是不可被消耗的,一次购买终身使用的,非消耗型的商品是跟appleId绑定的,就是你平时下载APP让你输入账号密码的内个。
你需要一个恢复购买的按钮,来让用户恢复他购买的内容
3.2.3.订阅类型商品:如果你的公司是外包公司,有订阅类型商品的APP一定要用客户的账号提交审核,因为当APP中有过订阅类型商品,注意是有过,创建过再删除也算,这个APP无法被转移账号
注意:使用或曾经使用过订阅型商品的APP无法转移
3.3.创建好的产品
3.4.在上线的时候记得添加内购的商品
四.添加沙盒测试账号
4.1.添加沙盒测试的入口
4.2.添加沙盒测试账号
4.3.具体的测试账号信息填写
五.内购代码的具体实现(这才是大家所期望看到的)
5.1.我创建了一个购买金币的内购控制器ApplePayCIOViewController
在此,我仅仅向大家贴出.m的详细代码
5.2.内购的流程详细讲解
5.2.1. 用户先拿到购买产品的单子,
5.2.2.拿着单子去苹果那里交钱,交完钱让苹果在单子上盖个章
5.2.3.拿着盖了章的单子传给自己的服务器来验证是否真的支付成功
5.2.4.根据服务器返回的信息做具体的处理
5.3.上代码
5.3.1..先导入StoreKit.framework库;
5.3.2.创建ApplePayCIOViewController,遵守协议<SKPaymentTransactionObserver,SKProductsRequestDelegate>
5.3.3.ApplePayCIOViewController.m代码
#import "ApplePayCIOViewController.h"
#import <StoreKit/StoreKit.h>
// 产品的ID
#define ProductID1 @"CIOCourses1"
@interface ApplePayCIOViewController ()<SKProductsRequestDelegate,SKPaymentTransactionObserver>
{
NSString *selectProductID;
}
@end
@implementation ApplePayCIOViewController
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// 添加观察者
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// 移除观察者
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
} - (void)viewDidLoad {
[super viewDidLoad];
self.title = @"内购"; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"测试" style:UIBarButtonItemStylePlain target:self action:@selector(test)]; // 恢复购买的按钮
UIButton * revert = [[UIButton alloc]initWithFrame:CGRectMake(, , , )];
[revert setBackgroundColor:JKRandomColor];
[revert addTarget:self action:@selector(replyToBuy) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview: revert];
self.view.backgroundColor =[UIColor redColor];
} #pragma mark 恢复购买(主要是针对非消耗产品)
-(void)replyToBuy{ [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
#pragma mark 测试内购
-(void)test{ if([SKPaymentQueue canMakePayments]){ // productID就是你在创建购买项目时所填写的产品ID
selectProductID = [NSString stringWithFormat:@"%@",ProductID1];
[self requestProductID:selectProductID]; }else{ // NSLog(@"不允许程序内付费");
UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"温馨提示"
message:@"请先开启应用内付费购买功能。"
delegate:nil
cancelButtonTitle:@"确定"
otherButtonTitles: nil];
[alertError show];
}
}
#pragma mark 1.请求所有的商品ID
-(void)requestProductID:(NSString *)productID{ // 1.拿到所有可卖商品的ID数组
NSArray *productIDArray = [[NSArray alloc]initWithObjects:productID, nil];
NSSet *sets = [[NSSet alloc]initWithArray:productIDArray]; // 2.向苹果发送请求,请求所有可买的商品
// 2.1.创建请求对象
SKProductsRequest *sKProductsRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:sets];
// 2.2.设置代理(在代理方法里面获取所有的可卖的商品)
sKProductsRequest.delegate = self;
// 2.3.开始请求
[sKProductsRequest start]; }
#pragma mark 2.苹果那边的内购监听
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ NSLog(@"可卖商品的数量=%ld",response.products.count); NSArray *product = response.products;
if([product count] == ){ NSLog(@"没有商品");
return;
} for (SKProduct *sKProduct in product) { NSLog(@"pro info");
NSLog(@"SKProduct 描述信息:%@", sKProduct.description);
NSLog(@"localizedTitle 产品标题:%@", sKProduct.localizedTitle);
NSLog(@"localizedDescription 产品描述信息:%@",sKProduct.localizedDescription);
NSLog(@"price 价格:%@",sKProduct.price);
NSLog(@"productIdentifier Product id:%@",sKProduct.productIdentifier); if([sKProduct.productIdentifier isEqualToString: selectProductID]){ [self buyProduct:sKProduct]; break; }else{ //NSLog(@"不不不相同");
}
} } #pragma mark 内购的代码调用
-(void)buyProduct:(SKProduct *)product{ // 1.创建票据
SKPayment *skpayment = [SKPayment paymentWithProduct:product]; // 2.将票据加入到交易队列
[[SKPaymentQueue defaultQueue] addPayment:skpayment]; // 3.添加观察者,监听用户是否付钱成功(不在此处添加观察者)
//[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } #pragma mark 4.实现观察者监听付钱的代理方法,只要交易发生变化就会走下面的方法
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{ /*
SKPaymentTransactionStatePurchasing, 正在购买
SKPaymentTransactionStatePurchased, 已经购买
SKPaymentTransactionStateFailed, 购买失败
SKPaymentTransactionStateRestored, 回复购买中
SKPaymentTransactionStateDeferred 交易还在队列里面,但最终状态还没有决定
*/ for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:{ NSLog(@"正在购买");
}break;
case SKPaymentTransactionStatePurchased:{ NSLog(@"购买成功");
// 购买后告诉交易队列,把这个成功的交易移除掉
[queue finishTransaction:transaction];
[self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
}break;
case SKPaymentTransactionStateFailed:{ NSLog(@"购买失败");
// 购买失败也要把这个交易移除掉
[queue finishTransaction:transaction];
}break;
case SKPaymentTransactionStateRestored:{
NSLog(@"回复购买中,也叫做已经购买");
// 回复购买中也要把这个交易移除掉
[queue finishTransaction:transaction];
}break;
case SKPaymentTransactionStateDeferred:{ NSLog(@"交易还在队列里面,但最终状态还没有决定");
}break;
default:
break;
}
}
} // 苹果内购支付成功
- (void)buyAppleStoreProductSucceedWithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp { NSString * productIdentifier = paymentTransactionp.payment.productIdentifier;
// NSLog(@"productIdentifier Product id:%@", productIdentifier);
NSString *transactionReceiptString= nil; //系统IOS7.0以上获取支付验证凭证的方式应该改变,切验证返回的数据结构也不一样了。
NSString *version = [UIDevice currentDevice].systemVersion;
if([version intValue] >= 7.0){
// 验证凭据,获取到苹果返回的交易凭据
// appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
NSURLRequest * appstoreRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle]appStoreReceiptURL]];
NSError *error = nil;
NSData * receiptData = [NSURLConnection sendSynchronousRequest:appstoreRequest returningResponse:nil error:&error];
transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}else{ NSData * receiptData = paymentTransactionp.transactionReceipt;
// transactionReceiptString = [receiptData base64EncodedString];
transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}
// 去验证是否真正的支付成功了
[self checkAppStorePayResultWithBase64String:transactionReceiptString]; } - (void)checkAppStorePayResultWithBase64String:(NSString *)base64String { /* 生成订单参数,注意沙盒测试账号与线上正式苹果账号的验证途径不一样,要给后台标明 */
/*
注意:
自己测试的时候使用的是沙盒购买(测试环境)
App Store审核的时候也使用的是沙盒购买(测试环境)
上线以后就不是用的沙盒购买了(正式环境)
所以此时应该先验证正式环境,在验证测试环境 正式环境验证成功,说明是线上用户在使用
正式环境验证不成功返回21007,说明是自己测试或者审核人员在测试
*/
/*
苹果AppStore线上的购买凭证地址是: https://buy.itunes.apple.com/verifyReceipt
测试地址是:https://sandbox.itunes.apple.com/verifyReceipt
*/
// NSNumber *sandbox;
NSString *sandbox;
#if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
//sandbox = @(0);
sandbox = @"";
#else
//sandbox = @(1);
sandbox = @"";
#endif NSMutableDictionary *prgam = [[NSMutableDictionary alloc] init];;
[prgam setValue:sandbox forKey:@"sandbox"];
[prgam setValue:base64String forKey:@"reciept"]; /*
请求后台接口,服务器处验证是否支付成功,依据返回结果做相应逻辑处理
0 代表沙盒 1代表 正式的内购
最后最验证后的
*/
/*
内购验证凭据返回结果状态码说明
21000 App Store无法读取你提供的JSON数据
21002 收据数据不符合格式
21003 收据无法被验证
21004 你提供的共享密钥和账户的共享密钥不一致
21005 收据服务器当前不可用
21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
*/ NSLog(@"字典==%@",prgam); } #pragma mark 客户端验证购买凭据
- (void)verifyTransactionResult
{
// 验证凭据,获取到苹果返回的交易凭据
// appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// 从沙盒中获取到购买凭据
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
// 传输的是BASE64编码的字符串
/**
BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
BASE64是可以编码和解码的
*/
NSDictionary *requestContents = @{
@"receipt-data": [receipt base64EncodedStringWithOptions:]
};
NSError *error;
// 转换为 JSON 格式
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:
error:&error];
// 不存在
if (!requestData) { /* ... Handle error ... */ } // 发送网络POST请求,对购买凭据进行验证
NSString *verifyUrlString;
#if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
verifyUrlString = @"https://sandbox.itunes.apple.com/verifyReceipt";
#else
verifyUrlString = @"https://buy.itunes.apple.com/verifyReceipt";
#endif
// 国内访问苹果服务器比较慢,timeoutInterval 需要长一点
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[[NSURL alloc] initWithString:verifyUrlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f]; [storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData]; // 在后台对列中提交验证请求,并获得官方的验证JSON结果
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
NSLog(@"链接失败");
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options: error:&error];
if (!jsonResponse) {
NSLog(@"验证失败");
} // 比对 jsonResponse 中以下信息基本上可以保证数据安全
/*
bundle_id
application_version
product_id
transaction_id
*/ NSLog(@"验证成功");
}
}]; }
@end
六.内购的注意事项
6.1.一般发生于首次提交app或添加新商品,当你的app通过审核以后,你发现在生产环境下获取不到商品,这是因为app虽然过审核了,但是内购商品还没有正式添加到苹果的服务器里,耐心等待一段时间就可以啦~
6.2. 代码中的_currentProId所填写的是你的购买项目的的ID,这个和第二步创建的内购的productID要一致;本例中是 123。
6.3. 在监听购买结果后,一定要调用[[SKPaymentQueue defaultQueue] finishTransaction:tran];来允许你从支付队列中移除交易。
6.4. 沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。
6.5. 请务必使用真机来测试,一切以真机为准。
6.6. 项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。
6.7. 真机测试的时候,一定要退出原来的账号,才能用沙盒测试账号
6.8. 二次验证,请注意区分宏, 测试用沙盒验证,App Store审核的时候也使用的是沙盒购买,所以验证购买凭证的时候需要判断返回Status Code决定是否去沙盒进行二次验证,为了线上用户的使用,验证的顺序肯定是先验证正式环境,此时若返回值为21007,就需要去沙盒二次验证,因为此购买的是在沙盒进行的。
6.9.您的应用是否处于等待开发者发布(Pending Developer Release)状态?等待发布状态的IAP是无法测试的。
6.10.您的内购项目是否是最近才新建的,或者进行了更改?内购项目需要一段时间才能反应到所有服务器上,这个过程一般是一两小时,也可能再长一些达到若干小时。
6.11.您在iTC中Contracts, Tax, and Banking Information项目中是否有还没有设置或者过期了的项目?不完整的财务信息无法进行内购测试。
6.12.您是在越狱设备上进行内购测试么?越狱设备不能用于正常内购,您需要重装或者寻找一台没有越狱的设备。
6.13.您的应用是否是被拒状态(Rejected)或自己拒绝(Developer Rejected)了?被拒绝状态的应用的话对应还未通过的内购项目也会一起被拒,因此您需要重新将IAP项目设为Cleared for Sale。
6.14.您使用的测试账号是否是美国区账号?虽然不是一定需要,但是鉴于其他地区的测试账号经常抽风,加上美国区账号一直很稳定,因此强烈建议使用美国区账号。正常情况下IAP不需要进行信用卡绑定和其他信息填写,如果你遇到了这种情况,可以试试删除这个测试账号再新建一个其他地区的。
6.15.您是否将设备上原来的app删除了,并重新进行了安装?记得在安装前做一下Clean和Clean Build Folder。
6.16.您的plist中的Bundle identifier的内容是否和您的AppID一致?
iOS 内购讲解的更多相关文章
- iOS - 内购总结
如果有人以后要在做内购这一块.希望可以好好的阅读这篇文章,虽然不是字字珠玑.但是也是本人亲人趟过了无数的坑,希望可以对大家有所帮助! 下面是在研究工程中遇到的问题(iOS 内购的流程如下 1 ...
- IOS内购支付server验证模式
IOS 内购支付两种模式: 内置模式 server模式 内置模式的流程: app从app store 获取产品信息 用户选择须要购买的产品 app发送支付请求到app store app store ...
- IOS内购支付服务器验证模式
IOS 内购支付两种模式: 内置模式 服务器模式 内置模式的流程: app从app store 获取产品信息 用户选择需要购买的产品 app发送支付请求到app store app store 处理支 ...
- Unity苹果(iOS)内购接入(Unity内置IAP)
https://www.jianshu.com/p/4045ebf81a1c Unity苹果(iOS)内购接入(Unity内置IAP) Kakarottog ...
- iOS 内购遇到的坑
一.内购沙盒测试账号在支付成功后,再次购买相同 ID 的物品,会提示如下内容的弹窗.您以购买过此APP内购项目,此项目将免费恢复 原因: 当使用内购购买过商品后没有把这个交易事件关,所以当我们再次去购 ...
- 苹果IOS内购二次验证返回state为21002的坑
项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...
- iOS 内购相关
iOS 内购相关 下面总结一下过往订阅和内购的项目的代码方面的实现细节和注意事项,特别是掉单方面的处理. 后台的协议.商品ID.银行卡.内购类型.沙盒账号测试人员都由运营或者产品在苹果后台中申请处理. ...
- IOS内购--后台PHP认证
参考网址:https://blog.csdn.net/que_csdn/article/details/80861408 http://www.php.cn/php-weizijiaocheng-39 ...
- IOS - 内购
内购的五种产品类别 •非消耗品(Nonconsumable)买了就有,头衔,功能 –指的是在游戏中一次性购买并拥有永久访问权的物品或服务.非消耗品物品可以被用户再次下载,并且能够在用户的所有设备上使用 ...
随机推荐
- java反射对实体类取值和赋值
public static void checkDesignerEdit(Object dtos) throws Exception { Class dtosClass = dtos.getClass ...
- BOM 和 DOM
目录 一.BOM 1.什么是BOM 2. 浏览器内容划分 归BOM管的: 归DOM管的: 3. BOM常见方法 二.DOM 1 什么是DOM 2. DOM常见方法 一.BOM 1.什么是BOM BOM ...
- 动手创建 SSD 目标检测框架
参考:单发多框检测(SSD) 本文代码被我放置在 Github:https://github.com/XinetAI/CVX/blob/master/app/gluoncvx/ssd.py 关于 SS ...
- 使用CCS调试基于AM335X的SPL、Uboot(原创)
使用CCS调试基于AM335X的SPL.Uboot 一.开发环境 1.硬件平台:创龙AM3359核心板 2.SDK版本:ti-processor-sdk-linux-am335x-evm-03.00. ...
- Xamarin Essentials应用教程文件系统FileSystem
Xamarin Essentials应用教程文件系统FileSystem 文件系统用于管理设备内的各类文件.通过文件系统,应用程序可以创建永久文件和临时文件,也可以获取预先打包的文件,如预设数据库文件 ...
- 前缀和的应用 CodeForces - 932B Recursive Queries
题目链接: https://vjudge.net/problem/1377985/origin 题目大意就是要你把一个数字拆开,然后相乘. 要求得数要小于9,否则递归下去. 这里用到一个递归函数: i ...
- BZOJ.3504.[CQOI2014]危桥(最大流ISAP)
BZOJ 洛谷 这种题大多是多源多汇跑网络流.往返\(a_n/b_n\)次可以看做去\(a_n/b_n\)次,直接把危桥能走的次数看做\(1\). 先不考虑别的,直接按原图建模:危桥建双向边容量为\( ...
- IAR7.51提示秘钥无效IAR 以及 CCDebug驱动(包含win7 64bit)
今天IAR不识别我的仿真器,然后我感觉驱动有问题,就把之前的驱动卸载了,但是按照以前的方法按章驱动(选择路径到IAR的某个目录),提示找不到驱动... 也不想重新装个IAR了,于是到CSDN上下载了这 ...
- 去重+排序板子(set+map)
题意: 给定n个数,去重排序后输出个数和每个数 1.map实现 #pragma GCC optimize("O2") #include<iostream> #inclu ...
- Ruby语法基础(二)
Ruby语法基础(二) 继续ruby的学习,这次主要把目光放到运算符,条件判断,循环,方法,以及其他ruby特有的基本概念上 运算符 算术运算符:+,-,/,%,**,值的注意的是,ruby中一切皆为 ...