Skip to content

Commit

Permalink
Attach billingDetails to PaymentOptionDisplayData (#9891)
Browse files Browse the repository at this point in the history
* Attach billingDetails to PaymentOptionDisplayData
  • Loading branch information
tjclawson-stripe authored Jan 13, 2025
1 parent 04eaa8d commit 875aa0e
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
import com.stripe.android.paymentelement.EmbeddedPaymentElement
import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.paymentsheet.model.billingDetails
import com.stripe.android.paymentsheet.model.darkThemeIconUrl
import com.stripe.android.paymentsheet.model.drawableResourceId
import com.stripe.android.paymentsheet.model.label
import com.stripe.android.paymentsheet.model.lightThemeIconUrl
import com.stripe.android.paymentsheet.model.paymentMethodType
import com.stripe.android.paymentsheet.model.toPaymentSheetBillingDetails
import javax.inject.Inject

@ExperimentalEmbeddedPaymentElementApi
Expand Down Expand Up @@ -53,7 +55,7 @@ internal class PaymentOptionDisplayDataFactory @Inject constructor(
darkThemeIconUrl = selection.darkThemeIconUrl,
)
},
billingDetails = null,
billingDetails = selection.billingDetails?.toPaymentSheetBillingDetails(),
paymentMethodType = selection.paymentMethodType,
mandateText = if (mandate == null) null else AnnotatedString(mandate.resolve(context))
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.stripe.android.model.PaymentMethod.Type.USBankAccount
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.PaymentMethodExtraParams
import com.stripe.android.model.PaymentMethodOptionsParams
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.R
import com.stripe.android.paymentsheet.paymentdatacollection.ach.BankFormScreenState
import com.stripe.android.paymentsheet.paymentdatacollection.ach.USBankAccountTextBuilder
Expand Down Expand Up @@ -395,3 +396,28 @@ internal val PaymentSelection.paymentMethodType: String
is PaymentSelection.New.USBankAccount -> paymentMethodCreateParams.typeCode
is PaymentSelection.Saved -> paymentMethod.type?.name ?: "card"
}

internal val PaymentSelection.billingDetails: PaymentMethod.BillingDetails?
get() = when (this) {
is PaymentSelection.ExternalPaymentMethod -> billingDetails
PaymentSelection.GooglePay -> null
PaymentSelection.Link -> null
is PaymentSelection.New -> paymentMethodCreateParams.billingDetails
is PaymentSelection.Saved -> paymentMethod.billingDetails
}

internal fun PaymentMethod.BillingDetails.toPaymentSheetBillingDetails(): PaymentSheet.BillingDetails {
return PaymentSheet.BillingDetails(
address = PaymentSheet.Address(
city = address?.city,
country = address?.country,
line1 = address?.line1,
line2 = address?.line2,
postalCode = address?.postalCode,
state = address?.state
),
email = email,
name = name,
phone = phone
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.stripe.android.paymentelement.embedded

import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadataFactory
import com.stripe.android.model.Address
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.PaymentMethodFixtures
import com.stripe.android.model.SetupIntentFixtures
import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.model.PaymentSelection
import com.stripe.android.ui.core.cbc.CardBrandChoiceEligibility
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.robolectric.RobolectricTestRunner

@OptIn(ExperimentalEmbeddedPaymentElementApi::class)
@RunWith(RobolectricTestRunner::class)
internal class PaymentOptionDisplayDataFactoryTest {

private val displayDataFactory = PaymentOptionDisplayDataFactory(
iconLoader = mock(),
context = ApplicationProvider.getApplicationContext(),
)

@Test
fun `create attaches PaymentMethod BillingDetails as PaymentSheet BillingDetails `() {
val option = displayDataFactory.create(
selection = PaymentMethodFixtures.CARD_PAYMENT_SELECTION.copy(
paymentMethodCreateParams = PaymentMethodCreateParams(
code = "card",
requiresMandate = false,
billingDetails = paymentMethodBillingDetails
)
),
paymentMethodMetadata = paymentMethodMetadata,
)

assertThat(option?.billingDetails).isEqualTo(paymentSheetBillingDetails)
}

@Test
fun `create does not attach BillingDetails for Google Pay`() {
val option = displayDataFactory.create(
selection = PaymentSelection.GooglePay,
paymentMethodMetadata = paymentMethodMetadata
)

assertThat(option?.billingDetails).isNull()
}

@Test
fun `selecting saved card does not attach mandate to paymentMethodMetadata`() {
val option = displayDataFactory.create(
selection = PaymentSelection.Saved(PaymentMethodFixtures.CARD_PAYMENT_METHOD),
paymentMethodMetadata = paymentMethodMetadata
)

assertThat(option?.mandateText).isNull()
}

@Test
fun `selecting new card does attach mandate to paymentMethodMetadata`() {
val option = displayDataFactory.create(
selection = PaymentMethodFixtures.CARD_PAYMENT_SELECTION,
paymentMethodMetadata = paymentMethodMetadata
)

assertThat(option?.mandateText).isNotNull()
}

@Test
fun `selecting google pay does not attach mandate to paymentMethodMetadata`() {
val option = displayDataFactory.create(
selection = PaymentSelection.GooglePay,
paymentMethodMetadata = paymentMethodMetadata
)

assertThat(option?.mandateText).isNull()
}

companion object {
private val paymentSheetBillingDetails = PaymentSheet.BillingDetails(
name = "Jenny Rosen",
email = "[email protected]",
phone = "+13105551234",
address = PaymentSheet.Address(
postalCode = "94111",
country = "US",
),
)
private val paymentMethodBillingDetails = PaymentMethod.BillingDetails(
address = Address(
postalCode = "94111",
country = "US",
),
email = "[email protected]",
name = "Jenny Rosen",
phone = "+13105551234"
)

private val paymentMethodMetadata = PaymentMethodMetadataFactory.create(
stripeIntent = SetupIntentFixtures.SI_SUCCEEDED.copy(
paymentMethodTypes = listOf("card", "cashapp", "google_pay"),
),
billingDetailsCollectionConfiguration = PaymentSheet.BillingDetailsCollectionConfiguration(),
allowsDelayedPaymentMethods = false,
allowsPaymentMethodsRequiringShippingAddress = false,
isGooglePayReady = true,
cbcEligibility = CardBrandChoiceEligibility.Ineligible,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import com.stripe.android.common.model.asCommonConfiguration
import com.stripe.android.isInstanceOf
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadataFactory
import com.stripe.android.model.PaymentIntentFixtures
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.model.PaymentMethodFixtures
import com.stripe.android.model.SetupIntentFixtures
import com.stripe.android.paymentelement.EmbeddedPaymentElement
import com.stripe.android.paymentelement.ExperimentalEmbeddedPaymentElementApi
import com.stripe.android.paymentelement.confirmation.FakeConfirmationHandler
Expand Down Expand Up @@ -306,94 +303,6 @@ internal class SharedPaymentElementViewModelTest {
assertThat(embeddedContentHelper.dataLoadedTurbine.awaitItem()).isNotNull()
}

@Test
fun `selecting lpm PaymentOption with mandate attaches mandate to paymentMethodMetadata`() {
mandateTest(
paymentSelection = PaymentMethodFixtures.CASHAPP_PAYMENT_SELECTION.copy(
paymentMethodCreateParams = PaymentMethodCreateParams(code = "cashapp", requiresMandate = true)
),
validate = {
assertThat(it?.mandateText).isNotNull()
}
)
}

@Test
fun `selecting saved card does not attach mandate to paymentMethodMetadata`() {
mandateTest(
paymentSelection = PaymentSelection.Saved(PaymentMethodFixtures.CARD_PAYMENT_METHOD),
validate = {
assertThat(it?.mandateText).isNull()
}
)
}

@Test
fun `selecting new card does attach mandate to paymentMethodMetadata`() {
mandateTest(
paymentSelection = PaymentMethodFixtures.CARD_PAYMENT_SELECTION,
validate = {
assertThat(it?.mandateText).isNotNull()
}
)
}

@Test
fun `selecting google pay does not attach mandate to paymentMethodMetadata`() {
mandateTest(
paymentSelection = PaymentSelection.GooglePay,
validate = {
assertThat(it?.mandateText).isNull()
}
)
}

private fun mandateTest(
paymentSelection: PaymentSelection,
validate: (paymentOption: EmbeddedPaymentElement.PaymentOptionDisplayData?) -> Unit
) = testScenario {
val configuration = EmbeddedPaymentElement.Configuration.Builder("Example, Inc.").build()
configurationHandler.emit(
Result.success(
PaymentElementLoader.State(
config = configuration.asCommonConfiguration(),
customer = null,
linkState = null,
paymentSelection = paymentSelection,
validationError = null,
paymentMethodMetadata = PaymentMethodMetadataFactory.create(
stripeIntent = SetupIntentFixtures.SI_SUCCEEDED.copy(
paymentMethodTypes = listOf("card", "cashapp"),
),
billingDetailsCollectionConfiguration = configuration
.billingDetailsCollectionConfiguration,
allowsDelayedPaymentMethods = configuration.allowsDelayedPaymentMethods,
allowsPaymentMethodsRequiringShippingAddress = configuration
.allowsPaymentMethodsRequiringShippingAddress,
isGooglePayReady = true,
cbcEligibility = CardBrandChoiceEligibility.Ineligible,
),
)
)
)

assertThat(selectionHolder.selection.value?.paymentMethodType).isNull()
viewModel.paymentOption.test {
assertThat(awaitItem()).isNull()

assertThat(
viewModel.configure(
PaymentSheet.IntentConfiguration(
PaymentSheet.IntentConfiguration.Mode.Setup("USD"),
),
configuration = configuration,
)
).isInstanceOf<EmbeddedPaymentElement.ConfigureResult.Succeeded>()
validate.invoke(awaitItem())
}
assertThat(embeddedContentHelper.dataLoadedTurbine.awaitItem()).isNotNull()
}

private fun testScenario(
block: suspend Scenario.() -> Unit,
) = runTest {
Expand Down

0 comments on commit 875aa0e

Please sign in to comment.