Skip to content

Commit

Permalink
Support networking relink flow
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe committed Jan 31, 2025
1 parent 05840cd commit 2c010b4
Show file tree
Hide file tree
Showing 14 changed files with 743 additions and 473 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.stripe.android.financialconnections.domain

import com.stripe.android.financialconnections.FinancialConnectionsSheet
import com.stripe.android.financialconnections.di.APPLICATION_ID
import com.stripe.android.financialconnections.model.FinancialConnectionsAuthorizationSession
import com.stripe.android.financialconnections.repository.FinancialConnectionsManifestRepository
import javax.inject.Inject
import javax.inject.Named

internal class RepairAuthorizationSession @Inject constructor(
private val repository: FinancialConnectionsManifestRepository,
private val configuration: FinancialConnectionsSheet.Configuration,
@Named(APPLICATION_ID) private val applicationId: String,
) {

suspend operator fun invoke(
coreAuthorization: String
): FinancialConnectionsAuthorizationSession {
return repository.repairAuthorizationSession(
clientSecret = configuration.financialConnectionsSessionClientSecret,
coreAuthorization = coreAuthorization,
applicationId = applicationId,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.stripe.android.financialconnections.presentation.Async
import com.stripe.android.financialconnections.presentation.Async.Uninitialized
import com.stripe.android.financialconnections.presentation.FinancialConnectionsViewModel
import com.stripe.android.financialconnections.repository.AccountUpdateRequiredContentRepository
import com.stripe.android.financialconnections.repository.CoreAuthorizationPendingNetworkingRepairRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand All @@ -33,6 +34,7 @@ internal class AccountUpdateRequiredViewModel @AssistedInject constructor(
@Assisted initialState: AccountUpdateRequiredState,
nativeAuthFlowCoordinator: NativeAuthFlowCoordinator,
private val updateRequiredContentRepository: AccountUpdateRequiredContentRepository,
private val pendingRepairRepository: CoreAuthorizationPendingNetworkingRepairRepository,
private val navigationManager: NavigationManager,
private val eventTracker: FinancialConnectionsAnalyticsTracker,
private val updateLocalManifest: UpdateLocalManifest,
Expand Down Expand Up @@ -60,7 +62,11 @@ internal class AccountUpdateRequiredViewModel @AssistedInject constructor(
val referrer = state.referrer
when (val type = requireNotNull(state.payload()?.type)) {
is Type.Repair -> {
handleUnsupportedRepairAction(referrer)
if (type.authorization != null) {
openBankAuthRepair(type.institution, type.authorization, referrer)
} else {
handleUnsupportedRepairAction(referrer)
}
}
is Type.Supportability -> {
openPartnerAuth(type.institution, referrer)
Expand All @@ -80,6 +86,24 @@ internal class AccountUpdateRequiredViewModel @AssistedInject constructor(
navigationManager.tryNavigateTo(InstitutionPicker(referrer))
}

private fun openBankAuthRepair(
institution: FinancialConnectionsInstitution?,
authorization: String,
referrer: Pane,
) {
if (institution != null) {
updateLocalManifest {
it.copy(activeInstitution = institution)
}

pendingRepairRepository.set(authorization)
navigationManager.tryNavigateTo(Destination.BankAuthRepair(referrer))
} else {
// Fall back to the institution picker
navigationManager.tryNavigateTo(InstitutionPicker(referrer))
}
}

private fun openPartnerAuth(
institution: FinancialConnectionsInstitution?,
referrer: Pane,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ internal fun BankAuthRepairScreen() {

SharedPartnerAuth(
state = state.value,
onContinueClick = { /*TODO*/ },
onCancelClick = { /*TODO*/ },
onClickableTextClick = { /*TODO*/ },
onWebAuthFlowFinished = { /*TODO*/ },
onViewEffectLaunched = { /*TODO*/ },
onContinueClick = viewModel::onLaunchAuthClick,
onCancelClick = viewModel::onCancelClick,
onClickableTextClick = viewModel::onClickableTextClick,
onWebAuthFlowFinished = viewModel::onWebAuthFlowFinished,
onViewEffectLaunched = viewModel::onViewEffectLaunched,
inModal = false
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,86 @@ import android.os.Parcelable
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.stripe.android.core.Logger
import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker
import com.stripe.android.financialconnections.browser.BrowserManager
import com.stripe.android.financialconnections.di.APPLICATION_ID
import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativeComponent
import com.stripe.android.financialconnections.domain.CancelAuthorizationSession
import com.stripe.android.financialconnections.domain.CompleteAuthorizationSession
import com.stripe.android.financialconnections.domain.GetOrFetchSync
import com.stripe.android.financialconnections.domain.HandleError
import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator
import com.stripe.android.financialconnections.domain.PollAuthorizationSessionOAuthResults
import com.stripe.android.financialconnections.domain.PostAuthSessionEvent
import com.stripe.android.financialconnections.domain.PostAuthorizationSession
import com.stripe.android.financialconnections.domain.RepairAuthorizationSession
import com.stripe.android.financialconnections.domain.RetrieveAuthorizationSession
import com.stripe.android.financialconnections.features.notice.PresentSheet
import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState
import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthState.Payload
import com.stripe.android.financialconnections.features.partnerauth.SharedPartnerAuthViewModel
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane
import com.stripe.android.financialconnections.navigation.topappbar.TopAppBarStateUpdate
import com.stripe.android.financialconnections.presentation.FinancialConnectionsViewModel
import com.stripe.android.financialconnections.utils.error
import com.stripe.android.financialconnections.model.SynchronizeSessionResponse
import com.stripe.android.financialconnections.navigation.NavigationManager
import com.stripe.android.financialconnections.repository.CoreAuthorizationPendingNetworkingRepairRepository
import com.stripe.android.financialconnections.utils.UriUtils
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.parcelize.Parcelize
import javax.inject.Named

internal class BankAuthRepairViewModel @AssistedInject constructor(
completeAuthorizationSession: CompleteAuthorizationSession,
createAuthorizationSession: PostAuthorizationSession,
cancelAuthorizationSession: CancelAuthorizationSession,
retrieveAuthorizationSession: RetrieveAuthorizationSession,
eventTracker: FinancialConnectionsAnalyticsTracker,
@Named(APPLICATION_ID) applicationId: String,
uriUtils: UriUtils,
postAuthSessionEvent: PostAuthSessionEvent,
getOrFetchSync: GetOrFetchSync,
browserManager: BrowserManager,
handleError: HandleError,
navigationManager: NavigationManager,
pollAuthorizationSessionOAuthResults: PollAuthorizationSessionOAuthResults,
logger: Logger,
presentSheet: PresentSheet,
@Assisted initialState: SharedPartnerAuthState,
nativeAuthFlowCoordinator: NativeAuthFlowCoordinator,
) : FinancialConnectionsViewModel<SharedPartnerAuthState>(initialState, nativeAuthFlowCoordinator) {
private val pendingRepairRepository: CoreAuthorizationPendingNetworkingRepairRepository,
private val repairAuthSession: RepairAuthorizationSession,
) : SharedPartnerAuthViewModel(
completeAuthorizationSession,
createAuthorizationSession,
cancelAuthorizationSession,
retrieveAuthorizationSession,
eventTracker,
applicationId,
uriUtils,
postAuthSessionEvent,
getOrFetchSync,
browserManager,
handleError,
navigationManager,
pollAuthorizationSessionOAuthResults,
logger,
presentSheet,
initialState,
nativeAuthFlowCoordinator,
) {

override fun updateTopAppBar(state: SharedPartnerAuthState): TopAppBarStateUpdate {
return TopAppBarStateUpdate(
pane = Pane.BANK_AUTH_REPAIR,
allowBackNavigation = state.canNavigateBack,
error = state.payload.error,
override suspend fun fetchPayload(sync: SynchronizeSessionResponse): Payload {
val authorization = requireNotNull(pendingRepairRepository.get()?.coreAuthorization)
val activeInstitution = requireNotNull(sync.manifest.activeInstitution)

val authSession = repairAuthSession(authorization)

return Payload(
isStripeDirect = sync.manifest.isStripeDirect ?: false,
institution = activeInstitution,
authSession = authSession,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ private fun SharedPartnerAuthBody(
state.payload()?.let {
LoadedContent(
showInModal = inModal,
isRelinkSession = state.isRelinkSession,
authenticationStatus = state.authenticationStatus,
payload = it,
onContinueClick = onContinueClick,
Expand All @@ -210,6 +211,7 @@ private fun SharedPartnerAuthBody(
@Composable
private fun LoadedContent(
showInModal: Boolean,
isRelinkSession: Boolean,
authenticationStatus: Async<AuthenticationStatus>,
payload: SharedPartnerAuthState.Payload,
onContinueClick: () -> Unit,
Expand All @@ -226,6 +228,7 @@ private fun LoadedContent(
// is Loading or Success (completing auth after redirect)
authenticationStatus = authenticationStatus,
showInModal = showInModal,
showSecondaryButton = !isRelinkSession,
onContinueClick = onContinueClick,
onCancelClick = onCancelClick,
content = requireNotNull(payload.authSession.display?.text?.oauthPrepane),
Expand All @@ -240,6 +243,7 @@ private fun LoadedContent(
@Composable
private fun PrePaneContent(
showInModal: Boolean,
showSecondaryButton: Boolean,
content: OauthPrepane,
authenticationStatus: Async<AuthenticationStatus>,
onContinueClick: () -> Unit,
Expand Down Expand Up @@ -282,6 +286,7 @@ private fun PrePaneContent(
status = authenticationStatus,
oAuthPrepane = content,
showInModal = showInModal,
showSecondaryButton = showSecondaryButton,
)
}
)
Expand Down Expand Up @@ -356,6 +361,7 @@ private fun PrepaneFooter(
status: Async<AuthenticationStatus>,
oAuthPrepane: OauthPrepane,
showInModal: Boolean,
showSecondaryButton: Boolean,
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
Expand Down Expand Up @@ -389,25 +395,28 @@ private fun PrepaneFooter(
}
}
}
FinancialConnectionsButton(
onClick = onCancelClick,
type = Type.Secondary,
enabled = status !is Loading,
modifier = Modifier
.semantics { testTagsAsResourceId = true }
.testTag("cancel_cta")
.fillMaxWidth()
) {
Text(
text = stringResource(
id = if (showInModal) {
R.string.stripe_prepane_cancel_cta
} else {
R.string.stripe_prepane_choose_different_bank_cta
}
),
textAlign = TextAlign.Center
)

if (showSecondaryButton) {
FinancialConnectionsButton(
onClick = onCancelClick,
type = Type.Secondary,
enabled = status !is Loading,
modifier = Modifier
.semantics { testTagsAsResourceId = true }
.testTag("cancel_cta")
.fillMaxWidth()
) {
Text(
text = stringResource(
id = if (showInModal) {
R.string.stripe_prepane_cancel_cta
} else {
R.string.stripe_prepane_choose_different_bank_cta
}
),
textAlign = TextAlign.Center
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ internal class LinkAccountPickerViewModel @AssistedInject constructor(
generic = genericContent,
type = Repair(
authorization = authorization?.let { payload.partnerToCoreAuths?.getValue(it) },
institution = institution,
),
)
PARTNER_AUTH -> UpdateRequired(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,15 @@ internal data class NoticeSheetState(
sealed interface Type : Parcelable {

@Parcelize
data class Repair(val authorization: String?) : Type
data class Repair(
val authorization: String?,
val institution: FinancialConnectionsInstitution?,
) : Type

@Parcelize
data class Supportability(val institution: FinancialConnectionsInstitution?) : Type
data class Supportability(
val institution: FinancialConnectionsInstitution?,
) : Type
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.stripe.android.financialconnections.features.partnerauth

import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import com.stripe.android.financialconnections.features.common.SharedPartnerAuth
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane
import com.stripe.android.financialconnections.presentation.paneViewModel
Expand All @@ -15,11 +15,11 @@ internal fun PartnerAuthScreen(inModal: Boolean) {
args = PartnerAuthViewModel.Args(inModal, Pane.PARTNER_AUTH)
)
}
val state: State<SharedPartnerAuthState> = viewModel.stateFlow.collectAsState()
val state by viewModel.stateFlow.collectAsState()

SharedPartnerAuth(
inModal = inModal,
state = state.value,
state = state,
onContinueClick = viewModel::onLaunchAuthClick,
onCancelClick = viewModel::onCancelClick,
onClickableTextClick = viewModel::onClickableTextClick,
Expand Down
Loading

0 comments on commit 2c010b4

Please sign in to comment.