diff --git a/.idea/codestyles/Project.xml b/.idea/codestyles/Project.xml
index aa7276802a9..90ee029868b 100644
--- a/.idea/codestyles/Project.xml
+++ b/.idea/codestyles/Project.xml
@@ -32,8 +32,6 @@
-
-
@@ -160,4 +158,4 @@
-
+
\ No newline at end of file
diff --git a/financial-connections-example/src/main/java/com/stripe/android/financialconnections/example/FinancialConnectionsPlaygroundViewModel.kt b/financial-connections-example/src/main/java/com/stripe/android/financialconnections/example/FinancialConnectionsPlaygroundViewModel.kt
index a63255e0bfb..77dda6e7678 100644
--- a/financial-connections-example/src/main/java/com/stripe/android/financialconnections/example/FinancialConnectionsPlaygroundViewModel.kt
+++ b/financial-connections-example/src/main/java/com/stripe/android/financialconnections/example/FinancialConnectionsPlaygroundViewModel.kt
@@ -442,9 +442,7 @@ enum class Merchant(
Networking("networking"),
LiveTesting("live_testing", canSwitchBetweenTestAndLive = false),
TestMode("testmode", canSwitchBetweenTestAndLive = false),
- NmeDefaultVerification("nme", canSwitchBetweenTestAndLive = true),
- NmeABAVVerification("nme_abav", canSwitchBetweenTestAndLive = true),
- NmeSkipVerification("nme_skip", canSwitchBetweenTestAndLive = true),
+ Trusted("trusted", canSwitchBetweenTestAndLive = false),
Custom("other");
companion object {
diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupAccount.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupAccount.kt
index 1b14c57fa9e..2d4ab3c0e0b 100644
--- a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupAccount.kt
+++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupAccount.kt
@@ -1,21 +1,38 @@
package com.stripe.android.financialconnections.domain
+import android.app.Application
import com.stripe.android.financialconnections.FinancialConnectionsSheet
import com.stripe.android.financialconnections.repository.FinancialConnectionsConsumerSessionRepository
import com.stripe.android.model.ConsumerSessionLookup
+import com.stripe.attestation.IntegrityRequestManager
import javax.inject.Inject
internal class LookupAccount @Inject constructor(
+ private val application: Application,
+ private val integrityRequestManager: IntegrityRequestManager,
private val consumerSessionRepository: FinancialConnectionsConsumerSessionRepository,
val configuration: FinancialConnectionsSheet.Configuration,
) {
suspend operator fun invoke(
- email: String
- ): ConsumerSessionLookup = requireNotNull(
- consumerSessionRepository.lookupConsumerSession(
- email = email.lowercase().trim(),
- clientSecret = configuration.financialConnectionsSessionClientSecret
- )
- )
+ email: String,
+ verifiedFlow: Boolean
+ ): ConsumerSessionLookup {
+ return if (verifiedFlow) {
+ requireNotNull(
+ consumerSessionRepository.mobileLookupConsumerSession(
+ email = email.lowercase().trim(),
+ verificationToken = integrityRequestManager.requestToken().getOrThrow(),
+ appId = application.packageName
+ )
+ )
+ } else {
+ requireNotNull(
+ consumerSessionRepository.postConsumerSession(
+ email = email.lowercase().trim(),
+ clientSecret = configuration.financialConnectionsSessionClientSecret
+ )
+ )
+ }
+ }
}
diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupConsumerAndStartVerification.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupConsumerAndStartVerification.kt
index 4816b09d398..61c13c7094f 100644
--- a/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupConsumerAndStartVerification.kt
+++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/domain/LookupConsumerAndStartVerification.kt
@@ -22,13 +22,14 @@ internal class LookupConsumerAndStartVerification @Inject constructor(
email: String,
businessName: String?,
verificationType: VerificationType,
+ appVerificationEnabled: Boolean,
onConsumerNotFound: suspend () -> Unit,
onLookupError: suspend (Throwable) -> Unit,
onStartVerification: suspend () -> Unit,
onVerificationStarted: suspend (ConsumerSession) -> Unit,
onStartVerificationError: suspend (Throwable) -> Unit
) {
- runCatching { lookupAccount(email) }
+ runCatching { lookupAccount(email, appVerificationEnabled) }
.onSuccess { session ->
if (session.exists) {
onStartVerification()
diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupPreviewParameterProvider.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupPreviewParameterProvider.kt
index b15b00e9fa6..7cfa965f1b9 100644
--- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupPreviewParameterProvider.kt
+++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupPreviewParameterProvider.kt
@@ -30,6 +30,7 @@ internal class NetworkingLinkSignupPreviewParameterProvider :
initiallySelectedCountryCode = null,
),
isInstantDebits = false,
+ appVerificationEnabled = false,
content = networkingLinkSignupPane(),
)
),
@@ -49,6 +50,7 @@ internal class NetworkingLinkSignupPreviewParameterProvider :
initiallySelectedCountryCode = null,
),
isInstantDebits = false,
+ appVerificationEnabled = false,
content = networkingLinkSignupPane(),
)
),
@@ -74,6 +76,7 @@ internal class NetworkingLinkSignupPreviewParameterProvider :
initiallySelectedCountryCode = null,
),
isInstantDebits = false,
+ appVerificationEnabled = false,
content = networkingLinkSignupPane(),
)
),
@@ -99,6 +102,7 @@ internal class NetworkingLinkSignupPreviewParameterProvider :
initiallySelectedCountryCode = null,
),
isInstantDebits = true,
+ appVerificationEnabled = false,
content = linkLoginPane(),
)
),
diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModel.kt
index 2bb6f76bd17..e39f948c903 100644
--- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModel.kt
+++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinksignup/NetworkingLinkSignupViewModel.kt
@@ -103,6 +103,7 @@ internal class NetworkingLinkSignupViewModel @AssistedInject constructor(
NetworkingLinkSignupState.Payload(
content = requireNotNull(content),
merchantName = sync.manifest.getBusinessName(),
+ appVerificationEnabled = sync.manifest.appVerificationEnabled,
emailController = SimpleTextFieldController(
textFieldConfig = EmailConfig(label = R.string.stripe_networking_signup_email_label),
initialValue = sync.manifest.accountholderCustomerEmailAddress ?: prefillDetails?.email,
@@ -195,7 +196,7 @@ internal class NetworkingLinkSignupViewModel @AssistedInject constructor(
/**
* @param validEmail valid email, or null if entered email is invalid.
*/
- private suspend fun onEmailEntered(
+ private fun onEmailEntered(
validEmail: String?
) {
setState { copy(validEmail = validEmail) }
@@ -203,7 +204,10 @@ internal class NetworkingLinkSignupViewModel @AssistedInject constructor(
logger.debug("VALID EMAIL ADDRESS $validEmail.")
searchJob += suspend {
delay(getLookupDelayMs(validEmail))
- lookupAccount(validEmail)
+ lookupAccount(
+ email = validEmail,
+ verifiedFlow = stateFlow.value.payload()?.appVerificationEnabled == true
+ )
}.execute { copy(lookupAccount = if (it.isCancellationError()) Uninitialized else it) }
} else {
setState { copy(lookupAccount = Uninitialized) }
@@ -342,6 +346,7 @@ internal data class NetworkingLinkSignupState(
data class Payload(
val merchantName: String?,
val emailController: SimpleTextFieldController,
+ val appVerificationEnabled: Boolean,
val phoneController: PhoneNumberController,
val isInstantDebits: Boolean,
val content: Content,
diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinkverification/NetworkingLinkVerificationViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinkverification/NetworkingLinkVerificationViewModel.kt
index 90337dfe901..84ff7eeb8c8 100644
--- a/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinkverification/NetworkingLinkVerificationViewModel.kt
+++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/features/networkinglinkverification/NetworkingLinkVerificationViewModel.kt
@@ -99,6 +99,7 @@ internal class NetworkingLinkVerificationViewModel @AssistedInject constructor(
return InitData(
businessName = manifest.businessName,
emailAddress = requireNotNull(email),
+ appVerificationEnabled = manifest.appVerificationEnabled,
initialInstitution = manifest.initialInstitution,
)
}
@@ -108,6 +109,7 @@ internal class NetworkingLinkVerificationViewModel @AssistedInject constructor(
) {
lookupConsumerAndStartVerification(
email = initData.emailAddress,
+ appVerificationEnabled = initData.appVerificationEnabled,
businessName = initData.businessName,
verificationType = VerificationType.SMS,
onConsumerNotFound = {
@@ -228,6 +230,7 @@ internal class NetworkingLinkVerificationViewModel @AssistedInject constructor(
private data class InitData(
val businessName: String?,
val emailAddress: String,
+ val appVerificationEnabled: Boolean,
val initialInstitution: FinancialConnectionsInstitution?,
)
diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/model/FinancialConnectionsSessionManifest.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/model/FinancialConnectionsSessionManifest.kt
index d4605c0aadd..69c7bfedd5f 100644
--- a/financial-connections/src/main/java/com/stripe/android/financialconnections/model/FinancialConnectionsSessionManifest.kt
+++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/model/FinancialConnectionsSessionManifest.kt
@@ -72,6 +72,8 @@ internal data class FinancialConnectionsSessionManifest(
@SerialName(value = "institution_search_disabled")
val institutionSearchDisabled: Boolean,
+ val appVerificationEnabled: Boolean = true,
+
@SerialName(value = "livemode")
val livemode: Boolean,
diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModel.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModel.kt
index 09d5023a004..8b75b28ba8c 100644
--- a/financial-connections/src/main/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModel.kt
+++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/presentation/FinancialConnectionsSheetNativeViewModel.kt
@@ -65,6 +65,7 @@ import com.stripe.android.financialconnections.ui.toLocalTheme
import com.stripe.android.financialconnections.utils.UriUtils
import com.stripe.android.financialconnections.utils.get
import com.stripe.android.financialconnections.utils.updateWithNewEntry
+import com.stripe.attestation.IntegrityRequestManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -86,6 +87,7 @@ internal class FinancialConnectionsSheetNativeViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val nativeAuthFlowCoordinator: NativeAuthFlowCoordinator,
private val uriUtils: UriUtils,
+ private val integrityRequestManager: IntegrityRequestManager,
private val completeFinancialConnectionsSession: CompleteFinancialConnectionsSession,
private val createInstantDebitsResult: CreateInstantDebitsResult,
private val eventTracker: FinancialConnectionsAnalyticsTracker,
@@ -124,6 +126,7 @@ internal class FinancialConnectionsSheetNativeViewModel @Inject constructor(
init {
savedStateHandle.registerSavedStateProvider()
setState { copy(firstInit = false) }
+ prepareIntegrityRequestManager()
viewModelScope.launch {
nativeAuthFlowCoordinator().collect { message ->
when (message) {
@@ -147,6 +150,12 @@ internal class FinancialConnectionsSheetNativeViewModel @Inject constructor(
}
}
+ private fun prepareIntegrityRequestManager() {
+ viewModelScope.launch {
+ integrityRequestManager.prepare()
+ }
+ }
+
private fun SavedStateHandle.registerSavedStateProvider() {
setSavedStateProvider(KEY_SAVED_STATE) {
val state = stateFlow.value
diff --git a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt
index 80378469a0e..9fb625db051 100644
--- a/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt
+++ b/financial-connections/src/main/java/com/stripe/android/financialconnections/repository/FinancialConnectionsConsumerSessionRepository.kt
@@ -27,17 +27,23 @@ internal interface FinancialConnectionsConsumerSessionRepository {
suspend fun getCachedConsumerSession(): CachedConsumerSession?
+ suspend fun postConsumerSession(
+ email: String,
+ clientSecret: String
+ ): ConsumerSessionLookup
+
+ suspend fun mobileLookupConsumerSession(
+ email: String,
+ verificationToken: String,
+ appId: String
+ ): ConsumerSessionLookup
+
suspend fun signUp(
email: String,
phoneNumber: String,
country: String,
): ConsumerSessionSignup
- suspend fun lookupConsumerSession(
- email: String,
- clientSecret: String
- ): ConsumerSessionLookup
-
suspend fun startConsumerVerification(
consumerSessionClientSecret: String,
connectionsMerchantName: String?,
@@ -123,15 +129,6 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl(
consumerSessionRepository.provideConsumerSession()
}
- override suspend fun lookupConsumerSession(
- email: String,
- clientSecret: String
- ): ConsumerSessionLookup = mutex.withLock {
- postConsumerSession(email, clientSecret).also { lookup ->
- updateCachedConsumerSessionFromLookup(lookup)
- }
- }
-
override suspend fun signUp(
email: String,
phoneNumber: String,
@@ -238,14 +235,30 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl(
).getOrThrow()
}
- private suspend fun postConsumerSession(
+ override suspend fun postConsumerSession(
email: String,
clientSecret: String
): ConsumerSessionLookup = financialConnectionsConsumersApiService.postConsumerSession(
email = email,
clientSecret = clientSecret,
requestSurface = requestSurface,
- )
+ ).also {
+ updateCachedConsumerSessionFromLookup(it)
+ }
+
+ override suspend fun mobileLookupConsumerSession(
+ email: String,
+ verificationToken: String,
+ appId: String
+ ): ConsumerSessionLookup = consumersApiService.mobileLookupConsumerSession(
+ email = email,
+ verificationToken = verificationToken,
+ appId = appId,
+ requestSurface = requestSurface,
+ requestOptions = provideApiRequestOptions(useConsumerPublishableKey = false),
+ ).also {
+ updateCachedConsumerSessionFromLookup(it)
+ }
private fun updateCachedConsumerSession(
source: String,
diff --git a/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt b/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt
index 343faa09b4e..0fbe9b6caa2 100644
--- a/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt
+++ b/payments-model/src/main/java/com/stripe/android/repository/ConsumersApiService.kt
@@ -26,6 +26,7 @@ import com.stripe.android.model.parsers.ConsumerSessionLookupJsonParser
import com.stripe.android.model.parsers.ConsumerSessionSignupJsonParser
import com.stripe.android.model.parsers.SharePaymentDetailsJsonParser
import java.util.Locale
+import java.util.UUID
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
interface ConsumersApiService {
@@ -50,6 +51,14 @@ interface ConsumersApiService {
requestOptions: ApiRequest.Options
): ConsumerSessionLookup
+ suspend fun mobileLookupConsumerSession(
+ email: String,
+ requestSurface: String,
+ verificationToken: String,
+ appId: String,
+ requestOptions: ApiRequest.Options
+ ): ConsumerSessionLookup
+
suspend fun startConsumerVerification(
consumerSessionClientSecret: String,
locale: Locale,
@@ -176,6 +185,35 @@ class ConsumersApiServiceImpl(
)
}
+ /**
+ * Retrieves the ConsumerSession if the given email is associated with a Link account.
+ */
+ override suspend fun mobileLookupConsumerSession(
+ email: String,
+ requestSurface: String,
+ verificationToken: String,
+ appId: String,
+ requestOptions: ApiRequest.Options
+ ): ConsumerSessionLookup {
+ return executeRequestWithModelJsonParser(
+ stripeErrorJsonParser = stripeErrorJsonParser,
+ stripeNetworkClient = stripeNetworkClient,
+ request = apiRequestFactory.createPost(
+ mobileConsumerSessionLookupUrl,
+ requestOptions,
+ mapOf(
+ "request_surface" to requestSurface,
+ "email_address" to email.lowercase(),
+ "android_verification_token" to verificationToken,
+ "session_id" to "12345", // TODO (carlosmuvi): remove this when we have a real session id
+ "email_source" to "user_action", // TODO (carlosmuvi): remove this when we have a real app id
+ "app_id" to appId
+ )
+ ),
+ responseJsonParser = ConsumerSessionLookupJsonParser()
+ )
+ }
+
/**
* Triggers a verification for the consumer corresponding to the given client secret.
*/
@@ -328,6 +366,12 @@ class ConsumersApiServiceImpl(
internal val consumerSessionLookupUrl: String =
getApiUrl("consumers/sessions/lookup")
+ /**
+ * @return `https://api.stripe.com/v1/consumers/sessions/lookup`
+ */
+ internal val mobileConsumerSessionLookupUrl: String =
+ getApiUrl("consumers/mobile/sessions/lookup")
+
/**
* @return `https://api.stripe.com/v1/consumers/sessions/start_verification`
*/