Skip to content

Commit

Permalink
Initialize DefaultVerticalModeFormInteractor
Browse files Browse the repository at this point in the history
  • Loading branch information
tjclawson-stripe committed Jan 25, 2025
1 parent a997c6d commit b0a1c79
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
import com.stripe.android.common.ui.ElementsBottomSheetLayout
import com.stripe.android.paymentsheet.verticalmode.VerticalModeFormUI
import com.stripe.android.uicore.StripeTheme
import com.stripe.android.uicore.elements.bottomsheet.rememberStripeBottomSheetState
import com.stripe.android.uicore.utils.fadeOut
Expand Down Expand Up @@ -47,7 +47,10 @@ internal class FormActivity : AppCompatActivity() {
finish()
}
) {
Text(viewModel.selectedPaymentMethodCode)
VerticalModeFormUI(
interactor = viewModel.formInteractor,
showsWalletHeader = false
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
package com.stripe.android.paymentelement.embedded.form

import android.content.Context
import androidx.lifecycle.SavedStateHandle
import com.stripe.android.cards.CardAccountRangeRepository
import com.stripe.android.cards.DefaultCardAccountRangeRepositoryFactory
import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.link.RealLinkConfigurationCoordinator
import com.stripe.android.link.injection.LinkAnalyticsComponent
import com.stripe.android.link.injection.LinkComponent
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.model.PaymentMethodCode
import com.stripe.android.paymentelement.embedded.EmbeddedCommonModule
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import javax.inject.Singleton

@Component
@Component(
modules = [
EmbeddedCommonModule::class,
FormActivityModule::class
]
)
@Singleton
internal interface FormActivityComponent {

Expand All @@ -20,6 +36,28 @@ internal interface FormActivityComponent {
@BindsInstance
fun selectedPaymentMethodCode(selectedPaymentMethodCode: PaymentMethodCode): Builder

@BindsInstance
fun context(context: Context): Builder

@BindsInstance
fun savedStateHandle(savedStateHandle: SavedStateHandle): Builder

fun build(): FormActivityComponent
}
}

@Module(
subcomponents = [
LinkAnalyticsComponent::class,
LinkComponent::class,
]
)
internal interface FormActivityModule {
@Binds
fun bindsCardAccountRangeRepositoryFactory(
defaultCardAccountRangeRepositoryFactory: DefaultCardAccountRangeRepositoryFactory
): CardAccountRangeRepository.Factory

@Binds
fun bindsLinkConfigurationCoordinator(impl: RealLinkConfigurationCoordinator): LinkConfigurationCoordinator
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,145 @@ package com.stripe.android.paymentelement.embedded.form

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import com.stripe.android.cards.CardAccountRangeRepository
import com.stripe.android.core.utils.requireApplication
import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.lpmfoundations.luxe.isSaveForFutureUseValueChangeable
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCode
import com.stripe.android.paymentelement.embedded.EmbeddedSelectionHolder
import com.stripe.android.payments.bankaccount.CollectBankAccountLauncher.Companion.HOSTED_SURFACE_PAYMENT_ELEMENT
import com.stripe.android.paymentsheet.DefaultFormHelper
import com.stripe.android.paymentsheet.FormHelper
import com.stripe.android.paymentsheet.LinkInlineHandler
import com.stripe.android.paymentsheet.NewOrExternalPaymentSelection
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.paymentdatacollection.ach.USBankAccountFormArguments
import com.stripe.android.paymentsheet.verticalmode.BankFormInteractor
import com.stripe.android.paymentsheet.verticalmode.DefaultVerticalModeFormInteractor
import com.stripe.android.paymentsheet.verticalmode.PaymentMethodIncentiveInteractor
import com.stripe.android.uicore.utils.stateFlowOf
import javax.inject.Inject

internal class FormActivityViewModel @Inject constructor(
val paymentMethodMetadata: PaymentMethodMetadata,
val selectedPaymentMethodCode: PaymentMethodCode
paymentMethodMetadata: PaymentMethodMetadata,
selectedPaymentMethodCode: PaymentMethodCode,
private val cardAccountRangeRepositoryFactory: CardAccountRangeRepository.Factory,
private val selectionHolder: EmbeddedSelectionHolder,
private val linkConfigurationCoordinator: LinkConfigurationCoordinator,
) : ViewModel() {

val formInteractor = initializeFormInteractor(paymentMethodMetadata, selectedPaymentMethodCode)

private fun initializeFormInteractor(
paymentMethodMetadata: PaymentMethodMetadata,
selectedPaymentMethodCode: PaymentMethodCode,
): DefaultVerticalModeFormInteractor {
val formHelper = createFormHelper(paymentMethodMetadata = paymentMethodMetadata)

return DefaultVerticalModeFormInteractor(
selectedPaymentMethodCode = selectedPaymentMethodCode,
formArguments = formHelper.createFormArguments(selectedPaymentMethodCode),
formElements = formHelper.formElementsForCode(selectedPaymentMethodCode),
onFormFieldValuesChanged = formHelper::onFormFieldValuesChanged,
usBankAccountArguments = createUsBankAccountFormArguments(
paymentMethodMetadata,
selectedPaymentMethodCode
),
reportFieldInteraction = {
},
headerInformation = null,
isLiveMode = paymentMethodMetadata.stripeIntent.isLiveMode,
processing = stateFlowOf(false),
paymentMethodIncentive = PaymentMethodIncentiveInteractor(
paymentMethodMetadata.paymentMethodIncentive
).displayedIncentive,
coroutineScope = viewModelScope,
)
}

private fun createFormHelper(paymentMethodMetadata: PaymentMethodMetadata): FormHelper {
return DefaultFormHelper(
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 = { selection ->
selectionHolder.set(selection)
},
linkConfigurationCoordinator = linkConfigurationCoordinator,
linkInlineHandler = LinkInlineHandler.create(),
coroutineScope = viewModelScope
)
}

private fun createUsBankAccountFormArguments(
paymentMethodMetadata: PaymentMethodMetadata,
selectedPaymentMethodCode: PaymentMethodCode,
): USBankAccountFormArguments {
val isSaveForFutureUseValueChangeable = isSaveForFutureUseValueChangeable(
code = selectedPaymentMethodCode,
intent = paymentMethodMetadata.stripeIntent,
paymentMethodSaveConsentBehavior = paymentMethodMetadata.paymentMethodSaveConsentBehavior,
hasCustomerConfiguration = paymentMethodMetadata.hasCustomerConfiguration,
)
val instantDebits = selectedPaymentMethodCode == PaymentMethod.Type.Link.code
val bankFormInteractor = BankFormInteractor(
updateSelection = { selectionHolder.set(it) },
paymentMethodIncentiveInteractor = PaymentMethodIncentiveInteractor(
paymentMethodMetadata.paymentMethodIncentive
)
)
return USBankAccountFormArguments(
showCheckbox = isSaveForFutureUseValueChangeable && instantDebits.not(),
hostedSurface = HOSTED_SURFACE_PAYMENT_ELEMENT,
instantDebits = instantDebits,
linkMode = paymentMethodMetadata.linkMode,
onBehalfOf = null,
isCompleteFlow = false,
isPaymentFlow = paymentMethodMetadata.stripeIntent is PaymentIntent,
stripeIntentId = paymentMethodMetadata.stripeIntent.id,
clientSecret = paymentMethodMetadata.stripeIntent.clientSecret,
shippingDetails = paymentMethodMetadata.shippingDetails,
draftPaymentSelection = null,
onMandateTextChanged = { _, _ ->
},
onLinkedBankAccountChanged = bankFormInteractor::handleLinkedBankAccountChanged,
onUpdatePrimaryButtonUIState = {
},
onUpdatePrimaryButtonState = {
},
onError = {
},
incentive = paymentMethodMetadata.paymentMethodIncentive,
)
}

class Factory(
private val argSupplier: () -> FormContract.Args
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val args = argSupplier()
val component = DaggerFormActivityComponent.builder()
.paymentMethodMetadata(args.paymentMethodMetadata)
.selectedPaymentMethodCode(args.selectedPaymentMethodCode)
.context(extras.requireApplication())
.savedStateHandle(extras.createSavedStateHandle())
.build()

return component.viewModel as T
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.stripe.android.paymentelement.embedded.form

import androidx.lifecycle.SavedStateHandle
import com.google.common.truth.Truth.assertThat
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadataFactory
import com.stripe.android.model.PaymentMethodCode
import com.stripe.android.paymentelement.embedded.EmbeddedSelectionHolder
import com.stripe.android.utils.FakeLinkConfigurationCoordinator
import com.stripe.android.utils.NullCardAccountRangeRepositoryFactory
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
internal class FormActivityViewModelTest {

@Test
fun `viewmodel initializes interactor correctly`() = runTest {
val paymentMethodMetadata = PaymentMethodMetadataFactory.create()

val selectedPaymentMethodCode = "klarna"

val viewModel = createViewModel(paymentMethodMetadata, selectedPaymentMethodCode)

assertThat(viewModel.formInteractor.state.value.formArguments.paymentMethodCode).isEqualTo("klarna")
}

private fun createViewModel(
paymentMethodMetadata: PaymentMethodMetadata,
paymentMethodCode: PaymentMethodCode
): FormActivityViewModel {
val savedStateHandle = SavedStateHandle()
val selectionHolder = EmbeddedSelectionHolder(savedStateHandle)

return FormActivityViewModel(
paymentMethodMetadata = paymentMethodMetadata,
selectedPaymentMethodCode = paymentMethodCode,
cardAccountRangeRepositoryFactory = NullCardAccountRangeRepositoryFactory,
selectionHolder = selectionHolder,
linkConfigurationCoordinator = FakeLinkConfigurationCoordinator()
)
}
}

0 comments on commit b0a1c79

Please sign in to comment.