diff --git a/paymentsheet/detekt-baseline.xml b/paymentsheet/detekt-baseline.xml
index e30e641f156..5a138f76faa 100644
--- a/paymentsheet/detekt-baseline.xml
+++ b/paymentsheet/detekt-baseline.xml
@@ -26,11 +26,10 @@
LargeClass:SavedPaymentMethodMutatorTest.kt$SavedPaymentMethodMutatorTest
LargeClass:USBankAccountFormViewModelTest.kt$USBankAccountFormViewModelTest
LongMethod:AutocompleteScreen.kt$@Composable internal fun AutocompleteScreenUI(viewModel: AutocompleteViewModel)
- LongMethod:ConfirmationOptionKtx.kt$internal fun PaymentSelection.toConfirmationOption( initializationMode: PaymentElementLoader.InitializationMode, configuration: CommonConfiguration, appearance: PaymentSheet.Appearance, linkConfiguration: LinkConfiguration?, ): ConfirmationHandler.Option?
LongMethod:CustomerSheetScreen.kt$@Composable internal fun SelectPaymentMethod( viewState: CustomerSheetViewState.SelectPaymentMethod, viewActionHandler: (CustomerSheetViewAction) -> Unit, paymentMethodNameProvider: (PaymentMethodCode?) -> ResolvableString, modifier: Modifier = Modifier, )
- LongMethod:DefaultConfirmationHandlerTest.kt$DefaultConfirmationHandlerTest$@Test fun `On lifecycle destroyed, should unregister all launchers`()
LongMethod:DefaultConfirmationHandlerTest.kt$DefaultConfirmationHandlerTest$private fun test( someDefinitionAction: ConfirmationDefinition.Action<SomeConfirmationDefinition.LauncherArgs> = ConfirmationDefinition.Action.Fail( cause = IllegalStateException("Failed!"), message = R.string.stripe_something_went_wrong.resolvableString, errorType = ConfirmationHandler.Result.Failed.ErrorType.Internal, ), someDefinitionResult: ConfirmationDefinition.Result = ConfirmationDefinition.Result.Failed( cause = IllegalStateException("Failed!"), message = R.string.stripe_something_went_wrong.resolvableString, type = ConfirmationHandler.Result.Failed.ErrorType.Internal, ), someOtherDefinitionAction: ConfirmationDefinition.Action<SomeOtherConfirmationDefinition.LauncherArgs> = ConfirmationDefinition.Action.Fail( cause = IllegalStateException("Failed!"), message = R.string.stripe_something_went_wrong.resolvableString, errorType = ConfirmationHandler.Result.Failed.ErrorType.Internal, ), someOtherDefinitionResult: ConfirmationDefinition.Result = ConfirmationDefinition.Result.Failed( cause = IllegalStateException("Failed!"), message = R.string.stripe_something_went_wrong.resolvableString, type = ConfirmationHandler.Result.Failed.ErrorType.Internal, ), shouldRegister: Boolean = true, savedStateHandle: SavedStateHandle = SavedStateHandle(), dispatcher: CoroutineDispatcher = UnconfinedTestDispatcher(), scenarioTest: suspend Scenario.() -> Unit )
LongMethod:EditPaymentMethod.kt$@Composable internal fun EditPaymentMethodUi( viewState: EditPaymentMethodViewState, viewActionHandler: (action: EditPaymentMethodViewAction) -> Unit, modifier: Modifier = Modifier )
+ LongMethod:EmbeddedContentHelper.kt$DefaultEmbeddedContentHelper$private fun createInteractor( coroutineScope: CoroutineScope, paymentMethodMetadata: PaymentMethodMetadata, ): PaymentMethodVerticalLayoutInteractor
LongMethod:FormViewModelTest.kt$FormViewModelTest$@Test fun `Verify params are set when element address fields are complete`()
LongMethod:FormViewModelTest.kt$FormViewModelTest$@Test fun `Verify params are set when required address fields are complete`()
LongMethod:PaymentMethodMetadataTest.kt$PaymentMethodMetadataTest$@OptIn(ExperimentalCardBrandFilteringApi::class) @Test fun `should create metadata properly with elements session response, payment sheet config, and data specs`()
@@ -49,7 +48,6 @@
MagicNumber:PrimaryButton.kt$PrimaryButton$0.5f
MagicNumber:USBankAccountForm.kt$0.5f
MaxLineLength:CardDefinition.kt$internal
- MaxLineLength:CommonConfiguration.kt$CommonConfiguration$"secret. See CustomerSession API: https://docs.stripe.com/api/customer_sessions/create"
MaxLineLength:CustomerRepositoryTest.kt$CustomerRepositoryTest$fun
MaxLineLength:CustomerSheetViewModelTest.kt$CustomerSheetViewModelTest$fun
MaxLineLength:CustomerSheetViewModelTest.kt$CustomerSheetViewModelTest$publishableKey = "pk_test_51HvTI7Lu5o3livep6t5AgBSkMvWoTtA0nyA7pVYDqpfLkRtWun7qZTYCOHCReprfLM464yaBeF72UFfB7cY9WG4a00ZnDtiC2C"
@@ -72,8 +70,6 @@
MaxLineLength:SupportedPaymentMethod.kt$SupportedPaymentMethod$/** This describes the image in the LPM selector. These can be found internally [here](https://www.figma.com/file/2b9r3CJbyeVAmKi1VHV2h9/Mobile-Payment-Element?node-id=1128%3A0) */
MaxLineLength:USBankAccountFormViewModelTest.kt$USBankAccountFormViewModelTest$fun
MaximumLineLength:CardDefinition.kt$internal
- MaximumLineLength:CommonConfiguration.kt$CommonConfiguration$
- ThrowsCount:CommonConfiguration.kt$CommonConfiguration$fun validate()
TooManyFunctions:CustomerSheetEventReporter.kt$CustomerSheetEventReporter
TooManyFunctions:DefaultCustomerSheetEventReporter.kt$DefaultCustomerSheetEventReporter : CustomerSheetEventReporter
TooManyFunctions:DefaultEventReporter.kt$DefaultEventReporter : EventReporter
@@ -81,6 +77,7 @@
TooManyFunctions:DelegateDrawable.kt$DelegateDrawable : Drawable
TooManyFunctions:EventReporter.kt$EventReporter
TooManyFunctions:PaymentMethodMetadata.kt$PaymentMethodMetadata : Parcelable
+ TooManyFunctions:SharedPaymentElementViewModel.kt$SharedPaymentElementViewModelModule
UnusedPrivateClass:PaymentOptionsViewModelTest.kt$PaymentOptionsViewModelTest$MyHostActivity : AppCompatActivity
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/EmbeddedContent.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/EmbeddedContent.kt
index 8e2f51eb9e2..a2efc3c33bf 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/EmbeddedContent.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/EmbeddedContent.kt
@@ -1,5 +1,6 @@
package com.stripe.android.paymentelement.embedded
+import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
@@ -16,13 +17,14 @@ import com.stripe.android.uicore.strings.resolve
@Immutable
internal data class EmbeddedContent(
private val interactor: PaymentMethodVerticalLayoutInteractor,
- private val mandate: ResolvableString? = null,
+ val mandate: ResolvableString? = null,
) {
@Composable
fun Content() {
Column(
modifier = Modifier
.padding(top = 8.dp)
+ .animateContentSize()
) {
EmbeddedVerticalList()
EmbeddedMandate()
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/EmbeddedContentHelper.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/EmbeddedContentHelper.kt
new file mode 100644
index 00000000000..24a710a3821
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/EmbeddedContentHelper.kt
@@ -0,0 +1,258 @@
+package com.stripe.android.paymentelement.embedded
+
+import androidx.lifecycle.SavedStateHandle
+import com.stripe.android.cards.CardAccountRangeRepository
+import com.stripe.android.core.injection.IOContext
+import com.stripe.android.core.strings.ResolvableString
+import com.stripe.android.core.strings.orEmpty
+import com.stripe.android.link.LinkConfigurationCoordinator
+import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
+import com.stripe.android.paymentsheet.CustomerStateHolder
+import com.stripe.android.paymentsheet.FormHelper
+import com.stripe.android.paymentsheet.LinkInlineHandler
+import com.stripe.android.paymentsheet.NewOrExternalPaymentSelection
+import com.stripe.android.paymentsheet.SavedPaymentMethodMutator
+import com.stripe.android.paymentsheet.analytics.EventReporter
+import com.stripe.android.paymentsheet.model.PaymentSelection
+import com.stripe.android.paymentsheet.repositories.CustomerRepository
+import com.stripe.android.paymentsheet.verticalmode.DefaultPaymentMethodVerticalLayoutInteractor
+import com.stripe.android.paymentsheet.verticalmode.DefaultPaymentMethodVerticalLayoutInteractor.FormType
+import com.stripe.android.paymentsheet.verticalmode.PaymentMethodIncentiveInteractor
+import com.stripe.android.paymentsheet.verticalmode.PaymentMethodVerticalLayoutInteractor
+import com.stripe.android.ui.core.cbc.CardBrandChoiceEligibility
+import com.stripe.android.uicore.utils.stateFlowOf
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import kotlin.coroutines.CoroutineContext
+
+internal interface EmbeddedContentHelper {
+ val embeddedContent: StateFlow
+
+ fun dataLoaded(paymentMethodMetadata: PaymentMethodMetadata)
+}
+
+internal fun interface EmbeddedContentHelperFactory {
+ fun create(coroutineScope: CoroutineScope): EmbeddedContentHelper
+}
+
+@AssistedFactory
+internal interface DefaultEmbeddedContentHelperFactory : EmbeddedContentHelperFactory {
+ override fun create(coroutineScope: CoroutineScope): DefaultEmbeddedContentHelper
+}
+
+internal class DefaultEmbeddedContentHelper @AssistedInject constructor(
+ @Assisted private val coroutineScope: CoroutineScope,
+ private val cardAccountRangeRepositoryFactory: CardAccountRangeRepository.Factory,
+ private val savedStateHandle: SavedStateHandle,
+ private val eventReporter: EventReporter,
+ private val linkConfigurationCoordinator: LinkConfigurationCoordinator,
+ @IOContext private val workContext: CoroutineContext,
+ private val customerRepository: CustomerRepository,
+ private val selectionHolder: EmbeddedSelectionHolder,
+) : EmbeddedContentHelper {
+
+ private val paymentMethodMetadata: StateFlow = savedStateHandle.getStateFlow(
+ key = PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT,
+ initialValue = null,
+ )
+ private val mandate: StateFlow = savedStateHandle.getStateFlow(
+ key = MANDATE_KEY_EMBEDDED_CONTENT,
+ initialValue = null,
+ )
+ private val _embeddedContent = MutableStateFlow(null)
+ override val embeddedContent: StateFlow = _embeddedContent.asStateFlow()
+
+ init {
+ coroutineScope.launch {
+ paymentMethodMetadata.collect { paymentMethodMetadata ->
+ _embeddedContent.value = if (paymentMethodMetadata == null) {
+ null
+ } else {
+ EmbeddedContent(
+ interactor = createInteractor(
+ coroutineScope = coroutineScope,
+ paymentMethodMetadata = paymentMethodMetadata,
+ )
+ )
+ }
+ }
+ }
+ coroutineScope.launch {
+ mandate.collect { mandate ->
+ _embeddedContent.update { originalEmbeddedContent ->
+ originalEmbeddedContent?.copy(mandate = mandate)
+ }
+ }
+ }
+ }
+
+ override fun dataLoaded(paymentMethodMetadata: PaymentMethodMetadata) {
+ savedStateHandle[PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT] = paymentMethodMetadata
+ }
+
+ private fun createInteractor(
+ coroutineScope: CoroutineScope,
+ paymentMethodMetadata: PaymentMethodMetadata,
+ ): PaymentMethodVerticalLayoutInteractor {
+ val paymentMethodIncentiveInteractor = PaymentMethodIncentiveInteractor(
+ incentive = paymentMethodMetadata.paymentMethodIncentive,
+ )
+ val customerStateHolder: CustomerStateHolder = CustomerStateHolder(
+ savedStateHandle = savedStateHandle,
+ selection = selectionHolder.selection,
+ )
+ val formHelper = createFormHelper(
+ coroutineScope = coroutineScope,
+ paymentMethodMetadata = paymentMethodMetadata,
+ )
+ val savedPaymentMethodMutator = createSavedPaymentMethodMutator(
+ coroutineScope = coroutineScope,
+ paymentMethodMetadata = paymentMethodMetadata,
+ customerStateHolder = customerStateHolder,
+ )
+
+ return DefaultPaymentMethodVerticalLayoutInteractor(
+ paymentMethodMetadata = paymentMethodMetadata,
+ processing = stateFlowOf(false),
+ selection = selectionHolder.selection,
+ paymentMethodIncentiveInteractor = paymentMethodIncentiveInteractor,
+ formTypeForCode = { code ->
+ if (formHelper.requiresFormScreen(code)) {
+ FormType.UserInteractionRequired
+ } else {
+ val mandate = formHelper.formElementsForCode(code).firstNotNullOfOrNull { it.mandateText }
+ if (mandate == null) {
+ FormType.Empty
+ } else {
+ FormType.MandateOnly(mandate)
+ }
+ }
+ },
+ onFormFieldValuesChanged = formHelper::onFormFieldValuesChanged,
+ transitionToManageScreen = {
+ },
+ transitionToManageOneSavedPaymentMethodScreen = {
+ },
+ transitionToFormScreen = {
+ },
+ paymentMethods = customerStateHolder.paymentMethods,
+ mostRecentlySelectedSavedPaymentMethod = customerStateHolder.mostRecentlySelectedSavedPaymentMethod,
+ providePaymentMethodName = savedPaymentMethodMutator.providePaymentMethodName,
+ canRemove = customerStateHolder.canRemove,
+ onEditPaymentMethod = {
+ },
+ onSelectSavedPaymentMethod = {
+ setSelection(PaymentSelection.Saved(it))
+ },
+ walletsState = stateFlowOf(null),
+ canShowWalletsInline = true,
+ onMandateTextUpdated = { updatedMandate ->
+ savedStateHandle[MANDATE_KEY_EMBEDDED_CONTENT] = updatedMandate
+ },
+ updateSelection = { updatedSelection ->
+ setSelection(updatedSelection)
+ },
+ isCurrentScreen = stateFlowOf(false),
+ reportPaymentMethodTypeSelected = eventReporter::onSelectPaymentMethod,
+ reportFormShown = eventReporter::onPaymentMethodFormShown,
+ onUpdatePaymentMethod = savedPaymentMethodMutator::updatePaymentMethod,
+ isLiveMode = paymentMethodMetadata.stripeIntent.isLiveMode,
+ )
+ }
+
+ private fun createSavedPaymentMethodMutator(
+ coroutineScope: CoroutineScope,
+ paymentMethodMetadata: PaymentMethodMetadata,
+ customerStateHolder: CustomerStateHolder,
+ ): SavedPaymentMethodMutator {
+ return SavedPaymentMethodMutator(
+ eventReporter = eventReporter,
+ coroutineScope = coroutineScope,
+ workContext = workContext,
+ customerRepository = customerRepository,
+ selection = selectionHolder.selection,
+ providePaymentMethodName = { code ->
+ code?.let {
+ paymentMethodMetadata.supportedPaymentMethodForCode(code)
+ }?.displayName.orEmpty()
+ },
+ clearSelection = {
+ setSelection(null)
+ },
+ customerStateHolder = customerStateHolder,
+ onPaymentMethodRemoved = {
+ },
+ onModifyPaymentMethod = { _, _, _, _, _ ->
+ },
+ onUpdatePaymentMethod = { _, _, _, _ ->
+ },
+ navigationPop = {
+ },
+ isCbcEligible = {
+ paymentMethodMetadata.cbcEligibility is CardBrandChoiceEligibility.Eligible
+ },
+ isGooglePayReady = stateFlowOf(false),
+ isLinkEnabled = stateFlowOf(false),
+ isNotPaymentFlow = false,
+ )
+ }
+
+ private fun createFormHelper(
+ coroutineScope: CoroutineScope,
+ paymentMethodMetadata: PaymentMethodMetadata,
+ ): FormHelper {
+ val linkInlineHandler = createLinkInlineHandler(coroutineScope)
+ return FormHelper(
+ cardAccountRangeRepositoryFactory = cardAccountRangeRepositoryFactory,
+ paymentMethodMetadata = paymentMethodMetadata,
+ newPaymentSelectionProvider = {
+ when (val currentSelection = selectionHolder.selection.value) {
+ is PaymentSelection.ExternalPaymentMethod -> {
+ NewOrExternalPaymentSelection.External(currentSelection)
+ }
+ is PaymentSelection.New -> {
+ NewOrExternalPaymentSelection.New(currentSelection)
+ }
+ else -> null
+ }
+ },
+ selectionUpdater = {
+ setSelection(it)
+ },
+ linkConfigurationCoordinator = linkConfigurationCoordinator,
+ onLinkInlineSignupStateChanged = linkInlineHandler::onStateUpdated,
+ )
+ }
+
+ private fun createLinkInlineHandler(
+ coroutineScope: CoroutineScope,
+ ): LinkInlineHandler {
+ return LinkInlineHandler(
+ coroutineScope = coroutineScope,
+ payWithLink = { _, _, _ ->
+ },
+ selection = selectionHolder.selection,
+ updateLinkPrimaryButtonUiState = {
+ },
+ primaryButtonLabel = stateFlowOf(null),
+ shouldCompleteLinkFlowInline = false,
+ )
+ }
+
+ private fun setSelection(paymentSelection: PaymentSelection?) {
+ savedStateHandle[MANDATE_KEY_EMBEDDED_CONTENT] = null
+ selectionHolder.set(paymentSelection)
+ }
+
+ companion object {
+ const val PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT = "PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT"
+ const val MANDATE_KEY_EMBEDDED_CONTENT = "MANDATE_KEY_EMBEDDED_CONTENT"
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/SharedPaymentElementViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/SharedPaymentElementViewModel.kt
index e9af5e52b8c..1118d5f4130 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/SharedPaymentElementViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/embedded/SharedPaymentElementViewModel.kt
@@ -9,6 +9,8 @@ import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import com.stripe.android.PaymentConfiguration
+import com.stripe.android.cards.CardAccountRangeRepository
+import com.stripe.android.cards.DefaultCardAccountRangeRepositoryFactory
import com.stripe.android.core.injection.CoreCommonModule
import com.stripe.android.core.injection.ENABLE_LOGGING
import com.stripe.android.core.injection.IOContext
@@ -83,6 +85,7 @@ internal class SharedPaymentElementViewModel @Inject constructor(
private val configurationHandler: EmbeddedConfigurationHandler,
private val paymentOptionDisplayDataFactory: PaymentOptionDisplayDataFactory,
private val selectionHolder: EmbeddedSelectionHolder,
+ embeddedContentHelperFactory: EmbeddedContentHelperFactory,
) : ViewModel() {
private val _paymentOption: MutableStateFlow = MutableStateFlow(null)
val paymentOption: StateFlow = _paymentOption.asStateFlow()
@@ -90,8 +93,8 @@ internal class SharedPaymentElementViewModel @Inject constructor(
val confirmationStateHolder = confirmationStateHolderFactory.create(viewModelScope)
val confirmationHandler = confirmationHandlerFactory.create(viewModelScope + ioContext)
- private val _embeddedContent = MutableStateFlow(null)
- val embeddedContent: StateFlow = _embeddedContent.asStateFlow()
+ private val embeddedContentHelper = embeddedContentHelperFactory.create(viewModelScope)
+ val embeddedContent: StateFlow = embeddedContentHelper.embeddedContent
init {
viewModelScope.launch {
@@ -117,6 +120,9 @@ internal class SharedPaymentElementViewModel @Inject constructor(
configuration = configuration,
)
selectionHolder.set(state.paymentSelection)
+ embeddedContentHelper.dataLoaded(
+ paymentMethodMetadata = state.paymentMethodMetadata,
+ )
ConfigureResult.Succeeded()
},
onFailure = { error ->
@@ -179,6 +185,16 @@ internal interface SharedPaymentElementViewModelComponent {
],
)
internal interface SharedPaymentElementViewModelModule {
+ @Binds
+ fun bindsEmbeddedContentHelperFactory(
+ factory: DefaultEmbeddedContentHelperFactory
+ ): EmbeddedContentHelperFactory
+
+ @Binds
+ fun bindsCardAccountRangeRepositoryFactory(
+ defaultCardAccountRangeRepositoryFactory: DefaultCardAccountRangeRepositoryFactory
+ ): CardAccountRangeRepository.Factory
+
@Binds
fun bindsConfigurationHandler(
handler: DefaultEmbeddedConfigurationHandler
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/DefaultEmbeddedContentHelperTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/DefaultEmbeddedContentHelperTest.kt
new file mode 100644
index 00000000000..fa482262a3f
--- /dev/null
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/DefaultEmbeddedContentHelperTest.kt
@@ -0,0 +1,109 @@
+package com.stripe.android.paymentelement.embedded
+
+import androidx.lifecycle.SavedStateHandle
+import app.cash.turbine.test
+import com.google.common.truth.Truth.assertThat
+import com.stripe.android.core.strings.resolvableString
+import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
+import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadataFactory
+import com.stripe.android.paymentelement.embedded.DefaultEmbeddedContentHelper.Companion.MANDATE_KEY_EMBEDDED_CONTENT
+import com.stripe.android.paymentelement.embedded.DefaultEmbeddedContentHelper.Companion.PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT
+import com.stripe.android.utils.FakeCustomerRepository
+import com.stripe.android.utils.FakeLinkConfigurationCoordinator
+import com.stripe.android.utils.NullCardAccountRangeRepositoryFactory
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.runTest
+import org.mockito.Mockito.mock
+import kotlin.test.Test
+
+internal class DefaultEmbeddedContentHelperTest {
+ @Test
+ fun `dataLoaded updates savedStateHandle with paymentMethodMetadata`() = testScenario {
+ assertThat(savedStateHandle.get(PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT))
+ .isNull()
+ val paymentMethodMetadata = PaymentMethodMetadataFactory.create()
+ embeddedContentHelper.dataLoaded(paymentMethodMetadata)
+ assertThat(savedStateHandle.get(PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT))
+ .isEqualTo(paymentMethodMetadata)
+ }
+
+ @Test
+ fun `dataLoaded emits embeddedContent event`() = testScenario {
+ embeddedContentHelper.embeddedContent.test {
+ assertThat(awaitItem()).isNull()
+ embeddedContentHelper.dataLoaded(PaymentMethodMetadataFactory.create())
+ assertThat(awaitItem()).isNotNull()
+ }
+ }
+
+ @Test
+ fun `setting mandate emits embeddedContent event`() = testScenario {
+ embeddedContentHelper.embeddedContent.test {
+ assertThat(awaitItem()).isNull()
+ embeddedContentHelper.dataLoaded(PaymentMethodMetadataFactory.create())
+ awaitItem().run {
+ assertThat(this).isNotNull()
+ assertThat(this?.mandate).isNull()
+ }
+ savedStateHandle[MANDATE_KEY_EMBEDDED_CONTENT] = "Hi".resolvableString
+ awaitItem().run {
+ assertThat(this).isNotNull()
+ assertThat(this?.mandate).isEqualTo("Hi".resolvableString)
+ }
+ }
+ }
+
+ @Test
+ fun `initializing embeddedContentHelper with paymentMethodMetadata emits correct initial event`() = testScenario(
+ setup = {
+ set(PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT, PaymentMethodMetadataFactory.create())
+ }
+ ) {
+ embeddedContentHelper.embeddedContent.test {
+ assertThat(awaitItem()).isNotNull()
+ }
+ }
+
+ @Test
+ fun `initializing embeddedContentHelper with mandate emits correct initial event`() = testScenario(
+ setup = {
+ set(PAYMENT_METHOD_METADATA_KEY_EMBEDDED_CONTENT, PaymentMethodMetadataFactory.create())
+ set(MANDATE_KEY_EMBEDDED_CONTENT, "Hi".resolvableString)
+ }
+ ) {
+ embeddedContentHelper.embeddedContent.test {
+ awaitItem().run {
+ assertThat(this).isNotNull()
+ assertThat(this?.mandate).isEqualTo("Hi".resolvableString)
+ }
+ }
+ }
+
+ private class Scenario(
+ val embeddedContentHelper: DefaultEmbeddedContentHelper,
+ val savedStateHandle: SavedStateHandle,
+ )
+
+ private fun testScenario(
+ setup: SavedStateHandle.() -> Unit = {},
+ block: suspend Scenario.() -> Unit,
+ ) = runTest {
+ val savedStateHandle = SavedStateHandle()
+ savedStateHandle.setup()
+ val embeddedContentHelper = DefaultEmbeddedContentHelper(
+ coroutineScope = CoroutineScope(Dispatchers.Unconfined),
+ cardAccountRangeRepositoryFactory = NullCardAccountRangeRepositoryFactory,
+ savedStateHandle = savedStateHandle,
+ eventReporter = mock(),
+ linkConfigurationCoordinator = FakeLinkConfigurationCoordinator(),
+ workContext = Dispatchers.Unconfined,
+ customerRepository = FakeCustomerRepository(),
+ selectionHolder = EmbeddedSelectionHolder(savedStateHandle),
+ )
+ Scenario(
+ embeddedContentHelper = embeddedContentHelper,
+ savedStateHandle = savedStateHandle,
+ ).block()
+ }
+}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/FakeEmbeddedContentHelper.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/FakeEmbeddedContentHelper.kt
new file mode 100644
index 00000000000..b8915ccc4a5
--- /dev/null
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/FakeEmbeddedContentHelper.kt
@@ -0,0 +1,21 @@
+package com.stripe.android.paymentelement.embedded
+
+import app.cash.turbine.ReceiveTurbine
+import app.cash.turbine.Turbine
+import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
+import kotlinx.coroutines.flow.MutableStateFlow
+
+internal class FakeEmbeddedContentHelper(
+ override val embeddedContent: MutableStateFlow = MutableStateFlow(null)
+) : EmbeddedContentHelper {
+ private val _dataLoadedTurbine = Turbine()
+ val dataLoadedTurbine: ReceiveTurbine = _dataLoadedTurbine
+
+ override fun dataLoaded(paymentMethodMetadata: PaymentMethodMetadata) {
+ _dataLoadedTurbine.add(paymentMethodMetadata)
+ }
+
+ fun validate() {
+ dataLoadedTurbine.ensureAllEventsConsumed()
+ }
+}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/SharedPaymentElementViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/SharedPaymentElementViewModelTest.kt
index ab65e1d087d..4a02d6cb8e5 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/SharedPaymentElementViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/embedded/SharedPaymentElementViewModelTest.kt
@@ -64,6 +64,7 @@ internal class SharedPaymentElementViewModelTest {
configuration = configuration,
)
).isInstanceOf()
+ assertThat(embeddedContentHelper.dataLoadedTurbine.awaitItem()).isNotNull()
assertThat(viewModel.confirmationStateHolder.state?.paymentMethodMetadata).isNotNull()
}
@@ -103,6 +104,7 @@ internal class SharedPaymentElementViewModelTest {
configuration = configuration,
)
).isInstanceOf()
+ assertThat(embeddedContentHelper.dataLoadedTurbine.awaitItem()).isNotNull()
assertThat(viewModel.confirmationStateHolder.state?.selection?.paymentMethodType).isEqualTo("google_pay")
selectionHolder.set(null)
@@ -145,6 +147,7 @@ internal class SharedPaymentElementViewModelTest {
viewModel.paymentOption.test {
assertThat(awaitItem()).isNull()
}
+ assertThat(embeddedContentHelper.dataLoadedTurbine.awaitItem()).isNotNull()
}
@Test
@@ -188,6 +191,7 @@ internal class SharedPaymentElementViewModelTest {
assertThat(awaitItem()?.paymentMethodType).isEqualTo("google_pay")
}
assertThat(selectionHolder.selection.value?.paymentMethodType).isEqualTo("google_pay")
+ assertThat(embeddedContentHelper.dataLoadedTurbine.awaitItem()).isNotNull()
}
@Test
@@ -248,52 +252,58 @@ internal class SharedPaymentElementViewModelTest {
assertThat(selectionHolder.selection.value?.paymentMethodType).isNull()
assertThat(awaitItem()).isNull()
}
+ assertThat(embeddedContentHelper.dataLoadedTurbine.awaitItem()).isNotNull()
}
private fun testScenario(
block: suspend Scenario.() -> Unit,
- ) {
- runTest {
- val confirmationHandler = FakeConfirmationHandler()
- val configurationHandler = FakeEmbeddedConfigurationHandler()
- val paymentOptionDisplayDataFactory = PaymentOptionDisplayDataFactory(
- iconLoader = mock(),
- context = ApplicationProvider.getApplicationContext(),
- )
- val savedStateHandle = SavedStateHandle()
- val selectionHolder = EmbeddedSelectionHolder(savedStateHandle)
- val confirmationStateHolder = EmbeddedConfirmationStateHolder(
- savedStateHandle = savedStateHandle,
- selectionHolder = selectionHolder,
- coroutineScope = CoroutineScope(UnconfinedTestDispatcher()),
- )
+ ) = runTest {
+ val confirmationHandler = FakeConfirmationHandler()
+ val configurationHandler = FakeEmbeddedConfigurationHandler()
+ val paymentOptionDisplayDataFactory = PaymentOptionDisplayDataFactory(
+ iconLoader = mock(),
+ context = ApplicationProvider.getApplicationContext(),
+ )
+ val savedStateHandle = SavedStateHandle()
+ val selectionHolder = EmbeddedSelectionHolder(savedStateHandle)
+ val confirmationStateHolder = EmbeddedConfirmationStateHolder(
+ savedStateHandle = savedStateHandle,
+ selectionHolder = selectionHolder,
+ coroutineScope = CoroutineScope(UnconfinedTestDispatcher()),
+ )
+ val embeddedContentHelper = FakeEmbeddedContentHelper()
- val viewModel = SharedPaymentElementViewModel(
- confirmationStateHolderFactory = EmbeddedConfirmationStateHolderFactory {
- confirmationStateHolder
- },
- confirmationHandlerFactory = { confirmationHandler },
- ioContext = testScheduler,
- configurationHandler = configurationHandler,
- paymentOptionDisplayDataFactory = paymentOptionDisplayDataFactory,
- selectionHolder = selectionHolder,
- )
+ val viewModel = SharedPaymentElementViewModel(
+ confirmationStateHolderFactory = EmbeddedConfirmationStateHolderFactory {
+ confirmationStateHolder
+ },
+ confirmationHandlerFactory = { confirmationHandler },
+ ioContext = testScheduler,
+ configurationHandler = configurationHandler,
+ paymentOptionDisplayDataFactory = paymentOptionDisplayDataFactory,
+ selectionHolder = selectionHolder,
+ embeddedContentHelperFactory = EmbeddedContentHelperFactory {
+ embeddedContentHelper
+ }
+ )
- Scenario(
- configurationHandler = configurationHandler,
- viewModel = viewModel,
- selectionHolder = selectionHolder,
- ).block()
+ Scenario(
+ configurationHandler = configurationHandler,
+ viewModel = viewModel,
+ selectionHolder = selectionHolder,
+ embeddedContentHelper = embeddedContentHelper,
+ ).block()
- configurationHandler.turbine.ensureAllEventsConsumed()
- confirmationHandler.validate()
- }
+ configurationHandler.turbine.ensureAllEventsConsumed()
+ confirmationHandler.validate()
+ embeddedContentHelper.validate()
}
private class Scenario(
val configurationHandler: FakeEmbeddedConfigurationHandler,
val viewModel: SharedPaymentElementViewModel,
val selectionHolder: EmbeddedSelectionHolder,
+ val embeddedContentHelper: FakeEmbeddedContentHelper,
)
private class FakeEmbeddedConfigurationHandler : EmbeddedConfigurationHandler {