问题描述
我正在 swift 中测试自动更新的应用内购买,我发现我的代码有一些奇怪的问题。
我正在沙盒环境中测试这些功能
- 用户可以购买一个月、一年的自动续订订阅或永久许可
- 每次用户打开应用程序时,应用程序应检查订阅是否仍然有效,如果不是,则锁定所有高级功能
- 用户可以恢复购买的计划,应用程序应该获取之前购买的类型即。一个月、一年或永久。
经过长时间的教程研究,我仍然对验证感到困惑
- 我看到有两种方法可以验证收据,一种在本地,另一种在服务器上。 但是我没有服务器,是不是只能在本地验证
- 每次自动续订订阅到期时,本地收据不会更新,所以当我重新打开应用程序时,我收到订阅到期警报(我自己定义的用于验证检查的方法),当我单击恢复按钮时,应用成功恢复并更新收据
- 手动恢复并刷新收据6次后(沙盒用户只能更新6次),当我单击恢复按钮时,直到调用部分交易== .purchased,并且我的应用程序解锁了高级功能,但是当我重新打开我的应用,我的应用提醒订阅已过期,这是应该的。
我的核心问题是我每次打开app的时候怎么查看苹果订阅的验证,我没有服务器,不知道为什么收据没有自动刷新
这是我的代码的一些部分,我在打开应用程序时调用 checkUserSubsriptionStatus(),我使用的是 TPInAppReceipt 库
class InAppPurchaseManager {
static var shared = InAppPurchaseManager()
init() {
}
public func getUserPurchaseType() -> PurchaseType {
if let receipt = try? InAppReceipt.localReceipt() {
var purchaseType: PurchaseType = .none
if let purchase = receipt.lastAutoRenewableSubscriptionPurchase(ofProductIdentifier: PurchaseType.oneMonth.productID) {
purchaseType = .oneMonth
}
if let purchase = receipt.lastAutoRenewableSubscriptionPurchase(ofProductIdentifier: PurchaseType.oneYear.productID) {
purchaseType = .oneYear
}
if receipt.containsPurchase(ofProductIdentifier: PurchaseType.permanent.productID) {
purchaseType = .permanent
}
return purchaseType
} else {
print("Receipt not found")
return .none
}
}
public func restorePurchase(in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
if SKPaymentQueue.canMakePayments() {
SKPaymentQueue.default().restoreCompletedTransactions()
} else {
self.userIsNotAbletoPurchase()
}
}
public func checkUserSubsriptionStatus() {
dispatchQueue.main.async {
if let receipt = try? InAppReceipt.localReceipt() {
self.checkUserPermanentSubsriptionStatus(with: receipt)
}
}
}
private func checkUserPermanentSubsriptionStatus(with receipt: InAppReceipt) {
if let receipt = try? InAppReceipt.localReceipt() { //Check permsnent subscription
if receipt.containsPurchase(ofProductIdentifier: PurchaseType.permanent.productID) {
print("User has permament permission")
if !AppEngine.shared.currentUser.isVip {
self.updateAfterappPurchased(withType: .permanent)
}
} else {
self.checkUserAutoRenewableSubsrption(with: receipt)
}
}
}
private func checkUserAutoRenewableSubsrption(with receipt: InAppReceipt) {
if receipt.hasActiveAutoRenewablePurchases {
print("Subsription still valid")
if !AppEngine.shared.currentUser.isVip {
let purchaseType = InAppPurchaseManager.shared.getUserPurchaseType()
updateAfterappPurchased(withType: purchaseType)
}
} else {
print("Subsription expired")
if AppEngine.shared.currentUser.isVip {
self.subsrptionCheckFailed()
}
}
}
private func updateAfterappPurchased(withType purchaseType: PurchaseType) {
AppEngine.shared.currentUser.purchasedType = purchaseType
AppEngine.shared.currentUser.energy += 5
AppEngine.shared.userSetting.hasViewedEnergyUpdate = false
AppEngine.shared.saveUser()
AppEngine.shared.notifyAllUIObservers()
}
public func updateAfterEnergyPurchased() {
AppEngine.shared.currentUser.energy += 3
AppEngine.shared.saveUser()
AppEngine.shared.notifyAllUIObservers()
}
public func purchaseApp(with purchaseType: PurchaseType,in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
if SKPaymentQueue.canMakePayments() {
let paymentRequest = SKMutablePayment()
paymentRequest.productIdentifier = purchaseType.productID
SKPaymentQueue.default().add(paymentRequest)
} else {
self.userIsNotAbletoPurchase()
}
}
public func purchaseEnergy(in viewController: SKPaymentTransactionObserver) {
SKPaymentQueue.default().add(viewController)
let productID = "com.crazycat.Reborn.threePointOfEnergy"
if SKPaymentQueue.canMakePayments() {
let paymentRequest = SKMutablePayment()
paymentRequest.productIdentifier = productID
SKPaymentQueue.default().add(paymentRequest)
} else {
self.userIsNotAbletoPurchase()
}
}
}
解决方法
如果您无法使用服务器,则需要在本地进行验证。由于您已经包含 TPInAppReceipt 库,因此这相对容易。
要检查用户是否拥有有效的高级产品及其类型,您可以使用以下代码:
// Get all active purchases which are convertible to `PurchaseType`.
let premiumPurchases = receipt.activeAutoRenewableSubscriptionPurchases.filter({ PurchaseType(rawValue: $0.productIdentifier) != nil })
// It depends on how your premium access works,but if it doesn't matter what kind of premium the user has,it is enough to take one of the available active premium products.
// Note: with the possibility to share subscriptions via family sharing,the receipt can contain multiple active subscriptions.
guard let product = premiumPurchases.first else {
// User has no active premium product => lock all premium features
return
}
// To be safe you can use a "guard" or a "if let",but since we filtered for products conforming to PurchaseType,this shouldn't fail
let purchaseType = PurchaseType(rawValue: product.productIdentifier)!
// => Setup app corresponding to active premium product type
我在您的代码中注意到可能导致问题的一点是您不断添加新的 SKPaymentTransactionObserver
。您应该有一个符合 SKPaymentTransactionObserver
的类,并且只在应用启动时添加一次,而不是在每次公开调用时添加。此外,当您不再需要它时,您需要将其删除(如果您只创建了一次,则应在类的 deinit
中进行,符合观察者协议。
我认为这就是第 2 点的原因。
从技术上讲,第 3 点中描述的行为是正确的,因为您使用的方法要求付款队列恢复之前完成的所有购买(请参阅 here)。
Apple 声明 restoreCompletedTransactions()
应仅用于以下场景(请参阅 here):
- 如果您使用 Apple 托管的内容,恢复已完成的交易将为您的应用提供用于下载内容的交易对象。
- 如果您需要支持 iOS 7 之前的 iOS 版本,但应用收据不可用,请改为恢复已完成的交易。
- 如果您的应用使用非续订订阅,则您的应用负责恢复过程。
对于您的情况,建议使用 SKReceiptRefreshRequest
,它请求更新当前收据。
通过调用 AppDelegate 中的方法在每次应用启动时获取收据。
getAppReceipt(forTransaction: nil)
现在,下面是所需的方法:
func getAppReceipt(forTransaction transaction: SKPaymentTransaction?) {
guard let receiptURL = receiptURL else { /* receiptURL is nil,it would be very weird to end up here */ return }
do {
let receipt = try Data(contentsOf: receiptURL)
receiptValidation(receiptData: receipt,transaction: transaction)
} catch {
// there is no app receipt,don't panic,ask apple to refresh it
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
// If all goes well control will land in the requestDidFinish() delegate method.
// If something bad happens control will land in didFailWithError.
}
}
这是方法receiptValidation:
func receiptValidation(receiptData: Data?,transaction: SKPaymentTransaction?) {
guard let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) else { return }
verify_in_app_receipt(with_receipt_string: receiptString,transaction: transaction)
}
接下来是验证收据和获取订阅到期日期的最终方法:
func verify_in_app_receipt(with_receipt_string receiptString: String,transaction: SKPaymentTransaction?) {
let params: [String: Any] = ["receipt-data": receiptString,"password": "USE YOUR PASSWORD GENERATED FROM ITUNES","exclude-old-transactions": true]
// Below are the url's used for in app receipt validation
//appIsInDevelopment ? "https://sandbox.itunes.apple.com/verifyReceipt" : "https://buy.itunes.apple.com/verifyReceipt"
super.startService(apiType: .verify_in_app_receipt,parameters: params,files: [],modelType: SubscriptionReceipt.self) { (result) in
switch result {
case .Success(let receipt):
if let receipt = receipt {
print("Receipt is: \(receipt)")
if let _ = receipt.latest_receipt,let receiptArr = receipt.latest_receipt_info {
var expiryDate: Date? = nil
for latestReceipt in receiptArr {
if let dateInMilliseconds = latestReceipt.expires_date_ms,let product_id = latestReceipt.product_id {
let date = Date(timeIntervalSince1970: dateInMilliseconds / 1000)
if date >= Date() {
// Premium is valid
}
}
}
if expiryDate == nil {
// Premium is not purchased or is expired
}
}
}
case .Error(let message):
print("Error in api is: \(message)")
}
}
}