如何检查用户是否已在Swift中购买App购买中的自动续订订阅付款



i当前在iTunesConnect中设置的应用程序中有一个Non-ConsumableAuto Renewal Subscription。我的问题是,我不确定如何检查Auto Renewal Subscription用户是否可以解锁内容。我对应用程序购买中的Non-Consumables没有问题,我通过检查UserDefaults中是否存在产品ID来验证它们,如果确实如此,我会解锁内容,否则我会通知用户,但此方法在应用程序购买中不使用Auto Renewal Subscription。当我测试它时,我可以通过App Store进行购买转移,但是当我尝试查看UserDefaults中的产品ID是否存在时,它会返回false。实际上,我手动检查了钥匙是否存在,并且不存在,它仅显示Non-Consumable购买的密钥。

这是我正在使用的代码。

这是我多年来使用的工作代码来验证应用程序购买中的Non-Consumable

这是我正在使用的In App Manager类。

import UIKit
import StoreKit
protocol IAPManagerDelegate {
    func managerDidRestorePurchases()
}
class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver, SKRequestDelegate {
    static let sharedInstance = IAPManager()
    var request:SKProductsRequest!
    var products:NSArray!
    var delegate:IAPManagerDelegate?
    func setupInAppPurchases(){
        self.validateProductIdentifiers(self.getProductIdentifiersFromMainBundle())
        SKPaymentQueue.default().add(self)
    }
    func getProductIdentifiersFromMainBundle() -> NSArray {
        var identifiers = NSArray()
        if let url = Bundle.main.url(forResource: "iap_product_ids", withExtension: "plist"){
            identifiers = NSArray(contentsOf: url)!
        }
        return identifiers
    }
    func validateProductIdentifiers(_ identifiers:NSArray) {
        let productIdentifiers = NSSet(array: identifiers as [AnyObject])
        let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
        self.request = productRequest
        productRequest.delegate = self
        productRequest.start()
    }
    func createPaymentRequestForProduct(_ product:SKProduct){
        let payment = SKMutablePayment(product: product)
        payment.quantity = 1
        SKPaymentQueue.default().add(payment)
    }
    func verifyReceipt(_ transaction:SKPaymentTransaction?){
        let receiptURL = Bundle.main.appStoreReceiptURL!
        if let receipt = try? Data(contentsOf: receiptURL){
            let requestContents = ["receipt-data" : receipt.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))]
            do {
                let requestData = try JSONSerialization.data(withJSONObject: requestContents, options: JSONSerialization.WritingOptions(rawValue: 0))
                //  PRODUCTION URL
                // let storeURL = URL(string: "https://buy.itunes.apple.com/verifyReceipt")
                //  TESTING URL: Uncomment for testing InAppPurchases
                let storeURL = URL(string: "https:/sandbox.itunes.apple.com/verifyReceipt")
                var request = URLRequest(url: storeURL!)
                request.httpMethod = "Post"
                request.httpBody = requestData
                let session = URLSession.shared
                let task = session.dataTask(with: request,
                                            completionHandler: { (responseData, response, error) -> Void in
                    do {
                        let json = try JSONSerialization.jsonObject(with: responseData!, options: .mutableLeaves) as! NSDictionary
                        if (json.object(forKey: "status") as! NSNumber) == 0 {
                            if let latest_receipt = json["latest_receipt_info"]{
                                self.validatePurchaseArray(latest_receipt as! NSArray)
                            } else {
                                let receipt_dict = json["receipt"] as! NSDictionary
                                if let purchases = receipt_dict["in_app"] as? NSArray{
                                    self.validatePurchaseArray(purchases)
                                }
                            }
                            if transaction != nil {
                                SKPaymentQueue.default().finishTransaction(transaction!)
                            }
                            DispatchQueue.main.sync(execute: { () -> Void in
                                self.delegate?.managerDidRestorePurchases()
                            })
                        } else {
                            print(json.object(forKey: "status") as! NSNumber)
                        }
                    } catch {
                        print(error)
                    }
                })
                task.resume()
            } catch {
                print(error)
            }
        } else {
            print("No Receipt")
        }
    }
    func validatePurchaseArray(_ purchases:NSArray){
        for purchase in purchases as! [NSDictionary]{
            self.unlockPurchasedFunctionalityForProductIdentifier(purchase["product_id"] as! String)
        }
    }
    func unlockPurchasedFunctionalityForProductIdentifier(_ productIdentifier:String){
        UserDefaults.standard.set(true, forKey: productIdentifier)
        UserDefaults.standard.synchronize()
        DispatchQueue.main.async {
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
        }
    }
    func lockPurchasedFunctionalityForProductIdentifier(_ productIdentifier:String){
        UserDefaults.standard.set(false, forKey: productIdentifier)
        UserDefaults.standard.synchronize()
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
    }
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        let inAppPurchases = response.products
        // Sort the items
        self.products = inAppPurchases.reversed() as NSArray
    }
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions as [SKPaymentTransaction]{
            switch transaction.transactionState{
            case .purchasing:
                print("Purchasing")
                UIApplication.shared.isNetworkActivityIndicatorVisible = true
            case .deferred:
                print("Deferrred")
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
            case .failed:
                print("Failed")
                //print(transaction.error?.localizedDescription)
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
                SKPaymentQueue.default().finishTransaction(transaction)
            case.purchased:
                print("Purchased")
                self.verifyReceipt(transaction)
            case .restored:
                print("Restored")
            }
        }
    }
    func restorePurchases(){
        let request = SKReceiptRefreshRequest()
        request.delegate = self
        request.start()
    }
    func requestDidFinish(_ request: SKRequest) {
        self.verifyReceipt(nil)
    }
}

这是我在UITableView中介绍In App Purchases的方式。

class StoreTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, IAPManagerDelegate {
    @IBOutlet weak var premiumFeaturesTable: UITableView!
    @IBOutlet weak var buttonClose: UIButton!
    @IBOutlet weak var buttonRestore: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        IAPManager.sharedInstance.delegate = self
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return IAPManager.sharedInstance.products.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell =  tableView.dequeueReusableCell(withIdentifier: "cellInAppPurchase")as! CustomCellForInAppPurchasesTableViewCell
        let product = IAPManager.sharedInstance.products.object(at: indexPath.row) as! SKProduct
         cell.labelIAppItem.text = product.localizedTitle
        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        IAPManager.sharedInstance.createPaymentRequestForProduct(IAPManager.sharedInstance.products.object(at: indexPath.row) as! SKProduct)
    }
    @IBAction func closeViewController(_ sender: AnyObject) {
        self.presentingViewController!.dismiss(animated: true, completion: nil)
    }
    @IBAction func restorePurchases(_ sender: AnyObject) {
        IAPManager.sharedInstance.restorePurchases()
    }
}

这是我解锁内容的方式

if NSUserDefaults.standardUserDefaults().boolForKey("com.theAppID.app"){
    // Unlock content.
}else{
    // Notify user.
}

再次适用于Non-Consumables,但对于Auto Renewal Subscriptions,我不确定用户购买后如何解锁内容。

我缺少什么,正确检查用户是否基于上述代码为Auto Renewal Subscription付款的正确方法是什么?

编辑:最简单的答案是...用于基于订阅的应用程序,将RevenueCat使用,它使您的生活更轻松。

请检查此链接以获取自动续订订阅。

您可以在应用程序内管理器类中使用以下功能。

func verifyReceipt(_ transaction:SKPaymentTransaction?) 

验证后,您将获得订阅最后日期的响应代码和详细信息。请检查此链接。

注意:不要忘记在自动续订订阅的收据验证中传递"密码"字段。

这是您检查用户是否将使用StoreKit 2:

进行自动续订订阅的续订方式。
try await subscribedProduct.subscription?.status.first?.renewalInfo.payloadValue.willAutoRenew

如果您需要完整的操场代码以获取更多上下文:

import Foundation
import StoreKit

public enum StoreError: Error {
    case failedVerification
}
@MainActor
func willRenewSubscription(from subscriptions: [Product]) async -> Bool {
    for await result in Transaction.currentEntitlements {
        do {
            let transaction = try checkVerified(result)
            switch transaction.productType {
            case .autoRenewable:
                if let subscription = subscriptions.first(where: { $0.id == transaction.productID }) {
                    Task {
                        let status = try await subscription.subscription?.status.first?.renewalInfo.payloadValue.willAutoRenew
                        return status
                    }
                } else {
                    return false
                }
            default:
                return false
            }
        } catch {
            return false
        }
    }
    return false
}
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
    switch result {
    case .unverified:
        throw StoreError.failedVerification
    case .verified(let safe):
        return safe
    }
}

用法:

Task {
    let willRenew = await willRenewSubscription(from: [])
    print(willRenew)
}