iOS 内购(In-App Purchase)总结

随笔9个月前发布 兰兰香花飘
95 0 0

一、简单介绍

iOS 内购即(In-App Purchase)一共分为四种类型:(详细文档参考官网)

  1. 消耗型商品:只可使用一次的产品,使用之后即失效,必须再次购买。 示例:钓鱼 App 中的鱼食。

  2. 非消耗型商品:只需购买一次,不会过期或随着使用而减少的产品。 示例:游戏 App 的赛道。

  3. 自动续期订阅:允许用户在固定时间段内购买动态内容的产品。除非用户选择取消,否则此类订阅会自动续期。 示例:每月订阅提供流媒体服务的 App

  4. 非续期订阅:允许用户购买有时限性服务的产品。此 App 内购买项目的内容可以是静态的。此类订阅不会自动续期。 示例:为期一年的已归档文章目录订阅。

只要在 iOS/iPadOS 设备上的 App 里购买非实物产品 (也就是虚拟产品,如:“金币、qq 币、鱼翅、电子书……”) ,都需要走内购流程,苹果从这里面抽走 30% 分成,实际结算是分成之前还需要先扣除交易税

二、内购前准备

APP内集成IAP代码之前需要先去开发账号的ITunes Connect进行以下三步操作(可参考该配置文档):

  1. 后台填写银行账户信息

  2. 配置商品信息,包括产品ID,产品价格等

这里需要注意的是产品 ID 具有唯一性,建议使用项目的 Bundle Identidier 作为前缀后面拼接自定义的唯一的商品名或者 ID(字母、数字),这里有个坑:一旦新建一个内购商品,它的产品ID将永远被占用,即使该商品已经被删除,已创建的内购商品除了产品 ID 之外的所有信息都可以修改,如果删除了一个内购商品,将无法再创建一个相同产品 ID 的商品,也意味着该产品 ID 永久失效,一般来说产品ID有特定的命名规则,如果命名规则下有某个产品 ID 永久失效,可能会导致整个产品ID命名规则都要修改,这里千万要注意!

  1. 配置用于测试IAP支付功能的沙箱账户。

三、内购流程

内购通用流程

iOS 内购(In-App Purchase)总结

  1. 用户向苹果服务器发起购买请求,收到购买完成的回调(购买完成后会把钱打给申请内购的银行卡内)

  2. 购买成功流程结束后, 向服务器发起验证凭证(app端自己也可以不依靠服务器自行验证)

  3. 自己的服务器工作分 4 步:
    3.1 接收 iOS 端发过来的购买凭证。
    3.2 判断凭证是否已经存在或验证过,然后存储该凭证。
    3.3 将该凭证发送到苹果的服务器(区分沙盒环境还是正式环境)验证,并将验证结果返回给客户端(注意需要传密钥,要不会报错21003)
    sandbox 开发环境: https://sandbox.itunes.apple.com/verifyReceipt
    prod 生产环境: https://buy.itunes.apple.com/verifyReceipt 具体操作可以看这个 通过App Store验证收据。
    3.4 修改用户相应的会员权限或发放虚拟物品。

简单来说就是将该购买凭证用 Base64 编码,然后 POST 给苹果的验证服务器,苹果将验证结果以 JSON 形式返回

iOS 内购(In-App Purchase)总结

恢复购买

内购有4种:消耗型项目,非消耗型,自动续期订阅,非续期订阅。 其中”非消耗型“和”自动续期订阅“需要提供恢复购买的功能,例如创建一个恢复按钮,不然审核很可能会被拒绝。

//调起苹果内购恢复接口[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

“消耗型项目”和“非续期订阅”苹果不会提供恢复的接口,不要调用上述方法去恢复,否则有可能被拒!!!

“非续期订阅”也是跨设备同步的,所以原则上来说也需要提供恢复购买的功能,但需要依靠app自建的账户体系恢复,不能用上述苹果提供的接口。

四、常见错误

获取不到商品信息:检查itc商品审核以及下线情况,检查设备知否支持内购

无法连接: 网络问题,切记沙盒环境下不可开vpn或者网络代理

iOS 内购(In-App Purchase)总结

五、注意事项

  1. 订阅产品需要验证订阅是否过期,自动续费在购买流程上,与普通购买没有区别,主要的区别:”除了第一次购买行为是用户主动触发的,后续续费都是 Apple 自动完成的,一般在要过期的前24小时开始,苹果会尝试扣费,扣费成功的话,在 App 下次启动的时候主动推送给 App“。

// 订阅特殊处理
if (transaction.originalTransaction) {  
    // 如果是自动续费的订单 originalTransaction 会有内容 
    } else {
    // 普通购买,以及第一次购买自动订阅
    }

  1. 沙盒账号在创建时就已经设置好了地区,中国的只能在中国的 App Store 测试,否则会提示不在此地区,请切回本地的应用商店

  2. 关于掉单的问题 答案:一定要在服务器校验完票据后,客户端收到服务器的反馈结果后再: [[SKPaymentQueue defaultQueue] finishTransaction: transaction];

  3. 自动订阅时间在沙盒环境下时间会缩短

iOS 内购(In-App Purchase)总结

六、自己本地验证收据

有时候可能需要自己本地验证收据来调试一些问题,可以参考以下代码,本地调用https://sandbox.itunes.apple.com/verifyReceipt去验证,传参记得传递密码(即共享密钥),成功后会收到苹果返回的数据

然后对于连续订阅型:

1.第一次对账

遍历latest_receipt_info里的票据,如果transactionIdentifier相同,那么本次交易就是有效的,找到对应的票据后,其中最关键的是expires_date_ms这个时间戳,这个和expires_date字符串不相符, expires_date是GMT时间,少8个小时,而expires_date_ms转换后是正常的,因此不用把这个时间戳再加8小时.

2.之后的对账

由于苹果会在到期前充值,充值失败也会有回调通知,所以当数据库中的时间到期后,再去用之前存的凭据调用对账接口,

然后遍历latest_receipt_info根据product_id,也就是商品id,把连续订阅的票据筛选出来

现在筛选出来的这些就是所有的此商品的订阅票据了,而且顺序是按照时间排序的,新的在后面,如果不放心,可以自己根据里面的expires_date_ms(或者其他时间戳)再排序,然后创建订单,更新过期时间等等操作

-(void)verifyFinishedTransaction:(SKPaymentTransaction *)transaction{
    NSString *str = [[NSString alloc] initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];

    NSString *environment = [self environmentForReceipt:str];
    NSLog(@"------ 完成交易调用的方法completedTransaction 1----------%@", environment);

    // 验证凭据,获取到苹果返回的交易凭据
    // appStoreReceiptURL iOS 7.0 增加的,购买交易完成后,会将凭据存放在该地址
    NSURL *url = [[NSBundle mainBundle] appStoreReceiptURL];
    NSString *receipt = [[NSData dataWithContentsOfURL:url] jk_base64EncodedString];
    [self log:[NSString stringWithFormat:@"sendString %@", receipt]];

    /** 注意:这里可以不用自己去验证,直接调用自己服务器接口,让后台去APP Store 验证*/

    NSURL *storeUrl = nil;
    storeUrl = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];

    NSDictionary *requestContents = @{
            @"receipt-data": receipt,
            @"password":@"共享密钥",
            @"exclude-old-transactions":@(YES)
    };
    NSError *error;
    // 转换为 JSON 格式
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                          options:0
                                                            error:&error];
    NSString *verifyUrlString = @"https://sandbox.itunes.apple.com/verifyReceipt";
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[[NSURL alloc] initWithString:verifyUrlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];

    // 在后台对列中提交验证请求,并获得官方的验证JSON结果
    NSURLSession *session = [NSURLSession sharedSession];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"链接失败");
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            [self log:[NSString stringWithFormat:@"verifyFinishedTransaction error%@", error]];
        } else {
            NSError *error;
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
            if (!jsonResponse) {
                NSLog(@"验证失败");
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            }
            [self log:[NSString stringWithFormat:@"verifyFinishedTransaction %@", jsonResponse]];

            NSLog(@"验证成功");
            //TODO:取这个json的数据去判断,道具是否下发
        }
    }];
    [task resume];
}

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...