Skip to content

Commit

Permalink
Create consumer session before verification pane
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe committed Aug 13, 2024
1 parent 067813a commit c7f078c
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 387 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,24 @@ internal class LookupConsumerAndStartVerification @Inject constructor(
private val startVerification: StartVerification,
) {

/**
* Looks up a consumer account and starts verification.
*
* If the consumer account exists, starts verification.
* If the consumer account does not exist, calls [onConsumerNotFound].
* If there is an error looking up the consumer account, calls [onLookupError].
* If there is an error starting verification, calls [onStartVerificationError].
* If verification is started successfully, calls [onVerificationStarted].
*/
sealed interface Result {
data class Success(val consumerSession: ConsumerSession) : Result
data object ConsumerNotFound : Result
data class LookupError(val error: Throwable) : Result
data class VerificationError(val error: Throwable) : Result
}

suspend operator fun invoke(
email: String,
businessName: String?,
verificationType: VerificationType,
onConsumerNotFound: suspend () -> Unit,
onLookupError: suspend (Throwable) -> Unit,
onStartVerification: suspend () -> Unit,
onVerificationStarted: suspend (ConsumerSession) -> Unit,
onStartVerificationError: suspend (Throwable) -> Unit
) {
runCatching { lookupAccount(email) }
.onSuccess { session ->
): Result {
return runCatching {
lookupAccount(email)
}.fold(
onSuccess = { session ->
if (session.exists) {
onStartVerification()
kotlin.runCatching {
runCatching {
val consumerSecret = session.consumerSession!!.clientSecret
when (verificationType) {
VerificationType.EMAIL -> startVerification.email(
Expand All @@ -43,12 +37,17 @@ internal class LookupConsumerAndStartVerification @Inject constructor(
consumerSessionClientSecret = consumerSecret
)
}
}
.onSuccess { onVerificationStarted(it) }
.onFailure { onStartVerificationError(it) }
}.fold(
onSuccess = { Result.Success(it) },
onFailure = { Result.VerificationError(it) }
)
} else {
onConsumerNotFound()
Result.ConsumerNotFound
}
}.onFailure { onLookupError(it) }
},
onFailure = {
Result.LookupError(it)
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.stripe.android.financialconnections.domain.ConfirmVerification
import com.stripe.android.financialconnections.domain.GetCachedAccounts
import com.stripe.android.financialconnections.domain.GetOrFetchSync
import com.stripe.android.financialconnections.domain.LookupConsumerAndStartVerification
import com.stripe.android.financialconnections.domain.LookupConsumerAndStartVerification.Result
import com.stripe.android.financialconnections.domain.MarkLinkStepUpVerified
import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator
import com.stripe.android.financialconnections.domain.SelectNetworkedAccounts
Expand Down Expand Up @@ -79,28 +80,31 @@ internal class LinkStepUpVerificationViewModel @AssistedInject constructor(
.onFailure { setState { copy(payload = Fail(it)) } }
.onSuccess { manifest ->
setState { copy(payload = Loading()) }
lookupConsumerAndStartVerification(

val result = lookupConsumerAndStartVerification(
email = requireNotNull(manifest.accountholderCustomerEmailAddress),
businessName = manifest.businessName,
verificationType = VerificationType.EMAIL,
onConsumerNotFound = {
)

when (result) {
is Result.ConsumerNotFound -> {
eventTracker.track(VerificationStepUpError(PANE, ConsumerNotFoundError))
navigationManager.tryNavigateTo(InstitutionPicker(referrer = PANE))
},
onLookupError = { error ->
}
is Result.LookupError -> {
eventTracker.track(VerificationStepUpError(PANE, LookupConsumerSession))
setState { copy(payload = Fail(error)) }
},
onStartVerification = { /* no-op */ },
onVerificationStarted = { consumerSession ->
val payload = buildPayload(consumerSession)
setState { copy(payload = Success(payload)) }
},
onStartVerificationError = { error ->
setState { copy(payload = Fail(result.error)) }
}
is Result.VerificationError -> {
eventTracker.track(VerificationStepUpError(PANE, StartVerificationError))
setState { copy(payload = Fail(error)) }
setState { copy(payload = Fail(result.error)) }
}
)
is Result.Success -> {
val payload = buildPayload(result.consumerSession)
setState { copy(payload = Success(payload)) }
}
}
}

private fun buildPayload(consumerSession: ConsumerSession) = Payload(
Expand Down Expand Up @@ -190,25 +194,30 @@ internal class LinkStepUpVerificationViewModel @AssistedInject constructor(
.onFailure { setState { copy(resendOtp = Fail(it)) } }
.onSuccess { manifest ->
setState { copy(resendOtp = Loading()) }
lookupConsumerAndStartVerification(

val result = lookupConsumerAndStartVerification(
email = requireNotNull(manifest.accountholderCustomerEmailAddress),
businessName = manifest.businessName,
verificationType = VerificationType.EMAIL,
onConsumerNotFound = {
)

when (result) {
is Result.ConsumerNotFound -> {
eventTracker.track(VerificationStepUpError(PANE, ConsumerNotFoundError))
navigationManager.tryNavigateTo(InstitutionPicker(referrer = PANE))
},
onLookupError = { error ->
}
is Result.LookupError -> {
eventTracker.track(VerificationStepUpError(PANE, LookupConsumerSession))
setState { copy(resendOtp = Fail(error)) }
},
onStartVerification = { /* no-op */ },
onVerificationStarted = { setState { copy(resendOtp = Success(Unit)) } },
onStartVerificationError = { error ->
setState { copy(resendOtp = Fail(result.error)) }
}
is Result.VerificationError -> {
eventTracker.track(VerificationStepUpError(PANE, StartVerificationError))
setState { copy(resendOtp = Fail(error)) }
setState { copy(resendOtp = Fail(result.error)) }
}
)
is Result.Success -> {
setState { copy(resendOtp = Success(Unit)) }
}
}
}

@AssistedFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ private fun NetworkingLinkLoginWarmupContent(
},
footer = {
Footer(
loadingPrimary = state.continueAsync is Loading,
loading = state.disableNetworkingAsync is Loading || state.payload() == null,
secondaryButtonLabel = state.secondaryButtonLabel,
onContinueClick = onContinueClick,
Expand Down Expand Up @@ -115,14 +116,15 @@ private fun HeaderSection() {
@Composable
@OptIn(ExperimentalComposeUiApi::class)
private fun Footer(
loadingPrimary: Boolean,
loading: Boolean,
secondaryButtonLabel: Int,
onContinueClick: () -> Unit,
onSkipClicked: () -> Unit
) {
Column {
FinancialConnectionsButton(
loading = false,
loading = loadingPrimary,
enabled = loading.not(),
type = Type.Primary,
onClick = onContinueClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.stripe.android.financialconnections.features.networkinglinkloginwarm

import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.stripe.android.financialconnections.R
Expand All @@ -13,11 +12,13 @@ import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativ
import com.stripe.android.financialconnections.domain.DisableNetworking
import com.stripe.android.financialconnections.domain.GetOrFetchSync
import com.stripe.android.financialconnections.domain.HandleError
import com.stripe.android.financialconnections.domain.LookupAccount
import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator
import com.stripe.android.financialconnections.features.common.getBusinessName
import com.stripe.android.financialconnections.features.common.getRedactedEmail
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane.NETWORKING_LINK_VERIFICATION
import com.stripe.android.financialconnections.navigation.Destination
import com.stripe.android.financialconnections.navigation.Destination.Companion.KEY_NEXT_PANE_ON_DISABLE_NETWORKING
import com.stripe.android.financialconnections.navigation.NavigationManager
Expand All @@ -31,7 +32,6 @@ import com.stripe.android.financialconnections.presentation.FinancialConnections
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch

internal class NetworkingLinkLoginWarmupViewModel @AssistedInject constructor(
@Assisted initialState: NetworkingLinkLoginWarmupState,
Expand All @@ -40,7 +40,8 @@ internal class NetworkingLinkLoginWarmupViewModel @AssistedInject constructor(
private val handleError: HandleError,
private val getOrFetchSync: GetOrFetchSync,
private val disableNetworking: DisableNetworking,
private val navigationManager: NavigationManager
private val navigationManager: NavigationManager,
private val lookupAccount: LookupAccount,
) : FinancialConnectionsViewModel<NetworkingLinkLoginWarmupState>(initialState, nativeAuthFlowCoordinator) {

init {
Expand Down Expand Up @@ -84,9 +85,16 @@ internal class NetworkingLinkLoginWarmupViewModel @AssistedInject constructor(
)
}

fun onContinueClick() = viewModelScope.launch {
eventTracker.track(Click("click.continue", PANE))
navigationManager.tryNavigateTo(Destination.NetworkingLinkVerification(referrer = PANE))
fun onContinueClick() {
val payload = stateFlow.value.payload() ?: return

suspend {
eventTracker.track(Click("click.continue", PANE))
lookupAccount(payload.email)
navigationManager.tryNavigateTo(NETWORKING_LINK_VERIFICATION.destination(referrer = PANE))
}.execute {
copy(continueAsync = it)
}
}

fun onSecondaryButtonClicked() {
Expand All @@ -99,6 +107,38 @@ internal class NetworkingLinkLoginWarmupViewModel @AssistedInject constructor(
}
}

// private suspend fun startVerification(
// payload: NetworkingLinkLoginWarmupState.Payload,
// ): Pane? {
// val verificationResult = lookupConsumerAndStartVerification(
// email = payload.email,
// businessName = payload.merchantName,
// verificationType = VerificationType.SMS,
// )
//
// return when (verificationResult) {
// is Result.ConsumerNotFound -> {
// eventTracker.track(VerificationError(NetworkingLinkVerificationViewModel.PANE, ConsumerNotFoundError))
// Pane.INSTITUTION_PICKER
// }
// is Result.LookupError -> {
// eventTracker.track(VerificationError(NetworkingLinkVerificationViewModel.PANE, LookupConsumerSession))
// setState { copy(payload = Fail(verificationResult.error)) }
// null
// }
// is Result.VerificationError -> {
// eventTracker.track(
// VerificationError(NetworkingLinkVerificationViewModel.PANE, StartVerificationSessionError)
// )
// setState { copy(payload = Fail(verificationResult.error)) }
// null
// }
// is Result.Success -> {
// NETWORKING_LINK_VERIFICATION
// }
// }
// }

private fun skipNetworking() {
suspend {
eventTracker.track(Click("click.skip_sign_in", PANE))
Expand Down Expand Up @@ -159,6 +199,7 @@ internal data class NetworkingLinkLoginWarmupState(
val nextPaneOnDisableNetworking: String? = null,
val payload: Async<Payload> = Uninitialized,
val disableNetworkingAsync: Async<FinancialConnectionsSessionManifest> = Uninitialized,
val continueAsync: Async<Unit> = Uninitialized,
val isInstantDebits: Boolean = false,
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.stripe.android.financialconnections.domain.GetOrFetchSync
import com.stripe.android.financialconnections.domain.GetOrFetchSync.RefetchCondition
import com.stripe.android.financialconnections.domain.LookupAccount
import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator
import com.stripe.android.financialconnections.domain.StartVerification
import com.stripe.android.financialconnections.features.common.getBusinessName
import com.stripe.android.financialconnections.features.networkinglinksignup.NetworkingLinkSignupState.ViewEffect.OpenUrl
import com.stripe.android.financialconnections.features.notice.NoticeSheetState.NoticeSheetContent.Legal
Expand Down Expand Up @@ -61,6 +62,7 @@ internal class NetworkingLinkSignupViewModel @AssistedInject constructor(
@Assisted initialState: NetworkingLinkSignupState,
nativeAuthFlowCoordinator: NativeAuthFlowCoordinator,
private val lookupAccount: LookupAccount,
private val startVerification: StartVerification,
private val uriUtils: UriUtils,
private val eventTracker: FinancialConnectionsAnalyticsTracker,
private val getOrFetchSync: GetOrFetchSync,
Expand Down Expand Up @@ -198,7 +200,15 @@ internal class NetworkingLinkSignupViewModel @AssistedInject constructor(
logger.debug("VALID EMAIL ADDRESS $validEmail.")
searchJob += suspend {
delay(getLookupDelayMs(validEmail))
lookupAccount(validEmail)

val lookup = lookupAccount(validEmail)
val clientSecret = lookup.consumerSession?.clientSecret

if (lookup.exists && clientSecret != null) {
startVerification.sms(consumerSessionClientSecret = clientSecret)
}

lookup
}.execute { copy(lookupAccount = if (it.isCancellationError()) Uninitialized else it) }
} else {
setState { copy(lookupAccount = Uninitialized) }
Expand Down
Loading

0 comments on commit c7f078c

Please sign in to comment.