iOS 14应用内购买请求登录/验证两次
我正在向应用程序添加一个非消费功能。除了购买操作请求登录外,一切都很好,成功后几乎立即会出现另一个登录表。如果第二次登录成功,订单将进入完成状态,否则将失败。诊断打印跟踪(如下)显示UpdateTransactions观察者将“正在处理”事件视为队列中的单个事件,然后是两个登录表,最后是“已购买”事件。调用时,观察者队列中只有一个项目,而这一切似乎都发生在进程结束时的Apple内部。恢复功能正常工作。这种行为发生在我的设备上,也发生在我的TestFlight测试人员身上。有人知道发生了什么事吗iOS 14应用内购买请求登录/验证两次,ios,swift,in-app-purchase,ios14,Ios,Swift,In App Purchase,Ios14,我正在向应用程序添加一个非消费功能。除了购买操作请求登录外,一切都很好,成功后几乎立即会出现另一个登录表。如果第二次登录成功,订单将进入完成状态,否则将失败。诊断打印跟踪(如下)显示UpdateTransactions观察者将“正在处理”事件视为队列中的单个事件,然后是两个登录表,最后是“已购买”事件。调用时,观察者队列中只有一个项目,而这一切似乎都发生在进程结束时的Apple内部。恢复功能正常工作。这种行为发生在我的设备上,也发生在我的TestFlight测试人员身上。有人知道发生了什么事吗
Buy button tapped
Entered delegate Updated transactions with n = 1 items
TransactionState = 0
Updated Tranaction: purch in process
At this point, the signin sheet appears, pw entered, then a ping and DONE is checked
a few seconds later, the signin re-appears, pw again re-entered and again success signaled on sheet
Entered delegate Updated transactions with n = 1 items
TransactionState = 1
Update Transaction : Successful Purchase
下面是我的相当普通的IAP manager类的代码:
class IAPManager: NSObject {
// MARK: - Properties
static let shared = IAPManager()
// MARK: - Init
private override init() {
super.init()
}
//MARK: - Control methods
func startObserving() {
SKPaymentQueue.default().add(self)
}
func stopObserving() {
SKPaymentQueue.default().remove(self)
}
func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
func peelError(err: SKError) -> String {
let userInfo = err.errorUserInfo
let usr0 = userInfo["NSUnderlyingError"] as! NSError
let usr1 = (usr0.userInfo["NSUnderlyingError"] as! NSError).userInfo
let usr2 = usr1["NSLocalizedDescription"] as? String
return usr2 ?? "\nreason not available"
}
//MARK: - Generic Alert for nonVC instances
func simpleAlert(title:String, message:String) -> Void {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
if #available(iOS 13.0, *) {
var topWindow = UIApplication.shared.currentWindow
if topWindow == nil {
topWindow = UIApplication.shared.currentWindowInactive
}
let topvc = topWindow?.rootViewController?.presentedViewController
topvc?.present(alert, animated: false, completion: nil)
if var topController = UIApplication.shared.keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
// topController.present(alert, animated: false, completion: nil)
}
} else {
var alertWindow : UIWindow!
alertWindow = UIWindow.init(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController.init()
alertWindow.windowLevel = UIWindow.Level.alert + 1
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: false)
}
}
// MARK: - Purchase Products
func buy(product:String) -> Bool {
if !canMakePayments() {return false}
let paymentRequest = SKMutablePayment()
paymentRequest.productIdentifier = product
SKPaymentQueue.default().add(paymentRequest)
return true
}
func restore() -> Void {
SKPaymentQueue.default().restoreCompletedTransactions()
}
}
// MARK: - Methods Specific to my app
func enableBigD() -> Void {
let glob = Globals.shared
let user = UserDefaults.standard
.........
//Post local notification signal that purch success/restored (to change UI in BuyView)
NotificationCenter.default.post(name: Notification.Name(rawValue: NotificationNames.kNotificationBuyDictEvent), object: nil)
}
// MARK: - SKPaymentTransactionObserver
extension IAPManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
//Debugging Code
print("Entered delegate Updated transactions with n = \(transactions.count) items")
for tx in transactions {
print(" TransactionState = \(tx.transactionState.rawValue)")
}
// END debugging code
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:
enableBigD()
print("Update Transaction : Successful Purchase")
SKPaymentQueue.default().finishTransaction(transaction)
simpleAlert(title: "Purchase Confirmed", message: "Thank You!")
case .restored:
print("Update Transaction : Restored Purchase")
enableBigDict()
SKPaymentQueue.default().finishTransaction(transaction)
simpleAlert(title: "Success", message: "Your access has been restored!")
case .failed:
print("Updated Tranaction: FAIL")
if let err = transaction.error as? SKError {
let reason = peelError(err: err)
simpleAlert(title: "Purchase Problem", message: "Sorry, the requested purchase did not complete.\nThe reason was: \n\(err.localizedDescription) because:\(reason)")
}
UserDefaults.standard.set(false, forKey: Keys.kHasPaidForDict)
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred:
print("Updated Transaction: purch deferred ")
if let err = transaction.error as? SKError {
print("purch deferred \(err.localizedDescription)")
}
break
case .purchasing:
print("Updated Tranaction: purch in process ")
break
@unknown default:
print("Update Transaction: UNKNOWN STATE!")
break
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
let err = error.localizedDescription
let reason = peelError(err: error as! SKError)
let fullerr = "The Restore request had a problem. If it persists after retrying, Please send us a note with the Code through the Feedback button in Settings. The Error Code is: \(err) because \(reason)"
simpleAlert(title: "Restore Problem", message: fullerr)
}
} //end Extension of Queue Observer
//MARK: - Extension on UIAppl to get current Window in Scene-based iOS14 environment
// https://stackoverflow.com/questions/57009283/how-get-current-keywindow-equivalent-for-multi-window-scenedelegate-xcode-11
extension UIApplication {
var currentWindow: UIWindow? {
connectedScenes
.filter(({$0.activationState == .foregroundActive}))
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
}
var currentWindowInactive: UIWindow? {
connectedScenes
.filter(({$0.activationState == .foregroundInactive}))
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
}
}
是的,我刚刚写了一篇关于这个的文章。(尚未发布。)基本上,这是一个bug,在整个过程中,应用程序内购买都是为了测试。您只需忽略这样一个事实,即对话框的循环会出现两次 如果您从
paymentQueue(uu2;:updatedTransactions:)
实现中注销(您已经这样做了,除了您应该使用OSLog而不是print
),您将发现那里发生的一切都是正确的。它对对话的双重循环一无所知,这一切完全是在过程之外发生的
只有当您是TestFlight测试人员或使用沙盒测试人员帐户时,才会出现此问题
因此,由于这个问题不会影响代码的工作,而且当真正的用户这样做时不会发生,所以您只需闭上眼睛继续工作。不要担心,并警告您的TestFlight用户,告诉他们也不要担心。谢谢!关于奥斯陆日志的观点。我的print()示例在postHey@matt中是一次性的。你发表那篇文章了吗。我需要向客户解释这种行为。不,对不起,你只能自己解释。:)我知道,这很令人不安。我想确认一下@matt,你指的是登录窗口还是要求用户付费的部分?在第一次完成付款后,我将获得两次付款窗口,控制台日志确实显示paymentQueue被调用了两次。第一次绕州是。采购,第二次绕州是。失败