1

背景:我的应用程序从一组数组中提供了许多提示。“包”确定可以打开哪些提示。您从默认包开始,然后可以为非消耗性 IAP 购买三个额外的包(packA、packB 和 packC)。

我的目标是让用户支付一次,然后可以随时访问包;但是,一旦沙盒用户进行了 IAP,就会弹出一个窗口说“你已经购买了这个。你想免费再次获得它吗?” . 我显然不希望每次用户选择一个包时都会弹出这个。有没有办法使购买成为永久使用,而不需要不断恢复购买?

以下是我当前的代码(匿名并简化为仅基本组件):

import UIKit
import QuartzCore
import StoreKit

class ViewController: UIViewController, SKPaymentTransactionObserver {


    //MAIN SETUP SECTION XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    let productID = "com.domain.appName.additionalPackages" 
    let defaults = UserDefaults.standard

    //I'm just putting "Various" here as a placeholder for my multiple buttons
    @IBOutlet weak var (Various): UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()


        SKPaymentQueue.default().add(self) 

        //I don’t know if this actually does anything        
        func cleanUp() {
            for transaction in SKPaymentQueue.default().transactions {
                SKPaymentQueue.default().finishTransaction(transaction)
            }
        }

    }




    //PACK SELECTION SECTION XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    var packA = 2
    var packB = 2
    var packC = 2
    var packsUnlocked = false

    @IBAction func selectPackA(_ sender: UIButton) {
        if packsUnlocked == false {
            print("It's locked, ‘Pack A’ not enabled")
        } else if packCounterA % 2 == 0 {
            if SKPaymentQueue.canMakePayments() { // In App Purchase
            let paymentRequest = SKMutablePayment()
            paymentRequest.productIdentifier = productID
            SKPaymentQueue.default().add(paymentRequest)
            print("Initiating Transaction")
        } else {
            print("No Purchased")
        }
        promptProvider.includeA.toggle()
            packCounterA += 1
        } else {
            promptProvider.includeA.toggle()
            packCounterA += 1
        }
    }

    @IBAction func selectPackB(_ sender: UIButton) {
        if packsUnlocked == false {
            print("It's locked, ‘Pack B’ not enabled")
        } else if packCounterB % 2 == 0 {
            if SKPaymentQueue.canMakePayments() { // In App Purchase
            let paymentRequest = SKMutablePayment()
            paymentRequest.productIdentifier = productID
            SKPaymentQueue.default().add(paymentRequest)
            print("Initiating Transaction")
        } else {
            print("No Purchased")
        }
            promptProvider.includeB.toggle()
            packCounterB += 1
        } else {
            promptProvider.includeB.toggle()
            packCounterB += 1
        }
    }

    @IBAction func selectPackC(_ sender: UIButton) {
        if packsUnlocked == false {
            print("It's locked, ‘Pack C' not enabled")
        } else if packCounterC % 2 == 0 {
            if SKPaymentQueue.canMakePayments() { // In App Purchase
            let paymentRequest = SKMutablePayment()
            paymentRequest.productIdentifier = productID
            SKPaymentQueue.default().add(paymentRequest)
            print("Initiating Transaction")
        } else {
            print("No Purchased")
        }
            promptProvider.includeC.toggle()
            packCounterC += 1
        } else {
            promptProvider.includeC.toggle()
            packCounterC += 1
        }
    }




    //TRANSACTION FINALIZATION SECTION XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { 
        for transaction in transactions {
            guard
                transaction.transactionState != .purchasing,
                transaction.transactionState != .deferred
            else {
                //Optionally provide user feedback for pending or processing transactions
                continue
            }

            if transaction.transactionState == .purchased || transaction.transactionState == .restored {
                print("Transaction Successful")
                packsUnlocked = true
                //new
                defaults.set(true, forKey: "Purchase Pack") 
                UserDefaults.standard.synchronize() 
            } else if transaction.transactionState == .failed {
                print("Transaction Failed with error")
            }

            //Transaction can now be safely finished
            queue.finishTransaction(transaction)
        }
    }



}

这是我的第一个应用程序,但我认为问题在于将购买保存到UserDefaults. 我对此很陌生,因此非常感谢任何帮助。

谢谢

4

1 回答 1

3

基本

购买完成后,您可以访问收据Bundle.main.appStoreReceiptURL。您可以使用该收据并针对 App Store 进行验证,以防止有人使用假收据。

验证回复将告诉您收据是合法的并且尚未退还。它还告诉您订阅仍然有效以及何时到期。

您可以在客户端上执行所有这些操作,但最好将收据存储在服务器上,这样您就可以最终验证它,而不是将其留给客户端进行潜在的操作。

不要从您的应用程序调用 App Store 服务器 verifyReceipt 端点。您无法直接在用户设备和 App Store 之间建立受信任的连接,因为您无法控制该连接的任何一端,这使其容易受到中间人攻击。

您可以在文档中找到更多信息,包括代码示例。此外,您可以设置挂钩,以便在订阅状态更改时主动通知您的服务器。

概念验证

如果您不关心上述安全隐患而只想做概念验证,您可以在应用程序中执行此操作并检查用户是否已经购买:

  1. 从那里获取收据Bundle.main.appStoreReceiptURL?.path
  2. 如此处所述,使用 App Store 服务器验证收据;请注意,沙盒应用商店和生产应用商店的验证 URL 不同
  3. 获取product_idexpires_date从验证响应中了解已购买的产品以及是否已过期

请记住,如果用户卸载并重新安装应用程序,收据不会存储在设备上。然后,用户必须在应用程序从 App Store 服务器下载收据时恢复购买。当您按下您可能从其他应用程序中熟悉的“恢复购买”按钮时,就会发生这种情况。只需使用SKReceiptRefreshRequest.

于 2020-05-27T23:34:13.870 回答