Skip to content

Commit

Permalink
Add SignUpParams model class
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosmuvi-stripe committed Dec 26, 2024
1 parent 83d5bb9 commit fc381b6
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.stripe.android.core.Logger
import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsEvent.Click
import com.stripe.android.financialconnections.analytics.FinancialConnectionsAnalyticsTracker
import com.stripe.android.financialconnections.analytics.logError
import com.stripe.android.financialconnections.di.APPLICATION_ID
import com.stripe.android.financialconnections.domain.AttachConsumerToLinkAccountSession
import com.stripe.android.financialconnections.domain.GetCachedAccounts
import com.stripe.android.financialconnections.domain.GetOrFetchSync
Expand All @@ -19,12 +20,14 @@ import com.stripe.android.financialconnections.navigation.Destination.Networking
import com.stripe.android.financialconnections.navigation.Destination.Success
import com.stripe.android.financialconnections.navigation.NavigationManager
import com.stripe.android.financialconnections.repository.FinancialConnectionsConsumerSessionRepository
import com.stripe.attestation.IntegrityRequestManager
import javax.inject.Inject
import javax.inject.Named

internal interface LinkSignupHandler {

suspend fun performSignup(
state: NetworkingLinkSignupState,
state: NetworkingLinkSignupState
): Pane

suspend fun handleSignupFailure(
Expand All @@ -37,8 +40,10 @@ internal interface LinkSignupHandler {
internal class LinkSignupHandlerForInstantDebits @Inject constructor(
private val consumerRepository: FinancialConnectionsConsumerSessionRepository,
private val attachConsumerToLinkAccountSession: AttachConsumerToLinkAccountSession,
private val integrityRequestManager: IntegrityRequestManager,
private val getOrFetchSync: GetOrFetchSync,
private val navigationManager: NavigationManager,
@Named(APPLICATION_ID) private val applicationId: String,
private val handleError: HandleError,
) : LinkSignupHandler {

Expand All @@ -47,18 +52,31 @@ internal class LinkSignupHandlerForInstantDebits @Inject constructor(
): Pane {
val phoneController = state.payload()!!.phoneController

val signup = consumerRepository.signUp(
email = state.validEmail!!,
phoneNumber = state.validPhone!!,
country = phoneController.getCountryCode(),
)
val manifest = getOrFetchSync().manifest
val signup = if (manifest.appVerificationEnabled) {
val token = integrityRequestManager.requestToken().getOrThrow()
consumerRepository.mobileSignUp(
email = state.validEmail!!,
phoneNumber = state.validPhone!!,
country = phoneController.getCountryCode(),
verificationToken = token,
appId = applicationId
)
} else {
consumerRepository.signUp(
email = state.validEmail!!,
phoneNumber = state.validPhone!!,
country = phoneController.getCountryCode(),
)
}


attachConsumerToLinkAccountSession(
consumerSessionClientSecret = signup.consumerSession.clientSecret,
)

val manifest = getOrFetchSync(refetchCondition = Always).manifest
return manifest.nextPane
// Refresh manifest to get the next pane
return getOrFetchSync(refetchCondition = Always).manifest.nextPane
}

override fun navigateToVerification() {
Expand All @@ -76,11 +94,14 @@ internal class LinkSignupHandlerForInstantDebits @Inject constructor(
}

internal class LinkSignupHandlerForNetworking @Inject constructor(
private val consumerRepository: FinancialConnectionsConsumerSessionRepository,
private val getOrFetchSync: GetOrFetchSync,
private val getCachedAccounts: GetCachedAccounts,
private val integrityRequestManager: IntegrityRequestManager,
private val saveAccountToLink: SaveAccountToLink,
private val eventTracker: FinancialConnectionsAnalyticsTracker,
private val navigationManager: NavigationManager,
@Named(APPLICATION_ID) private val applicationId: String,
private val logger: Logger,
) : LinkSignupHandler {

Expand All @@ -92,14 +113,36 @@ internal class LinkSignupHandlerForNetworking @Inject constructor(
val manifest = getOrFetchSync().manifest
val phoneController = state.payload()!!.phoneController
require(state.valid) { "Form invalid! ${state.validEmail} ${state.validPhone}" }
saveAccountToLink.new(
country = phoneController.getCountryCode(),
email = state.validEmail!!,
phoneNumber = state.validPhone!!,
selectedAccounts = selectedAccounts,
shouldPollAccountNumbers = manifest.isDataFlow,
)

if (manifest.appVerificationEnabled) {
// ** New signup flow on verified flows: 2 requests **
// 1. Mobile signup endpoint providing email + phone number
// 2. Separately call SaveAccountToLink with the newly created account.
val token = integrityRequestManager.requestToken().getOrThrow()
val signup = consumerRepository.mobileSignUp(
email = state.validEmail!!,
phoneNumber = state.validPhone!!,
country = phoneController.getCountryCode(),
verificationToken = token,
appId = applicationId,
)
saveAccountToLink.existing(
consumerSessionClientSecret = signup.consumerSession.clientSecret,
selectedAccounts = selectedAccounts,
shouldPollAccountNumbers = manifest.isDataFlow,
)
} else {
// ** Legacy signup endpoint on unverified flows: 1 request **
// SaveAccountToLink endpoint Signs up when providing email + phone number
// **and** saves accounts to link in the same request.
saveAccountToLink.new(
country = phoneController.getCountryCode(),
email = state.validEmail!!,
phoneNumber = state.validPhone!!,
selectedAccounts = selectedAccounts,
shouldPollAccountNumbers = manifest.isDataFlow,
)
}
return Pane.SUCCESS
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.stripe.android.model.ConsumerSessionSignup
import com.stripe.android.model.ConsumerSignUpConsentAction.EnteredPhoneNumberClickedSaveToLink
import com.stripe.android.model.CustomEmailType
import com.stripe.android.model.SharePaymentDetails
import com.stripe.android.model.SignUpParams
import com.stripe.android.model.VerificationType
import com.stripe.android.repository.ConsumersApiService
import kotlinx.coroutines.sync.Mutex
Expand Down Expand Up @@ -44,6 +45,14 @@ internal interface FinancialConnectionsConsumerSessionRepository {
country: String,
): ConsumerSessionSignup

suspend fun mobileSignUp(
email: String,
phoneNumber: String,
country: String,
verificationToken: String,
appId: String
): ConsumerSessionSignup

suspend fun startConsumerVerification(
consumerSessionClientSecret: String,
connectionsMerchantName: String?,
Expand Down Expand Up @@ -135,17 +144,49 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl(
country: String,
): ConsumerSessionSignup = mutex.withLock {
consumersApiService.signUp(
email = email,
phoneNumber = phoneNumber,
country = country,
name = null,
locale = locale,
amount = elementsSessionContext?.amount,
currency = elementsSessionContext?.currency,
incentiveEligibilitySession = elementsSessionContext?.incentiveEligibilitySession,
SignUpParams(
email = email,
phoneNumber = phoneNumber,
country = country,
name = null,
locale = locale,
amount = elementsSessionContext?.amount,
currency = elementsSessionContext?.currency,
incentiveEligibilitySession = elementsSessionContext?.incentiveEligibilitySession,
requestSurface = requestSurface,
consentAction = EnteredPhoneNumberClickedSaveToLink,
verificationToken = null,
appId = null,
),
requestOptions = provideApiRequestOptions(useConsumerPublishableKey = false)
).onSuccess { signup ->
updateCachedConsumerSessionFromSignup(signup)
}.getOrThrow()
}

override suspend fun mobileSignUp(
email: String,
phoneNumber: String,
country: String,
verificationToken: String,
appId: String
): ConsumerSessionSignup = mutex.withLock {
consumersApiService.mobileSignUp(
SignUpParams(
email = email,
phoneNumber = phoneNumber,
country = country,
name = null,
locale = locale,
amount = elementsSessionContext?.amount,
currency = elementsSessionContext?.currency,
incentiveEligibilitySession = elementsSessionContext?.incentiveEligibilitySession,
requestSurface = requestSurface,
consentAction = EnteredPhoneNumberClickedSaveToLink,
verificationToken = verificationToken,
appId = appId,
),
requestOptions = provideApiRequestOptions(useConsumerPublishableKey = false),
requestSurface = requestSurface,
consentAction = EnteredPhoneNumberClickedSaveToLink,
).onSuccess { signup ->
updateCachedConsumerSessionFromSignup(signup)
}.getOrThrow()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.stripe.android.model;

import androidx.annotation.RestrictTo
import java.util.Locale

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
data class SignUpParams(
val email: String,
val phoneNumber: String,
val country: String,
val name: String?,
val locale: Locale?,
val amount: Long?,
val currency: String?,
val incentiveEligibilitySession: IncentiveEligibilitySession?,
val requestSurface: String,
val consentAction: ConsumerSignUpConsentAction,
val verificationToken: String? = null,
val appId: String? = null
) {
fun toParamMap(): Map<String, *> {
val params = mutableMapOf(
"email_address" to email.lowercase(),
"phone_number" to phoneNumber,
"country" to country,
"country_inferring_method" to "PHONE_NUMBER",
"amount" to amount,
"currency" to currency,
"consent_action" to consentAction.value,
"request_surface" to requestSurface
)

locale?.let {
params["locale"] = it.toLanguageTag()
}

name?.let {
params["legal_name"] = it
}

verificationToken?.let {
params["android_verification_token"] = it
}

appId?.let {
params["app_id"] = it
}

params.putAll(incentiveEligibilitySession?.toParamMap().orEmpty())

return params.toMap()
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.stripe.android.model.ConsumerSignUpConsentAction
import com.stripe.android.model.CustomEmailType
import com.stripe.android.model.IncentiveEligibilitySession
import com.stripe.android.model.SharePaymentDetails
import com.stripe.android.model.SignUpParams
import com.stripe.android.model.VerificationType
import com.stripe.android.model.parsers.AttachConsumerToLinkAccountSessionJsonParser
import com.stripe.android.model.parsers.ConsumerPaymentDetailsJsonParser
Expand All @@ -31,19 +32,15 @@ import java.util.Locale
interface ConsumersApiService {

suspend fun signUp(
email: String,
phoneNumber: String,
country: String,
name: String?,
locale: Locale?,
amount: Long?,
currency: String?,
incentiveEligibilitySession: IncentiveEligibilitySession?,
requestSurface: String,
consentAction: ConsumerSignUpConsentAction,
params: SignUpParams,
requestOptions: ApiRequest.Options,
): Result<ConsumerSessionSignup>

suspend fun mobileSignUp(
params: SignUpParams,
requestOptions: ApiRequest.Options
): Result<ConsumerSessionSignup>

suspend fun lookupConsumerSession(
email: String,
requestSurface: String,
Expand Down Expand Up @@ -118,16 +115,7 @@ class ConsumersApiServiceImpl(
)

override suspend fun signUp(
email: String,
phoneNumber: String,
country: String,
name: String?,
locale: Locale?,
amount: Long?,
currency: String?,
incentiveEligibilitySession: IncentiveEligibilitySession?,
requestSurface: String,
consentAction: ConsumerSignUpConsentAction,
params: SignUpParams,
requestOptions: ApiRequest.Options,
): Result<ConsumerSessionSignup> {
return executeRequestWithResultParser(
Expand All @@ -136,26 +124,26 @@ class ConsumersApiServiceImpl(
request = apiRequestFactory.createPost(
url = consumerAccountsSignUpUrl,
options = requestOptions,
params = mapOf(
"email_address" to email.lowercase(),
"phone_number" to phoneNumber,
"country" to country,
"country_inferring_method" to "PHONE_NUMBER",
"amount" to amount,
"currency" to currency,
"consent_action" to consentAction.value,
"request_surface" to requestSurface,
).plus(
locale?.let {
mapOf("locale" to it.toLanguageTag())
} ?: emptyMap()
).plus(
name?.let {
mapOf("legal_name" to it)
} ?: emptyMap()
).plus(
incentiveEligibilitySession?.toParamMap().orEmpty()
),
params = params.toParamMap()
),
responseJsonParser = ConsumerSessionSignupJsonParser,
)
}

/**
* Retrieves the ConsumerSession if the given email is associated with a Link account.
*/
override suspend fun mobileSignUp(
params: SignUpParams,
requestOptions: ApiRequest.Options
): Result<ConsumerSessionSignup> {
return executeRequestWithResultParser(
stripeErrorJsonParser = stripeErrorJsonParser,
stripeNetworkClient = stripeNetworkClient,
request = apiRequestFactory.createPost(
url = consumerMobileSignUpUrl,
options = requestOptions,
params = params.toParamMap()
),
responseJsonParser = ConsumerSessionSignupJsonParser,
)
Expand Down Expand Up @@ -359,6 +347,13 @@ class ConsumersApiServiceImpl(
internal val consumerAccountsSignUpUrl: String =
getApiUrl("consumers/accounts/sign_up")

/**
* @return `https://api.stripe.com/v1/consumers/mobile/sign_up`
*/
internal val consumerMobileSignUpUrl: String =
getApiUrl("consumers/mobile/sign_up")


/**
* @return `https://api.stripe.com/v1/consumers/sessions/lookup`
*/
Expand Down
Loading

0 comments on commit fc381b6

Please sign in to comment.