当前位置: 首页 > 知识库问答 >
问题:

前端 - 苹果内购订阅无法完成?

蒋奕
2024-09-03

第一次订阅内容的时候,没有问题可以正常订阅成功。
但是等订阅到期或者取消后,第二次重新订阅总是失败。(测试中也没有弹出苹果订阅支付的弹窗)

代码中那一部分出错了?急,谢谢大神~

//
//  TYPayManager.swift
//  XiangXuniOS
//
//

import UIKit
import HandyJSON
import StoreKit
import SwiftDate

enum SIAPPurchType {
    case Success       // 购买成功
    case Failed        // 购买失败
    case Cancle        // 取消购买
    case VerFailed     // 订单校验失败
    case VerSuccess    // 订单校验成功
    case NotAllow      // 不允许内购
}


class TYPayManagerReceiptModel: HandyJSON {
    
    var in_app: [TYPayManagerReceiptInAppModel] = []
    
    required init() {}
}

class TYPayManagerReceiptInAppModel: HandyJSON{

    /// 过期时间
    var expires_date: Date?
    
    /// 过期时间的时间戳
    var expires_date_ms = 0
    
    required init() {}
}

class TYPayManager: NSObject{
    
    static let shared = TYPayManager()
    
    var payOrderStatus: ((Int) -> ())?
    
    var statusBlock: ((SIAPPurchType) -> ())?
    
    var purchID: String!
    
    /// 内购生成的订单号
    var order_no = ""
    
    var tips: QMUITips?
    
    class var share: TYPayManager {
        let obj = shared
        obj.initAction()
        return obj
    }
    
    /// 内购
    func initAction() -> Void {
        SKPaymentQueue.default().add(self)
    }
    
    func restore(order_no: String, view: UIView, completeHandle: @escaping ((SIAPPurchType) -> ())){
        let tips = QMUITips.showLoading(in: view)
        self.tips = tips
        self.order_no = order_no
        self.statusBlock = completeHandle
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
    
    // MARK: 开始内购
    func startPurchWithID(purchID: String, order_no: String, view: UIView, completeHandle: @escaping ((SIAPPurchType) -> ())) {
        let tips = QMUITips.showLoading(in: view)
        self.tips = tips
        print(message: purchID)
        self.order_no = order_no
        if SKPaymentQueue.canMakePayments() {
            self.purchID = purchID
            self.statusBlock = completeHandle
            let request = SKProductsRequest.init(productIdentifiers: [purchID])
            request.delegate = self
            request.start()
        } else {
            completeHandle(.NotAllow)
        }
    }
    // MARK: 交易失败
    func failedTransaction(transaction: SKPaymentTransaction) {
        if let tips = self.tips{
            tips.hide(animated: true)
        }
        self.statusBlock?(.Failed)
        SKPaymentQueue.default().finishTransaction(transaction)
    }
    // MARK: 交易结束
    func completeTransaction(transaction: SKPaymentTransaction) {
        let productIdentifier = transaction.payment.productIdentifier
        if productIdentifier.count > 0 {
            // 向自己的服务器验证购买凭证
        }
        self.verifyPurchaseWithPaymentTransaction(transaction: transaction, isTestServer: false)
    }
    
    // MARK: 验证交易
   func verifyPurchaseWithPaymentTransaction(transaction: SKPaymentTransaction, isTestServer: Bool) {
       let recepitURL = Bundle.main.appStoreReceiptURL
       let receipt = try! Data(contentsOf: recepitURL!)
       if let tips = self.tips{
           tips.hide(animated: true)
       }
       self.toServiceVerifyPurchaseWithPaymentTransaction(receipt: receipt, transaction: transaction, isTestServer: false)
   }
    // MARK: 再次向服务器验证交易
    func toServiceVerifyPurchaseWithPaymentTransaction(receipt: Data, transaction: SKPaymentTransaction, isTestServer: Bool) {
        let para = ["receipt-data" : receipt.base64EncodedString(options: .endLineWithLineFeed), "password" : "7032ef52286840be8f2d32e7ee22b516"]
        var appStoreUrl = "https://sandbox.itunes.apple.com/verifyReceipt"
        if isProductServer {
            appStoreUrl = "https://buy.itunes.apple.com/verifyReceipt"
        }
        var verTips: QMUITips?
        if self.order_no.count > 0{
            verTips = QMUITips.showLoading("加载中,请稍后", in: (QMUIHelper.visibleViewController()?.view)!, hideAfterDelay: 20)
        }
        HttpUtils.shared.postJson(url: appStoreUrl, parameters: para) { (result: TYPayManagerReceiptModel?) in
            print(message: "应该是后台进行验证的")
            if let tips = verTips{
                tips.hide(animated: true)
            }
            if let in_app = result?.in_app{
                for item in in_app{
                    let expires_date = Date(timeIntervalSince1970: TimeInterval(item.expires_date_ms / 1000))
                    Keychain.shared.expiresDate = expires_date.toString(.custom("yyyy年MM月dd号"))
                    Keychain.shared.expiresDateSecond = "\(item.expires_date_ms / 1000)"
                    SKPaymentQueue.default().finishTransaction(transaction)
                }
            }
            SKPaymentQueue.default().finishTransaction(transaction)
            self.statusBlock?(.VerSuccess)
        } failure: { error in
            SKPaymentQueue.default().finishTransaction(transaction)
            if let tips = verTips{
                tips.hide(animated: true)
            }
            self.statusBlock?(.VerFailed)
        }
    }
}

extension TYPayManager: SKPaymentTransactionObserver, SKProductsRequestDelegate{
    // MARK: 交易状态
   func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
       for transtion in transactions {
           if transtion.transactionState == .purchased {
               if let tips = self.tips{
                   tips.hide(animated: true)
               }
               // 购买成功
               print(message: "购买成功")
               self.completeTransaction(transaction: transtion)
               break;
           } else if transtion.transactionState == .purchasing {
               // 正在购买
               print(message: "正在购买")
               break;
           } else if transtion.transactionState == .restored {
               // 恢复
               print(message: "已经购买过商品")
               self.completeTransaction(transaction: transtion)
               SKPaymentQueue.default().finishTransaction(transtion)
               break;
           } else if transtion.transactionState == .failed {
               // 购买失败
               print(message: "购买失败")
               self.failedTransaction(transaction: transtion)
               SKPaymentQueue.default().finishTransaction(transtion)
               break;
           } else {
               break;
           }
       }
   }
    // MARK: 请求查看存在的产品
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        let products = response.products
        if products.count == 0 {
            print("没有商品")
            return
        }
        var product: SKProduct?
        for item in products {
            if item.productIdentifier == self.purchID {
                product = item
                break
            }
        }
        if product != nil {
            print("产品信息:")
            print(product)
            let payment = SKPayment.init(product: product!)
            SKPaymentQueue.default().add(payment)
        }
    }
    
    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        if let tips = self.tips{
            tips.hide(animated: true)
        }
        if queue.transactions.count > 0{
            print(message: "有可恢复的购买项")
            for item in queue.transactions{
                print(message: item.original?.transactionDate)
                print(message: item.original?.transactionIdentifier)

            }
        }else{
            print(message: "没有可恢复的购买项")
            QMUITips.show(withText: "没有可恢复的购买项", in: (QMUIHelper.visibleViewController()?.view)!)
        }
    }
    
    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: any Error) {
        print(message: "恢复失败")
        if let tips = self.tips{
            tips.hide(animated: true)
        }
        QMUITips.show(withText: "没有可恢复的购买项", in: (QMUIHelper.visibleViewController()?.view)!)
    }
}

共有1个答案

燕俊明
2024-09-03

根据你提供的代码和问题描述,有几个可能的原因和解决方案,导致订阅在第一次之后无法再次成功:

可能的原因

  1. 重复添加交易到队列
    productsRequest 方法中,你为每个找到的产品添加了一个 SKPayment 到队列。如果 SKPaymentQueue 中已经有一个相同的交易在处理中(例如,用户之前尝试订阅但尚未完成),再次添加相同的交易可能会导致问题。
  2. 未正确处理交易状态
    paymentQueue 方法中,你检查了 .purchased.purchasing.restored.failed 状态,但没有检查 .deferred 状态。在某些情况下,如等待用户确认付款时,交易可能会进入 .deferred 状态。
  3. 服务器验证问题
    如果服务器验证购买凭证时出现问题(如网络问题、服务器响应延迟或验证逻辑错误),也可能导致订阅失败。
  4. 沙盒测试环境问题
    如果你在沙盒环境中测试,并且测试用户账号的购买历史已经包含了该订阅,那么沙盒环境可能会模拟一些特殊的行为(如直接返回失败或要求用户确认)。
  5. Apple ID 设置问题
    用户的 Apple ID 可能需要更新其支付信息或验证其购买权限。

解决方案

  1. 确保不重复添加交易
    在添加 SKPayment 到队列之前,检查 SKPaymentQueue 中是否已存在相同的交易。
  2. 处理所有交易状态
    paymentQueue 方法中添加对 .deferred 状态的处理。
  3. 增强服务器验证的健壮性
    确保服务器验证逻辑能够处理各种网络问题,并在验证失败时给出明确的错误信息。
  4. 清理沙盒测试环境
    尝试重置沙盒测试环境或使用新的测试用户账号进行测试。
  5. 提示用户检查其 Apple ID
    如果问题持续存在,提示用户检查其 Apple ID 的支付信息和购买权限。
  6. 添加更详细的日志记录
    在代码中添加更多的日志记录,以便在出现问题时能够更准确地定位问题所在。
  7. 使用 Apple 的官方文档和论坛
    查阅 Apple 的官方文档和开发者论坛,看看是否有其他开发者遇到并解决了类似的问题。

示例代码片段

以下是一个简单的示例,展示如何在添加 SKPayment 之前检查队列中是否已存在相同的交易:

func startPurchWithID(purchID: String, order_no: String, view: UIView, completeHandle: @escaping ((SIAPPurchType) -> ())) {
    // ...(省略其他代码)

    if SKPaymentQueue.canMakePayments() {
        self.purchID = purchID
        self.statusBlock = completeHandle

        // 检查队列中是否已存在相同的交易
        if !SKPaymentQueue.default().transactions.contains(where: { $0.payment.productIdentifier == purchID && $0.transactionState != .purchasing }) {
            let request = SKProductsRequest(productIdentifiers: [purchID])
            request.delegate = self
            request.start()
        } else {
            // 处理已存在交易的情况,例如显示一个错误消息
            completeHandle(.Failed)
        }
    } else {
        completeHandle(.NotAllow)
    }
}

注意:上面的代码片段只是一个示例,它假设如果队列中存在具有相同 productIdentifier 且不是 .purchasing 状态的交易,则认为该交易已存在。然而,这种方法可能不完全准确,因为 .purchasing 状态可能只是短暂的。你可能需要根据你的具体需求来调整这个逻辑。

 类似资料:
  • 问题描述:uniapp内部使用苹果内购(IAP),购买消耗类产品可以成功拉起支付并支付成功。支付订阅产品的时候首次可以支付成功,但是在App Store中手动取消产品订阅,再回到app内中复购则无法成功拉起支付,使怎么回事呢? 支付环境:沙盒环境

  • 当我更新到RxJS 6时,我的应用程序坏了。我得到了它的大部分工作,但这一个方法难倒了我。 之前,我们有一组被平面映射的观测值,然后使用CombineTest,如下所示: 我可以订阅新观察,并从所有其他网站获得一系列最新的输出。 现在我试着做这样的事情: 这给了我一个很长很复杂的 类型为“(res:Observable)的参数 坦率地说,我的阅读障碍妨碍了我进行语法分析。 如果我只返回上面的res

  • 我通读了RxJS文档,并希望确保我理解了< code > subscriber . unsubscribe()和< code > subscriber . complete()之间的区别。 假设我有一个有两个订阅者的可观察对象,订阅者1和订阅者2。如果订阅者1对其订阅调用取消订阅,它将不再接收来自可观察对象的通知,但订阅者2将继续接收它们。 <代码>的文档。complete(): 观察者回调,用于

  • 我是新的数据流和发布子工具在GCP。 需要将prem过程中的电流迁移到GCP。 当前流程如下: 我们有两种类型的数据馈送 Full Feed–其adhoc作业–完整XML的大小约为100GB(单个XML–非常复杂的一个–完整的数据–ETL作业处理此XML并将其加载到约60个表中) 单独的ETL作业用于处理完整提要。ETL作业过程完全馈送并创建负载就绪文件,所有表将被截断并重新加载 源系统每30分钟

  • 我试图回到IabHelper。OnIabPurchaseFinishedListener当购买结束时,我的订阅,但问题是它没有调用购买完成。 我已经尝试了onactive结果,但仍然不能在这里工作是我的代码块,我正在扩展下面的片段 我提到了这个链接

  • 我正在尝试升级贝宝订阅使用Rest API的计划。 https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_remission

  • 问题内容: 下面是Stuff类型的结构。它具有三个整数。A ,它和它的。让我们假设计算给定int列表的double和power是昂贵的计算。 的结果应与初始化内容相同,例如: 我知道我可以使用并从通道中收集值,但是我不知道什么双/次方属于什么数字。 问题答案: Goroutines独立地并行运行,因此,如果没有显式同步,您将无法预测执行和完成顺序。因此,您无法将返回的数字与输入的数字配对。 您可以

  • 在ngOnDestory中,我取消了两个订阅,但仍然得到前面的错误。 现在我几乎可以肯定问题出在这行:即使我在注销之前取消了proposalSubscription和chatSubscription的订阅,但仍然会出现错误。有没有解决这个问题的方法?而且,我对RXJ和操作符没有太多的经验。有没有操作符可以用来避免这种嵌套订阅? 提前道谢。