diff --git a/link/src/main/java/com/stripe/android/link/LinkActivityContract.kt b/link/src/main/java/com/stripe/android/link/LinkActivityContract.kt index e7b967fe3b7..1e3c0141b3e 100644 --- a/link/src/main/java/com/stripe/android/link/LinkActivityContract.kt +++ b/link/src/main/java/com/stripe/android/link/LinkActivityContract.kt @@ -6,7 +6,6 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.annotation.RestrictTo import com.stripe.android.PaymentConfiguration import com.stripe.android.link.serialization.PopupPayload -import com.stripe.android.model.PaymentMethodCreateParams import com.stripe.android.networking.StripeRepository import javax.inject.Inject @@ -34,7 +33,6 @@ class LinkActivityContract @Inject internal constructor( @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) data class Args internal constructor( internal val configuration: LinkConfiguration, - internal val prefilledCardParams: PaymentMethodCreateParams? = null, ) @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) diff --git a/link/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt b/link/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt index 97101cad6cf..68c722aebc1 100644 --- a/link/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt +++ b/link/src/main/java/com/stripe/android/link/LinkPaymentLauncher.kt @@ -6,7 +6,6 @@ import androidx.activity.result.ActivityResultRegistry import androidx.annotation.RestrictTo import com.stripe.android.link.injection.LinkAnalyticsComponent import com.stripe.android.link.ui.paymentmethod.SupportedPaymentMethod -import com.stripe.android.model.PaymentMethodCreateParams import javax.inject.Inject import javax.inject.Singleton @@ -56,16 +55,12 @@ class LinkPaymentLauncher @Inject internal constructor( * Launch the Link UI to process a payment. * * @param configuration The payment and customer settings - * @param prefilledNewCardParams The card information prefilled by the user. If non null, Link - * will launch into adding a new card, with the card information pre-filled. */ fun present( configuration: LinkConfiguration, - prefilledNewCardParams: PaymentMethodCreateParams? = null, ) { val args = LinkActivityContract.Args( configuration, - prefilledNewCardParams, ) linkActivityResultLauncher?.launch(args) analyticsHelper.onLinkLaunched() diff --git a/link/src/main/java/com/stripe/android/link/account/LinkAccountManager.kt b/link/src/main/java/com/stripe/android/link/account/LinkAccountManager.kt index 3f51503a653..b51544387cc 100644 --- a/link/src/main/java/com/stripe/android/link/account/LinkAccountManager.kt +++ b/link/src/main/java/com/stripe/android/link/account/LinkAccountManager.kt @@ -45,8 +45,7 @@ internal class LinkAccountManager @Inject constructor( val accountStatus = linkAccount.map { value -> // If we already fetched an account, return its status value?.accountStatus - // If a customer email was passed in, lookup the account, - // unless the user has logged out of this account + // If a customer email was passed in, lookup the account. ?: config.customerEmail?.let { customerEmail -> lookupConsumer(customerEmail).map { it?.accountStatus diff --git a/link/src/test/java/com/stripe/android/link/LinkActivityContractTest.kt b/link/src/test/java/com/stripe/android/link/LinkActivityContractTest.kt index 35916aab79e..e912b8bdc61 100644 --- a/link/src/test/java/com/stripe/android/link/LinkActivityContractTest.kt +++ b/link/src/test/java/com/stripe/android/link/LinkActivityContractTest.kt @@ -45,7 +45,6 @@ class LinkActivityContractTest { val args = LinkActivityContract.Args( config, - null, ) val stripeRepository = mock() whenever(stripeRepository.buildPaymentUserAgent(any())).thenReturn("test") 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 f924a9180b8..7eea5913a0d 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/LinkHandler.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/LinkHandler.kt @@ -45,6 +45,8 @@ internal class LinkHandler @Inject constructor( data class PaymentMethodCollected(val paymentMethod: PaymentMethod) : ProcessingState() class CompletedWithPaymentResult(val result: PaymentResult) : ProcessingState() + + object CompleteWithoutLink : ProcessingState() } private val _processingState = @@ -102,7 +104,7 @@ internal class LinkHandler @Inject constructor( } AccountStatus.VerificationStarted, AccountStatus.NeedsVerification -> { - linkLauncher.present(configuration, params) + _processingState.emit(ProcessingState.CompleteWithoutLink) } AccountStatus.SignedOut, AccountStatus.Error -> { @@ -137,7 +139,7 @@ internal class LinkHandler @Inject constructor( shouldCompleteLinkInlineFlow: Boolean ) { if (shouldCompleteLinkInlineFlow) { - launchLink(configuration, paymentMethodCreateParams) + _processingState.emit(ProcessingState.CompleteWithoutLink) } else { _processingState.emit( ProcessingState.PaymentDetailsCollected( @@ -152,16 +154,9 @@ internal class LinkHandler @Inject constructor( fun launchLink() { val config = linkConfiguration.value ?: return - launchLink(config) - } - fun launchLink( - configuration: LinkConfiguration, - paymentMethodCreateParams: PaymentMethodCreateParams? = null - ) { linkLauncher.present( - configuration, - paymentMethodCreateParams, + config, ) _processingState.tryEmit(ProcessingState.Launched) 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 22f513412a2..4ff08f6d0a0 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionsViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentOptionsViewModel.kt @@ -173,6 +173,9 @@ internal class PaymentOptionsViewModel @Inject constructor( LinkHandler.ProcessingState.Started -> { updatePrimaryButtonState(PrimaryButton.State.StartProcessing) } + LinkHandler.ProcessingState.CompleteWithoutLink -> { + onUserSelection() + } } } 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 276545ad5af..dc7faf83fa1 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheetViewModel.kt @@ -278,6 +278,9 @@ internal class PaymentSheetViewModel @Inject internal constructor( LinkHandler.ProcessingState.Started -> { updatePrimaryButtonState(PrimaryButton.State.StartProcessing) } + LinkHandler.ProcessingState.CompleteWithoutLink -> { + checkout() + } } } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowController.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowController.kt index a8532b5c508..c2d4fbf6a05 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowController.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/flowcontroller/DefaultFlowController.kt @@ -19,9 +19,7 @@ import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncherContractV2 import com.stripe.android.googlepaylauncher.injection.GooglePayPaymentMethodLauncherFactory import com.stripe.android.link.LinkActivityResult -import com.stripe.android.link.LinkConfigurationCoordinator import com.stripe.android.link.LinkPaymentLauncher -import com.stripe.android.link.model.AccountStatus import com.stripe.android.model.ConfirmPaymentIntentParams import com.stripe.android.model.ConfirmSetupIntentParams import com.stripe.android.model.ConfirmStripeIntentParams @@ -54,7 +52,6 @@ import com.stripe.android.paymentsheet.state.PaymentSheetState import com.stripe.android.utils.AnimationConstants import dagger.Lazy import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import javax.inject.Inject @@ -84,7 +81,6 @@ internal class DefaultFlowController @Inject internal constructor( @Named(PRODUCT_USAGE) private val productUsage: Set, private val googlePayPaymentMethodLauncherFactory: GooglePayPaymentMethodLauncherFactory, private val linkLauncher: LinkPaymentLauncher, - private val linkConfigurationCoordinator: LinkConfigurationCoordinator, private val configurationHandler: FlowControllerConfigurationHandler, private val intentConfirmationInterceptor: IntentConfirmationInterceptor, ) : PaymentSheet.FlowController { @@ -516,26 +512,12 @@ internal class DefaultFlowController @Inject internal constructor( ) { val linkConfig = requireNotNull(state.linkState).configuration - viewModelScope.launch { - val accountStatus = linkConfigurationCoordinator.getAccountStatusFlow(linkConfig).first() - - val linkInline = (paymentSelection as? PaymentSelection.New.LinkInline)?.takeIf { - accountStatus == AccountStatus.Verified - } - - if (linkInline != null) { - // If a returning user is paying with a new card inline, launch Link - linkLauncher.present( - configuration = linkConfig, - prefilledNewCardParams = linkInline.linkPaymentDetails.originalParams, - ) - } else if (paymentSelection is PaymentSelection.Link) { - // User selected Link as the payment method, not inline - linkLauncher.present(linkConfig) - } else { - // New user paying inline, complete without launching Link - confirmPaymentSelection(paymentSelection, state) - } + if (paymentSelection is PaymentSelection.Link) { + // User selected Link as the payment method, not inline + linkLauncher.present(linkConfig) + } else { + // New user paying inline, complete without launching Link + confirmPaymentSelection(paymentSelection, state) } } diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/viewmodels/BaseSheetViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/viewmodels/BaseSheetViewModel.kt index 90e9ed189b5..f418a0025e3 100644 --- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/viewmodels/BaseSheetViewModel.kt +++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/viewmodels/BaseSheetViewModel.kt @@ -433,7 +433,7 @@ internal abstract class BaseSheetViewModel( abstract val shouldCompleteLinkFlowInline: Boolean - fun payWithLinkInline(userInput: UserInput?) { + private fun payWithLinkInline(userInput: UserInput?) { viewModelScope.launch { linkHandler.payWithLinkInline( userInput = userInput, 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 52afe582841..5f7595124aa 100644 --- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/LinkHandlerTest.kt +++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/LinkHandlerTest.kt @@ -44,17 +44,6 @@ class LinkHandlerTest { assertThat(savedStateHandle.get(SAVE_SELECTION)).isNull() } - @Test - fun `launchLink presents with configuration`() = runLinkTest { - handler.launchLink(configuration) - - assertThat(processingStateTurbine.awaitItem()).isEqualTo(LinkHandler.ProcessingState.Launched) - processingStateTurbine.ensureAllEventsConsumed() - - verify(linkLauncher).present(configuration, null) - verifyNoMoreInteractions(linkLauncher) - } - @Test fun `Completed result sets processing state to Completed`() = runLinkTest { handler.setupLink( @@ -62,6 +51,8 @@ class LinkHandlerTest { ) handler.launchLink() assertThat(processingStateTurbine.awaitItem()).isEqualTo(LinkHandler.ProcessingState.Launched) + verify(linkLauncher).present(configuration) + verifyNoMoreInteractions(linkLauncher) handler.onLinkActivityResult(LinkActivityResult.Completed(mock())) assertThat(processingStateTurbine.awaitItem()).isInstanceOf( LinkHandler.ProcessingState.PaymentMethodCollected::class.java @@ -127,8 +118,8 @@ class LinkHandlerTest { handler.payWithLinkInline(userInput, cardSelection(), shouldCompleteLinkFlow) } assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started) - assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Launched) - verify(linkLauncher).present(eq(configuration), any()) + assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.CompleteWithoutLink) + verify(linkLauncher, never()).present(eq(configuration)) } handler.accountStatus.test { @@ -160,7 +151,7 @@ class LinkHandlerTest { } assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started) assertThat(awaitItem()).isInstanceOf(LinkHandler.ProcessingState.PaymentDetailsCollected::class.java) - verify(linkLauncher, never()).present(eq(configuration), any()) + verify(linkLauncher, never()).present(eq(configuration)) } handler.accountStatus.test { @@ -171,6 +162,34 @@ class LinkHandlerTest { accountStatusTurbine.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 = LinkState( + loginState = LinkState.LoginState.LoggedOut, + configuration = configuration, + ) + ) + + handler.processingState.test { + accountStatusFlow.emit(AccountStatus.NeedsVerification) + ensureAllEventsConsumed() // Begin with no events. + testScope.launch { + handler.payWithLinkInline(userInput, cardSelection(), shouldCompleteLinkFlow) + } + assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started) + assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.CompleteWithoutLink) + verify(linkLauncher, never()).present(eq(configuration)) + } + + processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above. + accountStatusTurbine.cancelAndIgnoreRemainingEvents() // Validated above. + } + @Test fun `payWithLinkInline completes successfully for signedOut user in complete flow`() = runLinkInlineTest( MutableSharedFlow(replay = 0), @@ -197,9 +216,9 @@ class LinkHandlerTest { assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started) assertThat(accountStatusTurbine.awaitItem()).isEqualTo(AccountStatus.SignedOut) accountStatusFlow.emit(AccountStatus.Verified) - assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Launched) + assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.CompleteWithoutLink) assertThat(accountStatusTurbine.awaitItem()).isEqualTo(AccountStatus.Verified) - verify(linkLauncher).present(eq(configuration), any()) + verify(linkLauncher, never()).present(eq(configuration)) } processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above. @@ -235,7 +254,7 @@ class LinkHandlerTest { accountStatusFlow.emit(AccountStatus.Verified) assertThat(awaitItem()).isInstanceOf(LinkHandler.ProcessingState.PaymentDetailsCollected::class.java) assertThat(accountStatusTurbine.awaitItem()).isEqualTo(AccountStatus.Verified) - verify(linkLauncher, never()).present(eq(configuration), any()) + verify(linkLauncher, never()).present(eq(configuration)) } processingStateTurbine.cancelAndIgnoreRemainingEvents() // Validated above. @@ -263,7 +282,7 @@ class LinkHandlerTest { assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started) assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Error("Whoops")) assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Ready) - verify(linkLauncher, never()).present(eq(configuration), any()) + verify(linkLauncher, never()).present(eq(configuration)) } handler.accountStatus.test { @@ -291,7 +310,7 @@ class LinkHandlerTest { } assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Started) assertThat(awaitItem()).isEqualTo(LinkHandler.ProcessingState.Ready) - verify(linkLauncher, never()).present(eq(configuration), any()) + verify(linkLauncher, never()).present(eq(configuration)) } handler.accountStatus.test { 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 a2e6992036b..1ec31d86207 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 @@ -20,10 +20,8 @@ import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncherContra import com.stripe.android.googlepaylauncher.injection.GooglePayPaymentMethodLauncherFactory import com.stripe.android.link.LinkActivityContract import com.stripe.android.link.LinkActivityResult -import com.stripe.android.link.LinkConfigurationCoordinator import com.stripe.android.link.LinkPaymentDetails import com.stripe.android.link.LinkPaymentLauncher -import com.stripe.android.link.model.AccountStatus import com.stripe.android.model.Address import com.stripe.android.model.CardBrand import com.stripe.android.model.ConfirmPaymentIntentParams @@ -68,7 +66,6 @@ import com.stripe.android.utils.IntentConfirmationInterceptorTestRule import com.stripe.android.utils.RelayingPaymentSheetLoader import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.resetMain @@ -126,7 +123,6 @@ internal class DefaultFlowControllerTest { mock>() private val linkPaymentLauncher = mock() - private val linkConfigurationCoordinator = mock() private val lifeCycleOwner = mock() @@ -601,7 +597,6 @@ internal class DefaultFlowControllerTest { @Test fun `confirmPaymentSelection() with link payment method should launch LinkPaymentLauncher`() = runTest { - whenever(linkConfigurationCoordinator.getAccountStatusFlow(any())).thenReturn(flowOf(AccountStatus.Verified)) val flowController = createFlowController( paymentSelection = PaymentSelection.Link, stripeIntent = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD.copy( @@ -615,45 +610,11 @@ internal class DefaultFlowControllerTest { flowController.confirm() - verify(linkPaymentLauncher).present(any(), isNull()) - } - - @Test - fun `confirmPaymentSelection() with LinkInline and user signed in should launch LinkPaymentLauncher`() = runTest { - whenever(linkConfigurationCoordinator.getAccountStatusFlow(any())).thenReturn(flowOf(AccountStatus.Verified)) - - val flowController = createFlowController( - paymentSelection = PaymentSelection.Link, - stripeIntent = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD.copy( - paymentMethodTypes = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD.paymentMethodTypes.plus("link") - ) - ) - - flowController.configureExpectingSuccess( - configuration = PaymentSheetFixtures.CONFIG_CUSTOMER_WITH_GOOGLEPAY - ) - - flowController.onPaymentOptionResult( - PaymentOptionResult.Succeeded( - PaymentSelection.New.LinkInline( - LinkPaymentDetails.New( - PaymentDetailsFixtures.CONSUMER_SINGLE_PAYMENT_DETAILS.paymentDetails.first(), - mock(), - PaymentMethodCreateParamsFixtures.DEFAULT_CARD - ) - ) - ) - ) - - flowController.confirm() - - verify(linkPaymentLauncher).present(any(), eq(PaymentMethodCreateParamsFixtures.DEFAULT_CARD)) + verify(linkPaymentLauncher).present(any()) } @Test fun `confirmPaymentSelection() with LinkInline and user not signed in should confirm with PaymentLauncher`() = runTest { - whenever(linkConfigurationCoordinator.getAccountStatusFlow(any())).thenReturn(flowOf(AccountStatus.SignedOut)) - val flowController = createFlowController( paymentSelection = PaymentSelection.Link, stripeIntent = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD.copy( @@ -693,8 +654,6 @@ internal class DefaultFlowControllerTest { @Test fun `confirmPaymentSelection() with Link and shipping should have shipping details in confirm params`() = runTest { - whenever(linkConfigurationCoordinator.getAccountStatusFlow(any())).thenReturn(flowOf(AccountStatus.SignedOut)) - val flowController = createFlowController( paymentSelection = PaymentSelection.Link, stripeIntent = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD.copy( @@ -745,8 +704,6 @@ internal class DefaultFlowControllerTest { @Test fun `confirmPaymentSelection() with Link and no shipping should not have shipping details in confirm params`() = runTest { - whenever(linkConfigurationCoordinator.getAccountStatusFlow(any())).thenReturn(flowOf(AccountStatus.SignedOut)) - val flowController = createFlowController( paymentSelection = PaymentSelection.Link, stripeIntent = PaymentIntentFixtures.PI_REQUIRES_PAYMENT_METHOD.copy( @@ -873,7 +830,6 @@ internal class DefaultFlowControllerTest { @Test fun `confirmPayment() with Link should launch Link`() = runTest { - whenever(linkConfigurationCoordinator.getAccountStatusFlow(any())).thenReturn(flowOf(AccountStatus.Verified)) val flowController = createFlowController() flowController.configureExpectingSuccess( @@ -884,7 +840,7 @@ internal class DefaultFlowControllerTest { ) flowController.confirm() - verify(linkPaymentLauncher).present(any(), anyOrNull()) + verify(linkPaymentLauncher).present(any()) } @Test @@ -1391,7 +1347,6 @@ internal class DefaultFlowControllerTest { productUsage = PRODUCT_USAGE, googlePayPaymentMethodLauncherFactory = createGooglePayPaymentMethodLauncherFactory(), linkLauncher = linkPaymentLauncher, - linkConfigurationCoordinator = linkConfigurationCoordinator, configurationHandler = FlowControllerConfigurationHandler( paymentSheetLoader = paymentSheetLoader, uiContext = testDispatcher,