From e5a9c93f0f6cb0075eafed2ec71113df5ceba2be Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 16 Sep 2025 17:24:31 +0300 Subject: [PATCH 01/25] ConfigurationCache --- .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 276c83868..ffa7a3ea1 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 @@ -152,6 +152,17 @@ class PONativeAlternativePaymentLauncher private constructor( ) } + private object ConfigurationCache { + + var value: PONativeAlternativePaymentConfiguration? = null + + fun remove(): PONativeAlternativePaymentConfiguration? { + val cached = value + value = null + return cached + } + } + init { dispatchEvents() dispatchDefaultValues() From 6da91643f2e365e0c4ed3649946573d15d5bf1b6 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 30 Sep 2025 17:44:02 +0300 Subject: [PATCH 02/25] RedirectConfiguration --- .../PONativeAlternativePaymentConfiguration.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt index 162c48c11..49bf08947 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt @@ -279,6 +279,20 @@ data class PONativeAlternativePaymentConfiguration( val touchOutside: Boolean = true ) : Parcelable + /** + * Redirect configuration. + * + * @param[returnUrl] Deep link return URL. Required for the flows that include web redirect. + * @param[enableHeadlessMode] Enables headless mode. + * The web redirect will be handled directly when it's the first step in the flow, + * and if it's the only required step it will complete the flow without starting the bottom sheet. + */ + @Parcelize + data class RedirectConfiguration( + val returnUrl: String, + val enableHeadlessMode: Boolean = false + ) : Parcelable + /** * Payment confirmation configuration. * From 9b32941934d653490f6b777661dbcc02303ac684 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 30 Sep 2025 18:22:07 +0300 Subject: [PATCH 03/25] Apply RedirectConfiguration to main constructor and usages --- .../ui/checkout/PODynamicCheckoutActivity.kt | 6 ++- .../NativeAlternativePaymentBottomSheet.kt | 3 +- .../NativeAlternativePaymentInteractor.kt | 2 +- ...PONativeAlternativePaymentConfiguration.kt | 53 ++++++++++++++++++- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutActivity.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutActivity.kt index 1468f42bf..fa00ea4cb 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutActivity.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/PODynamicCheckoutActivity.kt @@ -49,8 +49,7 @@ import com.processout.sdk.ui.core.theme.ProcessOutTheme import com.processout.sdk.ui.googlepay.POGooglePayCardTokenizationLauncher import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModel import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration -import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow -import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.PaymentConfirmationConfiguration +import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.* import com.processout.sdk.ui.savedpaymentmethods.POSavedPaymentMethodsLauncher import com.processout.sdk.ui.savedpaymentmethods.delegate.POSavedPaymentMethodsDelegate import com.processout.sdk.ui.savedpaymentmethods.delegate.POSavedPaymentMethodsEvent @@ -134,6 +133,7 @@ class PODynamicCheckoutActivity : POBaseTransparentPortraitActivity() { } private fun nativeAlternativePaymentConfiguration(): PONativeAlternativePaymentConfiguration { + val returnUrl = configuration.alternativePayment.returnUrl val paymentConfirmation = configuration.alternativePayment.paymentConfirmation return PONativeAlternativePaymentConfiguration( flow = Flow.Authorization( @@ -142,6 +142,8 @@ class PODynamicCheckoutActivity : POBaseTransparentPortraitActivity() { ), submitButton = configuration.submitButton.map(), cancelButton = configuration.cancelButton?.map(), + redirect = if (!returnUrl.isNullOrBlank()) + RedirectConfiguration(returnUrl = returnUrl) else null, paymentConfirmation = PaymentConfirmationConfiguration( timeoutSeconds = paymentConfirmation.timeoutSeconds, confirmButton = paymentConfirmation.confirmButton?.map(), diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt index 67dcb8983..cdbabde13 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt @@ -76,7 +76,8 @@ internal class NativeAlternativePaymentBottomSheet : BaseBottomSheetDialogFragme flow = Flow.Authorization( invoiceId = String(), gatewayConfigurationId = String() - ) + ), + redirect = null ) alternativePaymentLauncher = POAlternativePaymentMethodCustomTabLauncher.create( from = this, 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 1aeb453dc..6beb49d79 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 @@ -620,7 +620,7 @@ internal class NativeAlternativePaymentInteractor( stateValue: NextStepStateValue, redirectUrl: String ) { - val returnUrl = configuration.returnUrl + val returnUrl = configuration.redirect?.returnUrl if (returnUrl.isNullOrBlank()) { val failure = ProcessOutResult.Failure( code = Generic(), diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt index 49bf08947..2809da064 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt @@ -26,7 +26,7 @@ import kotlinx.parcelize.Parcelize * displayed inline for parameters where user should select a single option (e.g. radio buttons). * Default value is _5_. * @param[barcode] Barcode configuration. - * @param[returnUrl] Deep link return URL. Required for the flows that include web redirect. + * @param[redirect] Redirect configuration. Required for the flows that include web redirect. * @param[paymentConfirmation] Payment confirmation configuration. * @param[success] Success screen configuration. Pass _null_ to skip the success screen. * @param[bottomSheet] Bottom sheet configuration. @@ -40,7 +40,7 @@ data class PONativeAlternativePaymentConfiguration( val cancelButton: CancelButton? = null, val inlineSingleSelectValuesLimit: Int = 5, val barcode: POBarcodeConfiguration = POBarcodeConfiguration(saveButton = POBarcodeConfiguration.Button()), - val returnUrl: String? = null, + val redirect: RedirectConfiguration? = null, val paymentConfirmation: PaymentConfirmationConfiguration = PaymentConfirmationConfiguration(), val success: SuccessConfiguration? = SuccessConfiguration(), val bottomSheet: POBottomSheetConfiguration = POBottomSheetConfiguration( @@ -83,6 +83,54 @@ data class PONativeAlternativePaymentConfiguration( ) : Flow() } + /** + * Native alternative payment configuration. + * + * @param[flow] Payment flow configuration. + * @param[title] Custom title. + * @param[submitButton] Submit button configuration. + * @param[cancelButton] Cancel button configuration. Use _null_ to hide, this is a default behaviour. + * @param[inlineSingleSelectValuesLimit] Defines the maximum number of options that will be + * displayed inline for parameters where user should select a single option (e.g. radio buttons). + * Default value is _5_. + * @param[barcode] Barcode configuration. + * @param[returnUrl] Deep link return URL. Required for the flows that include web redirect. + * @param[paymentConfirmation] Payment confirmation configuration. + * @param[success] Success screen configuration. Pass _null_ to skip the success screen. + * @param[bottomSheet] Bottom sheet configuration. + * @param[style] Custom style. + */ + @Deprecated(message = "Use alternative constructor.") + constructor( + flow: Flow, + title: String? = null, + submitButton: Button = Button(), + cancelButton: CancelButton? = null, + inlineSingleSelectValuesLimit: Int = 5, + barcode: POBarcodeConfiguration = POBarcodeConfiguration(saveButton = POBarcodeConfiguration.Button()), + returnUrl: String? = null, + paymentConfirmation: PaymentConfirmationConfiguration = PaymentConfirmationConfiguration(), + success: SuccessConfiguration? = SuccessConfiguration(), + bottomSheet: POBottomSheetConfiguration = POBottomSheetConfiguration( + height = WrapContent, + expandable = true + ), + style: Style? = null + ) : this( + flow = flow, + title = title, + submitButton = submitButton, + cancelButton = cancelButton, + inlineSingleSelectValuesLimit = inlineSingleSelectValuesLimit, + barcode = barcode, + redirect = if (!returnUrl.isNullOrBlank()) + RedirectConfiguration(returnUrl = returnUrl) else null, + paymentConfirmation = paymentConfirmation, + success = success, + bottomSheet = bottomSheet, + style = style + ) + /** * Native alternative payment configuration. * @@ -125,6 +173,7 @@ data class PONativeAlternativePaymentConfiguration( cancelButton = cancelButton, inlineSingleSelectValuesLimit = inlineSingleSelectValuesLimit, barcode = barcode, + returnUrl = null, paymentConfirmation = paymentConfirmation, success = if (!skipSuccessScreen) SuccessConfiguration(message = successMessage) else null, From ef1e418bf8bae7f045fd96e120c7f746bae09fe5 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Tue, 30 Sep 2025 18:27:02 +0300 Subject: [PATCH 04/25] Update example app --- .../ui/screen/nativeapm/NativeApmFragment.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt b/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt index cfc434891..517dcd9a6 100644 --- a/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt +++ b/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt @@ -119,7 +119,9 @@ class NativeApmFragment : BaseFragment( gatewayConfigurationId = uiModel.gatewayConfigurationId ), cancelButton = CancelButton(), - returnUrl = Constants.RETURN_URL, + redirect = RedirectConfiguration( + returnUrl = Constants.RETURN_URL + ), paymentConfirmation = PaymentConfirmationConfiguration( confirmButton = Button(), cancelButton = CancelButton(disabledForSeconds = 3) @@ -134,7 +136,10 @@ class NativeApmFragment : BaseFragment( customerTokenId = uiModel.customerTokenId ), cancelButton = CancelButton(), - returnUrl = Constants.RETURN_URL, + redirect = RedirectConfiguration( + returnUrl = Constants.RETURN_URL, + enableHeadlessMode = true + ), paymentConfirmation = PaymentConfirmationConfiguration( confirmButton = Button(), cancelButton = CancelButton(disabledForSeconds = 3) @@ -155,7 +160,9 @@ class NativeApmFragment : BaseFragment( gatewayConfigurationId = uiModel.gatewayConfigurationId ), cancelButton = CancelButton(), - returnUrl = Constants.RETURN_URL, + redirect = RedirectConfiguration( + returnUrl = Constants.RETURN_URL + ), paymentConfirmation = PaymentConfirmationConfiguration( confirmButton = Button(), cancelButton = CancelButton(disabledForSeconds = 3) From 06568d692a47f8adb31db0ad08026e63d43202f2 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 1 Oct 2025 17:08:24 +0300 Subject: [PATCH 05/25] Pass callback to launcher --- .../PONativeAlternativePaymentLauncher.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) 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 ffa7a3ea1..ede17456a 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,6 +25,7 @@ class PONativeAlternativePaymentLauncher private constructor( private val launcher: ActivityResultLauncher, private val activityOptions: ActivityOptionsCompat, private val delegate: PONativeAlternativePaymentDelegate, + private val callback: (ProcessOutActivityResult) -> Unit, private val eventDispatcher: POEventDispatcher = POEventDispatcher.instance ) { @@ -44,7 +45,8 @@ class PONativeAlternativePaymentLauncher private constructor( callback ), activityOptions = createActivityOptions(from.requireContext()), - delegate = delegate + delegate = delegate, + callback = callback ) /** @@ -65,7 +67,8 @@ class PONativeAlternativePaymentLauncher private constructor( callback ), activityOptions = createActivityOptions(from.requireContext()), - delegate = object : PONativeAlternativePaymentDelegate {} + delegate = object : PONativeAlternativePaymentDelegate {}, + callback = callback ) /** @@ -83,7 +86,8 @@ class PONativeAlternativePaymentLauncher private constructor( callback ), activityOptions = createActivityOptions(from.requireContext()), - delegate = object : PONativeAlternativePaymentDelegate {} + delegate = object : PONativeAlternativePaymentDelegate {}, + callback = callback ) /** @@ -102,7 +106,8 @@ class PONativeAlternativePaymentLauncher private constructor( callback ), activityOptions = createActivityOptions(from), - delegate = delegate + delegate = delegate, + callback = callback ) /** @@ -124,7 +129,8 @@ class PONativeAlternativePaymentLauncher private constructor( callback ), activityOptions = createActivityOptions(from), - delegate = object : PONativeAlternativePaymentDelegate {} + delegate = object : PONativeAlternativePaymentDelegate {}, + callback = callback ) /** @@ -143,7 +149,8 @@ class PONativeAlternativePaymentLauncher private constructor( callback ), activityOptions = createActivityOptions(from), - delegate = object : PONativeAlternativePaymentDelegate {} + delegate = object : PONativeAlternativePaymentDelegate {}, + callback = callback ) private fun createActivityOptions(context: Context) = From 5d10c4c47017e976e24ea437c3a402ced76847f3 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 1 Oct 2025 17:12:11 +0300 Subject: [PATCH 06/25] Pass services to launcher --- .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 ede17456a..1e930f99e 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 @@ -7,7 +7,10 @@ import androidx.core.app.ActivityOptionsCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.processout.sdk.R +import com.processout.sdk.api.ProcessOut import com.processout.sdk.api.dispatcher.POEventDispatcher +import com.processout.sdk.api.service.POCustomerTokensService +import com.processout.sdk.api.service.POInvoicesService import com.processout.sdk.core.POUnit import com.processout.sdk.core.ProcessOutActivityResult import com.processout.sdk.ui.napm.delegate.v2.NativeAlternativePaymentDefaultValuesRequest @@ -26,7 +29,9 @@ class PONativeAlternativePaymentLauncher private constructor( private val activityOptions: ActivityOptionsCompat, private val delegate: PONativeAlternativePaymentDelegate, private val callback: (ProcessOutActivityResult) -> Unit, - private val eventDispatcher: POEventDispatcher = POEventDispatcher.instance + private val eventDispatcher: POEventDispatcher = POEventDispatcher.instance, + private val invoicesService: POInvoicesService = ProcessOut.instance.invoices, + private val customerTokensService: POCustomerTokensService = ProcessOut.instance.customerTokens ) { companion object { From 4aff5eda6c1e789c56c26866d71c5afd9713c2a1 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 1 Oct 2025 17:45:43 +0300 Subject: [PATCH 07/25] Init customTabLauncher --- .../PONativeAlternativePaymentLauncher.kt | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) 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 1e930f99e..8f0847542 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 @@ -9,10 +9,13 @@ import androidx.lifecycle.lifecycleScope import com.processout.sdk.R import com.processout.sdk.api.ProcessOut import com.processout.sdk.api.dispatcher.POEventDispatcher +import com.processout.sdk.api.model.response.POAlternativePaymentMethodResponse import com.processout.sdk.api.service.POCustomerTokensService import com.processout.sdk.api.service.POInvoicesService import com.processout.sdk.core.POUnit import com.processout.sdk.core.ProcessOutActivityResult +import com.processout.sdk.core.ProcessOutResult +import com.processout.sdk.ui.apm.POAlternativePaymentMethodCustomTabLauncher 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 @@ -34,6 +37,8 @@ class PONativeAlternativePaymentLauncher private constructor( private val customerTokensService: POCustomerTokensService = ProcessOut.instance.customerTokens ) { + private lateinit var customTabLauncher: POAlternativePaymentMethodCustomTabLauncher + companion object { /** * Creates the launcher from Fragment. @@ -52,7 +57,12 @@ class PONativeAlternativePaymentLauncher private constructor( activityOptions = createActivityOptions(from.requireContext()), delegate = delegate, callback = callback - ) + ).apply { + customTabLauncher = POAlternativePaymentMethodCustomTabLauncher.create( + from = from, + callback = ::handleRedirect + ) + } /** * Creates the launcher from Fragment. @@ -74,7 +84,12 @@ class PONativeAlternativePaymentLauncher private constructor( activityOptions = createActivityOptions(from.requireContext()), delegate = object : PONativeAlternativePaymentDelegate {}, callback = callback - ) + ).apply { + customTabLauncher = POAlternativePaymentMethodCustomTabLauncher.create( + from = from, + callback = ::handleRedirect + ) + } /** * Creates the launcher from Fragment. @@ -93,7 +108,12 @@ class PONativeAlternativePaymentLauncher private constructor( activityOptions = createActivityOptions(from.requireContext()), delegate = object : PONativeAlternativePaymentDelegate {}, callback = callback - ) + ).apply { + customTabLauncher = POAlternativePaymentMethodCustomTabLauncher.create( + from = from, + callback = ::handleRedirect + ) + } /** * Creates the launcher from Activity. @@ -113,7 +133,12 @@ class PONativeAlternativePaymentLauncher private constructor( activityOptions = createActivityOptions(from), delegate = delegate, callback = callback - ) + ).apply { + customTabLauncher = POAlternativePaymentMethodCustomTabLauncher.create( + from = from, + callback = ::handleRedirect + ) + } /** * Creates the launcher from Activity. @@ -136,7 +161,12 @@ class PONativeAlternativePaymentLauncher private constructor( activityOptions = createActivityOptions(from), delegate = object : PONativeAlternativePaymentDelegate {}, callback = callback - ) + ).apply { + customTabLauncher = POAlternativePaymentMethodCustomTabLauncher.create( + from = from, + callback = ::handleRedirect + ) + } /** * Creates the launcher from Activity. @@ -156,7 +186,12 @@ class PONativeAlternativePaymentLauncher private constructor( activityOptions = createActivityOptions(from), delegate = object : PONativeAlternativePaymentDelegate {}, callback = callback - ) + ).apply { + customTabLauncher = POAlternativePaymentMethodCustomTabLauncher.create( + from = from, + callback = ::handleRedirect + ) + } private fun createActivityOptions(context: Context) = ActivityOptionsCompat.makeCustomAnimation( @@ -206,4 +241,8 @@ class PONativeAlternativePaymentLauncher private constructor( fun launch(configuration: PONativeAlternativePaymentConfiguration) { launcher.launch(configuration, activityOptions) } + + private fun handleRedirect(result: ProcessOutResult) { + // TODO + } } From 1a2d433eab27dbe77a9b2233107f1663ccfd2d8b Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 1 Oct 2025 18:09:35 +0300 Subject: [PATCH 08/25] launchHeadlessMode() --- .../PONativeAlternativePaymentLauncher.kt | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) 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 8f0847542..00d9209a9 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 @@ -16,6 +16,8 @@ import com.processout.sdk.core.POUnit import com.processout.sdk.core.ProcessOutActivityResult import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.ui.apm.POAlternativePaymentMethodCustomTabLauncher +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.PONativeAlternativePaymentDelegate import com.processout.sdk.ui.napm.delegate.v2.PONativeAlternativePaymentEvent @@ -239,7 +241,29 @@ class PONativeAlternativePaymentLauncher private constructor( * Launches the activity. */ fun launch(configuration: PONativeAlternativePaymentConfiguration) { - launcher.launch(configuration, activityOptions) + if (configuration.redirect?.enableHeadlessMode == true) { + launchHeadlessMode(configuration) + } else { + launcher.launch(configuration, activityOptions) + } + } + + private fun launchHeadlessMode(configuration: PONativeAlternativePaymentConfiguration) { + ConfigurationCache.value = configuration + scope.launch { + when (val flow = configuration.flow) { + is Authorization -> fetchAuthorizationDetails(flow) + is Tokenization -> fetchTokenizationDetails(flow) + } + } + } + + private suspend fun fetchAuthorizationDetails(flow: Authorization) { + // TODO + } + + private suspend fun fetchTokenizationDetails(flow: Tokenization) { + // TODO } private fun handleRedirect(result: ProcessOutResult) { From aeab8940f9158a8a89d17ea377773c201448829c Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 1 Oct 2025 18:24:46 +0300 Subject: [PATCH 09/25] launchActivity() --- .../PONativeAlternativePaymentLauncher.kt | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) 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 00d9209a9..ab5f1f8d0 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 @@ -244,25 +244,38 @@ class PONativeAlternativePaymentLauncher private constructor( if (configuration.redirect?.enableHeadlessMode == true) { launchHeadlessMode(configuration) } else { - launcher.launch(configuration, activityOptions) + launchActivity(configuration) } } + private fun launchActivity(configuration: PONativeAlternativePaymentConfiguration) { + launcher.launch( + input = configuration, + options = activityOptions + ) + } + private fun launchHeadlessMode(configuration: PONativeAlternativePaymentConfiguration) { ConfigurationCache.value = configuration scope.launch { when (val flow = configuration.flow) { - is Authorization -> fetchAuthorizationDetails(flow) - is Tokenization -> fetchTokenizationDetails(flow) + is Authorization -> fetchAuthorizationDetails(flow, configuration) + is Tokenization -> fetchTokenizationDetails(flow, configuration) } } } - private suspend fun fetchAuthorizationDetails(flow: Authorization) { + private suspend fun fetchAuthorizationDetails( + flow: Authorization, + configuration: PONativeAlternativePaymentConfiguration + ) { // TODO } - private suspend fun fetchTokenizationDetails(flow: Tokenization) { + private suspend fun fetchTokenizationDetails( + flow: Tokenization, + configuration: PONativeAlternativePaymentConfiguration + ) { // TODO } From 6c66497f6a63e046715aa6ba8617f738650400c6 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 1 Oct 2025 18:51:24 +0300 Subject: [PATCH 10/25] completeHeadlessMode() --- .../PONativeAlternativePaymentLauncher.kt | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) 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 ab5f1f8d0..7ba85a676 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 @@ -15,6 +15,7 @@ import com.processout.sdk.api.service.POInvoicesService import com.processout.sdk.core.POUnit import com.processout.sdk.core.ProcessOutActivityResult import com.processout.sdk.core.ProcessOutResult +import com.processout.sdk.core.toActivityResult import com.processout.sdk.ui.apm.POAlternativePaymentMethodCustomTabLauncher import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Authorization import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Tokenization @@ -41,6 +42,10 @@ class PONativeAlternativePaymentLauncher private constructor( private lateinit var customTabLauncher: POAlternativePaymentMethodCustomTabLauncher + private object ConfigurationCache { + var value: PONativeAlternativePaymentConfiguration? = null + } + companion object { /** * Creates the launcher from Fragment. @@ -201,17 +206,6 @@ class PONativeAlternativePaymentLauncher private constructor( ) } - private object ConfigurationCache { - - var value: PONativeAlternativePaymentConfiguration? = null - - fun remove(): PONativeAlternativePaymentConfiguration? { - val cached = value - value = null - return cached - } - } - init { dispatchEvents() dispatchDefaultValues() @@ -282,4 +276,9 @@ class PONativeAlternativePaymentLauncher private constructor( private fun handleRedirect(result: ProcessOutResult) { // TODO } + + private fun completeHeadlessMode(result: ProcessOutResult) { + ConfigurationCache.value = null + callback(result.toActivityResult()) + } } From a307fd18587b060510d2ce23b760053b62d18caf Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 1 Oct 2025 19:26:40 +0300 Subject: [PATCH 11/25] Fetch payment details --- .../PONativeAlternativePaymentLauncher.kt | 50 +++++++++++++++++-- 1 file changed, 45 insertions(+), 5 deletions(-) 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 7ba85a676..3aad20f49 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 @@ -9,13 +9,15 @@ import androidx.lifecycle.lifecycleScope import com.processout.sdk.R import com.processout.sdk.api.ProcessOut import com.processout.sdk.api.dispatcher.POEventDispatcher +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.POAlternativePaymentMethodResponse +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentRedirect +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState import com.processout.sdk.api.service.POCustomerTokensService import com.processout.sdk.api.service.POInvoicesService -import com.processout.sdk.core.POUnit -import com.processout.sdk.core.ProcessOutActivityResult -import com.processout.sdk.core.ProcessOutResult -import com.processout.sdk.core.toActivityResult +import com.processout.sdk.core.* +import com.processout.sdk.core.logger.POLogger import com.processout.sdk.ui.apm.POAlternativePaymentMethodCustomTabLauncher import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Authorization import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Tokenization @@ -263,12 +265,50 @@ class PONativeAlternativePaymentLauncher private constructor( flow: Authorization, configuration: PONativeAlternativePaymentConfiguration ) { - // TODO + val request = PONativeAlternativePaymentAuthorizationRequest( + invoiceId = flow.invoiceId, + gatewayConfigurationId = flow.gatewayConfigurationId, + source = flow.customerTokenId + ) + invoicesService.authorize(request) + .onSuccess { response -> + handlePaymentState( + state = response.state, + redirect = response.redirect, + configuration = configuration + ) + }.onFailure { failure -> + POLogger.info("Failed to fetch authorization details: %s", failure) + completeHeadlessMode(result = failure) + } } private suspend fun fetchTokenizationDetails( flow: Tokenization, configuration: PONativeAlternativePaymentConfiguration + ) { + val request = PONativeAlternativePaymentTokenizationRequest( + customerId = flow.customerId, + customerTokenId = flow.customerTokenId, + gatewayConfigurationId = flow.gatewayConfigurationId + ) + customerTokensService.tokenize(request) + .onSuccess { response -> + handlePaymentState( + state = response.state, + redirect = response.redirect, + configuration = configuration + ) + }.onFailure { failure -> + POLogger.info("Failed to fetch tokenization details: %s", failure) + completeHeadlessMode(result = failure) + } + } + + private fun handlePaymentState( + state: PONativeAlternativePaymentState, + redirect: PONativeAlternativePaymentRedirect?, + configuration: PONativeAlternativePaymentConfiguration ) { // TODO } From 4fea2f47e46d640ff3e68b8df7d370ad6f7b1a62 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Thu, 2 Oct 2025 17:33:31 +0300 Subject: [PATCH 12/25] Add 'initialResponse' to Flow configuration --- .../sdk/api/model/response/POImageResource.kt | 8 +++-- ...AlternativePaymentAuthorizationResponse.kt | 8 +++-- ...veAlternativePaymentCustomerInstruction.kt | 8 ++++- .../v2/PONativeAlternativePaymentElement.kt | 31 ++++++++++++++++--- ...PONativeAlternativePaymentMethodDetails.kt | 5 ++- .../v2/PONativeAlternativePaymentRedirect.kt | 5 ++- ...eAlternativePaymentTokenizationResponse.kt | 5 ++- ...PONativeAlternativePaymentConfiguration.kt | 8 +++-- 8 files changed, 64 insertions(+), 14 deletions(-) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/POImageResource.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/POImageResource.kt index 894b3b51c..92ae06fa4 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/POImageResource.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/POImageResource.kt @@ -1,7 +1,9 @@ package com.processout.sdk.api.model.response +import android.os.Parcelable import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize /** * Image resource for light/dark themes. @@ -9,21 +11,23 @@ import com.squareup.moshi.JsonClass * @param[lightUrl] URL to image for light theme. * @param[darkUrl] URL to image for dark theme. */ +@Parcelize @JsonClass(generateAdapter = true) data class POImageResource( @Json(name = "light_url") val lightUrl: ResourceUrl, @Json(name = "dark_url") val darkUrl: ResourceUrl? -) { +) : Parcelable { /** * Image resource URL. * * @param[raster] URL to raster image. */ + @Parcelize @JsonClass(generateAdapter = true) data class ResourceUrl( val raster: String - ) + ) : Parcelable } 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 b2ba4adc8..fa29e3d0e 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 @@ -1,8 +1,10 @@ package com.processout.sdk.api.model.response.napm.v2 +import android.os.Parcelable import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAuthorizationResponse.Invoice import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize /** * Specifies details of native alternative payment. @@ -13,13 +15,14 @@ import com.squareup.moshi.JsonClass * @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. */ +@Parcelize data class PONativeAlternativePaymentAuthorizationResponse( val state: PONativeAlternativePaymentState, val invoice: Invoice, val paymentMethod: PONativeAlternativePaymentMethodDetails, val elements: List?, val redirect: PONativeAlternativePaymentRedirect? -) { +) : Parcelable { /** * Invoice details. @@ -27,11 +30,12 @@ data class PONativeAlternativePaymentAuthorizationResponse( * @param[amount] Invoice amount. * @param[currency] Invoice currency. */ + @Parcelize @JsonClass(generateAdapter = true) data class Invoice( val amount: String, val currency: String - ) + ) : Parcelable } @JsonClass(generateAdapter = true) diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentCustomerInstruction.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentCustomerInstruction.kt index 1e8d341ba..be6e6f758 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentCustomerInstruction.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentCustomerInstruction.kt @@ -1,15 +1,17 @@ package com.processout.sdk.api.model.response.napm.v2 +import android.os.Parcelable import com.processout.sdk.api.model.response.POBarcode import com.processout.sdk.api.model.response.POImageResource import com.processout.sdk.core.annotation.ProcessOutInternalApi import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize /** * Specifies instruction for the customer, providing additional information and/or describing required actions. */ -sealed class PONativeAlternativePaymentCustomerInstruction { +sealed class PONativeAlternativePaymentCustomerInstruction : Parcelable { /** * Customer instruction provided as a markdown text. @@ -17,6 +19,7 @@ sealed class PONativeAlternativePaymentCustomerInstruction { * @param[label] Optional label. * @param[value] Markdown text. */ + @Parcelize @JsonClass(generateAdapter = true) data class Message( val label: String?, @@ -28,6 +31,7 @@ sealed class PONativeAlternativePaymentCustomerInstruction { * * @param[value] Image resource. */ + @Parcelize @JsonClass(generateAdapter = true) data class Image( val value: POImageResource @@ -39,6 +43,7 @@ sealed class PONativeAlternativePaymentCustomerInstruction { * @param[rawSubtype] Raw barcode subtype. * @param[rawValue] Base64 encoded value. */ + @Parcelize @JsonClass(generateAdapter = true) data class Barcode( @Json(name = "subtype") @@ -59,6 +64,7 @@ sealed class PONativeAlternativePaymentCustomerInstruction { * Placeholder that allows adding additional cases while staying backward compatible. * __Warning:__ Do not match this case directly, use _when-else_ instead. */ + @Parcelize @ProcessOutInternalApi data object Unknown : PONativeAlternativePaymentCustomerInstruction() } diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentElement.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentElement.kt index 7665dee08..9f5e39962 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentElement.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentElement.kt @@ -1,19 +1,23 @@ package com.processout.sdk.api.model.response.napm.v2 +import android.os.Parcelable import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentElement.Form.Parameter import com.processout.sdk.core.annotation.ProcessOutInternalApi import com.processout.sdk.core.util.findBy import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize /** * Specifies an element that needs to be rendered on the UI during native alternative payment flow. */ -sealed class PONativeAlternativePaymentElement { +sealed class PONativeAlternativePaymentElement : Parcelable { /** * A form element with the specified [parameterDefinitions]. */ + @Parcelize data class Form( val parameterDefinitions: List ) : PONativeAlternativePaymentElement() { @@ -21,7 +25,7 @@ sealed class PONativeAlternativePaymentElement { /** * Payment parameter definition. */ - sealed class Parameter { + sealed class Parameter : Parcelable { /** Parameter key. */ abstract val key: String @@ -41,6 +45,7 @@ sealed class PONativeAlternativePaymentElement { * @param[minLength] Optional minimum length. * @param[maxLength] Optional maximum length. */ + @Parcelize @JsonClass(generateAdapter = true) data class Text( override val key: String, @@ -60,6 +65,7 @@ sealed class PONativeAlternativePaymentElement { * @param[required] Indicates whether the parameter is required. * @param[availableValues] Available values. */ + @Parcelize @JsonClass(generateAdapter = true) data class SingleSelect( override val key: String, @@ -80,12 +86,13 @@ sealed class PONativeAlternativePaymentElement { * @param[label] Value display label. * @param[preselected] Indicates whether the value should be preselected by default. */ + @Parcelize @JsonClass(generateAdapter = true) data class AvailableValue( val value: String, val label: String, val preselected: Boolean - ) + ) : Parcelable } /** @@ -95,6 +102,7 @@ sealed class PONativeAlternativePaymentElement { * @param[label] Parameter display label. * @param[required] Indicates whether the parameter is required. */ + @Parcelize @JsonClass(generateAdapter = true) data class Bool( override val key: String, @@ -111,6 +119,7 @@ sealed class PONativeAlternativePaymentElement { * @param[minLength] Optional minimum length. * @param[maxLength] Optional maximum length. */ + @Parcelize @JsonClass(generateAdapter = true) data class Digits( override val key: String, @@ -130,6 +139,7 @@ sealed class PONativeAlternativePaymentElement { * @param[required] Indicates whether the parameter is required. * @param[dialingCodes] Supported international dialing codes. */ + @Parcelize @JsonClass(generateAdapter = true) data class PhoneNumber( override val key: String, @@ -146,12 +156,13 @@ sealed class PONativeAlternativePaymentElement { * Corresponds to a two-letter ISO 3166-1 alpha-2 country code. * @param[value] Dialing code value. */ + @Parcelize @JsonClass(generateAdapter = true) data class DialingCode( @Json(name = "region_code") val regionCode: String, val value: String - ) + ) : Parcelable } /** @@ -161,6 +172,7 @@ sealed class PONativeAlternativePaymentElement { * @param[label] Parameter display label. * @param[required] Indicates whether the parameter is required. */ + @Parcelize @JsonClass(generateAdapter = true) data class Email( override val key: String, @@ -177,6 +189,7 @@ sealed class PONativeAlternativePaymentElement { * @param[minLength] Optional minimum length. * @param[maxLength] Optional maximum length. */ + @Parcelize @JsonClass(generateAdapter = true) data class Card( override val key: String, @@ -198,6 +211,7 @@ sealed class PONativeAlternativePaymentElement { * @param[minLength] Optional minimum length. * @param[maxLength] Optional maximum length. */ + @Parcelize @JsonClass(generateAdapter = true) data class Otp( override val key: String, @@ -238,10 +252,16 @@ sealed class PONativeAlternativePaymentElement { * Placeholder that allows adding additional cases while staying backward compatible. * __Warning:__ Do not match this case directly, use _when-else_ instead. */ + @Parcelize @ProcessOutInternalApi data object Unknown : Parameter() { + @IgnoredOnParcel override val key = String() + + @IgnoredOnParcel override val label = String() + + @IgnoredOnParcel override val required = false } } @@ -250,6 +270,7 @@ sealed class PONativeAlternativePaymentElement { /** * Specifies instruction for the customer, providing additional information and/or describing required actions. */ + @Parcelize data class CustomerInstruction( val instruction: PONativeAlternativePaymentCustomerInstruction ) : PONativeAlternativePaymentElement() @@ -260,6 +281,7 @@ sealed class PONativeAlternativePaymentElement { * @param[label] Optional group display label. * @param[instructions] Grouped instructions for the customer that provide additional information and/or describe required actions. */ + @Parcelize data class CustomerInstructionGroup( val label: String?, val instructions: List @@ -269,6 +291,7 @@ sealed class PONativeAlternativePaymentElement { * Placeholder that allows adding additional cases while staying backward compatible. * __Warning:__ Do not match this case directly, use _when-else_ instead. */ + @Parcelize @ProcessOutInternalApi data object Unknown : PONativeAlternativePaymentElement() } diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentMethodDetails.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentMethodDetails.kt index 83f65c3c2..52d48cfd7 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentMethodDetails.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentMethodDetails.kt @@ -1,8 +1,10 @@ package com.processout.sdk.api.model.response.napm.v2 +import android.os.Parcelable import com.processout.sdk.api.model.response.POImageResource import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize /** * Specifies payment method details. @@ -10,9 +12,10 @@ import com.squareup.moshi.JsonClass * @param[displayName] Payment method display name. * @param[logo] Image resource for light/dark themes. */ +@Parcelize @JsonClass(generateAdapter = true) data class PONativeAlternativePaymentMethodDetails( @Json(name = "display_name") val displayName: String, val logo: POImageResource -) +) : Parcelable diff --git a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentRedirect.kt b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentRedirect.kt index e86643acf..38787b5d5 100644 --- a/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentRedirect.kt +++ b/sdk/src/main/kotlin/com/processout/sdk/api/model/response/napm/v2/PONativeAlternativePaymentRedirect.kt @@ -1,6 +1,8 @@ package com.processout.sdk.api.model.response.napm.v2 +import android.os.Parcelable import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize /** * Specifies native alternative payment redirect parameters. @@ -8,8 +10,9 @@ import com.squareup.moshi.JsonClass * @param[url] Redirect URL. * @param[hint] A hint or description associated with the redirect URL. */ +@Parcelize @JsonClass(generateAdapter = true) data class PONativeAlternativePaymentRedirect( val url: String, val hint: String -) +) : Parcelable 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 a58e781e5..2ac5cf539 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 @@ -1,7 +1,9 @@ package com.processout.sdk.api.model.response.napm.v2 +import android.os.Parcelable import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize /** * Specifies details of native alternative payment. @@ -11,12 +13,13 @@ import com.squareup.moshi.JsonClass * @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. */ +@Parcelize data class PONativeAlternativePaymentTokenizationResponse( val state: PONativeAlternativePaymentState, val paymentMethod: PONativeAlternativePaymentMethodDetails, val elements: List?, val redirect: PONativeAlternativePaymentRedirect? -) +) : Parcelable @JsonClass(generateAdapter = true) internal data class NativeAlternativePaymentTokenizationResponseBody( diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt index 2809da064..6e6ba0db7 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt @@ -4,6 +4,8 @@ import android.os.Parcelable import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.IntRange +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAuthorizationResponse +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentTokenizationResponse import com.processout.sdk.ui.core.shared.image.PODrawableImage import com.processout.sdk.ui.core.style.* import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.CancelButton @@ -65,7 +67,8 @@ data class PONativeAlternativePaymentConfiguration( data class Authorization( val invoiceId: String, val gatewayConfigurationId: String, - val customerTokenId: String? = null + val customerTokenId: String? = null, + internal val initialResponse: PONativeAlternativePaymentAuthorizationResponse? = null ) : Flow() /** @@ -79,7 +82,8 @@ data class PONativeAlternativePaymentConfiguration( data class Tokenization( val customerId: String, val customerTokenId: String, - val gatewayConfigurationId: String + val gatewayConfigurationId: String, + internal val initialResponse: PONativeAlternativePaymentTokenizationResponse? = null ) : Flow() } From 1148b5e2eaeb1f23a11f4f0ce47ed45dcfe42466 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 18:19:01 +0300 Subject: [PATCH 13/25] Use 'initialResponse' in interactor when its non-null in configuration --- .../NativeAlternativePaymentInteractor.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) 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 6beb49d79..82b994c05 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 @@ -159,6 +159,19 @@ internal class NativeAlternativePaymentInteractor( } private suspend fun fetchAuthorizationDetails(flow: Authorization) { + val initialResponse = flow.initialResponse + if (initialResponse != null) { + handlePaymentState( + stateValue = initNextStepStateValue( + paymentMethod = initialResponse.paymentMethod, + invoice = initialResponse.invoice + ), + paymentState = initialResponse.state, + elements = initialResponse.elements, + redirect = initialResponse.redirect + ) + return + } val request = PONativeAlternativePaymentAuthorizationRequest( invoiceId = flow.invoiceId, gatewayConfigurationId = flow.gatewayConfigurationId, @@ -182,6 +195,19 @@ internal class NativeAlternativePaymentInteractor( } private suspend fun fetchTokenizationDetails(flow: Tokenization) { + val initialResponse = flow.initialResponse + if (initialResponse != null) { + handlePaymentState( + stateValue = initNextStepStateValue( + paymentMethod = initialResponse.paymentMethod, + invoice = null + ), + paymentState = initialResponse.state, + elements = initialResponse.elements, + redirect = initialResponse.redirect + ) + return + } val request = PONativeAlternativePaymentTokenizationRequest( customerId = flow.customerId, customerTokenId = flow.customerTokenId, From ac384992b05d5bee691c418664693b78ae9c8807 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 18:25:41 +0300 Subject: [PATCH 14/25] Set 'initialResponse' in configuration during headless mode --- .../ui/napm/PONativeAlternativePaymentLauncher.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 3aad20f49..9cca7430b 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 @@ -272,10 +272,14 @@ class PONativeAlternativePaymentLauncher private constructor( ) invoicesService.authorize(request) .onSuccess { response -> + val updatedConfiguration = configuration.copy( + flow = flow.copy(initialResponse = response) + ) + ConfigurationCache.value = updatedConfiguration handlePaymentState( state = response.state, redirect = response.redirect, - configuration = configuration + configuration = updatedConfiguration ) }.onFailure { failure -> POLogger.info("Failed to fetch authorization details: %s", failure) @@ -294,10 +298,14 @@ class PONativeAlternativePaymentLauncher private constructor( ) customerTokensService.tokenize(request) .onSuccess { response -> + val updatedConfiguration = configuration.copy( + flow = flow.copy(initialResponse = response) + ) + ConfigurationCache.value = updatedConfiguration handlePaymentState( state = response.state, redirect = response.redirect, - configuration = configuration + configuration = updatedConfiguration ) }.onFailure { failure -> POLogger.info("Failed to fetch tokenization details: %s", failure) From 580a8335e026507358228d78edfd40396d4aebd1 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 19:04:24 +0300 Subject: [PATCH 15/25] Handle UNKNOWN state --- .../napm/PONativeAlternativePaymentLauncher.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 9cca7430b..933db2a4e 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 @@ -14,9 +14,11 @@ import com.processout.sdk.api.model.request.napm.v2.PONativeAlternativePaymentTo import com.processout.sdk.api.model.response.POAlternativePaymentMethodResponse import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentRedirect import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState +import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentState.* import com.processout.sdk.api.service.POCustomerTokensService import com.processout.sdk.api.service.POInvoicesService import com.processout.sdk.core.* +import com.processout.sdk.core.POFailure.Code.Internal import com.processout.sdk.core.logger.POLogger import com.processout.sdk.ui.apm.POAlternativePaymentMethodCustomTabLauncher import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Authorization @@ -318,7 +320,19 @@ class PONativeAlternativePaymentLauncher private constructor( redirect: PONativeAlternativePaymentRedirect?, configuration: PONativeAlternativePaymentConfiguration ) { - // TODO + when (state) { + NEXT_STEP_REQUIRED -> TODO() + PENDING -> TODO() + SUCCESS -> TODO() + UNKNOWN -> { + val failure = ProcessOutResult.Failure( + code = Internal(), + message = "Unsupported payment state." + ) + POLogger.error(message = "%s", failure) + completeHeadlessMode(result = failure) + } + } } private fun handleRedirect(result: ProcessOutResult) { From 35dbdcc96065070114034bccc8cab76ec5c1671f Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 19:12:06 +0300 Subject: [PATCH 16/25] Handle SUCCESS state --- .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 933db2a4e..6ac8e3a1f 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 @@ -323,7 +323,13 @@ class PONativeAlternativePaymentLauncher private constructor( when (state) { NEXT_STEP_REQUIRED -> TODO() PENDING -> TODO() - SUCCESS -> TODO() + SUCCESS -> + if (configuration.success == null) { + POLogger.info("Success: payment completed.") + completeHeadlessMode(result = ProcessOutResult.Success(value = POUnit)) + } else { + launchActivity(configuration) + } UNKNOWN -> { val failure = ProcessOutResult.Failure( code = Internal(), From b659d269213a4a4ff416cb41e9406067540e9e39 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 19:14:03 +0300 Subject: [PATCH 17/25] Handle PENDING state --- .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6ac8e3a1f..aa49733ec 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 @@ -322,7 +322,7 @@ class PONativeAlternativePaymentLauncher private constructor( ) { when (state) { NEXT_STEP_REQUIRED -> TODO() - PENDING -> TODO() + PENDING -> launchActivity(configuration) SUCCESS -> if (configuration.success == null) { POLogger.info("Success: payment completed.") From ff77a7c02bfa8f23a492d7d8fb41c93d39710294 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 19:18:33 +0300 Subject: [PATCH 18/25] Code fix --- .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 aa49733ec..fd3ac5518 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 @@ -324,11 +324,11 @@ class PONativeAlternativePaymentLauncher private constructor( NEXT_STEP_REQUIRED -> TODO() PENDING -> launchActivity(configuration) SUCCESS -> - if (configuration.success == null) { + if (configuration.success != null) { + launchActivity(configuration) + } else { POLogger.info("Success: payment completed.") completeHeadlessMode(result = ProcessOutResult.Success(value = POUnit)) - } else { - launchActivity(configuration) } UNKNOWN -> { val failure = ProcessOutResult.Failure( From 7d34bf1531b870928f5c5690a2aeacaee7bb0fe0 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 19:41:42 +0300 Subject: [PATCH 19/25] handleNextStep() --- .../PONativeAlternativePaymentLauncher.kt | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) 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 fd3ac5518..e30119be1 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 @@ -4,6 +4,7 @@ import android.content.Context import androidx.activity.ComponentActivity import androidx.activity.result.ActivityResultLauncher import androidx.core.app.ActivityOptionsCompat +import androidx.core.net.toUri import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import com.processout.sdk.R @@ -18,6 +19,7 @@ import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentS import com.processout.sdk.api.service.POCustomerTokensService import com.processout.sdk.api.service.POInvoicesService import com.processout.sdk.core.* +import com.processout.sdk.core.POFailure.Code.Generic import com.processout.sdk.core.POFailure.Code.Internal import com.processout.sdk.core.logger.POLogger import com.processout.sdk.ui.apm.POAlternativePaymentMethodCustomTabLauncher @@ -321,7 +323,7 @@ class PONativeAlternativePaymentLauncher private constructor( configuration: PONativeAlternativePaymentConfiguration ) { when (state) { - NEXT_STEP_REQUIRED -> TODO() + NEXT_STEP_REQUIRED -> handleNextStep(redirect, configuration) PENDING -> launchActivity(configuration) SUCCESS -> if (configuration.success != null) { @@ -341,6 +343,30 @@ class PONativeAlternativePaymentLauncher private constructor( } } + private fun handleNextStep( + redirect: PONativeAlternativePaymentRedirect?, + configuration: PONativeAlternativePaymentConfiguration + ) { + if (redirect == null) { + launchActivity(configuration) + return + } + val returnUrl = configuration.redirect?.returnUrl + if (returnUrl.isNullOrBlank()) { + val failure = ProcessOutResult.Failure( + code = Generic(), + message = "Return URL is missing in configuration during redirect flow." + ) + POLogger.warn(message = "Failed redirect: %s", failure) + completeHeadlessMode(result = failure) + return + } + customTabLauncher.launch( + uri = redirect.url.toUri(), + returnUrl = returnUrl + ) + } + private fun handleRedirect(result: ProcessOutResult) { // TODO } From c34c1f97218b4b231fff9c8d1a63ecd38d9404e6 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 19:50:18 +0300 Subject: [PATCH 20/25] Rename methods --- .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 e30119be1..2c4abd9ce 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 @@ -259,13 +259,13 @@ class PONativeAlternativePaymentLauncher private constructor( ConfigurationCache.value = configuration scope.launch { when (val flow = configuration.flow) { - is Authorization -> fetchAuthorizationDetails(flow, configuration) - is Tokenization -> fetchTokenizationDetails(flow, configuration) + is Authorization -> authorize(flow, configuration) + is Tokenization -> tokenize(flow, configuration) } } } - private suspend fun fetchAuthorizationDetails( + private suspend fun authorize( flow: Authorization, configuration: PONativeAlternativePaymentConfiguration ) { @@ -291,7 +291,7 @@ class PONativeAlternativePaymentLauncher private constructor( } } - private suspend fun fetchTokenizationDetails( + private suspend fun tokenize( flow: Tokenization, configuration: PONativeAlternativePaymentConfiguration ) { From a43f9e6a533404e1a9869cf984aa27b56c57c561 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Mon, 6 Oct 2025 20:00:42 +0300 Subject: [PATCH 21/25] handleRedirect() --- .../ui/screen/nativeapm/NativeApmFragment.kt | 3 ++- .../napm/PONativeAlternativePaymentLauncher.kt | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt b/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt index 517dcd9a6..d04aeb8fb 100644 --- a/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt +++ b/example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt @@ -143,7 +143,8 @@ class NativeApmFragment : BaseFragment( paymentConfirmation = PaymentConfirmationConfiguration( confirmButton = Button(), cancelButton = CancelButton(disabledForSeconds = 3) - ) + ), + success = null ) ) AUTHORIZE_LEGACY -> launcherLegacy.launch( 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 2c4abd9ce..79f452655 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 @@ -368,7 +368,21 @@ class PONativeAlternativePaymentLauncher private constructor( } private fun handleRedirect(result: ProcessOutResult) { - // TODO + result.onSuccess { + val configuration = ConfigurationCache.value + if (configuration == null) { + // TODO - completeHeadlessMode + return + } + scope.launch { + when (val flow = configuration.flow) { + is Authorization -> authorize(flow, configuration) + is Tokenization -> tokenize(flow, configuration) + } + } + }.onFailure { failure -> + completeHeadlessMode(result = failure) + } } private fun completeHeadlessMode(result: ProcessOutResult) { From dec82156f7a6cab33bf60e585d6a378187fa524a Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 8 Oct 2025 13:54:01 +0300 Subject: [PATCH 22/25] Complete when configuration is not cached --- .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 79f452655..73890fb67 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 @@ -357,7 +357,7 @@ class PONativeAlternativePaymentLauncher private constructor( code = Generic(), message = "Return URL is missing in configuration during redirect flow." ) - POLogger.warn(message = "Failed redirect: %s", failure) + POLogger.warn(message = "Failed headless redirect: %s", failure) completeHeadlessMode(result = failure) return } @@ -371,7 +371,12 @@ class PONativeAlternativePaymentLauncher private constructor( result.onSuccess { val configuration = ConfigurationCache.value if (configuration == null) { - // TODO - completeHeadlessMode + val failure = ProcessOutResult.Failure( + code = Internal(), + message = "Configuration is not cached when handling a redirect result." + ) + POLogger.error(message = "Failed headless redirect: %s", failure) + completeHeadlessMode(result = failure) return } scope.launch { From 81ad87cbc8db7bc44ca45902a2d2b22642f2257d Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 8 Oct 2025 14:11:49 +0300 Subject: [PATCH 23/25] Log --- .../sdk/ui/napm/PONativeAlternativePaymentLauncher.kt | 2 ++ 1 file changed, 2 insertions(+) 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 73890fb67..bd61a55f5 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 @@ -256,6 +256,7 @@ class PONativeAlternativePaymentLauncher private constructor( } private fun launchHeadlessMode(configuration: PONativeAlternativePaymentConfiguration) { + POLogger.info("Starting native alternative payment in headless mode.") ConfigurationCache.value = configuration scope.launch { when (val flow = configuration.flow) { @@ -386,6 +387,7 @@ class PONativeAlternativePaymentLauncher private constructor( } } }.onFailure { failure -> + POLogger.warn(message = "Failed headless redirect: %s", failure) completeHeadlessMode(result = failure) } } From 0e8d3122eb0bb6eed2c612bfee5c58b8a6f56b52 Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 8 Oct 2025 14:44:05 +0300 Subject: [PATCH 24/25] Dispatch DidFail event --- .../ui/napm/PONativeAlternativePaymentLauncher.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 bd61a55f5..42a30bcae 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 @@ -28,6 +28,7 @@ import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.T 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.PONativeAlternativePaymentEvent.DidFail import com.processout.sdk.ui.napm.delegate.v2.toResponse import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -393,6 +394,20 @@ class PONativeAlternativePaymentLauncher private constructor( } private fun completeHeadlessMode(result: ProcessOutResult) { + result.onFailure { failure -> + scope.launch { + eventDispatcher.send( + event = DidFail( + failure = failure, + paymentState = when (val flow = ConfigurationCache.value?.flow) { + is Authorization -> flow.initialResponse?.state ?: UNKNOWN + is Tokenization -> flow.initialResponse?.state ?: UNKNOWN + null -> UNKNOWN + } + ) + ) + } + } ConfigurationCache.value = null callback(result.toActivityResult()) } From fae4d3ae495a1953062d876a604715fc707fd1ba Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 8 Oct 2025 16:52:03 +0300 Subject: [PATCH 25/25] LogAttributes --- .../processout/sdk/ui/napm/LogAttributes.kt | 21 ++++++++++++ .../NativeAlternativePaymentInteractor.kt | 34 +++++-------------- .../PONativeAlternativePaymentLauncher.kt | 17 +++++++--- 3 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 ui/src/main/kotlin/com/processout/sdk/ui/napm/LogAttributes.kt diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/LogAttributes.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/LogAttributes.kt new file mode 100644 index 000000000..73cd2c9ed --- /dev/null +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/LogAttributes.kt @@ -0,0 +1,21 @@ +package com.processout.sdk.ui.napm + +import com.processout.sdk.core.logger.POLogAttribute.CUSTOMER_ID +import com.processout.sdk.core.logger.POLogAttribute.CUSTOMER_TOKEN_ID +import com.processout.sdk.core.logger.POLogAttribute.GATEWAY_CONFIGURATION_ID +import com.processout.sdk.core.logger.POLogAttribute.INVOICE_ID +import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Authorization +import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Tokenization + +internal val PONativeAlternativePaymentConfiguration.logAttributes: Map + get() = when (flow) { + is Authorization -> mapOf( + INVOICE_ID to flow.invoiceId, + GATEWAY_CONFIGURATION_ID to flow.gatewayConfigurationId + ) + is Tokenization -> mapOf( + CUSTOMER_ID to flow.customerId, + CUSTOMER_TOKEN_ID to flow.customerTokenId, + GATEWAY_CONFIGURATION_ID to flow.gatewayConfigurationId + ) + } 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 82b994c05..ad3271fc4 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 @@ -38,7 +38,6 @@ import com.processout.sdk.core.POFailure.InvalidField import com.processout.sdk.core.POFailure.ValidationCode import com.processout.sdk.core.ProcessOutResult import com.processout.sdk.core.fold -import com.processout.sdk.core.logger.POLogAttribute import com.processout.sdk.core.logger.POLogger import com.processout.sdk.core.onFailure import com.processout.sdk.core.onSuccess @@ -53,7 +52,6 @@ import com.processout.sdk.ui.napm.NativeAlternativePaymentInteractorState.* import com.processout.sdk.ui.napm.NativeAlternativePaymentSideEffect.PermissionRequest import com.processout.sdk.ui.napm.NativeAlternativePaymentSideEffect.Redirect import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.CancelButton -import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow 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 @@ -82,25 +80,9 @@ internal class NativeAlternativePaymentInteractor( private val barcodeBitmapProvider: BarcodeBitmapProvider, private val mediaStorageProvider: MediaStorageProvider, private val captureRetryStrategy: PORetryStrategy, - private val eventDispatcher: POEventDispatcher = POEventDispatcher.instance, - private var logAttributes: Map = logAttributes(configuration.flow) + private val eventDispatcher: POEventDispatcher = POEventDispatcher.instance ) : BaseInteractor() { - private companion object { - fun logAttributes(flow: Flow): Map = - when (flow) { - is Authorization -> mapOf( - POLogAttribute.INVOICE_ID to flow.invoiceId, - POLogAttribute.GATEWAY_CONFIGURATION_ID to flow.gatewayConfigurationId - ) - is Tokenization -> mapOf( - POLogAttribute.CUSTOMER_ID to flow.customerId, - POLogAttribute.CUSTOMER_TOKEN_ID to flow.customerTokenId, - POLogAttribute.GATEWAY_CONFIGURATION_ID to flow.gatewayConfigurationId - ) - } - } - private val _completion = MutableStateFlow(Awaiting) val completion = _completion.asStateFlow() @@ -135,7 +117,6 @@ internal class NativeAlternativePaymentInteractor( return } this.configuration = configuration - logAttributes = logAttributes(configuration.flow) start() } @@ -278,7 +259,7 @@ internal class NativeAlternativePaymentInteractor( ) POLogger.error( message = "%s", failure, - attributes = logAttributes + attributes = configuration.logAttributes ) _completion.update { Failure(failure) } } @@ -298,7 +279,7 @@ internal class NativeAlternativePaymentInteractor( if (parameters.isEmpty()) { POLogger.warn( message = "Parameters is empty in response.", - attributes = logAttributes + attributes = configuration.logAttributes ) } if (failWithUnknownParameter(parameters)) { @@ -389,7 +370,7 @@ internal class NativeAlternativePaymentInteractor( ) POLogger.error( message = "Unexpected response: %s", failure, - attributes = logAttributes + attributes = configuration.logAttributes ) _completion.update { Failure(failure) } return true @@ -654,7 +635,7 @@ internal class NativeAlternativePaymentInteractor( ) POLogger.warn( message = "Failed redirect: %s", failure, - attributes = logAttributes + attributes = configuration.logAttributes ) _completion.update { Failure(failure) } return @@ -1284,7 +1265,10 @@ internal class NativeAlternativePaymentInteractor( interactorScope.launch { _completion.collect { if (it is Failure) { - POLogger.warn("%s", it.failure, attributes = logAttributes) + POLogger.warn( + message = "%s", it.failure, + attributes = configuration.logAttributes + ) dispatch(DidFail(it.failure, paymentState)) } } 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 42a30bcae..172815de6 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 @@ -339,7 +339,10 @@ class PONativeAlternativePaymentLauncher private constructor( code = Internal(), message = "Unsupported payment state." ) - POLogger.error(message = "%s", failure) + POLogger.error( + message = "%s", failure, + attributes = configuration.logAttributes + ) completeHeadlessMode(result = failure) } } @@ -359,7 +362,10 @@ class PONativeAlternativePaymentLauncher private constructor( code = Generic(), message = "Return URL is missing in configuration during redirect flow." ) - POLogger.warn(message = "Failed headless redirect: %s", failure) + POLogger.warn( + message = "Failed headless redirect: %s", failure, + attributes = configuration.logAttributes + ) completeHeadlessMode(result = failure) return } @@ -370,8 +376,8 @@ class PONativeAlternativePaymentLauncher private constructor( } private fun handleRedirect(result: ProcessOutResult) { + val configuration = ConfigurationCache.value result.onSuccess { - val configuration = ConfigurationCache.value if (configuration == null) { val failure = ProcessOutResult.Failure( code = Internal(), @@ -388,7 +394,10 @@ class PONativeAlternativePaymentLauncher private constructor( } } }.onFailure { failure -> - POLogger.warn(message = "Failed headless redirect: %s", failure) + POLogger.warn( + message = "Failed headless redirect: %s", failure, + attributes = configuration?.logAttributes + ) completeHeadlessMode(result = failure) } }