Skip to content

Commit

Permalink
Pass billing address in ElementsSessionContext
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe committed Oct 17, 2024
1 parent 87ab380 commit 7396227
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ internal class FinancialConnectionsPlaygroundViewModel(
amount = it.amount,
currency = it.currency,
linkMode = LinkMode.LinkPaymentMethod,
billingAddress = null,
),
experience = settings.get<ExperienceSetting>().selectedOption,
integrationType = settings.get<IntegrationTypeSetting>().selectedOption,
Expand Down
16 changes: 16 additions & 0 deletions financial-connections/api/financial-connections.api
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,22 @@ public final class com/stripe/android/financialconnections/FinancialConnectionsS
public synthetic fun newArray (I)[Ljava/lang/Object;
}

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

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

public final class com/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class FinancialConnectionsSheet internal constructor(
val amount: Long?,
val currency: String?,
val linkMode: LinkMode?,
val billingAddress: BillingAddress?,
) : Parcelable {

val paymentIntentId: String?
Expand All @@ -69,6 +70,51 @@ class FinancialConnectionsSheet internal constructor(
@Parcelize
data object DeferredIntent : InitializationMode
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Parcelize
data class BillingAddress(
val name: String? = null,
val phone: String? = null,
val address: Address? = null,
) : Parcelable {

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Parcelize
data class Address(
val line1: String? = null,
val line2: String? = null,
val postalCode: String? = null,
val city: String? = null,
val state: String? = null,
val country: String? = null,
) : Parcelable {

fun consumerApiParams(): Map<String, Any> {
return buildMap {
line1?.let { put("line_1", it) }
line2?.let { put("line_2", it) }
postalCode?.let { put("postal_code", it) }
city?.let { put("locality", it) }
state?.let { put("administrative_area", it) }
country?.let { put("country_code", it) }
}.filter { entry ->
entry.value.isNotBlank()
}
}
}

fun consumerApiParams(): Map<String, Any> {
val contactParams = buildMap {
name?.let { put("name", it) }
}.filter { entry ->
entry.value.isNotBlank()
}

val addressParams = address?.consumerApiParams().orEmpty()
return contactParams + addressParams
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,22 @@ internal class RealCreateInstantDebitsResult @Inject constructor(
bankAccountId: String,
): InstantDebitsResult {
val consumerSession = consumerSessionProvider.provideConsumerSession()

val clientSecret = requireNotNull(consumerSession?.clientSecret) {
"Consumer session client secret cannot be null"
}

val billingEmailAddress = requireNotNull(consumerSession?.emailAddress) {
"Consumer session email address cannot be null"
}

val billingAddress = elementsSessionContext?.billingAddress

val response = consumerRepository.createPaymentDetails(
consumerSessionClientSecret = clientSecret,
bankAccountId = bankAccountId,
billingAddress = billingAddress,
billingEmailAddress = billingEmailAddress,
)

val paymentDetails = response.paymentDetails.filterIsInstance<BankAccount>().first()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.stripe.android.financialconnections.repository
import com.stripe.android.core.Logger
import com.stripe.android.core.frauddetection.FraudDetectionDataRepository
import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext
import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext.BillingAddress
import com.stripe.android.financialconnections.domain.IsLinkWithStripe
import com.stripe.android.financialconnections.repository.api.FinancialConnectionsConsumersApiService
import com.stripe.android.financialconnections.repository.api.ProvideApiRequestOptions
Expand Down Expand Up @@ -57,6 +58,8 @@ internal interface FinancialConnectionsConsumerSessionRepository {
suspend fun createPaymentDetails(
bankAccountId: String,
consumerSessionClientSecret: String,
billingAddress: BillingAddress?,
billingEmailAddress: String,
): ConsumerPaymentDetails

suspend fun sharePaymentDetails(
Expand Down Expand Up @@ -200,12 +203,16 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl(

override suspend fun createPaymentDetails(
bankAccountId: String,
consumerSessionClientSecret: String
consumerSessionClientSecret: String,
billingAddress: BillingAddress?,
billingEmailAddress: String,
): ConsumerPaymentDetails {
return consumersApiService.createPaymentDetails(
consumerSessionClientSecret = consumerSessionClientSecret,
paymentDetailsCreateParams = ConsumerPaymentDetailsCreateParams.BankAccount(
bankAccountId = bankAccountId,
billingAddress = billingAddress?.consumerApiParams(),
billingEmailAddress = billingEmailAddress,
),
requestSurface = requestSurface,
requestOptions = provideApiRequestOptions(useConsumerPublishableKey = true),
Expand All @@ -223,6 +230,7 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl(
consumerSessionClientSecret = consumerSessionClientSecret,
paymentDetailsId = paymentDetailsId,
expectedPaymentMethodType = expectedPaymentMethodType,
billingPhone = elementsSessionContext?.billingAddress?.phone?.takeIf { it.isNotBlank() },
requestSurface = requestSurface,
requestOptions = provideApiRequestOptions(useConsumerPublishableKey = false),
extraParams = fraudDetectionData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class FinancialConnectionsSheetViewModelTest {
amount = 123,
currency = "usd",
linkMode = LinkMode.LinkPaymentMethod,
billingAddress = null,
),
)
)
Expand Down Expand Up @@ -180,6 +181,7 @@ class FinancialConnectionsSheetViewModelTest {
amount = 123,
currency = "usd",
linkMode = null,
billingAddress = null,
),
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.stripe.android.financialconnections.domain

import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext
import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext.BillingAddress
import com.stripe.android.financialconnections.model.PaymentMethod
import com.stripe.android.financialconnections.repository.CachedConsumerSession
import com.stripe.android.financialconnections.repository.FinancialConnectionsConsumerSessionRepository
Expand All @@ -11,6 +12,7 @@ import com.stripe.android.model.SharePaymentDetails
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
Expand Down Expand Up @@ -87,6 +89,79 @@ class RealCreateInstantDebitsResultTest {
)
}

@Test
fun `Passes along billing details to createPaymentMethod if available`() = runTest {
val consumerRepository = makeConsumerSessionRepository()
val repository = makeRepository()

val billingDetails = BillingAddress(
name = "Some name",
phone = "+15555555555",
address = BillingAddress.Address(
city = "San Francisco",
country = "US",
line1 = "123 Main St",
line2 = "Apt 4",
postalCode = "94111",
state = "CA",
),
)

val createInstantDebitResult = RealCreateInstantDebitsResult(
consumerRepository = consumerRepository,
repository = repository,
consumerSessionProvider = { makeCachedConsumerSession() },
elementsSessionContext = makeElementsSessionContext(
linkMode = null,
billingAddress = billingDetails,
),
)

createInstantDebitResult("bank_account_id_001")

verify(repository).createPaymentMethod(
paymentDetailsId = "ba_1234",
consumerSessionClientSecret = "clientSecret",
)
}

@Test
fun `Passes along billing details to sharePaymentDetails if available`() = runTest {
val consumerRepository = makeConsumerSessionRepository()
val repository = makeRepository()

val billingDetails = BillingAddress(
name = "Some name",
phone = "+15555555555",
address = BillingAddress.Address(
city = "San Francisco",
country = "US",
line1 = "123 Main St",
line2 = "Apt 4",
postalCode = "94111",
state = "CA",
),
)

val createInstantDebitResult = RealCreateInstantDebitsResult(
consumerRepository = consumerRepository,
repository = repository,
consumerSessionProvider = { makeCachedConsumerSession() },
elementsSessionContext = makeElementsSessionContext(
linkMode = LinkMode.LinkCardBrand,
billingAddress = billingDetails,
),
)

createInstantDebitResult("bank_account_id_001")

verify(consumerRepository).sharePaymentDetails(
paymentDetailsId = "ba_1234",
consumerSessionClientSecret = "clientSecret",
expectedPaymentMethodType = "card",
)
}

private fun makeConsumerSessionRepository(): FinancialConnectionsConsumerSessionRepository {
val consumerPaymentDetails = ConsumerPaymentDetails(
paymentDetails = listOf(
Expand All @@ -103,7 +178,7 @@ class RealCreateInstantDebitsResultTest {
)

return mock<FinancialConnectionsConsumerSessionRepository> {
onBlocking { createPaymentDetails(any(), any()) } doReturn consumerPaymentDetails
onBlocking { createPaymentDetails(any(), any(), anyOrNull(), any()) } doReturn consumerPaymentDetails
onBlocking { sharePaymentDetails(any(), any(), any()) } doReturn sharePaymentDetails
}
}
Expand All @@ -130,12 +205,14 @@ class RealCreateInstantDebitsResultTest {

private fun makeElementsSessionContext(
linkMode: LinkMode?,
billingAddress: BillingAddress? = null,
): ElementsSessionContext {
return ElementsSessionContext(
initializationMode = ElementsSessionContext.InitializationMode.PaymentIntent("pi_123"),
amount = 100L,
currency = "usd",
linkMode = linkMode,
billingAddress = billingAddress,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest {
amount = 1234,
currency = "cad",
linkMode = LinkMode.LinkPaymentMethod,
billingAddress = null,
)
)

Expand Down Expand Up @@ -427,6 +428,7 @@ class FinancialConnectionsConsumerSessionRepositoryImplTest {
consumerSessionClientSecret = anyOrNull(),
paymentDetailsId = anyOrNull(),
expectedPaymentMethodType = anyOrNull(),
billingPhone = anyOrNull(),
requestSurface = anyOrNull(),
requestOptions = anyOrNull(),
extraParams = eq(fraudParams.params),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,27 @@ sealed interface ConsumerPaymentDetailsCreateParams : StripeParamsModel, Parcela
@Parcelize
data class BankAccount(
private val bankAccountId: String,
private val billingAddress: Map<String, @RawValue Any>?,
private val billingEmailAddress: String,
) : ConsumerPaymentDetailsCreateParams {

override fun toParamMap(): Map<String, Any> {
return mapOf(
val billingParams = buildMap {
put("billing_email_address", billingEmailAddress)

if (!billingAddress.isNullOrEmpty()) {
put("billing_address", billingAddress)
}
}

val accountParams = mapOf(
"type" to "bank_account",
"bank_account" to mapOf(
"account" to bankAccountId,
),
)

return accountParams + billingParams
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ interface ConsumersApiService {
consumerSessionClientSecret: String,
paymentDetailsId: String,
expectedPaymentMethodType: String,
billingPhone: String?,
requestSurface: String,
requestOptions: ApiRequest.Options,
extraParams: Map<String, Any?>,
Expand Down Expand Up @@ -295,6 +296,7 @@ class ConsumersApiServiceImpl(
consumerSessionClientSecret: String,
paymentDetailsId: String,
expectedPaymentMethodType: String,
billingPhone: String?,
requestSurface: String,
requestOptions: ApiRequest.Options,
extraParams: Map<String, Any?>,
Expand All @@ -312,6 +314,7 @@ class ConsumersApiServiceImpl(
"credentials" to mapOf(
"consumer_session_client_secret" to consumerSessionClientSecret
),
"billing_phone" to billingPhone,
) + extraParams,
),
responseJsonParser = SharePaymentDetailsJsonParser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,20 @@ internal class USBankAccountFormViewModel @Inject internal constructor(
amount = args.formArgs.amount?.value,
currency = args.formArgs.amount?.currencyCode,
linkMode = args.linkMode,
billingAddress = ElementsSessionContext.BillingAddress(
name = name.value,
phone = phone.value?.let { phoneController.getE164PhoneNumber(it) },
address = address.value?.let {
ElementsSessionContext.BillingAddress.Address(
line1 = it.line1,
line2 = it.line2,
postalCode = it.postalCode,
city = it.city,
state = it.state,
country = it.country,
)
},
),
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConf
import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
import org.junit.Test

class BillingDetailsCollectionConfigurationTest {
class BillingAddressCollectionConfigurationTest {

@Test
fun `Creates correct Google Pay billing address config with default collection configuration`() {
Expand Down
Loading

0 comments on commit 7396227

Please sign in to comment.