Skip to content

Commit

Permalink
Update link inline checkbox UX for redesign. (#6974)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaynewstrom-stripe authored Jul 7, 2023
1 parent 266e7a2 commit a9cf650
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ class LinkActivityContractTest {

val args = LinkActivityContract.Args(
config,
null,
)
val stripeRepository = mock<StripeRepository>()
whenever(stripeRepository.buildPaymentUserAgent(any())).thenReturn("test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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 -> {
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ internal class PaymentOptionsViewModel @Inject constructor(
LinkHandler.ProcessingState.Started -> {
updatePrimaryButtonState(PrimaryButton.State.StartProcessing)
}
LinkHandler.ProcessingState.CompleteWithoutLink -> {
onUserSelection()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ internal class PaymentSheetViewModel @Inject internal constructor(
LinkHandler.ProcessingState.Started -> {
updatePrimaryButtonState(PrimaryButton.State.StartProcessing)
}
LinkHandler.ProcessingState.CompleteWithoutLink -> {
checkout()
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -84,7 +81,6 @@ internal class DefaultFlowController @Inject internal constructor(
@Named(PRODUCT_USAGE) private val productUsage: Set<String>,
private val googlePayPaymentMethodLauncherFactory: GooglePayPaymentMethodLauncherFactory,
private val linkLauncher: LinkPaymentLauncher,
private val linkConfigurationCoordinator: LinkConfigurationCoordinator,
private val configurationHandler: FlowControllerConfigurationHandler,
private val intentConfirmationInterceptor: IntentConfirmationInterceptor,
) : PaymentSheet.FlowController {
Expand Down Expand Up @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,15 @@ class LinkHandlerTest {
assertThat(savedStateHandle.get<PaymentSelection>(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(
LinkState(configuration, LinkState.LoginState.LoggedIn),
)
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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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),
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit a9cf650

Please sign in to comment.