内购全面总结
苹果IAP最大的坑点:applicationUsername=nil,你懂得
另外:IAP和第三方支付最大的不同点
第三方支付:客户端只要给服务器传商品参数给服务器让我们服务器向第三方支付服务器请求交易订单这样的好处是安全,可控制,可查询然后我们客户端根据服务器给我们的交易订单来拉起支付

但是IAP:如果也向第三方流程一样由服务器创建订单再下发给客户端然后调用IAP的话我们无法控制这笔订单是否成功不可控制因素太多,比如这笔交易没有成功而服务器已经生产了订单然后客户端根据苹果的交易结果去服务器验证在有服务器去向IAP验证延时太长。所以内购我们不能按照这样的流程来
而是客户端先向IAP发起请求支付,交易完成以后客户端再去我们自己的服务器创建订单然后再让服务器根据证订单再向苹果服务器去验证这笔交易是否完成最后通知客户端
第三方支付的流程图(此图来源简书:NewPan)

内购流程

一:协议
1.协议,税务和银行业务信息的填写
2.内购商品的添加
3.添加沙盒测试账号
4.内购代码具体实现
5.内购注意事项
二.协议.税务和银行信息的填写入口
• 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个都要填写)

先点击Contact Info 的Set Up

有些银行通过下面的Look up CNAPS Code方法查不到,就需要借助百度了,一定要准确查询,否则会有问题。推荐一个地址
https://e.czbank.com/CORPORBANK/query_unionBank_index.jsp如果查不到自己的银行cnaps code可以打电话给银行客服

货币类型可能有歧义,看你是想收美元还是人民币了,都说美元合适。不过,我做的时候为了避免事情,还是选择了CNY,支持国产。还有一点,银行账号如果是对公的账号,需要填写公司的英文名称,如果没有的话,上拼音!然后点击保存银行信息就算ok了,然后退回到最开始的页面

如果以上信息填写完毕,状态一直是Processing不要怀疑自己填写出错,那是需要审核一般1到3天就能通过

二、为app添加内购产品

在你点击添加内购产品按钮后会有弹框,提示你选择类型,这个就要看你app的需求了

填写完审核信息后,点击右上角的“存储”按钮,就添加了一个内购产品~

三、添加沙盒技术测试员
在iTunes Connect的用户和智能中选择“沙盒技术测试员”,填写信息保存以后就有一个测试员了

四、具体实现

import

import

pragma mark–内购代理

//收到产品返回信息这个时获取苹果服务器的产品列表根据id来的
- (void)productsRequest:(SKProductsRequest )request didReceiveResponse:(SKProductsResponse )response{

// NSLog(@”————–收到产品反馈消息———————”);
NSArray *product = response.products;
if([product count] == 0){
// [SVProgressHUD dismiss];
// NSLog(@”————–没有商品——————”);
return;
}

// NSLog(@”productID:%@”, response.invalidProductIdentifiers);
// NSLog(@”产品付费数量:%lu”,(unsigned long)[product count]);

SKProduct *p = nil;
for (SKProduct *pro in product) {NSLog(@"%@", [pro description]);NSLog(@"%@", [pro localizedTitle]);NSLog(@"%@", [pro localizedDescription]);NSLog(@"%@", [pro price]);NSLog(@"%@", [pro productIdentifier]);if([pro.productIdentifier isEqualToString:@"jianyinyue6"]){p = pro;}
}SKPayment *payment = [SKPayment paymentWithProduct:p];

// NSLog(@”发送购买请求”);
[[SKPaymentQueue defaultQueue] addPayment:payment];
// _hud.hidden = YES;

}

//请求失败
- (void)request:(SKRequest )request didFailWithError:(NSError )error{
// [SVProgressHUD showErrorWithStatus:@”支付失败”];
_hud.hidden = YES;
// NSLog(@”——————错误—————–:%@”, error);
}

  • (void)requestDidFinish:(SKRequest *)request{
    // [SVProgressHUD dismiss];
    // NSLog(@”————反馈信息结束—————–”);
    }
    //沙盒测试环境验证

define SANDBOX @”https://sandbox.itunes.apple.com/verifyReceipt”

//正式环境验证

define AppStore @”https://buy.itunes.apple.com/verifyReceipt”

//
-(void)verifyPurchaseWithPaymentTransaction{
//从沙盒中获取交易凭证并且拼接成请求体数据
NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];

NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接请求数据
NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];//创建请求到苹果官方进行购买验证
NSURL *url=[NSURL URLWithString:AppStore];
NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];
requestM.HTTPBody=bodyData;
requestM.HTTPMethod=@"POST";
//创建连接并发送同步请求
NSError *error=nil;
NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];
if (error) {NSLog(@"验证购买过程中发生错误,错误信息:%@",error.localizedDescription);return;
}
NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];

// NSLog(@”%@”,dic);
if([dic[@”status”] intValue]==0){
// NSLog(@”购买成功!”);
//验证过

    NSDictionary *dicReceipt= dic[@"receipt"];NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject];NSString *productIdentifier= dicInApp[@"product_id"];//读取产品标识//如果是消耗品则记录购买数量,非消耗品则记录是否购买过NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];if ([productIdentifier isEqualToString:@" "]) {

// 以后确认购买成功了
[self cfingMBhud];
[[NSUserDefaults standardUserDefaults] setValue:productIdentifier forKey:@”appidsting”];
[[NSUserDefaults standardUserDefaults]synchronize];
}else{
[defaults setBool:YES forKey:productIdentifier];
}
//在此处对购买记录进行存储,可以存储到开发商的服务器端
}else{

    //从沙盒中获取交易凭证并且拼接成请求体数据NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接请求数据NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];//创建请求到苹果官方进行购买验证NSURL *url=[NSURL URLWithString:SANDBOX];NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];requestM.HTTPBody=bodyData;requestM.HTTPMethod=@"POST";//创建连接并发送同步请求NSError *error=nil;NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];if (error) {NSLog(@"验证购买过程中发生错误,错误信息:%@",error.localizedDescription);return;}NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];NSLog(@"%@",dic);if([dic[@"status"] intValue]==0){//        NSLog(@"购买成功!");//验证过_hud.hidden = YES;NSDictionary *dicReceipt= dic[@"receipt"];NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject];NSString *productIdentifier= dicInApp[@"product_id"];//读取产品标识//如果是消耗品则记录购买数量,非消耗品则记录是否购买过NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];if ([productIdentifier isEqualToString:@"jianyinyue6"]) {//            以后确认购买成功了[self cfingMBhud];[[NSUserDefaults standardUserDefaults] setValue:productIdentifier forKey:@"appidsting"];[[NSUserDefaults standardUserDefaults]synchronize];}else{[defaults setBool:YES forKey:productIdentifier];}}else{_hud.hidden = YES;}}

}

//监听购买结果
- (void)paymentQueue:(SKPaymentQueue )queue updatedTransactions:(NSArray )transaction{
for(SKPaymentTransaction *tran in transaction){
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:{
// NSLog(@”交易完成”);
_hud.hidden = YES;
// 发送到苹果服务器验证凭证
[self verifyPurchaseWithPaymentTransaction];
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
case SKPaymentTransactionStatePurchasing:
// NSLog(@”商品添加进列表”);
break;
case SKPaymentTransactionStateRestored:{
// NSLog(@”已经购买过商品”);
_hud.hidden= YES;
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
case SKPaymentTransactionStateFailed:{
// NSLog(@”交易失败%@”,tran);
// [self verifyPurchaseWithPaymentTransaction];
_hud.hidden = YES;
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
default:
break;
}
}
}
//可以知道恢复购买购买了哪些东西这里可以喝服务器做交互
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{

NSMutableArray *ar = [[NSMutableArray alloc] init];
NSLog(@"received restored transactions: %lu", (unsigned long)queue.transactions.count);
//没有购买过
if (queue.transactions.count==0) {_hud.hidden = YES;_hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];_hud.mode =  MBProgressHUDModeText;_hud.removeFromSuperViewOnHide=NO;_hud.label.text = @"哦哦,你还没购买过此项目,赶快去购买吧!";_hud.bezelView.color = [UIColor blackColor];[_hud hideAnimated:YES afterDelay:2.0];
}
//购买过
for (SKPaymentTransaction *transaction in queue.transactions)
{NSString *productID = transaction.payment.productIdentifier;[ar addObject:productID];[[NSUserDefaults standardUserDefaults] setValue:ar[0] forKey:@"appidsting"];[[NSUserDefaults standardUserDefaults]synchronize];[self cfingMBhud];
}

}

//交易结束
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
// NSLog(@”交易结束”);
_hud.hidden = YES;
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

  • (void)dealloc{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    }

购买成功后我们iOS前端可以单独在客户端完成订单正确性的验证。但是因为有的项目后台要Android和iOS两端生成账单便于对账。所以我们请求后台接口,服务器处验证是否支付成功,依据后台返回结果做相应逻辑处理。

订单正确性的验证本来可以是:iOS客户端(购买成功)→ 前端到苹果服务器验证→处理苹果返回结果做相应逻辑处理; 现在:iOS客户端(购买成功)→ 后台→后台到苹果服务器验证→处理后台返回结果做相应逻辑处理)

服务器要做的是:
1.接收iOS前端发过来的购买凭证。
2.判断凭证是否已经存在或验证过,然后存储该凭证。
3.将该凭证发送到对应环境下的苹果服务器验证,并将验证结果返回给客户端。
4.根据需求,是否修改用户相应信息。
官方文档应该也是支持的这么做的→In-App Purchase Programming Guide
- (void)verifyTransactionResult{

//验证凭据,获取到苹果返回的交易凭据
// appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];

//从沙盒中获取到购买凭据
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
//传输的是BASE64编码的字符串
BASE64常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性,BASE64是可以编码和解码的。

NSDictionary *requestDict =@{@”receipt-data”: [receipt base64EncodedStringWithOptions:0],@”sandbox”:@”1”};

请求后台接口,服务器处验证是否支付成功,依据返回结果做相应逻辑处理
与后台协调好,让后台根据你的“sandbox”字段的1,0来区分请求是正式环境还是测试环境
当然“sandbox”这个字段也可以替换为你想要的,但是“receipt-data”不能替换,要注意!)
//请求成功的response自己输出看一下吧,status是0就成功了,这里就不贴出来了,因为有一些敏感数据,比如你的bundleID,product_id之类的
}

下面是两种环境下的苹果服务器验证地址
测试环境(审核用这个)

define SANDBOX @”https://sandbox.itunes.apple.com/verifyReceipt”

//正式环境验证

define AppStore @”https://buy.itunes.apple.com/verifyReceipt”

五、要注意的事项!
1.bundleID要与iTunes Connect上你App的相同,不然是请求不到产品信息的
2.在沙盒环境进行测试内购的时候,要使用没有越狱的苹果手机。
3.在沙盒环境下真机测试内购时,请去app store中注销你的apple ID,不然发起支付购买请求后会直接case:SKPaymentTransactionStateFailed。使用沙盒测试员的账号时不需要真正花钱的。
4.如果只添加了一个沙盒测试员账号,当一个真机已经使用了这个账号,另一个真机再使用这个账号支付也是会发生错误的。那就去多建几个沙盒测试员账号使用不同的,反正也是免费的,填写也很快。
5.监听购买结果,当失败和成功时代码中要调用:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
该方法通知苹果支付队列该交易已完成,不然就会已发起相同 ID 的商品购买就会有此项目将免费恢复的提示

六、请在本地做一下凭证存储!
现在订单正确性的验证是:iOS客户端(购买成功)→ 后台→后台到苹果服务器验证→处理后台返回结果做相应逻辑处理。
我们前端购买成功后,凭证本地保留一份,当与后台验证成功后,再将本地保留的凭证删除。否者一直使用本地已经保留的凭证与后台交互。
注:由于以前我在的iTunes Connect中填写过协议,税务,和银行业务步骤无法复现所以部分图片来自简书作者:睡不着的叶-《iOS开发 内购流程 手把手教你还不学?》文章。

七:出现的问题
如果在真机上运行代码请将真是apple id账号退出用测试账号登入
App Store审核的时候也使用的是沙盒购买,所以验证购买凭证的时候需要判断返回Status Code决定是否去沙盒进行二次验证,为了线上用户的使用,验证的顺序肯定是先验证正式环境,此时若返回值为21007,就需要去沙盒二次验证,因为此购买的是在沙盒进行的。
审核不通过:原因没有提供测试账号,或者审核时用的是正式环境验证链接
如果年会员或者月会员的话选择是—消耗性商品,订阅和消耗性返回数据有区别
生成的订单怎么对应上这个订单呢-》后台来做(推荐)也可以前台来做
凭证存储一定要在本地做一下(防止网络原因或者和后台中断访问)然后等和后台交互过确认以后再删除

八:坑点(转自简书NewPan)
最大的一个就是,从 IAP 交易结果出来到通知 APP,只有一次。
1.如果用户后买成功以后,网络就不行了,那么苹果的 IAP 也收不到支付成功的通知,就没法通知 APP,我们也没法给用户发货。
2.如果 IAP 通知我们支付成功,我们驱动服务器去 IAP 服务器查询失败的话,那就要等下次 APP 启动的时候,才会重新通知我们有未验证的订单。这个周期根本没法想象,如果用户一个月不重启 APP,那么我们可能一个月没法给用户发货
3.有人反馈,IAP 通知已经交易成功了,此时去沙盒里取收据数据,发现为空,或者出现通知交易成功那笔交易没有被及时的写入到沙盒数据中,导致我们服务器去 IAP 服务器查询的时候,查不到这笔订单。
4.如果用户的交易还没有得到验证,就把 APP 给卸载了,以后要怎么恢复那些没有被验证的订单?
5.越狱手机有无数奇葩的收据丢失或无效或被替换的问题,应该怎样酌情处理?
解决:越狱手机一律不准内购
检查是否越狱:友盟统计有一个方法#import

ios-IAP坑点和遇到的问题总结相关推荐

  1. Flutter实现微信支付和iOS IAP支付

    Flutter支付 微信支付 iOS IAP应用内支付 测试IAP中断购买的测试 公司近期将收费的功能排期了,由于项目做的是线上教育,提供的服务属于虚拟物品.根据iOS官方的规定,虚拟物品交易只能使用 ...

  2. Date对象 IOS踩坑

    Date对象 IOS踩坑 最近在做一个托管教师端小程序,里面有一个功能是选择某个日期加载该老师相关的课程,这里就需要用到日历组件.基于后台返回的数据及减少对日历组件的修改,我选择了对小程序一开始自配备 ...

  3. IOS iap处理逻辑流程图再次梳理

    序言: 本文补全一下iOS iap处理逻辑. iap处理逻辑 苹果退单wiki:https://developer.apple.com/documentation/storekit/in-app_pu ...

  4. 苹果应用内支付(iOS IAP)的流程与常用攻击方式

    苹果应用内支付(iOS IAP)的流程与常用攻击方式 Jan 19, 2017 常见支付流程 iap(in app purchase)指苹果应用内支付, 目前主要有两种方式. 1. 客户端直接veri ...

  5. Flutter实现微信支付和iOS IAP支付,ndk开发入门

    } } 页面端是这样调用的 WechatPayment paymentUtils = new WechatPayment(); paymentUtils.wxPay( state.model.wxPa ...

  6. iOS IAP应用内购详细步骤和问题总结指南

    最近公司在做APP内购会员功能 遇到了很多问题 总结记录一下 首先一定要区分Apple pay 和IAP内购的区别 可以先去看一下官方文档地址 有每个步骤的详细解释 本篇文章分为:1. 内购支付流程: ...

  7. Flutter实现微信支付和iOS IAP支付,老师讲的真棒

    // ① 弹出支付框后使用系统返回键关闭: // ② 进入微信支付密码框后不输入使用系统导航切回app或者系统返回键返回: // ③ 进入微信后直接返回桌面再回到应用: // ④ 弹出支付框后锁屏再开 ...

  8. cache failed module status_Flutter混编之路——iOS踩坑记录

    一.运行Xcode编译或者flutter run/build 过程中报错:"x86_64" is not an allowed value for option "ios ...

  9. iap java md5_苹果应用内支付(iOS IAP)的流程与常用攻击方式

    1. 客户端直接verify苹果的receipt 如果verify成功 自行发放商品 2. 客户端将receipt传给server,由server进行验证并发放商品 按照安全性原则, 客户端的所有信息 ...

  10. IOS IAP APP内支付 Java服务端代码

    场景:作为后台需要为app提供服务,在ios中,app内进行支付购买时需要进行二次验证. 基础:可以参考上一篇转载的博文In-App Purchase(iap)快速指南了解原理. 直接先上服务端测试通 ...

最新文章

  1. 赠 看穿一切的var_dump
  2. 【Harvest源码分析】GetFilteredSignal函数
  3. Protobuf 的 proto3 与 proto2 的区别
  4. ansible普通用户部署K8s要点
  5. python定时播放音乐程序_python实现闹钟定时播放音乐功能
  6. LeetCode MySQL 1532. The Most Recent Three Orders(dense_rank + over窗口函数)
  7. python计算组合数_python排列组合算法
  8. java怎么从后天往前台传参_Java后台解析前台的get中文请求
  9. POJ 3740 Easy Finding (DLX模板)
  10. 学习SEO排行榜网站的SEO手法优化网站
  11. 物业费管理系统c语言作业,c语言物业管理系统.doc
  12. TMEA:源于音乐,高于盛典
  13. 74cms搭建 2021.03.13
  14. 专升本管理学知识点总结——管理环境与创新
  15. 台式计算机开机风扇不转,电脑开机显卡风扇不转是怎么回事|电脑开机风扇不转的解决方法...
  16. [E::hts_idx_push] NO_COOR reads not in a single block at the end 10 -1
  17. 特殊纪念日Android APP内设置黑灰色背景
  18. 互联网早报:腾讯内测游戏社交 App“NokNok”,对标 Discord
  19. max3490esa_MAX1661EUB-T_maxim芯片后缀tg16是什么意思
  20. tmux常用命令大全

热门文章

  1. 刷机风暴—3Q大战第二季
  2. emu8086汇编语言操作文件指令详解
  3. 推送跳转到对应的详情页(使用友盟推送的方式)
  4. 无为一中2021高考成绩查询,高考成绩出来啦!2017无为各中学高考成绩喜报!!给所有教师点赞!!!...
  5. 每日学习(每天学习后的笔记)
  6. OpenCVSharp入门教程——导读
  7. vue实现聊天框,模拟对话附上html
  8. 文本数据分析之中文酒店评论数据分析
  9. Java source1.5不支持diamond运算符,请使用source 7或更高版本以启用diamond运算符
  10. python语音识别库kaldi_pytorch使用pytorch-kaldi实现ASR语音识别 - pytorch中文网