1、内购流程

  • 1、在 AppStore 中创建相应的物品,创建内购沙盒测试账号
  • 2、客户端从后台获取相应的物品 ID (当然也可以再客户端写死,但后期扩展性就受限制了)
  • 3、依据相应的物品 ID 请求商品的相关信息
  • 4、依据商品信息创建订单请求交易
  • 5、依据返回的订单状态处理交易结果
  • 6、请求后台再次验证订单状态
  • 7、依据后台返回结果处理相关逻辑

2、创建内购物品以及沙盒测试账号

  • 已经有朋友写出了完善的教程,请参考如下链接,一步一步来就可以

    iOS开发内购全套图文教程 
    iOS应用程序内购/内付费(一)

3、客户端编写相关代码

  • 再这里我把和支付相关的逻辑都抽取到了一个单例中,在最后贴上个人梳理的相关代码大家一起学习

4、做内购过程中遇到的坑


  • 1、内购沙盒测试账号在支付成功后,再次购买相同 ID 的物品,会提示如下内容的弹窗。

    您已购买此 App 内购买项目。此项目将免费恢复


    解决方法:在使用

    [[SKPaymentQueue defaultQueue] addPayment:payment];

    将支付信息添加进苹果的支付队列后,苹果会自动完成后续的购买请求,在用户购买成功或者点击取消购买的选项后回调

    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction;

    方法返回响应的结果信息,在该方法内除了得到响应的支付信息编写自身的业务的代码外还要记得调用

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

    方法通知苹果的支付队列该交易已经完成,否者就会已发起相同 ID 的商品购买就会有此项目将免费恢复的提示。


  • 2、每次启动一个新的内购支付流程,刚发起的时候系统就会调用- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction;这个方法,结果扰乱一部分的支付业务逻辑


    在 SKPaymentQueue 被启动并且添加了 addObserver之后,如果其判断到有未完成的交易,会主动调用paymentQueue updatedTransactions 这个方法来继续完成相关的交易流程,所以如果在上面那种情况下得到结果后不去调用 finish 接口,下次重新开启支付流程就会检查未完成的支付并调用该接口。

    解决方法:

    1.在得到支付结果后及时调用 finish 方法

    2.添加一个是否是新发起的支付流程的条件,在条件符合的情况下才触发应用的相关逻辑的代码

    (PS:在拿到苹果的支付结果凭据的时候最好在客户端做一份持久化的数据备份,等待后台验证完成后再清除掉,避免出现验证中间出现问题导致用户支付成功但后台相关的增值处理没有完成导致用户金钱损失的问题)


  • 3、如何区分购买物品的是 沙盒测试账号 还是 真实账号 


    后台再验证支付凭据的时候要区分是沙盒测试账号购买的还是用户真实账号购买的,所以在传凭据的时候还需要告诉后台当前购买的账号性质。

    解决方法:通过在配置文件中定义相关的宏定义并结合 Debug 与 Release 的编译环境确定相关的参数

    [objc] view plaincopyprint?
    1. // 苹果内购是否为沙盒测试账号,打开就代表为沙盒测试账号,注意上线时注释掉
    2. #define APPSTORE_ASK_TO_BUY_IN_SANDBOX 1
    3. // 生成订单参数,注意沙盒测试账号与线上正式苹果账号的验证途径不一样,要给后台标明
    4. NSNumber *sandbox;
    5. #if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
    6. sandbox = @(0);
    7. #else
    8. sandbox = @(1);
    9. #endif

    个人没有找到相关的方法可以打完包后动态的检测购买物品的账号性质,希望知道的朋友分享一下,感谢 ^_^


5、iOS7 客户端验证的订单状态

  • 苹果在iOS7提升了购买凭据的安全性,可以直接单独在客户端完成订单正确性的验证,但是处于金钱考虑,购买完成后,建议还是要做凭据的后台验证工作。

    [objc] view plaincopyprint?
    1. #pragma mark 客户端验证购买凭据
    2. - (void)verifyTransactionResult
    3. {
    4. // 验证凭据,获取到苹果返回的交易凭据
    5. // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址
    6. NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    7. // 从沙盒中获取到购买凭据
    8. NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    9. // 传输的是BASE64编码的字符串
    10. /**
    11. BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性
    12. BASE64是可以编码和解码的
    13. */
    14. NSDictionary *requestContents = @{
    15. @"receipt-data": [receipt base64EncodedStringWithOptions:0]
    16. };
    17. NSError *error;
    18. // 转换为 JSON 格式
    19. NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
    20. options:0
    21. error:&error];
    22. // 不存在
    23. if (!requestData) { /* ... Handle error ... */ }
    24. // 发送网络POST请求,对购买凭据进行验证
    25. NSString *verifyUrlString;
    26. #if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
    27. verifyUrlString = @"https://sandbox.itunes.apple.com/verifyReceipt";
    28. #else
    29. verifyUrlString = @"https://buy.itunes.apple.com/verifyReceipt";
    30. #endif
    31. // 国内访问苹果服务器比较慢,timeoutInterval 需要长一点
    32. NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[[NSURL alloc] initWithString:verifyUrlString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f];
    33. [storeRequest setHTTPMethod:@"POST"];
    34. [storeRequest setHTTPBody:requestData];
    35. // 在后台对列中提交验证请求,并获得官方的验证JSON结果
    36. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    37. [NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
    38. completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    39. if (connectionError) {
    40. NSLog(@"链接失败");
    41. } else {
    42. NSError *error;
    43. NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
    44. if (!jsonResponse) {
    45. NSLog(@"验证失败");
    46. }
    47. // 比对 jsonResponse 中以下信息基本上可以保证数据安全
    48. /*
    49. bundle_id
    50. application_version
    51. product_id
    52. transaction_id
    53. */
    54. NSLog(@"验证成功");
    55. }
    56. }];
    57. }

6、内购验证凭据返回结果状态码说明

  • 苹果反馈的状态码:

    [objc] view plaincopyprint?
    1. 21000 App Store无法读取你提供的JSON数据
    2. 21002 收据数据不符合格式
    3. 21003 收据无法被验证
    4. 21004 你提供的共享密钥和账户的共享密钥不一致
    5. 21005 收据服务器当前不可用
    6. 21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
    7. 21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
    8. 21008 收据信息是产品环境中使用,但却被发送到测试环境中验证

更为详细的信息请参考

ios 应用内支付(In-App Purchase,沙盒测试,后台验证)

【IOS一气呵成】之IAP集成:内购和内购恢复

另附:苹果官网内购 API 链接

7、如何恢复购买

  • 注:此部分内容后期再详细添加 ^_^

备注:

相关代码:

XYPayManager.h

[objc] view plaincopyprint?
  1. //
  2. //  XYPayManager.h
  3. //  xingyun
  4. //
  5. //  Created by 郑亚恒 on 15/11/2.
  6. //  Copyright © 2015年 郑亚恒. All rights reserved.
  7. //
  8. #import <Foundation/Foundation.h>
  9. // 苹果内购是否为沙盒测试账号,打开就代表为沙盒测试账号,注意上线时注释掉!!
  10. #define APPSTORE_ASK_TO_BUY_IN_SANDBOX 1
  11. typedef void(^payCompleteBlock)(NSDictionary *resultDic, BOOL isSuccess);
  12. @interface XYPayManager : NSObject
  13. + (instancetype)sharedPayManager;
  14. /// 苹果内购
  15. - (void)requestAppleStoreProductDataWithString:(NSString *)productIdentifier payComplete:(payCompleteBlock)payCompletionBlock;
  16. /// 验证苹果支付订单凭证
  17. - (void)checkAppStorePayResultWithBase64String:(NSString *)base64String;
  18. @end

XYPayManager.m

[objc] view plaincopyprint?
  1. //
  2. //  XYPayManager.m
  3. //  xingyun
  4. //
  5. //  Created by 郑亚恒 on 15/11/2.
  6. //  Copyright © 2015年 郑亚恒. All rights reserved.
  7. //
  8. #import "XYPayManager.h"
  9. #import <StoreKit/StoreKit.h>
  10. @interface XYPayManager() <SKPaymentTransactionObserver, SKProductsRequestDelegate>
  11. // 苹果内购
  12. @property (nonatomic, copy) NSString *appleProductIdentifier;
  13. @property (nonatomic, copy) payCompleteBlock payComplete;
  14. @end
  15. @implementation XYPayManager
  16. + (instancetype)sharedPayManager {
  17. static XYPayManager *payManager;
  18. static dispatch_once_t once = 0;
  19. dispatch_once(&once, ^{
  20. payManager = [[XYPayManager alloc] init];
  21. // 注册苹果内购
  22. [[SKPaymentQueue defaultQueue] addTransactionObserver:payManager];
  23. });
  24. return payManager;
  25. }
  26. - (void)dealloc {
  27. [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
  28. }
  29. #pragma mark - 苹果支付充值
  30. //请求商品
  31. - (void)requestAppleStoreProductDataWithString:(NSString *)productIdentifier payComplete:(payCompleteBlock)payCompletionBlock {
  32. if(![SKPaymentQueue canMakePayments]) {
  33. NSLog(@"不允许程序内付费");
  34. //        [APPCONTEXT.hudHelper showHudOnWindow:@"不允许程序内付费" image:nil acitivity:NO autoHideTime:DEFAULTTIME];
  35. return;
  36. }
  37. NSLog(@"-------------请求对应的产品信息----------------");
  38. self.startBuyAppleProduct = YES;
  39. self.payComplete = payCompletionBlock;
  40. self.appleProductIdentifier = productIdentifier;
  41. NSLog(@"生成产品信息");
  42. NSArray *product = [[NSArray alloc] initWithObjects:productIdentifier, nil nil];
  43. NSSet *nsset = [NSSet setWithArray:product];
  44. SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
  45. request.delegate = self;
  46. [request start];
  47. }
  48. //收到产品返回信息
  49. - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
  50. NSLog(@"--------------收到产品反馈消息---------------------");
  51. NSArray *productArray = response.products;
  52. if([productArray count] == 0){
  53. NSLog(@"--------------没有商品------------------");
  54. return;
  55. }
  56. NSLog(@"productID:%@", response.invalidProductIdentifiers);
  57. NSLog(@"产品付费数量:%lu",(unsigned long)[productArray count]);
  58. SKProduct *product = nil;
  59. for (SKProduct *pro in productArray) {
  60. NSLog(@"%@", [pro description]);
  61. NSLog(@"%@", [pro localizedTitle]);
  62. NSLog(@"%@", [pro localizedDescription]);
  63. NSLog(@"%@", [pro price]);
  64. NSLog(@"%@", [pro productIdentifier]);
  65. if([pro.productIdentifier isEqualToString:self.appleProductIdentifier]){
  66. product = pro;
  67. }
  68. }
  69. SKPayment *payment = [SKPayment paymentWithProduct:product];
  70. NSLog(@"发送购买请求");
  71. [[SKPaymentQueue defaultQueue] addPayment:payment];
  72. }
  73. //请求失败
  74. - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
  75. NSLog(@"------------------错误-----------------:%@", error);
  76. }
  77. - (void)requestDidFinish:(SKRequest *)request{
  78. NSLog(@"------------反馈信息结束-----------------");
  79. }
  80. //监听购买结果
  81. - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction {
  82. for(SKPaymentTransaction *paymentTransactionp in transaction){
  83. switch (paymentTransactionp.transactionState) {
  84. case SKPaymentTransactionStatePurchased:
  85. {
  86. NSLog(@"交易完成-restoreCompletedTransactions");
  87. /* your code */
  88. [self buyAppleStoreProductSucceedWithPaymentTransactionp:paymentTransactionp];
  89. [self completeTransaction:paymentTransactionp];
  90. }
  91. break;
  92. case SKPaymentTransactionStatePurchasing:
  93. NSLog(@"商品添加进列表");
  94. break;
  95. case SKPaymentTransactionStateRestored:
  96. NSLog(@"已经购买过商品");
  97. break;
  98. case SKPaymentTransactionStateFailed:
  99. {
  100. NSLog(@"交易失败");
  101. /* your code */
  102. [self completeTransaction:paymentTransactionp];
  103. }
  104. break;
  105. }
  106. }
  107. }
  108. // 苹果内购支付成功
  109. - (void)buyAppleStoreProductSucceedWithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp {
  110. /* 获取相应的凭据,并做 base64 编码处理 */
  111. NSString *base64Str = [paymentTransactionp.transactionReceipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
  112. NSLog(@"苹果内购凭据号\n\n\n\n\n\n%@\n\n\n\n\n\n",base64Str);
  113. [self checkAppStorePayResultWithBase64String:base64Str];
  114. }
  115. - (void)checkAppStorePayResultWithBase64String:(NSString *)base64String {
  116. /* 生成订单参数,注意沙盒测试账号与线上正式苹果账号的验证途径不一样,要给后台标明 */
  117. NSNumber *sandbox;
  118. #if (defined(APPSTORE_ASK_TO_BUY_IN_SANDBOX) && defined(DEBUG))
  119. sandbox = @(0);
  120. #else
  121. sandbox = @(1);
  122. #endif
  123. NSMutableDictionary *prgam = [[NSMutableDictionary alloc] init];;
  124. [prgam setValue:sandbox forKey:@"sandbox"];
  125. [prgam setValue:base64String forKey:@"reciept"];
  126. /*
  127. 请求后台接口,服务器处验证是否支付成功,依据返回结果做相应逻辑处理
  128. */
  129. }
  130. //交易结束
  131. - (void)completeTransaction:(SKPaymentTransaction *)transaction{
  132. NSLog(@"交易结束");
  133. [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
  134. }
  135. @end

iOS开发之内购完全笔记(您已购买此 App 内购买项目。此项目将免费恢复。)相关推荐

  1. IOS开发之内购-AppStore

    iOS开发之内购-AppStore AppStore 内购 支付 iOS开发 前言 本文会给大家详细介绍iOS内购,虽然之前网上也有内购的教程,但是还不够详细,我重新整理出一份教程,希望对大家有所帮助 ...

  2. iOS 开发之内购 – AppStore

    来源:Yi'mouleng(@丶伊眸冷) 链接:http://t.cn/R4L0rgA 前言 本文会给大家详细介绍iOS内购,虽然之前网上也有内购的教程,但是还不够详细,我重新整理出一份教程,希望对大 ...

  3. iOS 内购提示不允许App内购买项目,打开内购方式和检测不允许内购的方法。

    这个问题是屏幕使用时间里面关闭了内购功能 打开方法如下 设置->屏幕使用时间->内容和隐私访问限制->iTunes Store与App Store购买项目->APP内购买项目- ...

  4. 2018版苹果开发者设置内购(App内购买项目)、税务、银行问题,开通苹果支付

    项目中使用到了中间货币(金币)的形式来进行功能使用,模式是使用RMB换成-金币比如:(1RMB = 10金币),所以会集成第三方的支付平台,使用了微信和支付宝的第三方平台过后,发现审核失败,被苹果拒绝 ...

  5. iOS 上 App 内购买(in-app purchase)的误消费可以退款吗?

    iOS 上 App 内购买(in-app purchase)的误消费可以退款吗?修改 周二晚按照@任轶 的答案做了,把购物清单的四个订单号列在内容里,当晚收到了苹果公司自动回复,周三受到苹果公司团队中 ...

  6. IOS 开发高手课 学习笔记(第三部分)

    这一部分戴铭老师说的是一些底层的东西,然后推荐了一下第三方库,但是看同学者的反应,很多内容(主要是推荐的第三方库)都不是很完善(不能完全接受),所以这部分内容,要谨慎对待,然后自己实践和查询足够的资料 ...

  7. (0009) iOS 开发之友盟统计分析SDK已全面支持HTTPS的更新

    iOS开发技术分享群 147787076 [友盟+]友盟统计分析SDK已全面支持HTTPS,请IOS开发者及时升级文档(http://dev.umeng.com/analytics/ios-doc/i ...

  8. IOS 开发高手课 学习笔记(第一部分)

    年前购买了三门课程,Android 开发高手课.IOS开发高手课与从0开始学架构,利用工作的空余时间匆匆忙忙的,快6个月了,终于把前两门课程都过了一遍,也把其中一些遗漏的知识补充了,可以开始看第二遍, ...

  9. 《iOS开发进阶》读书笔记

    相关代码:https://github.com/tangqiaoboy/iOS-Pro 一.iOS开发工具 1.类似java的maven,iOS中管理第三方库的工具cocoapods 2.网络封包分析 ...

最新文章

  1. 使用TextRank算法为文本生成关键字和摘要
  2. Ice笔记-利用Ice::Application类简化Ice应用
  3. 我的WCF之旅(1):创建一个简单的WCF程序
  4. Try to create new xs project in AG3
  5. html代码里面换图片不显示不出来了,微擎 Ueditor 百度编辑器 替换图片不显示问题...
  6. piblog 0.1
  7. 《锋利的jQuery》要点归纳(五)jQuery与ajax的应用(上)
  8. leetcode 1202 python
  9. 从服务器断开并删除套接字
  10. AI复活明朝历代皇帝,来一场穿越时空的对话!
  11. python jdict_jdict python中的javascript dict
  12. Final Cut Pro X Guru: Advanced Trimming Final Cut Pro X Guru:高级修剪 Lynda课程中文字幕
  13. 休闲平台,何去何从?(1)
  14. wallpaper代码_70 行 python 代码实现壁纸批量下载!
  15. 为什么青少年一定要学Python?
  16. Flutter学习指南:UI布局和控件,作为Android开发者
  17. 好嗨游戏:LPL春季赛决赛在即,黑8传奇JDG迎战S8冠军IG
  18. 【node.js】识别图片中的文字
  19. 【深度学习】【积分梯度】深度网络的公理归因(Axiomatic Attribution for Deep Networks)
  20. 微信小程序服务商模式支付巨坑解决!

热门文章

  1. git错误:exceeds file size limit of 100.0 MB remote: error: hook declined to update refs/heads/master
  2. 高德地图的基础使用(一)显示地图
  3. 王者荣耀主播(孤王)过度劳累猝死 曾被誉为国服第一辅助
  4. Win10系统补丁卸载工具
  5. 程序员遇到人生低谷期怎么做?
  6. ajax异步请求刷新
  7. 我的推荐系统入门经验~
  8. 同样是IT行业,测试和开发薪资真有这么大差别?
  9. 仿微信控制输入法的显示和隐藏
  10. matplotlib basemap 绘制多边形区域曲线