首页 > 其他分享 >苹果内购IAP记录-1

苹果内购IAP记录-1

时间:2023-01-17 16:46:19浏览次数:43  
标签:内购 purchase 12 08 request 苹果 2022 date IAP

这段时间做了苹果内购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] = completion
        request.delegate = self
        request.start()
    }
//代理回调结果,注意代理回调结果是在多线程中,根据需求是否要切换成主线程
// MARK: SKProductsRequestDelegate
extension MXLiveIAPPayment : SKProductsRequestDelegate{
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        
        guard let callback = productFetchCallbacks[request] else { return }
        productFetchCallbacks[request] = nil

        DispatchQueue.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] = nil
            DispatchQueue.main.async {
                callback([])
            }
        }
    }
//    public func requestDidFinish(_ request: SKRequest) {
//        print(request)
//    }
}

2.设置代理发起内购,

         let payment = SKMutablePayment()
            payment.quantity = 1
           payment.applicationUsername = currentOrder?.uuid
            payment.productIdentifier = order.productId
            payment.simulatesAskToBuyInSandbox = true // test deferred
            SKPaymentQueue.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 = tran
                completePay(transaction: tran)
                print("-------IAP pay purchased--------------")
                break
            case.purchasing://商品添加进列表
//                 tran.transactionIdentifier此时未nil
                self.delegate?.orderStatusChanged(order: currentOrder, status: .purchasing)
                currentTransaction = tran
                self.updatePurchaseStatus(status: "purchasing")
                print("-------IAP pay purchasing--------------")
                break
            case.restored://已经购买过该商品
                self.delegate?.orderStatusChanged(order: currentOrder, status: .failed(MXLiveIAPError(reason: "product restored", code: -1)))
                self.updatePurchaseStatus(status: "restored")
                currentTransaction = tran
                finishCurrentOrder()
                print("-------IAP pay restored--------------")
                break
            case.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 = tran
                finishCurrentOrder()
                print("-------IAP pay failed--------------")
                break
            case .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 = tran
                currentOrder?.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 NSError
        guard 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:
            fallthrough
        default:
            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"] = base64Str
        let 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 = body
        let session = URLSession.shared
        let task = session.dataTask(with: request) { [weak self](data, response, error) in
            guard 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? Int
            switch(status){
            case 0:
                self?.delegate?.orderStatusChanged(order: self?.currentOrder, status: .complete)
                break
            case 21007:
                self?.receiptState = 21007
                self?.verifyForApple(data: data, transaction: transaction)
                break
            default:
                self?.delegate?.orderStatusChanged(order: self?.currentOrder, status: .failed(MXLiveIAPError(reason: "receipt check failed", code: -1)))
                break
            }
        }
        task.resume()
    }

 票据验证结果事例:

{
         receipt = {
             receipt_type = "ProductionSandbox";
             app_item_id = 0;
             receipt_creation_date = "2022-12-08 12:36:33 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 = "1670502431000";
                     transaction_id = "2000000222942381";
                     is_trial_period = "false";
                     original_transaction_id = "2000000222942381";
                     purchase_date = "2022-12-08 12:27:11 Etc/GMT";
                     product_id = "mx_dq_00001";
                     original_purchase_date_pst = "2022-12-08 04:27:11 America/Los_Angeles";
                     in_app_ownership_type = "PURCHASED";
                     original_purchase_date_ms = "1670502431000";
                     purchase_date_pst = "2022-12-08 04:27:11 America/Los_Angeles";
                     original_purchase_date = "2022-12-08 12:27:11 Etc/GMT";
                 }
             );
             adam_id = 0;
             receipt_creation_date_pst = "2022-12-08 04:36:33 America/Los_Angeles";
             request_date = "2022-12-08 12:36:34 Etc/GMT";
             request_date_pst = "2022-12-08 04:36:34 America/Los_Angeles";
             version_external_identifier = 0;
             request_date_ms = "1670502994515";
             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 = "1670502993000";
             original_application_version = "1.0";
             download_id = 0;
         };
         status = 0;
         environment = "Sandbox";
     }

 可以根据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://juejin.cn/post/6974733392260644895

https://juejin.cn/post/7050408490682023966

https://juejin.cn/post/7118958291446661134

 

 

标签:内购,purchase,12,08,request,苹果,2022,date,IAP
From: https://www.cnblogs.com/duzhaoquan/p/17058132.html

相关文章

  • 苹果内购IAP记录-2 StoreKit新版
    苹果内购新版的StoreKit2只支持iOS15以上,新的nsync同步接口,简单的使用如下:@available(iOS15.0,*)publicclassMXLiveIAPStoreV2{staticletshared:MXLi......
  • 黑苹果双系统时间不一致_黑苹果与Windows系统时间不对(不同步)
    解决办法:WIN+x选择管理员模式进入CMD复制下面的代码,点击CDM右键可以直接进行粘贴,然后按回车键即可。RegaddHKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformat......
  • 买苹果
    题目描述如图:提示:有两种带子,分别只能装6个和8个,不能多装,也不能少装。求最小的需要的袋子数。思路:显然如果能用8个袋子装绝对不用6的袋子,8的袋子能装更多,所需要的袋子......
  • uniapp裁剪视频使用ffmpeg
    <template><view><button@click="cutVideo">CutVideo</button></view></template><script>importffmpegfrom'ffmpeg.js'exportdefault{method......
  • Uniapp Vue 中v-if判断class
       如果是true运行问好后面的,如果false运行‘’<viewclass="recharge_head_licenter":class="totalIn>0&&totalIn<30?'chs':''......
  • ios苹果app上架流程
    iOSAPP发布分两大步骤,首先测试APP,如没问题再上传APP审核!1、真机测试调试APP2、上传APP到AppStore审核上架基本需求资料1、苹果开发者账号(如还没账号先申请)2、开发好的APP......
  • uniapp 开发微信小程序问题笔记
    最近接手了一个小程序开发,从头开始。使用了uniapp搭建,以前没有做过小程序开发,着手看文档、查文档。一步一步完成了任务的开发。特此记录开发过程中的问题。开发建议:使......
  • uniapp或vue项目里如何接入第三方在线客服代码
    UniApp是一个使用Vue.js框架开发的跨平台应用程序,可以在iOS、Android、H5、微信小程序、支付宝小程序、字节跳动小程序等多个平台上运行。如果要在UniApp中接入第三......
  • 视频直播源码,uniapp页面跳转的几种方法和区别
    视频直播源码,uniapp页面跳转的几种方法和区别在讲它们的差异之前,我们先引入一个概念:页面栈 1.栈是一种连续储存的数据结构,具有先进后出的性质。2.页面栈就是用来储......
  • 苹果电脑mac关机、重启、锁屏快捷键
    1、mac立即锁屏control+command +q 2、关闭屏幕control+shift+电源键 3、mac重启control+command+电源键:强制Mac重新启动,系统不会提示你存储任何......