Skip to content

Commit 354f22e

Browse files
cttsai-stripecodex
andcommitted
Generalize automatic-tax billing address primitives (MOBILESDK-4667)
Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com> Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com>
1 parent e68faca commit 354f22e

11 files changed

Lines changed: 71 additions & 72 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.stripe.android.ui.core.elements
2+
3+
import androidx.annotation.RestrictTo
4+
import com.stripe.android.uicore.elements.IdentifierSpec
5+
6+
/**
7+
* Billing address fields required in addition to country for automatic tax calculation.
8+
* Countries absent from this map require country only.
9+
*
10+
* Source: https://docs.stripe.com/tax/customer-locations
11+
*/
12+
private val additionalAutomaticTaxFieldsByCountry: Map<String, Set<IdentifierSpec>> = mapOf(
13+
"CA" to setOf(IdentifierSpec.PostalCode),
14+
"GB" to setOf(IdentifierSpec.PostalCode),
15+
"IN" to setOf(IdentifierSpec.PostalCode),
16+
"PR" to setOf(IdentifierSpec.Line1, IdentifierSpec.City, IdentifierSpec.PostalCode),
17+
"US" to setOf(IdentifierSpec.Line1, IdentifierSpec.City, IdentifierSpec.State, IdentifierSpec.PostalCode),
18+
)
19+
20+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
21+
fun automaticTaxRequiredFields(countryCode: String): Set<IdentifierSpec> {
22+
return additionalAutomaticTaxFieldsByCountry[countryCode].orEmpty()
23+
}

payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/CardBillingAddressElement.kt renamed to payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/BillingAddressElement.kt

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,10 @@ import com.stripe.android.uicore.utils.mapAsStateFlow
3030
import kotlinx.coroutines.flow.StateFlow
3131

3232
/**
33-
* This is a special type of AddressElement that
34-
* removes fields from the address based on the country. It
35-
* is only intended to be used with the card payment method.
33+
* An address element that dynamically removes fields based on the selected country and collection mode.
3634
*/
3735
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
38-
class CardBillingAddressElement(
36+
class BillingAddressElement(
3937
override val identifier: IdentifierSpec,
4038
rawValuesMap: Map<IdentifierSpec, String?> = emptyMap(),
4139
countryCodes: Set<String> = emptySet(),
@@ -213,25 +211,3 @@ class CardBillingAddressElement(
213211
addressElement.onValidationStateChanged(isValidating)
214212
}
215213
}
216-
217-
/**
218-
* Billing address fields required in addition to the country, for a Checkout Session using
219-
* automatic tax with the billing address as the tax source. Most countries only need the
220-
* country. Source: https://docs.stripe.com/tax/customer-locations
221-
*
222-
* Billing only - shipping is out of scope, since it's always collected in full for delivery
223-
* regardless of tax, so there's no omittable mode there for tax to rescue.
224-
*/
225-
private val additionalAutomaticTaxFieldsByCountry: Map<String, Set<IdentifierSpec>> = mapOf(
226-
"CA" to setOf(IdentifierSpec.PostalCode),
227-
"GB" to setOf(IdentifierSpec.PostalCode),
228-
"IN" to setOf(IdentifierSpec.PostalCode),
229-
"PR" to setOf(IdentifierSpec.Line1, IdentifierSpec.City, IdentifierSpec.PostalCode),
230-
"US" to setOf(IdentifierSpec.Line1, IdentifierSpec.City, IdentifierSpec.State, IdentifierSpec.PostalCode),
231-
)
232-
233-
private fun automaticTaxRequiredFields(countryCode: String): Set<IdentifierSpec> {
234-
// Matches the raw, non-uppercased comparison the AVS check above uses - countryCode is
235-
// already an uppercase ISO code in practice (from CountryConfig).
236-
return additionalAutomaticTaxFieldsByCountry[countryCode].orEmpty()
237-
}

payments-ui-core/src/test/java/com/stripe/android/ui/core/elements/CardBillingAddressElementTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ internal class CardBillingAddressElementTest {
281281
private fun createCardBillingAddressElement(
282282
requiresBillingAddressForAutomaticTax: Boolean = false,
283283
collectionConfiguration: BillingDetailsCollectionConfiguration = BillingDetailsCollectionConfiguration(),
284-
): CardBillingAddressElement {
285-
return CardBillingAddressElement(
284+
): BillingAddressElement {
285+
return BillingAddressElement(
286286
identifier = IdentifierSpec.Generic("billing_element"),
287287
rawValuesMap = emptyMap(),
288288
countryCodes = emptySet(),
@@ -349,10 +349,10 @@ internal class CardBillingAddressElementTest {
349349

350350
private fun autocompleteTest(
351351
configuration: BillingDetailsCollectionConfiguration,
352-
block: (CardBillingAddressElement) -> Unit,
352+
block: (BillingAddressElement) -> Unit,
353353
) = runTest {
354354
block(
355-
CardBillingAddressElement(
355+
BillingAddressElement(
356356
identifier = IdentifierSpec.Generic("billing_element"),
357357
rawValuesMap = emptyMap(),
358358
countryCodes = emptySet(),

paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/CardDefinition.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import com.stripe.android.model.PaymentMethod
2727
import com.stripe.android.paymentsheet.PaymentSheet
2828
import com.stripe.android.paymentsheet.model.PaymentMethodIncentive
2929
import com.stripe.android.ui.core.BillingDetailsCollectionConfiguration
30-
import com.stripe.android.ui.core.elements.CardBillingAddressElement
30+
import com.stripe.android.ui.core.elements.BillingAddressElement
3131
import com.stripe.android.ui.core.elements.CardDetailsAction
3232
import com.stripe.android.ui.core.elements.CardDetailsSectionElement
3333
import com.stripe.android.ui.core.elements.CardScanAction
@@ -285,7 +285,7 @@ private fun cardBillingElements(
285285
controller = SameAsShippingController(it)
286286
)
287287
}
288-
val addressElement = CardBillingAddressElement(
288+
val addressElement = BillingAddressElement(
289289
IdentifierSpec.Generic("credit_billing"),
290290
countryCodes = allowedCountries,
291291
rawValuesMap = initialValues,

paymentsheet/src/main/java/com/stripe/android/paymentsheet/forms/FormViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import androidx.lifecycle.viewModelScope
77
import com.stripe.android.paymentsheet.forms.PlaceholderHelper.connectBillingDetailsFields
88
import com.stripe.android.paymentsheet.model.PaymentSelection
99
import com.stripe.android.paymentsheet.paymentdatacollection.FormArguments
10-
import com.stripe.android.ui.core.elements.CardBillingAddressElement
10+
import com.stripe.android.ui.core.elements.BillingAddressElement
1111
import com.stripe.android.uicore.elements.FormElement
1212
import com.stripe.android.uicore.elements.IdentifierSpec
1313
import com.stripe.android.uicore.elements.SectionElement
@@ -60,7 +60,7 @@ internal class FormViewModel(
6060

6161
private val cardBillingElement = elements.filterIsInstance<SectionElement>()
6262
.flatMap { it.fields }
63-
.filterIsInstance<CardBillingAddressElement>()
63+
.filterIsInstance<BillingAddressElement>()
6464
.firstOrNull()
6565

6666
private var externalHiddenIdentifiers = MutableStateFlow(emptySet<IdentifierSpec>())

paymentsheet/src/main/java/com/stripe/android/paymentsheet/ui/BillingDetailsForm.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import com.stripe.android.model.PaymentMethod
66
import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode
77
import com.stripe.android.ui.core.BillingDetailsCollectionConfiguration
88
import com.stripe.android.ui.core.R
9-
import com.stripe.android.ui.core.elements.CardBillingAddressElement
9+
import com.stripe.android.ui.core.elements.BillingAddressElement
1010
import com.stripe.android.uicore.elements.AutocompleteAddressInteractor
1111
import com.stripe.android.uicore.elements.IdentifierSpec
1212
import com.stripe.android.uicore.elements.NameConfig
@@ -38,7 +38,7 @@ internal class BillingDetailsForm(
3838
null
3939
}
4040

41-
private val cardBillingAddressElement: CardBillingAddressElement = CardBillingAddressElement(
41+
private val cardBillingAddressElement: BillingAddressElement = BillingAddressElement(
4242
identifier = IdentifierSpec.BillingAddress,
4343
sameAsShippingElement = null,
4444
shippingValuesMap = null,

paymentsheet/src/test/java/com/stripe/android/customersheet/CustomerSheetViewModelTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import com.stripe.android.testing.PaymentMethodFactory
5151
import com.stripe.android.testing.PaymentMethodFactory.update
5252
import com.stripe.android.testing.SetupIntentFactory
5353
import com.stripe.android.ui.core.cbc.CardBrandChoiceEligibility
54-
import com.stripe.android.ui.core.elements.CardBillingAddressElement
54+
import com.stripe.android.ui.core.elements.BillingAddressElement
5555
import com.stripe.android.ui.core.elements.CardDetailsSectionController
5656
import com.stripe.android.ui.core.elements.CardDetailsSectionElement
5757
import com.stripe.android.uicore.elements.FormElement
@@ -474,7 +474,7 @@ class CustomerSheetViewModelTest : CustomerSheetTestHelper {
474474
assertThat(formElements[0]).isInstanceOf<CardDetailsSectionElement>()
475475
assertThat(formElements[1]).isInstanceOf<SectionElement>()
476476
assertThat(formElements[1].asSectionElement().fields[0])
477-
.isInstanceOf<CardBillingAddressElement>()
477+
.isInstanceOf<BillingAddressElement>()
478478
}
479479
}
480480

paymentsheet/src/test/java/com/stripe/android/link/ui/updatecard/UpdateCardScreenViewModelTest.kt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.stripe.android.paymentsheet.PaymentSheet
2121
import com.stripe.android.paymentsheet.utils.ViewModelStoreTestRule
2222
import com.stripe.android.testing.CoroutineTestRule
2323
import com.stripe.android.testing.FakeLogger
24-
import com.stripe.android.ui.core.elements.CardBillingAddressElement
24+
import com.stripe.android.ui.core.elements.BillingAddressElement
2525
import com.stripe.android.uicore.elements.IdentifierSpec
2626
import com.stripe.android.uicore.elements.RowElement
2727
import com.stripe.android.uicore.navigation.NavigationManager
@@ -170,9 +170,9 @@ class UpdateCardScreenViewModelTest {
170170
val nonNullBillingElements = requireNotNull(billingElements)
171171

172172
assertThat(nonNullBillingElements).hasSize(1)
173-
assertThat(nonNullBillingElements[0]).isInstanceOf<CardBillingAddressElement>()
173+
assertThat(nonNullBillingElements[0]).isInstanceOf<BillingAddressElement>()
174174

175-
val cardBillingAddressElement = nonNullBillingElements[0] as CardBillingAddressElement
175+
val cardBillingAddressElement = nonNullBillingElements[0] as BillingAddressElement
176176

177177
val addressFields = cardBillingAddressElement.addressController.value.fieldsFlowable.value
178178

@@ -216,9 +216,9 @@ class UpdateCardScreenViewModelTest {
216216
val nonNullBillingElements = requireNotNull(billingElements)
217217

218218
assertThat(nonNullBillingElements).hasSize(1)
219-
assertThat(nonNullBillingElements[0]).isInstanceOf<CardBillingAddressElement>()
219+
assertThat(nonNullBillingElements[0]).isInstanceOf<BillingAddressElement>()
220220

221-
val cardBillingAddressElement = nonNullBillingElements[0] as CardBillingAddressElement
221+
val cardBillingAddressElement = nonNullBillingElements[0] as BillingAddressElement
222222

223223
val addressFields = cardBillingAddressElement.addressController.value.fieldsFlowable.value
224224

@@ -262,9 +262,9 @@ class UpdateCardScreenViewModelTest {
262262
val nonNullBillingElements = requireNotNull(billingElements)
263263

264264
assertThat(nonNullBillingElements).hasSize(1)
265-
assertThat(nonNullBillingElements[0]).isInstanceOf<CardBillingAddressElement>()
265+
assertThat(nonNullBillingElements[0]).isInstanceOf<BillingAddressElement>()
266266

267-
val cardBillingAddressElement = nonNullBillingElements[0] as CardBillingAddressElement
267+
val cardBillingAddressElement = nonNullBillingElements[0] as BillingAddressElement
268268

269269
val addressFields = cardBillingAddressElement.addressController.value.fieldsFlowable.value
270270

@@ -315,9 +315,9 @@ class UpdateCardScreenViewModelTest {
315315
val nonNullBillingElements = requireNotNull(billingElements)
316316

317317
assertThat(nonNullBillingElements).hasSize(1)
318-
assertThat(nonNullBillingElements[0]).isInstanceOf<CardBillingAddressElement>()
318+
assertThat(nonNullBillingElements[0]).isInstanceOf<BillingAddressElement>()
319319

320-
val cardBillingAddressElement = nonNullBillingElements[0] as CardBillingAddressElement
320+
val cardBillingAddressElement = nonNullBillingElements[0] as BillingAddressElement
321321

322322
assertThat(cardBillingAddressElement.countryElement.controller.displayItems)
323323
.hasSize(CountryUtils.supportedBillingCountries.size)
@@ -356,9 +356,9 @@ class UpdateCardScreenViewModelTest {
356356
val nonNullBillingElements = requireNotNull(billingElements)
357357

358358
assertThat(nonNullBillingElements).hasSize(1)
359-
assertThat(nonNullBillingElements[0]).isInstanceOf<CardBillingAddressElement>()
359+
assertThat(nonNullBillingElements[0]).isInstanceOf<BillingAddressElement>()
360360

361-
val cardBillingAddressElement = nonNullBillingElements[0] as CardBillingAddressElement
361+
val cardBillingAddressElement = nonNullBillingElements[0] as BillingAddressElement
362362

363363
// Billing details update flow should respect country filtering
364364
assertThat(cardBillingAddressElement.countryElement.controller.displayItems).containsExactly(
@@ -400,9 +400,9 @@ class UpdateCardScreenViewModelTest {
400400
val nonNullBillingElements = requireNotNull(billingElements)
401401

402402
assertThat(nonNullBillingElements).hasSize(1)
403-
assertThat(nonNullBillingElements[0]).isInstanceOf<CardBillingAddressElement>()
403+
assertThat(nonNullBillingElements[0]).isInstanceOf<BillingAddressElement>()
404404

405-
val cardBillingAddressElement = nonNullBillingElements[0] as CardBillingAddressElement
405+
val cardBillingAddressElement = nonNullBillingElements[0] as BillingAddressElement
406406

407407
// Regular edit flow should show all countries, ignoring filter
408408
assertThat(cardBillingAddressElement.countryElement.controller.displayItems)

paymentsheet/src/test/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/CardDefinitionTest.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import com.stripe.android.paymentsheet.repositories.CheckoutSessionResponse
3131
import com.stripe.android.paymentsheet.repositories.CheckoutSessionResponseFactory
3232
import com.stripe.android.paymentsheet.state.LinkState
3333
import com.stripe.android.ui.core.elements.AutomaticallyLaunchedCardScanFormDataHelper
34-
import com.stripe.android.ui.core.elements.CardBillingAddressElement
34+
import com.stripe.android.ui.core.elements.BillingAddressElement
3535
import com.stripe.android.ui.core.elements.CardDetailsAction
3636
import com.stripe.android.ui.core.elements.CardDetailsSectionController
3737
import com.stripe.android.ui.core.elements.CardDetailsSectionElement
@@ -94,7 +94,7 @@ class CardDefinitionTest {
9494
val contactElement = formElements[1] as SectionElement
9595
assertThat(contactElement.fields).hasSize(1)
9696

97-
val cardBillingElement = contactElement.fields[0] as CardBillingAddressElement
97+
val cardBillingElement = contactElement.fields[0] as BillingAddressElement
9898
val billingElements = cardBillingElement.addressController.value.fieldsFlowable.value
9999

100100
assertThat(billingElements.size).isEqualTo(7)
@@ -133,7 +133,7 @@ class CardDefinitionTest {
133133
val contactInformationElement = formElements[1] as SectionElement
134134
val contactInformationFields = contactInformationElement.fields
135135

136-
val cardBillingElement = contactInformationFields[0] as CardBillingAddressElement
136+
val cardBillingElement = contactInformationFields[0] as BillingAddressElement
137137
val billingElements = cardBillingElement.addressController.value.fieldsFlowable.value
138138

139139
assertThat(billingElements.size).isEqualTo(6)
@@ -416,7 +416,7 @@ class CardDefinitionTest {
416416
)
417417

418418
val cardBillingAddressElements = formElements.filterIsInstance<SectionElement>().map {
419-
it.fields.filterIsInstance<CardBillingAddressElement>().firstOrNull()
419+
it.fields.filterIsInstance<BillingAddressElement>().firstOrNull()
420420
}
421421

422422
assertThat(cardBillingAddressElements).hasSize(1)
@@ -497,9 +497,9 @@ class CardDefinitionTest {
497497
val sectionFields = sectionElement.fields
498498

499499
assertThat(sectionFields.size).isEqualTo(1)
500-
assertThat(sectionFields.firstOrNull()).isInstanceOf<CardBillingAddressElement>()
500+
assertThat(sectionFields.firstOrNull()).isInstanceOf<BillingAddressElement>()
501501

502-
val addressElement = sectionFields.first() as CardBillingAddressElement
502+
val addressElement = sectionFields.first() as BillingAddressElement
503503

504504
assertThat(addressElement.countryElement.controller.displayItems)
505505
.hasSize(CountryUtils.supportedBillingCountries.size)
@@ -526,9 +526,9 @@ class CardDefinitionTest {
526526
val sectionFields = sectionElement.fields
527527

528528
assertThat(sectionFields.size).isEqualTo(1)
529-
assertThat(sectionFields.firstOrNull()).isInstanceOf<CardBillingAddressElement>()
529+
assertThat(sectionFields.firstOrNull()).isInstanceOf<BillingAddressElement>()
530530

531-
val addressElement = sectionFields.first() as CardBillingAddressElement
531+
val addressElement = sectionFields.first() as BillingAddressElement
532532

533533
assertThat(addressElement.countryElement.controller.displayItems).containsExactly(
534534
"\uD83C\uDDFA\uD83C\uDDF8 United States",
@@ -797,7 +797,7 @@ class CardDefinitionTest {
797797

798798
private fun createAutomaticCardBillingAddressElement(
799799
checkoutSessionResponse: CheckoutSessionResponse?,
800-
): CardBillingAddressElement {
800+
): BillingAddressElement {
801801
val formElements = CardDefinition.formElements(
802802
metadata = PaymentMethodMetadataFactory.create(
803803
billingDetailsCollectionConfiguration = PaymentSheet.BillingDetailsCollectionConfiguration(
@@ -809,7 +809,7 @@ class CardDefinitionTest {
809809

810810
return formElements.filterIsInstance<SectionElement>()
811811
.flatMap { it.fields }
812-
.filterIsInstance<CardBillingAddressElement>()
812+
.filterIsInstance<BillingAddressElement>()
813813
.first()
814814
}
815815

@@ -820,7 +820,7 @@ class CardDefinitionTest {
820820
)
821821
}
822822

823-
private fun CardBillingAddressElement.shownIdentifierParamPaths(): List<String> {
823+
private fun BillingAddressElement.shownIdentifierParamPaths(): List<String> {
824824
return addressController.value.fieldsFlowable.value
825825
.filterOutHiddenIdentifiers(hiddenIdentifiers.value)
826826
.flatMap { field ->

paymentsheet/src/test/java/com/stripe/android/paymentsheet/ui/BillingDetailsFormTest.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.stripe.android.model.PaymentMethod
88
import com.stripe.android.model.PaymentMethodFixtures
99
import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode
1010
import com.stripe.android.testing.CoroutineTestRule
11-
import com.stripe.android.ui.core.elements.CardBillingAddressElement
11+
import com.stripe.android.ui.core.elements.BillingAddressElement
1212
import com.stripe.android.uicore.forms.FormFieldEntry
1313
import kotlinx.coroutines.test.UnconfinedTestDispatcher
1414
import kotlinx.coroutines.test.runTest
@@ -72,9 +72,9 @@ internal class BillingDetailsFormTest {
7272
allowedCountries = emptySet()
7373
) {
7474
assertThat(addressSectionElement.fields.size).isEqualTo(1)
75-
assertThat(addressSectionElement.fields.firstOrNull()).isInstanceOf<CardBillingAddressElement>()
75+
assertThat(addressSectionElement.fields.firstOrNull()).isInstanceOf<BillingAddressElement>()
7676

77-
val cardBillingAddressElement = addressSectionElement.fields[0] as CardBillingAddressElement
77+
val cardBillingAddressElement = addressSectionElement.fields[0] as BillingAddressElement
7878

7979
assertThat(cardBillingAddressElement.countryElement.controller.displayItems)
8080
.hasSize(CountryUtils.supportedBillingCountries.size)
@@ -85,9 +85,9 @@ internal class BillingDetailsFormTest {
8585
allowedCountries = setOf("US", "CA")
8686
) {
8787
assertThat(addressSectionElement.fields.size).isEqualTo(1)
88-
assertThat(addressSectionElement.fields.firstOrNull()).isInstanceOf<CardBillingAddressElement>()
88+
assertThat(addressSectionElement.fields.firstOrNull()).isInstanceOf<BillingAddressElement>()
8989

90-
val cardBillingAddressElement = addressSectionElement.fields[0] as CardBillingAddressElement
90+
val cardBillingAddressElement = addressSectionElement.fields[0] as BillingAddressElement
9191

9292
assertThat(cardBillingAddressElement.countryElement.controller.displayItems).containsExactly(
9393
"\uD83C\uDDFA\uD83C\uDDF8 United States",

0 commit comments

Comments
 (0)