Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support networking relink flows #10000

Merged
merged 10 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions financial-connections/api/financial-connections.api
Original file line number Diff line number Diff line change
Expand Up @@ -438,14 +438,6 @@ public final class com/stripe/android/financialconnections/features/attachpaymen
public final fun getLambda-1$financial_connections_release ()Lkotlin/jvm/functions/Function2;
}

public final class com/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel$Args$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel$Args;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/financialconnections/features/bankauthrepair/BankAuthRepairViewModel$Args;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/financialconnections/features/common/ComposableSingletons$AccountItemKt {
public static final field INSTANCE Lcom/stripe/android/financialconnections/features/common/ComposableSingletons$AccountItemKt;
public fun <init> ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.stripe.android.financialconnections.FinancialConnectionsSheet
import com.stripe.android.financialconnections.features.accountpicker.AccountPickerViewModel
import com.stripe.android.financialconnections.features.accountupdate.AccountUpdateRequiredViewModel
import com.stripe.android.financialconnections.features.attachpayment.AttachPaymentViewModel
import com.stripe.android.financialconnections.features.bankauthrepair.BankAuthRepairViewModel
import com.stripe.android.financialconnections.features.consent.ConsentViewModel
import com.stripe.android.financialconnections.features.error.ErrorViewModel
import com.stripe.android.financialconnections.features.exit.ExitViewModel
Expand Down Expand Up @@ -50,7 +49,6 @@ internal interface FinancialConnectionsSheetNativeComponent {
val manualEntryViewModelFactory: ManualEntryViewModel.Factory
val manualEntrySuccessViewModelFactory: ManualEntrySuccessViewModel.Factory
val partnerAuthViewModelFactory: PartnerAuthViewModel.Factory
val bankAuthRepairViewModelFactory: BankAuthRepairViewModel.Factory
val successViewModelFactory: SuccessViewModel.Factory
val attachPaymentViewModelFactory: AttachPaymentViewModel.Factory
val resetViewModelFactory: ResetViewModel.Factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import com.stripe.android.financialconnections.analytics.FinancialConnectionsAna
import com.stripe.android.financialconnections.analytics.FinancialConnectionsEventReporter
import com.stripe.android.financialconnections.domain.GetOrFetchSync
import com.stripe.android.financialconnections.domain.IsLinkWithStripe
import com.stripe.android.financialconnections.domain.IsNetworkingRelinkSession
import com.stripe.android.financialconnections.domain.RealIsLinkWithStripe
import com.stripe.android.financialconnections.domain.RealIsNetworkingRelinkSession
import com.stripe.android.financialconnections.features.common.enableWorkManager
import com.stripe.android.financialconnections.repository.ConsumerSessionProvider
import com.stripe.android.financialconnections.repository.ConsumerSessionRepository
Expand Down Expand Up @@ -86,6 +88,9 @@ internal interface FinancialConnectionsSheetSharedModule {
@Binds
fun bindsIsLinkWithStripe(impl: RealIsLinkWithStripe): IsLinkWithStripe

@Binds
fun bindsIsNetworkingRelinkSession(impl: RealIsNetworkingRelinkSession): IsNetworkingRelinkSession

companion object {

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.stripe.android.financialconnections.domain

import com.stripe.android.financialconnections.repository.CoreAuthorizationPendingNetworkingRepairRepository
import javax.inject.Inject

internal fun interface IsNetworkingRelinkSession {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have multiple screens where we want to know if we’re in a networking relink flow. This is easier to inject and test than using the full CoreAuthorizationPendingNetworkingRepairRepository.

operator fun invoke(): Boolean
}

internal class RealIsNetworkingRelinkSession @Inject constructor(
private val pendingRepairRepository: CoreAuthorizationPendingNetworkingRepairRepository,
) : IsNetworkingRelinkSession {

override fun invoke(): Boolean {
return pendingRepairRepository.get() != null
}
}
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 @@ internal class SaveAccountToLink @Inject constructor(
private val successContentRepository: SuccessContentRepository,
private val repository: FinancialConnectionsManifestRepository,
private val accountsRepository: FinancialConnectionsAccountsRepository,
private val isNetworkingRelinkSession: IsNetworkingRelinkSession,
) {

suspend fun new(
Expand Down Expand Up @@ -90,9 +91,13 @@ internal class SaveAccountToLink @Inject constructor(
}.mapCatching {
action(selectedAccountIds)
}.onSuccess { manifest ->
storeSavedToLinkMessage(manifest, selectedAccountIds.size)
if (!isNetworkingRelinkSession()) {
storeSavedToLinkMessage(manifest, selectedAccountIds.size)
}
}.onFailure {
storeFailedToSaveToLinkMessage(selectedAccountIds.size)
if (!isNetworkingRelinkSession()) {
storeFailedToSaveToLinkMessage(selectedAccountIds.size)
}
}.getOrThrow()
}

Expand Down
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,7 @@ internal class AccountUpdateRequiredViewModel @AssistedInject constructor(
val referrer = state.referrer
when (val type = requireNotNull(state.payload()?.type)) {
is Type.Repair -> {
handleUnsupportedRepairAction(referrer)
openBankAuthRepair(type.institution, type.authorization, referrer)
}
is Type.Supportability -> {
openPartnerAuth(type.institution, referrer)
Expand All @@ -69,15 +71,31 @@ internal class AccountUpdateRequiredViewModel @AssistedInject constructor(
}
}

private fun handleUnsupportedRepairAction(referrer: Pane) {
eventTracker.logError(
extraMessage = "Updating a repair account, but repairs are not supported in Mobile.",
error = UnclassifiedError("UpdateRepairAccountError"),
logger = logger,
pane = PANE,
)
// Fall back to the institution picker for now
navigationManager.tryNavigateTo(InstitutionPicker(referrer))
private fun openBankAuthRepair(
institution: FinancialConnectionsInstitution?,
authorization: String?,
referrer: Pane,
) {
if (institution != null && authorization != null) {
updateLocalManifest {
it.copy(activeInstitution = institution)
}

pendingRepairRepository.set(authorization)
navigationManager.tryNavigateTo(Destination.BankAuthRepair(referrer))
} else {
val missingAuth = authorization == null
val missingInstitution = institution == null
eventTracker.logError(
extraMessage = "Unable to open repair flow " +
"(missing auth: $missingAuth, missing institution: $missingInstitution).",
error = UnclassifiedError("UpdateRepairAccountError"),
logger = logger,
pane = PANE,
)
// Fall back to the institution picker
navigationManager.tryNavigateTo(InstitutionPicker(referrer))
Comment on lines +96 to +97
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this considered an error? should we log anything?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll combing a few else branches to address this!

}
}

private fun openPartnerAuth(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import com.stripe.android.financialconnections.analytics.FinancialConnectionsAna
import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker
import com.stripe.android.financialconnections.analytics.logError
import com.stripe.android.financialconnections.di.FinancialConnectionsSheetNativeComponent
import com.stripe.android.financialconnections.domain.CachedPartnerAccount
import com.stripe.android.financialconnections.domain.GetCachedAccounts
import com.stripe.android.financialconnections.domain.GetOrFetchSync
import com.stripe.android.financialconnections.domain.IsNetworkingRelinkSession
import com.stripe.android.financialconnections.domain.NativeAuthFlowCoordinator
import com.stripe.android.financialconnections.domain.PollAttachPaymentAccount
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane
import com.stripe.android.financialconnections.model.LinkAccountSessionPaymentAccount
import com.stripe.android.financialconnections.model.PaymentAccountParams
Expand Down Expand Up @@ -41,7 +44,8 @@ internal class AttachPaymentViewModel @AssistedInject constructor(
private val getCachedAccounts: GetCachedAccounts,
private val navigationManager: NavigationManager,
private val getOrFetchSync: GetOrFetchSync,
private val logger: Logger
private val logger: Logger,
private val isNetworkingRelinkSession: IsNetworkingRelinkSession,
) : FinancialConnectionsViewModel<AttachPaymentState>(initialState, nativeAuthFlowCoordinator) {

init {
Expand All @@ -60,15 +64,8 @@ internal class AttachPaymentViewModel @AssistedInject constructor(
params = PaymentAccountParams.LinkedAccount(requireNotNull(id))
)
}
if (manifest.isNetworkingUserFlow == true && manifest.accountholderIsLinkConsumer == true) {
result.networkingSuccessful?.let {
successContentRepository.set(
message = PluralId(
value = R.plurals.stripe_success_pane_desc_link_success,
count = accounts.size
)
)
}
if (result.networkingSuccessful == true) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was broken before, since result.networkingSuccessful?.let { … } would run even if networkingSuccessful was false.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦

setSuccessMessageIfNecessary(manifest, accounts)
}
eventTracker.track(
PollAttachPaymentsSucceeded(
Expand All @@ -91,6 +88,20 @@ internal class AttachPaymentViewModel @AssistedInject constructor(
)
}

private fun setSuccessMessageIfNecessary(
manifest: FinancialConnectionsSessionManifest,
accounts: List<CachedPartnerAccount>,
) {
if (manifest.canSetCustomLinkSuccessMessage && !isNetworkingRelinkSession()) {
successContentRepository.set(
message = PluralId(
value = R.plurals.stripe_success_pane_desc_link_success,
count = accounts.size
)
)
}
}

private fun logErrors() {
onAsync(
AttachPaymentState::linkPaymentAccount,
Expand Down Expand Up @@ -132,3 +143,6 @@ internal class AttachPaymentViewModel @AssistedInject constructor(
internal data class AttachPaymentState(
val linkPaymentAccount: Async<LinkAccountSessionPaymentAccount> = Uninitialized
)

private val FinancialConnectionsSessionManifest.canSetCustomLinkSuccessMessage: Boolean
get() = isNetworkingUserFlow == true && accountholderIsLinkConsumer == true

This file was deleted.

This file was deleted.

Loading
Loading