Skproduct 跳过产品,因为没有可用价格

Dji*_*rro 9 storekit in-app-purchase skproduct swift in-app-subscription

这是我第一次创建购买的经历。我正在开发的应用程序尚未发布。我一直在使用 Configuration.storekit 文件在本地测试订阅。一切正常。\n我最近遇到了一个问题 - 我的订阅不再显示在项目中。我在终端中收到如下错误:\n终端输出的图像

\n

更新:

\n
    \n
  • 我决定在模拟器上检查应用程序,一切正常。据我记得安装 xcode 14 并更新到 ios 16 后一切都崩溃了。
  • \n
  • 在物理设备上,问题仍然存在。
  • \n
\n

我没有更改那些地方的代码。我尝试创建新的 .storekit 文件,但仍然不起作用。\n我尝试通过同步加载 .storekit 文件。在其中,价格被拉高并正确显示,与网站上一样,但在终端中再次写入相同的错误。

\n

这是适用于购买的文件:

\n
import StoreKit\n\ntypealias RequestProductsResult = Result<[SKProduct], Error>\ntypealias PurchaseProductResult = Result<Bool, Error>\n\ntypealias RequestProductsCompletion = (RequestProductsResult) -> Void\ntypealias PurchaseProductCompletion = (PurchaseProductResult) -> Void\n\n\nclass Purchases: NSObject {\n    static let `default` = Purchases()\n    private let productIdentifiers = Set<String>(\n        arrayLiteral: "test.1month", "test.6month", "test.12month"\n    )\n\n    private var products: [String: SKProduct]?\n    private var productRequest: SKProductsRequest?\n    private var productsRequestCallbacks = [RequestProductsCompletion]()\n    fileprivate var productPurchaseCallback: ((PurchaseProductResult) -> Void)?\n    \n    \n    func initialize(completion: @escaping RequestProductsCompletion) {\n        requestProducts(completion: completion)\n    }\n    \n\n    private func requestProducts(completion: @escaping RequestProductsCompletion) {\n        guard productsRequestCallbacks.isEmpty else {\n            productsRequestCallbacks.append(completion)\n            return\n        }\n\n        productsRequestCallbacks.append(completion)\n\n        let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers)\n        productRequest.delegate = self\n        productRequest.start()\n\n        self.productRequest = productRequest\n    }\n    \n\n    func purchaseProduct(productId: String, completion: @escaping (PurchaseProductResult) -> Void) {\n        \n        guard productPurchaseCallback == nil else {\n            completion(.failure(PurchasesError.purchaseInProgress))\n            return\n        }\n        \n        guard let product = products?[productId] else {\n            completion(.failure(PurchasesError.productNotFound))\n            return\n        }\n\n        productPurchaseCallback = completion\n\n        let payment = SKPayment(product: product)\n        SKPaymentQueue.default().add(payment)\n    }\n\n    \n    public func restorePurchases(completion: @escaping (PurchaseProductResult) -> Void) {\n        guard productPurchaseCallback == nil else {\n            completion(.failure(PurchasesError.purchaseInProgress))\n            return\n        }\n        productPurchaseCallback = completion\n        SKPaymentQueue.default().restoreCompletedTransactions()\n    }\n}\n\n\nextension Purchases: SKProductsRequestDelegate {\n    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {\n        guard !response.products.isEmpty else {\n            print("Found 0 products")\n\n            productsRequestCallbacks.forEach { $0(.success(response.products)) }\n            productsRequestCallbacks.removeAll()\n            return\n        }\n\n        var products = [String: SKProduct]()\n        for skProduct in response.products {\n            print("Found product: \\(skProduct.productIdentifier)")\n            products[skProduct.productIdentifier] = skProduct\n        }\n\n        self.products = products\n\n        productsRequestCallbacks.forEach { $0(.success(response.products)) }\n        productsRequestCallbacks.removeAll()\n    }\n\n    func request(_ request: SKRequest, didFailWithError error: Error) {\n        print("Failed to load products with error:\\n \\(error)")\n\n        productsRequestCallbacks.forEach { $0(.failure(error)) }\n        productsRequestCallbacks.removeAll()\n    }\n}\n\n\nextension Purchases: SKPaymentTransactionObserver {\n    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {\n        \n        for transaction in transactions {\n            switch transaction.transactionState {\n            case .purchased, .restored:\n                if finishTransaction(transaction) {\n                    SKPaymentQueue.default().finishTransaction(transaction)\n                    productPurchaseCallback?(.success(true))\n                    UserDefaults.setValue(true, forKey: "isPurchasedSubscription")\n                } else {\n                    productPurchaseCallback?(.failure(PurchasesError.unknown))\n                }\n        \n            case .failed:\n                productPurchaseCallback?(.failure(transaction.error ?? PurchasesError.unknown))\n                SKPaymentQueue.default().finishTransaction(transaction)\n                \n            default:\n                break\n                \n            }\n        }\n\n        productPurchaseCallback = nil\n        \n    }\n}\n\n\nextension Purchases {\n    func finishTransaction(_ transaction: SKPaymentTransaction) -> Bool {\n        let productId = transaction.payment.productIdentifier\n        print("Product \\(productId) successfully purchased")\n        return true\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

还有一个文件负责显示可用的订阅选项:

\n
\n//\n//  PremiumRatesTVC.swift\n//  CalcYou\n//\n//  Created by Admin on 29.08.2022.\n//\n\nimport StoreKit\nimport UIKit\n\nclass PremiumRatesTVC: UITableViewController {\n    var oneMonthPrice    = ""\n    var sixMonthPrice    = ""\n    var twelveMonthPrice = ""\n    \n    @IBOutlet weak var oneMonthPriceLabel:     UILabel!\n    @IBOutlet weak var oneMothDailyPriceLabel: UILabel!\n    \n    @IBOutlet weak var sixMonthPriceLabel:      UILabel!\n    @IBOutlet weak var sixMonthDailyPriceLabel: UILabel!\n    \n    @IBOutlet weak var twelveMonthPriceLabel:      UILabel!\n    @IBOutlet weak var twelveMonthDailyPriceLabel: UILabel!\n    \n    @IBOutlet weak var tableViewCellOneMonth:    UITableViewCell!\n    @IBOutlet weak var tableViewCellSixMonth:    UITableViewCell!\n    @IBOutlet weak var tableViewCellTwelveMonth: UITableViewCell!\n    \n    \n    @IBAction func cancelButton(_ sender: Any) {\n        dismiss(animated: true, completion: nil)\n    }\n    \n    \n    // MARK: ViewDidLoad()\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        \n        hideSubscriptions()\n        navigationItem.title = "Premium PRO version"\n        \n        Purchases.default.initialize { [weak self] result in\n            guard let self = self else { return }\n\n            switch result {\n            case let .success(products):\n                guard products.count > 0 else {\n                    let message = "Failed to get a list of subscriptions. Please try again later."\n                    self.showMessage("Oops", withMessage: message)\n                    return\n                    \n                }\n                self.showSubscriptions()\n                \n                DispatchQueue.main.async {\n                    self.updateInterface(products: products)\n                    \n                }\n                \n            default:\n                break\n                \n            }\n        }\n    }\n    \n    \n    // MARK: Functions()\n    private func updateInterface(products: [SKProduct]) {\n        updateOneMonth(with: products[0])\n        updateSixMonth(with: products[1])\n        updateTwelveMonth(with: products[2])\n    }\n    \n    \n    private func hideSubscriptions() {\n        DispatchQueue.main.async {\n            self.tableViewCellOneMonth.isHidden = true\n            self.tableViewCellSixMonth.isHidden = true\n            self.tableViewCellTwelveMonth.isHidden = true\n            \n        }\n    }\n    \n    \n    private func showSubscriptions() {\n        DispatchQueue.main.async {\n            self.tableViewCellOneMonth.isHidden = false\n            self.tableViewCellSixMonth.isHidden = false\n            self.tableViewCellTwelveMonth.isHidden = false\n            \n        }\n    }\n    \n    \n    func showMessage(_ title: String, withMessage message: String) {\n        DispatchQueue.main.async {\n            let alert = UIAlertController(title: title,\n                                          message: message,\n                                          preferredStyle: UIAlertController.Style.alert)\n            let dismiss = UIAlertAction(title: "Ok",\n                                        style: UIAlertAction.Style.default,\n                                        handler: nil)\n            \n            alert.addAction(dismiss)\n            self.present(alert, animated: true, completion: nil)\n        }\n    }\n\n    \n    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {\n        \n        let storyboard = UIStoryboard(name: "Main", bundle: nil)\n        \n        if indexPath.section == 0 && indexPath.row == 0 {\n            guard let premiumBuyVC = storyboard.instantiateViewController(identifier: "PremiumBuyVC") as? PremiumBuyVC else { return }\n            \n            premiumBuyVC.price = oneMonthPrice\n            premiumBuyVC.productId = "1month"\n            premiumBuyVC.period = "per month"\n            show(premiumBuyVC, sender: nil)\n        }\n        \n        if indexPath.section == 1 && indexPath.row == 0 {\n            guard let premiumBuyVC = storyboard.instantiateViewController(identifier: "PremiumBuyVC") as? PremiumBuyVC else { return }\n            \n            premiumBuyVC.price = sixMonthPrice\n            premiumBuyVC.productId = "6month"\n            premiumBuyVC.period = "per 6 month"\n            show(premiumBuyVC, sender: nil)\n        }\n        \n        if indexPath.section == 2 && indexPath.row == 0 {\n            guard let premiumBuyVC = storyboard.instantiateViewController(identifier: "PremiumBuyVC") as? PremiumBuyVC else { return }\n            \n            premiumBuyVC.price = twelveMonthPrice\n            premiumBuyVC.productId = "12month"\n            premiumBuyVC.period = "per 12 month"\n            show(premiumBuyVC, sender: nil)\n        }\n    }\n}\n\n\nextension SKProduct {\n    public var localizedPrice: String? {\n        let numberFormatter = NumberFormatter()\n        numberFormatter.locale = self.priceLocale\n        numberFormatter.numberStyle = .currency\n        return numberFormatter.string(from: self.price)\n    }\n}\n\n\n// MARK: \xd0\x9e\xd0\xb1\xd0\xbd\xd0\xbe\xd0\xb2\xd0\xbb\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5 \xd0\xb8\xd0\xbd\xd1\x84\xd0\xbe\xd1\x80\xd0\xbc\xd0\xb0\xd1\x86\xd0\xb8\xd0\xb8\n// \xd0\xb2 cell \xd0\xb4\xd0\xbb\xd1\x8f 1, 6, 12 \xd0\xbc\xd0\xb5\xd1\x81\xd1\x8f\xd1\x86\xd0\xb5\xd0\xb2\nextension PremiumRatesTVC {\n    func updateOneMonth(with product: SKProduct) {\n        let withCurrency = "\\(product.priceLocale.currencyCode ?? " ")"\n        let daily = dailyPrice(from: Double(truncating: product.price), withMonth: 1.0)\n        \n        oneMonthPriceLabel.text = "\\(product.price) \\(withCurrency)"\n        oneMothDailyPriceLabel.text = "\\(daily) \\(withCurrency)"\n        oneMonthPrice = "\\(product.price) \\(withCurrency)"\n    }\n    \n    func updateSixMonth(with product: SKProduct) {\n        let withCurrency = "\\(product.priceLocale.currencyCode ?? " ")"\n        let daily = dailyPrice(from: Double(truncating: product.price), withMonth: 6.0)\n        \n        sixMonthPriceLabel.text = "\\(product.price) \\(withCurrency)"\n        sixMonthDailyPriceLabel.text = "\\(daily) \\(withCurrency)"\n        sixMonthPrice = "\\(product.price) \\(withCurrency)"\n    }\n\n    func updateTwelveMonth(with product: SKProduct) {\n        let withCurrency = "\\(product.priceLocale.currencyCode ?? " ")"\n        let daily = dailyPrice(from: Double(truncating: product.price), withMonth: 12.0)\n        \n        twelveMonthPriceLabel.text = "\\(product.price) \\(withCurrency)"\n        twelveMonthDailyPriceLabel.text = "\\(daily) \\(withCurrency)"\n        twelveMonthPrice = "\\(product.price) \\(withCurrency)"\n    }\n    \n    func dailyPrice(from value: Double, withMonth: Double) -> String {\n        let days = withMonth * 30\n        let result = value / days\n        \n        return String(format: "%.2f", result)\n    }\n}\n\n\n
Run Code Online (Sandbox Code Playgroud)\n

此图显示了 testConfiguration.storekit 文件:

\n

testConfiguration.storekit 文件镜像

\n

还有来自编辑方案的图像:

\n

编辑方案图像

\n

还有左侧菜单中带问号的 testConfiguration.storekit 文件。\n带有问题图像的文件

\n

我希望我详细且正确地描述了我遇到的问题。非常感谢所有抽出时间的人。

\n

Dji*_*rro 1

我的老板没有填写“付费应用程序”字段。请务必检查以确保它处于活动状态。 检查这个答案