Skip to content

Commit

Permalink
Add full support for ConsentAction tracking in Link. (stripe#7825)
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe authored Jan 30, 2024
1 parent c0db7df commit 8ad9814
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.stripe.android.link.injection.LinkScope
import com.stripe.android.link.model.AccountStatus
import com.stripe.android.link.model.LinkAccount
import com.stripe.android.link.repositories.LinkRepository
import com.stripe.android.link.ui.inline.SignUpConsentAction
import com.stripe.android.link.ui.inline.UserInput
import com.stripe.android.model.ConsumerSession
import com.stripe.android.model.ConsumerSignUpConsentAction
Expand Down Expand Up @@ -73,7 +74,9 @@ internal class LinkAccountManager @Inject constructor(
* Use the user input in memory to sign in to an existing account or sign up for a new Link
* account, starting verification if needed.
*/
suspend fun signInWithUserInput(userInput: UserInput): Result<LinkAccount> =
suspend fun signInWithUserInput(
userInput: UserInput
): Result<LinkAccount> =
when (userInput) {
is UserInput.SignIn -> lookupConsumer(userInput.email).mapCatching {
requireNotNull(it) { "Error fetching user account" }
Expand All @@ -82,7 +85,8 @@ internal class LinkAccountManager @Inject constructor(
email = userInput.email,
country = userInput.country,
phone = userInput.phone,
name = userInput.name
name = userInput.name,
consentAction = userInput.consentAction,
)
}

Expand All @@ -93,7 +97,8 @@ internal class LinkAccountManager @Inject constructor(
email: String,
phone: String,
country: String,
name: String?
name: String?,
consentAction: SignUpConsentAction
): Result<LinkAccount> {
val currentAccount = _linkAccount.value
val currentEmail = currentAccount?.email ?: config.customerInfo.email
Expand Down Expand Up @@ -127,7 +132,7 @@ internal class LinkAccountManager @Inject constructor(
phone = phone,
country = country,
name = name,
consentAction = ConsumerSignUpConsentAction.Checkbox
consentAction = consentAction
).onSuccess {
linkEventsReporter.onSignupCompleted(true)
}.onFailure { error ->
Expand All @@ -145,9 +150,9 @@ internal class LinkAccountManager @Inject constructor(
phone: String,
country: String,
name: String?,
consentAction: ConsumerSignUpConsentAction
consentAction: SignUpConsentAction
): Result<LinkAccount> =
linkRepository.consumerSignUp(email, phone, country, name, authSessionCookie, consentAction)
linkRepository.consumerSignUp(email, phone, country, name, authSessionCookie, consentAction.consumerAction)
.map { consumerSession ->
setAccount(consumerSession)
}
Expand Down Expand Up @@ -239,14 +244,30 @@ internal class LinkAccountManager @Inject constructor(
}

private suspend fun LinkAccount?.fetchAccountStatus(): AccountStatus =
// If we already fetched an account, return its status
/**
* If we already fetched an account, return its status, otherwise if a customer email was passed in,
* lookup the account.
*/
this?.accountStatus
// If a customer email was passed in, lookup the account.
?: config.customerInfo.email?.let { customerEmail ->
lookupConsumer(customerEmail).map {
it?.accountStatus
}.getOrElse {
AccountStatus.Error
}
} ?: AccountStatus.SignedOut

private val SignUpConsentAction.consumerAction: ConsumerSignUpConsentAction
get() = when (this) {
SignUpConsentAction.Checkbox ->
ConsumerSignUpConsentAction.Checkbox
SignUpConsentAction.CheckboxWithPrefilledEmail ->
ConsumerSignUpConsentAction.CheckboxWithPrefilledEmail
SignUpConsentAction.CheckboxWithPrefilledEmailAndPhone ->
ConsumerSignUpConsentAction.CheckboxWithPrefilledEmailAndPhone
SignUpConsentAction.Implied ->
ConsumerSignUpConsentAction.Implied
SignUpConsentAction.ImpliedWithPrefilledEmail ->
ConsumerSignUpConsentAction.ImpliedWithPrefilledEmail
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ internal class InlineSignupViewModel @Inject constructor(

private val shouldPrefill = config.customerInfo.shouldPrefill
private val prefilledEmail = config.customerInfo.email.takeIf { shouldPrefill }
private val prefilledPhone = config.customerInfo.phone.takeIf { shouldPrefill }.orEmpty()
private val prefilledPhone = config.customerInfo.phone.takeIf { shouldPrefill }
private val prefilledName = config.customerInfo.name.takeIf { shouldPrefill }

val emailController = EmailConfig.createController(
Expand All @@ -47,7 +47,7 @@ internal class InlineSignupViewModel @Inject constructor(
)

val phoneController = PhoneNumberController.createPhoneNumberController(
initialValue = prefilledPhone,
initialValue = prefilledPhone.orEmpty(),
initiallySelectedCountryCode = config.customerInfo.billingCountryCode,
)

Expand Down Expand Up @@ -175,13 +175,24 @@ internal class InlineSignupViewModel @Inject constructor(
phoneNumber: String?,
name: String?
): UserInput? {
return if (email != null && phoneNumber != null) {
val signUpMode = config.signupMode

return if (email != null && phoneNumber != null && signUpMode != null) {
val isNameValid = !requiresNameCollection || !name.isNullOrBlank()

val phone = phoneController.getE164PhoneNumber(phoneNumber)
val country = phoneController.getCountryCode()

UserInput.SignUp(email, phone, country, name).takeIf { isNameValid }
UserInput.SignUp(
email = email,
phone = phone,
country = country,
name = name,
consentAction = signUpMode.toConsentAction(
hasPrefilledEmail = prefilledEmail != null,
hasPrefilledPhone = prefilledPhone != null
)
).takeIf { isNameValid }
} else {
null
}
Expand Down Expand Up @@ -232,6 +243,30 @@ internal class InlineSignupViewModel @Inject constructor(
_errorMessage.value = it
}

private fun LinkSignupMode.toConsentAction(
hasPrefilledEmail: Boolean,
hasPrefilledPhone: Boolean
): SignUpConsentAction {
return when (this) {
LinkSignupMode.AlongsideSaveForFutureUse -> {
when (hasPrefilledEmail) {
true -> SignUpConsentAction.ImpliedWithPrefilledEmail
false -> SignUpConsentAction.Implied
}
}
LinkSignupMode.InsteadOfSaveForFutureUse -> {
when {
hasPrefilledEmail && hasPrefilledPhone ->
SignUpConsentAction.CheckboxWithPrefilledEmailAndPhone
hasPrefilledEmail ->
SignUpConsentAction.CheckboxWithPrefilledEmail
else ->
SignUpConsentAction.Checkbox
}
}
}
}

internal class Factory(
private val linkComponent: LinkComponent
) : ViewModelProvider.Factory {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.stripe.android.link.ui.inline

import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
enum class SignUpConsentAction {
Checkbox,
CheckboxWithPrefilledEmail,
CheckboxWithPrefilledEmailAndPhone,
Implied,
ImpliedWithPrefilledEmail
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ sealed class UserInput {
val email: String,
val phone: String,
val country: String,
val name: String?
val name: String?,
val consentAction: SignUpConsentAction
) : UserInput()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.stripe.android.link.analytics.LinkEventsReporter
import com.stripe.android.link.model.AccountStatus
import com.stripe.android.link.repositories.LinkRepository
import com.stripe.android.link.ui.inline.LinkSignupMode
import com.stripe.android.link.ui.inline.SignUpConsentAction
import com.stripe.android.link.ui.inline.UserInput
import com.stripe.android.model.CardParams
import com.stripe.android.model.ConsumerPaymentDetails
Expand Down Expand Up @@ -138,7 +139,15 @@ class LinkAccountManagerTest {
val country = "country"
val name = "name"

accountManager.signInWithUserInput(UserInput.SignUp(EMAIL, phone, country, name))
accountManager.signInWithUserInput(
UserInput.SignUp(
email = EMAIL,
phone = phone,
country = country,
name = name,
consentAction = SignUpConsentAction.Checkbox
)
)

verify(linkRepository).consumerSignUp(
email = eq(EMAIL),
Expand All @@ -151,6 +160,56 @@ class LinkAccountManagerTest {
assertThat(accountManager.linkAccount.value).isNotNull()
}

@Test
fun `signInWithUserInput sends correct consumer action on 'Checkbox' consent action`() = runSuspendTest {
accountManager().signInWithUserInput(createUserInputWithAction(SignUpConsentAction.Checkbox))

verifyConsumerAction(ConsumerSignUpConsentAction.Checkbox)
}

@Test
fun `signInWithUserInput sends correct consumer action on 'CheckboxWithPrefilledEmail' consent action`() =
runSuspendTest {
accountManager().signInWithUserInput(
createUserInputWithAction(
SignUpConsentAction.CheckboxWithPrefilledEmail
)
)

verifyConsumerAction(ConsumerSignUpConsentAction.CheckboxWithPrefilledEmail)
}

@Test
fun `signInWithUserInput sends correct consumer action on 'CheckboxWithPrefilledEmailAndPhone' consent action`() =
runSuspendTest {
accountManager().signInWithUserInput(
createUserInputWithAction(
SignUpConsentAction.CheckboxWithPrefilledEmailAndPhone
)
)

verifyConsumerAction(ConsumerSignUpConsentAction.CheckboxWithPrefilledEmailAndPhone)
}

@Test
fun `signInWithUserInput sends correct consumer action on 'Implied' consent action`() = runSuspendTest {
accountManager().signInWithUserInput(createUserInputWithAction(SignUpConsentAction.Implied))

verifyConsumerAction(ConsumerSignUpConsentAction.Implied)
}

@Test
fun `signInWithUserInput sends correct consumer action on 'ImpliedWithPrefilledEmail' consent action`() =
runSuspendTest {
accountManager().signInWithUserInput(
createUserInputWithAction(
SignUpConsentAction.ImpliedWithPrefilledEmail
)
)

verifyConsumerAction(ConsumerSignUpConsentAction.ImpliedWithPrefilledEmail)
}

@Test
fun `signInWithUserInput for new user sends analytics event when call succeeds`() =
runSuspendTest {
Expand All @@ -159,7 +218,8 @@ class LinkAccountManagerTest {
email = EMAIL,
phone = "phone",
country = "country",
name = "name"
name = "name",
consentAction = SignUpConsentAction.Checkbox
)
)

Expand All @@ -178,7 +238,8 @@ class LinkAccountManagerTest {
email = EMAIL,
phone = "phone",
country = "country",
name = "name"
name = "name",
consentAction = SignUpConsentAction.Checkbox
)
)

Expand All @@ -202,7 +263,8 @@ class LinkAccountManagerTest {
email = EMAIL,
phone = "phone",
country = "country",
name = "name"
name = "name",
consentAction = SignUpConsentAction.Checkbox
)
)

Expand All @@ -228,7 +290,8 @@ class LinkAccountManagerTest {
email = EMAIL,
phone = "phone",
country = "country",
name = "name"
name = "name",
consentAction = SignUpConsentAction.Checkbox
)
)

Expand Down Expand Up @@ -390,6 +453,27 @@ class LinkAccountManagerTest {
linkEventsReporter,
)

private fun createUserInputWithAction(consentAction: SignUpConsentAction): UserInput.SignUp {
return UserInput.SignUp(
email = EMAIL,
phone = "phone",
country = "country",
name = "name",
consentAction = consentAction
)
}

private suspend fun verifyConsumerAction(consumerAction: ConsumerSignUpConsentAction) {
verify(linkRepository).consumerSignUp(
email = any(),
phone = any(),
country = any(),
name = anyOrNull(),
authSessionCookie = anyOrNull(),
consentAction = eq(consumerAction)
)
}

private companion object {
const val EMAIL = "[email protected]"
const val CLIENT_SECRET = "client_secret"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class LinkApiRepositoryTest {
"country",
"name",
"cookie",
ConsumerSignUpConsentAction.Button
ConsumerSignUpConsentAction.Implied
)

assertThat(result.isFailure).isTrue()
Expand Down
Loading

0 comments on commit 8ad9814

Please sign in to comment.