Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] - Fix VPN subscriptions ignoring empty transactions states (uplift to 1.72.x) #26078

Open
wants to merge 1 commit into
base: 1.72.x
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,36 @@ public class BraveVPNInAppPurchaseObserver: NSObject, SKPaymentTransactionObserv

// MARK: - Handling transactions

public func paymentQueue(
_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]
) {
private func processTransactions(_ transactions: [SKPaymentTransaction], on queue: SKPaymentQueue)
{
// This helper variable helps to call the IAPObserverDelegate delegate purchased method only once.
// Reason is when restoring or sometimes when purchasing or restoring a product there's multiple transactions
// that are returned in `transactions` array.
// Apple advices to call `finishTransaction` for all of them,
// but to show the UI we only want to call the delegate method once.
var callPurchaseDelegateOnce = true

// For safety let's start processing from the newest transaction.
transactions
.filter({
($0.payment.productIdentifier == BraveVPNProductInfo.ProductIdentifiers.monthlySub)
|| ($0.payment.productIdentifier == BraveVPNProductInfo.ProductIdentifiers.yearlySub)
})
// Filter for VPN only transactions. We do not want to call `finishTransaction` on other transactions
let vpnTransactions = transactions.filter({
($0.payment.productIdentifier == BraveVPNProductInfo.ProductIdentifiers.monthlySub)
|| ($0.payment.productIdentifier == BraveVPNProductInfo.ProductIdentifiers.yearlySub)
})

// There was no VPN purchases
if vpnTransactions.isEmpty {
let errorRestore = SKError(SKError.unknown, userInfo: ["detail": "not-purchased"])
self.delegate?.purchaseFailed(error: .transactionError(error: errorRestore))
return
}

vpnTransactions
.sorted(by: { $0.transactionDate ?? Date() > $1.transactionDate ?? Date() })
.forEach { transaction in
switch transaction.transactionState {
case .purchased:
Logger.module.debug("Received transaction state: purchased")
// This should be always called, no matter if transaction is successful or not.
SKPaymentQueue.default().finishTransaction(transaction)
queue.finishTransaction(transaction)
if callPurchaseDelegateOnce {
Preferences.VPN.subscriptionProductId.value = transaction.payment.productIdentifier
self.delegate?.purchasedOrRestoredProduct(validateReceipt: true)
Expand All @@ -59,7 +65,7 @@ public class BraveVPNInAppPurchaseObserver: NSObject, SKPaymentTransactionObserv
case .restored:
Logger.module.debug("Received transaction state: restored")
// This should be always called, no matter if transaction is successful or not.
SKPaymentQueue.default().finishTransaction(transaction)
queue.finishTransaction(transaction)

if callPurchaseDelegateOnce {
Preferences.VPN.subscriptionProductId.value = transaction.payment.productIdentifier
Expand All @@ -86,16 +92,26 @@ public class BraveVPNInAppPurchaseObserver: NSObject, SKPaymentTransactionObserv
Logger.module.debug("Received transaction state: purchasing")
case .failed:
Logger.module.debug("Received transaction state: failed")
SKPaymentQueue.default().finishTransaction(transaction)
self.delegate?.purchaseFailed(
error: .transactionError(error: transaction.error as? SKError)
)
queue.finishTransaction(transaction)
if callPurchaseDelegateOnce {
self.delegate?.purchaseFailed(
error: .transactionError(error: transaction.error as? SKError)
)
}
callPurchaseDelegateOnce = false
@unknown default:
assertionFailure("Unknown transactionState")
}
}
}

public func paymentQueue(
_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]
) {
processTransactions(transactions, on: queue)
}

// MARK: - Restoring Transactions

public func paymentQueue(
Expand All @@ -115,7 +131,10 @@ public class BraveVPNInAppPurchaseObserver: NSObject, SKPaymentTransactionObserv

let errorRestore = SKError(SKError.unknown, userInfo: ["detail": "not-purchased"])
delegate?.purchaseFailed(error: .transactionError(error: errorRestore))
return
}

processTransactions(queue.transactions, on: queue)
}

// MARK: - Handling promoted in-app purchases
Expand Down
Loading