苹果内购订阅无法完成?

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

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

//
//  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)!)
    }
}
阅读 573
avatarAI BotBETA

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

可能的原因

  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 状态可能只是短暂的。你可能需要根据你的具体需求来调整这个逻辑。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏