Skip to content

Commit

Permalink
Pass billing details in ElementsSessionContext (#9446)
Browse files Browse the repository at this point in the history
* Pass billing address in `ElementsSessionContext`

* Address code review feedback

1. Rename `BillingAddress` to `BillingDetails` and add email
2. Move extension to separate file and add comments
3. Remove duplicate `billing_phone` param

* Put changes behind feature flag
  • Loading branch information
tillh-stripe authored Oct 28, 2024
1 parent b664fe3 commit 0f3d653
Show file tree
Hide file tree
Showing 17 changed files with 416 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.stripe.android.financialconnections.analytics.FinancialConnectionsEve
import com.stripe.android.financialconnections.example.data.BackendRepository
import com.stripe.android.financialconnections.example.data.Settings
import com.stripe.android.financialconnections.example.settings.ConfirmIntentSetting
import com.stripe.android.financialconnections.example.settings.EmailSetting
import com.stripe.android.financialconnections.example.settings.ExperienceSetting
import com.stripe.android.financialconnections.example.settings.FinancialConnectionsPlaygroundUrlHelper
import com.stripe.android.financialconnections.example.settings.FlowSetting
Expand Down Expand Up @@ -136,6 +137,9 @@ internal class FinancialConnectionsPlaygroundViewModel(
amount = it.amount,
currency = it.currency,
linkMode = LinkMode.LinkPaymentMethod,
billingDetails = ElementsSessionContext.BillingDetails(
email = settings.get<EmailSetting>().selectedOption,
),
),
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$BillingDetails$Address$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$BillingDetails$Address;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$BillingDetails$Address;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$BillingDetails$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$BillingDetails;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/financialconnections/FinancialConnectionsSheet$ElementsSessionContext$BillingDetails;
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 billingDetails: BillingDetails?,
) : Parcelable {

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

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Parcelize
data class BillingDetails(
val name: String? = null,
val phone: String? = null,
val email: 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
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ 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 billingDetails = elementsSessionContext?.billingDetails

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

val paymentDetails = response.paymentDetails.filterIsInstance<BankAccount>().first()
Expand All @@ -42,11 +46,13 @@ internal class RealCreateInstantDebitsResult @Inject constructor(
paymentDetailsId = paymentDetails.id,
consumerSessionClientSecret = clientSecret,
expectedPaymentMethodType = elementsSessionContext.linkMode.expectedPaymentMethodType,
billingPhone = elementsSessionContext.billingDetails?.phone,
).paymentMethodId
} else {
repository.createPaymentMethod(
paymentDetailsId = paymentDetails.id,
consumerSessionClientSecret = clientSecret,
billingDetails = billingDetails,
).id
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ 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.BillingDetails
import com.stripe.android.financialconnections.domain.IsLinkWithStripe
import com.stripe.android.financialconnections.repository.api.FinancialConnectionsConsumersApiService
import com.stripe.android.financialconnections.repository.api.ProvideApiRequestOptions
import com.stripe.android.financialconnections.utils.toConsumerBillingAddressParams
import com.stripe.android.model.AttachConsumerToLinkAccountSession
import com.stripe.android.model.ConsumerPaymentDetails
import com.stripe.android.model.ConsumerPaymentDetailsCreateParams
Expand Down Expand Up @@ -57,12 +59,14 @@ internal interface FinancialConnectionsConsumerSessionRepository {
suspend fun createPaymentDetails(
bankAccountId: String,
consumerSessionClientSecret: String,
billingDetails: BillingDetails?,
): ConsumerPaymentDetails

suspend fun sharePaymentDetails(
paymentDetailsId: String,
consumerSessionClientSecret: String,
expectedPaymentMethodType: String,
billingPhone: String?,
): SharePaymentDetails

companion object {
Expand Down Expand Up @@ -200,12 +204,15 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl(

override suspend fun createPaymentDetails(
bankAccountId: String,
consumerSessionClientSecret: String
consumerSessionClientSecret: String,
billingDetails: BillingDetails?,
): ConsumerPaymentDetails {
return consumersApiService.createPaymentDetails(
consumerSessionClientSecret = consumerSessionClientSecret,
paymentDetailsCreateParams = ConsumerPaymentDetailsCreateParams.BankAccount(
bankAccountId = bankAccountId,
billingAddress = billingDetails?.toConsumerBillingAddressParams(),
billingEmailAddress = billingDetails?.email,
),
requestSurface = requestSurface,
requestOptions = provideApiRequestOptions(useConsumerPublishableKey = true),
Expand All @@ -215,14 +222,16 @@ private class FinancialConnectionsConsumerSessionRepositoryImpl(
override suspend fun sharePaymentDetails(
paymentDetailsId: String,
consumerSessionClientSecret: String,
expectedPaymentMethodType: String
expectedPaymentMethodType: String,
billingPhone: String?,
): SharePaymentDetails {
val fraudDetectionData = fraudDetectionDataRepository.getCached()?.params.orEmpty()

return consumersApiService.sharePaymentDetails(
consumerSessionClientSecret = consumerSessionClientSecret,
paymentDetailsId = paymentDetailsId,
expectedPaymentMethodType = expectedPaymentMethodType,
billingPhone = elementsSessionContext?.billingDetails?.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 @@ -6,6 +6,7 @@ import com.stripe.android.core.exception.AuthenticationException
import com.stripe.android.core.exception.InvalidRequestException
import com.stripe.android.core.frauddetection.FraudDetectionDataRepository
import com.stripe.android.core.networking.ApiRequest
import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext.BillingDetails
import com.stripe.android.financialconnections.model.FinancialConnectionsAccountList
import com.stripe.android.financialconnections.model.FinancialConnectionsSession
import com.stripe.android.financialconnections.model.GetFinancialConnectionsAcccountsParams
Expand All @@ -15,6 +16,7 @@ import com.stripe.android.financialconnections.network.FinancialConnectionsReque
import com.stripe.android.financialconnections.network.NetworkConstants
import com.stripe.android.financialconnections.repository.api.ProvideApiRequestOptions
import com.stripe.android.financialconnections.utils.filterNotNullValues
import com.stripe.android.financialconnections.utils.toApiParams
import javax.inject.Inject

internal interface FinancialConnectionsRepository {
Expand Down Expand Up @@ -57,6 +59,7 @@ internal interface FinancialConnectionsRepository {
suspend fun createPaymentMethod(
paymentDetailsId: String,
consumerSessionClientSecret: String,
billingDetails: BillingDetails?,
): PaymentMethod
}

Expand Down Expand Up @@ -135,26 +138,29 @@ internal class FinancialConnectionsRepositoryImpl @Inject constructor(

override suspend fun createPaymentMethod(
paymentDetailsId: String,
consumerSessionClientSecret: String
consumerSessionClientSecret: String,
billingDetails: BillingDetails?,
): PaymentMethod {
val credentials = mapOf(
"consumer_session_client_secret" to consumerSessionClientSecret,
)

val params = mapOf(
val linkParams = mapOf(
"type" to "link",
"link" to mapOf(
"credentials" to credentials,
"credentials" to mapOf(
"consumer_session_client_secret" to consumerSessionClientSecret,
),
"payment_details_id" to paymentDetailsId,
),
)

val billingParams = billingDetails?.let {
mapOf("billing_details" to billingDetails.toApiParams())
}.orEmpty()

val fraudDetectionParams = fraudDetectionDataRepository.getCached()?.params.orEmpty()

val request = apiRequestFactory.createPost(
url = paymentMethodsUrl,
options = provideApiRequestOptions(useConsumerPublishableKey = false),
params = params + fraudDetectionParams,
params = linkParams + billingParams + fraudDetectionParams,
)

return requestExecutor.execute(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.stripe.android.financialconnections.utils

import com.stripe.android.financialconnections.FinancialConnectionsSheet.ElementsSessionContext.BillingDetails

/**
* Creates API params for use with the Stripe core API.
*
* These params include the phone number and a nested address object.
*/
internal fun BillingDetails.toApiParams(): Map<String, Any> {
val addressParams = address?.let { address ->
buildMap {
address.line1?.let { put("line1", it) }
address.line2?.let { put("line2", it) }
address.postalCode?.let { put("postal_code", it) }
address.city?.let { put("city", it) }
address.state?.let { put("state", it) }
address.country?.let { put("country", it) }
}.filterValues {
it.isNotBlank()
}
}
return mapOf(
"name" to name,
"email" to email,
"phone" to phone,
"address" to addressParams,
).filterNotNullValues()
}

/**
* Creates API params for use with the consumer API.
*
* These params don't include the phone number and flatten the address.
*/
internal fun BillingDetails.toConsumerBillingAddressParams(): Map<String, Any> {
val contactParams = buildMap {
name?.let { put("name", it) }
}.filter { entry ->
entry.value.isNotBlank()
}

val addressParams = buildMap {
address?.line1?.let { put("line_1", it) }
address?.line2?.let { put("line_2", it) }
address?.postalCode?.let { put("postal_code", it) }
address?.city?.let { put("locality", it) }
address?.state?.let { put("administrative_area", it) }
address?.country?.let { put("country_code", it) }
}.filterValues {
it.isNotBlank()
}

return contactParams + addressParams
}
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,
billingDetails = null,
),
)
)
Expand Down Expand Up @@ -180,6 +181,7 @@ class FinancialConnectionsSheetViewModelTest {
amount = 123,
currency = "usd",
linkMode = null,
billingDetails = null,
),
)
)
Expand Down
Loading

0 comments on commit 0f3d653

Please sign in to comment.