From 19d87bd81e7f964e91ce53582870e5aae8a5aed9 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 1 Sep 2026 19:04:27 +0300 Subject: [PATCH 01/24] AGP 9.3.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3e563df3..38022000 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension buildscript { ext { - androidGradlePluginVersion = '9.3.1' + androidGradlePluginVersion = '9.3.2' kotlinVersion = '2.1.20' kspVersion = '2.1.20-1.0.32' dokkaVersion = '1.9.20' From f07a5e94e52c00e387964d6aafd48ea11e085d4a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 2 Sep 2026 14:41:56 +0300 Subject: [PATCH 02/24] Add new payment states --- .../napm/v2/PONativeAlternativePaymentState.kt | 10 ++++++++++ .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 3 +++ 2 files changed, 13 insertions(+) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentState.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentState.kt index 245ad23a..c5366787 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentState.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentState.kt @@ -8,9 +8,19 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = false) enum class PONativeAlternativePaymentState { + /** Next step is required to proceed. */ NEXT_STEP_REQUIRED, + /** Customer interaction completed. Explicit authorization and/or capture is expected to advance the payment. */ + CUSTOMER_ACTIONS_COMPLETED, + + /** Authorization was requested and it’s still pending. No further actions are expected, payment will advance to next state automatically. */ + AUTHORIZATION_PENDING, + + /** Authorization was requested and completed. */ + AUTHORIZED, + /** Pending payment processing. */ PENDING, diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index c73f744d..21473c01 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -242,6 +242,9 @@ internal class NativeAlternativePaymentInteractor( preloadImages(resources = mappedElements.images()) when (paymentState) { NEXT_STEP_REQUIRED -> handleNextStep(stateValue, mappedElements, redirect) + CUSTOMER_ACTIONS_COMPLETED -> TODO() + AUTHORIZATION_PENDING -> TODO() + AUTHORIZED -> TODO() PENDING -> handlePending(stateValue, mappedElements) SUCCESS -> handleSuccess( stateValue.toPendingStateValue( From fc6cd58aadd6bc76f5a86a9c03347ddd9031c317 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 2 Sep 2026 15:08:12 +0300 Subject: [PATCH 03/24] PONativeAlternativePaymentAvailableAction --- ...NativeAlternativePaymentAvailableAction.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAvailableAction.kt diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAvailableAction.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAvailableAction.kt new file mode 100644 index 00000000..54d0fa58 --- /dev/null +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAvailableAction.kt @@ -0,0 +1,24 @@ +package com.processout.sdk.api.model.response.napm.v2 + +import com.processout.sdk.core.annotation.ProcessOutInternalApi +import com.squareup.moshi.JsonClass + +/** + * Available action to advance the payment. + */ +@JsonClass(generateAdapter = false) +enum class PONativeAlternativePaymentAvailableAction { + + /** Authorization is possible. */ + AUTHORIZE, + + /** Capture is possible. */ + CAPTURE, + + /** + * Placeholder that allows adding additional cases while staying backward compatible. + * __Warning:__ Do not match this case directly, use _when-else_ instead. + */ + @ProcessOutInternalApi + UNKNOWN +} From 83c5e18687c124c895c4b6eba99f59e8b57f243b Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 2 Sep 2026 15:18:01 +0300 Subject: [PATCH 04/24] Include `availableActions` in responses --- .../v2/PONativeAlternativePaymentAuthorizationResponse.kt | 7 +++++-- .../v2/PONativeAlternativePaymentTokenizationResponse.kt | 7 +++++-- .../v2/PONativeAlternativePaymentUrlResolutionResponse.kt | 7 +++++-- .../sdk/api/repository/DefaultCustomerTokensRepository.kt | 3 ++- .../sdk/api/repository/DefaultInvoicesRepository.kt | 6 ++++-- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt index 119c2581..f616c491 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt @@ -21,7 +21,8 @@ data class PONativeAlternativePaymentAuthorizationResponse( val invoice: Invoice, val paymentMethod: PONativeAlternativePaymentMethodDetails, val elements: List?, - val redirect: PONativeAlternativePaymentRedirect? + val redirect: PONativeAlternativePaymentRedirect?, + val availableActions: List? ) : Parcelable { /** @@ -47,5 +48,7 @@ internal data class NativeAlternativePaymentAuthorizationResponseBody( @Json(name = "payment_method") val paymentMethod: PONativeAlternativePaymentMethodDetails, val elements: List?, - val redirect: PONativeAlternativePaymentRedirect? + val redirect: PONativeAlternativePaymentRedirect?, + @Json(name = "available_actions") + val availableActions: List? ) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentTokenizationResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentTokenizationResponse.kt index 2ac5cf53..17161795 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentTokenizationResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentTokenizationResponse.kt @@ -18,7 +18,8 @@ data class PONativeAlternativePaymentTokenizationResponse( val state: PONativeAlternativePaymentState, val paymentMethod: PONativeAlternativePaymentMethodDetails, val elements: List?, - val redirect: PONativeAlternativePaymentRedirect? + val redirect: PONativeAlternativePaymentRedirect?, + val availableActions: List? ) : Parcelable @JsonClass(generateAdapter = true) @@ -27,5 +28,7 @@ internal data class NativeAlternativePaymentTokenizationResponseBody( @Json(name = "payment_method") val paymentMethod: PONativeAlternativePaymentMethodDetails, val elements: List?, - val redirect: PONativeAlternativePaymentRedirect? + val redirect: PONativeAlternativePaymentRedirect?, + @Json(name = "available_actions") + val availableActions: List? ) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt index 49a6e5e4..cfe6d24f 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt @@ -21,7 +21,8 @@ data class PONativeAlternativePaymentUrlResolutionResponse( val invoice: PONativeAlternativePaymentInvoice?, val customerToken: PONativeAlternativePaymentCustomerToken?, val elements: List?, - val redirect: PONativeAlternativePaymentRedirect? + val redirect: PONativeAlternativePaymentRedirect?, + val availableActions: List? ) @JsonClass(generateAdapter = true) @@ -33,5 +34,7 @@ internal data class NativeAlternativePaymentUrlResolutionResponseBody( @Json(name = "customer_token") val customerToken: PONativeAlternativePaymentCustomerToken?, val elements: List?, - val redirect: PONativeAlternativePaymentRedirect? + val redirect: PONativeAlternativePaymentRedirect?, + @Json(name = "available_actions") + val availableActions: List? ) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/repository/DefaultCustomerTokensRepository.kt b/sdk/src/main/kotlin/com/processout/sdk/api/repository/DefaultCustomerTokensRepository.kt index 753fbe97..3a5f08fe 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/repository/DefaultCustomerTokensRepository.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/repository/DefaultCustomerTokensRepository.kt @@ -118,6 +118,7 @@ internal class DefaultCustomerTokensRepository( PONativeAlternativePaymentElement.Unknown } }, - redirect = redirect + redirect = redirect, + availableActions = availableActions ) } diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/repository/DefaultInvoicesRepository.kt b/sdk/src/main/kotlin/com/processout/sdk/api/repository/DefaultInvoicesRepository.kt index 30e86d1f..89fa24a1 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/repository/DefaultInvoicesRepository.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/repository/DefaultInvoicesRepository.kt @@ -176,7 +176,8 @@ internal class DefaultInvoicesRepository( invoice = invoice, paymentMethod = paymentMethod, elements = elements?.map { it.toModel() }, - redirect = redirect + redirect = redirect, + availableActions = availableActions ) private fun NativeAlternativePaymentUrlResolutionResponseBody.toModel() = @@ -186,7 +187,8 @@ internal class DefaultInvoicesRepository( invoice = invoice, customerToken = customerToken, elements = elements?.map { it.toModel() }, - redirect = redirect + redirect = redirect, + availableActions = availableActions ) private fun NativeAlternativePaymentElement.toModel() = From f95394fad40ecc28067cda24a118086414ab196f Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 2 Sep 2026 15:54:49 +0300 Subject: [PATCH 05/24] Add PreferredFinalizationMode configuration to POCreateInvoiceRequest --- .../model/request/POCreateInvoiceRequest.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/request/POCreateInvoiceRequest.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/request/POCreateInvoiceRequest.kt index 41b460ef..6f9abeb2 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/request/POCreateInvoiceRequest.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/request/POCreateInvoiceRequest.kt @@ -16,6 +16,26 @@ data class POCreateInvoiceRequest( @Json(name = "return_url") val returnUrl: String? = null, val shipping: POContact? = null, + @Json(name = "payment_configuration") + val paymentConfiguration: PaymentConfiguration? = null, val device: Map = mapOf("channel" to "android"), val metadata: Map? = null -) +) { + + @JsonClass(generateAdapter = true) + data class PaymentConfiguration( + val apm: AlternativePaymentConfiguration + ) + + @JsonClass(generateAdapter = true) + data class AlternativePaymentConfiguration( + @Json(name = "preferred_finalization_mode") + val preferredFinalizationMode: PreferredFinalizationMode + ) + + @Suppress("EnumEntryName") + @JsonClass(generateAdapter = false) + enum class PreferredFinalizationMode { + automatic, manual + } +} From 062116704da3b9dd1c41d5b9bf50fa70d55ab227 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Sep 2026 14:55:19 +0300 Subject: [PATCH 06/24] AGP 9.4.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 38022000..de0b7774 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension buildscript { ext { - androidGradlePluginVersion = '9.3.2' + androidGradlePluginVersion = '9.4.1' kotlinVersion = '2.1.20' kspVersion = '2.1.20-1.0.32' dokkaVersion = '1.9.20' From 44a50278c2197c35447d04f001722b4f1b3e3a16 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 21 Sep 2026 18:01:05 +0300 Subject: [PATCH 07/24] Pass `availableActions` from response to `handlePaymentState()` function in interactor --- .../napm/NativeAlternativePaymentInteractor.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 21473c01..0a74c66a 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -176,7 +176,8 @@ internal class NativeAlternativePaymentInteractor( ), paymentState = response.state, elements = response.elements, - redirect = response.redirect + redirect = response.redirect, + availableActions = response.availableActions ) }.onFailure { failure -> POLogger.info("Failed to fetch authorization details: %s", failure) @@ -200,7 +201,8 @@ internal class NativeAlternativePaymentInteractor( ), paymentState = response.state, elements = response.elements, - redirect = response.redirect + redirect = response.redirect, + availableActions = response.availableActions ) }.onFailure { failure -> POLogger.info("Failed to fetch tokenization details: %s", failure) @@ -235,7 +237,8 @@ internal class NativeAlternativePaymentInteractor( stateValue: NextStepStateValue, paymentState: PONativeAlternativePaymentState, elements: List?, - redirect: PONativeAlternativePaymentRedirect? + redirect: PONativeAlternativePaymentRedirect?, + availableActions: List? ) { this.paymentState = paymentState val mappedElements = elements?.map() ?: emptyList() @@ -615,7 +618,8 @@ internal class NativeAlternativePaymentInteractor( ), paymentState = response.state, elements = response.elements, - redirect = response.redirect + redirect = response.redirect, + availableActions = response.availableActions ) }.onFailure { failure -> val isInvalidRedirectResult = when (val code = failure.code) { @@ -952,7 +956,8 @@ internal class NativeAlternativePaymentInteractor( stateValue = stateValue, paymentState = response.state, elements = response.elements, - redirect = response.redirect + redirect = response.redirect, + availableActions = response.availableActions ) }.onFailure { failure -> handlePaymentFailure(failure) @@ -981,7 +986,8 @@ internal class NativeAlternativePaymentInteractor( stateValue = stateValue, paymentState = response.state, elements = response.elements, - redirect = response.redirect + redirect = response.redirect, + availableActions = response.availableActions ) }.onFailure { failure -> handlePaymentFailure(failure) From 6fbe9a5b22c7bd3e810a38853a5b71624b39b712 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Sep 2026 16:50:08 +0300 Subject: [PATCH 08/24] KDoc --- .../napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt | 1 + .../napm/v2/PONativeAlternativePaymentTokenizationResponse.kt | 1 + .../napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt | 1 + 3 files changed, 3 insertions(+) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt index f616c491..af5526b2 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentAuthorizationResponse.kt @@ -14,6 +14,7 @@ import kotlinx.parcelize.Parcelize * @param[paymentMethod] Payment method details. * @param[elements] An ordered list of elements that needs to be rendered on the UI during native alternative payment flow. * @param[redirect] Indicates required redirect. + * @param[availableActions] Available actions to advance the payment. */ @Parcelize data class PONativeAlternativePaymentAuthorizationResponse( diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentTokenizationResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentTokenizationResponse.kt index 17161795..f2cb9d14 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentTokenizationResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentTokenizationResponse.kt @@ -12,6 +12,7 @@ import kotlinx.parcelize.Parcelize * @param[paymentMethod] Payment method details. * @param[elements] An ordered list of elements that needs to be rendered on the UI during native alternative payment flow. * @param[redirect] Indicates required redirect. + * @param[availableActions] Available actions to advance the payment. */ @Parcelize data class PONativeAlternativePaymentTokenizationResponse( diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt index cfe6d24f..68ceb76f 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentUrlResolutionResponse.kt @@ -13,6 +13,7 @@ import com.squareup.moshi.JsonClass * @param[customerToken] Customer token details if any. * @param[elements] An ordered list of elements that needs to be rendered on the UI during native alternative payment flow. * @param[redirect] Indicates required redirect. + * @param[availableActions] Available actions to advance the payment. */ @ProcessOutInternalApi data class PONativeAlternativePaymentUrlResolutionResponse( From 2eb0fdadcce89630c6016eeea6f244caf144c5a4 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Sep 2026 17:39:51 +0300 Subject: [PATCH 09/24] Dispatch manual finalization via delegate --- .../PONativeAlternativePaymentLauncher.kt | 17 ++++++++++++---- ...iveAlternativePaymentManualFinalization.kt | 20 +++++++++++++++++++ .../v2/PONativeAlternativePaymentDelegate.kt | 14 +++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/NativeAlternativePaymentManualFinalization.kt diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt index d606b5f3..02c9e9b5 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt @@ -25,10 +25,7 @@ import com.processout.sdk.ui.napm.NativeAlternativePaymentCompletion.Success import com.processout.sdk.ui.napm.NativeAlternativePaymentEvent.WebRedirectResult import com.processout.sdk.ui.napm.NativeAlternativePaymentSideEffect.WebRedirect import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Authorization -import com.processout.sdk.ui.napm.delegate.v2.NativeAlternativePaymentDefaultValuesRequest -import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentDelegate -import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentEvent -import com.processout.sdk.ui.napm.delegate.v2.toResponse +import com.processout.sdk.ui.napm.delegate.v2.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -321,6 +318,7 @@ class PONativeAlternativePaymentLauncher private constructor( private fun dispatchAllEvents(coroutineScope: CoroutineScope) { dispatchEvents(coroutineScope) dispatchDefaultValues(coroutineScope) + dispatchManualFinalization(coroutineScope) } private fun dispatchEvents(coroutineScope: CoroutineScope) { @@ -343,6 +341,17 @@ class PONativeAlternativePaymentLauncher private constructor( } } + private fun dispatchManualFinalization(coroutineScope: CoroutineScope) { + eventDispatcher.subscribeForRequest( + coroutineScope + ) { request -> + coroutineScope.launch { + val result = delegate.finalize(request.availableActions) + eventDispatcher.send(request.toResponse(result)) + } + } + } + /** * Launches the payment. */ diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/NativeAlternativePaymentManualFinalization.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/NativeAlternativePaymentManualFinalization.kt new file mode 100644 index 00000000..086bd938 --- /dev/null +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/NativeAlternativePaymentManualFinalization.kt @@ -0,0 +1,20 @@ +package com.processout.sdk.ui.napm.delegate.v2 + +import com.processout.sdk.api.dispatcher.POEventDispatcher +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAvailableAction +import com.processout.sdk.core.ProcessOutResult +import java.util.UUID + +internal data class NativeAlternativePaymentManualFinalizationRequest( + override val uuid: UUID = UUID.randomUUID(), + val availableActions: List +) : POEventDispatcher.Request + +internal data class NativeAlternativePaymentManualFinalizationResponse( + override val uuid: UUID, + val result: ProcessOutResult +) : POEventDispatcher.Response + +internal fun NativeAlternativePaymentManualFinalizationRequest.toResponse( + result: ProcessOutResult +) = NativeAlternativePaymentManualFinalizationResponse(uuid, result) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt index 1309ebe3..59e0e1b4 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt @@ -1,6 +1,9 @@ package com.processout.sdk.ui.napm.delegate.v2 +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAvailableAction import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentElement +import com.processout.sdk.core.POFailure +import com.processout.sdk.core.ProcessOutResult /** * Delegate that allows to handle events during native alternative payments. @@ -21,4 +24,15 @@ interface PONativeAlternativePaymentDelegate { gatewayConfigurationId: String, parameters: List ): Map = emptyMap() + + /** + * Asks delegate to finalize the payment with the given [availableActions]. + */ + suspend fun finalize( + availableActions: List + ): ProcessOutResult = + ProcessOutResult.Failure( + code = POFailure.Code.Generic(), + message = "Manual finalization is not implemented by the delegate." + ) } From 37e35cf37895a14002c65b8f0b652b7ac511bf2b Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 23 Sep 2026 18:30:40 +0300 Subject: [PATCH 10/24] Interactor: requestManualFinalization() & collectManualFinalizationResult() --- .../NativeAlternativePaymentInteractor.kt | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 0a74c66a..0dddc7cb 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -59,11 +59,8 @@ import com.processout.sdk.ui.napm.NativeAlternativePaymentSideEffect.WebRedirect import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.CancelButton import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Authorization import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Tokenization -import com.processout.sdk.ui.napm.delegate.v2.NativeAlternativePaymentDefaultValuesRequest -import com.processout.sdk.ui.napm.delegate.v2.NativeAlternativePaymentDefaultValuesResponse -import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentEvent +import com.processout.sdk.ui.napm.delegate.v2.* import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentEvent.* -import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentParameterValue import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentParameterValue.Value import com.processout.sdk.ui.shared.extension.dpToPx import com.processout.sdk.ui.shared.extension.openDeepLink @@ -108,6 +105,7 @@ internal class NativeAlternativePaymentInteractor( private var paymentState: PONativeAlternativePaymentState = UNKNOWN private var latestDefaultValuesRequest: NativeAlternativePaymentDefaultValuesRequest? = null + private var latestManualFinalizationRequest: NativeAlternativePaymentManualFinalizationRequest? = null private var latestWillSubmitParametersEvent: WillSubmitParameters? = null private var isCapturePolling = false @@ -124,6 +122,7 @@ internal class NativeAlternativePaymentInteractor( dispatch(WillStart) dispatchFailure() collectDefaultValues() + collectManualFinalizationResult() collectDeepLink() fetchPaymentDetails() } @@ -146,6 +145,7 @@ internal class NativeAlternativePaymentInteractor( handler.removeCallbacksAndMessages(null) paymentState = UNKNOWN latestDefaultValuesRequest = null + latestManualFinalizationRequest = null latestWillSubmitParametersEvent = null _completion.update { Awaiting } _state.update { Idle } @@ -580,6 +580,36 @@ internal class NativeAlternativePaymentInteractor( //endregion + //region Manual Finalization + + private fun requestManualFinalization( + availableActions: List + ) { + interactorScope.launch { + val request = NativeAlternativePaymentManualFinalizationRequest( + availableActions = availableActions + ) + latestManualFinalizationRequest = request + eventDispatcher.send(request) + POLogger.info("Requested to finalize the payment manually with available actions: %s", availableActions) + } + } + + private fun collectManualFinalizationResult() { + eventDispatcher.subscribeForResponse( + coroutineScope = interactorScope + ) { response -> + if (response.uuid == latestManualFinalizationRequest?.uuid) { + latestManualFinalizationRequest = null + val result = response.result + POLogger.info("Collected manual finalization result: %s", result) + // TODO: handle result + } + } + } + + //endregion + //region Deep Link private fun collectDeepLink() { From afe187560e4ae872cf2e9061ef027132fb482858 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 28 Sep 2026 16:32:21 +0300 Subject: [PATCH 11/24] Pass `paymentState` via delegate --- .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 2 ++ .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 5 ++++- .../v2/NativeAlternativePaymentManualFinalization.kt | 2 ++ .../napm/delegate/v2/PONativeAlternativePaymentDelegate.kt | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 0dddc7cb..bcbcaefe 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -583,10 +583,12 @@ internal class NativeAlternativePaymentInteractor( //region Manual Finalization private fun requestManualFinalization( + paymentState: PONativeAlternativePaymentState, availableActions: List ) { interactorScope.launch { val request = NativeAlternativePaymentManualFinalizationRequest( + paymentState = paymentState, availableActions = availableActions ) latestManualFinalizationRequest = request diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt index 02c9e9b5..18ad50ac 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt @@ -346,7 +346,10 @@ class PONativeAlternativePaymentLauncher private constructor( coroutineScope ) { request -> coroutineScope.launch { - val result = delegate.finalize(request.availableActions) + val result = delegate.finalize( + paymentState = request.paymentState, + availableActions = request.availableActions + ) eventDispatcher.send(request.toResponse(result)) } } diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/NativeAlternativePaymentManualFinalization.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/NativeAlternativePaymentManualFinalization.kt index 086bd938..6a57d3ce 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/NativeAlternativePaymentManualFinalization.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/NativeAlternativePaymentManualFinalization.kt @@ -2,11 +2,13 @@ package com.processout.sdk.ui.napm.delegate.v2 import com.processout.sdk.api.dispatcher.POEventDispatcher import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAvailableAction +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState import com.processout.sdk.core.ProcessOutResult import java.util.UUID internal data class NativeAlternativePaymentManualFinalizationRequest( override val uuid: UUID = UUID.randomUUID(), + val paymentState: PONativeAlternativePaymentState, val availableActions: List ) : POEventDispatcher.Request diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt index 59e0e1b4..364cdce9 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt @@ -2,6 +2,7 @@ package com.processout.sdk.ui.napm.delegate.v2 import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAvailableAction import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentElement +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState import com.processout.sdk.core.POFailure import com.processout.sdk.core.ProcessOutResult @@ -29,6 +30,7 @@ interface PONativeAlternativePaymentDelegate { * Asks delegate to finalize the payment with the given [availableActions]. */ suspend fun finalize( + paymentState: PONativeAlternativePaymentState, availableActions: List ): ProcessOutResult = ProcessOutResult.Failure( From 6449b399e896a7d6ca9c60ef5833f5b505bfbe86 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 28 Sep 2026 18:40:25 +0300 Subject: [PATCH 12/24] KDoc --- .../ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt index 364cdce9..ab985c88 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/delegate/v2/PONativeAlternativePaymentDelegate.kt @@ -27,7 +27,9 @@ interface PONativeAlternativePaymentDelegate { ): Map = emptyMap() /** - * Asks delegate to finalize the payment with the given [availableActions]. + * Asks the delegate to finalize the payment by explicitly advancing it to an authorized and/or captured state using one of the available actions. + * Method is invoked at most once, either when all customer actions are completed or when the payment is already authorized and can still be captured. + * In the latter case, the implementation may return without advancing the payment, leaving it in the authorized state. */ suspend fun finalize( paymentState: PONativeAlternativePaymentState, From 5669cb536ae7528801616aa59bfd54681a5a3416 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 28 Sep 2026 19:07:45 +0300 Subject: [PATCH 13/24] didRequestManualFinalization --- .../ui/napm/NativeAlternativePaymentInteractor.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index bcbcaefe..f1b6d83c 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -101,14 +101,17 @@ internal class NativeAlternativePaymentInteractor( private val _sideEffects = Channel() val sideEffects = _sideEffects.receiveAsFlow() - private val handler = Handler(Looper.getMainLooper()) - private var paymentState: PONativeAlternativePaymentState = UNKNOWN - private var latestDefaultValuesRequest: NativeAlternativePaymentDefaultValuesRequest? = null + + private var didRequestManualFinalization = false private var latestManualFinalizationRequest: NativeAlternativePaymentManualFinalizationRequest? = null + private var latestDefaultValuesRequest: NativeAlternativePaymentDefaultValuesRequest? = null private var latestWillSubmitParametersEvent: WillSubmitParameters? = null + private var isCapturePolling = false + private val handler = Handler(Looper.getMainLooper()) + init { ProcessLifecycleOwner.get().lifecycle.addObserver(this) } @@ -144,8 +147,9 @@ internal class NativeAlternativePaymentInteractor( interactorScope.coroutineContext.cancelChildren() handler.removeCallbacksAndMessages(null) paymentState = UNKNOWN - latestDefaultValuesRequest = null + didRequestManualFinalization = false latestManualFinalizationRequest = null + latestDefaultValuesRequest = null latestWillSubmitParametersEvent = null _completion.update { Awaiting } _state.update { Idle } @@ -591,6 +595,7 @@ internal class NativeAlternativePaymentInteractor( paymentState = paymentState, availableActions = availableActions ) + didRequestManualFinalization = true latestManualFinalizationRequest = request eventDispatcher.send(request) POLogger.info("Requested to finalize the payment manually with available actions: %s", availableActions) From 9184be8fb1ec358028c5f426160b1a15219da318 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 28 Sep 2026 19:36:23 +0300 Subject: [PATCH 14/24] Handle AUTHORIZATION_PENDING and AUTHORIZED --- .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index f1b6d83c..7f1c35d6 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -249,11 +249,9 @@ internal class NativeAlternativePaymentInteractor( preloadImages(resources = mappedElements.images()) when (paymentState) { NEXT_STEP_REQUIRED -> handleNextStep(stateValue, mappedElements, redirect) + PENDING, AUTHORIZATION_PENDING -> handlePending(stateValue, mappedElements) CUSTOMER_ACTIONS_COMPLETED -> TODO() - AUTHORIZATION_PENDING -> TODO() - AUTHORIZED -> TODO() - PENDING -> handlePending(stateValue, mappedElements) - SUCCESS -> handleSuccess( + AUTHORIZED, SUCCESS -> handleSuccess( stateValue.toPendingStateValue( uuid = UUID.randomUUID().toString(), elements = mappedElements From e9ff7252b6558ad8517009ac9649662b402f15d7 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 29 Sep 2026 15:08:37 +0300 Subject: [PATCH 15/24] Request manual finalization --- .../NativeAlternativePaymentInteractor.kt | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 7f1c35d6..8fd97d8a 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -31,6 +31,7 @@ import com.processout.sdk.api.model.request.napm.v2.PONativeAlternativePaymentSu import com.processout.sdk.api.model.response.POImageResource import com.processout.sdk.api.model.response.napm.v2.* import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAuthorizationResponse.Invoice +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAvailableAction.CAPTURE import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentElement.Form.Parameter import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentElement.Form.Parameter.Otp.Subtype import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentRedirect.DeepLinkConfiguration @@ -250,7 +251,9 @@ internal class NativeAlternativePaymentInteractor( when (paymentState) { NEXT_STEP_REQUIRED -> handleNextStep(stateValue, mappedElements, redirect) PENDING, AUTHORIZATION_PENDING -> handlePending(stateValue, mappedElements) - CUSTOMER_ACTIONS_COMPLETED -> TODO() + CUSTOMER_ACTIONS_COMPLETED -> requestManualFinalization(paymentState, availableActions) + AUTHORIZED if !didRequestManualFinalization && availableActions?.contains(CAPTURE) == true -> + requestManualFinalization(paymentState, availableActions) AUTHORIZED, SUCCESS -> handleSuccess( stateValue.toPendingStateValue( uuid = UUID.randomUUID().toString(), @@ -586,8 +589,32 @@ internal class NativeAlternativePaymentInteractor( private fun requestManualFinalization( paymentState: PONativeAlternativePaymentState, - availableActions: List + availableActions: List? ) { + if (didRequestManualFinalization) { + val failure = ProcessOutResult.Failure( + code = Generic(), + message = "Payment was not advanced." + ) + POLogger.info( + message = "Unexpected state during manual finalization: %s", failure, + attributes = configuration.logAttributes + ) + _completion.update { Failure(failure) } + return + } + if (availableActions.isNullOrEmpty()) { + val failure = ProcessOutResult.Failure( + code = Internal(), + message = "No available actions to finalize the payment." + ) + POLogger.error( + message = "Unexpected response during manual finalization: %s", failure, + attributes = configuration.logAttributes + ) + _completion.update { Failure(failure) } + return + } interactorScope.launch { val request = NativeAlternativePaymentManualFinalizationRequest( paymentState = paymentState, From 3a1d64a799d1d82df8c7501f467015699cce3ea5 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 29 Sep 2026 15:18:51 +0300 Subject: [PATCH 16/24] Log --- .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 8fd97d8a..1727061b 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -623,7 +623,10 @@ internal class NativeAlternativePaymentInteractor( didRequestManualFinalization = true latestManualFinalizationRequest = request eventDispatcher.send(request) - POLogger.info("Requested to finalize the payment manually with available actions: %s", availableActions) + POLogger.info( + "Requested to finalize the payment manually. Payment state: %s. Available actions: %s.", + paymentState, availableActions + ) } } From 41da8914272aabca60e70282e1beccdf0f32c35e Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 29 Sep 2026 15:34:01 +0300 Subject: [PATCH 17/24] Handle failure --- .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 1727061b..d84ef52c 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -638,7 +638,11 @@ internal class NativeAlternativePaymentInteractor( latestManualFinalizationRequest = null val result = response.result POLogger.info("Collected manual finalization result: %s", result) - // TODO: handle result + result.onSuccess { + // TODO: handle success + }.onFailure { failure -> + _completion.update { Failure(failure) } + } } } } From 86d5c70b7d179b489621868991727021bff243d8 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 1 Oct 2026 15:10:29 +0300 Subject: [PATCH 18/24] fetchPaymentDetails() on successful manual finalization --- .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index d84ef52c..b6515fd6 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -639,7 +639,7 @@ internal class NativeAlternativePaymentInteractor( val result = response.result POLogger.info("Collected manual finalization result: %s", result) result.onSuccess { - // TODO: handle success + fetchPaymentDetails() }.onFailure { failure -> _completion.update { Failure(failure) } } From 6af172589f665f5b0edf12a69a99a17a16a2593a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 1 Oct 2026 17:32:10 +0300 Subject: [PATCH 19/24] Handle AUTHORIZATION_PENDING when resolving url --- .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index b6515fd6..dee6d910 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -677,7 +677,7 @@ internal class NativeAlternativePaymentInteractor( ) invoicesService.resolveUrl(request) .onSuccess { response -> - if (response.state == PENDING && _state.value is Pending) { + if (setOf(PENDING, AUTHORIZATION_PENDING).contains(response.state) && _state.value is Pending) { return@onSuccess } handlePaymentState( From cf0d9e489e5ffcda0f754ee2828cd1b21c8d4232 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 5 Oct 2026 14:17:18 +0300 Subject: [PATCH 20/24] Unify handling of payment state during capture --- .../NativeAlternativePaymentCapturePoller.kt | 29 +++++++++++++------ .../NativeAlternativePaymentInteractor.kt | 21 +++++++------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentCapturePoller.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentCapturePoller.kt index 82e879f5..69cdabbd 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentCapturePoller.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentCapturePoller.kt @@ -5,11 +5,10 @@ package com.processout.sdk.ui.napm import android.os.SystemClock import com.processout.sdk.api.model.request.napm.v2.PONativeAlternativePaymentAuthorizationRequest import com.processout.sdk.api.model.request.napm.v2.PONativeAlternativePaymentTokenizationRequest -import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAuthorizationResponse -import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentElement -import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState -import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState.SUCCESS -import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentTokenizationResponse +import com.processout.sdk.api.model.response.napm.v2.* +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAuthorizationResponse.Invoice +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState.AUTHORIZATION_PENDING +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState.PENDING import com.processout.sdk.api.service.POCustomerTokensService import com.processout.sdk.api.service.POInvoicesService import com.processout.sdk.core.POFailure.Code.* @@ -41,7 +40,11 @@ internal class NativeAlternativePaymentCapturePoller( data class CaptureResponse( val state: PONativeAlternativePaymentState, - val elements: List? + val invoice: Invoice?, + val paymentMethod: PONativeAlternativePaymentMethodDetails, + val elements: List?, + val redirect: PONativeAlternativePaymentRedirect?, + val availableActions: List? ) private var backoffIterator = retryStrategy.newBackoffIterator() @@ -106,7 +109,7 @@ internal class NativeAlternativePaymentCapturePoller( private fun isRetryable( result: ProcessOutResult ): Boolean = result.fold( - onSuccess = { it.state != SUCCESS }, + onSuccess = { it.state == PENDING || it.state == AUTHORIZATION_PENDING }, onFailure = { failure -> val retryableCodes = listOf( NetworkUnreachable, @@ -124,7 +127,11 @@ internal class NativeAlternativePaymentCapturePoller( ProcessOutResult.Success( CaptureResponse( state = it.state, - elements = it.elements + invoice = it.invoice, + paymentMethod = it.paymentMethod, + elements = it.elements, + redirect = it.redirect, + availableActions = it.availableActions ) ) }, @@ -138,7 +145,11 @@ internal class NativeAlternativePaymentCapturePoller( ProcessOutResult.Success( CaptureResponse( state = it.state, - elements = it.elements + invoice = null, + paymentMethod = it.paymentMethod, + elements = it.elements, + redirect = it.redirect, + availableActions = it.availableActions ) ) }, diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index dee6d910..59aff961 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -217,7 +217,7 @@ internal class NativeAlternativePaymentInteractor( private suspend fun initNextStepStateValue( paymentMethod: PONativeAlternativePaymentMethodDetails, - invoice: Invoice?, + invoice: Invoice? ): NextStepStateValue { preloadImages(resources = listOf(paymentMethod.logo)) return NextStepStateValue( @@ -1188,15 +1188,16 @@ internal class NativeAlternativePaymentInteractor( try { capturePoller.poll() .onSuccess { response -> - val elements = response.elements?.map() - _state.whenPending { stateValue -> - handleSuccess( - stateValue.copy( - uuid = UUID.randomUUID().toString(), - elements = elements - ) - ) - } + handlePaymentState( + stateValue = initNextStepStateValue( + paymentMethod = response.paymentMethod, + invoice = response.invoice + ), + paymentState = response.state, + elements = response.elements, + redirect = response.redirect, + availableActions = response.availableActions + ) }.onFailure { failure -> _completion.update { Failure(failure) } } From 11cf3344aaa27b450137134405c472e5da4ddc20 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 5 Oct 2026 14:58:43 +0300 Subject: [PATCH 21/24] Ignore `unexpectedRedirectResult` failure code --- sdk/src/main/kotlin/com/processout/sdk/core/POFailure.kt | 1 + .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/core/POFailure.kt b/sdk/src/main/kotlin/com/processout/sdk/core/POFailure.kt index 0651d78f..acdbdb48 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/core/POFailure.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/core/POFailure.kt @@ -149,6 +149,7 @@ class POFailure private constructor() { invalidUrl("request.validation.invalid-url"), invalidUser("request.validation.invalid-user"), invalidRedirectResult("request.validation.redirect-result-invalid"), + unexpectedRedirectResult("request.validation.redirect-result-unexpected"), missingCurrency("request.validation.missing-currency"), missingCustomerInput("gateway.missing-customer-input"), missingDescription("request.validation.missing-description"), diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 59aff961..cddb47ec 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -43,6 +43,8 @@ import com.processout.sdk.core.POFailure.Code.* import com.processout.sdk.core.POFailure.GenericCode.mobileHeadlessModeNotSupported import com.processout.sdk.core.POFailure.InvalidField import com.processout.sdk.core.POFailure.ValidationCode +import com.processout.sdk.core.POFailure.ValidationCode.invalidRedirectResult +import com.processout.sdk.core.POFailure.ValidationCode.unexpectedRedirectResult import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.core.fold import com.processout.sdk.core.logger.POLogger @@ -691,11 +693,12 @@ internal class NativeAlternativePaymentInteractor( availableActions = response.availableActions ) }.onFailure { failure -> - val isInvalidRedirectResult = when (val code = failure.code) { - is Validation -> code.validationCode == ValidationCode.invalidRedirectResult + val ignoredFailureCodes = setOf(invalidRedirectResult, unexpectedRedirectResult) + val shouldIgnoreRedirectResult = when (val code = failure.code) { + is Validation -> ignoredFailureCodes.contains(code.validationCode) else -> false } - if (isInvalidRedirectResult) { + if (shouldIgnoreRedirectResult) { return@onFailure } _completion.update { Failure(failure) } From 110633fbf6c415f60c6f472baadefbdb80719b53 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 5 Oct 2026 15:42:45 +0300 Subject: [PATCH 22/24] Ignore result from deep link resolving if manual finalization was requested --- .../sdk/ui/napm/NativeAlternativePaymentInteractor.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index cddb47ec..7a3086c6 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -679,7 +679,9 @@ internal class NativeAlternativePaymentInteractor( ) invoicesService.resolveUrl(request) .onSuccess { response -> - if (setOf(PENDING, AUTHORIZATION_PENDING).contains(response.state) && _state.value is Pending) { + val pendingStates = setOf(PENDING, AUTHORIZATION_PENDING) + val isPending = _state.value is Pending && pendingStates.contains(response.state) + if (didRequestManualFinalization || isPending) { return@onSuccess } handlePaymentState( @@ -698,7 +700,7 @@ internal class NativeAlternativePaymentInteractor( is Validation -> ignoredFailureCodes.contains(code.validationCode) else -> false } - if (shouldIgnoreRedirectResult) { + if (didRequestManualFinalization || shouldIgnoreRedirectResult) { return@onFailure } _completion.update { Failure(failure) } From d46d10d40606f45222815efd66605684cf31aac5 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 5 Oct 2026 15:51:57 +0300 Subject: [PATCH 23/24] Code improvement: filterIsInstance() --- .../napm/NativeAlternativePaymentInteractor.kt | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 7a3086c6..5e8c0f52 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -1356,9 +1356,7 @@ internal class NativeAlternativePaymentInteractor( // TODO(v2): use barcode ID, also inspect instructions group private fun saveBarcode() { _state.whenNextStep { stateValue -> - val instructions = stateValue.elements.mapNotNull { - it as? Element.Instruction - } + val instructions = stateValue.elements.filterIsInstance() instructions.forEach { if (it.instruction is Instruction.Barcode) { saveBarcode(barcode = it.instruction) @@ -1367,9 +1365,7 @@ internal class NativeAlternativePaymentInteractor( } } _state.whenPending { stateValue -> - val instructions = stateValue.elements?.mapNotNull { - it as? Element.Instruction - } + val instructions = stateValue.elements?.filterIsInstance() instructions?.forEach { if (it.instruction is Instruction.Barcode) { saveBarcode(barcode = it.instruction) @@ -1399,9 +1395,7 @@ internal class NativeAlternativePaymentInteractor( Manifest.permission.WRITE_EXTERNAL_STORAGE -> if (result.isGranted) { _state.whenNextStep { stateValue -> - val instructions = stateValue.elements.mapNotNull { - it as? Element.Instruction - } + val instructions = stateValue.elements.filterIsInstance() instructions.forEach { if (it.instruction is Instruction.Barcode) { interactorScope.launch { @@ -1414,9 +1408,7 @@ internal class NativeAlternativePaymentInteractor( } } _state.whenPending { stateValue -> - val instructions = stateValue.elements?.mapNotNull { - it as? Element.Instruction - } + val instructions = stateValue.elements?.filterIsInstance() instructions?.forEach { if (it.instruction is Instruction.Barcode) { interactorScope.launch { From 0b1431f7917a6c59dab167735d17aeaf499e7e46 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 5 Oct 2026 17:07:59 +0300 Subject: [PATCH 24/24] Rename: fetchPaymentDetails() -> fetchPaymentState() --- .../napm/NativeAlternativePaymentInteractor.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt index 5e8c0f52..7ee9dc9f 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt @@ -130,7 +130,7 @@ internal class NativeAlternativePaymentInteractor( collectDefaultValues() collectManualFinalizationResult() collectDeepLink() - fetchPaymentDetails() + fetchPaymentState() } fun start(configuration: PONativeAlternativePaymentConfiguration) { @@ -158,16 +158,16 @@ internal class NativeAlternativePaymentInteractor( _state.update { Idle } } - private fun fetchPaymentDetails() { + private fun fetchPaymentState() { interactorScope.launch { when (val flow = configuration.flow) { - is Authorization -> fetchAuthorizationDetails(flow) - is Tokenization -> fetchTokenizationDetails(flow) + is Authorization -> fetchAuthorizationState(flow) + is Tokenization -> fetchTokenizationState(flow) } } } - private suspend fun fetchAuthorizationDetails(flow: Authorization) { + private suspend fun fetchAuthorizationState(flow: Authorization) { val request = PONativeAlternativePaymentAuthorizationRequest( invoiceId = flow.invoiceId, gatewayConfigurationId = flow.gatewayConfigurationId, @@ -187,12 +187,12 @@ internal class NativeAlternativePaymentInteractor( availableActions = response.availableActions ) }.onFailure { failure -> - POLogger.info("Failed to fetch authorization details: %s", failure) + POLogger.info("Failed to fetch authorization state: %s", failure) _completion.update { Failure(failure) } } } - private suspend fun fetchTokenizationDetails(flow: Tokenization) { + private suspend fun fetchTokenizationState(flow: Tokenization) { val request = PONativeAlternativePaymentTokenizationRequest( customerId = flow.customerId, customerTokenId = flow.customerTokenId, @@ -212,7 +212,7 @@ internal class NativeAlternativePaymentInteractor( availableActions = response.availableActions ) }.onFailure { failure -> - POLogger.info("Failed to fetch tokenization details: %s", failure) + POLogger.info("Failed to fetch tokenization state: %s", failure) _completion.update { Failure(failure) } } } @@ -641,7 +641,7 @@ internal class NativeAlternativePaymentInteractor( val result = response.result POLogger.info("Collected manual finalization result: %s", result) result.onSuccess { - fetchPaymentDetails() + fetchPaymentState() }.onFailure { failure -> _completion.update { Failure(failure) } }