iOS内购遇到问题记录(未完待续)

网上有很多内购教程,推荐几篇比较写的比较好的
文章一: iOS内购(IAP,In App Purchases-在APP内部支付),设置及使用
文章二: iOS开发内购全套图文教程,这篇文章比较老,但是下面的评论可以看看,很有价值
文章三: iOS 内购2017.4

1、内购集成成功,但是请求到的商品数是0
// 获取商品的查询结果
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response

打印商品的数量response.products.count结果一直是零,打印response.invalidProductIdentifiers发现商品是无效状态
解决方法:

可能出现的商品列表为0的情况:

  • 1.银行信息未填写完整。
  • 2.Bundle ID 不统一, bundleID要与iTunes Connect上你App的相同,不然是请求不到产品信息的
2、购买消耗性产品后, 再次购买会提示您已购买此APP内购项目,此项目将免费恢复
再次购买曾购买过的商品

解决方法:

监听购买结果,当失败和成功时代码中要调用如下代码:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
该方法通知苹果支付队列该交易已完成,不然就会已发起相同 ID 的商品购买就会有此项目将免费恢复的提示。

3、每次重新进入选购商品页面,都会提示之前购买商品后的成功或者失败提示即再次运行-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions这个方法

解决方法:

在购买成功或者失败后,没有调用[[SKPaymentQueue defaultQueue] finishTransaction:transaction];告诉苹果支付队列,改交易已经完成,
在购买成功或者失败后,加上这句代码

4、注意事项

在沙盒环境下真机测试内购时,请去app store中注销你的apple ID,不然发起支付购买请求后会直接case:SKPaymentTransactionStateFailed。使用沙盒测试员的账号时不需要真正花钱的。

5、校验票据

票据的校验是保证内购安全完成的非常关键的一步,一般有三种方式:
1、服务器验证,获取票据信息后上传至信任的服务器,由服务器完成与App Store的验证(提倡使用此方法,比较安全)
2、本地票据校验
3、本地App Store请求验证

a)、本地票据校验

票据验证部分部分可参考此文章

本地票据校验的一般步骤
要验证收据,请按顺序执行以下测试:
1.找到收据。
如果没有收据,则验证失败。
2.验证收据是否由Apple 正确签署
如果收据不是由 Apple 签署,则验证失败。
3.验证收据中的Bundle Identifier(数据包标识符)与在Info.plist文件中含有您要的CFBundleIdentifier值的硬编码常量相匹配。
如果两者不匹配,则验证失败。
4.验证收据中的版本标识符字符串与在Info.plist文件中含有您要的CFBundleShortVersionString值(macOS)或
CFBundleVersion 值(iOS)的硬编码常量相匹配。
如果两者不匹配,则验证失败。
5.按照“计算 GUID 的哈希(Hash)(第 8 页)”所述计算GUID 的哈希(Hash)。
如果结果与收据中的哈希(Hash)不匹配,则验证失败。
如果通过所有测试,则验证成功。

注意: Bundle Identifier(数据包标识符)和版本标识符字符串是UTF-8 字符串,而不仅仅是一系列字节。确保您相应地编写比较逻辑代码。

如果您的 App 支持“批量购买计划”,请检查收据的有效日期。

b)、App Store验证:
// 14.交易成功,与服务器比对
// 交易结束,当交易结束后还要去appstore上验证支付信息是否都正确,只有所有都正确后,我们就可以给用户发放我们的虚拟物品了。
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{

    NSString * str=[[NSString alloc]initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding];
    NSString *environment = [self environmentForReceipt:str]; // 判断当前的环境(正式环境还是测试环境)
    NSLog(@"environment:%@",environment);
    
    // 验证凭据,获取到苹果返回的交易凭据
    // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址,目前苹果公司提倡的获取购买凭证的方法
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    // 从沙盒中获取到购买的票据信息
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    /**
        BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
        BASE64是可以编码和解码的
        base64位的产品验证码单,base64是服务端和苹果进行校验所必须的,苹果的文档要求凭证经过Base64加密
    */
    NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
    NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr];

    NSURL *StoreURL=nil;
    // 沙盒状态下使用:https://sandbox.itunes.apple.com/verifyReceipt来验证
    // 生产环境下使用:https://buy.itunes.apple.com/verifyReceipt
    if ([environment isEqualToString:@"environment = Sandbox"]) {
        
        StoreURL= [[NSURL alloc] initWithString: @"https://sandbox.itunes.apple.com/verifyReceipt"];
    }
    else{
        
        StoreURL= [[NSURL alloc] initWithString: @"https://buy.itunes.apple.com/verifyReceipt"];
    }
    
    //这个二进制数据由服务器进行验证;zl
    NSData *postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]];

    NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:StoreURL];
    
    [connectionRequest setHTTPMethod:@"POST"];
    [connectionRequest setTimeoutInterval:50.0];//120.0---50.0zl
    [connectionRequest setCachePolicy:NSURLRequestUseProtocolCachePolicy];
    [connectionRequest setHTTPBody:postData];

    //开始请求
    NSError *error=nil;
    NSData *responseData=[NSURLConnection sendSynchronousRequest:connectionRequest returningResponse:nil error:&error];
    if (error) {
        NSLog(@"验证购买过程中发生错误,错误信息:%@",error.localizedDescription);
        return;
    }
    NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];

    /*
     *  这里可以等待上面请求的数据完成后并且state = 0 验证凭据成功来判断后进入自己服务器逻辑的判断,也可以直接进行服务器逻辑的判断,验证凭据也就是一个安全的问题。楼主这里没有用state = 0 来判断。
     *  完整结束此次在App Store的交易,没有这句代码的调用,下次购买会提示已经购买该商品
     *  [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
     */
    NSLog(@"请求成功后的数据:%@",dic);
    
    NSString *product = transaction.payment.productIdentifier;
    
    NSLog(@"+++ transaction.payment.productIdentifier:%@",product);
    
    if ([product length] > 0)
    {
        NSArray *tt = [product componentsSeparatedByString:@"."];
        
        NSString *bookid = [tt lastObject];
        
        if([bookid length] > 0)
        {
            
            NSLog(@"打印bookid%@",bookid);
            //这里可以做操作吧用户对应的虚拟物品通过自己服务器进行下发操作,或者在这里通过判断得到用户将会得到多少虚拟物品,在后面([self getApplePayDataToServerRequsetWith:transaction];的地方)上传上面自己的服务器。
        }
    }
    //此方法为将这一次操作上传给我本地服务器,记得在上传成功过后一定要记得销毁本次操作。调用[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
//    [self getApplePayDataToServerRequsetWith:transaction];

    
}

-(NSString *)environmentForReceipt:(NSString *)str
{
    str = [str stringByReplacingOccurrencesOfString:@"\r\n" withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@"\n" withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@"\t" withString:@""];
    str = [str stringByReplacingOccurrencesOfString: @"" withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@"\"" withString:@""];
    NSArray *arr = [str componentsSeparatedByString:@";"];
    
    // 存储收据环境的变量
    if (arr.count > 2) {
        NSString *environment = arr[2];
        return environment;
    }
    
    return nil;
  
}

注意:

获取到票据以后我们通过App Store来验证票据是否真实
沙盒状态下使用:https://sandbox.itunes.apple.com/verifyReceipt来验证
生产环境下使用:https://buy.itunes.apple.com/verifyReceipt
附常见的验证状态代码:

状态码

如果此时未获取到票据的信息,使用SKReceiptRefreshRequest来刷新票据结果。

SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}];
refreshReceiptRequest.delegate = self;
[refreshReceiptRequest start];
6、漏单问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 最近有个项目客户总是反应掉单,于是乎就看了看内购相关的东西,发现坑还真是不少,这里做个总结。 IAP,即in-Ap...
    糖炒0栗子阅读 18,255评论 0 22
  • 自己开发的视频直播项目,牵涉到充值金币,用到了苹果公司的内购,趴坑了两天,这里总结下实现苹果内购。 一. 创建测试...
    Leo丶Dicaprio阅读 3,453评论 8 7
  • iOS应用如果涉及到支付功能,分为两类:第三方支付和苹果内购。那么什么情况下选择使用第三方支付,又在什么情况下选择...
    ZfRee阅读 39,039评论 36 66
  • 每个优秀的人,并不是与生俱来就带着光环,也不一定是比别人幸运。他们只是在任何一件小事上,都对自己有所要求,不因舒适...
    圆圆的兔兔阅读 143评论 0 0
  • 慵懒是罪,却总被无罪释放。 每天醒来,总觉得后背像是与床缝在了一起,感觉腰肢失去了脊梁骨,撑不起疲软的身躯。一个翻...
    景漓脩阅读 273评论 0 0