From 31ec3491c8db702383d6e9172f1b483a39c461bd Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 9 Jul 2025 15:09:33 +0300 Subject: [PATCH 1/4] Added Flow to config, previous constructor deprecated, updated affected implementations --- .../ui/screen/nativeapm/NativeApmFragment.kt | 7 +- .../ui/checkout/DynamicCheckoutActivity.kt | 7 +- .../ui/checkout/DynamicCheckoutInteractor.kt | 7 +- .../NativeAlternativePaymentBottomSheet.kt | 25 ++---- ...PONativeAlternativePaymentConfiguration.kt | 89 ++++++++++++++++--- .../v2/NativeAlternativePaymentInteractor.kt | 5 +- 6 files changed, 103 insertions(+), 37 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 3f13a63e0..2d62e6bcc 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 @@ -17,6 +17,7 @@ import com.processout.sdk.core.onFailure import com.processout.sdk.core.onSuccess import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Button +import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow import com.processout.sdk.ui.napm.PONativeAlternativePaymentLauncher import com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentDelegate import com.processout.sdk.ui.nativeapm.PONativeAlternativePaymentMethodConfiguration @@ -100,8 +101,10 @@ class NativeApmFragment : BaseFragment( if (uiModel.launchCompose) { launcherCompose.launch( PONativeAlternativePaymentConfiguration( - invoiceId = uiModel.invoiceId, - gatewayConfigurationId = uiModel.gatewayConfigurationId, + flow = Flow.Authorization( + invoiceId = uiModel.invoiceId, + gatewayConfigurationId = uiModel.gatewayConfigurationId + ), submitButton = Button() ) ) diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt index fbb2eff83..213e04582 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt @@ -49,6 +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.savedpaymentmethods.POSavedPaymentMethodsLauncher import com.processout.sdk.ui.savedpaymentmethods.delegate.POSavedPaymentMethodsDelegate @@ -128,8 +129,10 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() { private fun nativeAlternativePaymentConfiguration(): PONativeAlternativePaymentConfiguration { val paymentConfirmation = configuration.alternativePayment.paymentConfirmation return PONativeAlternativePaymentConfiguration( - invoiceId = configuration.invoiceRequest.invoiceId, - gatewayConfigurationId = String(), + flow = Flow.Authorization( + invoiceId = configuration.invoiceRequest.invoiceId, + gatewayConfigurationId = String() + ), submitButton = configuration.submitButton.map(), cancelButton = configuration.cancelButton?.map(), paymentConfirmation = PaymentConfirmationConfiguration( diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt index 195465e84..f58870f07 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt @@ -56,6 +56,7 @@ import com.processout.sdk.ui.checkout.delegate.* import com.processout.sdk.ui.checkout.delegate.PODynamicCheckoutEvent.* import com.processout.sdk.ui.napm.* import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.* +import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow import com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentEvent import com.processout.sdk.ui.savedpaymentmethods.POSavedPaymentMethodsConfiguration import com.processout.sdk.ui.shared.extension.orElse @@ -531,8 +532,10 @@ internal class DynamicCheckoutInteractor( gatewayConfigurationId: String, configuration: AlternativePaymentConfiguration ) = copy( - invoiceId = invoiceId, - gatewayConfigurationId = gatewayConfigurationId, + flow = Flow.Authorization( + invoiceId = invoiceId, + gatewayConfigurationId = gatewayConfigurationId + ), paymentConfirmation = paymentConfirmation.apply(configuration.paymentConfirmation), barcode = configuration.barcode, inlineSingleSelectValuesLimit = configuration.inlineSingleSelectValuesLimit 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 5204e8846..de3930cd0 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 @@ -16,7 +16,10 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.processout.sdk.api.dispatcher.PODefaultEventDispatchers -import com.processout.sdk.core.* +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.base.BaseBottomSheetDialogFragment import com.processout.sdk.ui.core.component.POIme.isImeVisibleAsState import com.processout.sdk.ui.core.theme.ProcessOutTheme @@ -29,7 +32,7 @@ import com.processout.sdk.ui.napm.NativeAlternativePaymentEvent.PermissionReques import com.processout.sdk.ui.napm.NativeAlternativePaymentScreen.AnimationDurationMillis import com.processout.sdk.ui.napm.NativeAlternativePaymentSideEffect.PermissionRequest import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.Capture -import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Button +import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow import com.processout.sdk.ui.shared.component.screenModeAsState import com.processout.sdk.ui.shared.extension.collectImmediately import com.processout.sdk.ui.shared.extension.dpToPx @@ -52,9 +55,10 @@ internal class NativeAlternativePaymentBottomSheet : BaseBottomSheetDialogFragme NativeAlternativePaymentViewModel.Factory( app = requireActivity().application, configuration = configuration ?: PONativeAlternativePaymentConfiguration( - invoiceId = String(), - gatewayConfigurationId = String(), - submitButton = Button() + flow = Flow.Authorization( + invoiceId = String(), + gatewayConfigurationId = String() + ) ), legacyEventDispatcher = PODefaultEventDispatchers.defaultNativeAlternativePaymentMethod ) @@ -69,17 +73,6 @@ internal class NativeAlternativePaymentBottomSheet : BaseBottomSheetDialogFragme super.onAttach(context) @Suppress("DEPRECATION") configuration = arguments?.getParcelable(EXTRA_CONFIGURATION) - configuration?.run { - if (invoiceId.isBlank() || gatewayConfigurationId.isBlank()) { - dismiss( - ProcessOutResult.Failure( - code = POFailure.Code.Generic(), - message = "Invalid configuration: 'invoiceId' and 'gatewayConfigurationId' is required." - ) - ) - return - } - } viewModel.start() } 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 c3652e935..af9b5087e 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 @@ -7,7 +7,6 @@ import androidx.annotation.IntRange import com.processout.sdk.ui.core.shared.image.PODrawableImage import com.processout.sdk.ui.core.style.* import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.* -import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow.Authorization import com.processout.sdk.ui.shared.configuration.POActionConfirmationConfiguration import com.processout.sdk.ui.shared.configuration.POBarcodeConfiguration import com.processout.sdk.ui.shared.configuration.POCancellationConfiguration @@ -16,8 +15,7 @@ import kotlinx.parcelize.Parcelize /** * Defines native alternative payment configuration. * - * @param[invoiceId] Invoice ID. - * @param[gatewayConfigurationId] Gateway configuration ID. + * @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. @@ -33,8 +31,7 @@ import kotlinx.parcelize.Parcelize */ @Parcelize data class PONativeAlternativePaymentConfiguration( - val invoiceId: String, - val gatewayConfigurationId: String, + val flow: Flow, val title: String? = null, val submitButton: Button = Button(), val cancelButton: CancelButton? = null, @@ -47,26 +44,41 @@ data class PONativeAlternativePaymentConfiguration( val style: Style? = null ) : Parcelable { - // TODO(v2): move to constructor - internal val flow: Flow - get() = Authorization( - invoiceId = invoiceId, - gatewayConfigurationId = gatewayConfigurationId - ) + // TODO(v2): remove + internal val invoiceId: String + get() = when (flow) { + is Flow.Authorization -> flow.invoiceId + is Flow.Tokenization -> String() + } + + // TODO(v2): remove + internal val gatewayConfigurationId: String + get() = when (flow) { + is Flow.Authorization -> flow.gatewayConfigurationId + is Flow.Tokenization -> String() + } - // TODO(v2): make public - internal sealed class Flow : Parcelable { + /** + * Native alternative payment flow configuration. + */ + sealed class Flow : Parcelable { /** + * Configuration for native alternative payment authorization. + * * @param[invoiceId] Invoice identifier. * @param[gatewayConfigurationId] Gateway configuration identifier. + * @param[customerTokenId] Optional customer token identifier that will be used for authorization. */ @Parcelize data class Authorization( val invoiceId: String, - val gatewayConfigurationId: String + val gatewayConfigurationId: String, + val customerTokenId: String? = null ) : Flow() /** + * Configuration for native alternative payment tokenization. + * * @param[customerId] Customer identifier. * @param[customerTokenId] Customer token identifier. * @param[gatewayConfigurationId] Gateway configuration identifier. @@ -79,6 +91,55 @@ data class PONativeAlternativePaymentConfiguration( ) : Flow() } + /** + * Defines native alternative payment configuration. + * + * @param[invoiceId] Invoice ID. + * @param[gatewayConfigurationId] Gateway configuration ID. + * @param[title] Custom title. + * @param[submitButton] Submit button configuration. + * @param[cancelButton] Cancel button configuration. Use _null_ to hide, this is a default behaviour. + * @param[cancellation] Specifies cancellation behaviour. + * @param[paymentConfirmation] Specifies payment confirmation configuration. + * @param[barcode] Specifies barcode configuration. + * @param[inlineSingleSelectValuesLimit] Defines maximum number of options that will be + * displayed inline for parameters where user should select single option (e.g. radio buttons). + * Default value is _5_. + * @param[skipSuccessScreen] Only applies when [PaymentConfirmationConfiguration.waitsConfirmation] is _true_. + * @param[successMessage] Custom success message when payment is completed. + * @param[style] Allows to customize the look and feel. + */ + @Deprecated(message = "Use alternative constructor.") + constructor( + invoiceId: String, + gatewayConfigurationId: String, + title: String? = null, + submitButton: Button = Button(), + cancelButton: CancelButton? = null, + cancellation: POCancellationConfiguration = POCancellationConfiguration(), + paymentConfirmation: PaymentConfirmationConfiguration = PaymentConfirmationConfiguration(confirmButton = null), + barcode: POBarcodeConfiguration = POBarcodeConfiguration(saveButton = POBarcodeConfiguration.Button()), + inlineSingleSelectValuesLimit: Int = 5, + skipSuccessScreen: Boolean = false, + successMessage: String? = null, + style: Style? = null + ) : this( + flow = Flow.Authorization( + invoiceId = invoiceId, + gatewayConfigurationId = gatewayConfigurationId + ), + title = title, + submitButton = submitButton, + cancelButton = cancelButton, + cancellation = cancellation, + paymentConfirmation = paymentConfirmation, + barcode = barcode, + inlineSingleSelectValuesLimit = inlineSingleSelectValuesLimit, + skipSuccessScreen = skipSuccessScreen, + successMessage = successMessage, + style = style + ) + /** * Defines native alternative payment configuration. * diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentInteractor.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentInteractor.kt index 47ea17c58..c4e1a3e50 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentInteractor.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/v2/NativeAlternativePaymentInteractor.kt @@ -388,7 +388,10 @@ internal class NativeAlternativePaymentInteractor( private fun requestDefaultValues(parameters: List) { interactorScope.launch { val request = NativeAlternativePaymentDefaultValuesRequest( - gatewayConfigurationId = configuration.gatewayConfigurationId, + gatewayConfigurationId = when (val flow = configuration.flow) { + is Authorization -> flow.gatewayConfigurationId + is Tokenization -> flow.gatewayConfigurationId + }, parameters = parameters ) latestDefaultValuesRequest = request From bcccfaf9dfd180e6bb7bb76560acf92ea3dcb48d Mon Sep 17 00:00:00 2001 From: Vitalii Vanziak Date: Wed, 9 Jul 2025 15:20:42 +0300 Subject: [PATCH 2/4] Update example app --- .../example/ui/screen/nativeapm/NativeApmFragment.kt | 8 ++++---- example/src/main/res/layout/fragment_native_apm.xml | 12 ++++++------ example/src/main/res/values/strings.xml | 2 -- 3 files changed, 10 insertions(+), 12 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 2d62e6bcc..0650e23c0 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 @@ -74,10 +74,10 @@ class NativeApmFragment : BaseFragment( } private fun setOnClickListeners() { - binding.launchNativeApmButton.setOnClickListener { + binding.buttonAuthorizeLegacy.setOnClickListener { onSubmitClick(launchCompose = false) } - binding.launchNativeApmComposeButton.setOnClickListener { + binding.buttonAuthorizeCompose.setOnClickListener { onSubmitClick(launchCompose = true) } } @@ -131,8 +131,8 @@ class NativeApmFragment : BaseFragment( with(binding) { amountInput.isEnabled = isEnabled currencyInput.isEnabled = isEnabled - launchNativeApmButton.isClickable = isEnabled - launchNativeApmComposeButton.isClickable = isEnabled + buttonAuthorizeLegacy.isClickable = isEnabled + buttonAuthorizeCompose.isClickable = isEnabled amountInput.clearFocus() currencyInput.clearFocus() } diff --git a/example/src/main/res/layout/fragment_native_apm.xml b/example/src/main/res/layout/fragment_native_apm.xml index 4c6caa92c..3130a95c1 100644 --- a/example/src/main/res/layout/fragment_native_apm.xml +++ b/example/src/main/res/layout/fragment_native_apm.xml @@ -82,30 +82,30 @@ android:layout_height="@dimen/po_borderWidth" android:layout_marginBottom="@dimen/button_marginVertical" android:background="@color/po_border_subtle" - app:layout_constraintBottom_toTopOf="@id/launch_native_apm_button" + app:layout_constraintBottom_toTopOf="@id/button_authorize_legacy" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" />