From f73d8092c8e685652958e93f999910059c38ff1b Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Mon, 10 Aug 2026 13:35:50 +0200 Subject: [PATCH 1/6] Add payment cancellation reason --- .../PONativeAlternativePaymentComponent.swift | 2 +- ...eAlternativePaymentCancelationReason.swift | 25 +++++++++++++++++++ ...eAlternativePaymentDefaultInteractor.swift | 19 ++++++++++---- .../NativeAlternativePaymentInteractor.swift | 3 +++ ...ultNativeAlternativePaymentViewModel.swift | 6 ++--- 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift index a838ab825..ea2e912ef 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift @@ -52,7 +52,7 @@ public final class PONativeAlternativePaymentComponent { /// Cancels the ongoing payment. public func cancel() { - interactor.cancel() + interactor.cancel(reason: .programmatic) } // MARK: - Internal diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift new file mode 100644 index 000000000..fefe9fb7b --- /dev/null +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift @@ -0,0 +1,25 @@ +// +// NativeAlternativePaymentCancelationReason.swift +// ProcessOut +// +// Created by Andrii Vysotskyi on 10.08.2026. +// + +import Foundation + +/// Describes the reason why the payment flow was cancelled. +enum NativeAlternativePaymentCancelationReason { + + /// The customer explicitly cancelled the payment flow (for example, by tapping a Cancel button). + case customer + + /// The payment flow was cancelled because the associated objects were released as part of the + /// application's lifecycle. + /// + /// This commonly happens when the screen is dismissed, but it can also occur for other reasons + /// that result in the flow being deallocated. + case lifecycle + + /// The payment flow was explicitly cancelled by the application code. + case programmatic +} diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift index 477dd94ba..55f784916 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift @@ -151,7 +151,15 @@ final class NativeAlternativePaymentDefaultInteractor: state = .redirecting(newState) } + func didRequestCancelConfirmation() { + send(event: .didRequestCancelConfirmation) + } + override func cancel() { + cancel(reason: .programmatic) + } + + func cancel(reason: NativeAlternativePaymentCancelationReason) { switch state { case .starting(let currentState): currentState.task.cancel() @@ -168,11 +176,12 @@ final class NativeAlternativePaymentDefaultInteractor: default: break } - setFailureState(error: POFailure(message: "Alternative payment has been canceled.", code: .Mobile.cancelled)) - } - - func didRequestCancelConfirmation() { - send(event: .didRequestCancelConfirmation) + setFailureState( + error: POFailure( + message: "Alternative payment has been canceled. Reason: \(reason).", + code: .Mobile.cancelled + ) + ) } // MARK: - Private Properties diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift index 23142538e..40e9754e7 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift @@ -33,4 +33,7 @@ protocol NativeAlternativePaymentInteractor: Interactor Date: Mon, 10 Aug 2026 13:37:39 +0200 Subject: [PATCH 2/6] Cancel payment when component lifecycle ends --- .../Component/PONativeAlternativePaymentComponent.swift | 4 ++++ .../NativeAlternativePaymentDefaultInteractor.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift index ea2e912ef..cedadf73a 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift @@ -45,6 +45,10 @@ public final class PONativeAlternativePaymentComponent { self.interactor = interactor } + deinit { + Task { @MainActor [interactor] in interactor.cancel(reason: .lifecycle) } + } + /// Starts the payment. public func start() async { await interactor.start() diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift index 55f784916..778357066 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift @@ -178,7 +178,7 @@ final class NativeAlternativePaymentDefaultInteractor: } setFailureState( error: POFailure( - message: "Alternative payment has been canceled. Reason: \(reason).", + message: "Alternative payment has been canceled. Reason: '\(reason)'.", code: .Mobile.cancelled ) ) From 8d248396cd0d5c2ad92c1dba918f16340bcc1c53 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Mon, 10 Aug 2026 13:39:00 +0200 Subject: [PATCH 3/6] Cancel tokenization when component lifecycle ends --- .../Component/POCardTokenizationComponent.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Component/POCardTokenizationComponent.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Component/POCardTokenizationComponent.swift index 224b4e3d4..6a05d657c 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Component/POCardTokenizationComponent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Component/POCardTokenizationComponent.swift @@ -26,6 +26,10 @@ public final class POCardTokenizationComponent { self.interactor = interactor } + deinit { + Task { @MainActor [interactor] in interactor.cancel() } + } + /// Starts the card tokenization process. public func tokenize() { interactor.tokenize() From 4b0fe08e4add775c8a380c745ce5c7081bc5eca9 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Mon, 10 Aug 2026 13:40:49 +0200 Subject: [PATCH 4/6] Update comment --- .../Interactor/NativeAlternativePaymentCancelationReason.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift index fefe9fb7b..9b15b7f7a 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift @@ -14,7 +14,7 @@ enum NativeAlternativePaymentCancelationReason { case customer /// The payment flow was cancelled because the associated objects were released as part of the - /// application's lifecycle. + /// lifecycle. /// /// This commonly happens when the screen is dismissed, but it can also occur for other reasons /// that result in the flow being deallocated. From 289e412ea8677a1b9aeb573bfff558103e58a581 Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Mon, 10 Aug 2026 13:53:09 +0200 Subject: [PATCH 5/6] Avoid canceling from component to prevent breaking changes --- .../Component/POCardTokenizationComponent.swift | 4 ---- .../Component/PONativeAlternativePaymentComponent.swift | 4 ---- 2 files changed, 8 deletions(-) diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Component/POCardTokenizationComponent.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Component/POCardTokenizationComponent.swift index 6a05d657c..224b4e3d4 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Component/POCardTokenizationComponent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Component/POCardTokenizationComponent.swift @@ -26,10 +26,6 @@ public final class POCardTokenizationComponent { self.interactor = interactor } - deinit { - Task { @MainActor [interactor] in interactor.cancel() } - } - /// Starts the card tokenization process. public func tokenize() { interactor.tokenize() diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift index cedadf73a..ea2e912ef 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Component/PONativeAlternativePaymentComponent.swift @@ -45,10 +45,6 @@ public final class PONativeAlternativePaymentComponent { self.interactor = interactor } - deinit { - Task { @MainActor [interactor] in interactor.cancel(reason: .lifecycle) } - } - /// Starts the payment. public func start() async { await interactor.start() From 6d414d24a2563dcbf7d3e700b16ff96a0cfd225b Mon Sep 17 00:00:00 2001 From: Andrii Vysotskyi Date: Mon, 10 Aug 2026 14:00:32 +0200 Subject: [PATCH 6/6] Update naming --- ...swift => NativeAlternativePaymentCancellationReason.swift} | 4 ++-- .../NativeAlternativePaymentDefaultInteractor.swift | 2 +- .../Interactor/NativeAlternativePaymentInteractor.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/{NativeAlternativePaymentCancelationReason.swift => NativeAlternativePaymentCancellationReason.swift} (86%) diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancellationReason.swift similarity index 86% rename from Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift rename to Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancellationReason.swift index 9b15b7f7a..d82d0c226 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancelationReason.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentCancellationReason.swift @@ -1,5 +1,5 @@ // -// NativeAlternativePaymentCancelationReason.swift +// NativeAlternativePaymentCancellationReason.swift // ProcessOut // // Created by Andrii Vysotskyi on 10.08.2026. @@ -8,7 +8,7 @@ import Foundation /// Describes the reason why the payment flow was cancelled. -enum NativeAlternativePaymentCancelationReason { +enum NativeAlternativePaymentCancellationReason { /// The customer explicitly cancelled the payment flow (for example, by tapping a Cancel button). case customer diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift index 778357066..50b2d1f12 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift @@ -159,7 +159,7 @@ final class NativeAlternativePaymentDefaultInteractor: cancel(reason: .programmatic) } - func cancel(reason: NativeAlternativePaymentCancelationReason) { + func cancel(reason: NativeAlternativePaymentCancellationReason) { switch state { case .starting(let currentState): currentState.task.cancel() diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift index 40e9754e7..cef69200b 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift @@ -35,5 +35,5 @@ protocol NativeAlternativePaymentInteractor: Interactor