在實際設備上測驗中斷的購買,下面的第 10 項不會出現在同一會話中。它只會出現在
- 應用程式重新啟動
- 應用程式進入后臺,然后回傳前臺(對于#2,我認為這是因為,當應用程式回傳前臺時,
TransactionObserver再次呼叫?)
這個 SO 也談到了類似的事情:

uj5u.com熱心網友回復:
許多周以來,我一直在努力尋找解決這種情況的方法。通過許多 SO 以及來自蘋果論壇的執行緒。
參考執行緒:
- https://developer.apple.com/forums/thread/674081
- https://developer.apple.com/forums/thread/671492
- https://developer.apple.com/forums/thread/685938
- Apple In App Purchase,在沙盒中中斷購買
根據 Apple Docs,在用戶同意 T&C/繼續中斷的購買后,中斷的交易應該發送 FAIL 然后 PURCHASED(但實際上它似乎沒有發生 - 請參閱上面的鏈接執行緒)
這就是我最終得到的當前解決方案。這并不完全理想,但鑒于這種情況,它是最好的(我發現的)。請參閱鏈接 #3,其中用戶觀察到呼叫restoreCompletedTransactions()實際上能夠讓事務觀察者處理(已完成)中斷的事務。這樣做,用戶不必關閉(到后臺)然后再次打開應用程式。(請注意,我嘗試了各種再次呼叫事務觀察者的方法,但都沒有幫助)
這是我的解決方案。
private func purchaseFailed(_ transaction: SKPaymentTransaction) {
var failureReason: String = ""
var message: String = ""
var code = Int()
print("\(FormatDisplay.datems(Date())) [IAP] Transcation FAILED/CANCELLED")
// https://stackoverflow.com/q/55641652/14414215
// https://adapty.io/blog/ios-in-app-purchases-part-5-list-of-skerror-codes-and-how-to-handle-them
if let skError = transaction.error as? SKError {
switch skError.code { // https://developer.apple.com/reference/storekit/skerror.code
case .unknown:
// https://developer.apple.com/forums/thread/674081
if let underlyingError = skError.userInfo["NSUnderlyingError"] as? NSError {
if underlyingError.code == 3038 {
print(">> General conditions have changed, don't display an error for the interrupted transaction")
failureReason = "ERROR: Unknown Error. Transaction Interrupted"
message = "Transaction Interrupted. Your Purchase is being processed. Please Check Back in 5mins."
code = underlyingError.code
} else {
failureReason = "Unknown or unexpected error occurred"
message = "Oops, something unknown occurred or the transaction was interrupted. If the interrupted purchase was successful, please check back in 5mins."
code = skError.code.rawValue
}
}
break
case .clientInvalid:
failureReason = "ERROR: Invalid Client"
message = "The purchase cannot be completed. Please, change your account or device."
code = skError.code.rawValue
break
case .paymentCancelled:
failureReason = "ERROR: User Cancelled Payment"
message = ""
code = skError.code.rawValue
break
case .paymentInvalid:
failureReason = "ERROR: Invalid Payment"
message = "Your purchase was declined. Please, check the payment details and make sure there are enough funds in your account."
code = skError.code.rawValue
break
case .paymentNotAllowed:
failureReason = "ERROR: Payment not allowed"
message = "The purchase is not available for the selected payment method. Please, make sure your payment method allows you to make online purchases."
code = skError.code.rawValue
break
case .storeProductNotAvailable:
failureReason = "ERROR: Store product not available"
message = "This product is not available in your region. Please, change the store and try again"
code = skError.code.rawValue
break
case .cloudServicePermissionDenied:
failureReason = "ERROR: Cloud service permission denied"
message = "Your purchase was declined"
code = skError.code.rawValue
break
case .cloudServiceNetworkConnectionFailed:
failureReason = "ERROR: Cloud service network connection failed"
message = "he purchase cannot be completed because your device is not connected to the Internet. Please, try again later with a stable internet connection"
code = skError.code.rawValue
break
case .cloudServiceRevoked:
failureReason = "ERROR: Cloud service revoked"
message = "Sorry, an error has occurred."
code = skError.code.rawValue
break
case .privacyAcknowledgementRequired:
failureReason = "ERROR: Privacy Acknowledgement Required"
message = "The purchase cannot be completed because you have not accepted the terms of use of the AppStore. Please, confirm your consent in the settings and then return to the purchase."
code = skError.code.rawValue
break
case .unauthorizedRequestData:
failureReason = "ERROR: Unauthorized Request Data"
message = "An error has occurred. Please, try again later."
code = skError.code.rawValue
break
case .invalidOfferIdentifier:
failureReason = "ERROR: Invalid offer identifier"
message = "The promotional offer is invalid or expired."
code = skError.code.rawValue
break
case .invalidSignature:
failureReason = "ERROR: Invalid Signature"
message = "Sorry, an error has occurred when applying the promo code. Please, try again later."
code = skError.code.rawValue
break
case .missingOfferParams:
failureReason = "ERROR: Missing offer params"
message = "Sorry, an error has occurred when applying the promo code. Please, try again later."
code = skError.code.rawValue
break
case .invalidOfferPrice:
failureReason = "ERROR: Invalid offer price"
message = "Sorry, your purchase cannot be completed. Please, try again later."
code = skError.code.rawValue
break
case .overlayCancelled:
failureReason = "ERROR: overlay Cancelled"
message = ""
code = skError.code.rawValue
break
case .overlayInvalidConfiguration:
failureReason = "ERROR: Overlay Invalid Configuration"
message = ""
code = skError.code.rawValue
break
case .overlayTimeout:
failureReason = "ERROR: Overlay Timeout"
message = ""
code = skError.code.rawValue
break
case .ineligibleForOffer:
failureReason = "ERROR: Ineligible Offer"
message = "Sorry, your purchase cannot be completed. Please, try again later."
code = skError.code.rawValue
break
case .unsupportedPlatform:
failureReason = "ERROR: Unsupported Platform"
message = "Sorry, unsupported Platform"
code = skError.code.rawValue
break
case .overlayPresentedInBackgroundScene:
failureReason = "ERROR: Overlay Presented In Background Scene"
message = ""
code = skError.code.rawValue
break
@unknown default:
failureReason = "ERROR: Unknown Default"
message = "Oops. Something Has Happened. Please try again later."
code = skError.code.rawValue
break
}
let title = "Error"
let errorMsg = failureReason
if message != "" {
DispatchQueue.main.asyncAfter(deadline: .now() 0.5) {
DisplayAlert.presentIapFailed(title: title, message: message, errMsg: errorMsg)
}
}
failureReason = " ErrorCode: \(code)"
}
print("\(FormatDisplay.datems(Date())) [IAP] \(failureReason)")
print("\(FormatDisplay.datems(Date())) [IAP] -- \(transaction.error?.localizedDescription ?? "")")
print("\(FormatDisplay.datems(Date())) [IAP] -- \(transaction.error.debugDescription)")
print("\(FormatDisplay.datems(Date())) [IAP] -- Calling SKPaymentQ.FinishTransaction")
SKPaymentQueue.default().finishTransaction(transaction)
purchaseCompletionHandler?(true)
}
這是用戶按下確定按鈕后purchaseIapFailed()呼叫的內容restoreCompletedPurchases()。
static func presentIapFailed(title: String, message: String, errMsg: String) {
let root = UIApplication.shared.keyWindow?.rootViewController
let alertController = UIAlertController(title: title,
message: message,
preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: {( action: UIAlertAction ) in
print(" displayAlert - presentIapFailed: \(errMsg) - OK Pressed")
// Call RestorePurchases to force replaying the transaction list.
Medals.store.restorePurchases()
}))
root?.present(alertController, animated: true, completion: nil)
}
一旦交易被“恢復”,那么它將繼續觸發后續代碼以跟進購買,發送通知(購買完成/成功),然后會彈出警告訊息告訴用戶購買成功消耗品已入賬。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/394796.html
