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内购项目,此项目将免费恢复iOS内购遇到问题记录(未完待续)
文章图片
再次购买曾购买过的商品
解决方法:
监听购买结果,当失败和成功时代码中要调用如下代码:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
该方法通知苹果支付队列该交易已完成,不然就会已发起相同 ID 的商品购买就会有此项目将免费恢复的提示。
3、每次重新进入选购商品页面,都会提示之前购买商品后的成功或者失败提示即再次运行-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)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 = https://www.it610.com/article/[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 = https://www.it610.com/article/[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=https://www.it610.com/article/[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
附常见的验证状态代码:

iOS内购遇到问题记录(未完待续)
文章图片
状态码
【iOS内购遇到问题记录(未完待续)】如果此时未获取到票据的信息,使用SKReceiptRefreshRequest来刷新票据结果。
SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}]; refreshReceiptRequest.delegate = self; [refreshReceiptRequest start];

6、漏单问题

    推荐阅读