diff --git a/paymentsheet/detekt-baseline.xml b/paymentsheet/detekt-baseline.xml
index 29d6a472d79..d8ca452e7ba 100644
--- a/paymentsheet/detekt-baseline.xml
+++ b/paymentsheet/detekt-baseline.xml
@@ -3,6 +3,7 @@
ConstructorParameterNaming:BankFormScreenState.kt$BankFormScreenState$private val _isProcessing: Boolean = false
+ CyclomaticComplexMethod:ConfirmationOptionKtx.kt$internal fun PaymentSelection.toConfirmationOption( configuration: CommonConfiguration, linkConfiguration: LinkConfiguration?, ): ConfirmationHandler.Option?
CyclomaticComplexMethod:CustomerSheetViewModel.kt$CustomerSheetViewModel$fun handleViewAction(viewAction: CustomerSheetViewAction)
CyclomaticComplexMethod:PlaceholderHelper.kt$PlaceholderHelper$@VisibleForTesting internal fun specForPlaceholderField( field: PlaceholderField, placeholderOverrideList: List<IdentifierSpec>, requiresMandate: Boolean, configuration: PaymentSheet.BillingDetailsCollectionConfiguration, )
CyclomaticComplexMethod:TransformSpecToElements.kt$TransformSpecToElements$fun transform( specs: List<FormItemSpec>, placeholderOverrideList: List<IdentifierSpec> = emptyList(), ): List<FormElement>
@@ -25,6 +26,7 @@
LargeClass:PaymentSheetViewModelTest.kt$PaymentSheetViewModelTest
LargeClass:USBankAccountFormViewModelTest.kt$USBankAccountFormViewModelTest
LongMethod:AutocompleteScreen.kt$@Composable internal fun AutocompleteScreenUI(viewModel: AutocompleteViewModel)
+ LongMethod:ConfirmationOptionKtx.kt$internal fun PaymentSelection.toConfirmationOption( configuration: CommonConfiguration, 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$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, ), someDefinitionIsConfirmable: Boolean = true, 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:EmbeddedContentHelper.kt$DefaultEmbeddedContentHelper$private fun createInteractor( coroutineScope: CoroutineScope, paymentMethodMetadata: PaymentMethodMetadata, walletsState: StateFlow<WalletsState?>, ): PaymentMethodVerticalLayoutInteractor
diff --git a/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/utils/ProductIntegrationType.kt b/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/utils/ProductIntegrationType.kt
index 3a40178d6be..5cc6ad55a23 100644
--- a/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/utils/ProductIntegrationType.kt
+++ b/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/utils/ProductIntegrationType.kt
@@ -9,6 +9,6 @@ internal enum class ProductIntegrationType {
internal object ProductIntegrationTypeProvider : TestParameterValuesProvider() {
override fun provideValues(context: Context?): List {
- return ProductIntegrationType.entries
+ return listOf(ProductIntegrationType.FlowController)
}
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/ui/inline/UserInput.kt b/paymentsheet/src/main/java/com/stripe/android/link/ui/inline/UserInput.kt
index f3cb06dfa91..a7460e903c0 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/ui/inline/UserInput.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/ui/inline/UserInput.kt
@@ -6,11 +6,11 @@ import kotlinx.parcelize.Parcelize
/**
* Valid user input into the inline sign up view.
*/
-@Parcelize
internal sealed class UserInput : Parcelable {
/**
* Represents an input that is valid for signing in to a link account.
*/
+ @Parcelize
data class SignIn(
val email: String
) : UserInput()
@@ -18,6 +18,7 @@ internal sealed class UserInput : Parcelable {
/**
* Represents an input that is valid for signing up to a link account.
*/
+ @Parcelize
data class SignUp(
val email: String,
val phone: String,
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/ConfirmationOptionKtx.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/ConfirmationOptionKtx.kt
index 0353aeecba7..a27892185eb 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/ConfirmationOptionKtx.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/ConfirmationOptionKtx.kt
@@ -8,6 +8,7 @@ import com.stripe.android.paymentelement.confirmation.bacs.BacsConfirmationOptio
import com.stripe.android.paymentelement.confirmation.epms.ExternalPaymentMethodConfirmationOption
import com.stripe.android.paymentelement.confirmation.gpay.GooglePayConfirmationOption
import com.stripe.android.paymentelement.confirmation.link.LinkConfirmationOption
+import com.stripe.android.paymentelement.confirmation.linkinline.LinkInlineSignupConfirmationOption
import com.stripe.android.paymentsheet.model.PaymentSelection
internal fun PaymentSelection.toConfirmationOption(
@@ -39,6 +40,22 @@ internal fun PaymentSelection.toConfirmationOption(
)
}
}
+ is PaymentSelection.New.LinkInline -> linkConfiguration?.let {
+ LinkInlineSignupConfirmationOption(
+ createParams = paymentMethodCreateParams,
+ optionsParams = paymentMethodOptionsParams,
+ userInput = input,
+ linkConfiguration = linkConfiguration,
+ saveOption = when (customerRequestedSave) {
+ PaymentSelection.CustomerRequestedSave.RequestReuse ->
+ LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedReuse
+ PaymentSelection.CustomerRequestedSave.RequestNoReuse ->
+ LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedNoReuse
+ PaymentSelection.CustomerRequestedSave.NoRequest ->
+ LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.NoRequest
+ }
+ )
+ }
is PaymentSelection.New -> {
if (paymentMethodCreateParams.typeCode == PaymentMethod.Type.BacsDebit.code) {
BacsConfirmationOption(
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/injection/PaymentElementConfirmationModule.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/injection/PaymentElementConfirmationModule.kt
index a4ef1ebff45..7a3a9f5d54f 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/injection/PaymentElementConfirmationModule.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/injection/PaymentElementConfirmationModule.kt
@@ -4,6 +4,7 @@ import com.stripe.android.paymentelement.confirmation.bacs.BacsConfirmationModul
import com.stripe.android.paymentelement.confirmation.epms.ExternalPaymentMethodConfirmationModule
import com.stripe.android.paymentelement.confirmation.gpay.GooglePayConfirmationModule
import com.stripe.android.paymentelement.confirmation.link.LinkConfirmationModule
+import com.stripe.android.paymentelement.confirmation.linkinline.LinkInlineSignupConfirmationModule
import dagger.Module
@Module(
@@ -13,6 +14,7 @@ import dagger.Module
ExternalPaymentMethodConfirmationModule::class,
GooglePayConfirmationModule::class,
LinkConfirmationModule::class,
+ LinkInlineSignupConfirmationModule::class,
]
)
internal interface PaymentElementConfirmationModule
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationDefinition.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationDefinition.kt
new file mode 100644
index 00000000000..5a8173facaf
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationDefinition.kt
@@ -0,0 +1,196 @@
+package com.stripe.android.paymentelement.confirmation.linkinline
+
+import android.os.Parcelable
+import androidx.activity.result.ActivityResultCaller
+import com.stripe.android.link.LinkConfigurationCoordinator
+import com.stripe.android.link.LinkPaymentDetails
+import com.stripe.android.link.account.LinkStore
+import com.stripe.android.link.analytics.LinkAnalyticsHelper
+import com.stripe.android.link.model.AccountStatus
+import com.stripe.android.model.ConfirmPaymentIntentParams
+import com.stripe.android.model.PaymentMethod
+import com.stripe.android.model.PaymentMethodCreateParams
+import com.stripe.android.model.PaymentMethodOptionsParams
+import com.stripe.android.model.wallets.Wallet
+import com.stripe.android.paymentelement.confirmation.ConfirmationDefinition
+import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
+import com.stripe.android.paymentelement.confirmation.PaymentMethodConfirmationOption
+import com.stripe.android.paymentelement.confirmation.intent.DeferredIntentConfirmationType
+import kotlinx.coroutines.flow.first
+import kotlinx.parcelize.Parcelize
+
+internal class LinkInlineSignupConfirmationDefinition(
+ private val linkConfigurationCoordinator: LinkConfigurationCoordinator,
+ private val linkAnalyticsHelper: LinkAnalyticsHelper,
+ private val linkStore: LinkStore,
+) : ConfirmationDefinition<
+ LinkInlineSignupConfirmationOption,
+ LinkInlineSignupConfirmationDefinition.Launcher,
+ LinkInlineSignupConfirmationDefinition.LauncherArguments,
+ LinkInlineSignupConfirmationDefinition.Result,
+ > {
+ override val key: String = "LinkInlineSignup"
+
+ override fun option(confirmationOption: ConfirmationHandler.Option): LinkInlineSignupConfirmationOption? {
+ return confirmationOption as? LinkInlineSignupConfirmationOption
+ }
+
+ override suspend fun action(
+ confirmationOption: LinkInlineSignupConfirmationOption,
+ confirmationParameters: ConfirmationDefinition.Parameters
+ ): ConfirmationDefinition.Action {
+ val nextConfirmationOption = createPaymentMethodConfirmationOption(confirmationOption)
+
+ return ConfirmationDefinition.Action.Launch(
+ launcherArguments = LauncherArguments(nextConfirmationOption),
+ receivesResultInProcess = true,
+ deferredIntentConfirmationType = null,
+ )
+ }
+
+ override fun createLauncher(
+ activityResultCaller: ActivityResultCaller,
+ onResult: (Result) -> Unit
+ ): Launcher {
+ return Launcher(onResult)
+ }
+
+ override fun launch(
+ launcher: Launcher,
+ arguments: LauncherArguments,
+ confirmationOption: LinkInlineSignupConfirmationOption,
+ confirmationParameters: ConfirmationDefinition.Parameters,
+ ) {
+ launcher.onResult(Result(arguments.nextConfirmationOption))
+ }
+
+ override fun toResult(
+ confirmationOption: LinkInlineSignupConfirmationOption,
+ confirmationParameters: ConfirmationDefinition.Parameters,
+ deferredIntentConfirmationType: DeferredIntentConfirmationType?,
+ result: Result,
+ ): ConfirmationDefinition.Result {
+ return ConfirmationDefinition.Result.NextStep(
+ confirmationOption = result.nextConfirmationOption,
+ parameters = confirmationParameters,
+ )
+ }
+
+ private suspend fun createPaymentMethodConfirmationOption(
+ linkInlineSignupConfirmationOption: LinkInlineSignupConfirmationOption,
+ ): PaymentMethodConfirmationOption {
+ val configuration = linkInlineSignupConfirmationOption.linkConfiguration
+ val userInput = linkInlineSignupConfirmationOption.userInput
+
+ return when (linkConfigurationCoordinator.getAccountStatusFlow(configuration).first()) {
+ AccountStatus.Verified -> createOptionAfterAttachingToLink(linkInlineSignupConfirmationOption)
+ AccountStatus.VerificationStarted,
+ AccountStatus.NeedsVerification -> {
+ linkAnalyticsHelper.onLinkPopupSkipped()
+
+ linkInlineSignupConfirmationOption.toNewOption()
+ }
+ AccountStatus.SignedOut,
+ AccountStatus.Error -> {
+ linkConfigurationCoordinator.signInWithUserInput(configuration, userInput).fold(
+ onSuccess = {
+ // If successful, the account was fetched or created, so try again
+ createPaymentMethodConfirmationOption(linkInlineSignupConfirmationOption)
+ },
+ onFailure = {
+ linkInlineSignupConfirmationOption.toNewOption()
+ }
+ )
+ }
+ }
+ }
+
+ private suspend fun createOptionAfterAttachingToLink(
+ linkInlineSignupConfirmationOption: LinkInlineSignupConfirmationOption,
+ ): PaymentMethodConfirmationOption {
+ val createParams = linkInlineSignupConfirmationOption.createParams
+ val saveOption = linkInlineSignupConfirmationOption.saveOption
+
+ val linkPaymentDetails = linkConfigurationCoordinator.attachNewCardToAccount(
+ linkInlineSignupConfirmationOption.linkConfiguration,
+ createParams,
+ ).getOrNull()
+
+ return when (linkPaymentDetails) {
+ is LinkPaymentDetails.New -> {
+ linkStore.markLinkAsUsed()
+
+ linkPaymentDetails.toNewOption(saveOption)
+ }
+ is LinkPaymentDetails.Saved -> {
+ linkStore.markLinkAsUsed()
+
+ linkPaymentDetails.toSavedOption(createParams, saveOption)
+ }
+ null -> linkInlineSignupConfirmationOption.toNewOption()
+ }
+ }
+
+ private fun LinkPaymentDetails.Saved.toSavedOption(
+ createParams: PaymentMethodCreateParams,
+ saveOption: LinkInlineSignupConfirmationOption.PaymentMethodSaveOption,
+ ): PaymentMethodConfirmationOption.Saved {
+ val last4 = paymentDetails.last4
+
+ return PaymentMethodConfirmationOption.Saved(
+ paymentMethod = PaymentMethod.Builder()
+ .setId(paymentDetails.id)
+ .setCode(createParams.typeCode)
+ .setCard(
+ PaymentMethod.Card(
+ last4 = last4,
+ wallet = Wallet.LinkWallet(last4),
+ )
+ )
+ .setType(PaymentMethod.Type.Card)
+ .build(),
+ optionsParams = PaymentMethodOptionsParams.Card(
+ setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession?.takeIf {
+ saveOption.shouldSave()
+ } ?: ConfirmPaymentIntentParams.SetupFutureUsage.Blank
+ ),
+ )
+ }
+
+ private fun LinkPaymentDetails.New.toNewOption(
+ saveOption: LinkInlineSignupConfirmationOption.PaymentMethodSaveOption
+ ): PaymentMethodConfirmationOption.New {
+ return PaymentMethodConfirmationOption.New(
+ createParams = paymentMethodCreateParams,
+ optionsParams = PaymentMethodOptionsParams.Card(
+ setupFutureUsage = saveOption.setupFutureUsage,
+ ),
+ shouldSave = saveOption.shouldSave(),
+ )
+ }
+
+ private fun LinkInlineSignupConfirmationOption.toNewOption(): PaymentMethodConfirmationOption.New {
+ return PaymentMethodConfirmationOption.New(
+ createParams = createParams,
+ optionsParams = optionsParams,
+ shouldSave = saveOption.shouldSave(),
+ )
+ }
+
+ private fun LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.shouldSave(): Boolean {
+ return this == LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedReuse
+ }
+
+ @Parcelize
+ data class Result(
+ val nextConfirmationOption: PaymentMethodConfirmationOption,
+ ) : Parcelable
+
+ data class LauncherArguments(
+ val nextConfirmationOption: PaymentMethodConfirmationOption,
+ )
+
+ class Launcher(
+ val onResult: (Result) -> Unit,
+ )
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationModule.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationModule.kt
new file mode 100644
index 00000000000..6efe7cef711
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationModule.kt
@@ -0,0 +1,31 @@
+package com.stripe.android.paymentelement.confirmation.linkinline
+
+import com.stripe.android.link.LinkConfigurationCoordinator
+import com.stripe.android.link.account.LinkStore
+import com.stripe.android.link.injection.LinkAnalyticsComponent
+import com.stripe.android.paymentelement.confirmation.ConfirmationDefinition
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+
+@Module(
+ subcomponents = [
+ LinkAnalyticsComponent::class,
+ ]
+)
+internal object LinkInlineSignupConfirmationModule {
+ @JvmSuppressWildcards
+ @Provides
+ @IntoSet
+ fun providesLinkConfirmationDefinition(
+ linkStore: LinkStore,
+ linkConfigurationCoordinator: LinkConfigurationCoordinator,
+ linkAnalyticsComponentBuilder: LinkAnalyticsComponent.Builder,
+ ): ConfirmationDefinition<*, *, *, *> {
+ return LinkInlineSignupConfirmationDefinition(
+ linkStore = linkStore,
+ linkConfigurationCoordinator = linkConfigurationCoordinator,
+ linkAnalyticsHelper = linkAnalyticsComponentBuilder.build().linkAnalyticsHelper,
+ )
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationOption.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationOption.kt
new file mode 100644
index 00000000000..15c4088b765
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationOption.kt
@@ -0,0 +1,24 @@
+package com.stripe.android.paymentelement.confirmation.linkinline
+
+import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.ui.inline.UserInput
+import com.stripe.android.model.ConfirmPaymentIntentParams
+import com.stripe.android.model.PaymentMethodCreateParams
+import com.stripe.android.model.PaymentMethodOptionsParams
+import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+internal data class LinkInlineSignupConfirmationOption(
+ val createParams: PaymentMethodCreateParams,
+ val optionsParams: PaymentMethodOptionsParams?,
+ val saveOption: PaymentMethodSaveOption,
+ val linkConfiguration: LinkConfiguration,
+ val userInput: UserInput,
+) : ConfirmationHandler.Option {
+ enum class PaymentMethodSaveOption(val setupFutureUsage: ConfirmPaymentIntentParams.SetupFutureUsage?) {
+ RequestedReuse(ConfirmPaymentIntentParams.SetupFutureUsage.OffSession),
+ RequestedNoReuse(ConfirmPaymentIntentParams.SetupFutureUsage.Blank),
+ NoRequest(null)
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/LinkHandler.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/LinkHandler.kt
index 03ed29f3fc2..7b8defc2840 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/LinkHandler.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/LinkHandler.kt
@@ -1,64 +1,25 @@
package com.stripe.android.paymentsheet
-import androidx.lifecycle.SavedStateHandle
-import com.stripe.android.core.strings.resolvableString
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.LinkConfigurationCoordinator
-import com.stripe.android.link.LinkPaymentDetails
-import com.stripe.android.link.account.LinkStore
-import com.stripe.android.link.analytics.LinkAnalyticsHelper
-import com.stripe.android.link.injection.LinkAnalyticsComponent
-import com.stripe.android.link.model.AccountStatus
-import com.stripe.android.link.ui.inline.UserInput
-import com.stripe.android.model.ConfirmPaymentIntentParams
-import com.stripe.android.model.PaymentMethod
-import com.stripe.android.model.PaymentMethodCreateParams
-import com.stripe.android.model.PaymentMethodOptionsParams
-import com.stripe.android.model.wallets.Wallet
-import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.state.LinkState
-import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel.Companion.SAVE_PROCESSING
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
internal class LinkHandler @Inject constructor(
val linkConfigurationCoordinator: LinkConfigurationCoordinator,
- private val savedStateHandle: SavedStateHandle,
- private val linkStore: LinkStore,
- linkAnalyticsComponentBuilder: LinkAnalyticsComponent.Builder,
) {
- sealed class ProcessingState {
- data object Ready : ProcessingState()
-
- data object Started : ProcessingState()
-
- data class PaymentDetailsCollected(
- val paymentSelection: PaymentSelection
- ) : ProcessingState()
- }
-
- private val _processingState =
- MutableSharedFlow(replay = 1, extraBufferCapacity = 5)
- val processingState: Flow = _processingState
-
private val _isLinkEnabled = MutableStateFlow(null)
val isLinkEnabled: StateFlow = _isLinkEnabled
private val _linkConfiguration = MutableStateFlow(null)
val linkConfiguration: StateFlow = _linkConfiguration.asStateFlow()
- private val linkAnalyticsHelper: LinkAnalyticsHelper by lazy {
- linkAnalyticsComponentBuilder.build().linkAnalyticsHelper
- }
-
fun setupLink(state: LinkState?) {
_isLinkEnabled.value = state != null
@@ -67,148 +28,6 @@ internal class LinkHandler @Inject constructor(
_linkConfiguration.value = state.configuration
}
- suspend fun payWithLinkInline(
- paymentSelection: PaymentSelection.New.LinkInline,
- shouldCompleteLinkInlineFlow: Boolean,
- ) {
- savedStateHandle[SAVE_PROCESSING] = true
- _processingState.emit(ProcessingState.Started)
-
- val configuration = requireNotNull(_linkConfiguration.value)
-
- when (linkConfigurationCoordinator.getAccountStatusFlow(configuration).first()) {
- AccountStatus.Verified -> {
- completeLinkInlinePayment(
- paymentSelection,
- configuration,
- paymentSelection.input is UserInput.SignIn && shouldCompleteLinkInlineFlow
- )
- }
- AccountStatus.VerificationStarted,
- AccountStatus.NeedsVerification -> {
- linkAnalyticsHelper.onLinkPopupSkipped()
- _processingState.emit(ProcessingState.PaymentDetailsCollected(paymentSelection.toNewSelection()))
- }
- AccountStatus.SignedOut,
- AccountStatus.Error -> {
- linkConfigurationCoordinator.signInWithUserInput(configuration, paymentSelection.input).fold(
- onSuccess = {
- // If successful, the account was fetched or created, so try again
- payWithLinkInline(
- paymentSelection = paymentSelection,
- shouldCompleteLinkInlineFlow = shouldCompleteLinkInlineFlow,
- )
- },
- onFailure = {
- _processingState.emit(
- ProcessingState.PaymentDetailsCollected(paymentSelection.toNewSelection())
- )
- }
- )
- }
- }
- }
-
- private suspend fun completeLinkInlinePayment(
- paymentSelection: PaymentSelection.New.LinkInline,
- configuration: LinkConfiguration,
- shouldCompleteLinkInlineFlow: Boolean
- ) {
- val paymentMethodCreateParams = paymentSelection.paymentMethodCreateParams
- val customerRequestedSave = paymentSelection.customerRequestedSave
-
- if (shouldCompleteLinkInlineFlow) {
- linkAnalyticsHelper.onLinkPopupSkipped()
- _processingState.emit(ProcessingState.PaymentDetailsCollected(paymentSelection.toNewSelection()))
- } else {
- val linkPaymentDetails = linkConfigurationCoordinator.attachNewCardToAccount(
- configuration,
- paymentMethodCreateParams
- ).getOrNull()
-
- val nextSelection = when (linkPaymentDetails) {
- is LinkPaymentDetails.New -> createGenericSelection(
- linkPaymentDetails = linkPaymentDetails,
- customerRequestedSave = customerRequestedSave,
- )
- is LinkPaymentDetails.Saved -> createSavedSelection(
- linkPaymentDetails = linkPaymentDetails,
- paymentMethodCreateParams = paymentMethodCreateParams,
- customerRequestedSave = customerRequestedSave,
- )
- null -> null
- }
-
- if (nextSelection != null) {
- linkStore.markLinkAsUsed()
- }
-
- _processingState.emit(
- ProcessingState.PaymentDetailsCollected(
- paymentSelection = nextSelection ?: paymentSelection.toNewSelection()
- )
- )
- }
- }
-
- private fun createGenericSelection(
- linkPaymentDetails: LinkPaymentDetails.New,
- customerRequestedSave: PaymentSelection.CustomerRequestedSave,
- ): PaymentSelection.New.GenericPaymentMethod {
- return PaymentSelection.New.GenericPaymentMethod(
- paymentMethodCreateParams = linkPaymentDetails.paymentMethodCreateParams,
- paymentMethodOptionsParams = PaymentMethodOptionsParams.Card(
- setupFutureUsage = customerRequestedSave.setupFutureUsage
- ),
- paymentMethodExtraParams = null,
- customerRequestedSave = customerRequestedSave,
- label = resolvableString("···· ${linkPaymentDetails.paymentDetails.last4}"),
- iconResource = R.drawable.stripe_ic_paymentsheet_link,
- lightThemeIconUrl = null,
- darkThemeIconUrl = null,
- createdFromLink = true,
- )
- }
-
- private fun createSavedSelection(
- linkPaymentDetails: LinkPaymentDetails.Saved,
- paymentMethodCreateParams: PaymentMethodCreateParams,
- customerRequestedSave: PaymentSelection.CustomerRequestedSave,
- ): PaymentSelection.Saved {
- val last4 = linkPaymentDetails.paymentDetails.last4
-
- return PaymentSelection.Saved(
- paymentMethod = PaymentMethod.Builder()
- .setId(linkPaymentDetails.paymentDetails.id)
- .setCode(paymentMethodCreateParams.typeCode)
- .setCard(
- PaymentMethod.Card(
- last4 = last4,
- wallet = Wallet.LinkWallet(last4)
- )
- )
- .setType(PaymentMethod.Type.Card)
- .build(),
- walletType = PaymentSelection.Saved.WalletType.Link,
- paymentMethodOptionsParams = PaymentMethodOptionsParams.Card(
- setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession?.takeIf {
- customerRequestedSave ==
- PaymentSelection.CustomerRequestedSave.RequestReuse
- } ?: ConfirmPaymentIntentParams.SetupFutureUsage.Blank
- )
- )
- }
-
- private fun PaymentSelection.New.LinkInline.toNewSelection(): PaymentSelection.New.Card {
- return PaymentSelection.New.Card(
- paymentMethodCreateParams = paymentMethodCreateParams,
- brand = brand,
- customerRequestedSave = customerRequestedSave,
- paymentMethodOptionsParams = paymentMethodOptionsParams,
- paymentMethodExtraParams = paymentMethodExtraParams
- )
- }
-
@OptIn(DelicateCoroutinesApi::class)
fun logOut() {
val configuration = linkConfiguration.value ?: return
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionsViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionsViewModel.kt
index ac5e3ccadcf..56a761e2d53 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionsViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionsViewModel.kt
@@ -27,7 +27,6 @@ import com.stripe.android.paymentsheet.state.WalletsProcessingState
import com.stripe.android.paymentsheet.state.WalletsState
import com.stripe.android.paymentsheet.ui.DefaultAddPaymentMethodInteractor
import com.stripe.android.paymentsheet.ui.DefaultSelectSavedPaymentMethodsInteractor
-import com.stripe.android.paymentsheet.ui.PrimaryButton
import com.stripe.android.paymentsheet.verticalmode.VerticalModeInitialScreenFactory
import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel
import com.stripe.android.paymentsheet.viewmodels.PrimaryButtonUiStateMapper
@@ -40,7 +39,6 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
@@ -130,12 +128,6 @@ internal class PaymentOptionsViewModel @Inject constructor(
init {
SessionSavedStateHandler.attachTo(this, savedStateHandle)
- viewModelScope.launch {
- linkHandler.processingState.collect { processingState ->
- handleLinkProcessingState(processingState)
- }
- }
-
// This is bad, but I don't think there's a better option
PaymentSheet.FlowController.linkHandler = linkHandler
@@ -160,21 +152,6 @@ internal class PaymentOptionsViewModel @Inject constructor(
)
}
- private fun handleLinkProcessingState(processingState: LinkHandler.ProcessingState) {
- when (processingState) {
- is LinkHandler.ProcessingState.PaymentDetailsCollected -> {
- updateSelection(processingState.paymentSelection)
- onUserSelection()
- }
- LinkHandler.ProcessingState.Ready -> {
- updatePrimaryButtonState(PrimaryButton.State.Ready)
- }
- LinkHandler.ProcessingState.Started -> {
- updatePrimaryButtonState(PrimaryButton.State.StartProcessing)
- }
- }
- }
-
override fun onUserCancel() {
eventReporter.onDismiss()
_paymentOptionResult.tryEmit(
@@ -214,14 +191,6 @@ internal class PaymentOptionsViewModel @Inject constructor(
eventReporter.onSelectPaymentOption(paymentSelection)
when (paymentSelection) {
- is PaymentSelection.New.LinkInline -> {
- viewModelScope.launch(workContext) {
- linkHandler.payWithLinkInline(
- paymentSelection = paymentSelection,
- shouldCompleteLinkInlineFlow = false,
- )
- }
- }
is PaymentSelection.Saved,
is PaymentSelection.GooglePay,
is PaymentSelection.Link -> processExistingPaymentMethod(paymentSelection)
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt
index 3daf2325c62..fd0a8b97865 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt
@@ -215,12 +215,6 @@ internal class PaymentSheetViewModel @Inject internal constructor(
init {
SessionSavedStateHandler.attachTo(this, savedStateHandle)
- viewModelScope.launch {
- linkHandler.processingState.collect { processingState ->
- handleLinkProcessingState(processingState)
- }
- }
-
val isDeferred = args.initializationMode is PaymentElementLoader.InitializationMode.DeferredIntent
eventReporter.onInit(
@@ -233,23 +227,6 @@ internal class PaymentSheetViewModel @Inject internal constructor(
}
}
- private fun handleLinkProcessingState(processingState: LinkHandler.ProcessingState) {
- when (processingState) {
- is LinkHandler.ProcessingState.PaymentDetailsCollected -> {
- updateSelection(processingState.paymentSelection)
- checkout(selection.value, CheckoutIdentifier.SheetBottomBuy)
- }
- LinkHandler.ProcessingState.Ready -> {
- this.checkoutIdentifier = CheckoutIdentifier.SheetBottomBuy
- viewState.value = PaymentSheetViewState.Reset()
- }
- LinkHandler.ProcessingState.Started -> {
- this.checkoutIdentifier = CheckoutIdentifier.SheetBottomBuy
- viewState.value = PaymentSheetViewState.StartProcessing
- }
- }
- }
-
private suspend fun loadPaymentSheetState() {
val result = withContext(workContext) {
paymentElementLoader.load(
@@ -372,16 +349,7 @@ internal class PaymentSheetViewModel @Inject internal constructor(
) {
this.checkoutIdentifier = identifier
- if (paymentSelection is PaymentSelection.New.LinkInline) {
- viewModelScope.launch(workContext) {
- linkHandler.payWithLinkInline(
- paymentSelection = paymentSelection,
- shouldCompleteLinkInlineFlow = false,
- )
- }
- } else {
- confirmPaymentSelection(paymentSelection)
- }
+ confirmPaymentSelection(paymentSelection)
}
override fun handlePaymentMethodSelected(selection: PaymentSelection?) {
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/analytics/PaymentSheetEvent.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/analytics/PaymentSheetEvent.kt
index 254ee44f4e8..9952626bd52 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/analytics/PaymentSheetEvent.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/analytics/PaymentSheetEvent.kt
@@ -477,16 +477,7 @@ internal sealed class PaymentSheetEvent : AnalyticsEvent {
is PaymentSelection.Link,
is PaymentSelection.New.LinkInline -> "link"
is PaymentSelection.ExternalPaymentMethod,
- is PaymentSelection.New -> {
- if (
- paymentSelection is PaymentSelection.New.GenericPaymentMethod &&
- paymentSelection.createdFromLink
- ) {
- "link"
- } else {
- "newpm"
- }
- }
+ is PaymentSelection.New -> "newpm"
null -> "unknown"
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/model/PaymentSelection.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/model/PaymentSelection.kt
index b12163fe741..212a8364edf 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/model/PaymentSelection.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/model/PaymentSelection.kt
@@ -236,7 +236,6 @@ internal sealed class PaymentSelection : Parcelable {
override val customerRequestedSave: CustomerRequestedSave,
override val paymentMethodOptionsParams: PaymentMethodOptionsParams? = null,
override val paymentMethodExtraParams: PaymentMethodExtraParams? = null,
- val createdFromLink: Boolean = false,
) : New()
}
@@ -294,7 +293,6 @@ internal val PaymentSelection.isLink: Boolean
is PaymentSelection.GooglePay -> false
is PaymentSelection.Link -> true
is PaymentSelection.New.LinkInline -> true
- is PaymentSelection.New.GenericPaymentMethod -> createdFromLink
is PaymentSelection.New -> false
is PaymentSelection.Saved -> walletType == PaymentSelection.Saved.WalletType.Link
is PaymentSelection.ExternalPaymentMethod -> false
diff --git a/paymentsheet/src/test/java/com/stripe/android/customersheet/utils/CustomerSheetTestHelper.kt b/paymentsheet/src/test/java/com/stripe/android/customersheet/utils/CustomerSheetTestHelper.kt
index e79ca54fd44..ff6b760fd60 100644
--- a/paymentsheet/src/test/java/com/stripe/android/customersheet/utils/CustomerSheetTestHelper.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/customersheet/utils/CustomerSheetTestHelper.kt
@@ -44,6 +44,7 @@ import com.stripe.android.ui.core.cbc.CardBrandChoiceEligibility
import com.stripe.android.utils.CompletableSingle
import com.stripe.android.utils.DummyActivityResultCaller
import com.stripe.android.utils.FakeIntentConfirmationInterceptor
+import com.stripe.android.utils.FakeLinkConfigurationCoordinator
import com.stripe.android.utils.RecordingLinkPaymentLauncher
import kotlinx.coroutines.CoroutineScope
import org.mockito.kotlin.mock
@@ -141,6 +142,7 @@ internal object CustomerSheetTestHelper {
savedStateHandle = SavedStateHandle(),
errorReporter = FakeErrorReporter(),
linkLauncher = RecordingLinkPaymentLauncher.noOp(),
+ linkConfigurationCoordinator = FakeLinkConfigurationCoordinator(),
cvcRecollectionLauncherFactory = RecordingCvcRecollectionLauncherFactory.noOp(),
),
eventReporter = eventReporter,
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationHandlerOptionKtxTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationHandlerOptionKtxTest.kt
index bf241624a9a..0c170e89e84 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationHandlerOptionKtxTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationHandlerOptionKtxTest.kt
@@ -3,7 +3,9 @@ package com.stripe.android.paymentelement.confirmation
import com.google.common.truth.Truth.assertThat
import com.stripe.android.common.model.asCommonConfiguration
import com.stripe.android.core.strings.resolvableString
-import com.stripe.android.link.TestFactory
+import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.ui.inline.SignUpConsentAction
+import com.stripe.android.link.ui.inline.UserInput
import com.stripe.android.lpmfoundations.paymentmethod.PaymentSheetCardBrandFilter
import com.stripe.android.model.Address
import com.stripe.android.model.CardBrand
@@ -17,11 +19,12 @@ import com.stripe.android.paymentelement.confirmation.bacs.BacsConfirmationOptio
import com.stripe.android.paymentelement.confirmation.epms.ExternalPaymentMethodConfirmationOption
import com.stripe.android.paymentelement.confirmation.gpay.GooglePayConfirmationOption
import com.stripe.android.paymentelement.confirmation.link.LinkConfirmationOption
+import com.stripe.android.paymentelement.confirmation.linkinline.LinkInlineSignupConfirmationOption
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetFixtures
import com.stripe.android.paymentsheet.R
import com.stripe.android.paymentsheet.model.PaymentSelection
-import com.stripe.android.paymentsheet.state.PaymentElementLoader
+import com.stripe.android.testing.PaymentIntentFactory
import com.stripe.android.testing.PaymentMethodFactory
import com.stripe.android.utils.BankFormScreenStateFactory
import org.junit.Test
@@ -262,6 +265,50 @@ class ConfirmationHandlerOptionKtxTest {
)
}
+ @Test
+ fun `On new Link inline selection without config, should return null`() {
+ assertThat(
+ PaymentSelection.New.LinkInline(
+ paymentMethodCreateParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
+ brand = CardBrand.Visa,
+ customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest,
+ paymentMethodOptionsParams = null,
+ paymentMethodExtraParams = null,
+ input = UserInput.SignUp(
+ email = "email@email.com",
+ phone = "1234567890",
+ name = "John Doe",
+ country = "CA",
+ consentAction = SignUpConsentAction.Checkbox,
+ ),
+ ).toConfirmationOption(
+ configuration = PaymentSheetFixtures.CONFIG_CUSTOMER.asCommonConfiguration(),
+ linkConfiguration = null,
+ )
+ ).isNull()
+ }
+
+ @Test
+ fun `On new Link inline selection with no reuse request, should return expected confirmation`() =
+ testLinkInlineSignupConfirmationOption(
+ customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest,
+ expectedSaveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.NoRequest
+ )
+
+ @Test
+ fun `On new Link inline selection with requested reuse, should return expected confirmation`() =
+ testLinkInlineSignupConfirmationOption(
+ customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestReuse,
+ expectedSaveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedReuse
+ )
+
+ @Test
+ fun `On new Link inline selection with requested no reuse, should return expected confirmation`() =
+ testLinkInlineSignupConfirmationOption(
+ customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestNoReuse,
+ expectedSaveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedNoReuse,
+ )
+
@Test
fun `Converts Instant Debits into a saved payment confirmation option`() {
val paymentSelection = createNewBankAccountPaymentSelection(linkMode = LinkMode.LinkPaymentMethod)
@@ -300,6 +347,42 @@ class ConfirmationHandlerOptionKtxTest {
)
}
+ private fun testLinkInlineSignupConfirmationOption(
+ customerRequestedSave: PaymentSelection.CustomerRequestedSave,
+ expectedSaveOption: LinkInlineSignupConfirmationOption.PaymentMethodSaveOption,
+ ) {
+ val paymentMethodCreateParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD
+ val userInput = UserInput.SignUp(
+ email = "email@email.com",
+ phone = "1234567890",
+ name = "John Doe",
+ country = "CA",
+ consentAction = SignUpConsentAction.Checkbox,
+ )
+
+ assertThat(
+ PaymentSelection.New.LinkInline(
+ paymentMethodCreateParams = paymentMethodCreateParams,
+ brand = CardBrand.Visa,
+ customerRequestedSave = customerRequestedSave,
+ paymentMethodOptionsParams = null,
+ paymentMethodExtraParams = null,
+ input = userInput,
+ ).toConfirmationOption(
+ configuration = PaymentSheetFixtures.CONFIG_CUSTOMER.asCommonConfiguration(),
+ linkConfiguration = LINK_CONFIGURATION,
+ )
+ ).isEqualTo(
+ LinkInlineSignupConfirmationOption(
+ createParams = paymentMethodCreateParams,
+ optionsParams = null,
+ linkConfiguration = LINK_CONFIGURATION,
+ saveOption = expectedSaveOption,
+ userInput = userInput,
+ )
+ )
+ }
+
private fun createNewPaymentSelection(
createParams: PaymentMethodCreateParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
optionsParams: PaymentMethodOptionsParams? = null,
@@ -341,14 +424,21 @@ class ConfirmationHandlerOptionKtxTest {
}
private companion object {
- val PI_INITIALIZATION_MODE = PaymentElementLoader.InitializationMode.PaymentIntent(
- clientSecret = "pi_123"
- )
-
- val SI_INITIALIZATION_MODE = PaymentElementLoader.InitializationMode.SetupIntent(
- clientSecret = "pi_123"
+ val LINK_CONFIGURATION = LinkConfiguration(
+ stripeIntent = PaymentIntentFactory.create(),
+ merchantName = "Merchant, Inc.",
+ merchantCountryCode = "CA",
+ customerInfo = LinkConfiguration.CustomerInfo(
+ name = "John Doe",
+ email = null,
+ phone = null,
+ billingCountryCode = "CA",
+ ),
+ shippingDetails = null,
+ passthroughModeEnabled = false,
+ cardBrandChoice = null,
+ flags = mapOf(),
+ useAttestationEndpointsForLink = false,
)
-
- val LINK_CONFIGURATION = TestFactory.LINK_CONFIGURATION
}
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationTestUtils.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationTestUtils.kt
index 19c02dc7728..698c9700475 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationTestUtils.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationTestUtils.kt
@@ -110,6 +110,10 @@ internal fun ConfirmationHandler.Option.asSaved(): PaymentMethodConfirmationOpti
return this as PaymentMethodConfirmationOption.Saved
}
+internal fun ConfirmationHandler.Option.asNew(): PaymentMethodConfirmationOption.New {
+ return this as PaymentMethodConfirmationOption.New
+}
+
internal fun ConfirmationDefinition.Result?.asSucceeded(): ConfirmationDefinition.Result.Succeeded {
return this as ConfirmationDefinition.Result.Succeeded
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationUtils.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationUtils.kt
index f71b5a17918..54b2fdefaeb 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationUtils.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ConfirmationUtils.kt
@@ -3,7 +3,9 @@ package com.stripe.android.paymentelement.confirmation
import androidx.lifecycle.SavedStateHandle
import com.stripe.android.PaymentConfiguration
import com.stripe.android.googlepaylauncher.injection.GooglePayPaymentMethodLauncherFactory
+import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.link.LinkPaymentLauncher
+import com.stripe.android.link.analytics.FakeLinkAnalyticsHelper
import com.stripe.android.paymentelement.confirmation.bacs.BacsConfirmationDefinition
import com.stripe.android.paymentelement.confirmation.cvc.CvcRecollectionConfirmationDefinition
import com.stripe.android.paymentelement.confirmation.epms.ExternalPaymentMethodConfirmationDefinition
@@ -11,6 +13,7 @@ import com.stripe.android.paymentelement.confirmation.gpay.GooglePayConfirmation
import com.stripe.android.paymentelement.confirmation.intent.IntentConfirmationDefinition
import com.stripe.android.paymentelement.confirmation.intent.IntentConfirmationInterceptor
import com.stripe.android.paymentelement.confirmation.link.LinkConfirmationDefinition
+import com.stripe.android.paymentelement.confirmation.linkinline.LinkInlineSignupConfirmationDefinition
import com.stripe.android.payments.core.analytics.ErrorReporter
import com.stripe.android.payments.paymentlauncher.StripePaymentLauncherAssistedFactory
import com.stripe.android.paymentsheet.ExternalPaymentMethodInterceptor
@@ -28,6 +31,7 @@ internal fun createTestConfirmationHandlerFactory(
stripePaymentLauncherAssistedFactory: StripePaymentLauncherAssistedFactory,
googlePayPaymentMethodLauncherFactory: GooglePayPaymentMethodLauncherFactory,
cvcRecollectionLauncherFactory: CvcRecollectionLauncherFactory,
+ linkConfigurationCoordinator: LinkConfigurationCoordinator,
linkLauncher: LinkPaymentLauncher,
paymentConfiguration: PaymentConfiguration,
statusBarColor: Int?,
@@ -65,6 +69,11 @@ internal fun createTestConfirmationHandlerFactory(
linkPaymentLauncher = linkLauncher,
linkStore = RecordingLinkStore.noOp(),
),
+ LinkInlineSignupConfirmationDefinition(
+ linkConfigurationCoordinator = linkConfigurationCoordinator,
+ linkStore = RecordingLinkStore.noOp(),
+ linkAnalyticsHelper = FakeLinkAnalyticsHelper(),
+ ),
CvcRecollectionConfirmationDefinition(
factory = cvcRecollectionLauncherFactory,
handler = CvcRecollectionHandlerImpl(),
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ExtendedPaymentElementConfirmationTestActivity.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ExtendedPaymentElementConfirmationTestActivity.kt
index c8875c1bbe3..7c26611ac9d 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ExtendedPaymentElementConfirmationTestActivity.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/ExtendedPaymentElementConfirmationTestActivity.kt
@@ -23,6 +23,7 @@ import com.stripe.android.core.utils.UserFacingLogger
import com.stripe.android.core.utils.requireApplication
import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayRepository
+import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.networking.StripeApiRepository
import com.stripe.android.networking.StripeRepository
import com.stripe.android.paymentelement.confirmation.injection.ExtendedPaymentElementConfirmationModule
@@ -34,6 +35,7 @@ import com.stripe.android.testing.FakeAnalyticsRequestExecutor
import com.stripe.android.testing.FakeErrorReporter
import com.stripe.android.testing.FakeLogger
import com.stripe.android.utils.FakeDurationProvider
+import com.stripe.android.utils.FakeLinkConfigurationCoordinator
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
@@ -159,5 +161,9 @@ internal interface ExtendedPaymentElementConfirmationTestModule {
@Provides
@Named(STRIPE_ACCOUNT_ID)
fun providesStripeAccountId(config: PaymentConfiguration): () -> String? = { config.stripeAccountId }
+
+ @Provides
+ fun providesFakeLinkConfigurationCoordinator(): LinkConfigurationCoordinator =
+ FakeLinkConfigurationCoordinator()
}
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/PaymentElementConfirmationTestActivity.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/PaymentElementConfirmationTestActivity.kt
index ea13602b514..4e0e1121ccb 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/PaymentElementConfirmationTestActivity.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/PaymentElementConfirmationTestActivity.kt
@@ -23,6 +23,7 @@ import com.stripe.android.core.utils.UserFacingLogger
import com.stripe.android.core.utils.requireApplication
import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayRepository
+import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.networking.StripeApiRepository
import com.stripe.android.networking.StripeRepository
import com.stripe.android.paymentelement.confirmation.injection.PaymentElementConfirmationModule
@@ -34,6 +35,7 @@ import com.stripe.android.testing.FakeAnalyticsRequestExecutor
import com.stripe.android.testing.FakeErrorReporter
import com.stripe.android.testing.FakeLogger
import com.stripe.android.utils.FakeDurationProvider
+import com.stripe.android.utils.FakeLinkConfigurationCoordinator
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
@@ -159,5 +161,9 @@ internal interface PaymentElementConfirmationTestModule {
@Provides
@Named(STRIPE_ACCOUNT_ID)
fun providesStripeAccountId(config: PaymentConfiguration): () -> String? = { config.stripeAccountId }
+
+ @Provides
+ fun providesFakeLinkConfigurationCoordinator(): LinkConfigurationCoordinator =
+ FakeLinkConfigurationCoordinator()
}
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationDefinitionTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationDefinitionTest.kt
new file mode 100644
index 00000000000..3eada062be4
--- /dev/null
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/linkinline/LinkInlineSignupConfirmationDefinitionTest.kt
@@ -0,0 +1,821 @@
+package com.stripe.android.paymentelement.confirmation.linkinline
+
+import app.cash.turbine.ReceiveTurbine
+import app.cash.turbine.Turbine
+import com.google.common.truth.Truth.assertThat
+import com.stripe.android.core.model.CountryCode
+import com.stripe.android.isInstanceOf
+import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.LinkConfigurationCoordinator
+import com.stripe.android.link.LinkPaymentDetails
+import com.stripe.android.link.account.LinkStore
+import com.stripe.android.link.analytics.FakeLinkAnalyticsHelper
+import com.stripe.android.link.analytics.LinkAnalyticsHelper
+import com.stripe.android.link.injection.LinkComponent
+import com.stripe.android.link.model.AccountStatus
+import com.stripe.android.link.ui.inline.SignUpConsentAction
+import com.stripe.android.link.ui.inline.UserInput
+import com.stripe.android.model.CardBrand
+import com.stripe.android.model.CardParams
+import com.stripe.android.model.ConfirmPaymentIntentParams
+import com.stripe.android.model.ConsumerPaymentDetails
+import com.stripe.android.model.ConsumerSession
+import com.stripe.android.model.CvcCheck
+import com.stripe.android.model.PaymentMethod
+import com.stripe.android.model.PaymentMethodCreateParams
+import com.stripe.android.model.PaymentMethodCreateParamsFixtures
+import com.stripe.android.model.PaymentMethodOptionsParams
+import com.stripe.android.model.wallets.Wallet
+import com.stripe.android.paymentelement.confirmation.ConfirmationDefinition
+import com.stripe.android.paymentelement.confirmation.FakeConfirmationOption
+import com.stripe.android.paymentelement.confirmation.PaymentMethodConfirmationOption
+import com.stripe.android.paymentelement.confirmation.asLaunch
+import com.stripe.android.paymentelement.confirmation.asNew
+import com.stripe.android.paymentelement.confirmation.asNextStep
+import com.stripe.android.paymentelement.confirmation.asSaved
+import com.stripe.android.paymentsheet.PaymentSheet
+import com.stripe.android.paymentsheet.addresselement.AddressDetails
+import com.stripe.android.paymentsheet.state.PaymentElementLoader
+import com.stripe.android.testing.PaymentIntentFactory
+import com.stripe.android.testing.PaymentMethodFactory
+import com.stripe.android.utils.DummyActivityResultCaller
+import com.stripe.android.utils.FakeLinkConfigurationCoordinator
+import com.stripe.android.utils.RecordingLinkStore
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.kotlin.mock
+
+internal class LinkInlineSignupConfirmationDefinitionTest {
+ @Test
+ fun `'key' should be 'LinkInlineSignup'`() {
+ val definition = createLinkInlineSignupConfirmationDefinition()
+
+ assertThat(definition.key).isEqualTo("LinkInlineSignup")
+ }
+
+ @Test
+ fun `'option' return casted 'LinkInlineSignupConfirmationOption'`() {
+ val definition = createLinkInlineSignupConfirmationDefinition()
+
+ val option = createLinkInlineSignupConfirmationOption()
+
+ assertThat(definition.option(option)).isEqualTo(option)
+ }
+
+ @Test
+ fun `'option' return null for unknown option`() {
+ val definition = createLinkInlineSignupConfirmationDefinition()
+
+ assertThat(definition.option(FakeConfirmationOption())).isNull()
+ }
+
+ @Test
+ fun `'createLauncher' should create launcher properly`() = test {
+ val definition = createLinkInlineSignupConfirmationDefinition()
+
+ val activityResultCaller = DummyActivityResultCaller.noOp()
+ val onResult: (LinkInlineSignupConfirmationDefinition.Result) -> Unit = {}
+
+ val launcher = definition.createLauncher(
+ activityResultCaller = activityResultCaller,
+ onResult = onResult,
+ )
+
+ assertThat(launcher.onResult).isEqualTo(onResult)
+ }
+
+ @Test
+ fun `'action' should skip signup if signup failed on 'SignedOut' account status`() =
+ testSkippedLinkSignupOnSignInError(
+ accountStatus = AccountStatus.SignedOut,
+ )
+
+ @Test
+ fun `'action' should skip signup if signup failed on 'Error' account status`() =
+ testSkippedLinkSignupOnSignInError(
+ accountStatus = AccountStatus.Error,
+ )
+
+ @Test
+ fun `'action' should skip signup and return 'Launch' on 'VerificationStarted' account status`() =
+ testSkippedLinkSignupOnAccountStatus(
+ accountStatus = AccountStatus.VerificationStarted,
+ )
+
+ @Test
+ fun `'action' should skip signup and return 'Launch' on 'NeedsVerification' account status`() =
+ testSkippedLinkSignupOnAccountStatus(
+ accountStatus = AccountStatus.NeedsVerification,
+ )
+
+ @Test
+ fun `'action' should return 'Launch' with new option with null SFU if no reuse request`() =
+ testSuccessfulSignupWithNewCard(
+ saveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.NoRequest,
+ expectedSetupForFutureUsage = null,
+ expectedShouldSave = false,
+ )
+
+ @Test
+ fun `'action' should return 'Launch' with new option with 'Blank' SFU if requested no reuse`() =
+ testSuccessfulSignupWithNewCard(
+ saveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedNoReuse,
+ expectedSetupForFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.Blank,
+ expectedShouldSave = false,
+ )
+
+ @Test
+ fun `'action' should return 'Launch' with new option with 'OffSession' SFU if requested reuse`() =
+ testSuccessfulSignupWithNewCard(
+ saveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedReuse,
+ expectedSetupForFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession,
+ expectedShouldSave = true,
+ )
+
+ @Test
+ fun `'action' should return 'Launch' with saved option with 'Blank' SFU if no reuse request`() =
+ testSuccessfulSignupWithSavedLinkCard(
+ saveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.NoRequest,
+ expectedSetupForFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.Blank,
+ )
+
+ @Test
+ fun `'action' should return 'Launch' with saved option with 'Blank' SFU if requested no reuse`() =
+ testSuccessfulSignupWithSavedLinkCard(
+ saveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedNoReuse,
+ expectedSetupForFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.Blank,
+ )
+
+ @Test
+ fun `'action' should return 'Launch' with saved option with 'OffSession' SFU if requested reuse`() =
+ testSuccessfulSignupWithSavedLinkCard(
+ saveOption = LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.RequestedReuse,
+ expectedSetupForFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession,
+ )
+
+ @Test
+ fun `'action' should skip & return 'Launch' if failed to attach card`() = test(
+ attachNewCardToAccountResult = Result.failure(IllegalStateException("Failed!")),
+ initialAccountStatus = AccountStatus.Verified,
+ ) {
+ val confirmationOption = createLinkInlineSignupConfirmationOption()
+
+ val action = definition.action(
+ confirmationOption = confirmationOption,
+ confirmationParameters = CONFIRMATION_PARAMETERS,
+ )
+
+ val getAccountStatusFlowCall = coordinatorScenario.getAccountStatusFlowCalls.awaitItem()
+
+ assertThat(getAccountStatusFlowCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+
+ val attachNewCardToAccountCall = coordinatorScenario.attachNewCardToAccountCalls.awaitItem()
+
+ assertThat(attachNewCardToAccountCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+ assertThat(attachNewCardToAccountCall.paymentMethodCreateParams)
+ .isEqualTo(confirmationOption.createParams)
+
+ assertThat(action).isInstanceOf>()
+
+ val launchAction = action.asLaunch()
+
+ validateSkippedLaunchAction(confirmationOption, launchAction)
+ }
+
+ @Test
+ fun `'action' should return 'Launch' after successful sign-in & attach`() = test(
+ attachNewCardToAccountResult = Result.success(
+ LinkPaymentDetails.Saved(
+ paymentDetails = ConsumerPaymentDetails.Card(
+ id = "pm_1",
+ last4 = "4242",
+ isDefault = false,
+ expiryYear = 2030,
+ expiryMonth = 4,
+ brand = CardBrand.Visa,
+ cvcCheck = CvcCheck.Pass,
+ billingAddress = null,
+ ),
+ paymentMethodCreateParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
+ )
+ ),
+ signInResult = Result.success(true),
+ initialAccountStatus = AccountStatus.SignedOut,
+ accountStatusOnSignIn = AccountStatus.Verified,
+ ) {
+ val confirmationOption = createLinkInlineSignupConfirmationOption()
+
+ val action = definition.action(
+ confirmationOption = confirmationOption,
+ confirmationParameters = CONFIRMATION_PARAMETERS,
+ )
+
+ val firstGetAccountStatusFlowCall = coordinatorScenario.getAccountStatusFlowCalls.awaitItem()
+
+ assertThat(firstGetAccountStatusFlowCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+
+ val signInCall = coordinatorScenario.signInCalls.awaitItem()
+
+ assertThat(signInCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+ assertThat(signInCall.userInput).isEqualTo(confirmationOption.userInput)
+
+ val secondGetAccountStatusFlowCall = coordinatorScenario.getAccountStatusFlowCalls.awaitItem()
+
+ assertThat(secondGetAccountStatusFlowCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+
+ val attachNewCardToAccountCall = coordinatorScenario.attachNewCardToAccountCalls.awaitItem()
+
+ assertThat(attachNewCardToAccountCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+ assertThat(attachNewCardToAccountCall.paymentMethodCreateParams)
+ .isEqualTo(confirmationOption.createParams)
+
+ assertThat(action).isInstanceOf>()
+
+ val launchAction = action.asLaunch()
+
+ val nextConfirmationOption = launchAction.launcherArguments.nextConfirmationOption
+
+ assertThat(nextConfirmationOption).isInstanceOf()
+
+ val savedConfirmationOption = nextConfirmationOption.asSaved()
+
+ assertThat(savedConfirmationOption.optionsParams).isEqualTo(
+ PaymentMethodOptionsParams.Card(
+ setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.Blank,
+ )
+ )
+
+ val paymentMethod = savedConfirmationOption.paymentMethod
+
+ assertThat(paymentMethod.id).isEqualTo("pm_1")
+ assertThat(paymentMethod.type).isEqualTo(PaymentMethod.Type.Card)
+ assertThat(paymentMethod.card?.last4).isEqualTo("4242")
+ assertThat(paymentMethod.card?.wallet).isEqualTo(Wallet.LinkWallet(dynamicLast4 = "4242"))
+
+ assertThat(launchAction.receivesResultInProcess).isTrue()
+ assertThat(launchAction.deferredIntentConfirmationType).isNull()
+
+ assertThat(storeScenario.markAsUsedCalls.awaitItem()).isNotNull()
+ }
+
+ @Test
+ fun `'launch' should immediately call 'onResult'`() = test {
+ val definition = createLinkInlineSignupConfirmationDefinition()
+ val launcher = LinkInlineSignupConfirmationDefinition.Launcher(onResultScenario.onResult)
+
+ val nextOption = PaymentMethodConfirmationOption.Saved(
+ paymentMethod = PaymentMethodFactory.card(random = true),
+ optionsParams = PaymentMethodOptionsParams.Card(
+ setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OnSession,
+ ),
+ )
+
+ definition.launch(
+ confirmationOption = createLinkInlineSignupConfirmationOption(),
+ confirmationParameters = CONFIRMATION_PARAMETERS,
+ launcher = launcher,
+ arguments = LinkInlineSignupConfirmationDefinition.LauncherArguments(
+ nextConfirmationOption = nextOption,
+ ),
+ )
+
+ val onResultCall = onResultScenario.onResultCalls.awaitItem()
+
+ assertThat(onResultCall.result.nextConfirmationOption).isEqualTo(nextOption)
+ }
+
+ @Test
+ fun `'toResult' should be 'NextStep' on result`() = test {
+ val definition = createLinkInlineSignupConfirmationDefinition(linkStore = storeScenario.linkStore)
+
+ val nextOption = PaymentMethodConfirmationOption.Saved(
+ paymentMethod = PaymentMethodFactory.card(random = true),
+ optionsParams = PaymentMethodOptionsParams.Card(
+ setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OnSession,
+ ),
+ )
+
+ val result = definition.toResult(
+ confirmationOption = createLinkInlineSignupConfirmationOption(),
+ confirmationParameters = CONFIRMATION_PARAMETERS,
+ result = LinkInlineSignupConfirmationDefinition.Result(
+ nextConfirmationOption = nextOption,
+ ),
+ deferredIntentConfirmationType = null,
+ )
+
+ assertThat(result).isInstanceOf()
+
+ val nextStepResult = result.asNextStep()
+
+ assertThat(nextStepResult.confirmationOption).isEqualTo(nextOption)
+ assertThat(nextStepResult.parameters).isEqualTo(CONFIRMATION_PARAMETERS)
+ }
+
+ private fun testSkippedLinkSignupOnSignInError(
+ accountStatus: AccountStatus
+ ) = test {
+ val userInput = UserInput.SignIn(email = "email@email.com")
+ val confirmationOption = createLinkInlineSignupConfirmationOption(
+ userInput = userInput,
+ )
+
+ actionTest(
+ attachNewCardToAccountResult = Result.failure(IllegalStateException("Should not be used!")),
+ accountStatus = accountStatus,
+ signInResult = Result.failure(IllegalStateException("Something went wrong!")),
+ confirmationOption = confirmationOption,
+ ) { launchAction ->
+ val signInCall = coordinatorScenario.signInCalls.awaitItem()
+
+ assertThat(signInCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+ assertThat(signInCall.userInput).isEqualTo(userInput)
+
+ validateSkippedLaunchAction(confirmationOption, launchAction)
+ }
+ }
+
+ private fun testSkippedLinkSignupOnAccountStatus(
+ accountStatus: AccountStatus
+ ) = test {
+ val confirmationOption = createLinkInlineSignupConfirmationOption()
+
+ actionTest(
+ attachNewCardToAccountResult = Result.failure(IllegalStateException("Should not be used!")),
+ accountStatus = accountStatus,
+ signInResult = Result.success(true),
+ confirmationOption = confirmationOption,
+ ) { launchAction ->
+ validateSkippedLaunchAction(confirmationOption, launchAction)
+
+ assertThat(analyticsScenario.onLinkPopupSkippedCalls.awaitItem()).isNotNull()
+ }
+ }
+
+ private fun testSuccessfulSignupWithNewCard(
+ saveOption: LinkInlineSignupConfirmationOption.PaymentMethodSaveOption,
+ expectedSetupForFutureUsage: ConfirmPaymentIntentParams.SetupFutureUsage?,
+ expectedShouldSave: Boolean,
+ ) {
+ val expectedCreateParams = PaymentMethodCreateParams.createCard(
+ CardParams(
+ number = "4242424242424242",
+ expMonth = 7,
+ expYear = 2025,
+ )
+ )
+
+ val confirmationOption = createLinkInlineSignupConfirmationOption(
+ saveOption = saveOption,
+ )
+
+ actionTest(
+ attachNewCardToAccountResult = Result.success(
+ LinkPaymentDetails.New(
+ paymentDetails = ConsumerPaymentDetails.Card(
+ id = "pm_1",
+ last4 = "4242",
+ isDefault = false,
+ expiryYear = 2030,
+ expiryMonth = 4,
+ brand = CardBrand.Visa,
+ cvcCheck = CvcCheck.Pass,
+ billingAddress = null,
+ ),
+ paymentMethodCreateParams = expectedCreateParams,
+ originalParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
+ )
+ ),
+ accountStatus = AccountStatus.Verified,
+ signInResult = Result.success(true),
+ confirmationOption = confirmationOption,
+ ) { launchAction ->
+ val attachNewCardToAccountCall = coordinatorScenario.attachNewCardToAccountCalls.awaitItem()
+
+ assertThat(attachNewCardToAccountCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+ assertThat(attachNewCardToAccountCall.paymentMethodCreateParams)
+ .isEqualTo(confirmationOption.createParams)
+
+ val nextConfirmationOption = launchAction.launcherArguments.nextConfirmationOption
+
+ assertThat(nextConfirmationOption).isInstanceOf()
+
+ val newConfirmationOption = nextConfirmationOption.asNew()
+
+ assertThat(newConfirmationOption.createParams).isEqualTo(expectedCreateParams)
+ assertThat(newConfirmationOption.optionsParams).isEqualTo(
+ PaymentMethodOptionsParams.Card(
+ setupFutureUsage = expectedSetupForFutureUsage,
+ )
+ )
+ assertThat(newConfirmationOption.shouldSave).isEqualTo(expectedShouldSave)
+
+ assertThat(launchAction.receivesResultInProcess).isTrue()
+ assertThat(launchAction.deferredIntentConfirmationType).isNull()
+
+ assertThat(storeScenario.markAsUsedCalls.awaitItem()).isNotNull()
+ }
+ }
+
+ private fun testSuccessfulSignupWithSavedLinkCard(
+ saveOption: LinkInlineSignupConfirmationOption.PaymentMethodSaveOption,
+ expectedSetupForFutureUsage: ConfirmPaymentIntentParams.SetupFutureUsage,
+ ) {
+ val confirmationOption = createLinkInlineSignupConfirmationOption(
+ saveOption = saveOption,
+ )
+
+ actionTest(
+ attachNewCardToAccountResult = Result.success(
+ LinkPaymentDetails.Saved(
+ paymentDetails = ConsumerPaymentDetails.Card(
+ id = "pm_1",
+ last4 = "4242",
+ isDefault = false,
+ expiryYear = 2030,
+ expiryMonth = 4,
+ brand = CardBrand.Visa,
+ cvcCheck = CvcCheck.Pass,
+ billingAddress = null,
+ ),
+ paymentMethodCreateParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
+ )
+ ),
+ signInResult = Result.success(true),
+ accountStatus = AccountStatus.Verified,
+ confirmationOption = confirmationOption,
+ ) { launchAction ->
+ val attachNewCardToAccountCall = coordinatorScenario.attachNewCardToAccountCalls.awaitItem()
+
+ assertThat(attachNewCardToAccountCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+ assertThat(attachNewCardToAccountCall.paymentMethodCreateParams)
+ .isEqualTo(confirmationOption.createParams)
+
+ val nextConfirmationOption = launchAction.launcherArguments.nextConfirmationOption
+
+ assertThat(nextConfirmationOption).isInstanceOf()
+
+ val savedConfirmationOption = nextConfirmationOption.asSaved()
+
+ assertThat(savedConfirmationOption.optionsParams).isEqualTo(
+ PaymentMethodOptionsParams.Card(
+ setupFutureUsage = expectedSetupForFutureUsage,
+ )
+ )
+
+ val paymentMethod = savedConfirmationOption.paymentMethod
+
+ assertThat(paymentMethod.id).isEqualTo("pm_1")
+ assertThat(paymentMethod.type).isEqualTo(PaymentMethod.Type.Card)
+ assertThat(paymentMethod.card?.last4).isEqualTo("4242")
+ assertThat(paymentMethod.card?.wallet).isEqualTo(Wallet.LinkWallet(dynamicLast4 = "4242"))
+
+ assertThat(launchAction.receivesResultInProcess).isTrue()
+ assertThat(launchAction.deferredIntentConfirmationType).isNull()
+
+ assertThat(storeScenario.markAsUsedCalls.awaitItem()).isNotNull()
+ }
+ }
+
+ private fun actionTest(
+ attachNewCardToAccountResult: Result,
+ signInResult: Result,
+ accountStatus: AccountStatus,
+ confirmationOption: LinkInlineSignupConfirmationOption = createLinkInlineSignupConfirmationOption(),
+ test: suspend Scenario.(
+ action: ConfirmationDefinition.Action.Launch
+ ) -> Unit
+ ) = test(
+ attachNewCardToAccountResult = attachNewCardToAccountResult,
+ signInResult = signInResult,
+ initialAccountStatus = accountStatus,
+ accountStatusOnSignIn = AccountStatus.Verified,
+ ) {
+ val action = definition.action(
+ confirmationOption = confirmationOption,
+ confirmationParameters = CONFIRMATION_PARAMETERS,
+ )
+
+ val getAccountStatusFlowCall = coordinatorScenario.getAccountStatusFlowCalls.awaitItem()
+
+ assertThat(getAccountStatusFlowCall.configuration).isEqualTo(confirmationOption.linkConfiguration)
+
+ assertThat(action).isInstanceOf>()
+
+ test(action.asLaunch())
+ }
+
+ private fun validateSkippedLaunchAction(
+ confirmationOption: LinkInlineSignupConfirmationOption,
+ launchAction: ConfirmationDefinition.Action.Launch
+ ) {
+ val nextConfirmationOption = launchAction.launcherArguments.nextConfirmationOption
+
+ assertThat(nextConfirmationOption).isInstanceOf()
+
+ val nextNewConfirmationOption = nextConfirmationOption.asNew()
+
+ assertThat(nextNewConfirmationOption.createParams).isEqualTo(confirmationOption.createParams)
+ assertThat(nextNewConfirmationOption.optionsParams).isEqualTo(confirmationOption.optionsParams)
+
+ assertThat(launchAction.receivesResultInProcess).isTrue()
+ assertThat(launchAction.deferredIntentConfirmationType).isNull()
+ }
+
+ private fun test(
+ attachNewCardToAccountResult: Result = Result.success(
+ LinkPaymentDetails.New(
+ paymentDetails = ConsumerPaymentDetails.Card(
+ id = "pm_123",
+ last4 = "4242",
+ expiryYear = 2024,
+ expiryMonth = 4,
+ brand = CardBrand.DinersClub,
+ cvcCheck = CvcCheck.Fail,
+ isDefault = false,
+ billingAddress = ConsumerPaymentDetails.BillingAddress(
+ countryCode = CountryCode.US,
+ postalCode = "42424"
+ )
+ ),
+ paymentMethodCreateParams = mock(),
+ originalParams = mock(),
+ )
+ ),
+ signInResult: Result = Result.success(true),
+ initialAccountStatus: AccountStatus = AccountStatus.Verified,
+ accountStatusOnSignIn: AccountStatus = AccountStatus.Verified,
+ hasUsedLink: Boolean = false,
+ test: suspend Scenario.() -> Unit
+ ) = runTest {
+ RecordingLinkConfigurationCoordinator.test(
+ attachNewCardToAccountResult = attachNewCardToAccountResult,
+ signInResult = signInResult,
+ initialAccountStatus = initialAccountStatus,
+ accountStatusOnSignIn = accountStatusOnSignIn,
+ ) {
+ val coordinatorScenario = this
+
+ RecordingLinkAnalyticsHelper.test {
+ val analyticsScenario = this
+
+ RecordingOnLinkInlineResult.test {
+ val onResultScenario = this
+
+ RecordingLinkStore.test(hasUsedLink) {
+ val linkStoreScenario = this
+
+ test(
+ Scenario(
+ definition = createLinkInlineSignupConfirmationDefinition(
+ linkConfigurationCoordinator = coordinatorScenario.coordinator,
+ linkAnalyticsHelper = analyticsScenario.helper,
+ linkStore = linkStoreScenario.linkStore,
+ ),
+ coordinatorScenario = coordinatorScenario,
+ storeScenario = linkStoreScenario,
+ analyticsScenario = analyticsScenario,
+ onResultScenario = onResultScenario,
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun createLinkInlineSignupConfirmationDefinition(
+ linkConfigurationCoordinator: LinkConfigurationCoordinator = FakeLinkConfigurationCoordinator(),
+ linkAnalyticsHelper: LinkAnalyticsHelper = FakeLinkAnalyticsHelper(),
+ linkStore: LinkStore = RecordingLinkStore.noOp(),
+ ): LinkInlineSignupConfirmationDefinition {
+ return LinkInlineSignupConfirmationDefinition(
+ linkConfigurationCoordinator = linkConfigurationCoordinator,
+ linkAnalyticsHelper = linkAnalyticsHelper,
+ linkStore = linkStore,
+ )
+ }
+
+ private fun createLinkInlineSignupConfirmationOption(
+ createParams: PaymentMethodCreateParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
+ saveOption: LinkInlineSignupConfirmationOption.PaymentMethodSaveOption =
+ LinkInlineSignupConfirmationOption.PaymentMethodSaveOption.NoRequest,
+ userInput: UserInput = UserInput.SignUp(
+ email = "email@email.com",
+ phone = "1234567890",
+ country = "CA",
+ name = "John Doe",
+ consentAction = SignUpConsentAction.Checkbox,
+ )
+ ): LinkInlineSignupConfirmationOption {
+ return LinkInlineSignupConfirmationOption(
+ createParams = createParams,
+ optionsParams = null,
+ saveOption = saveOption,
+ linkConfiguration = LinkConfiguration(
+ stripeIntent = PaymentIntentFactory.create(),
+ merchantName = "Merchant Inc.",
+ merchantCountryCode = "CA",
+ customerInfo = LinkConfiguration.CustomerInfo(
+ name = "Jphn Doe",
+ email = "johndoe@email.com",
+ phone = "+1123456789",
+ billingCountryCode = "CA"
+ ),
+ shippingDetails = null,
+ passthroughModeEnabled = false,
+ flags = mapOf(),
+ cardBrandChoice = null,
+ useAttestationEndpointsForLink = false,
+ ),
+ userInput = userInput,
+ )
+ }
+
+ private class Scenario(
+ val definition: LinkInlineSignupConfirmationDefinition,
+ val coordinatorScenario: RecordingLinkConfigurationCoordinator.Scenario,
+ val storeScenario: RecordingLinkStore.Scenario,
+ val analyticsScenario: RecordingLinkAnalyticsHelper.Scenario,
+ val onResultScenario: RecordingOnLinkInlineResult.Scenario,
+ )
+
+ private class RecordingOnLinkInlineResult private constructor() {
+ data class OnResultCall(
+ val result: LinkInlineSignupConfirmationDefinition.Result,
+ )
+
+ class Scenario(
+ val onResult: (LinkInlineSignupConfirmationDefinition.Result) -> Unit,
+ val onResultCalls: ReceiveTurbine,
+ )
+
+ companion object {
+ suspend fun test(
+ test: suspend Scenario.() -> Unit
+ ) {
+ val onResultCalls = Turbine()
+
+ test(
+ Scenario(
+ onResult = {
+ onResultCalls.add(OnResultCall(it))
+ },
+ onResultCalls = onResultCalls,
+ )
+ )
+
+ onResultCalls.ensureAllEventsConsumed()
+ }
+ }
+ }
+
+ private class RecordingLinkAnalyticsHelper private constructor() : FakeLinkAnalyticsHelper() {
+ private val onLinkPopupSkippedCalls = Turbine()
+
+ override fun onLinkPopupSkipped() {
+ onLinkPopupSkippedCalls.add(Unit)
+ }
+
+ class Scenario(
+ val helper: LinkAnalyticsHelper,
+ val onLinkPopupSkippedCalls: ReceiveTurbine,
+ )
+
+ companion object {
+ suspend fun test(
+ test: suspend Scenario.() -> Unit
+ ) {
+ val helper = RecordingLinkAnalyticsHelper()
+
+ test(
+ Scenario(
+ helper = helper,
+ onLinkPopupSkippedCalls = helper.onLinkPopupSkippedCalls,
+ )
+ )
+
+ helper.onLinkPopupSkippedCalls.ensureAllEventsConsumed()
+ }
+ }
+ }
+
+ private class RecordingLinkConfigurationCoordinator private constructor(
+ private val attachNewCardToAccountResult: Result,
+ private val signInResult: Result,
+ initialAccountStatus: AccountStatus,
+ private val accountStatusOnSignIn: AccountStatus,
+ ) : LinkConfigurationCoordinator {
+ private val getAccountStatusFlowCalls = Turbine()
+ private val signInCalls = Turbine()
+ private val attachNewCardToAccountCalls = Turbine()
+
+ private val accountStatusFlow = MutableStateFlow(initialAccountStatus)
+
+ override val emailFlow: StateFlow
+ get() {
+ throw NotImplementedError()
+ }
+
+ override fun getComponent(configuration: LinkConfiguration): LinkComponent {
+ throw NotImplementedError()
+ }
+
+ override fun getAccountStatusFlow(configuration: LinkConfiguration): Flow {
+ getAccountStatusFlowCalls.add(GetAccountStatusFlowCall(configuration))
+
+ return accountStatusFlow
+ }
+
+ override suspend fun signInWithUserInput(
+ configuration: LinkConfiguration,
+ userInput: UserInput,
+ ): Result {
+ signInCalls.add(SignInCall(configuration, userInput))
+
+ accountStatusFlow.value = accountStatusOnSignIn
+
+ return signInResult
+ }
+
+ override suspend fun attachNewCardToAccount(
+ configuration: LinkConfiguration,
+ paymentMethodCreateParams: PaymentMethodCreateParams,
+ ): Result {
+ attachNewCardToAccountCalls.add(AttachNewCardToAccountCall(configuration, paymentMethodCreateParams))
+
+ return attachNewCardToAccountResult
+ }
+
+ override suspend fun logOut(configuration: LinkConfiguration): Result {
+ throw NotImplementedError()
+ }
+
+ data class GetAccountStatusFlowCall(
+ val configuration: LinkConfiguration
+ )
+
+ data class SignInCall(
+ val configuration: LinkConfiguration,
+ val userInput: UserInput,
+ )
+
+ data class AttachNewCardToAccountCall(
+ val configuration: LinkConfiguration,
+ val paymentMethodCreateParams: PaymentMethodCreateParams,
+ )
+
+ class Scenario(
+ val coordinator: LinkConfigurationCoordinator,
+ val getAccountStatusFlowCalls: ReceiveTurbine,
+ val signInCalls: ReceiveTurbine,
+ val attachNewCardToAccountCalls: ReceiveTurbine,
+ )
+
+ companion object {
+ suspend fun test(
+ attachNewCardToAccountResult: Result,
+ signInResult: Result,
+ initialAccountStatus: AccountStatus,
+ accountStatusOnSignIn: AccountStatus,
+ test: suspend Scenario.() -> Unit,
+ ) {
+ val coordinator = RecordingLinkConfigurationCoordinator(
+ attachNewCardToAccountResult = attachNewCardToAccountResult,
+ signInResult = signInResult,
+ initialAccountStatus = initialAccountStatus,
+ accountStatusOnSignIn = accountStatusOnSignIn,
+ )
+
+ test(
+ Scenario(
+ coordinator = coordinator,
+ getAccountStatusFlowCalls = coordinator.getAccountStatusFlowCalls,
+ signInCalls = coordinator.signInCalls,
+ attachNewCardToAccountCalls = coordinator.attachNewCardToAccountCalls
+ )
+ )
+
+ coordinator.getAccountStatusFlowCalls.ensureAllEventsConsumed()
+ coordinator.signInCalls.ensureAllEventsConsumed()
+ coordinator.attachNewCardToAccountCalls.ensureAllEventsConsumed()
+ }
+ }
+ }
+
+ private companion object {
+ private val PAYMENT_INTENT = PaymentIntentFactory.create()
+
+ private val CONFIRMATION_PARAMETERS = ConfirmationDefinition.Parameters(
+ initializationMode = PaymentElementLoader.InitializationMode.PaymentIntent(
+ clientSecret = "pi_123_secret_123",
+ ),
+ intent = PAYMENT_INTENT,
+ appearance = PaymentSheet.Appearance(),
+ shippingDetails = AddressDetails(),
+ )
+ }
+}
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 ab95c0f8497..14ce5887034 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/FormHelperTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/FormHelperTest.kt
@@ -313,7 +313,6 @@ internal class FormHelperTest {
iconResource = R.drawable.stripe_ic_paymentsheet_pm_bancontact,
lightThemeIconUrl = null,
darkThemeIconUrl = null,
- createdFromLink = false,
)
)
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/LinkHandlerTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/LinkHandlerTest.kt
index 18abaf6268c..5bb531d9724 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/LinkHandlerTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/LinkHandlerTest.kt
@@ -1,38 +1,22 @@
package com.stripe.android.paymentsheet
import androidx.lifecycle.SavedStateHandle
-import app.cash.turbine.ReceiveTurbine
-import app.cash.turbine.test
import app.cash.turbine.turbineScope
import com.google.common.truth.Truth.assertThat
-import com.stripe.android.core.model.CountryCode
-import com.stripe.android.isInstanceOf
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.link.LinkPaymentDetails
import com.stripe.android.link.TestFactory
import com.stripe.android.link.account.LinkStore
import com.stripe.android.link.analytics.LinkAnalyticsHelper
-import com.stripe.android.link.injection.LinkAnalyticsComponent
import com.stripe.android.link.model.AccountStatus
import com.stripe.android.link.ui.inline.LinkSignupMode
-import com.stripe.android.link.ui.inline.SignUpConsentAction
-import com.stripe.android.link.ui.inline.UserInput
-import com.stripe.android.model.CardBrand
-import com.stripe.android.model.ConfirmPaymentIntentParams
-import com.stripe.android.model.ConsumerPaymentDetails
-import com.stripe.android.model.CvcCheck
-import com.stripe.android.model.PaymentMethod
-import com.stripe.android.model.PaymentMethodCreateParams
-import com.stripe.android.model.PaymentMethodOptionsParams
-import com.stripe.android.model.wallets.Wallet
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.state.LinkState
import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel.Companion.SAVE_SELECTION
import com.stripe.android.testing.PaymentIntentFactory
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -40,9 +24,6 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
-import org.mockito.kotlin.stub
-import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
@@ -73,401 +54,6 @@ class LinkHandlerTest {
assertThat(handler.isLinkEnabled.first()).isTrue()
assertThat(savedStateHandle.get(SAVE_SELECTION)).isNull()
}
-
- @Test
- fun `payWithLinkInline completes successfully for existing verified user in complete flow`() = runLinkInlineTest(
- shouldCompleteLinkFlowValues = listOf(true),
- ) {
- val userInput = UserInput.SignIn("example@example.com")
-
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.LoggedIn,
- )
- )
-
- handler.processingState.test {
- accountStatusFlow.emit(AccountStatus.Verified)
- ensureAllEventsConsumed() // Begin with no events.
- testScope.launch {
- handler.payWithLinkInline(linkInlineSelection(userInput), shouldCompleteLinkFlow)
- }
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.PaymentDetailsCollected(cardSelection()))
- verify(linkAnalyticsHelper).onLinkPopupSkipped()
- verify(linkStore, never()).markLinkAsUsed()
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above.
- }
-
- @Test
- fun `payWithLinkInline completes successfully for existing verified user in custom flow`() = runLinkInlineTest(
- shouldCompleteLinkFlowValues = listOf(false),
- ) {
- val userInput = UserInput.SignIn("example@example.com")
-
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.NeedsVerification,
- )
- )
-
- handler.processingState.test {
- accountStatusFlow.emit(AccountStatus.Verified)
- ensureAllEventsConsumed() // Begin with no events.
- testScope.launch {
- handler.payWithLinkInline(linkInlineSelection(userInput), shouldCompleteLinkFlow)
- }
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
- assertThat(awaitItem()).isInstanceOf()
- verify(linkStore).markLinkAsUsed()
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above.
- }
-
- @Test
- fun `payWithLinkInline completes successfully for existing user in custom flow`() = runLinkInlineTest(
- shouldCompleteLinkFlowValues = listOf(false),
- ) {
- val userInput = UserInput.SignIn("example@example.com")
-
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.LoggedOut,
- )
- )
-
- handler.processingState.test {
- accountStatusFlow.emit(AccountStatus.NeedsVerification)
- ensureAllEventsConsumed() // Begin with no events.
- testScope.launch {
- handler.payWithLinkInline(linkInlineSelection(userInput), shouldCompleteLinkFlow)
- }
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.PaymentDetailsCollected(cardSelection()))
- verify(linkAnalyticsHelper).onLinkPopupSkipped()
- verify(linkStore, never()).markLinkAsUsed()
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above.
- }
-
- @Test
- fun `payWithLinkInline completes successfully for signedOut user in complete flow`() = runLinkInlineTest(
- MutableSharedFlow(replay = 0),
- shouldCompleteLinkFlowValues = listOf(true),
- ) {
- val userInput = UserInput.SignIn(email = "example@example.com")
-
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.LoggedOut,
- )
- )
-
- handler.processingState.test {
- ensureAllEventsConsumed() // Begin with no events.
- whenever(linkConfigurationCoordinator.signInWithUserInput(any(), any()))
- .thenReturn(Result.success(true))
- testScope.launch {
- handler.payWithLinkInline(linkInlineSelection(userInput), shouldCompleteLinkFlow)
- }
- accountStatusFlow.emit(AccountStatus.SignedOut)
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
- accountStatusFlow.emit(AccountStatus.Verified)
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.PaymentDetailsCollected(cardSelection()))
- verify(linkAnalyticsHelper).onLinkPopupSkipped()
- verify(linkStore, never()).markLinkAsUsed()
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above.
- }
-
- @Test
- fun `payWithLinkInline collects payment details`() = runLinkInlineTest(
- accountStatusFlow = MutableSharedFlow(replay = 0),
- shouldCompleteLinkFlowValues = listOf(false),
- ) {
- val userInput = UserInput.SignIn(email = "example@example.com")
-
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.LoggedOut,
- )
- )
-
- handler.processingState.test {
- ensureAllEventsConsumed() // Begin with no events.
- whenever(linkConfigurationCoordinator.signInWithUserInput(any(), any()))
- .thenReturn(Result.success(true))
- testScope.launch {
- handler.payWithLinkInline(linkInlineSelection(userInput), shouldCompleteLinkFlow)
- }
- accountStatusFlow.emit(AccountStatus.SignedOut)
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
-
- accountStatusFlow.emit(AccountStatus.Verified)
- assertThat(awaitItem()).isInstanceOf()
- verify(linkStore).markLinkAsUsed()
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above.
- }
-
- @Test
- fun `payWithLinkInline requests payment is saved if selection requested reuse`() = runLinkInlineTest(
- accountStatusFlow = MutableSharedFlow(replay = 0),
- shouldCompleteLinkFlowValues = listOf(false),
- ) {
- setupBasicLink()
-
- handler.processingState.test {
- ensureAllEventsConsumed()
-
- payWithLinkInline(
- customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestReuse
- )
-
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
- accountStatusFlow.emit(AccountStatus.Verified)
-
- val genericSelection = assertAndGetGenericSelection(awaitItem())
-
- assertThat(genericSelection.customerRequestedSave).isEqualTo(
- PaymentSelection.CustomerRequestedSave.RequestReuse
- )
-
- assertThat(genericSelection.createdFromLink).isTrue()
-
- cancelAndConsumeRemainingEvents()
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents()
- }
-
- @Test
- fun `payWithLinkInline requests payment is not saved if selection doesn't request it`() = runLinkInlineTest(
- accountStatusFlow = MutableSharedFlow(replay = 0),
- shouldCompleteLinkFlowValues = listOf(false),
- ) {
- setupBasicLink()
-
- handler.processingState.test {
- ensureAllEventsConsumed()
-
- payWithLinkInline(
- customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestNoReuse
- )
-
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
- accountStatusFlow.emit(AccountStatus.Verified)
-
- val genericSelection = assertAndGetGenericSelection(awaitItem())
-
- assertThat(genericSelection.customerRequestedSave).isEqualTo(
- PaymentSelection.CustomerRequestedSave.RequestNoReuse
- )
-
- assertThat(genericSelection.createdFromLink).isTrue()
-
- cancelAndConsumeRemainingEvents()
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents()
- }
-
- @Test
- fun `payWithLinkInline collects payment details in passthrough mode`() = runLinkInlineTest(
- accountStatusFlow = MutableSharedFlow(replay = 0),
- shouldCompleteLinkFlowValues = listOf(false),
- linkConfiguration = defaultLinkConfiguration().copy(passthroughModeEnabled = true),
- attachNewCardToAccountResult = Result.success(
- LinkPaymentDetails.Saved(
- paymentDetails = ConsumerPaymentDetails.Passthrough(
- id = "pm_123",
- last4 = "4242"
- ),
- paymentMethodCreateParams = mock(),
- )
- ),
- ) {
- val userInput = UserInput.SignIn(email = "example@example.com")
-
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.LoggedOut,
- )
- )
-
- handler.processingState.test {
- ensureAllEventsConsumed() // Begin with no events.
- whenever(linkConfigurationCoordinator.signInWithUserInput(any(), any()))
- .thenReturn(Result.success(true))
- testScope.launch {
- handler.payWithLinkInline(linkInlineSelection(userInput), shouldCompleteLinkFlow)
- }
- accountStatusFlow.emit(AccountStatus.SignedOut)
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
-
- accountStatusFlow.emit(AccountStatus.Verified)
- assertThat(awaitItem()).isEqualTo(
- LinkHandler.ProcessingState.PaymentDetailsCollected(
- paymentSelection = PaymentSelection.Saved(
- paymentMethod = PaymentMethod.Builder()
- .setId("pm_123")
- .setCode("card")
- .setCard(
- PaymentMethod.Card(
- last4 = "4242",
- wallet = Wallet.LinkWallet("4242")
- )
- )
- .setType(PaymentMethod.Type.Card)
- .build(),
- walletType = PaymentSelection.Saved.WalletType.Link,
- paymentMethodOptionsParams = PaymentMethodOptionsParams.Card(
- setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession
- ),
- ),
- )
- )
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above.
- }
-
- @Test
- fun `if lookup fails, payWithLinkInline emits new selection with details from link`() = runLinkInlineTest {
- val userInput = UserInput.SignIn(email = "example@example.com")
-
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.LoggedOut,
- )
- )
-
- handler.processingState.test {
- accountStatusFlow.emit(AccountStatus.SignedOut)
- ensureAllEventsConsumed() // Begin with no events.
- whenever(linkConfigurationCoordinator.signInWithUserInput(any(), any()))
- .thenReturn(Result.failure(IllegalStateException("Whoops")))
- testScope.launch {
- handler.payWithLinkInline(linkInlineSelection(userInput), shouldCompleteLinkFlow)
- }
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.PaymentDetailsCollected(cardSelection()))
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above.
- }
-
- @Test
- fun `if sign up fails, payWithLinkInline emits event to pay without Link`() = runLinkInlineTest {
- val userInput = UserInput.SignUp(
- name = "John Doe",
- email = "example@example.com",
- phone = "+11234567890",
- country = "US",
- consentAction = SignUpConsentAction.Checkbox,
- )
-
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.LoggedOut,
- )
- )
-
- handler.processingState.test {
- accountStatusFlow.emit(AccountStatus.SignedOut)
- ensureAllEventsConsumed() // Begin with no events.
- whenever(linkConfigurationCoordinator.signInWithUserInput(any(), any()))
- .thenReturn(Result.failure(IllegalStateException("Whoops")))
- testScope.launch {
- handler.payWithLinkInline(linkInlineSelection(userInput), shouldCompleteLinkFlow)
- }
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started)
- assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.PaymentDetailsCollected(cardSelection()))
- }
-
- processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above.
- }
-
- private suspend fun LinkInlineTestData.setupBasicLink() {
- handler.setupLink(
- state = createLinkState(
- loginState = LinkState.LoginState.LoggedIn,
- )
- )
-
- whenever(linkConfigurationCoordinator.signInWithUserInput(any(), any()))
- .thenReturn(Result.success(true))
- }
-
- private suspend fun LinkInlineTestData.payWithLinkInline(
- customerRequestedSave: PaymentSelection.CustomerRequestedSave
- ) {
- testScope.launch {
- handler.payWithLinkInline(
- linkInlineSelection(
- input = UserInput.SignIn(email = "example@example.com"),
- customerRequestedSave = customerRequestedSave
- ),
- shouldCompleteLinkFlow
- )
- }
- }
-}
-
-// Used to run through both complete flow, and custom flow for link inline tests.
-private fun runLinkInlineTest(
- accountStatusFlow: MutableSharedFlow = MutableSharedFlow(replay = 1),
- shouldCompleteLinkFlowValues: List = listOf(true, false),
- linkConfiguration: LinkConfiguration = defaultLinkConfiguration(
- linkFundingSources = listOf("card"),
- ),
- attachNewCardToAccountResult: Result = Result.success(
- LinkPaymentDetails.New(
- paymentDetails = ConsumerPaymentDetails.Card(
- id = "pm_123",
- last4 = "4242",
- expiryYear = 2024,
- expiryMonth = 4,
- brand = CardBrand.DinersClub,
- cvcCheck = CvcCheck.Fail,
- isDefault = false,
- billingAddress = ConsumerPaymentDetails.BillingAddress(
- countryCode = CountryCode.US,
- postalCode = "42424"
- )
- ),
- paymentMethodCreateParams = mock(),
- originalParams = mock(),
- )
- ),
- testBlock: suspend LinkInlineTestData.() -> Unit,
-) {
- for (shouldCompleteLinkFlowValue in shouldCompleteLinkFlowValues) {
- runLinkTest(accountStatusFlow, linkConfiguration, attachNewCardToAccountResult) {
- with(LinkInlineTestData(shouldCompleteLinkFlowValue, this)) {
- testBlock()
- }
- }
- }
-}
-
-private fun assertAndGetGenericSelection(
- processingState: LinkHandler.ProcessingState
-): PaymentSelection.New.GenericPaymentMethod {
- assertThat(processingState).isInstanceOf()
-
- val paymentDetailsCollectedState = processingState as LinkHandler.ProcessingState.PaymentDetailsCollected
- val selection = paymentDetailsCollectedState.paymentSelection
-
- assertThat(selection).isInstanceOf()
-
- return selection as PaymentSelection.New.GenericPaymentMethod
}
private fun runLinkTest(
@@ -482,20 +68,10 @@ private fun runLinkTest(
val linkStore = mock()
val handler = LinkHandler(
linkConfigurationCoordinator = linkConfigurationCoordinator,
- savedStateHandle = savedStateHandle,
- linkStore = linkStore,
- linkAnalyticsComponentBuilder = mock().stub {
- val component = object : LinkAnalyticsComponent {
- override val linkAnalyticsHelper: LinkAnalyticsHelper = linkAnalyticsHelper
- }
- whenever(it.build()).thenReturn(component)
- },
)
val testScope = this
turbineScope {
- val processingStateTurbine = handler.processingState.testIn(backgroundScope)
-
whenever(linkConfigurationCoordinator.getAccountStatusFlow(eq(linkConfiguration))).thenReturn(accountStatusFlow)
whenever(linkConfigurationCoordinator.attachNewCardToAccount(eq(linkConfiguration), any())).thenReturn(
attachNewCardToAccountResult
@@ -510,12 +86,10 @@ private fun runLinkTest(
savedStateHandle = savedStateHandle,
configuration = linkConfiguration,
accountStatusFlow = accountStatusFlow,
- processingStateTurbine = processingStateTurbine,
linkAnalyticsHelper = linkAnalyticsHelper,
)
) {
testBlock()
- processingStateTurbine.ensureAllEventsConsumed()
}
}
}
@@ -541,40 +115,6 @@ private fun defaultLinkConfiguration(
)
}
-private fun linkInlineSelection(
- input: UserInput,
- customerRequestedSave: PaymentSelection.CustomerRequestedSave =
- PaymentSelection.CustomerRequestedSave.RequestReuse,
-): PaymentSelection.New.LinkInline {
- return PaymentSelection.New.LinkInline(
- paymentMethodCreateParams = defaultCardParams(),
- brand = CardBrand.Visa,
- customerRequestedSave = customerRequestedSave,
- input = input,
- )
-}
-
-private fun cardSelection(
- customerRequestedSave: PaymentSelection.CustomerRequestedSave =
- PaymentSelection.CustomerRequestedSave.RequestReuse,
-): PaymentSelection.New.Card {
- return PaymentSelection.New.Card(
- paymentMethodCreateParams = defaultCardParams(),
- brand = CardBrand.Visa,
- customerRequestedSave = customerRequestedSave,
- )
-}
-
-private fun defaultCardParams(): PaymentMethodCreateParams {
- return PaymentMethodCreateParams.create(
- PaymentMethodCreateParams.Card(
- number = "4242424242424242",
- expiryMonth = 1,
- expiryYear = 34,
- )
- )
-}
-
private class LinkTestDataImpl(
override val testScope: TestScope,
override val handler: LinkHandler,
@@ -583,7 +123,6 @@ private class LinkTestDataImpl(
override val savedStateHandle: SavedStateHandle,
override val configuration: LinkConfiguration,
override val accountStatusFlow: MutableSharedFlow,
- override val processingStateTurbine: ReceiveTurbine,
override val linkAnalyticsHelper: LinkAnalyticsHelper,
) : LinkTestData
@@ -595,11 +134,5 @@ private interface LinkTestData {
val savedStateHandle: SavedStateHandle
val configuration: LinkConfiguration
val accountStatusFlow: MutableSharedFlow
- val processingStateTurbine: ReceiveTurbine
val linkAnalyticsHelper: LinkAnalyticsHelper
}
-
-private class LinkInlineTestData(
- val shouldCompleteLinkFlow: Boolean,
- linkTestData: LinkTestData,
-) : LinkTestData by linkTestData
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentOptionsViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentOptionsViewModelTest.kt
index 77100b32bea..e832ae7c8e3 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentOptionsViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentOptionsViewModelTest.kt
@@ -10,10 +10,10 @@ import com.stripe.android.core.strings.resolvableString
import com.stripe.android.isInstanceOf
import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.link.model.AccountStatus
+import com.stripe.android.link.ui.inline.SignUpConsentAction
import com.stripe.android.link.ui.inline.UserInput
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadataFactory
import com.stripe.android.model.CardBrand
-import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.PaymentIntentFixtures
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
@@ -21,7 +21,6 @@ import com.stripe.android.model.PaymentMethodCreateParamsFixtures
import com.stripe.android.model.PaymentMethodCreateParamsFixtures.DEFAULT_CARD
import com.stripe.android.model.PaymentMethodFixtures
import com.stripe.android.model.PaymentMethodFixtures.toDisplayableSavedPaymentMethod
-import com.stripe.android.model.PaymentMethodOptionsParams
import com.stripe.android.paymentsheet.PaymentSheetFixtures.updateState
import com.stripe.android.paymentsheet.analytics.EventReporter
import com.stripe.android.paymentsheet.model.PaymentSelection
@@ -56,7 +55,6 @@ import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import kotlin.test.Test
import com.stripe.android.R as PaymentsCoreR
-import com.stripe.android.paymentsheet.R as PaymentSheetR
@RunWith(RobolectricTestRunner::class)
internal class PaymentOptionsViewModelTest {
@@ -725,27 +723,20 @@ internal class PaymentOptionsViewModelTest {
runTest {
val viewModel = createLinkViewModel()
- viewModel.linkHandler.payWithLinkInline(
- paymentSelection = createLinkInlinePaymentSelection(
- customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestReuse,
- input = UserInput.SignIn("email@email.com"),
- ),
- shouldCompleteLinkInlineFlow = false
- )
-
assertThat(viewModel.selection.value).isEqualTo(
- PaymentSelection.New.GenericPaymentMethod(
- paymentMethodCreateParams = LinkTestUtils.LINK_NEW_PAYMENT_DETAILS.paymentMethodCreateParams,
- paymentMethodOptionsParams = PaymentMethodOptionsParams.Card(
- setupFutureUsage = ConfirmPaymentIntentParams.SetupFutureUsage.OffSession,
- ),
+ PaymentSelection.New.LinkInline(
+ paymentMethodCreateParams = DEFAULT_CARD,
+ paymentMethodOptionsParams = null,
paymentMethodExtraParams = null,
+ brand = CardBrand.Visa,
customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestReuse,
- iconResource = PaymentSheetR.drawable.stripe_ic_paymentsheet_link,
- label = "···· 4242".resolvableString,
- lightThemeIconUrl = null,
- darkThemeIconUrl = null,
- createdFromLink = true,
+ input = UserInput.SignUp(
+ name = "John Doe",
+ email = "email@email.com",
+ phone = "1234567890",
+ country = "CA",
+ consentAction = SignUpConsentAction.Checkbox,
+ ),
)
)
}
@@ -755,26 +746,21 @@ internal class PaymentOptionsViewModelTest {
runTest {
val viewModel = createLinkViewModel()
- viewModel.linkHandler.payWithLinkInline(
- paymentSelection = createLinkInlinePaymentSelection(
- customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest,
- input = UserInput.SignIn("email@email.com"),
- ),
- shouldCompleteLinkInlineFlow = false
- )
-
assertThat(viewModel.selection.value).isEqualTo(
- PaymentSelection.New.GenericPaymentMethod(
- paymentMethodCreateParams = LinkTestUtils.LINK_NEW_PAYMENT_DETAILS.paymentMethodCreateParams,
- paymentMethodOptionsParams = PaymentMethodOptionsParams.Card(),
+ PaymentSelection.New.LinkInline(
+ paymentMethodCreateParams = DEFAULT_CARD,
+ paymentMethodOptionsParams = null,
paymentMethodExtraParams = null,
- customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest,
- iconResource = PaymentSheetR.drawable.stripe_ic_paymentsheet_link,
- label = "···· 4242".resolvableString,
- lightThemeIconUrl = null,
- darkThemeIconUrl = null,
- createdFromLink = true,
- )
+ brand = CardBrand.Visa,
+ customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestReuse,
+ input = UserInput.SignUp(
+ name = "John Doe",
+ email = "email@email.com",
+ phone = "1234567890",
+ country = "CA",
+ consentAction = SignUpConsentAction.Checkbox,
+ ),
+ ),
)
}
@@ -816,18 +802,6 @@ internal class PaymentOptionsViewModelTest {
)
}
- private fun createLinkInlinePaymentSelection(
- customerRequestedSave: PaymentSelection.CustomerRequestedSave,
- input: UserInput,
- ): PaymentSelection.New.LinkInline {
- return PaymentSelection.New.LinkInline(
- paymentMethodCreateParams = DEFAULT_CARD,
- brand = CardBrand.Visa,
- customerRequestedSave = customerRequestedSave,
- input = input,
- )
- }
-
private companion object {
private val PAYMENT_INTENT = PaymentIntentFactory.create()
private val DEFERRED_PAYMENT_INTENT = PAYMENT_INTENT.copy(clientSecret = null)
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt
index e34ba11405c..10abeb92ac6 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetActivityTest.kt
@@ -1137,11 +1137,13 @@ internal class PaymentSheetActivityTest {
args: PaymentSheetContractV2.Args = PaymentSheetFixtures.ARGS_CUSTOMER_WITH_GOOGLEPAY,
cbcEligibility: CardBrandChoiceEligibility = CardBrandChoiceEligibility.Ineligible,
): PaymentSheetViewModel = runBlocking {
+ val coordinator = mock().stub {
+ onBlocking { getAccountStatusFlow(any()) }.thenReturn(flowOf(AccountStatus.SignedOut))
+ on { emailFlow } doReturn stateFlowOf("email@email.com")
+ }
+
TestViewModelFactory.create(
- linkConfigurationCoordinator = mock().stub {
- onBlocking { getAccountStatusFlow(any()) }.thenReturn(flowOf(AccountStatus.SignedOut))
- on { emailFlow } doReturn stateFlowOf("email@email.com")
- },
+ linkConfigurationCoordinator = coordinator,
) { linkHandler, savedStateHandle ->
PaymentSheetViewModel(
args = args,
@@ -1175,6 +1177,7 @@ internal class PaymentSheetActivityTest {
statusBarColor = args.statusBarColor,
linkLauncher = linkPaymentLauncher,
errorReporter = FakeErrorReporter(),
+ linkConfigurationCoordinator = coordinator,
cvcRecollectionLauncherFactory = RecordingCvcRecollectionLauncherFactory.noOp(),
),
cardAccountRangeRepositoryFactory = NullCardAccountRangeRepositoryFactory,
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt
index e03f539579f..8f5ab6a3b48 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/PaymentSheetViewModelTest.kt
@@ -897,14 +897,15 @@ internal class PaymentSheetViewModelTest {
val viewModel = createLinkViewModel(intentConfirmationInterceptor)
- viewModel.linkHandler.payWithLinkInline(
- paymentSelection = createLinkInlinePaymentSelection(
+ viewModel.updateSelection(
+ createLinkInlinePaymentSelection(
customerRequestedSave = PaymentSelection.CustomerRequestedSave.RequestReuse,
input = UserInput.SignIn("email@email.com"),
- ),
- shouldCompleteLinkInlineFlow = false
+ )
)
+ viewModel.checkout()
+
verify(intentConfirmationInterceptor).intercept(
initializationMode = any(),
paymentMethod = any(),
@@ -924,14 +925,15 @@ internal class PaymentSheetViewModelTest {
val viewModel = createLinkViewModel(intentConfirmationInterceptor)
- viewModel.linkHandler.payWithLinkInline(
- paymentSelection = createLinkInlinePaymentSelection(
+ viewModel.updateSelection(
+ createLinkInlinePaymentSelection(
customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest,
input = UserInput.SignIn("email@email.com"),
- ),
- shouldCompleteLinkInlineFlow = false
+ )
)
+ viewModel.checkout()
+
verify(intentConfirmationInterceptor).intercept(
initializationMode = any(),
paymentMethod = any(),
@@ -3157,6 +3159,7 @@ internal class PaymentSheetViewModelTest {
statusBarColor = args.statusBarColor,
errorReporter = FakeErrorReporter(),
linkLauncher = linkPaymentLauncher,
+ linkConfigurationCoordinator = linkConfigurationCoordinator,
cvcRecollectionLauncherFactory = RecordingCvcRecollectionLauncherFactory.noOp(),
),
cardAccountRangeRepositoryFactory = NullCardAccountRangeRepositoryFactory,
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/TestViewModelFactory.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/TestViewModelFactory.kt
index 28030015e94..fdf7bd2aecc 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/TestViewModelFactory.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/TestViewModelFactory.kt
@@ -4,7 +4,6 @@ import androidx.lifecycle.SavedStateHandle
import com.stripe.android.link.LinkConfigurationCoordinator
import com.stripe.android.paymentsheet.viewmodels.BaseSheetViewModel
import com.stripe.android.utils.FakeLinkConfigurationCoordinator
-import org.mockito.kotlin.mock
internal object TestViewModelFactory {
fun create(
@@ -17,9 +16,6 @@ internal object TestViewModelFactory {
): T {
val linkHandler = LinkHandler(
linkConfigurationCoordinator = linkConfigurationCoordinator,
- savedStateHandle = savedStateHandle,
- linkAnalyticsComponentBuilder = mock(),
- linkStore = mock(),
)
return viewModelFactory(linkHandler, savedStateHandle)
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/analytics/PaymentSheetEventTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/analytics/PaymentSheetEventTest.kt
index 8cd8a9e209d..fa9c6a2f3d4 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/analytics/PaymentSheetEventTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/analytics/PaymentSheetEventTest.kt
@@ -13,7 +13,6 @@ import com.stripe.android.model.PaymentMethodFixtures
import com.stripe.android.paymentsheet.ExperimentalCustomerSessionApi
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetFixtures
-import com.stripe.android.paymentsheet.R
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.state.PaymentElementLoader
import org.junit.runner.RunWith
@@ -692,48 +691,6 @@ class PaymentSheetEventTest {
)
}
- @Test
- fun `Generic payment method event created from Link should return expected event`() {
- val inlineLinkEvent = PaymentSheetEvent.Payment(
- mode = EventReporter.Mode.Complete,
- paymentSelection = PaymentSelection.New.GenericPaymentMethod(
- paymentMethodCreateParams = PaymentMethodCreateParamsFixtures.DEFAULT_CARD,
- paymentMethodOptionsParams = null,
- paymentMethodExtraParams = null,
- customerRequestedSave = PaymentSelection.CustomerRequestedSave.NoRequest,
- label = resolvableString("**** 4444"),
- iconResource = R.drawable.stripe_ic_paymentsheet_card_visa,
- darkThemeIconUrl = null,
- lightThemeIconUrl = null,
- createdFromLink = true,
- ),
- duration = 1.milliseconds,
- result = PaymentSheetEvent.Payment.Result.Success,
- currency = "usd",
- isDeferred = false,
- linkEnabled = false,
- googlePaySupported = false,
- deferredIntentConfirmationType = null,
- )
- assertThat(
- inlineLinkEvent.eventName
- ).isEqualTo(
- "mc_complete_payment_link_success"
- )
- assertThat(
- inlineLinkEvent.params
- ).isEqualTo(
- mapOf(
- "currency" to "usd",
- "duration" to 0.001F,
- "selected_lpm" to "card",
- "is_decoupled" to false,
- "link_enabled" to false,
- "google_pay_enabled" to false,
- )
- )
- }
-
@Test
fun `External payment method event should return expected event`() {
val newPMEvent = PaymentSheetEvent.Payment(
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt
index e7a89cd0ea8..ac469b46a46 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowControllerTest.kt
@@ -91,6 +91,7 @@ import com.stripe.android.testing.CoroutineTestRule
import com.stripe.android.testing.FakeErrorReporter
import com.stripe.android.uicore.image.StripeImageLoader
import com.stripe.android.utils.FakeIntentConfirmationInterceptor
+import com.stripe.android.utils.FakeLinkConfigurationCoordinator
import com.stripe.android.utils.FakePaymentElementLoader
import com.stripe.android.utils.IntentConfirmationInterceptorTestRule
import com.stripe.android.utils.RelayingPaymentElementLoader
@@ -2343,6 +2344,7 @@ internal class DefaultFlowControllerTest {
stripePaymentLauncherAssistedFactory = paymentLauncherAssistedFactory,
cvcRecollectionLauncherFactory = cvcRecollectionLauncherFactory,
paymentConfiguration = PaymentConfiguration.getInstance(context),
+ linkConfigurationCoordinator = FakeLinkConfigurationCoordinator(),
linkLauncher = linkPaymentLauncher,
errorReporter = errorReporter,
savedStateHandle = viewModel.handle,
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/viewmodels/FakeBaseSheetViewModel.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/viewmodels/FakeBaseSheetViewModel.kt
index 85cac5692e1..f9083c40911 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/viewmodels/FakeBaseSheetViewModel.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/viewmodels/FakeBaseSheetViewModel.kt
@@ -22,14 +22,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
-import org.mockito.Mockito.mock
-private fun linkHandler(savedStateHandle: SavedStateHandle): LinkHandler {
+private fun linkHandler(): LinkHandler {
return LinkHandler(
linkConfigurationCoordinator = FakeLinkConfigurationCoordinator(),
- savedStateHandle = savedStateHandle,
- linkStore = mock(),
- linkAnalyticsComponentBuilder = mock(),
)
}
@@ -53,7 +49,7 @@ internal class FakeBaseSheetViewModel private constructor(
initialScreen: PaymentSheetScreen,
): FakeBaseSheetViewModel {
val savedStateHandle = SavedStateHandle()
- val linkHandler = linkHandler(savedStateHandle)
+ val linkHandler = linkHandler()
return FakeBaseSheetViewModel(savedStateHandle, linkHandler, paymentMethodMetadata).apply {
navigationHandler.transitionTo(
initialScreen
diff --git a/paymentsheet/src/test/java/com/stripe/android/utils/RecordingLinkStore.kt b/paymentsheet/src/test/java/com/stripe/android/utils/RecordingLinkStore.kt
index 54543a1f558..5104c7613ae 100644
--- a/paymentsheet/src/test/java/com/stripe/android/utils/RecordingLinkStore.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/utils/RecordingLinkStore.kt
@@ -11,13 +11,23 @@ internal object RecordingLinkStore {
return mock()
}
- suspend fun test(test: suspend Scenario.() -> Unit) {
+ suspend fun test(
+ hasUsedLink: Boolean = false,
+ test: suspend Scenario.() -> Unit
+ ) {
val markAsUsedCalls = Turbine()
+ val hasUsedLinkCalls = Turbine()
val linkStore = mock {
on { markLinkAsUsed() } doAnswer {
markAsUsedCalls.add(Unit)
}
+
+ on { hasUsedLink() } doAnswer {
+ hasUsedLinkCalls.add(Unit)
+
+ hasUsedLink
+ }
}
test(