内购流程:

1. 用户先拿到购买产品的单子,

2. 拿着单子去苹果那里交钱,交完钱让苹果在单子上盖个章

3.拿着盖了章的单子传给自己的服务器来验证是否真的支付成功,服务器是跟苹果验证(我们客户端也是可以跟苹果验证的,只是这样安全性不高)

4.根据服务器返回的信息做具体的处理

 先上代码干货

* 设置这个监听对象,会在该界面时时监测支付的状态变化

```
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// 添加观察者
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// 移除观察者
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
```

* 点击某个商品,开始购买流程

#pragma Mark -- 内购功能模块
-(void)buyYuBiWithId:(NSString *)ProductID{
if([SKPaymentQueue canMakePayments]){
// productID就是你在创建购买项目时所填写的产品ID
self.baseTableView.userInteractionEnabled = NO;
selectProductID = [NSString stringWithFormat:@"%@",ProductID];
[self requestProduct]; }else{
_isBuying = NO;
self.baseTableView.userInteractionEnabled = YES;
DebugLog(@"不允许程序内付费");
UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"温馨提示"
message:@"请先开启应用内付费购买功能。"
delegate:nil
cancelButtonTitle:@"确定"
otherButtonTitles: nil];
[alertError show];
}
} #pragma mark 1.请求所有的商品ID
-(void)requestProduct{ // 1.拿到所有可卖商品的ID数组 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{ NSArray *product = response.products;
if([product count] == 0){ 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]; }

* 下面这个方法就功能很多了
*  如果内购流程中的苹果支付完成,但是由于某些原因,没有结束该交易,苹果是会在页面出现的时候,回调这个交易的票据数据的,所以要有一个变量,标志SKPaymentTransactionStatePurchased是充值的时候,还是页面启动的时候回调的_isBuying

#pragma mark 4.实现观察者监听付钱的代理方法,只要交易发生变化就会走下面的方法
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{ /*
SKPaymentTransactionStatePurchasing, 正在购买
SKPaymentTransactionStatePurchased, 已经购买
SKPaymentTransactionStateFailed, 购买失败
SKPaymentTransactionStateRestored, 回复购买中
SKPaymentTransactionStateDeferred 交易还在队列里面,但最终状态还没有决定
*/ for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:{
NSLog(@"正在购买");
//存储购买的人ID
[[NSUserDefaults standardUserDefaults] setObject:[UserModel uid] forKey:@"applePayMan"];
[[NSUserDefaults standardUserDefaults] synchronize];
}break;
case SKPaymentTransactionStatePurchased:{
//[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
if (_isBuying) {//主动发起的回调
//存储支付成功的单子
[[StroeObserver shareStoreObserver] saveApplePayHisTransactionId:transaction.transactionIdentifier andState:@"0" isUpdate:NO];
[self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction]; }else{//这是启动的回调 if (_dbArr.count>0) {//数据库有数据,判断是否有记录
//[SDIndicator showInfoWithMessage:@"数据库有数据"];
for (NSDictionary *dic in _dbArr) {
if ([[dic objectForKey:@"transactionIdentifier"] isEqualToString:transaction.transactionIdentifier]) {//这是数据库中的单子
if ([[dic objectForKey:@"state"] isEqualToString:@"2"]) {//单子流程已经结束
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
//删除数据库中流程结束的单子
[[StroeObserver shareStoreObserver] deleteSuc:transaction.transactionIdentifier];
break;
}else{//状态是0,1的都是支付成功了
//[SDIndicator showInfoWithMessage:@"数据库数据充值去"];
//购买后告诉交易队列,把这个成功的交易移除掉-- [queue finishTransaction:transaction]
[self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
break;
}
}
}
} if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"applePayMan"] isEqualToString:[UserModel uid]]) {
//[SDIndicator showInfoWithMessage:@"数据库无数据充值去"];
[self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
}else{
//[SDIndicator showInfoWithMessage:@"结束交易"];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} } }break;
case SKPaymentTransactionStateFailed:{
[self hideHud];
NSLog(@"购买失败");
_isBuying = NO;
self.baseTableView.userInteractionEnabled = YES;
// 购买失败也要把这个交易移除掉
[queue finishTransaction:transaction];
}break;
case SKPaymentTransactionStateRestored:{
[self hideHud];
_isBuying = NO;
self.baseTableView.userInteractionEnabled = YES;
NSLog(@"恢复购买中,也叫做已经购买");
// 恢复购买中也要把这个交易移除掉
[queue finishTransaction:transaction];
}break;
case SKPaymentTransactionStateDeferred:{ NSLog(@"交易还在队列里面,但最终状态还没有决定");
}break;
default:
break;
}
}
}

我们可以看到,使用数据库了原因是
* 在苹果返回票据,支付成功的时候,之后的流程,我们走的是自己的服务器去验证,这样的话,中间流程一旦中断,这个交易就可能没有结束,然后呢,苹果的监听,就会给回调回来之前支付成功但是并未结束交易的票据数组。
* 但是并不能直接调用充值接口,因为我支付成功,立马杀掉进程,重新启动,换个账号,进入充值界面的话,就会充值给另一个用户,所以需要判断这个交易的用户属性,采用了数据库保存,发起的订单,当然成功的单子就删除了【当然也有个问题,及时极限操作,苹果的支付票据还没回来,我就杀掉app,自然也就不能本地保存了,虽然没人这么做,所以存了一下,发起交易的人的uid,方便处理漏掉的单子的时候,不要给另外的人存了钱,自然最后剩余的情况就让找客服吧,无解了】

下面是要拿苹果返回的交易票据,转成base64去验证(客户端,服务器验证两种)

// 苹果内购支付成功
- (void)buyAppleStoreProductSucceedWithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp { NSString * productIdentifier = paymentTransactionp.payment.productIdentifier;
DebugLog(@"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 WithPaymentTransactionp:paymentTransactionp]; }
  • 下面方法,接口调用成功的话,因为状态值是0的话,原因是已经充值成功,或者票据不对,这两种情况也必须直接结束交易,所以只要接口调用成功,一样结束交易

  • 接口调用失败的话,再次调用,直到成功

- (void)checkAppStorePayResultWithBase64String:(NSString *)base64String WithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp{

   NSMutableDictionary *prgam = [[NSMutableDictionary alloc] init];
[prgam setValue:base64String forKey:@"receipt-data"]; __weak typeof(self)WS = self;
[Api requestWithMethod:nil withPath:API_URL_Apple_pay withParams:prgam withSuccess:^(id responseObject) {
[WS hideHud];
_isBuying = NO;
WS.baseTableView.userInteractionEnabled = YES; //返回1成功充值,返回0,该单已经充值过了,没结束交易 或者票据错误---都结束交易 //更新充值成功的单子
[[StroeObserver shareStoreObserver] saveApplePayHisTransactionId:paymentTransactionp.transactionIdentifier andState:@"2" isUpdate:YES];
//结束苹果pay的交易队列,不然会出现--(您已购买此 App 内购买项目。此项目将免费恢复)提示
[[SKPaymentQueue defaultQueue] finishTransaction:paymentTransactionp];
//删除数据库中流程结束的单子
[[StroeObserver shareStoreObserver] deleteSuc:paymentTransactionp.transactionIdentifier]; if ([responseObject[@"status"] integerValue] == 1) { if(_giftRequestBlock){
WS.giftRequestBlock(@"1");
}
[SDIndicator showSuccessWithMessage:@"充值成功"];
[WS getData];
}
} withError:^(NSError *error) {
_isBuying = YES;
WS.baseTableView.userInteractionEnabled = NO;
[WS checkAppStorePayResultWithBase64String:base64String WithPaymentTransactionp:paymentTransactionp];
}];

 下面在说说,数据库存储的逻辑

#import <UIKit/UIKit.h>
#import "StroeObserver.h" @implementation StroeObserver + (StroeObserver *)shareStoreObserver
{
static StroeObserver *_storeOb = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_storeOb = [[StroeObserver alloc] init];
}); return _storeOb;
} -(void)saveApplePayHisTransactionId:(NSString *)transactionId andState:(NSString *)state isUpdate:(BOOL)isUpdate
{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/*将数据存入数据库*/
/*打开数据库*/
DB_INIT(db);
NSString *str = DB_PATH;
DebugLog("%@",str); DB_OPEN;
/*创建数据库*/
NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
//字段有:transactionIdentifier、productIdentifier、uid、transactionDate(支付时间)、rechargeDate(充值到账时间)、state([未支付]「已支付」和「已充值」)
NSString *tableSql = [NSString stringWithFormat:@"create table if not exists %@(transactionIdentifier,state,uid)",tableName];
DB_CREATE(tableSql) BOOL INSERT; if (isUpdate) {
INSERT = [db executeUpdate:[NSString stringWithFormat:@"update %@ set state = ? where transactionIdentifier = ?",tableName],state,transactionId];
}else{
INSERT = [db executeUpdate:[NSString stringWithFormat:@"insert into %@ values(?,?,?)",tableName],transactionId,state,[UserModel uid]];
} DB_INSERT;
DB_CLOSE;
});
} //删除成功的交易
-(void)deleteSuc:(NSString*)transactionIdentifier{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
DB_INIT(db);
DB_OPEN;
NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
BOOL ret = [db executeUpdate:[NSString stringWithFormat:@"delete from %@ where transactionIdentifier='%@'", tableName,transactionIdentifier]];
if (!ret) {
DebugLog(@"删除失败");
}else{
DebugLog(@"删除成功");
}
DB_CLOSE;
});
} //获取数据库中的支付成功,充值不成功订单
- (void)initApplePayHisDataFromDB
{
_dataArray = [[NSMutableArray alloc]init]; dispatch_queue_t queue = dispatch_queue_create("initApplePayHisDataFromDB", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
/*读取数据库旧数据*/
DB_INIT(db);
DB_OPEN;
NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
FMResultSet *rs = [db executeQuery:[NSString stringWithFormat:@"select * from %@ where uid=?",tableName],SWUID];
while ([rs next]) { NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
[rs stringForColumn:@"state"],@"state",
[rs stringForColumn:@"uid"],@"uid",
[rs stringForColumn:@"transactionIdentifier"],@"transactionIdentifier",nil]; [_dataArray addObject:dic];
}
DB_CLOSE;
});
} @end

就看效果了。。。

BYZqk

[https://www.jianshu.com/u/c6866a2b7547]

2018-04-2516:10:44

iOS内购总结的更多相关文章

  1. iOS - 内购总结

        如果有人以后要在做内购这一块.希望可以好好的阅读这篇文章,虽然不是字字珠玑.但是也是本人亲人趟过了无数的坑,希望可以对大家有所帮助!  下面是在研究工程中遇到的问题(iOS 内购的流程如下 1 ...

  2. IOS内购支付server验证模式

    IOS 内购支付两种模式: 内置模式 server模式 内置模式的流程: app从app store 获取产品信息 用户选择须要购买的产品 app发送支付请求到app store app store ...

  3. IOS内购支付服务器验证模式

    IOS 内购支付两种模式: 内置模式 服务器模式 内置模式的流程: app从app store 获取产品信息 用户选择需要购买的产品 app发送支付请求到app store app store 处理支 ...

  4. Unity苹果(iOS)内购接入(Unity内置IAP)

    https://www.jianshu.com/p/4045ebf81a1c Unity苹果(iOS)内购接入(Unity内置IAP) Kakarottog                       ...

  5. iOS 内购遇到的坑

    一.内购沙盒测试账号在支付成功后,再次购买相同 ID 的物品,会提示如下内容的弹窗.您以购买过此APP内购项目,此项目将免费恢复 原因: 当使用内购购买过商品后没有把这个交易事件关,所以当我们再次去购 ...

  6. 苹果IOS内购二次验证返回state为21002的坑

    项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...

  7. iOS 内购相关

    iOS 内购相关 下面总结一下过往订阅和内购的项目的代码方面的实现细节和注意事项,特别是掉单方面的处理. 后台的协议.商品ID.银行卡.内购类型.沙盒账号测试人员都由运营或者产品在苹果后台中申请处理. ...

  8. iOS 内购讲解

    一.总说内购的内容 1.协议.税务和银行业务 信息填写 2.内购商品的添加 3.添加沙盒测试账号 4.内购代码的具体实现 5.内购的注意事项 二.协议.税务和银行业务 信息填写 2.1.协议.税务和银 ...

  9. IOS内购--后台PHP认证

    参考网址:https://blog.csdn.net/que_csdn/article/details/80861408 http://www.php.cn/php-weizijiaocheng-39 ...

  10. IOS - 内购

    内购的五种产品类别 •非消耗品(Nonconsumable)买了就有,头衔,功能 –指的是在游戏中一次性购买并拥有永久访问权的物品或服务.非消耗品物品可以被用户再次下载,并且能够在用户的所有设备上使用 ...

随机推荐

  1. 初识Mysql之基本简单语法总结

    一.  DDL(data definition language)语句:数据定义语言. 这些语句定义了不同的数据段.数据库.表.列.索引等数据库对象.常用语句关键字:create.drop.alter ...

  2. python-类与继承

    类的继承 什么是继承? 继承是一种新建类的方式,新建的类称为子类,被继承的类称为父类.python中,父类.子类(派生类)只有在继承的时候才会产生. 继承的特性:子类会继承父类所有的属性. 为什么要用 ...

  3. The Best Path HDU - 5883 欧拉通路

    图(无向图或有向图)中恰好通过所有边一次且经过所有顶点的的通路成为欧拉通路,图中恰好通过所有边一次且经过所有顶点的回路称为欧拉回路,具有欧拉回路的图称为欧拉图,具有欧拉通路而无欧拉回路的图称为半欧拉图 ...

  4. poj 3614 奶牛美容问题 优先队列

    题意:每头奶牛需要涂抹防晒霜,其中有效的范围 min~max ,现在有L种防晒霜,每种防晒霜的指数为 f 瓶数为 l,问多少只奶牛可以涂上合适的防晒霜?思路: 优先队列+贪心 当奶牛的 min< ...

  5. Shell脚本完成hadoop的集群安装

    虽然整体实现的自动安装,但还是有很多需要完善的地方,比如说: 1. 代码目前只能在root权限下运行,否则会出错,这方面需要加权限判断: 2.另外可以增加几个函数,减少代码冗余: 3.还有一些判断不够 ...

  6. P3391 【模板】文艺平衡树(Splay)新板子

    P3391 [模板]文艺平衡树(Splay) 题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转 ...

  7. Tarjan算法及其应用

    Tarjan算法及其应用 引入 tarjan算法可以在图上求解LCA,强连通分量,双联通分量(点双,边双),割点,割边,等各种问题. 这里简单整理一下tarjan算法的几个应用. LCA http:/ ...

  8. launchMode

    launchMode在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task ...

  9. JS页面快捷键库hotkeys.js

    网友提供了一个好用的快捷键库,没有任何依赖,这是一个强健的 Javascript 库用于捕获键盘输入和输入的组合键,它没有依赖,压缩只有只有(~3kb). 这里也要特别感谢园友kacper的提醒与提供 ...

  10. [转] 彻底搞懂word-break、word-wrap、white-space

    white-space.word-break.word-wrap(overflow-wrap)估计是css里最基本又最让人迷惑的三个属性了,我也是用了n次都经常搞混,必须系统整理一下,今天我们就把这三 ...