From ac56735276c59d899d23ee46cf4da5029dd6d341 Mon Sep 17 00:00:00 2001 From: toluo-stripe Date: Wed, 15 Jan 2025 18:13:40 -0500 Subject: [PATCH] Payment Confirmation on Card creation screen (#9918) --- .../ui/paymentmenthod/PaymentMethodState.kt | 6 +- .../paymentmenthod/PaymentMethodViewModel.kt | 109 +++++++++-- .../android/paymentsheet/DefaultFormHelper.kt | 17 +- .../stripe/android/paymentsheet/FormHelper.kt | 6 + .../PaymentMethodViewModelTest.kt | 176 ++++++++++++++++-- .../android/paymentsheet/FormHelperTest.kt | 20 ++ 6 files changed, 288 insertions(+), 46 deletions(-) diff --git a/paymentsheet/src/main/java/com/stripe/android/link/ui/paymentmenthod/PaymentMethodState.kt b/paymentsheet/src/main/java/com/stripe/android/link/ui/paymentmenthod/PaymentMethodState.kt index f0b9c9fd399..f5107aae722 100644 --- a/paymentsheet/src/main/java/com/stripe/android/link/ui/paymentmenthod/PaymentMethodState.kt +++ b/paymentsheet/src/main/java/com/stripe/android/link/ui/paymentmenthod/PaymentMethodState.kt @@ -2,15 +2,15 @@ package com.stripe.android.link.ui.paymentmenthod import com.stripe.android.core.strings.ResolvableString import com.stripe.android.link.ui.PrimaryButtonState -import com.stripe.android.paymentsheet.model.PaymentSelection +import com.stripe.android.model.PaymentMethodCreateParams import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments import com.stripe.android.uicore.elements.FormElement internal data class PaymentMethodState( - val isProcessing: Boolean, val formArguments: FormArguments, val formElements: List, val primaryButtonState: PrimaryButtonState, val primaryButtonLabel: ResolvableString, - val paymentSelection: PaymentSelection? = null + val paymentMethodCreateParams: PaymentMethodCreateParams? = null, + val errorMessage: ResolvableString? = null ) diff --git a/paymentsheet/src/main/java/com/stripe/android/link/ui/paymentmenthod/PaymentMethodViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/link/ui/paymentmenthod/PaymentMethodViewModel.kt index 118b3f5dfe0..ac3b0779811 100644 --- a/paymentsheet/src/main/java/com/stripe/android/link/ui/paymentmenthod/PaymentMethodViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/link/ui/paymentmenthod/PaymentMethodViewModel.kt @@ -2,31 +2,43 @@ package com.stripe.android.link.ui.paymentmenthod import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory +import com.stripe.android.common.exception.stripeErrorMessage +import com.stripe.android.core.Logger +import com.stripe.android.link.LinkActivityResult import com.stripe.android.link.LinkConfiguration +import com.stripe.android.link.account.LinkAccountManager +import com.stripe.android.link.confirmation.LinkConfirmationHandler +import com.stripe.android.link.confirmation.Result import com.stripe.android.link.injection.NativeLinkComponent +import com.stripe.android.link.model.LinkAccount import com.stripe.android.link.ui.PrimaryButtonState import com.stripe.android.link.ui.completePaymentButtonLabel import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata +import com.stripe.android.model.ConsumerPaymentDetails import com.stripe.android.model.PaymentMethod import com.stripe.android.paymentsheet.DefaultFormHelper import com.stripe.android.paymentsheet.FormHelper import com.stripe.android.paymentsheet.forms.FormFieldValues -import com.stripe.android.paymentsheet.model.PaymentSelection import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject internal class PaymentMethodViewModel @Inject constructor( private val configuration: LinkConfiguration, - private val formHelperFactory: (UpdateSelection) -> FormHelper + private val linkAccount: LinkAccount, + private val linkAccountManager: LinkAccountManager, + private val linkConfirmationHandler: LinkConfirmationHandler, + private val logger: Logger, + private val formHelper: FormHelper, + private val dismissWithResult: (LinkActivityResult) -> Unit ) : ViewModel() { - private val formHelper = formHelperFactory(::updateSelection) private val _state = MutableStateFlow( PaymentMethodState( - isProcessing = false, formElements = formHelper.formElementsForCode(PaymentMethod.Type.Card.code), formArguments = formHelper.createFormArguments(PaymentMethod.Type.Card.code), primaryButtonState = PrimaryButtonState.Disabled, @@ -37,17 +49,14 @@ internal class PaymentMethodViewModel @Inject constructor( val state: StateFlow = _state fun formValuesChanged(formValues: FormFieldValues?) { - formHelper.onFormFieldValuesChanged( + val params = formHelper.getPaymentMethodParams( formValues = formValues, selectedPaymentMethodCode = PaymentMethod.Type.Card.code ) - } - - private fun updateSelection(selection: PaymentSelection?) { _state.update { it.copy( - paymentSelection = selection, - primaryButtonState = if (selection != null) { + paymentMethodCreateParams = params, + primaryButtonState = if (params != null) { PrimaryButtonState.Enabled } else { PrimaryButtonState.Disabled @@ -56,23 +65,83 @@ internal class PaymentMethodViewModel @Inject constructor( } } + fun onPayClicked() { + val paymentMethodCreateParams = _state.value.paymentMethodCreateParams + if (paymentMethodCreateParams == null) { + logger.error("PaymentMethodViewModel: onPayClicked without paymentMethodCreateParams") + return + } + viewModelScope.launch { + updateButtonState(PrimaryButtonState.Processing) + linkAccountManager.createCardPaymentDetails(paymentMethodCreateParams) + .fold( + onSuccess = { linkPaymentDetails -> + performConfirmation(linkPaymentDetails.paymentDetails) + }, + onFailure = { error -> + _state.update { + it.copy( + errorMessage = error.stripeErrorMessage() + ) + } + logger.error( + msg = "PaymentMethodViewModel: Failed to create card payment details", + t = error + ) + } + ) + } + updateButtonState(PrimaryButtonState.Enabled) + } + + private suspend fun performConfirmation(paymentDetails: ConsumerPaymentDetails.PaymentDetails) { + val result = linkConfirmationHandler.confirm( + paymentDetails = paymentDetails, + linkAccount = linkAccount, + cvc = null + ) + when (result) { + Result.Canceled -> Unit + is Result.Failed -> { + _state.update { it.copy(errorMessage = result.message) } + } + Result.Succeeded -> { + dismissWithResult(LinkActivityResult.Completed) + } + } + } + + private fun updateButtonState(state: PrimaryButtonState) { + _state.update { + it.copy( + primaryButtonState = state + ) + } + } + companion object { fun factory( - parentComponent: NativeLinkComponent + parentComponent: NativeLinkComponent, + linkAccount: LinkAccount, + dismissWithResult: (LinkActivityResult) -> Unit ): ViewModelProvider.Factory { return viewModelFactory { initializer { PaymentMethodViewModel( configuration = parentComponent.configuration, - formHelperFactory = { selectionUpdater -> - DefaultFormHelper.create( - cardAccountRangeRepositoryFactory = parentComponent.cardAccountRangeRepositoryFactory, - paymentMethodMetadata = PaymentMethodMetadata.create( - configuration = parentComponent.configuration, - ), - selectionUpdater = selectionUpdater - ) - } + linkAccount = linkAccount, + linkAccountManager = parentComponent.linkAccountManager, + linkConfirmationHandler = parentComponent.linkConfirmationHandlerFactory.create( + confirmationHandler = parentComponent.viewModel.confirmationHandler + ), + formHelper = DefaultFormHelper.create( + cardAccountRangeRepositoryFactory = parentComponent.cardAccountRangeRepositoryFactory, + paymentMethodMetadata = PaymentMethodMetadata.create( + configuration = parentComponent.configuration, + ), + ), + logger = parentComponent.logger, + dismissWithResult = dismissWithResult ) } } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/DefaultFormHelper.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/DefaultFormHelper.kt index ae6049692f9..b5f83589d91 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/DefaultFormHelper.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/DefaultFormHelper.kt @@ -8,10 +8,12 @@ import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory import com.stripe.android.model.PaymentMethod import com.stripe.android.model.PaymentMethodCode +import com.stripe.android.model.PaymentMethodCreateParams import com.stripe.android.paymentsheet.forms.FormArgumentsFactory import com.stripe.android.paymentsheet.forms.FormFieldValues import com.stripe.android.paymentsheet.model.PaymentSelection import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments +import com.stripe.android.paymentsheet.ui.transformToPaymentMethodCreateParams import com.stripe.android.paymentsheet.ui.transformToPaymentSelection import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel import com.stripe.android.uicore.elements.FormElement @@ -40,14 +42,13 @@ internal class DefaultFormHelper( onLinkInlineSignupStateChanged = linkInlineHandler::onStateUpdated, selectionUpdater = { viewModel.updateSelection(it) - } + }, ) } fun create( cardAccountRangeRepositoryFactory: CardAccountRangeRepository.Factory, paymentMethodMetadata: PaymentMethodMetadata, - selectionUpdater: (PaymentSelection?) -> Unit, ): FormHelper { return DefaultFormHelper( cardAccountRangeRepositoryFactory = cardAccountRangeRepositoryFactory, @@ -55,7 +56,7 @@ internal class DefaultFormHelper( newPaymentSelectionProvider = { null }, linkConfigurationCoordinator = null, onLinkInlineSignupStateChanged = {}, - selectionUpdater = selectionUpdater + selectionUpdater = {}, ) } } @@ -92,6 +93,16 @@ internal class DefaultFormHelper( selectionUpdater(newSelection) } + override fun getPaymentMethodParams( + formValues: FormFieldValues?, + selectedPaymentMethodCode: String + ): PaymentMethodCreateParams? { + return formValues?.transformToPaymentMethodCreateParams( + paymentMethodCode = selectedPaymentMethodCode, + paymentMethodMetadata = paymentMethodMetadata + ) + } + override fun requiresFormScreen(selectedPaymentMethodCode: String): Boolean { val userInteractionAllowed = formElementsForCode(selectedPaymentMethodCode).any { it.allowsUserInteraction } return userInteractionAllowed || diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/FormHelper.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/FormHelper.kt index 2e6a983abae..a628092c3bc 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/FormHelper.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/FormHelper.kt @@ -1,6 +1,7 @@ package com.stripe.android.paymentsheet import com.stripe.android.model.PaymentMethodCode +import com.stripe.android.model.PaymentMethodCreateParams import com.stripe.android.paymentsheet.forms.FormFieldValues import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments import com.stripe.android.uicore.elements.FormElement @@ -15,5 +16,10 @@ internal interface FormHelper { fun onFormFieldValuesChanged(formValues: FormFieldValues?, selectedPaymentMethodCode: String) + fun getPaymentMethodParams( + formValues: FormFieldValues?, + selectedPaymentMethodCode: String + ): PaymentMethodCreateParams? + fun requiresFormScreen(selectedPaymentMethodCode: String): Boolean } diff --git a/paymentsheet/src/test/java/com/stripe/android/link/ui/paymentmethod/PaymentMethodViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/link/ui/paymentmethod/PaymentMethodViewModelTest.kt index f253806e6ab..34fd7ca423d 100644 --- a/paymentsheet/src/test/java/com/stripe/android/link/ui/paymentmethod/PaymentMethodViewModelTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/link/ui/paymentmethod/PaymentMethodViewModelTest.kt @@ -1,19 +1,28 @@ package com.stripe.android.link.ui.paymentmethod import com.google.common.truth.Truth.assertThat +import com.stripe.android.core.Logger +import com.stripe.android.core.strings.resolvableString +import com.stripe.android.link.LinkActivityResult import com.stripe.android.link.TestFactory +import com.stripe.android.link.account.FakeLinkAccountManager +import com.stripe.android.link.account.LinkAccountManager +import com.stripe.android.link.confirmation.FakeLinkConfirmationHandler +import com.stripe.android.link.confirmation.LinkConfirmationHandler import com.stripe.android.link.ui.PrimaryButtonState import com.stripe.android.link.ui.completePaymentButtonLabel import com.stripe.android.link.ui.paymentmenthod.PaymentMethodState import com.stripe.android.link.ui.paymentmenthod.PaymentMethodViewModel -import com.stripe.android.link.ui.paymentmenthod.UpdateSelection import com.stripe.android.model.PaymentMethod import com.stripe.android.model.PaymentMethodCode -import com.stripe.android.model.PaymentMethodFixtures +import com.stripe.android.model.PaymentMethodCreateParams +import com.stripe.android.model.PaymentMethodCreateParamsFixtures import com.stripe.android.paymentsheet.FormHelper +import com.stripe.android.paymentsheet.R import com.stripe.android.paymentsheet.forms.FormFieldValues import com.stripe.android.paymentsheet.model.PaymentSelection import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments +import com.stripe.android.testing.FakeLogger import com.stripe.android.uicore.elements.FormElement import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -25,6 +34,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner +import com.stripe.android.link.confirmation.Result as LinkConfirmationResult @RunWith(RobolectricTestRunner::class) class PaymentMethodViewModelTest { @@ -47,7 +57,6 @@ class PaymentMethodViewModelTest { assertThat(vm.state.value).isEqualTo( PaymentMethodState( - isProcessing = false, formArguments = TestFactory.CARD_FORM_ARGS, formElements = TestFactory.CARD_FORM_ELEMENTS, primaryButtonState = PrimaryButtonState.Disabled, @@ -69,49 +78,176 @@ class PaymentMethodViewModelTest { vm.formValuesChanged(formValues) - assertThat(vm.state.value.paymentSelection).isEqualTo(PaymentMethodFixtures.CARD_PAYMENT_SELECTION) + assertThat(vm.state.value.paymentMethodCreateParams).isEqualTo(PaymentMethodCreateParamsFixtures.DEFAULT_CARD) assertThat(vm.state.value.primaryButtonState).isEqualTo(PrimaryButtonState.Enabled) - assertThat(formHelper.onFormFieldValuesChangedCalls).containsExactly( - PaymentMethodFormHelper.OnFormFieldValuesChangedCall( + assertThat(formHelper.getPaymentMethodParamsCalls).containsExactly( + PaymentMethodFormHelper.GetPaymentMethodParamsCall( formValues = formValues, selectedPaymentMethodCode = PaymentMethod.Type.Card.code ) ) - formHelper.paymentSelection = null + formHelper.paymentMethodCreateParams = null vm.formValuesChanged(null) - assertThat(vm.state.value.paymentSelection).isNull() + assertThat(vm.state.value.paymentMethodCreateParams).isNull() assertThat(vm.state.value.primaryButtonState).isEqualTo(PrimaryButtonState.Disabled) - assertThat(formHelper.onFormFieldValuesChangedCalls.last().formValues).isNull() + assertThat(formHelper.getPaymentMethodParamsCalls.last().formValues).isNull() + } + + @Test + fun `onPayClicked confirms payment successfully`() = runTest { + val linkConfirmationHandler = FakeLinkConfirmationHandler() + val linkAccountManager = FakeLinkAccountManager() + var result: LinkActivityResult? = null + val viewModel = createViewModel( + linkConfirmationHandler = linkConfirmationHandler, + linkAccountManager = linkAccountManager, + dismissWithResult = { result = it } + ) + + viewModel.formValuesChanged( + formValues = FormFieldValues(userRequestedReuse = PaymentSelection.CustomerRequestedSave.NoRequest) + ) + + viewModel.onPayClicked() + + assertThat(linkConfirmationHandler.calls.first().paymentDetails) + .isEqualTo(TestFactory.LINK_NEW_PAYMENT_DETAILS.paymentDetails) + assertThat(linkConfirmationHandler.calls.first().cvc).isNull() + assertThat(result).isEqualTo(LinkActivityResult.Completed) + assertThat(viewModel.state.value.primaryButtonState).isEqualTo(PrimaryButtonState.Enabled) + } + + @Test + fun `onPayClicked handles payment method creation failure correctly`() = runTest { + val error = Throwable("oops") + val linkConfirmationHandler = FakeLinkConfirmationHandler() + val linkAccountManager = FakeLinkAccountManager() + val logger = FakeLogger() + var result: LinkActivityResult? = null + + linkAccountManager.createCardPaymentDetailsResult = Result.failure(error) + + val viewModel = createViewModel( + linkConfirmationHandler = linkConfirmationHandler, + linkAccountManager = linkAccountManager, + logger = logger, + dismissWithResult = { result = it } + ) + + viewModel.formValuesChanged( + formValues = FormFieldValues(userRequestedReuse = PaymentSelection.CustomerRequestedSave.NoRequest) + ) + + viewModel.onPayClicked() + + assertThat(linkConfirmationHandler.calls).isEmpty() + + assertThat(result).isEqualTo(null) + assertThat(viewModel.state.value.primaryButtonState).isEqualTo(PrimaryButtonState.Enabled) + assertThat(viewModel.state.value.errorMessage).isEqualTo(R.string.stripe_something_went_wrong.resolvableString) + assertThat(logger.errorLogs).containsExactly( + "PaymentMethodViewModel: Failed to create card payment details" to error + ) + } + + @Test + fun `onPayClicked handles confirmation failure`() = runTest { + val linkConfirmationHandler = FakeLinkConfirmationHandler() + + linkConfirmationHandler.confirmResult = LinkConfirmationResult.Failed("Payment failed".resolvableString) + + val viewModel = createViewModel(linkConfirmationHandler = linkConfirmationHandler) + + viewModel.formValuesChanged( + formValues = FormFieldValues(userRequestedReuse = PaymentSelection.CustomerRequestedSave.NoRequest) + ) + + viewModel.onPayClicked() + + assertThat(linkConfirmationHandler.calls).hasSize(1) + assertThat(viewModel.state.value.primaryButtonState).isEqualTo(PrimaryButtonState.Enabled) + assertThat(viewModel.state.value.errorMessage).isEqualTo("Payment failed".resolvableString) + } + + @Test + fun `onPayClicked handles cancellation`() = runTest { + val linkConfirmationHandler = FakeLinkConfirmationHandler() + linkConfirmationHandler.confirmResult = LinkConfirmationResult.Canceled + + val viewModel = createViewModel(linkConfirmationHandler = linkConfirmationHandler) + + viewModel.formValuesChanged( + formValues = FormFieldValues(userRequestedReuse = PaymentSelection.CustomerRequestedSave.NoRequest) + ) + + viewModel.onPayClicked() + + assertThat(linkConfirmationHandler.calls).hasSize(1) + assertThat(viewModel.state.value.primaryButtonState).isEqualTo(PrimaryButtonState.Enabled) + assertThat(viewModel.state.value.errorMessage).isNull() + } + + @Test + fun `onPayClicked logs when payment selection is null`() = runTest { + val linkConfirmationHandler = FakeLinkConfirmationHandler() + val logger = FakeLogger() + + val viewModel = createViewModel( + linkConfirmationHandler = linkConfirmationHandler, + logger = logger + ) + + viewModel.onPayClicked() + + assertThat(linkConfirmationHandler.calls).isEmpty() + assertThat(viewModel.state.value.primaryButtonState).isEqualTo(PrimaryButtonState.Disabled) + assertThat(logger.errorLogs) + .containsExactly("PaymentMethodViewModel: onPayClicked without paymentMethodCreateParams" to null) } private fun createViewModel( formHelper: PaymentMethodFormHelper = PaymentMethodFormHelper(), + linkConfirmationHandler: LinkConfirmationHandler = FakeLinkConfirmationHandler(), + linkAccountManager: LinkAccountManager = FakeLinkAccountManager(), + logger: Logger = FakeLogger(), + dismissWithResult: (LinkActivityResult) -> Unit = {} ): PaymentMethodViewModel { return PaymentMethodViewModel( configuration = TestFactory.LINK_CONFIGURATION, - formHelperFactory = { updateSelection -> - formHelper.updateSelection = updateSelection - formHelper - } + linkAccount = TestFactory.LINK_ACCOUNT, + linkConfirmationHandler = linkConfirmationHandler, + dismissWithResult = dismissWithResult, + formHelper = formHelper, + logger = logger, + linkAccountManager = linkAccountManager ) } } private class PaymentMethodFormHelper : FormHelper { - var updateSelection: UpdateSelection? = null - var paymentSelection: PaymentSelection? = PaymentMethodFixtures.CARD_PAYMENT_SELECTION - val onFormFieldValuesChangedCalls = arrayListOf() + var paymentMethodCreateParams: PaymentMethodCreateParams? = PaymentMethodCreateParamsFixtures.DEFAULT_CARD + val getPaymentMethodParamsCalls = arrayListOf() override fun onFormFieldValuesChanged(formValues: FormFieldValues?, selectedPaymentMethodCode: String) { - onFormFieldValuesChangedCalls.add( - OnFormFieldValuesChangedCall( + TODO("Not yet implemented") + } + + override fun getPaymentMethodParams( + formValues: FormFieldValues?, + selectedPaymentMethodCode: String + ): PaymentMethodCreateParams? { + require(selectedPaymentMethodCode == PaymentMethod.Type.Card.code) { + "$selectedPaymentMethodCode payment not supported" + } + getPaymentMethodParamsCalls.add( + GetPaymentMethodParamsCall( formValues = formValues, selectedPaymentMethodCode = selectedPaymentMethodCode ) ) - updateSelection?.invoke(paymentSelection) + return paymentMethodCreateParams } override fun requiresFormScreen(selectedPaymentMethodCode: String): Boolean { @@ -132,7 +268,7 @@ private class PaymentMethodFormHelper : FormHelper { return TestFactory.CARD_FORM_ARGS } - data class OnFormFieldValuesChangedCall( + data class GetPaymentMethodParamsCall( val formValues: FormFieldValues?, val selectedPaymentMethodCode: String ) diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/FormHelperTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/FormHelperTest.kt index 008afbf8ebe..481e2540073 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/FormHelperTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/FormHelperTest.kt @@ -159,6 +159,26 @@ internal class FormHelperTest { assertThat(hasCalledSelectionUpdater).isTrue() } + @Test + fun `getPaymentMethodParams returns correct payment method params`() { + val cardBrand = "visa" + val name = "Joe" + val customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestNoReuse + val formFieldValues = FormFieldValues( + fieldValuePairs = mapOf( + IdentifierSpec.CardBrand to FormFieldEntry(cardBrand, true), + IdentifierSpec.Name to FormFieldEntry(name, true), + ), + userRequestedReuse = customerRequestedSave, + ) + + val formHelper = createFormHelper { } + val params = formHelper.getPaymentMethodParams(formFieldValues, "card") + + assertThat(params?.let { getNameFromParams(it) }).isEqualTo(name) + assertThat(params?.typeCode).isEqualTo("card") + } + @Test fun `requiresFormScreen returns false for an LPM with no fields`() { val formHelper = createFormHelper(