将 IAP 存储在 UserDefaults 中,这样用户就不需要在每次按下 "purchasable" 按钮时都恢复购买
Storing IAP in UserDefaults so the user doesn't need to restore the purchase every time a "purchasable" button is pressed
背景:我的应用程序提供了来自一组数组的大量提示。 'packs' 确定可以打开哪些提示。您从默认包开始,然后可以购买三个额外的包(packA、packB 和 packC)作为非消耗性 IAP。
我的目标是让用户支付一次,然后就可以在 he/she 喜欢的任何时候访问包;然而,一旦沙盒用户创建了 IAP,弹出窗口会显示 "You've already purchased this. Would you like to get it again for free?"。我显然不希望每次用户选择一个包时弹出这个。有没有办法让购买永久使用而不需要经常恢复购买?
以下是我当前的代码(匿名并简化为仅包含基本组件):
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
。我对此很陌生,所以非常感谢任何帮助。
谢谢
基础知识
购买完成后,您可以在 Bundle.main.appStoreReceiptURL
处查看收据。您可以使用该收据并根据 App Store 对其进行验证,以防止有人使用假收据。
验证响应会告诉您收据是合法的并且没有被退款。它还会告诉您订阅仍然有效以及何时到期。
您可以在客户端执行所有这些操作,但最佳做法是将收据存储在服务器上,这样您就可以在您的终端对其进行验证,而不是将其留给客户端进行潜在的操作。
Do not call the App Store server verifyReceipt endpoint from
your app. You can't build a trusted connection between a user’s device
and the App Store directly, because you don’t control either end of
that connection, which makes it susceptible to a man-in-the-middle
attack.
您可以在 docs. In addition you can set up hooks 中找到包括代码示例在内的更多信息,以便在订阅状态更改时主动通知您的服务器。
PoC
如果您不关心上述安全隐患而只想做一个概念验证,您可以在应用程序中执行此操作并检查用户是否已经购买:
- 从
Bundle.main.appStoreReceiptURL?.path
获取收据
- 按照所述 here 使用 App Store 服务器验证收据;请注意,沙盒和生产应用商店有不同的验证 URL
- 从验证响应中获取
product_id
和 expires_date
以了解已购买的产品以及是否已过期
请记住,如果用户卸载并重新安装应用程序,收据不会存储在设备上。然后用户必须恢复购买,应用程序从 App Store 服务器下载收据。这就是当您按下您可能在其他应用程序中熟悉的 "Restore Purchase" 按钮时发生的情况。恢复只需使用 SKReceiptRefreshRequest
.
背景:我的应用程序提供了来自一组数组的大量提示。 'packs' 确定可以打开哪些提示。您从默认包开始,然后可以购买三个额外的包(packA、packB 和 packC)作为非消耗性 IAP。
我的目标是让用户支付一次,然后就可以在 he/she 喜欢的任何时候访问包;然而,一旦沙盒用户创建了 IAP,弹出窗口会显示 "You've already purchased this. Would you like to get it again for free?"。我显然不希望每次用户选择一个包时弹出这个。有没有办法让购买永久使用而不需要经常恢复购买?
以下是我当前的代码(匿名并简化为仅包含基本组件):
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
。我对此很陌生,所以非常感谢任何帮助。
谢谢
基础知识
购买完成后,您可以在 Bundle.main.appStoreReceiptURL
处查看收据。您可以使用该收据并根据 App Store 对其进行验证,以防止有人使用假收据。
验证响应会告诉您收据是合法的并且没有被退款。它还会告诉您订阅仍然有效以及何时到期。
您可以在客户端执行所有这些操作,但最佳做法是将收据存储在服务器上,这样您就可以在您的终端对其进行验证,而不是将其留给客户端进行潜在的操作。
Do not call the App Store server verifyReceipt endpoint from your app. You can't build a trusted connection between a user’s device and the App Store directly, because you don’t control either end of that connection, which makes it susceptible to a man-in-the-middle attack.
您可以在 docs. In addition you can set up hooks 中找到包括代码示例在内的更多信息,以便在订阅状态更改时主动通知您的服务器。
PoC
如果您不关心上述安全隐患而只想做一个概念验证,您可以在应用程序中执行此操作并检查用户是否已经购买:
- 从
Bundle.main.appStoreReceiptURL?.path
获取收据
- 按照所述 here 使用 App Store 服务器验证收据;请注意,沙盒和生产应用商店有不同的验证 URL
- 从验证响应中获取
product_id
和expires_date
以了解已购买的产品以及是否已过期
请记住,如果用户卸载并重新安装应用程序,收据不会存储在设备上。然后用户必须恢复购买,应用程序从 App Store 服务器下载收据。这就是当您按下您可能在其他应用程序中熟悉的 "Restore Purchase" 按钮时发生的情况。恢复只需使用 SKReceiptRefreshRequest
.