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 9795201e..ebbfa878 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 @@ -248,46 +248,6 @@ internal class NativeAlternativePaymentInteractor( } } - //region Next Step - - private fun handleNextStep( - stateValue: NextStepStateValue, - elements: List, - redirect: PONativeAlternativePaymentRedirect? - ) { - val parameters = elements.flatMap { - if (it is Element.Form) it.form.parameterDefinitions else emptyList() - } - if (parameters.isEmpty()) { - POLogger.warn( - message = "Parameters is empty in response.", - attributes = configuration.logAttributes - ) - } - if (failWithUnknownParameter(parameters)) { - return - } - if (failWithUnknownRedirect(redirect)) { - return - } - val fields = parameters.toFields() - val updatedStateValue = stateValue.copy( - uuid = UUID.randomUUID().toString(), - redirect = redirect, - elements = elements, - fields = fields, - focusedFieldId = fields.firstFocusableFieldId() - ) - _state.update { - if (_state.value is Loading) { - Loaded(updatedStateValue) - } else { - Submitted(updatedStateValue) - } - } - requestDefaultValues(parameters) - } - private suspend fun List.map(): List = mapNotNull { element -> when (element) { @@ -345,16 +305,53 @@ internal class NativeAlternativePaymentInteractor( } } - private fun failWithUnknownParameter( - parameters: List + //region Next Step + + private fun handleNextStep( + stateValue: NextStepStateValue, + elements: List, + redirect: PONativeAlternativePaymentRedirect? + ) { + if (failWithUnsupportedHeadlessMode(redirect)) { + return + } + if (failWithUnknownRedirect(redirect)) { + return + } + val parameters = elements.flatMap { + if (it is Element.Form) it.form.parameterDefinitions else emptyList() + } + if (failWithUnknownParameter(parameters)) { + return + } + val fields = parameters.toFields() + val updatedStateValue = stateValue.copy( + uuid = UUID.randomUUID().toString(), + redirect = redirect, + elements = elements, + fields = fields, + focusedFieldId = fields.firstFocusableFieldId() + ) + _state.update { + if (_state.value is Loading) { + Loaded(updatedStateValue) + } else { + Submitted(updatedStateValue) + } + } + requestDefaultValues(parameters) + } + + private fun failWithUnsupportedHeadlessMode( + redirect: PONativeAlternativePaymentRedirect? ): Boolean { - parameters.find { it == Parameter.Unknown }?.let { + if (configuration.redirect?.enableHeadlessMode == true && redirect == null) { val failure = ProcessOutResult.Failure( - code = Internal(), - message = "Unknown parameter type." + code = Generic(genericCode = mobileOperationNotSupported), + message = "Headless mode is not supported: redirect parameters are missing in the response." ) POLogger.error( - message = "Unexpected response: %s", failure, + message = "Unsupported operation: %s", failure, attributes = configuration.logAttributes ) _completion.update { Failure(failure) } @@ -381,6 +378,24 @@ internal class NativeAlternativePaymentInteractor( return false } + private fun failWithUnknownParameter( + parameters: List + ): Boolean { + parameters.find { it == Parameter.Unknown }?.let { + val failure = ProcessOutResult.Failure( + code = Internal(), + message = "Unknown parameter type." + ) + POLogger.error( + message = "Unexpected response: %s", failure, + attributes = configuration.logAttributes + ) + _completion.update { Failure(failure) } + return true + } + return false + } + private fun List.toFields() = map { parameter -> val defaultValue = when (parameter) { @@ -414,7 +429,7 @@ internal class NativeAlternativePaymentInteractor( enableNextStepSecondaryAction() POLogger.info("Started: waiting for payment parameters.") dispatch(DidStart) - handleHeadlessRedirect() + handleAutoRedirect() } private fun continueNextStep(stateValue: NextStepStateValue) { @@ -433,33 +448,24 @@ internal class NativeAlternativePaymentInteractor( additionalParametersExpected = true ) ) - handleHeadlessRedirect() + handleAutoRedirect() } - private fun handleHeadlessRedirect() { - if (configuration.redirect?.enableHeadlessMode != true) { - return - } + private fun handleAutoRedirect() { _state.whenNextStep { stateValue -> - if (stateValue.redirect == null) { - val failure = ProcessOutResult.Failure( - code = Generic(genericCode = mobileOperationNotSupported), - message = "Headless mode is not supported: redirect parameters are missing in the response." - ) - POLogger.error( - message = "Unsupported operation: %s", failure, - attributes = configuration.logAttributes + if (stateValue.redirect != null && shouldAutoRedirect()) { + redirect( + stateValue = stateValue, + redirect = stateValue.redirect ) - _completion.update { Failure(failure) } - return@whenNextStep } - redirect( - stateValue = stateValue, - redirect = stateValue.redirect - ) } } + private fun shouldAutoRedirect(): Boolean = + configuration.redirect?.enableHeadlessMode == true || + configuration.redirect?.redirectButton == null + //endregion //region Default Values @@ -1314,7 +1320,7 @@ internal class NativeAlternativePaymentInteractor( private fun saveBarcode() { _state.whenNextStep { stateValue -> val instructions = stateValue.elements.mapNotNull { - if (it is Element.Instruction) it else null + it as? Element.Instruction } instructions.forEach { if (it.instruction is Instruction.Barcode) { @@ -1325,7 +1331,7 @@ internal class NativeAlternativePaymentInteractor( } _state.whenPending { stateValue -> val instructions = stateValue.elements?.mapNotNull { - if (it is Element.Instruction) it else null + it as? Element.Instruction } instructions?.forEach { if (it.instruction is Instruction.Barcode) { @@ -1357,7 +1363,7 @@ internal class NativeAlternativePaymentInteractor( if (result.isGranted) { _state.whenNextStep { stateValue -> val instructions = stateValue.elements.mapNotNull { - if (it is Element.Instruction) it else null + it as? Element.Instruction } instructions.forEach { if (it.instruction is Instruction.Barcode) { @@ -1372,7 +1378,7 @@ internal class NativeAlternativePaymentInteractor( } _state.whenPending { stateValue -> val instructions = stateValue.elements?.mapNotNull { - if (it is Element.Instruction) it else null + it as? Element.Instruction } instructions?.forEach { if (it.instruction is Instruction.Barcode) { diff --git a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt index 176c1034..7526b6e5 100644 --- a/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt +++ b/ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentViewModel.kt @@ -133,16 +133,7 @@ internal class NativeAlternativePaymentViewModel private constructor( ), elements = elements.map(fields) ), - primaryAction = POActionState( - id = primaryActionId, - text = redirect?.hint - ?: configuration.submitButton.text - ?: app.getString(R.string.po_native_apm_continue_button_text), - primary = true, - enabled = submitAllowed, - loading = submitting, - icon = configuration.submitButton.icon - ), + primaryAction = toSubmitAction(), secondaryAction = configuration.cancelButton?.toActionState( id = secondaryAction.id, enabled = secondaryAction.enabled && !submitting @@ -567,6 +558,28 @@ internal class NativeAlternativePaymentViewModel private constructor( private fun Invoice.priceSuccessMessage(): String? = price()?.let { app.getString(R.string.po_native_apm_success_message_format, it) } + private fun NextStepStateValue.toSubmitAction(): POActionState? { + val submitAction = POActionState( + id = primaryActionId, + text = configuration.submitButton.text + ?: app.getString(R.string.po_native_apm_continue_button_text), + primary = true, + enabled = submitAllowed, + loading = submitting, + icon = configuration.submitButton.icon + ) + return if (redirect != null) { + configuration.redirect?.redirectButton?.let { + submitAction.copy( + text = it.text ?: redirect.hint, + icon = it.icon + ) + } + } else { + submitAction + } + } + private fun CancelButton.toActionState( id: String, enabled: Boolean 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 91931d1c..4a0cf086 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 @@ -63,6 +63,7 @@ data class PONativeAlternativePaymentConfiguration( * @param[invoiceId] Invoice identifier. * @param[gatewayConfigurationId] Gateway configuration identifier. * @param[customerTokenId] Optional customer token identifier that will be used for authorization. + * @param[configuration] Authorization configuration. */ @Parcelize data class Authorization( @@ -78,6 +79,7 @@ data class PONativeAlternativePaymentConfiguration( * @param[customerId] Customer identifier. * @param[customerTokenId] Customer token identifier. * @param[gatewayConfigurationId] Gateway configuration identifier. + * @param[configuration] Tokenization configuration. */ @Parcelize data class Tokenization( @@ -394,11 +396,14 @@ data class PONativeAlternativePaymentConfiguration( * The redirect (web or deep link) will be handled directly when it's the first step in the flow, without starting the bottom sheet. * It will also capture the payment in the background when it's required by the flow. * __Note:__ use only with flows that do not require user input or instructions in the native UI. + * @param[redirectButton] Redirect button configuration. + * Pass _null_ to hide and redirect automatically, this is a default behaviour. */ @Parcelize data class RedirectConfiguration( val returnUrl: String, - val enableHeadlessMode: Boolean = false + val enableHeadlessMode: Boolean = false, + val redirectButton: Button? = null ) : Parcelable /**