diff --git a/paymentsheet/res/values/themes.xml b/paymentsheet/res/values/themes.xml
index b922dae5a5c..054956a56c8 100644
--- a/paymentsheet/res/values/themes.xml
+++ b/paymentsheet/res/values/themes.xml
@@ -44,4 +44,10 @@
- @color/stripe_link_window_background
- true
+
+
diff --git a/paymentsheet/src/main/AndroidManifest.xml b/paymentsheet/src/main/AndroidManifest.xml
index 6fc8699d491..55d7e7ea232 100644
--- a/paymentsheet/src/main/AndroidManifest.xml
+++ b/paymentsheet/src/main/AndroidManifest.xml
@@ -53,6 +53,15 @@
android:autoRemoveFromRecents="true"
android:configChanges="orientation|keyboard|keyboardHidden|screenLayout|screenSize|smallestScreenSize" />
+
+
+ fun getLinkAccountFlow(configuration: LinkConfiguration): StateFlow
+
suspend fun signInWithUserInput(
configuration: LinkConfiguration,
userInput: UserInput
@@ -62,6 +65,10 @@ internal class RealLinkConfigurationCoordinator @Inject internal constructor(
override fun getAccountStatusFlow(configuration: LinkConfiguration): Flow =
getLinkPaymentLauncherComponent(configuration).linkAccountManager.accountStatus
+ override fun getLinkAccountFlow(configuration: LinkConfiguration): StateFlow {
+ return getLinkPaymentLauncherComponent(configuration).linkAccountManager.linkAccount
+ }
+
/**
* Trigger Link sign in with the input collected from the user inline in PaymentSheet, whether
* it's a new or existing account.
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt b/paymentsheet/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt
index 63f8840547a..45ecefc268f 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt
@@ -6,6 +6,7 @@ import androidx.activity.result.ActivityResultRegistry
import com.stripe.android.link.LinkActivityResult.PaymentMethodObtained
import com.stripe.android.link.account.LinkStore
import com.stripe.android.link.injection.LinkAnalyticsComponent
+import com.stripe.android.link.model.LinkAccount
import javax.inject.Inject
import javax.inject.Singleton
@@ -72,9 +73,11 @@ internal class LinkPaymentLauncher @Inject internal constructor(
*/
fun present(
configuration: LinkConfiguration,
+ linkAccount: LinkAccount?
) {
val args = LinkActivityContract.Args(
- configuration,
+ configuration = configuration,
+ linkAccount = linkAccount
)
linkActivityResultLauncher?.launch(args)
analyticsHelper.onLinkLaunched()
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/NativeLinkActivityContract.kt b/paymentsheet/src/main/java/com/stripe/android/link/NativeLinkActivityContract.kt
index 22285fa7bdf..c7cac32ee81 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/NativeLinkActivityContract.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/NativeLinkActivityContract.kt
@@ -20,7 +20,8 @@ internal class NativeLinkActivityContract @Inject constructor() :
args = NativeLinkArgs(
configuration = input.configuration,
stripeAccountId = paymentConfiguration.stripeAccountId,
- publishableKey = paymentConfiguration.publishableKey
+ publishableKey = paymentConfiguration.publishableKey,
+ linkAccount = input.linkAccount
)
)
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/NativeLinkArgs.kt b/paymentsheet/src/main/java/com/stripe/android/link/NativeLinkArgs.kt
index c4416cbf717..d7bcf8426b3 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/NativeLinkArgs.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/NativeLinkArgs.kt
@@ -1,11 +1,13 @@
package com.stripe.android.link
import android.os.Parcelable
+import com.stripe.android.link.model.LinkAccount
import kotlinx.parcelize.Parcelize
@Parcelize
internal data class NativeLinkArgs(
val configuration: LinkConfiguration,
val publishableKey: String,
- val stripeAccountId: String?
+ val stripeAccountId: String?,
+ val linkAccount: LinkAccount?
) : Parcelable
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/account/DefaultLinkAccountManager.kt b/paymentsheet/src/main/java/com/stripe/android/link/account/DefaultLinkAccountManager.kt
index a8779fe887a..9478a36e30d 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/account/DefaultLinkAccountManager.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/account/DefaultLinkAccountManager.kt
@@ -207,6 +207,10 @@ internal class DefaultLinkAccountManager @Inject constructor(
return newAccount
}
+ internal fun setAccount(linkAccount: LinkAccount?) {
+ _linkAccount.value = linkAccount
+ }
+
override fun setLinkAccountFromLookupResult(
lookup: ConsumerSessionLookup,
startSession: Boolean,
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressActivity.kt b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressActivity.kt
new file mode 100644
index 00000000000..ee5dfadfef0
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressActivity.kt
@@ -0,0 +1,92 @@
+package com.stripe.android.link.express
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.annotation.VisibleForTesting
+import androidx.compose.ui.window.Dialog
+import androidx.core.os.bundleOf
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.NavHostController
+import com.stripe.android.core.Logger
+import com.stripe.android.link.NoArgsException
+import com.stripe.android.link.theme.DefaultLinkTheme
+import com.stripe.android.link.ui.verification.VerificationScreen
+import com.stripe.android.link.ui.verification.VerificationViewModel
+import com.stripe.android.paymentsheet.BuildConfig
+
+class LinkExpressActivity : ComponentActivity() {
+ internal var viewModel: VerificationViewModel? = null
+
+ @VisibleForTesting
+ internal lateinit var navController: NavHostController
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ try {
+ viewModel = ViewModelProvider(
+ owner = this,
+ factory = VerificationViewModel.factory(
+ onVerificationSucceeded = { linkAccount ->
+ dismissWithResult(LinkExpressResult.Authenticated(linkAccount))
+ },
+ onChangeEmailClicked = {},
+ onDismissClicked = {
+ dismissWithResult(LinkExpressResult.Canceled)
+ }
+ )
+ )[VerificationViewModel::class.java]
+ } catch (e: NoArgsException) {
+ Logger.getInstance(BuildConfig.DEBUG).error("Failed to create VerificationViewModel", e)
+ setResult(Activity.RESULT_CANCELED)
+ finish()
+ }
+
+ val vm = viewModel ?: return
+
+ setContent {
+ Dialog(
+ onDismissRequest = {
+ dismissWithResult(LinkExpressResult.Canceled)
+ }
+ ) {
+ DefaultLinkTheme {
+ VerificationScreen(vm)
+ }
+ }
+ }
+ }
+
+ private fun dismissWithResult(result: LinkExpressResult) {
+ val bundle = bundleOf(
+ LinkExpressContract.EXTRA_RESULT to result
+ )
+ this@LinkExpressActivity.setResult(
+ RESULT_COMPLETE,
+ intent.putExtras(bundle)
+ )
+ this@LinkExpressActivity.finish()
+ }
+
+ companion object {
+ internal const val EXTRA_ARGS = "link_express_args"
+ internal const val RESULT_COMPLETE = 57576
+
+ internal fun createIntent(
+ context: Context,
+ args: LinkExpressArgs
+ ): Intent {
+ return Intent(context, LinkExpressActivity::class.java)
+ .putExtra(EXTRA_ARGS, args)
+ }
+
+ internal fun getArgs(savedStateHandle: SavedStateHandle): LinkExpressArgs? {
+ return savedStateHandle.get(EXTRA_ARGS)
+ }
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressArgs.kt b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressArgs.kt
new file mode 100644
index 00000000000..cff6895fd90
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressArgs.kt
@@ -0,0 +1,14 @@
+package com.stripe.android.link.express
+
+import android.os.Parcelable
+import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.model.LinkAccount
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+internal data class LinkExpressArgs(
+ val configuration: LinkConfiguration,
+ val publishableKey: String,
+ val stripeAccountId: String?,
+ val linkAccount: LinkAccount?
+) : Parcelable
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressContract.kt b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressContract.kt
new file mode 100644
index 00000000000..8f5008885fd
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressContract.kt
@@ -0,0 +1,53 @@
+package com.stripe.android.link.express
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.core.os.BundleCompat
+import com.stripe.android.PaymentConfiguration
+import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.model.LinkAccount
+import javax.inject.Inject
+
+internal class LinkExpressContract @Inject constructor() :
+ ActivityResultContract() {
+
+ override fun createIntent(context: Context, input: Args): Intent {
+ val paymentConfiguration = PaymentConfiguration.getInstance(context)
+ return LinkExpressActivity.createIntent(
+ context = context,
+ args = LinkExpressArgs(
+ configuration = input.configuration,
+ stripeAccountId = paymentConfiguration.stripeAccountId,
+ publishableKey = paymentConfiguration.publishableKey,
+ linkAccount = input.linkAccount
+ )
+ )
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): LinkExpressResult {
+ return when (resultCode) {
+ LinkExpressActivity.RESULT_COMPLETE -> {
+ val result = intent?.extras?.let {
+ BundleCompat.getParcelable(it, EXTRA_RESULT, LinkExpressResult::class.java)
+ }
+ return result ?: LinkExpressResult.Canceled
+ }
+ Activity.RESULT_CANCELED -> {
+ LinkExpressResult.Canceled
+ }
+ else -> LinkExpressResult.Canceled
+ }
+ }
+
+ data class Args(
+ val configuration: LinkConfiguration,
+ val linkAccount: LinkAccount
+ )
+
+ companion object {
+ internal const val EXTRA_RESULT =
+ "com.stripe.android.link.express.LinkExpressContract.extra_result"
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressLauncher.kt b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressLauncher.kt
new file mode 100644
index 00000000000..df189a0c9a0
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressLauncher.kt
@@ -0,0 +1,68 @@
+package com.stripe.android.link.express
+
+import androidx.activity.result.ActivityResultCaller
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.ActivityResultRegistry
+import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.model.LinkAccount
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+internal class LinkExpressLauncher @Inject constructor(
+ private val linkExpressContract: LinkExpressContract
+) {
+ private var linkActivityResultLauncher:
+ ActivityResultLauncher? = null
+
+ fun register(
+ activityResultRegistry: ActivityResultRegistry,
+ callback: (LinkExpressResult) -> Unit,
+ ) {
+ linkActivityResultLauncher = activityResultRegistry.register(
+ "LinkPaymentLauncher",
+ linkExpressContract,
+ ) { linkExpressResult ->
+ handleActivityResult(linkExpressResult, callback)
+ }
+ }
+
+ fun register(
+ activityResultCaller: ActivityResultCaller,
+ callback: (LinkExpressResult) -> Unit,
+ ) {
+ linkActivityResultLauncher = activityResultCaller.registerForActivityResult(
+ linkExpressContract
+ ) { linkExpressContract ->
+ handleActivityResult(linkExpressContract, callback)
+ }
+ }
+
+ private fun handleActivityResult(
+ linkExpressResult: LinkExpressResult,
+ nextStep: (LinkExpressResult) -> Unit
+ ) {
+ nextStep(linkExpressResult)
+ }
+
+ fun unregister() {
+ linkActivityResultLauncher?.unregister()
+ linkActivityResultLauncher = null
+ }
+
+ /**
+ * Launch the Link UI to process a payment.
+ *
+ * @param configuration The payment and customer settings
+ */
+ fun present(
+ configuration: LinkConfiguration,
+ linkAccount: LinkAccount
+ ) {
+ val args = LinkExpressContract.Args(
+ configuration = configuration,
+ linkAccount = linkAccount
+ )
+ linkActivityResultLauncher?.launch(args)
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressResult.kt b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressResult.kt
new file mode 100644
index 00000000000..ee105fd09ee
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/link/express/LinkExpressResult.kt
@@ -0,0 +1,17 @@
+package com.stripe.android.link.express
+
+import android.os.Parcelable
+import com.stripe.android.link.model.LinkAccount
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+internal sealed interface LinkExpressResult : Parcelable {
+ @Parcelize
+ data class Authenticated(val linkAccount: LinkAccount) : LinkExpressResult
+
+ @Parcelize
+ data object Canceled : LinkExpressResult
+
+ @Parcelize
+ data class Failed(val error: Throwable) : LinkExpressResult
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkComponent.kt b/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkComponent.kt
index 56e4eb200d2..70a811808d1 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkComponent.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkComponent.kt
@@ -11,6 +11,7 @@ import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.analytics.LinkEventsReporter
import com.stripe.android.link.confirmation.LinkConfirmationHandler
+import com.stripe.android.link.model.LinkAccount
import com.stripe.android.paymentelement.confirmation.injection.DefaultConfirmationModule
import com.stripe.android.payments.core.injection.STATUS_BAR_COLOR
import dagger.BindsInstance
@@ -53,6 +54,9 @@ internal interface NativeLinkComponent {
@BindsInstance
fun context(context: Context): Builder
+ @BindsInstance
+ fun linkAccount(linkAccount: LinkAccount?): Builder
+
@BindsInstance
fun savedStateHandle(savedStateHandle: SavedStateHandle): Builder
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkModule.kt b/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkModule.kt
index d658eee3dda..d9b3cb49abe 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkModule.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/injection/NativeLinkModule.kt
@@ -20,12 +20,14 @@ import com.stripe.android.core.utils.ContextUtils.packageInfo
import com.stripe.android.core.utils.DefaultDurationProvider
import com.stripe.android.core.utils.DurationProvider
import com.stripe.android.core.version.StripeSdkVersion
+import com.stripe.android.link.LinkConfiguration
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.confirmation.DefaultLinkConfirmationHandler
import com.stripe.android.link.confirmation.LinkConfirmationHandler
+import com.stripe.android.link.model.LinkAccount
import com.stripe.android.link.repositories.LinkApiRepository
import com.stripe.android.link.repositories.LinkRepository
import com.stripe.android.networking.StripeApiRepository
@@ -55,10 +57,6 @@ internal interface NativeLinkModule {
@NativeLinkScope
fun bindLinkEventsReporter(linkEventsReporter: DefaultLinkEventsReporter): LinkEventsReporter
- @Binds
- @NativeLinkScope
- fun bindLinkAccountManager(linkAccountManager: DefaultLinkAccountManager): LinkAccountManager
-
@Binds
@NativeLinkScope
fun bindsErrorReporter(errorReporter: RealErrorReporter): ErrorReporter
@@ -164,5 +162,24 @@ internal interface NativeLinkModule {
@Provides
@NativeLinkScope
fun provideEventReporterMode(): EventReporter.Mode = EventReporter.Mode.Custom
+
+ @Provides
+ @NativeLinkScope
+ fun provideLinkAccountManager(
+ configuration: LinkConfiguration,
+ linkRepository: LinkRepository,
+ linkEventsReporter: LinkEventsReporter,
+ errorReporter: ErrorReporter,
+ linkAccount: LinkAccount?
+ ): LinkAccountManager {
+ return DefaultLinkAccountManager(
+ config = configuration,
+ linkRepository = linkRepository,
+ linkEventsReporter = linkEventsReporter,
+ errorReporter = errorReporter
+ ).apply {
+ setAccount(linkAccount)
+ }
+ }
}
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/model/LinkAccount.kt b/paymentsheet/src/main/java/com/stripe/android/link/model/LinkAccount.kt
index 17f6b22c9a2..d63cc2c02ee 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/model/LinkAccount.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/model/LinkAccount.kt
@@ -1,21 +1,30 @@
package com.stripe.android.link.model
+import android.os.Parcelable
import com.stripe.android.model.ConsumerSession
+import kotlinx.parcelize.IgnoredOnParcel
+import kotlinx.parcelize.Parcelize
/**
* Immutable object representing a Link account.
*/
-internal class LinkAccount(private val consumerSession: ConsumerSession) {
+@Parcelize
+internal class LinkAccount(private val consumerSession: ConsumerSession) : Parcelable {
+ @IgnoredOnParcel
val redactedPhoneNumber = consumerSession.redactedPhoneNumber
+ @IgnoredOnParcel
val clientSecret = consumerSession.clientSecret
+ @IgnoredOnParcel
val email = consumerSession.emailAddress
+ @IgnoredOnParcel
val isVerified: Boolean = consumerSession.containsVerifiedSMSSession() ||
consumerSession.isVerifiedForSignup()
+ @IgnoredOnParcel
val accountStatus = when {
isVerified -> {
AccountStatus.Verified
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/theme/Theme.kt b/paymentsheet/src/main/java/com/stripe/android/link/theme/Theme.kt
index 46191f6a89c..11bab1ce233 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/theme/Theme.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/theme/Theme.kt
@@ -2,6 +2,7 @@ package com.stripe.android.link.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
@@ -27,8 +28,13 @@ internal fun DefaultLinkTheme(
colors = colors.materialColors,
typography = Typography,
shapes = MaterialTheme.shapes,
- content = content
- )
+ ) {
+ Surface(
+ color = MaterialTheme.colors.background
+ ) {
+ content()
+ }
+ }
}
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/ui/LinkContent.kt b/paymentsheet/src/main/java/com/stripe/android/link/ui/LinkContent.kt
index 6cd2e2a5abb..0289a6700bb 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/ui/LinkContent.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/ui/LinkContent.kt
@@ -153,9 +153,14 @@ private fun Screens(
val viewModel: VerificationViewModel = linkViewModel { parentComponent ->
VerificationViewModel.factory(
parentComponent = parentComponent,
- goBack = goBack,
- navigateAndClearStack = navigateAndClearStack,
- linkAccount = linkAccount
+ onDismissClicked = goBack,
+ linkAccount = linkAccount,
+ onVerificationSucceeded = {
+ navigateAndClearStack(LinkScreen.Wallet)
+ },
+ onChangeEmailClicked = {
+ navigateAndClearStack(LinkScreen.SignUp)
+ }
)
}
VerificationScreen(viewModel)
@@ -164,17 +169,11 @@ private fun Screens(
composable(LinkScreen.Wallet.route) {
val linkAccount = getLinkAccount()
?: return@composable dismissWithResult(LinkActivityResult.Failed(NoLinkAccountFoundException()))
- val viewModel: WalletViewModel = linkViewModel { parentComponent ->
- WalletViewModel.factory(
- parentComponent = parentComponent,
- linkAccount = linkAccount,
- navigate = navigate,
- navigateAndClearStack = navigateAndClearStack,
- dismissWithResult = dismissWithResult
- )
- }
- WalletScreen(
- viewModel = viewModel,
+ WalletRoute(
+ linkAccount = linkAccount,
+ navigate = navigate,
+ navigateAndClearStack = navigateAndClearStack,
+ dismissWithResult = dismissWithResult,
showBottomSheetContent = showBottomSheetContent,
hideBottomSheetContent = hideBottomSheetContent
)
@@ -192,6 +191,31 @@ private fun Screens(
}
}
+@Composable
+private fun WalletRoute(
+ linkAccount: LinkAccount,
+ navigate: (route: LinkScreen) -> Unit,
+ navigateAndClearStack: (route: LinkScreen) -> Unit,
+ dismissWithResult: (LinkActivityResult) -> Unit,
+ showBottomSheetContent: (BottomSheetContent?) -> Unit,
+ hideBottomSheetContent: () -> Unit
+) {
+ val viewModel: WalletViewModel = linkViewModel { parentComponent ->
+ WalletViewModel.factory(
+ parentComponent = parentComponent,
+ linkAccount = linkAccount,
+ navigate = navigate,
+ navigateAndClearStack = navigateAndClearStack,
+ dismissWithResult = dismissWithResult
+ )
+ }
+ WalletScreen(
+ viewModel = viewModel,
+ showBottomSheetContent = showBottomSheetContent,
+ hideBottomSheetContent = hideBottomSheetContent
+ )
+}
+
@Composable
private fun PaymentMethodRoute(
linkAccount: LinkAccount,
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/ui/verification/VerificationScreen.kt b/paymentsheet/src/main/java/com/stripe/android/link/ui/verification/VerificationScreen.kt
index 66bc55d969e..01375402f3d 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/ui/verification/VerificationScreen.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/ui/verification/VerificationScreen.kt
@@ -78,7 +78,7 @@ internal fun VerificationScreen(
otpElement = viewModel.otpElement,
focusRequester = focusRequester,
onBack = viewModel::onBack,
- onChangeEmailClick = viewModel::onChangeEmailClicked,
+ onChangeEmailClick = viewModel::onChangeEmailButtonClicked,
onResendCodeClick = viewModel::resendCode
)
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/link/ui/verification/VerificationViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/link/ui/verification/VerificationViewModel.kt
index 6108ef39b96..1280fd4e17e 100644
--- a/paymentsheet/src/main/java/com/stripe/android/link/ui/verification/VerificationViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/link/ui/verification/VerificationViewModel.kt
@@ -1,16 +1,23 @@
package com.stripe.android.link.ui.verification
+import android.app.Application
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
+import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.stripe.android.core.Logger
import com.stripe.android.core.strings.ResolvableString
import com.stripe.android.core.strings.resolvableString
-import com.stripe.android.link.LinkScreen
+import com.stripe.android.link.NoArgsException
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.analytics.LinkEventsReporter
+import com.stripe.android.link.express.LinkExpressActivity.Companion.getArgs
+import com.stripe.android.link.express.LinkExpressArgs
+import com.stripe.android.link.injection.DaggerNativeLinkComponent
import com.stripe.android.link.injection.NativeLinkComponent
import com.stripe.android.link.model.AccountStatus
import com.stripe.android.link.model.LinkAccount
@@ -29,18 +36,19 @@ import javax.inject.Inject
* ViewModel that handles user verification confirmation logic.
*/
internal class VerificationViewModel @Inject constructor(
- private val linkAccount: LinkAccount,
+ private val linkAccount: LinkAccount?,
private val linkAccountManager: LinkAccountManager,
private val linkEventsReporter: LinkEventsReporter,
private val logger: Logger,
- private val goBack: () -> Unit,
- private val navigateAndClearStack: (route: LinkScreen) -> Unit,
+ private val onVerificationSucceeded: (LinkAccount) -> Unit,
+ private val onChangeEmailClicked: () -> Unit,
+ private val onDismissClicked: () -> Unit,
) : ViewModel() {
private val _viewState = MutableStateFlow(
value = VerificationViewState(
- redactedPhoneNumber = linkAccount.redactedPhoneNumber,
- email = linkAccount.email,
+ redactedPhoneNumber = linkAccount?.redactedPhoneNumber.orEmpty(),
+ email = linkAccount?.email.orEmpty(),
isProcessing = false,
requestFocus = true,
errorMessage = null,
@@ -60,6 +68,9 @@ internal class VerificationViewModel @Inject constructor(
}
private fun setUp() {
+ if (linkAccount == null) {
+ return onDismissClicked()
+ }
if (linkAccount.accountStatus != AccountStatus.VerificationStarted) {
startVerification()
}
@@ -80,11 +91,11 @@ internal class VerificationViewModel @Inject constructor(
}
linkAccountManager.confirmVerification(code).fold(
- onSuccess = {
+ onSuccess = { linkAccount ->
updateViewState {
it.copy(isProcessing = false)
}
- navigateAndClearStack(LinkScreen.Wallet)
+ onVerificationSucceeded(linkAccount)
},
onFailure = {
otpElement.controller.reset()
@@ -125,16 +136,16 @@ internal class VerificationViewModel @Inject constructor(
fun onBack() {
clearError()
- goBack()
+ onDismissClicked()
linkEventsReporter.on2FACancel()
viewModelScope.launch {
linkAccountManager.logOut()
}
}
- fun onChangeEmailClicked() {
+ fun onChangeEmailButtonClicked() {
clearError()
- navigateAndClearStack(LinkScreen.SignUp)
+ onChangeEmailClicked()
viewModelScope.launch {
linkAccountManager.logOut()
}
@@ -179,8 +190,9 @@ internal class VerificationViewModel @Inject constructor(
fun factory(
parentComponent: NativeLinkComponent,
linkAccount: LinkAccount,
- goBack: () -> Unit,
- navigateAndClearStack: (route: LinkScreen) -> Unit,
+ onVerificationSucceeded: (LinkAccount) -> Unit,
+ onChangeEmailClicked: () -> Unit,
+ onDismissClicked: () -> Unit,
): ViewModelProvider.Factory {
return viewModelFactory {
initializer {
@@ -189,8 +201,44 @@ internal class VerificationViewModel @Inject constructor(
linkAccountManager = parentComponent.linkAccountManager,
linkEventsReporter = parentComponent.linkEventsReporter,
logger = parentComponent.logger,
- goBack = goBack,
- navigateAndClearStack = navigateAndClearStack,
+ onVerificationSucceeded = onVerificationSucceeded,
+ onChangeEmailClicked = onChangeEmailClicked,
+ onDismissClicked = onDismissClicked,
+ )
+ }
+ }
+ }
+
+ fun factory(
+ savedStateHandle: SavedStateHandle? = null,
+ onVerificationSucceeded: (LinkAccount) -> Unit,
+ onChangeEmailClicked: () -> Unit,
+ onDismissClicked: () -> Unit,
+ ): ViewModelProvider.Factory {
+ return viewModelFactory {
+ initializer {
+ val handle: SavedStateHandle = savedStateHandle ?: createSavedStateHandle()
+ val app = this[APPLICATION_KEY] as Application
+ val args: LinkExpressArgs = getArgs(handle) ?: throw NoArgsException()
+
+ val component = DaggerNativeLinkComponent
+ .builder()
+ .configuration(args.configuration)
+ .linkAccount(args.linkAccount)
+ .publishableKeyProvider { args.publishableKey }
+ .stripeAccountIdProvider { args.stripeAccountId }
+ .savedStateHandle(handle)
+ .context(app)
+ .build()
+
+ VerificationViewModel(
+ linkAccount = args.linkAccount,
+ linkEventsReporter = component.linkEventsReporter,
+ linkAccountManager = component.linkAccountManager,
+ logger = component.logger,
+ onVerificationSucceeded = onVerificationSucceeded,
+ onChangeEmailClicked = onChangeEmailClicked,
+ onDismissClicked = onDismissClicked
)
}
}
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..0906e2eba87 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
@@ -2,12 +2,15 @@ package com.stripe.android.paymentelement.confirmation
import com.stripe.android.common.model.CommonConfiguration
import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.model.AccountStatus
+import com.stripe.android.link.model.LinkAccount
import com.stripe.android.lpmfoundations.paymentmethod.PaymentSheetCardBrandFilter
import com.stripe.android.model.PaymentMethod
import com.stripe.android.paymentelement.confirmation.bacs.BacsConfirmationOption
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.linkexpress.LinkExpressConfirmationOption
import com.stripe.android.paymentsheet.model.PaymentSelection
internal fun PaymentSelection.toConfirmationOption(
@@ -68,7 +71,42 @@ internal fun PaymentSelection.toConfirmationOption(
)
}
is PaymentSelection.Link -> linkConfiguration?.let {
- LinkConfirmationOption(configuration = linkConfiguration)
+ LinkConfirmationOption(
+ configuration = linkConfiguration,
+ linkAccount = null
+ )
+ }
+ }
+}
+
+internal fun LinkAccount.toConfirmationOption(
+ linkConfiguration: LinkConfiguration
+): ConfirmationHandler.Option? {
+ return toLinkConfirmationOption(
+ linkConfiguration = linkConfiguration,
+ linkAccount = this
+ )
+}
+
+private fun toLinkConfirmationOption(
+ linkConfiguration: LinkConfiguration?,
+ linkAccount: LinkAccount
+): ConfirmationHandler.Option? {
+ if (linkConfiguration == null) return null
+ return when (linkAccount.accountStatus) {
+ AccountStatus.Verified -> {
+ LinkConfirmationOption(
+ configuration = linkConfiguration,
+ linkAccount = linkAccount
+ )
+ }
+ AccountStatus.NeedsVerification,
+ AccountStatus.VerificationStarted -> {
+ LinkExpressConfirmationOption(
+ configuration = linkConfiguration,
+ linkAccount = linkAccount
+ )
}
+ else -> null
}
}
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..e796b39372f 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.linkexpress.LinkExpressConfirmationModule
import dagger.Module
@Module(
@@ -13,6 +14,7 @@ import dagger.Module
ExternalPaymentMethodConfirmationModule::class,
GooglePayConfirmationModule::class,
LinkConfirmationModule::class,
+ LinkExpressConfirmationModule::class
]
)
internal interface PaymentElementConfirmationModule
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationDefinition.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationDefinition.kt
index 7e47161ee93..62a14f9310f 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationDefinition.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationDefinition.kt
@@ -50,7 +50,10 @@ internal class LinkConfirmationDefinition(
confirmationOption: LinkConfirmationOption,
confirmationParameters: ConfirmationDefinition.Parameters,
) {
- launcher.present(confirmationOption.configuration)
+ launcher.present(
+ configuration = confirmationOption.configuration,
+ linkAccount = confirmationOption.linkAccount
+ )
}
override fun toResult(
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationOption.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationOption.kt
index 6f0db93e443..f918162321f 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationOption.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationOption.kt
@@ -1,10 +1,12 @@
package com.stripe.android.paymentelement.confirmation.link
import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.model.LinkAccount
import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
import kotlinx.parcelize.Parcelize
@Parcelize
internal data class LinkConfirmationOption(
val configuration: LinkConfiguration,
+ val linkAccount: LinkAccount?
) : ConfirmationHandler.Option
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationDefinition.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationDefinition.kt
new file mode 100644
index 00000000000..3b8b9d526f0
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationDefinition.kt
@@ -0,0 +1,87 @@
+package com.stripe.android.paymentelement.confirmation.linkexpress
+
+import androidx.activity.result.ActivityResultCaller
+import com.stripe.android.common.exception.stripeErrorMessage
+import com.stripe.android.link.express.LinkExpressLauncher
+import com.stripe.android.link.express.LinkExpressResult
+import com.stripe.android.paymentelement.confirmation.ConfirmationDefinition
+import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
+import com.stripe.android.paymentelement.confirmation.intent.DeferredIntentConfirmationType
+import com.stripe.android.paymentelement.confirmation.link.LinkConfirmationOption
+
+internal class LinkExpressConfirmationDefinition(
+ private val linkExpressLauncher: LinkExpressLauncher,
+) : ConfirmationDefinition {
+ override val key: String = "LinkExpress"
+
+ override fun option(confirmationOption: ConfirmationHandler.Option): LinkExpressConfirmationOption? {
+ return confirmationOption as? LinkExpressConfirmationOption
+ }
+
+ override fun createLauncher(
+ activityResultCaller: ActivityResultCaller,
+ onResult: (LinkExpressResult) -> Unit
+ ): LinkExpressLauncher {
+ return linkExpressLauncher.apply {
+ register(activityResultCaller, onResult)
+ }
+ }
+
+ override fun unregister(launcher: LinkExpressLauncher) {
+ launcher.unregister()
+ }
+
+ override suspend fun action(
+ confirmationOption: LinkExpressConfirmationOption,
+ confirmationParameters: ConfirmationDefinition.Parameters,
+ ): ConfirmationDefinition.Action {
+ return ConfirmationDefinition.Action.Launch(
+ launcherArguments = Unit,
+ receivesResultInProcess = false,
+ deferredIntentConfirmationType = null,
+ )
+ }
+
+ override fun launch(
+ launcher: LinkExpressLauncher,
+ arguments: Unit,
+ confirmationOption: LinkExpressConfirmationOption,
+ confirmationParameters: ConfirmationDefinition.Parameters,
+ ) {
+ launcher.present(
+ configuration = confirmationOption.configuration,
+ linkAccount = confirmationOption.linkAccount
+ )
+ }
+
+ override fun toResult(
+ confirmationOption: LinkExpressConfirmationOption,
+ confirmationParameters: ConfirmationDefinition.Parameters,
+ deferredIntentConfirmationType: DeferredIntentConfirmationType?,
+ result: LinkExpressResult
+ ): ConfirmationDefinition.Result {
+ return when (result) {
+ is LinkExpressResult.Authenticated -> {
+ ConfirmationDefinition.Result.NextStep(
+ parameters = confirmationParameters,
+ confirmationOption = LinkConfirmationOption(
+ configuration = confirmationOption.configuration,
+ linkAccount = result.linkAccount
+ )
+ )
+ }
+ LinkExpressResult.Canceled -> {
+ ConfirmationDefinition.Result.Canceled(
+ action = ConfirmationHandler.Result.Canceled.Action.InformCancellation,
+ )
+ }
+ is LinkExpressResult.Failed -> {
+ ConfirmationDefinition.Result.Failed(
+ cause = result.error,
+ message = result.error.stripeErrorMessage(),
+ type = ConfirmationHandler.Result.Failed.ErrorType.Payment,
+ )
+ }
+ }
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationModule.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationModule.kt
new file mode 100644
index 00000000000..483db7685f7
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationModule.kt
@@ -0,0 +1,26 @@
+package com.stripe.android.paymentelement.confirmation.linkexpress
+
+import com.stripe.android.link.express.LinkExpressLauncher
+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 LinkExpressConfirmationModule {
+ @JvmSuppressWildcards
+ @Provides
+ @IntoSet
+ fun providesLinkConfirmationDefinition(
+ linkExpressLauncher: LinkExpressLauncher,
+ ): ConfirmationDefinition<*, *, *, *> {
+ return LinkExpressConfirmationDefinition(
+ linkExpressLauncher,
+ )
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationOption.kt b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationOption.kt
new file mode 100644
index 00000000000..5b73183809d
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentelement/confirmation/linkexpress/LinkExpressConfirmationOption.kt
@@ -0,0 +1,12 @@
+package com.stripe.android.paymentelement.confirmation.linkexpress
+
+import com.stripe.android.link.LinkConfiguration
+import com.stripe.android.link.model.LinkAccount
+import com.stripe.android.paymentelement.confirmation.ConfirmationHandler
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+internal data class LinkExpressConfirmationOption(
+ val configuration: LinkConfiguration,
+ val linkAccount: LinkAccount
+) : ConfirmationHandler.Option
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..6f739b0a0ab 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/LinkHandler.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/LinkHandler.kt
@@ -9,6 +9,7 @@ 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.model.LinkAccount
import com.stripe.android.link.ui.inline.UserInput
import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.PaymentMethod
@@ -18,6 +19,7 @@ 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.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
@@ -59,7 +61,9 @@ internal class LinkHandler @Inject constructor(
linkAnalyticsComponentBuilder.build().linkAnalyticsHelper
}
- fun setupLink(state: LinkState?) {
+ fun setupLink(
+ state: LinkState?,
+ ) {
_isLinkEnabled.value = state != null
if (state == null) return
@@ -67,6 +71,36 @@ internal class LinkHandler @Inject constructor(
_linkConfiguration.value = state.configuration
}
+ fun setupLinkLaunchEagerly(
+ coroutineScope: CoroutineScope,
+ state: LinkState?,
+ launchEagerly: Boolean = false,
+ launchLink: suspend (LinkAccount) -> Unit = {},
+ ) {
+ setupLink(state)
+
+ if (state == null) return
+
+ _linkConfiguration.value = state.configuration
+
+ coroutineScope.launch {
+ if (launchEagerly.not()) return@launch
+ val linkAccount =
+ linkConfigurationCoordinator.getLinkAccountFlow(state.configuration).first() ?: return@launch
+ if (linkAccount.isVerified) {
+ launchLink(linkAccount)
+ }
+ when (linkAccount.accountStatus) {
+ AccountStatus.Verified,
+ AccountStatus.NeedsVerification,
+ AccountStatus.VerificationStarted -> {
+ launchLink(linkAccount)
+ }
+ else -> Unit
+ }
+ }
+ }
+
suspend fun payWithLinkInline(
paymentSelection: PaymentSelection.New.LinkInline,
shouldCompleteLinkInlineFlow: Boolean,
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..9f2b4a21334 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionsViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionsViewModel.kt
@@ -224,7 +224,9 @@ internal class PaymentOptionsViewModel @Inject constructor(
}
is PaymentSelection.Saved,
is PaymentSelection.GooglePay,
- is PaymentSelection.Link -> processExistingPaymentMethod(paymentSelection)
+ is PaymentSelection.Link -> {
+ processExistingPaymentMethod(paymentSelection)
+ }
is PaymentSelection.New -> processNewOrExternalPaymentMethod(paymentSelection)
is PaymentSelection.ExternalPaymentMethod -> processNewOrExternalPaymentMethod(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..5dfe0da5200 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt
@@ -17,6 +17,7 @@ import com.stripe.android.core.Logger
import com.stripe.android.core.exception.StripeException
import com.stripe.android.core.injection.IOContext
import com.stripe.android.core.strings.ResolvableString
+import com.stripe.android.core.utils.FeatureFlags
import com.stripe.android.core.utils.requireApplication
import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
@@ -296,7 +297,18 @@ internal class PaymentSheetViewModel @Inject internal constructor(
setPaymentMethodMetadata(state.paymentMethodMetadata)
- linkHandler.setupLink(state.paymentMethodMetadata.linkState)
+ linkHandler.setupLinkLaunchEagerly(
+ coroutineScope = viewModelScope,
+ state = state.paymentMethodMetadata.linkState,
+ launchEagerly = FeatureFlags.nativeLinkEnabled.isEnabled,
+ launchLink = { linkAccount ->
+ val configuration = state.paymentMethodMetadata.linkState?.configuration ?: return@setupLinkLaunchEagerly
+ val confirmationOption = linkAccount.toConfirmationOption(configuration)
+ confirmationOption?.let {
+ confirmWithConfirmationOption(it)
+ }
+ }
+ )
val pendingFailedPaymentResult = confirmationHandler.awaitResult()
as? ConfirmationHandler.Result.Failed
@@ -477,42 +489,46 @@ internal class PaymentSheetViewModel @Inject internal constructor(
linkConfiguration = linkHandler.linkConfiguration.value,
)
- confirmationOption?.let { option ->
- val stripeIntent = awaitStripeIntent()
-
- confirmationHandler.start(
- arguments = ConfirmationHandler.Args(
- intent = stripeIntent,
- confirmationOption = option,
- initializationMode = args.initializationMode,
- appearance = config.appearance,
- shippingDetails = config.shippingDetails,
- ),
- )
- } ?: run {
- val message = paymentSelection?.let {
- "Cannot confirm using a ${it::class.qualifiedName} payment selection!"
- } ?: "Cannot confirm without a payment selection!"
+ if (confirmationOption != null) {
+ return@launch confirmWithConfirmationOption(confirmationOption)
+ }
- val exception = IllegalStateException(message)
+ val message = paymentSelection?.let {
+ "Cannot confirm using a ${it::class.qualifiedName} payment selection!"
+ } ?: "Cannot confirm without a payment selection!"
- val event = paymentSelection?.let {
- ErrorReporter.UnexpectedErrorEvent.PAYMENT_SHEET_INVALID_PAYMENT_SELECTION_ON_CHECKOUT
- } ?: ErrorReporter.UnexpectedErrorEvent.PAYMENT_SHEET_NO_PAYMENT_SELECTION_ON_CHECKOUT
+ val exception = IllegalStateException(message)
- errorReporter.report(event, StripeException.create(exception))
+ val event = paymentSelection?.let {
+ ErrorReporter.UnexpectedErrorEvent.PAYMENT_SHEET_INVALID_PAYMENT_SELECTION_ON_CHECKOUT
+ } ?: ErrorReporter.UnexpectedErrorEvent.PAYMENT_SHEET_NO_PAYMENT_SELECTION_ON_CHECKOUT
- processIntentResult(
- ConfirmationHandler.Result.Failed(
- cause = exception,
- message = exception.stripeErrorMessage(),
- type = ConfirmationHandler.Result.Failed.ErrorType.Internal,
- )
+ errorReporter.report(event, StripeException.create(exception))
+
+ processIntentResult(
+ ConfirmationHandler.Result.Failed(
+ cause = exception,
+ message = exception.stripeErrorMessage(),
+ type = ConfirmationHandler.Result.Failed.ErrorType.Internal,
)
- }
+ )
}
}
+ private suspend fun confirmWithConfirmationOption(confirmationOption: ConfirmationHandler.Option) {
+ val stripeIntent = awaitStripeIntent()
+
+ confirmationHandler.start(
+ arguments = ConfirmationHandler.Args(
+ intent = stripeIntent,
+ confirmationOption = confirmationOption,
+ initializationMode = args.initializationMode,
+ appearance = config.appearance,
+ shippingDetails = config.shippingDetails,
+ ),
+ )
+ }
+
override fun onPaymentResult(paymentResult: PaymentResult) {
viewModelScope.launch(workContext) {
val stripeIntent = awaitStripeIntent()
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..6bfe8d8f7e9 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
@@ -383,6 +383,7 @@ internal val PaymentSelection.paymentMethodType: String
PaymentSelection.GooglePay -> "google_pay"
PaymentSelection.Link -> "link"
is PaymentSelection.New -> paymentMethodCreateParams.typeCode
+ PaymentSelection.Link -> "link"
is PaymentSelection.Saved -> paymentMethod.type?.name ?: "card"
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/ui/SelectSavedPaymentMethodsInteractor.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/ui/SelectSavedPaymentMethodsInteractor.kt
index 9dfd22e359f..f4381db497e 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/ui/SelectSavedPaymentMethodsInteractor.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/ui/SelectSavedPaymentMethodsInteractor.kt
@@ -170,10 +170,18 @@ internal class DefaultSelectSavedPaymentMethodsInteractor(
paymentOptionsItems: List,
): PaymentOptionsItem? {
val paymentSelection = when (selection) {
- is PaymentSelection.Saved, PaymentSelection.Link, PaymentSelection.GooglePay -> selection
+ is PaymentSelection.Saved,
+ PaymentSelection.Link,
+ PaymentSelection.GooglePay -> {
+ selection
+ }
- is PaymentSelection.New, is PaymentSelection.ExternalPaymentMethod, null -> savedSelection?.let {
- PaymentSelection.Saved(it)
+ is PaymentSelection.New,
+ is PaymentSelection.ExternalPaymentMethod,
+ null -> {
+ savedSelection?.let {
+ PaymentSelection.Saved(it)
+ }
}
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/link/LinkActivityContractTest.kt b/paymentsheet/src/test/java/com/stripe/android/link/LinkActivityContractTest.kt
index 3f4a69ad313..6e6c9becd49 100644
--- a/paymentsheet/src/test/java/com/stripe/android/link/LinkActivityContractTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/link/LinkActivityContractTest.kt
@@ -20,7 +20,10 @@ import org.robolectric.RobolectricTestRunner
class LinkActivityContractTest {
private val context: Context = ApplicationProvider.getApplicationContext()
- private val args = LinkActivityContract.Args(TestFactory.LINK_CONFIGURATION)
+ private val args = LinkActivityContract.Args(
+ configuration = TestFactory.LINK_CONFIGURATION,
+ linkAccount = null
+ )
@get:Rule
val featureFlagTestRule = FeatureFlagTestRule(
diff --git a/paymentsheet/src/test/java/com/stripe/android/link/LinkActivityViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/link/LinkActivityViewModelTest.kt
index 0cbf72e2b78..071225a44bf 100644
--- a/paymentsheet/src/test/java/com/stripe/android/link/LinkActivityViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/link/LinkActivityViewModelTest.kt
@@ -146,7 +146,8 @@ internal class LinkActivityViewModelTest {
val mockArgs = NativeLinkArgs(
configuration = mock(),
publishableKey = "",
- stripeAccountId = null
+ stripeAccountId = null,
+ linkAccount = null
)
val savedStateHandle = SavedStateHandle()
val factory = LinkActivityViewModel.factory(savedStateHandle)
diff --git a/paymentsheet/src/test/java/com/stripe/android/link/LinkPaymentLauncherTest.kt b/paymentsheet/src/test/java/com/stripe/android/link/LinkPaymentLauncherTest.kt
index b77b0191f75..99619b13eae 100644
--- a/paymentsheet/src/test/java/com/stripe/android/link/LinkPaymentLauncherTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/link/LinkPaymentLauncherTest.kt
@@ -82,11 +82,11 @@ internal class LinkPaymentLauncherTest {
val registerCall = awaitRegisterCall()
assertThat(registerCall).isNotNull()
- linkPaymentLauncher.present(TestFactory.LINK_CONFIGURATION)
+ linkPaymentLauncher.present(TestFactory.LINK_CONFIGURATION, null)
val launchCall = awaitLaunchCall()
- assertThat(launchCall).isEqualTo(LinkActivityContract.Args(TestFactory.LINK_CONFIGURATION))
+ assertThat(launchCall).isEqualTo(LinkActivityContract.Args(TestFactory.LINK_CONFIGURATION, null))
awaitNextRegisteredLauncher()
}
@@ -172,7 +172,7 @@ internal class LinkPaymentLauncherTest {
var callbackParam: LinkActivityResult? = null
linkPaymentLauncher.register(activityResultRegistry) { callbackParam = it }
- linkPaymentLauncher.present(TestFactory.LINK_CONFIGURATION)
+ linkPaymentLauncher.present(TestFactory.LINK_CONFIGURATION, null)
verifyActivityResultCallback(
linkActivityResult = linkActivityResult,
@@ -199,7 +199,7 @@ internal class LinkPaymentLauncherTest {
var callbackParam: LinkActivityResult? = null
linkPaymentLauncher.register(activityResultCaller) { callbackParam = it }
- linkPaymentLauncher.present(TestFactory.LINK_CONFIGURATION)
+ linkPaymentLauncher.present(TestFactory.LINK_CONFIGURATION, null)
val registerCall = awaitRegisterCall()
registerCall.callback.asCallbackFor().onActivityResult(linkActivityResult)
diff --git a/paymentsheet/src/test/java/com/stripe/android/link/NativeLinkActivityContractTest.kt b/paymentsheet/src/test/java/com/stripe/android/link/NativeLinkActivityContractTest.kt
index e43c3fb3d03..3420c70da7f 100644
--- a/paymentsheet/src/test/java/com/stripe/android/link/NativeLinkActivityContractTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/link/NativeLinkActivityContractTest.kt
@@ -33,7 +33,7 @@ class NativeLinkActivityContractTest {
@Test
fun `intent is created correctly`() {
val contract = NativeLinkActivityContract()
- val args = LinkActivityContract.Args(TestFactory.LINK_CONFIGURATION)
+ val args = LinkActivityContract.Args(TestFactory.LINK_CONFIGURATION, null)
val intent = contract.createIntent(ApplicationProvider.getApplicationContext(), args)
@@ -46,7 +46,8 @@ class NativeLinkActivityContractTest {
NativeLinkArgs(
configuration = TestFactory.LINK_CONFIGURATION,
publishableKey = "pk_test_abcdefg",
- stripeAccountId = null
+ stripeAccountId = null,
+ linkAccount = null
)
)
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/link/WebLinkActivityContractTest.kt b/paymentsheet/src/test/java/com/stripe/android/link/WebLinkActivityContractTest.kt
index 37b5aff9747..0dfe05b551a 100644
--- a/paymentsheet/src/test/java/com/stripe/android/link/WebLinkActivityContractTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/link/WebLinkActivityContractTest.kt
@@ -39,7 +39,7 @@ class WebLinkActivityContractTest {
override fun buildPaymentUserAgent(attribution: Set) = "test"
}
val contract = contract(stripeRepository)
- val args = LinkActivityContract.Args(TestFactory.LINK_CONFIGURATION)
+ val args = LinkActivityContract.Args(TestFactory.LINK_CONFIGURATION, null)
val intent = contract.createIntent(ApplicationProvider.getApplicationContext(), args)
diff --git a/paymentsheet/src/test/java/com/stripe/android/link/account/FakeLinkAccountManager.kt b/paymentsheet/src/test/java/com/stripe/android/link/account/FakeLinkAccountManager.kt
index fa64f8963ea..c35ac96470a 100644
--- a/paymentsheet/src/test/java/com/stripe/android/link/account/FakeLinkAccountManager.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/link/account/FakeLinkAccountManager.kt
@@ -23,11 +23,11 @@ internal open class FakeLinkAccountManager : LinkAccountManager {
override val accountStatus: Flow = _accountStatus
var lookupConsumerResult: Result = Result.success(null)
- var startVerificationResult: Result = Result.success(LinkAccount(ConsumerSession("", "", "", "")))
- var confirmVerificationResult: Result = Result.success(LinkAccount(ConsumerSession("", "", "", "")))
- var signUpResult: Result = Result.success(LinkAccount(ConsumerSession("", "", "", "")))
- var signInWithUserInputResult: Result = Result.success(LinkAccount(ConsumerSession("", "", "", "")))
- var logOutResult: Result = Result.success(ConsumerSession("", "", "", ""))
+ var startVerificationResult: Result = Result.success(TestFactory.LINK_ACCOUNT)
+ var confirmVerificationResult: Result = Result.success(TestFactory.LINK_ACCOUNT)
+ var signUpResult: Result = Result.success(TestFactory.LINK_ACCOUNT)
+ var signInWithUserInputResult: Result = Result.success(TestFactory.LINK_ACCOUNT)
+ var logOutResult: Result = Result.success(TestFactory.CONSUMER_SESSION)
var createCardPaymentDetailsResult: Result = Result.success(
value = TestFactory.LINK_NEW_PAYMENT_DETAILS
)
diff --git a/paymentsheet/src/test/java/com/stripe/android/link/ui/verification/VerificationScreenTest.kt b/paymentsheet/src/test/java/com/stripe/android/link/ui/verification/VerificationScreenTest.kt
index b0c9f21fc47..3709c714d92 100644
--- a/paymentsheet/src/test/java/com/stripe/android/link/ui/verification/VerificationScreenTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/link/ui/verification/VerificationScreenTest.kt
@@ -153,8 +153,9 @@ internal class VerificationScreenTest {
linkAccountManager = linkAccountManager,
linkEventsReporter = linkEventsReporter,
logger = logger,
- navigateAndClearStack = {},
- goBack = {},
+ onVerificationSucceeded = {},
+ onChangeEmailClicked = {},
+ onDismissClicked = {},
linkAccount = TestFactory.LINK_ACCOUNT
)
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/link/ui/verification/VerificationViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/link/ui/verification/VerificationViewModelTest.kt
index bdd7a67a2f1..36e3201e83d 100644
--- a/paymentsheet/src/test/java/com/stripe/android/link/ui/verification/VerificationViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/link/ui/verification/VerificationViewModelTest.kt
@@ -4,7 +4,6 @@ import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import com.stripe.android.core.Logger
import com.stripe.android.core.strings.resolvableString
-import com.stripe.android.link.LinkScreen
import com.stripe.android.link.TestFactory
import com.stripe.android.link.account.FakeLinkAccountManager
import com.stripe.android.link.account.LinkAccountManager
@@ -60,17 +59,16 @@ internal class VerificationViewModelTest {
@Test
fun `When confirmVerification succeeds then it navigates to Wallet`() =
runTest(dispatcher) {
- val screens = arrayListOf()
- fun navigateAndClearStack(screen: LinkScreen) {
- screens.add(screen)
- }
+ var linkAccountCall: LinkAccount? = null
val viewModel = createViewModel(
- navigateAndClearStack = ::navigateAndClearStack
+ onVerificationSucceeded = { linkAccount ->
+ linkAccountCall = linkAccount
+ }
)
viewModel.onVerificationCodeEntered("code")
- assertThat(screens).isEqualTo(listOf(LinkScreen.Wallet))
+ assertThat(linkAccountCall).isEqualTo(TestFactory.LINK_ACCOUNT)
}
@Test
@@ -122,18 +120,17 @@ internal class VerificationViewModelTest {
}
}
- val navScreens = arrayListOf()
- fun navigateAndClearStack(screen: LinkScreen) {
- navScreens.add(screen)
- }
+ var changeEmailCount = 0
createViewModel(
linkAccountManager = linkAccountManager,
- navigateAndClearStack = ::navigateAndClearStack
- ).onChangeEmailClicked()
+ onChangeEmailClicked = {
+ changeEmailCount += 1
+ },
+ ).onChangeEmailButtonClicked()
assertThat(linkAccountManager.callCount).isEqualTo(1)
- assertThat(navScreens).isEqualTo(listOf(LinkScreen.SignUp))
+ assertThat(changeEmailCount).isEqualTo(1)
}
@Test
@@ -213,15 +210,17 @@ internal class VerificationViewModelTest {
linkAccountManager: LinkAccountManager = FakeLinkAccountManager(),
linkEventsReporter: LinkEventsReporter = FakeLinkEventsReporter(),
logger: Logger = FakeLogger(),
- navigateAndClearStack: (LinkScreen) -> Unit = {},
- goBack: () -> Unit = {},
+ onVerificationSucceeded: (LinkAccount) -> Unit = {},
+ onChangeEmailClicked: () -> Unit = {},
+ onDismissClicked: () -> Unit = {},
): VerificationViewModel {
return VerificationViewModel(
linkAccountManager = linkAccountManager,
linkEventsReporter = linkEventsReporter,
logger = logger,
- navigateAndClearStack = navigateAndClearStack,
- goBack = goBack,
+ onVerificationSucceeded = onVerificationSucceeded,
+ onDismissClicked = onDismissClicked,
+ onChangeEmailClicked = onChangeEmailClicked,
linkAccount = TestFactory.LINK_ACCOUNT
)
}
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..8c5bc2bcaee 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
@@ -258,6 +258,7 @@ class ConfirmationHandlerOptionKtxTest {
).isEqualTo(
LinkConfirmationOption(
configuration = LINK_CONFIGURATION,
+ linkAccount = null
)
)
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationActivityTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationActivityTest.kt
index 3bcc6ed80cb..5cf35c3c253 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationActivityTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationActivityTest.kt
@@ -213,6 +213,7 @@ internal class LinkConfirmationActivityTest(private val nativeLinkEnabled: Boole
val LINK_CONFIRMATION_OPTION = LinkConfirmationOption(
configuration = TestFactory.LINK_CONFIGURATION,
+ linkAccount = TestFactory.LINK_ACCOUNT
)
val CONFIRMATION_PARAMETERS = ConfirmationDefinition.Parameters(
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationDefinitionTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationDefinitionTest.kt
index fed2b6733fc..c2037e3656b 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationDefinitionTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationDefinitionTest.kt
@@ -269,6 +269,7 @@ internal class LinkConfirmationDefinitionTest {
private val LINK_CONFIRMATION_OPTION = LinkConfirmationOption(
configuration = TestFactory.LINK_CONFIGURATION,
+ linkAccount = TestFactory.LINK_ACCOUNT
)
}
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationFlowTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationFlowTest.kt
index 219e033a5eb..4d7df624d6e 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationFlowTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentelement/confirmation/link/LinkConfirmationFlowTest.kt
@@ -147,6 +147,7 @@ class LinkConfirmationFlowTest {
private val LINK_CONFIRMATION_OPTION = LinkConfirmationOption(
configuration = TestFactory.LINK_CONFIGURATION,
+ linkAccount = TestFactory.LINK_ACCOUNT
)
}
}
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..5b3ad84a1f0 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
@@ -825,7 +825,7 @@ internal class DefaultFlowControllerTest {
flowController.confirm()
- verify(linkPaymentLauncher).present(any())
+ verify(linkPaymentLauncher).present(any(), anyOrNull())
}
@Test
@@ -1215,7 +1215,7 @@ internal class DefaultFlowControllerTest {
)
flowController.confirm()
- verify(linkPaymentLauncher).present(any())
+ verify(linkPaymentLauncher).present(any(), anyOrNull())
}
@Test
diff --git a/paymentsheet/src/test/java/com/stripe/android/utils/FakeLinkConfigurationCoordinator.kt b/paymentsheet/src/test/java/com/stripe/android/utils/FakeLinkConfigurationCoordinator.kt
index 780f57beac3..c7f95c18400 100644
--- a/paymentsheet/src/test/java/com/stripe/android/utils/FakeLinkConfigurationCoordinator.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/utils/FakeLinkConfigurationCoordinator.kt
@@ -4,8 +4,10 @@ import com.stripe.android.core.model.CountryCode
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.injection.LinkComponent
import com.stripe.android.link.model.AccountStatus
+import com.stripe.android.link.model.LinkAccount
import com.stripe.android.link.ui.inline.UserInput
import com.stripe.android.model.CardBrand
import com.stripe.android.model.ConsumerPaymentDetails
@@ -52,6 +54,10 @@ internal class FakeLinkConfigurationCoordinator(
return flowOf(accountStatus)
}
+ override fun getLinkAccountFlow(configuration: LinkConfiguration): StateFlow {
+ return stateFlowOf(TestFactory.LINK_ACCOUNT)
+ }
+
override suspend fun signInWithUserInput(configuration: LinkConfiguration, userInput: UserInput): Result {
return Result.success(true)
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/utils/RecordingLinkPaymentLauncher.kt b/paymentsheet/src/test/java/com/stripe/android/utils/RecordingLinkPaymentLauncher.kt
index 60b16954177..8479ebe0618 100644
--- a/paymentsheet/src/test/java/com/stripe/android/utils/RecordingLinkPaymentLauncher.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/utils/RecordingLinkPaymentLauncher.kt
@@ -7,6 +7,7 @@ import com.stripe.android.link.LinkActivityResult
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.LinkPaymentLauncher
import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
@@ -37,7 +38,7 @@ internal object RecordingLinkPaymentLauncher {
unregisterCalls.add(Unit)
}
- on { present(any()) } doAnswer { invocation ->
+ on { present(any(), anyOrNull()) } doAnswer { invocation ->
val arguments = invocation.arguments
presentCalls.add(