ios - InApp 购买耗材服务器验证设置

标签 ios swift in-app-purchase swiftystorekit

我正在使用 SwiftyStoreKit 作为小费 jar 的 InApp Purchase Consumable。一切都适用于测试,但根据 this answer并且评论服务器端验证不是必需的,但建议这样做。答案指出 “消耗品、非消耗品和订阅容易受到欺诈攻击。通常是通过 iap 黑客或网络欺骗。验证收据可以缓解这个问题”

1- 如果用户 A 向我发送小费,如果一切都通过 Apple,攻击者怎么可能拦截该小费并拿走钱?

2- 我需要设置一个 Heroku 实例还是使用其他东西来进行服务器验证?我在上面找不到任何东西。我假设我需要在 if product.needsFinishTransaction { SwiftyStoreKit.finishTransaction(product.transaction) } 下面的 success case 中添加服务器端代码,但我没有从那时起我就不知道如何设置服务器了。

SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in

    switch result {
        case .success(let product):
            // fetch content from your server, then:
            if product.needsFinishTransaction {
                SwiftyStoreKit.finishTransaction(product.transaction)
            }
            print("Purchase Success: \(product.productId)")
        // failed cases ...
    }
}

代码如下:

应用委托(delegate):

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
        for purchase in purchases {
            switch purchase.transaction.transactionState {
            case .purchased, .restored:
                if purchase.needsFinishTransaction {
                    // Deliver content from server, then:
                    SwiftyStoreKit.finishTransaction(purchase.transaction)
                }
            // Unlock content
            case .failed, .purchasing, .deferred:
                break // do nothing
            @unknown default:
                break
            }
        }
    }
}

TipJarVC。购买是在 collectionView 的 didSelect item 中进行的:

var dataSource = [Tip]()
var sharedSecret = appStoreConnectSecretKey

let inAppProductIds = ["com.myCo.myAppName.firstTip", // 0.99
                       "com.myCo.myAppName.secondTip", // 9.99 ]

override func viewDidLoad() {
    super.viewDidLoad()

   getInAppPurchaseAmounts()
}

func getInAppPurchaseAmounts() {

    // show spinner

    let dispatchGroup = DispatchGroup()

    for productId in inAppProductIds {

        dispatchGroup.enter()

        SwiftyStoreKit.retrieveProductsInfo([productId]) { [weak self](result) in
            if let product = result.retrievedProducts.first {
                let priceString = product.localizedPrice!
                print("Product: \(product.localizedDescription), price: \(priceString)")

                let tip = Tip(displayName: product.description,
                              description: product.localizedDescription,
                              productId: productId
                              price: priceString)


                self?.addTipToDataSource(tip)

                if let sharedSecret = self?.sharedSecret {

                    self?.verifyPurchase(with: productId, sharedSecret: sharedSecret)
                }
                dispatchGroup.leave()

            } else if let invalidProductId = result.invalidProductIDs.first {
                print("Invalid product identifier: \(invalidProductId)")
                dispatchGroup.leave()

            } else {
                print("Error: \(String(describing: result.error))")
                dispatchGroup.leave()
            }
        }
    }

    dispatchGroup.notify(queue: .global(qos: .background)) { [weak self] in
        DispatchQueue.main.async { [weak self] in

            // removeSpinnerAndReloadData()
        }
    }
}

func verifyPurchase(with productId: String, sharedSecret: String) {

    let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: sharedSecret)
    SwiftyStoreKit.verifyReceipt(using: appleValidator) { result in
        switch result {
        case .success(let receipt):
            let productId = productId
            // Verify the purchase of Consumable or NonConsumable
            let purchaseResult = SwiftyStoreKit.verifyPurchase(
                productId: productId,
                inReceipt: receipt)

            switch purchaseResult {
            case .purchased(let receiptItem):
                print("\(productId) is purchased: \(receiptItem)")
            case .notPurchased:
                print("The user has never purchased \(productId)")
            }
        case .error(let error):
            print("Receipt verification failed: \(error)")
        }
    }
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    guard let cell = collectionView.cellForItem(at: indexPath) as? TipCell else { return }
    guard let indexPath = collectionView.indexPath(for: cell) else { return }

    let tip = dataSource[indexPath.item]

    purchaseProduct(with: tip.productId)
}

func purchaseProduct(with productId: String) {

    SwiftyStoreKit.retrieveProductsInfo([productId]) { result in
        if let product = result.retrievedProducts.first {
            SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in

                switch result {
                case .success(let product):
                    // fetch content from your server, then:
                    if product.needsFinishTransaction {
                        SwiftyStoreKit.finishTransaction(product.transaction)
                    }
                    print("Purchase Success: \(product.productId)")
                case .error(let error):
                    switch error.code {
                    case .unknown:
                        print("Unknown error. Please contact support")
                    case .clientInvalid:
                        print("Not allowed to make the payment")
                    case .paymentCancelled:
                        print("Payment cancelled")
                    case .paymentInvalid:
                        print("The purchase identifier was invalid")
                    case .paymentNotAllowed:
                        print("The device is not allowed to make the payment")
                    case .storeProductNotAvailable:
                        print("The product is not available in the current storefront")
                    case .cloudServicePermissionDenied:
                        print("Access to cloud service information is not allowed")
                    case .cloudServiceNetworkConnectionFailed:
                        print("Could not connect to the network")
                    case .cloudServiceRevoked:
                        print("User has revoked permission to use this cloud service")
                    default:
                        print((error as NSError).localizedDescription)
                    }
                }
            }
        }
    }
}

最佳答案

您不必害怕攻击者——因为它只与越狱设备有关,他们唯一能做的就是免费使用您的付费功能。而且没有人能拿走这笔钱。一切都很安全。

如果您只想进行 IAP 购买,您可以使用 SwiftyStoreKit

您可以阅读 this article来 self 们关于收据验证的博客。

关于ios - InApp 购买耗材服务器验证设置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58490327/

相关文章:

android - Google Checkout 通知中的应用内开发人员负载

ios - 填写所有 UITextField 后,UIButton 未启用

swift - 除非我滚动,否则页脚不会显示在 TableView 上

ios - 如何从 NSLayoutConstraints 获取 CGFloat 值?

android - 在 Android 上的游戏中购买时 Soomla IAB 错误

android - AppLicensing 失败 - 签名为空字符串

ios - 从字典和数组的 plist 中读取/写入数据,并将不同级别加载到 TableView 中

iOS 重置设备方向

ios - 如何更正聊天和消息弱关系的谓词 CoreData Fetched 属性?

swift - 如何将 CFData 传递给 C char**?