这段时间做了苹果内购IAP,做一个整理记录,主要是开发层面。

一.前期工作:在开发者账号中添加银行信息同意协议等,添加沙盒账号,添加内购商品

二.项目开发,因为项目需要支持iOS15一下的版本所以使用旧版StoreKit,新版的StoreKit2只支持iOS15以上,新的nsync同步接口。

1.获取内购商品信息,可以在自己服务器中获取商品的productId数组,根据product ID 获取价格等具体商品信息,用于显示给用户,如果商品信息不经常变化,可以把结果缓存起来,不获取商品信息也可以发起内购不影响购买

private var productFetchCallbacks = [SKProductsRequest: ([SKProduct]) -> Void]()public func fetchProductsInfo(_ productIDs: [String],completion:@escaping ([SKProduct]) -> Void) {let set = Set<String>.init(productIDs)let request = SKProductsRequest.init(productIdentifiers: set)productFetchCallbacks[request] = completionrequest.delegate = selfrequest.start()}
//代理回调结果,注意代理回调结果是在多线程中,根据需求是否要切换成主线程
// MARK: SKProductsRequestDelegate
extension MXLiveIAPPayment : SKProductsRequestDelegate{public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {guard let callback = productFetchCallbacks[request] else { return }productFetchCallbacks[request] = nilDispatchQueue.main.async {callback(response.products)}}public func request(_ request: SKRequest, didFailWithError error: Error) {print(error.localizedDescription)if let productsFetchRequest = request as? SKProductsRequest {guard let callback = productFetchCallbacks[productsFetchRequest] else { return }productFetchCallbacks[productsFetchRequest] = nilDispatchQueue.main.async {callback([])}}}
//    public func requestDidFinish(_ request: SKRequest) {
//        print(request)
//    }
}

2.设置代理发起内购,

 let payment = SKMutablePayment()payment.quantity = 1payment.applicationUsername = currentOrder?.uuidpayment.productIdentifier = order.productIdpayment.simulatesAskToBuyInSandbox = true // test deferredSKPaymentQueue.default().add(payment)
//代理方法中收到支付结果
/ MARK: SKPaymentTransactionObserver
//处理未完成的交易
extension MXLiveIAPPayment : SKPaymentTransactionObserver{public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {for tran in transactions {switch tran.transactionState {case .purchased://购买完成//成功的未移出的transaction进入app会会掉,失败的不会回掉self.delegate?.orderStatusChanged(order: currentOrder, status:.purchased)currentTransaction = trancompletePay(transaction: tran)print("-------IAP pay purchased--------------")breakcase.purchasing://商品添加进列表
//                 tran.transactionIdentifier此时未nilself.delegate?.orderStatusChanged(order: currentOrder, status: .purchasing)currentTransaction = tranself.updatePurchaseStatus(status: "purchasing")print("-------IAP pay purchasing--------------")breakcase.restored://已经购买过该商品self.delegate?.orderStatusChanged(order: currentOrder, status: .failed(MXLiveIAPError(reason: "product restored", code: -1)))self.updatePurchaseStatus(status: "restored")currentTransaction = tranfinishCurrentOrder()print("-------IAP pay restored--------------")breakcase.failed://购买失败self.delegate?.orderStatusChanged(order: currentOrder, status: .failed(tran.error ?? MXLiveIAPError(reason: "purchase failed error", code: -1)))handleFailure(tran)self.updatePurchaseStatus(status: "failed")//低版本iOS13以下添加观察者之后有可能直接走到此处失败的回调中currentTransaction = tranfinishCurrentOrder()print("-------IAP pay failed--------------")breakcase .deferred://https://stackoverflow.com/questions/42152560/how-to-handle-skpaymenttransactionstatedeferred//ask permission for your parent or guardian//ask for buy,We get transaction deferred state, if user is part of Apple family sharing & family admin enabled ASK TO BUY.currentTransaction = trancurrentOrder?.deferedDate = Date()currentOrder?.updateTokeyChain()self.updatePurchaseStatus(status: "deferred")self.delegate?.orderStatusChanged(order: currentOrder, status: .deferred)print("-------IAP pay deferred--------------")break@unknown default:()}}}private func handleFailure(_ transaction: SKPaymentTransaction) {guard let error = transaction.error else { return }let nsError = error as NSErrorguard nsError.domain == SKError.errorDomain else { return }switch nsError.code {case SKError.clientInvalid.rawValue, SKError.paymentNotAllowed.rawValue:print ("You are not allowed to make IAP payment.")case SKError.paymentCancelled.rawValue:print ( "IAP Payment has been cancelled.")case SKError.unknown.rawValue, SKError.paymentInvalid.rawValue:fallthroughdefault:print ("Something went wrong making IAP payment.")}}

完成的transaction要记住调用finish接口,否则下一次支付代理回调中还会收到这条transaction

3.验证支付票据,支付票据客户端可以直接调苹果的接口验证,我们是调用后台接口让后台 去验证,这样验证通过可以直接进行后续下发商品等业务

private func verifyForApple(data:Data,transaction:SKPaymentTransaction?)  {self.delegate?.orderStatusChanged(order: currentOrder, status: .receiptChecking)let base64Str = data.base64EncodedString(options: .endLineWithLineFeed)let params = NSMutableDictionary()params["receipt-data"] = base64Strlet body = try? JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)var request = URLRequest.init(url: URL.init(string: receiptState == 21008 ? url_receipt_itunes : url_receipt_sandbox)!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 20)request.httpMethod = "POST"request.httpBody = bodylet session = URLSession.sharedlet task = session.dataTask(with: request) { [weak self](data, response, error) inguard let data = data, let dict = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary else{self?.delegate?.orderStatusChanged(order: self?.currentOrder, status: .failed(MXLiveIAPError(reason: "receipt check failed", code: -1)))return}print("receipt_info:")print(dict)let status = dict["status"] as? Intswitch(status){case 0:self?.delegate?.orderStatusChanged(order: self?.currentOrder, status: .complete)breakcase 21007:self?.receiptState = 21007self?.verifyForApple(data: data, transaction: transaction)breakdefault:self?.delegate?.orderStatusChanged(order: self?.currentOrder, status: .failed(MXLiveIAPError(reason: "receipt check failed", code: -1)))break}}task.resume()}

票据验证结果事例:

可以根据transaction.payment.productIdentifier去匹配自己的业务订单

苹果做了限制,如果有相同的productIdentifier的transaction没有处理完,不能发起重复支付,话句话说,transaction数组中不会同时包含两个productID相同的item,也就是如果上一个product未finish,发起新的相同productid的内购会返回失败

在客户端层面也做了限制,当前交易未处理完之前不能发起新的交易,所以基本不会出现多个truncation的情况,

多个transaction验证结果事例:

{receipt = {receipt_type = "ProductionSandbox";app_item_id = 0;receipt_creation_date = "2022-12-08 14:16:42 Etc/GMT";bundle_id = "com.mxplay.ios.live";original_purchase_date = "2013-08-01 07:00:00 Etc/GMT";in_app = ({quantity = "1";purchase_date_ms = "1670508859000";transaction_id = "2000000223045245";is_trial_period = "false";original_transaction_id = "2000000223045245";purchase_date = "2022-12-08 14:14:19 Etc/GMT";product_id = "mx_dq_00001";original_purchase_date_pst = "2022-12-08 06:14:19 America/Los_Angeles";in_app_ownership_type = "PURCHASED";original_purchase_date_ms = "1670508859000";purchase_date_pst = "2022-12-08 06:14:19 America/Los_Angeles";original_purchase_date = "2022-12-08 14:14:19 Etc/GMT";},{quantity = "1";purchase_date_ms = "1670508919000";transaction_id = "2000000223046251";is_trial_period = "false";original_transaction_id = "2000000223046251";purchase_date = "2022-12-08 14:15:19 Etc/GMT";product_id = "mx_dq_00002";original_purchase_date_pst = "2022-12-08 06:15:19 America/Los_Angeles";in_app_ownership_type = "PURCHASED";original_purchase_date_ms = "1670508919000";purchase_date_pst = "2022-12-08 06:15:19 America/Los_Angeles";original_purchase_date = "2022-12-08 14:15:19 Etc/GMT";});adam_id = 0;receipt_creation_date_pst = "2022-12-08 06:16:42 America/Los_Angeles";request_date = "2022-12-08 14:17:15 Etc/GMT";request_date_pst = "2022-12-08 06:17:15 America/Los_Angeles";version_external_identifier = 0;request_date_ms = "1670509035235";original_purchase_date_pst = "2013-08-01 00:00:00 America/Los_Angeles";application_version = "202202153";original_purchase_date_ms = "1375340400000";receipt_creation_date_ms = "1670509002000";original_application_version = "1.0";download_id = 0;};status = 0;environment = "Sandbox";}

验证的错误码如下

21000 App Store无法读取你提供的JSON数据

21002 收据数据不符合格式

21003 收据无法被验证

21004 你提供的共享密钥和账户的共享密钥不一致

21005 收据服务器当前不可用

21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中

21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证

21008 收据信息是产品环境中使用,但却被发送到测试环境中验证

注意:

IAP审核时, 需要提供沙盒测试账号和一个APP的测试账号, 在审核过程时, 我们整个流程都已经切换为正式环境, 但审核人员仍然使用测试凭证去进行验证, 我们服务器需要在审核阶段, 对于此时凭证仍然去沙盒测试验证接口去验证才能验证通过, 否则会被拒绝通过。

在审核阶段可以修改服务端验证支付凭证的流程,先验证正式的如果失败再验证沙盒环境

代码地址:https://github.com/duzhaoquan/DQIAPTool

相关参阅:

https://juejin.cn/post/6974733392260644895

https://juejin.cn/post/7050408490682023966

https://juejin.cn/post/7118958291446661134

苹果内购IAP记录-1相关推荐

  1. 苹果内购IAP记录-2 StoreKit新版

    苹果内购新版的StoreKit2只支持iOS15以上,新的nsync同步接口,简单的使用如下: @available(iOS 15.0, *) public class MXLiveIAPStoreV ...

  2. 苹果内购IAP流程(转载)

    一.In App Purchase概览 Store Kit代表App和App Store之间进行通信.程序将从App Store接收那些你想要提供的产品的信息,并将它们显示出来供用户购买. 当用户需要 ...

  3. PHP 苹果内购iap支付

    $receiptData = $_POST; // 验证参数 if (strlen($receiptData['receipt']) < 1000) {return; } $receipt = ...

  4. iOS_苹果内购详细步骤

    iOS苹果内购详细步骤 iOS开发支付的两种方式 1 Apple Pay + 调取外部支付,例如支付宝.微信.银联等 2 苹果内购IAP(In-App Purchase) 1 IAP规则详解 1.1 ...

  5. ios 内购正式环境_iOS开发-2017苹果内购最新教程

    公司项目中有虚拟产品,所以要使用苹果内购.自此记录一下苹果内购的流程.前提是已有开发者账号 协议,税务和银行业务 协议,税务和银行业务 协议,税务和银行业务 申请合同 这里提示地址太长,按照要求重新填 ...

  6. Unity接入苹果内购(IAP)

    Unity接入苹果内购(IAP) 前言 苹果支付流程 配置App 配置商品 协议.税务和银行业务 沙盒测试账号 Unity(IAP) 测试 前言 第一次发帖,有点激动嘿嘿!话不多说直接奔主题,项目中需 ...

  7. 真正手把手教你用unity接入苹果内购(IAP)

    原帖:真正手把手教你用unity接入苹果内购(IAP) http://www.manew.com/thread-100403-1-1.html (出处: -[游戏蛮牛]-ar增强现实,虚拟现实,uni ...

  8. PHP实现苹果(IOS)内购(IAP)

    反反复复经过多次重写(内部需要),发现苹果使用PHP来验证苹果内购数据是否正确并不是一件很难的事情.我把我的一些心得写出来,以供以后有这方面需求的小伙伴参考,以PHP语言为例,谁让PHP是最好的语言呢 ...

  9. iOS开发 IAP苹果内购

    为什么80%的码农都做不了架构师?>>>    1.概念介绍 <1>苹果内购: App内购买是指在苹果的 App Store 中购买应用程序的方式. 在玩一些游戏类应用软 ...

最新文章

  1. 想轻松入门Python编程,这10个经典案例你还不知道嘛?
  2. asp.net 播放flash
  3. python核心编程-第六章-个人笔记(一)
  4. 把学生类按单科成绩排序_重庆新高考几个核心点:分数线种类、96个志愿、投档排序规则...
  5. 【技术博客-总览--重要】
  6. 【hihocoder 1039 字符串消除】模拟
  7. 格符\b的使用示例:每隔1秒消去1个字符
  8. Xshell、Xftp免费版本
  9. 智慧校园云端管理系统的设计和实现(附源码及数据库)
  10. 汉字风格迁移篇---用于汉字多字体生成的多样性正则化StarGAN
  11. 量子化学计算机理,量子化学计算在反应机理确证中的应用
  12. ERP系统借贷关系表
  13. 小程序 Rsa加密
  14. 腾讯开发者登录不上去
  15. 在移动硬盘安装 Ubuntu
  16. 用keil如何编写c类型语言,keil怎么写程序
  17. 新车可以无牌上路7天_新车可以无牌上路多少天
  18. python轮子下载教程
  19. 微信小程序web-view使用说明,及链接打不开问题
  20. Windows自带输入法中文符号或全角半角切换

热门文章

  1. 百度竞价点击器_【竞价推广100问】百度竞价点击怎么收费,有计算公式吗?
  2. 红外通讯的信号调制及解调电路分析
  3. SLF4J使用slf4j-nop禁止日志打印
  4. 2022大湾区杯奥港金融数学建模竞赛思路及代码
  5. MTK开机LOGO图片的显示原理
  6. [INVEST]2018年国庆后黄金是否值得投资小结
  7. 【论文笔记】—目标姿态估计—EPro-PnP—2022-CVPR
  8. 华为Mate30EPro和华为Mate30的区别
  9. 使用JMeter进行MQTT压力测试
  10. 送你一份年终总结模板+特别福利 | 工具