iOS 内购详解及遇到的坑
前言
本文主要集中于代码实现,关于创建商品网上已经有很多了,就不说了,比较简单。
之前做过 消耗性 和 非续订型 内购,代码里一直都是这两种。最近有个新需求,需要续订型 VIP。现在项目里有三种内购类型的产品了,嗯...
流程与代码
流程图
下面这句代码应该在程序入口写,这样写的好处是,如果有未完成的payment,进入程序后会继续走下去。而如果是在特定页面写,只有进入到这个页面才会继续走内购流程。
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
按照流程图
第一步:请求产品列表
是在进入内购页面后,去我们服务器请求
[self requestProductInfo];
第二步:服务器返回 产品ID 列表
成功拿到一系列产品ID
第三步:去苹果后台请求详细的产品信息(也可以使用我们服务器的信息,不去请求苹果上的信息)
[[IAPManager getInstance] requestProductsInfo:array];//请求商品信息的代码 - (void)requestProductsInfo:(NSArray*)prodIds {SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:prodIds]];productsRequest.delegate = self;[productsRequest start]; }
第四步:苹果后台返回详细的产品信息
//在代理方法中,返回详细的商品信息#pragma mark - SKProductsRequestDelegate - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {if (self.delegate && [self.delegate respondsToSelector:@selector(didReceiveProductsResponse:)])[self.delegate didReceiveProductsResponse:response.products]; }
第五步:展示产品
- (void)didReceiveProductsResponse:(NSArray *)array {if (array != nil && array.count > 0){//可以先按价格排个序NSSortDescriptor* sortDes = [[NSSortDescriptor alloc] initWithKey:@"price.doubleValue" ascending:YES];NSArray *sortArray = [array sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDes]];_dataArray = [sortArray mutableCopy];[tableview reloadData];}}
这里有一个产品价格本地化
使用 NSNumberFormatter *_numberFormatter;
_numberFormatter = [[NSNumberFormatter alloc] init]; [_numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; [_numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];- (void)updatePrice {[_numberFormatter setLocale:product.priceLocale];NSString *priceStr = [_numberFormatter stringFromNumber:price];//priceStr 就是正确的当地价格(例如 Rs290,¥10 等) }
第六步:用户点击购买
点击某一产品购买时,应先判断
[SKPaymentQueue canMakePayments] 如果是 YES,继续
第六点五步:这里我们的流程略有差异,我们先去自己服务器下单,拿到一个订单号
第七步:发送 payment 请求
下单后,这里把订单号设置进来;苹果会透传过来,确认订单时,需要使用到订单号。
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; payment.quantity = count; payment.applicationUsername = orderId; [[SKPaymentQueue defaultQueue] addPayment:payment];
这时,会有弹框出现,需要用户输入账户密码购买产品,将从这个账户绑定的卡上扣钱,我们无需做什么。
第八步:代理方法中返回结果
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {for (SKPaymentTransaction *transaction in transactions){[self onTransactionCompleted:transaction];} }//根据状态做相应的操作- (void)onTransactionCompleted:(SKPaymentTransaction *)transaction {switch (transaction.transactionState){case SKPaymentTransactionStatePurchased:[self completeTransaction:transaction];break;case SKPaymentTransactionStateFailed:[self failedTransaction:transaction];break;case SKPaymentTransactionStateRestored:[self restoreTransaction:transaction]; //订阅型和非消耗型的商品才有恢复状态break;default:break;} }
第九步:成功支付的,会有收据,transaction 的状态是 SKPaymentTransactionStatePurchased
需要把必要信息发送给我们服务器
- (void)completeTransaction:(SKPaymentTransaction *)transaction {//这里就是第七步中设置进去的订单号,正常情况下,苹果会透传过来;偶尔的没有透传嘛,就是个坑了。填坑中会有解决方法。 NSString* orderId = transaction.payment.applicationUsername; NSString* transactionId = transaction.transactionIdentifier;NSString* transactionReceipt = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding]; //收据NSInteger count = transaction.payment.quantity;//把这些信息传给后台 ...//[self sendToServer:(id)data];}
之后 APP端就等着服务器返回即可
第十四步:成功返回
服务器成功返回后,要关掉这次交易,这一步非常重要。
[self finishTransaction:transaction];
没有成功返回的,不要关掉。有可能是我们的服务器在某个时刻,没有响应,或者返回了错误,这时候交易还在队列中,下次打开APP,
[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; //所以这句写在了程序入口处
之后,会重新触发代理方法:即从第八步再走一遍
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
下面开始填坑
坑一:确定订单时,没有订单号
经过线上运营一段时间,发现会出现少量的掉单情况。对于这类情况,能提供收据的,基本都是手动补发产品了。之后针对这类情况,优化了代码,就很少有掉单情况了。
经研究发现,我们的掉单,发生在第九步,去我们服务器确认时,苹果没有把 订单号 发过来。订单号为空,自然找不到对应的订单了。
于是,在下单成功后,先把订单保存在本地。去确认订单时,如果没有订单号,就从本地拿一下,再去确认;确认成功后,删除对应订单号。
- (void)purchaseProduct:(SKProduct*)product count:(int)count order:(NSString*)orderId{ { // 暂存最后一次支付订单的数据 [self saveDataWithProductIdentifier:product.productIdentifier orderId:orderId]; } SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product]; payment.quantity = count; payment.applicationUsername = orderId; [[SKPaymentQueue defaultQueue] addPayment:payment];} - (void)completeTransaction:(SKPaymentTransaction *)transaction{ NSString* orderId = transaction.payment.applicationUsername; if (orderId == nil) { //如果没有订单号,本地取一下订单号 orderId = [self getOrderIdWithProductIdentifier:transaction.payment.productIdentifier]; } BOOL bVIP = [self isVipTransaction:transaction]; NSString* transactionId = transaction.transactionIdentifier; NSString* transactionReceipt = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding]; NSInteger count = transaction.payment.quantity; ...} - (void)finishTransaction:(SKPaymentTransaction *)transaction{//移除订单号 [self removeOrderIdWithProductIdentifier:transaction.payment.productIdentifier]; // remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction:transaction];}
//对应产品ID,保存订单号 - (void)saveDataWithProductIdentifier:(NSString *)identifier orderId:(NSString *)orderId {if (!identifier || !orderId) {return;}NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *path = [pathArray objectAtIndex:0];NSString *filePath = [path stringByAppendingPathComponent:Last_Product_Order_Path];NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];if (!dic) {dic = [NSMutableDictionary dictionary];}[dic setValue:orderId forKey:identifier];BOOL flag = [dic writeToFile:filePath atomically:YES];if(!flag) {NSLog(@"orderId保存失败");} }//获取某一订单号 - (NSString *)getOrderIdWithProductIdentifier:(NSString *)productIdentifier {if (!productIdentifier) {return nil;}NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *path = [pathArray objectAtIndex:0];NSString *filePath = [path stringByAppendingPathComponent:Last_Product_Order_Path];NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];return [dic valueForKey:productIdentifier]; }//成功后删除对应订单号 - (void)removeOrderIdWithProductIdentifier:(NSString *)productIdentifier {if (!productIdentifier) {return;}NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *path = [pathArray objectAtIndex:0];NSString *filePath = [path stringByAppendingPathComponent:Last_Product_Order_Path];NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];[dic removeObjectForKey:productIdentifier];BOOL flag = [dic writeToFile:filePath atomically:YES];if(!flag) {NSLog(@"orderId重新保存失败");} }
其他多为业务逻辑,欢迎各位留言交流
代码地址:https://github.com/lionwhitcher/InAppPurchase
转载于:https://www.cnblogs.com/lion-witcher/p/11169992.html
iOS 内购详解及遇到的坑相关推荐
- iOS 内购详解-代码篇
内购项目-代码篇 一.分步骤说明 1.获取商品列表 2.苹果服务器返回的可购买商品 3.下单购买商品 4.购买队列状态变化,判断购买状态是否成功 5.交易验证 6.拿到的收据信息是,此App所有购买的 ...
- 苹果服务器维护时间表2019,ios 内购详解(2019)
#import "EInAppPurchasing.h" #import #if TARGET_IPHONE_SIMULATOR // 开发时模拟器使用的验证服务器地址 #defi ...
- IOS内购对接成功回调中的坑
1. 项目中服务器的流程如下,和代码请求逻辑相同 . 2.内购校验IOS/Cash 和device/IOS/cash接口使用沙箱和正式两种校验的目的 3.老demo中沙箱返回状态码21002,需要改为 ...
- iOS 内购(In-App Purchase)详解
iOS 内购(In-App Purchase)详解 概述 IAP 全称:In-App Purchase,是指苹果 App Store 的应用内购买,是苹果为 App 内购买虚拟商品或服务提供的一套交易 ...
- IOS 多线程04-GCD详解 底层并发 API
IOS 多线程04-GCD详解 底层并发 API 注:本人是翻译过来,并且加上本人的一点见解. 前言 想要揭示出表面之下深层次的一些可利用的方面.这些底层的 API 提供了大量的灵活性,随之而来的是大 ...
- iOS 2D绘图详解(Quartz 2D)之路径(点,直线,虚线,曲线,圆弧,椭圆,矩形)
前言:一个路径可以包含由一个或者多个shape以及子路径subpath,quartz提供了很多方便的shape可以直接调用.例如:point,line,Arc(圆弧),Curves(曲线),Ellip ...
- IOS内购流程从0-1手把手教会
苹果掌握着可能是全球最重要的APP分发渠道,然而30%的抽成近年来也被人批评,现在苹果似乎也看到反对意见了,从2021年1月1日开始,部分小型企业的分成费用降低到15%. 据报道,苹果将于2021年1 ...
- iOS核心动画详解swift版----基础动画
2019独角兽企业重金招聘Python工程师标准>>> iOS核心动画详解swift版---基础动画 创建工程,添加2个ViewController,通过rootViewContro ...
- iOS内购-防越狱破解刷单
---------------------------2018.10.16更新--------------------------- 最近我们公司丢单率上涨,尤其是10月份比9月份来说丢单率翻了3倍, ...
- IOS内购验证 (Java版)
此处给各位贴出apple官方文档 App 内购买项目配置流程 apple 收据文档 apple 收据responseBody字段释义 IOS内购逻辑图 IOS内购验证相关代码 package xxxx ...
最新文章
- 信息服务器已停止工作,游戏服务器已停止工作
- Opencv中的FaceRecognizer类
- 调试JDK源码-HashSet实现原理
- python花钱培训值吗-Python培训班学生刚学到70%就拿到月薪万元的Offer
- [转]ASP.Net缓存总结
- Python入门字符串
- 互联网日报 | 6月4日 星期五 | 蚂蚁消费金融获批开业;腾讯云四个国际数据中心同步开服;滴滴App上线“老人打车”模式...
- 企业即时通讯设计理念及实现代码
- 【毕业答辩】论文答辩过不了?做好这几点,再也不用担心自己被“仍论文”
- NRF51822基于蓝牙协议栈SDK+FDS-Flash data storage 的使用及应用例程
- 微信支付接口 java服务器,JAVA微信支付接口开发——支付(示例代码)
- 德保罗大学计算机专业,德保罗大学计算机、信息与网络安全研究生语言及申请要求-费用-课程设置...
- 数学建模:回归分析——regress 函数
- 机械革命计算机配置,单品:机械革命X6Ti-S_机械革命笔记本电脑_笔记本评测-中关村在线...
- 华为5G模块MH5000-31资料全集5G技术论坛
- Redis如何存储热点数据
- 为chromium增加底部工具栏
- echarts+vue——散点图+折线图——技能提升
- Linux文件系统的压缩
- Keil uVision5新建工程模板
热门文章
- JavaScript 是如何工作的:WebRTC和对等网络的机制!
- Netty中有哪些自带的ChannelHandler?
- java使用jeids实现redis2.6的HyperLogLog数据结构的操作
- [javaSE] 集合框架(迭代器)
- 陶哲轩实分析 习题 12.5.4,12.5.5
- winform datagridview 自定义tooltip
- csv数据源的创建(一)
- 常用的一些注入命令!!!
- ORM 革命 —— 复兴 | ORM Revolution -- Revived
- php成长之路--1.composer下载依赖