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

[FC] Gracefully fail when no browsers available #6794

Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
## XX.XX.XX - 2023-XX-XX
### All SDKs
* [FIXED][6771](https://github.com/stripe/stripe-android/pull/6771) Fixed the length of phone number field.

# Financial Connections
* [CHANGED][6789](https://github.com/stripe/stripe-android/pull/6789) Updated Mavericks to 3.0.3.
* [FIXED][6794](https://github.com/stripe/stripe-android/pull/6794) Gracefully fails when no web browser available.

## 20.25.3 - 2023-05-23

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ import com.stripe.android.financialconnections.FinancialConnectionsSheetState.Au
import com.stripe.android.financialconnections.FinancialConnectionsSheetViewEffect.FinishWithResult
import com.stripe.android.financialconnections.FinancialConnectionsSheetViewEffect.OpenAuthFlowWithUrl
import com.stripe.android.financialconnections.FinancialConnectionsSheetViewEffect.OpenNativeAuthFlow
import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker
import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent.Error
import com.stripe.android.financialconnections.analytics.FinancialConnectionsEventReporter
import com.stripe.android.financialconnections.di.APPLICATION_ID
import com.stripe.android.financialconnections.di.DaggerFinancialConnectionsSheetComponent
import com.stripe.android.financialconnections.domain.FetchFinancialConnectionsSession
import com.stripe.android.financialconnections.domain.FetchFinancialConnectionsSessionForToken
import com.stripe.android.financialconnections.domain.IsBrowserAvailable
import com.stripe.android.financialconnections.domain.NativeAuthFlowRouter
import com.stripe.android.financialconnections.domain.SynchronizeFinancialConnectionsSession
import com.stripe.android.financialconnections.exception.CustomManualEntryRequiredError
import com.stripe.android.financialconnections.exception.FinancialConnectionsClientError
import com.stripe.android.financialconnections.features.manualentry.isCustomManualEntryError
import com.stripe.android.financialconnections.launcher.FinancialConnectionsSheetActivityArgs.ForData
import com.stripe.android.financialconnections.launcher.FinancialConnectionsSheetActivityArgs.ForLink
Expand All @@ -30,6 +34,7 @@ import com.stripe.android.financialconnections.launcher.FinancialConnectionsShee
import com.stripe.android.financialconnections.launcher.FinancialConnectionsSheetActivityResult.Failed
import com.stripe.android.financialconnections.model.FinancialConnectionsSession
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane
import com.stripe.android.financialconnections.model.SynchronizeSessionResponse
import com.stripe.android.financialconnections.ui.FinancialConnectionsSheetNativeActivity
import com.stripe.android.financialconnections.utils.parcelable
Expand All @@ -38,6 +43,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.lang.IllegalArgumentException
import java.lang.IllegalStateException
import java.lang.UnsupportedOperationException
import javax.inject.Inject
import javax.inject.Named

Expand All @@ -49,6 +55,8 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor(
private val fetchFinancialConnectionsSessionForToken: FetchFinancialConnectionsSessionForToken,
private val logger: Logger,
private val eventReporter: FinancialConnectionsEventReporter,
private val analyticsTracker: FinancialConnectionsAnalyticsTracker,
private val isBrowserAvailable: IsBrowserAvailable,
private val nativeRouter: NativeAuthFlowRouter,
initialState: FinancialConnectionsSheetState
) : MavericksViewModel<FinancialConnectionsSheetState>(initialState) {
Expand Down Expand Up @@ -93,13 +101,13 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor(
*
*/
private fun openAuthFlow(sync: SynchronizeSessionResponse) {
// stores manifest in state for future references.
val manifest = sync.manifest
val nativeAuthFlowEnabled = nativeRouter.nativeAuthFlowEnabled(sync.manifest)
viewModelScope.launch {
nativeRouter.logExposure(sync.manifest)
if (isBrowserAvailable().not()) {
logNoBrowserAvailableAndFinish()
return
}
if (manifest.hostedAuthUrl == null) {
val nativeAuthFlowEnabled = nativeRouter.nativeAuthFlowEnabled(sync.manifest)
viewModelScope.launch { nativeRouter.logExposure(sync.manifest) }
if (sync.manifest.hostedAuthUrl == null) {
withState {
finishWithResult(
state = it,
Expand All @@ -114,18 +122,34 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor(
}
setState {
copy(
manifest = manifest,
manifest = sync.manifest,
webAuthFlowStatus = authFlowStatus,
viewEffect = if (nativeAuthFlowEnabled) {
OpenNativeAuthFlow(initialArgs.configuration, sync)
} else {
OpenAuthFlowWithUrl(manifest.hostedAuthUrl)
OpenAuthFlowWithUrl(sync.manifest.hostedAuthUrl)
}
)
}
}
}

private fun logNoBrowserAvailableAndFinish() {
viewModelScope.launch {
val errorMessage = "[Android] No Web browser available to launch AuthFlow"
analyticsTracker.track(
Error(
Pane.UNEXPECTED_ERROR,
FinancialConnectionsClientError("AppInitializationError", errorMessage)
)
)
finishWithResult(
state = awaitState(),
result = Failed(UnsupportedOperationException(errorMessage))
)
}
}

/**
* Activity recreation changes the lifecycle order:
*
Expand Down Expand Up @@ -167,6 +191,7 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor(
state = state,
result = Canceled
)

AuthFlowStatus.INTERMEDIATE_DEEPLINK -> setState {
copy(
webAuthFlowStatus = AuthFlowStatus.ON_EXTERNAL_ACTIVITY
Expand Down Expand Up @@ -195,6 +220,7 @@ internal class FinancialConnectionsSheetViewModel @Inject constructor(
state = state,
result = Canceled
)

AuthFlowStatus.INTERMEDIATE_DEEPLINK -> setState {
copy(
webAuthFlowStatus = AuthFlowStatus.ON_EXTERNAL_ACTIVITY
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.stripe.android.financialconnections.analytics

import com.stripe.android.core.exception.StripeException
import com.stripe.android.financialconnections.exception.FinancialConnectionsError
import com.stripe.android.financialconnections.exception.FinancialConnectionsClientError
import com.stripe.android.financialconnections.exception.FinancialConnectionsStripeError
import com.stripe.android.financialconnections.exception.WebAuthFlowFailedException

/**
Expand All @@ -15,7 +16,12 @@ internal fun Throwable.toEventParams(): Map<String, String?> = when (this) {
"error_message" to message,
"code" to null
)
is FinancialConnectionsError -> mapOf(
is FinancialConnectionsClientError -> mapOf(
"error" to name,
"error_type" to name,
"error_message" to message,
)
is FinancialConnectionsStripeError -> mapOf(
"error" to name,
"error_type" to name,
"error_message" to (stripeError?.message ?: message),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.stripe.android.financialconnections.analytics

import com.stripe.android.financialconnections.domain.ConfirmVerification.OTPError
import com.stripe.android.financialconnections.exception.FinancialConnectionsError
import com.stripe.android.financialconnections.exception.FinancialConnectionsStripeError
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest.Pane
import com.stripe.android.financialconnections.utils.filterNotNullValues

Expand Down Expand Up @@ -151,15 +151,6 @@ internal sealed class FinancialConnectionsEvent(
).filterNotNullValues()
)

class ClickLinkAnotherAccount(
pane: Pane,
) : FinancialConnectionsEvent(
name = "click.link_another_account",
mapOf(
"pane" to pane.value,
).filterNotNullValues()
)

Comment on lines -154 to -162
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unrelated - unused event (this button does not exist anymore)

class NetworkingNewConsumer(
pane: Pane,
) : FinancialConnectionsEvent(
Expand Down Expand Up @@ -256,7 +247,7 @@ internal sealed class FinancialConnectionsEvent(
exception: Throwable
) : FinancialConnectionsEvent(
name = when (exception) {
is FinancialConnectionsError,
is FinancialConnectionsStripeError,
is OTPError -> "error.expected"
else -> "error.unexpected"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.stripe.android.financialconnections.domain

import android.app.Application
import android.content.Intent
import android.net.Uri
import javax.inject.Inject

/**
* Check if a browser is available on the device.
*/
internal class IsBrowserAvailable @Inject constructor(
private val context: Application,
) {

operator fun invoke(): Boolean {
val url = "https://"
val webAddress = Uri.parse(url)
val intentWeb = Intent(Intent.ACTION_VIEW, webAddress)
return intentWeb.resolveActivity(context.packageManager) != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal class AccountLoadError(
val canRetry: Boolean,
val institution: FinancialConnectionsInstitution,
stripeException: StripeException
) : FinancialConnectionsError(
) : FinancialConnectionsStripeError(
name = "AccountLoadError",
stripeException = stripeException
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,7 @@ internal class AccountNoneEligibleForPaymentMethodError(
val institution: FinancialConnectionsInstitution,
val merchantName: String,
stripeException: StripeException,
) : FinancialConnectionsError(
) : FinancialConnectionsStripeError(
name = "AccountNoneEligibleForPaymentMethodError",
stripeException = stripeException
)

internal abstract class FinancialConnectionsError(
val name: String,
stripeException: StripeException,
) : StripeException(
stripeException.stripeError,
stripeException.requestId,
stripeException.statusCode,
stripeException.cause,
stripeException.message
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class AccountNumberRetrievalError(
val allowManualEntry: Boolean,
val institution: FinancialConnectionsInstitution,
stripeException: StripeException
) : FinancialConnectionsError(
) : FinancialConnectionsStripeError(
name = "AccountNumberRetrievalError",
stripeException = stripeException
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.stripe.android.financialconnections.exception

import java.lang.Exception

/**
* Base class for client errors that occur during the financial connections flow.
*/
internal class FinancialConnectionsClientError(
val name: String,
override val message: String
) : Exception()
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.stripe.android.financialconnections.exception

import com.stripe.android.core.exception.StripeException

/**
* Base class for errors that occur during the financial connections flow.
*/
internal abstract class FinancialConnectionsStripeError(
val name: String,
stripeException: StripeException,
) : StripeException(
stripeException.stripeError,
stripeException.requestId,
stripeException.statusCode,
stripeException.cause,
stripeException.message
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal class InstitutionPlannedDowntimeError(
val isToday: Boolean,
val backUpAt: Long,
stripeException: StripeException
) : FinancialConnectionsError(
) : FinancialConnectionsStripeError(
name = "InstitutionPlannedDowntimeError",
stripeException = stripeException,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class InstitutionUnplannedDowntimeError(
val institution: FinancialConnectionsInstitution,
val allowManualEntry: Boolean,
stripeException: StripeException
) : FinancialConnectionsError(
) : FinancialConnectionsStripeError(
name = "InstitutionUnplannedDowntimeError",
stripeException = stripeException
)
Loading