Skip to content

Commit

Permalink
Launch Link Express from PaymentSheet (#10053)
Browse files Browse the repository at this point in the history
  • Loading branch information
toluo-stripe authored Feb 4, 2025
1 parent 258a519 commit ab10877
Show file tree
Hide file tree
Showing 50 changed files with 483 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal object LinkTypeSettingsDefinition :
PlaygroundSettingDefinition.Saveable<LinkType> by EnumSaveable(
key = "LinkType",
values = LinkType.entries.toTypedArray(),
defaultValue = LinkType.Native
defaultValue = LinkType.Web
),
PlaygroundSettingDefinition.Displayable<LinkType> {
override val displayName: String = "Link Type"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ interface CustomerAdapter {
}

is Link -> {
PaymentSelection.Link
PaymentSelection.Link()
}

is StripeId -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ internal class DefaultCustomerSheetLoader(
val paymentSelection = customerSheetSession.savedSelection?.let { selection ->
when (selection) {
is SavedSelection.GooglePay -> PaymentSelection.GooglePay
is SavedSelection.Link -> PaymentSelection.Link
is SavedSelection.Link -> PaymentSelection.Link()
is SavedSelection.PaymentMethod -> {
paymentMethods.find { paymentMethod ->
paymentMethod.id == selection.id
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stripe.android.link

import com.stripe.android.link.gate.LinkGate
import com.stripe.android.link.injection.LinkComponent
import com.stripe.android.link.model.AccountStatus
import com.stripe.android.link.ui.inline.UserInput
Expand All @@ -22,6 +23,8 @@ internal interface LinkConfigurationCoordinator {

fun getAccountStatusFlow(configuration: LinkConfiguration): Flow<AccountStatus>

fun linkGate(configuration: LinkConfiguration): LinkGate

suspend fun signInWithUserInput(
configuration: LinkConfiguration,
userInput: UserInput
Expand Down Expand Up @@ -62,6 +65,10 @@ internal class RealLinkConfigurationCoordinator @Inject internal constructor(
override fun getAccountStatusFlow(configuration: LinkConfiguration): Flow<AccountStatus> =
getLinkPaymentLauncherComponent(configuration).linkAccountManager.accountStatus

override fun linkGate(configuration: LinkConfiguration): LinkGate {
return getLinkPaymentLauncherComponent(configuration).linkGate
}

/**
* Trigger Link sign in with the input collected from the user inline in PaymentSheet, whether
* it's a new or existing account.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ internal class LinkPaymentLauncher @Inject internal constructor(
*/
fun present(
configuration: LinkConfiguration,
linkAccount: LinkAccount?
linkAccount: LinkAccount?,
useLinkExpress: Boolean
) {
val args = LinkActivityContract.Args(
configuration = configuration,
startWithVerificationDialog = false,
linkAccount = linkAccount
linkAccount = linkAccount,
startWithVerificationDialog = useLinkExpress
)
linkActivityResultLauncher?.launch(args)
analyticsHelper.onLinkLaunched()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ internal class DefaultLinkGate @Inject constructor(
return FeatureFlags.nativeLinkAttestationEnabled.isEnabled
}

override val suppress2faModal: Boolean
get() {
return useNativeLink.not() || configuration.suppress2faModal
}

class Factory @Inject constructor() : LinkGate.Factory {
override fun create(configuration: LinkConfiguration): LinkGate {
return DefaultLinkGate(configuration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.stripe.android.link.LinkConfiguration
internal interface LinkGate {
val useNativeLink: Boolean
val useAttestationEndpoints: Boolean
val suppress2faModal: Boolean

fun interface Factory {
fun create(configuration: LinkConfiguration): LinkGate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stripe.android.link.injection
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.LinkPaymentLauncher
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.gate.LinkGate
import dagger.BindsInstance
import dagger.Subcomponent
import javax.inject.Scope
Expand All @@ -24,6 +25,7 @@ internal annotation class LinkScope
internal abstract class LinkComponent {
internal abstract val linkAccountManager: LinkAccountManager
internal abstract val configuration: LinkConfiguration
internal abstract val linkGate: LinkGate
internal abstract val inlineSignupViewModelFactory: LinkInlineSignupAssistedViewModelFactory

@Subcomponent.Builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import com.stripe.android.link.account.DefaultLinkAccountManager
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.analytics.DefaultLinkEventsReporter
import com.stripe.android.link.analytics.LinkEventsReporter
import com.stripe.android.link.gate.DefaultLinkGate
import com.stripe.android.link.gate.LinkGate
import com.stripe.android.link.repositories.LinkApiRepository
import com.stripe.android.link.repositories.LinkRepository
import com.stripe.android.repository.ConsumersApiService
Expand All @@ -32,6 +34,10 @@ internal interface LinkModule {
@LinkScope
fun bindLinkAccountManager(linkAccountManager: DefaultLinkAccountManager): LinkAccountManager

@Binds
@LinkScope
fun bindsLinkGate(linkGate: DefaultLinkGate): LinkGate

companion object {
@Provides
@LinkScope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ private fun PaymentSelection.Link.toConfirmationOption(
linkConfiguration: LinkConfiguration?
): LinkConfirmationOption? {
return linkConfiguration?.let {
LinkConfirmationOption(configuration = linkConfiguration)
LinkConfirmationOption(
configuration = linkConfiguration,
useLinkExpress = useLinkExpress
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ internal class LinkConfirmationDefinition @Inject constructor(
) {
launcher.present(
configuration = confirmationOption.configuration,
linkAccount = linkAccountHolder.linkAccount.value
linkAccount = linkAccountHolder.linkAccount.value,
useLinkExpress = confirmationOption.useLinkExpress
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ import kotlinx.parcelize.Parcelize
@Parcelize
internal data class LinkConfirmationOption(
val configuration: LinkConfiguration,
val useLinkExpress: Boolean
) : ConfirmationHandler.Option
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ internal class PaymentOptionDisplayDataFactory @Inject constructor(
}
is PaymentSelection.ExternalPaymentMethod -> null
PaymentSelection.GooglePay -> null
PaymentSelection.Link -> null
is PaymentSelection.Link -> null
}

return EmbeddedPaymentElement.PaymentOptionDisplayData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ internal class LinkHandler @Inject constructor(
_linkConfiguration.value = state.configuration
}

fun setupLinkWithEagerLaunch(state: LinkState?): Boolean {
setupLink(state)

val configuration = state?.configuration ?: return false
val linkGate = linkConfigurationCoordinator.linkGate(configuration)

if (linkGate.suppress2faModal) return false

return when (state.loginState) {
LinkState.LoginState.LoggedIn,
LinkState.LoginState.NeedsVerification -> true
LinkState.LoginState.LoggedOut -> false
}
}

@OptIn(DelicateCoroutinesApi::class)
fun logOut() {
val configuration = linkConfiguration.value ?: return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ internal fun PaymentOptionsItem.toPaymentSelection(): PaymentSelection? {
return when (this) {
is PaymentOptionsItem.AddCard -> null
is PaymentOptionsItem.GooglePay -> PaymentSelection.GooglePay
is PaymentOptionsItem.Link -> PaymentSelection.Link
is PaymentOptionsItem.Link -> PaymentSelection.Link()
is PaymentOptionsItem.SavedPaymentMethod -> PaymentSelection.Saved(paymentMethod)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal class PaymentOptionsViewModel @Inject constructor(
onUserSelection()
},
onLinkPressed = {
updateSelection(PaymentSelection.Link)
updateSelection(PaymentSelection.Link())
onUserSelection()
},
isSetupIntent = paymentMethodMetadata.stripeIntent is SetupIntent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ internal class PaymentSheetViewModel @Inject internal constructor(

setPaymentMethodMetadata(state.paymentMethodMetadata)

linkHandler.setupLink(state.paymentMethodMetadata.linkState)
val shouldLaunchEagerly = linkHandler.setupLinkWithEagerLaunch(state.paymentMethodMetadata.linkState)

val pendingFailedPaymentResult = confirmationHandler.awaitResult()
as? ConfirmationHandler.Result.Failed
Expand All @@ -287,6 +287,10 @@ internal class PaymentSheetViewModel @Inject internal constructor(
)
)

if (shouldLaunchEagerly) {
checkoutWithLinkExpress()
}

viewModelScope.launch {
confirmationHandler.state.collectLatest { state ->
when (state) {
Expand Down Expand Up @@ -340,7 +344,11 @@ internal class PaymentSheetViewModel @Inject internal constructor(
}

fun checkoutWithLink() {
checkout(PaymentSelection.Link, CheckoutIdentifier.SheetTopWallet)
checkout(PaymentSelection.Link(useLinkExpress = false), CheckoutIdentifier.SheetTopWallet)
}

private fun checkoutWithLinkExpress() {
checkout(PaymentSelection.Link(useLinkExpress = true), CheckoutIdentifier.SheetTopWallet)
}

private fun checkout(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ internal class DefaultFlowController @Inject internal constructor(
is PaymentSelection.Saved -> {
when (currentSelection.walletType) {
PaymentSelection.Saved.WalletType.GooglePay -> PaymentSelection.GooglePay
PaymentSelection.Saved.WalletType.Link -> PaymentSelection.Link
PaymentSelection.Saved.WalletType.Link -> PaymentSelection.Link()
else -> currentSelection
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ internal sealed class PaymentSelection : Parcelable {
}

@Parcelize
data object Link : PaymentSelection() {
data class Link(
val useLinkExpress: Boolean = false
) : PaymentSelection() {

override val requiresConfirmation: Boolean
get() = false
Expand Down Expand Up @@ -107,7 +109,7 @@ internal sealed class PaymentSelection : Parcelable {
) : PaymentSelection() {

enum class WalletType(val paymentSelection: PaymentSelection) {
GooglePay(PaymentSelection.GooglePay), Link(PaymentSelection.Link)
GooglePay(PaymentSelection.GooglePay), Link(Link())
}

val showMandateAbovePrimaryButton: Boolean
Expand Down Expand Up @@ -309,7 +311,7 @@ internal val PaymentSelection.drawableResourceId: Int
get() = when (this) {
is PaymentSelection.ExternalPaymentMethod -> iconResource
PaymentSelection.GooglePay -> R.drawable.stripe_google_pay_mark
PaymentSelection.Link -> getLinkIcon()
is PaymentSelection.Link -> getLinkIcon()
is PaymentSelection.New.Card -> brand.getCardBrandIcon()
is PaymentSelection.New.GenericPaymentMethod -> iconResource
is PaymentSelection.New.LinkInline -> getLinkIcon()
Expand All @@ -334,7 +336,7 @@ internal val PaymentSelection.lightThemeIconUrl: String?
get() = when (this) {
is PaymentSelection.ExternalPaymentMethod -> lightThemeIconUrl
PaymentSelection.GooglePay -> null
PaymentSelection.Link -> null
is PaymentSelection.Link -> null
is PaymentSelection.New.Card -> null
is PaymentSelection.New.GenericPaymentMethod -> lightThemeIconUrl
is PaymentSelection.New.LinkInline -> null
Expand All @@ -346,7 +348,7 @@ internal val PaymentSelection.darkThemeIconUrl: String?
get() = when (this) {
is PaymentSelection.ExternalPaymentMethod -> darkThemeIconUrl
PaymentSelection.GooglePay -> null
PaymentSelection.Link -> null
is PaymentSelection.Link -> null
is PaymentSelection.New.Card -> null
is PaymentSelection.New.GenericPaymentMethod -> darkThemeIconUrl
is PaymentSelection.New.LinkInline -> null
Expand All @@ -358,7 +360,7 @@ internal val PaymentSelection.label: ResolvableString
get() = when (this) {
is PaymentSelection.ExternalPaymentMethod -> label
PaymentSelection.GooglePay -> StripeR.string.stripe_google_pay.resolvableString
PaymentSelection.Link -> StripeR.string.stripe_link.resolvableString
is PaymentSelection.Link -> StripeR.string.stripe_link.resolvableString
is PaymentSelection.New.Card -> createCardLabel(last4).orEmpty()
is PaymentSelection.New.GenericPaymentMethod -> label
is PaymentSelection.New.LinkInline -> createCardLabel(last4).orEmpty()
Expand All @@ -380,7 +382,7 @@ internal val PaymentSelection.paymentMethodType: String
get() = when (this) {
is PaymentSelection.ExternalPaymentMethod -> type
PaymentSelection.GooglePay -> "google_pay"
PaymentSelection.Link -> "link"
is PaymentSelection.Link -> "link"
is PaymentSelection.New -> paymentMethodCreateParams.typeCode
is PaymentSelection.Saved -> paymentMethod.type?.name ?: "card"
}
Expand All @@ -389,7 +391,7 @@ internal val PaymentSelection.billingDetails: PaymentMethod.BillingDetails?
get() = when (this) {
is PaymentSelection.ExternalPaymentMethod -> billingDetails
PaymentSelection.GooglePay -> null
PaymentSelection.Link -> null
is PaymentSelection.Link -> null
is PaymentSelection.New -> paymentMethodCreateParams.billingDetails
is PaymentSelection.Saved -> paymentMethod.billingDetails
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ internal class DefaultPaymentElementLoader @Inject constructor(
CustomerState.DefaultPaymentMethodState.Disabled, null -> {
when (val selection = savedSelection.await()) {
is SavedSelection.GooglePay -> PaymentSelection.GooglePay
is SavedSelection.Link -> PaymentSelection.Link
is SavedSelection.Link -> PaymentSelection.Link()
is SavedSelection.PaymentMethod -> {
customer?.paymentMethods?.find { it.id == selection.id }?.toPaymentSelection()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ private fun LinkTab(
iconTint = null,
labelText = stringResource(StripeR.string.stripe_link),
description = stringResource(StripeR.string.stripe_link),
onItemSelectedListener = { onItemSelected(PaymentSelection.Link) },
onItemSelectedListener = { onItemSelected(PaymentSelection.Link()) },
modifier = modifier,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ internal class DefaultSelectSavedPaymentMethodsInteractor(
coroutineScope.launch {
currentSelection.filter { selection ->
selection is PaymentSelection.Saved ||
selection == PaymentSelection.Link ||
selection is PaymentSelection.Link ||
selection == PaymentSelection.GooglePay
}.collect {
_paymentOptionsRelevantSelection.value = it
Expand Down Expand Up @@ -170,7 +170,7 @@ internal class DefaultSelectSavedPaymentMethodsInteractor(
paymentOptionsItems: List<PaymentOptionsItem>,
): PaymentOptionsItem? {
val paymentSelection = when (selection) {
is PaymentSelection.Saved, PaymentSelection.Link, PaymentSelection.GooglePay -> selection
is PaymentSelection.Saved, is PaymentSelection.Link, PaymentSelection.GooglePay -> selection

is PaymentSelection.New, is PaymentSelection.ExternalPaymentMethod, null -> savedSelection?.let {
PaymentSelection.Saved(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ internal class DefaultManageScreenInteractor(
null,
is PaymentSelection.ExternalPaymentMethod,
PaymentSelection.GooglePay,
PaymentSelection.Link,
is PaymentSelection.Link,
is PaymentSelection.New -> return null
is PaymentSelection.Saved -> selection.paymentMethod.id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ internal class DefaultPaymentMethodVerticalLayoutInteractor(
iconRequiresTinting = false,
subtitle = PaymentsCoreR.string.stripe_link_simple_secure_payments.resolvableString,
onClick = {
updateSelection(PaymentSelection.Link)
updateSelection(PaymentSelection.Link())
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ class CustomerSheetViewModelTest {
val error = assertFailsWith<IllegalStateException> {
viewModel.handleViewAction(
CustomerSheetViewAction.OnItemSelected(
selection = PaymentSelection.Link
selection = PaymentSelection.Link()
)
)
}
Expand Down
Loading

0 comments on commit ab10877

Please sign in to comment.