iOS 内购IAP(In-App Purchases)代码实现(下)

iOS 内购IAP(In-App Purchases)代码实现(下)

上次介绍了苹果内购的交易流程,接下来讲讲获取票据信息和防止漏单。

为什么要获取票据信息?票据信息是苹果返回给我们的购买凭证。我们可以拿这个凭证,到苹果服务器上去验证真伪,从而确定是否给用户发放商品。

一般验证票据的工作,要放到服务器上面去做,这样才能确保不会被人破解,造成不必要的损失。

获取票据信息

当系统响应交易队列回调的时候,即paymentQueue: updatedTransactions: 被调用,我们可以获得一个SKPaymentTransaction参数。通过SKPaymentTransaction参数,我们可以获取苹果返回来的票据。

下面方法使用SKPaymentTransaction来获取票据信息:

#pragma mark - 获取票据信息
- (NSData*)receiptWithTransaction:(SKPaymentTransaction*)transaction {
    NSData *receipt = nil;
    if ([[NSBundle mainBundle] respondsToSelector:@selector(appStoreReceiptURL)]) {
        NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
        receipt = [NSData dataWithContentsOfURL:receiptUrl];
    } else {
        if ([transaction respondsToSelector:@selector(transactionReceipt)]) {
            //Works in iOS3 - iOS8, deprected since iOS7, actual deprecated (returns nil) since iOS9
            receipt = [transaction transactionReceipt];
        }
    }
    return receipt;
}

获取票据的方式有两种。一种是直接获取SKPaymentTransaction里面的属性transactionReceipt。这种方式,在iOS7已经废弃了,到iOS9停用。但是为了兼容旧机型,我们还是加上这个方式。

一种是使用[[NSBundle mainBundle] appStoreReceiptURL],这种方式是最新的方式,建议使用。

保存订单信息和票据信息

在拿到票据之后,我们需要将订单信息,和票据一同传到游戏的服务器中。但是在此之前,我们还需要将订单信息和票据信息在客户端保存下来。

因为,如果请求游戏的服务器失败,那么订单和票据将不能顺利抵达游戏服务器。也就是说用户花了钱买的的道具,将不被发放。这样就造成丢单漏单,到时候每天会一大波用户到客服去投诉的。

所以,为了不必要的麻烦,我们在客户端保存一份订单信息,等到确定服务器收到以后,我们再将订单信息,从客户端删除。

获取到的票据,是一个NSData类型。我们现将票据,进行base64编码。

//base64编码
NSString *encodingReceipt = [receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

然后将订单信息和票据,用url参数的格式串起来。

//获取url参数
NSString *urlParas = [NSString stringWithFormat:@"order=%@&receipt=%@" ,order ,encodingReceipt];

然后将这一串字符串,保存下来。先将这一串字符串,放到一个NSArray中,然后再用UserDefaults保存下来。

为什么要放到一个NSArray中?因为假如我们有多个订单没有发送到服务器,那么把他们都加到一个数组中,在合适的时机将他们通通拿出来,来一次统一请求,是不是很方便呢?

连接服务器

这边我们对NSURLConnection进行了简单地封装。也可以使用其他网络框架。

#pragma mark - 连接服务器
- (void)connectServer:(NSString*)urlStr urlParas:(NSString*)urlParas {
    //向服务器发送验证请求
    FYHttpConnection *conn = [[FYHttpConnection alloc] initWithRequest:urlStr postStr:urlParas];
    [conn executeRequest:^(NSHTTPURLResponse *response, NSDictionary *data) {

        SDKLog(@"---response data---%@", data);

        NSNumber *code = data[@"code"];
        if (code.intValue == 0) {
            //交易验证成功,做交易成功处理
            [self.appStoreDelegate sdkAppStorePayComplete:YES];
            //从队列删除订单信息
            [self removeUrlParameters:urlParas];
            SDKLog(@"交易验证成功");
        } else {
            //交易验证失败,做交易失败处理
            [self.appStoreDelegate sdkAppStorePayComplete:NO];
            //从队列删除订单信息
            [self removeUrlParameters:urlParas];
            SDKLog(@"交易验证失败");
        }
    } failure:^(NSHTTPURLResponse *response, NSDictionary *data, NSError *error) {
        //再请求
        [self checkUnchekReceipt];
        SDKLog(@"网络异常");
    }];
}

当请求服务器成功,我们把订单和票据从客户端删除;当请求服务器失败的时候,我们调用[self checkUnchekReceipt]

验证遗漏的票据

#pragma mark - 验证遗漏的票据
- (void)checkUnchekReceipt {
    //取出票据
    NSArray *urlParas = [self loadUrlParameters];
    if ((!urlParas) || (urlParas.count == 0)) {
        return;
    }
    for (NSString *urlPara in urlParas) {
        [self connectServerForUncheckReceipt:urlPara];
    }
}

我们把保存在客户端,未请求成功的订单和票据,一个个拿出来,再请求一遍。

- (void)connectServerForUncheckReceipt:(NSString*)urlPara {
    //向服务器发送验证请求
    FYHttpConnection *conn = [[FYHttpConnection alloc] initWithRequest:SDKAppStoreCheckUrl postStr:urlPara];
    [conn executeRequest:^(NSHTTPURLResponse *response, NSDictionary *data) {

        SDKLog(@"---response data---%@", data);
        NSNumber *code = data[@"code"];
        if (code.intValue == 0) {
            //交易验证成功,做交易成功处理
            //从队列删除订单信息
            [self removeUrlParameters:urlPara];
            SDKLog(@"交易验证成功");
        } else {
            //交易验证失败,做交易失败处理
            //从队列删除订单信息
            [self removeUrlParameters:urlPara];
            SDKLog(@"交易验证失败");
        }
    } failure:^(NSHTTPURLResponse *response, NSDictionary *data, NSError *error) {
        //再请求
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)delayTime * NSEC_PER_SEC);
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [self connectServerForUncheckReceipt:urlPara];
        });
        SDKLog(@"遗漏订单验证网络异常");
    }];
}

connectServerForUncheckReceipt方法和connectServer方法没什么不同。不过在请求失败的时候,会开启一个线程,几分钟以后再请求一次,直到请求成功,再把订单和票据从客户端删掉。

为交易队列添加观察者

这样就万无一失了吗?并不是。当你的app在运行的情况下,一直没有请求成功怎么办?没关系,我们在打开app的时候,也来检查一遍,有没有遗漏的订单。

所以我们在AppDelegateapplication: didFinishLaunchingWithOptions: 方法中添加[[SDKAppStore sharedInstance] checkUnchekReceipt];

有一种情况是,当用户已经把想要交易的商品,加入到交易队列里面了,而paymentQueue: updatedTransactions:却迟迟得不到响应(有时候苹果服务器响应真的很慢)。这时候用户把app关掉了(等得不耐烦了)。所以完蛋了,票据接受不到了。。

不用担心,苹果已经为我们提供了解决方案。我们在app刚打开的时候,就把交易队列的观察者加上。这样,如果系统检查到交易队列中有未完成的交易,就会去调用代码中的paymentQueue: updatedTransactions:方法,以保证我们可以收得到票据。

#pragma mark - 添加交易队列观察者
- (void)addAppStoreObserver {
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

然后我们在AppDelegateapplication:didFinishLaunchingWithOptions:方法中添加[[SDKAppStore sharedInstance] addAppStoreObserver];

总结

1.先要获取商品信息。
2.将商品加到交易队列里面。
3.根据交易队列回调,做相应的事务。
4.获取票据信息。
5.将订单信息和票据信息保存到客户端。
6.请求服务器。如果连接成功,将信息从客户端删除;连接失败,继续请求,直到请求成功。
7.在app刚打开的时候,就添加交易队列观察者。

======2016.11.02更新======

关闭交易

在保证你把订单+票据,传到自家的服务器之后,记得要关闭交易。

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

如果没有关闭交易,系统会自动判定你交易还没完成。也就是说,在你为交易队列添加观察者的情况下,每次打开app,都有可能会继续发起未完成的交易。

服务端验证

在服务端,我们把票据,传给苹果服务器去验证。

发送地址

//测试地址
https://sandbox.itunes.apple.com/verifyReceipt 
//正式地址
https://buy.itunes.apple.com/verifyReceipt 

发送的格式是JSON

{
    "receipt-data":"你的票据"
}

返回的也是一个JSON
这里写图片描述

status表示状态码,0表示成功,其他的表示验证不通过
这里写图片描述

特别说明,21007那个状态码,表示你是在测试环境中取的票据,但是却到正式地址去验证。

利用这个,我们就不用两个地址间来回切换了。我们每次先到正式地址去验证,如果返回21007,我们就再到测试地址验证一次。

详情请看:IAP票据验证

返回的信息里面有一个receipt字段,也是JSON格式,包含了你票据的信息。

特别说明的是,receipt里面的in_app字段,这个字段包含了所有你未完成交易的票据信息。也就是在上节说到的关闭交易之后,这个票据信息,就会从in_app中消失。如果不关闭交易,这个票据信息就会在in_app中一直保留。(这个情况可能仅限于你的商品类型为消耗型)

如果你需要当前票据的唯一号,取in_app中最后一个票据的transaction_id就行。

详情请看:receipt各字段含义

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值