• 赚钱入口【需求资源】限时招募流量主、渠道主,站长合作;【合作模式】CPS长期分成,一次推广永久有收益。主动打款,不扣量;

iOS 14 In-App Purchase requests sign-in / validation twice

iOS cps12345 1个月前 (12-05) 49次浏览 0个评论

我正在向应用程序添加购买功能。除了“购买”操作要求登录外,其他所有操作均正常进行,该操作要求登录,登录成功后,几乎立即会出现另一个登录表。如果第二次登录成功,则订单将完成,否则将失败。诊断打印跟踪(如下所示)显示UpdateTransactions Observer将“正在处理”事件视为队列中的单个事件,随后是两个登录表,然后是“已购买”事件。调用时,观察者队列中只有一个项目,而这似乎都发生在Apple内部,过程结束。恢复功能正常工作。此行为在我的设备上以及我的TestFlight Beta人员中都本地发生。

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管理器类的代码:

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
    }
}

解决

您只需要忽略对话框循环显示两次的事实。

如果您从paymentQueue(_:updatedTransactions:)实现中注销该代码(已经完成,除了应该使用OSLog代替print),您会发现那里的所有事情都在正确地进行。它对对话框的双循环一无所知,而对话的双循环全都发生在过程之外。

并且仅当您是TestFlight测试人员或使用沙箱测试人员帐户时才会出现此问题。

因此,自本次发行并不会影响代码的工作,并且因为它不会当一个真正的用户做这一点,你只需要闭上眼睛,继续工作。不必担心,并警告您的TestFlight用户,并告诉他们也不要担心。

 

喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址