mirror of
https://github.com/ProtonMail/protoncore_ios.git
synced 2026-01-16 23:00:24 +00:00
fix: Correct flow completion for purchases initiated during signup [MAILIOS-4363]
Refs: https://gitlab.protontech.ch/apple/shared/protoncore/-/merge_requests/1852
This commit is contained in:
commit
ccb654dd5d
5 changed files with 28 additions and 29 deletions
|
|
@ -29,6 +29,7 @@ import ProtonCorePayments
|
|||
import ProtonCorePaymentsUI
|
||||
import ProtonCoreHumanVerification
|
||||
import ProtonCoreFoundations
|
||||
import ProtonCoreLog
|
||||
|
||||
enum FlowStartKind {
|
||||
case over(UIViewController, UIModalTransitionStyle)
|
||||
|
|
@ -575,6 +576,7 @@ extension SignupCoordinator: CompleteViewControllerDelegate {
|
|||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
private func errorHandler(error: Error) {
|
||||
PMLog.error(error, sendToExternal: true)
|
||||
longTermTask.inProgress = false
|
||||
if activeViewController != nil {
|
||||
navigationController?.popViewController(animated: true)
|
||||
|
|
|
|||
|
|
@ -126,8 +126,9 @@ class PaymentsManager {
|
|||
break
|
||||
case .toppedUpCredits:
|
||||
completionHandler(.success(()))
|
||||
case .planPurchaseProcessingInProgress:
|
||||
break
|
||||
case .planPurchaseProcessingInProgress(let plan):
|
||||
self?.selectedPlan = plan
|
||||
completionHandler(.success(()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -154,11 +155,13 @@ class PaymentsManager {
|
|||
}
|
||||
|
||||
case .right(let planDataSource):
|
||||
Task { [weak self] in
|
||||
do {
|
||||
try await planDataSource.fetchCurrentPlan()
|
||||
self?.payments.storeKitManager.retryProcessingAllPendingTransactions { [weak self] in
|
||||
|
||||
payments.storeKitManager.retryProcessingAllPendingTransactions { [weak self] in
|
||||
Task { [weak self] in
|
||||
do {
|
||||
var possiblyPurchasedPlan: InAppPurchasePlan?
|
||||
try await planDataSource.fetchCurrentPlan()
|
||||
|
||||
if planDataSource.currentPlan?.hasExistingProtonSubscription ?? false {
|
||||
possiblyPurchasedPlan = self?.selectedPlan
|
||||
}
|
||||
|
|
@ -166,9 +169,9 @@ class PaymentsManager {
|
|||
self?.restoreExistingDelegate()
|
||||
self?.payments.storeKitManager.unsubscribeFromPaymentQueue()
|
||||
completionHandler(.success(possiblyPurchasedPlan))
|
||||
} catch {
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
} catch {
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,6 +245,10 @@ final class PurchaseManager: PurchaseManagerProtocol {
|
|||
finishCallback(.toppedUpCredits)
|
||||
} else if case .autoRenewal = result {
|
||||
finishCallback(.renewalNotification)
|
||||
} else if case .withoutObtainingToken = result { // purchase during signup flow
|
||||
finishCallback(.planPurchaseProcessingInProgress(processingPlan: plan))
|
||||
} else if case .withoutExchangingToken = result { // purchase during signup flow
|
||||
finishCallback(.planPurchaseProcessingInProgress(processingPlan: plan))
|
||||
} else {
|
||||
finishCallback(.purchasedPlan(accountPlan: plan))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -529,6 +529,7 @@ final class StoreKitManager: NSObject, StoreKitManagerProtocol {
|
|||
errorCompletion: @escaping ErrorCallback,
|
||||
deferredCompletion: (() -> Void)? = nil) {
|
||||
|
||||
subscribeToPaymentQueue()
|
||||
let callbackCacheKey = UserInitiatedPurchaseCache(storeKitProductId: storeKitProduct.productIdentifier,
|
||||
hashedUserId: hashedUserId)
|
||||
threadSafeCache.set(value: successCompletion, for: callbackCacheKey, in: \.successCompletion)
|
||||
|
|
@ -685,7 +686,7 @@ extension StoreKitManager: SKPaymentTransactionObserver {
|
|||
// reappearing on every run
|
||||
callSuccessCompletion(for: cacheKey, with: .withPurchaseAlreadyProcessed)
|
||||
@unknown default:
|
||||
break
|
||||
PMLog.error("Unexpected unknown transaction state: \(transaction.transactionState)", sendToExternal: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -744,6 +745,7 @@ extension StoreKitManager: SKPaymentTransactionObserver {
|
|||
try informProtonBackendAboutPurchasedTransaction(transaction, cacheKey: cacheKey, completion: completion)
|
||||
|
||||
} catch Errors.haveTransactionOfAnotherUser { // user login error
|
||||
PMLog.error("Error: transaction is from another account", sendToExternal: true)
|
||||
confirmUserValidationBypass(cacheKey, Errors.haveTransactionOfAnotherUser) { [weak self] in
|
||||
self?.transactionsQueue.addOperation { [weak self] in
|
||||
self?.processStoreKitTransaction(transaction: transaction, shouldVerifyPurchaseWasForSameAccount: false)
|
||||
|
|
@ -752,16 +754,19 @@ extension StoreKitManager: SKPaymentTransactionObserver {
|
|||
}
|
||||
|
||||
} catch Errors.receiptLost { // receipt error
|
||||
PMLog.error("Error: lost receipt", sendToExternal: true)
|
||||
callErrorCompletion(for: cacheKey, with: Errors.receiptLost)
|
||||
finishTransaction(transaction, nil)
|
||||
group.leave()
|
||||
|
||||
} catch Errors.noNewSubscriptionInSuccessfulResponse { // error on BE
|
||||
PMLog.error("Error: no new subscription in success response", sendToExternal: true)
|
||||
callErrorCompletion(for: cacheKey, with: Errors.noNewSubscriptionInSuccessfulResponse)
|
||||
finishTransaction(transaction, nil)
|
||||
group.leave()
|
||||
|
||||
} catch Errors.alreadyPurchasedPlanDoesNotMatchBackend {
|
||||
PMLog.error("Error: Already purchased plan does not match backend", sendToExternal: true)
|
||||
callErrorCompletion(for: cacheKey, with: Errors.alreadyPurchasedPlanDoesNotMatchBackend)
|
||||
|
||||
if featureFlagsRepository.isEnabled(CoreFeatureFlagType.dynamicPlan) &&
|
||||
|
|
@ -773,6 +778,7 @@ extension StoreKitManager: SKPaymentTransactionObserver {
|
|||
|
||||
group.leave()
|
||||
} catch let error { // other errors
|
||||
PMLog.error("Error: \(error)", sendToExternal: true)
|
||||
callErrorCompletion(for: cacheKey, with: error)
|
||||
// should we call finishTransaction here, to avoid leaving transactions for the next run?
|
||||
group.leave()
|
||||
|
|
@ -862,8 +868,10 @@ extension StoreKitManager: SKPaymentTransactionObserver {
|
|||
do {
|
||||
let customCompletion: ProcessCompletionCallback = { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
ObservabilityEnv.report(.paymentSubscribeTotal(status: .successful, isDynamic: isDynamic))
|
||||
case let .finished(result):
|
||||
if case .withoutExchangingToken = result { } else {
|
||||
ObservabilityEnv.report(.paymentSubscribeTotal(status: .successful, isDynamic: isDynamic))
|
||||
}
|
||||
case .errored, .erroredWithUnspecifiedError:
|
||||
ObservabilityEnv.report(.paymentSubscribeTotal(status: .failed, isDynamic: isDynamic))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,24 +103,6 @@ public extension StoreKitManagerProtocol {
|
|||
retryProcessingAllPendingTransactions(finishHandler: finishHandler)
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "Please use SuccessCallback")
|
||||
typealias OldDeprecatedSuccessCallback = (PaymentToken?) -> Void
|
||||
|
||||
@available(*, deprecated, message: "Switch to variant using the SuccessCallback")
|
||||
func purchaseProduct(plan: InAppPurchasePlan,
|
||||
amountDue: Int,
|
||||
successCompletion: @escaping OldDeprecatedSuccessCallback,
|
||||
errorCompletion: @escaping ErrorCallback,
|
||||
deferredCompletion: FinishCallback?) {
|
||||
purchaseProduct(plan: plan, amountDue: amountDue, successCompletion: { result in
|
||||
switch result {
|
||||
case .withoutExchangingToken(let token):
|
||||
successCompletion(token)
|
||||
case .cancelled, .withoutIAP, .withoutObtainingToken, .withPurchaseAlreadyProcessed, .resolvingIAPToSubscription, .resolvingIAPToCredits, .resolvingIAPToCreditsCausedByError, .autoRenewal:
|
||||
successCompletion(nil)
|
||||
}
|
||||
}, errorCompletion: errorCompletion, deferredCompletion: deferredCompletion)
|
||||
}
|
||||
}
|
||||
|
||||
protocol PaymentQueueProtocol {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue