Skip to content

Commit ace5fd6

Browse files
cttsai-stripecodex
andcommitted
Centralize automatic tax form policy
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> 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> Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com>
1 parent 0dc291c commit ace5fd6

7 files changed

Lines changed: 125 additions & 88 deletions

File tree

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ sealed interface BillingAddressCollectionMode {
4646
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
4747
fun cardBillingAddressCollectionMode(
4848
addressCollectionMode: BillingDetailsCollectionConfiguration.AddressCollectionMode,
49-
requiresBillingAddressForAutomaticTax: Boolean,
5049
): BillingAddressCollectionMode {
5150
return when (addressCollectionMode) {
5251
BillingDetailsCollectionConfiguration.AddressCollectionMode.Never -> BillingAddressCollectionMode.Never
@@ -56,11 +55,6 @@ fun cardBillingAddressCollectionMode(
5655
listOf("US", "GB", "CA").forEach { countryCode ->
5756
put(countryCode, setOf(IdentifierSpec.PostalCode))
5857
}
59-
if (requiresBillingAddressForAutomaticTax) {
60-
additionalAutomaticTaxFieldsByCountry.forEach { (countryCode, fields) ->
61-
put(countryCode, get(countryCode).orEmpty() + fields)
62-
}
63-
}
6458
}
6559
BillingAddressCollectionMode.Country(additionalFieldsByCountry)
6660
}

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

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -75,76 +75,6 @@ internal class CardBillingAddressElementTest {
7575
}
7676
}
7777

78-
@Test
79-
fun `Verify that automatic tax fields are unioned with AVS defaults for IN`() = runTest {
80-
val element = createCardBillingAddressElement(requiresBillingAddressForAutomaticTax = true)
81-
82-
element.hiddenIdentifiers.test {
83-
// IN has no AVS default fields, but requires a postal code for automatic tax.
84-
dropdownFieldController.onRawValueChange("IN")
85-
expectMostRecentItem().verifyFieldsShown(IdentifierSpec.PostalCode)
86-
}
87-
}
88-
89-
@Test
90-
fun `Verify that automatic tax fields for PR do not require state`() = runTest {
91-
val element = createCardBillingAddressElement(requiresBillingAddressForAutomaticTax = true)
92-
93-
element.hiddenIdentifiers.test {
94-
dropdownFieldController.onRawValueChange("PR")
95-
expectMostRecentItem().verifyFieldsShown(
96-
IdentifierSpec.Line1,
97-
IdentifierSpec.City,
98-
IdentifierSpec.PostalCode,
99-
)
100-
}
101-
}
102-
103-
@Test
104-
fun `Verify that automatic tax fields are shown for US`() = runTest {
105-
val element = createCardBillingAddressElement(requiresBillingAddressForAutomaticTax = true)
106-
107-
element.hiddenIdentifiers.test {
108-
dropdownFieldController.onRawValueChange("US")
109-
expectMostRecentItem().verifyFieldsShown(
110-
IdentifierSpec.Line1,
111-
IdentifierSpec.City,
112-
IdentifierSpec.State,
113-
IdentifierSpec.PostalCode,
114-
)
115-
}
116-
}
117-
118-
@Test
119-
fun `Verify that automatic tax fields have no effect when address collection is Never`() = runTest {
120-
val element = createCardBillingAddressElement(
121-
requiresBillingAddressForAutomaticTax = true,
122-
collectionConfiguration = BillingDetailsCollectionConfiguration(
123-
address = BillingDetailsCollectionConfiguration.AddressCollectionMode.Never,
124-
),
125-
)
126-
127-
element.hiddenIdentifiers.test {
128-
dropdownFieldController.onRawValueChange("US")
129-
expectMostRecentItem().verifyFieldsShown()
130-
}
131-
}
132-
133-
@Test
134-
fun `Verify that automatic tax fields have no effect when address collection is Full`() = runTest {
135-
val element = createCardBillingAddressElement(
136-
requiresBillingAddressForAutomaticTax = true,
137-
collectionConfiguration = BillingDetailsCollectionConfiguration(
138-
address = BillingDetailsCollectionConfiguration.AddressCollectionMode.Full,
139-
),
140-
)
141-
142-
element.hiddenIdentifiers.test {
143-
dropdownFieldController.onRawValueChange("US")
144-
assertThat(expectMostRecentItem()).isEmpty()
145-
}
146-
}
147-
14878
@Test
14979
fun `Verify that AutocompleteAddressElement is used when billing details collection is Full`() =
15080
autocompleteTest(
@@ -291,14 +221,12 @@ internal class CardBillingAddressElementTest {
291221
}
292222

293223
private fun createCardBillingAddressElement(
294-
requiresBillingAddressForAutomaticTax: Boolean = false,
295224
collectionConfiguration: BillingDetailsCollectionConfiguration = BillingDetailsCollectionConfiguration(),
296225
): BillingAddressElement {
297226
return createBillingAddressElement(
298227
collectionConfiguration = collectionConfiguration,
299228
addressCollectionMode = cardBillingAddressCollectionMode(
300229
addressCollectionMode = collectionConfiguration.address,
301-
requiresBillingAddressForAutomaticTax = requiresBillingAddressForAutomaticTax,
302230
),
303231
)
304232
}
@@ -405,7 +333,6 @@ internal class CardBillingAddressElementTest {
405333
shippingValuesMap = null,
406334
addressCollectionMode = cardBillingAddressCollectionMode(
407335
addressCollectionMode = configuration.address,
408-
requiresBillingAddressForAutomaticTax = false,
409336
),
410337
collectionConfiguration = configuration,
411338
)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal object AutomaticTaxFormFinalizer {
3535
val addressFields = formElements.filterIsInstance<SectionElement>()
3636
.flatMap { it.fields }
3737
.filterIsInstance<AddressFieldsElement>()
38-
assert(addressFields.size <= 1) { "A payment method form must not contain multiple billing addresses." }
38+
check(addressFields.size <= 1) { "A payment method form must not contain multiple billing addresses." }
3939

4040
val addressField = addressFields.firstOrNull()
4141
if (addressField != null && addressField !is BillingAddressElement) {
@@ -65,7 +65,7 @@ internal object AutomaticTaxFormFinalizer {
6565

6666
return formElements + listOfNotNull(
6767
SectionElement.wrap(addressElement, R.string.stripe_billing_details.resolvableString),
68-
sameAsShippingElement,
68+
sameAsShippingElement.takeIf { existingSameAsShippingElement == null },
6969
)
7070
}
7171
}

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,6 @@ private object CardUiDefinitionFactory : UiDefinitionFactory.Custom {
243243
arguments.autocompleteAddressInteractorFactory,
244244
arguments.initialValues,
245245
arguments.shippingValues,
246-
arguments.requiresBillingAddressForAutomaticTax,
247246
)
248247
)
249248
}
@@ -276,7 +275,6 @@ private fun cardBillingElements(
276275
autocompleteAddressInteractorFactory: AutocompleteAddressInteractor.Factory?,
277276
initialValues: Map<IdentifierSpec, String?>,
278277
shippingValues: Map<IdentifierSpec, String?>?,
279-
requiresBillingAddressForAutomaticTax: Boolean,
280278
): List<FormElement> {
281279
val sameAsShippingElement =
282280
shippingValues?.get(IdentifierSpec.SameAsShipping)
@@ -296,7 +294,6 @@ private fun cardBillingElements(
296294
shippingValuesMap = shippingValues,
297295
addressCollectionMode = cardBillingAddressCollectionMode(
298296
addressCollectionMode = collectionConfiguration.address,
299-
requiresBillingAddressForAutomaticTax = requiresBillingAddressForAutomaticTax,
300297
),
301298
collectionConfiguration = collectionConfiguration,
302299
autocompleteAddressInteractorFactory = autocompleteAddressInteractorFactory,

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ internal class BillingDetailsForm(
6363
AddressCollectionMode.Never -> BillingDetailsCollectionConfiguration.AddressCollectionMode.Never
6464
AddressCollectionMode.Full -> BillingDetailsCollectionConfiguration.AddressCollectionMode.Full
6565
},
66-
requiresBillingAddressForAutomaticTax = false,
6766
),
6867
rawValuesMap = rawAddressValues(billingDetails),
6968
autocompleteAddressInteractorFactory = autocompleteAddressInteractorFactory,

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ import com.stripe.android.uicore.elements.AddressElement
2424
import com.stripe.android.uicore.elements.AddressFieldsElement
2525
import com.stripe.android.uicore.elements.FormElement
2626
import com.stripe.android.uicore.elements.IdentifierSpec
27+
import com.stripe.android.uicore.elements.SameAsShippingController
2728
import com.stripe.android.uicore.elements.SameAsShippingElement
2829
import com.stripe.android.uicore.elements.SectionElement
2930
import kotlinx.coroutines.flow.first
3031
import kotlinx.coroutines.flow.flowOf
3132
import kotlinx.coroutines.flow.map
3233
import kotlinx.coroutines.test.runTest
34+
import org.junit.Assert.assertThrows
3335
import org.junit.Rule
3436
import org.junit.Test
3537
import org.junit.runner.RunWith
@@ -157,6 +159,52 @@ class AutomaticTaxBillingAddressTest {
157159
assertThat(formElements.sectionFields().filterIsInstance<AddressFieldsElement>()).isEmpty()
158160
}
159161

162+
@Test
163+
fun `appended tax address retains an existing same as shipping element once`() {
164+
val metadata = createMetadata(
165+
paymentMethodCode = PaymentMethod.Type.Blik.code,
166+
checkoutSessionResponse = automaticTaxCheckoutSessionResponse,
167+
)
168+
val sameAsShippingElement = SameAsShippingElement(
169+
identifier = IdentifierSpec.SameAsShipping,
170+
controller = SameAsShippingController(true),
171+
)
172+
173+
val formElements = AutomaticTaxFormFinalizer.finalize(
174+
formElements = listOf(sameAsShippingElement),
175+
paymentMethodCode = PaymentMethod.Type.Blik.code,
176+
arguments = TestUiDefinitionFactoryArgumentsFactory.create().create(
177+
metadata = metadata,
178+
requiresMandate = false,
179+
),
180+
)
181+
182+
assertThat(formElements.filterIsInstance<SameAsShippingElement>()).containsExactly(sameAsShippingElement)
183+
}
184+
185+
@Test
186+
fun `multiple billing addresses cause automatic tax finalization to fail`() {
187+
val metadata = createMetadata(
188+
paymentMethodCode = PaymentMethod.Type.Blik.code,
189+
checkoutSessionResponse = automaticTaxCheckoutSessionResponse,
190+
)
191+
val formElements = BlikDefinition.formElements(metadata)
192+
193+
val exception = assertThrows(IllegalStateException::class.java) {
194+
AutomaticTaxFormFinalizer.finalize(
195+
formElements = formElements + formElements,
196+
paymentMethodCode = PaymentMethod.Type.Blik.code,
197+
arguments = TestUiDefinitionFactoryArgumentsFactory.create().create(
198+
metadata = metadata,
199+
requiresMandate = false,
200+
),
201+
)
202+
}
203+
204+
assertThat(exception).hasMessageThat()
205+
.contains("A payment method form must not contain multiple billing addresses.")
206+
}
207+
160208
@Test
161209
fun `existing country section is widened without duplication`() {
162210
val formElements = WeroDefinition.formElements(

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

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.stripe.android.lpmfoundations.paymentmethod.definitions
22

33
import androidx.lifecycle.SavedStateHandle
4+
import app.cash.turbine.test
45
import com.google.common.truth.Truth.assertThat
56
import com.stripe.android.common.nfcscan.IsNfcScanningAvailable
67
import com.stripe.android.common.nfcscan.NfcScanningAction
@@ -41,12 +42,14 @@ import com.stripe.android.ui.core.elements.SaveForFutureUseElement
4142
import com.stripe.android.ui.core.elements.SetAsDefaultPaymentMethodElement
4243
import com.stripe.android.uicore.elements.AutocompleteAddressElement
4344
import com.stripe.android.uicore.elements.FormElement
45+
import com.stripe.android.uicore.elements.IdentifierSpec
4446
import com.stripe.android.uicore.elements.RowElement
4547
import com.stripe.android.uicore.elements.SameAsShippingElement
4648
import com.stripe.android.uicore.elements.SectionElement
4749
import com.stripe.android.uicore.elements.filterOutHiddenIdentifiers
4850
import com.stripe.android.utils.FakeIsNfcScanningAvailable
4951
import com.stripe.android.utils.FakeLinkConfigurationCoordinator
52+
import kotlinx.coroutines.test.runTest
5053
import org.junit.Test
5154
import org.junit.runner.RunWith
5255
import org.robolectric.RobolectricTestRunner
@@ -437,6 +440,44 @@ class CardDefinitionTest {
437440
)
438441
}
439442

443+
@Test
444+
fun `createFormElements shows only postal code for IN when automatic tax billing address is required`() = runTest {
445+
val cardBillingElement = createAutomaticCardBillingAddressElement(
446+
checkoutSessionResponse = automaticTaxCheckoutSessionResponse(),
447+
)
448+
449+
cardBillingElement.hiddenIdentifiers.test {
450+
cardBillingElement.countryElement.controller.onRawValueChange("IN")
451+
452+
val hiddenIdentifiers = expectMostRecentItem()
453+
assertThat(hiddenIdentifiers).doesNotContain(IdentifierSpec.PostalCode)
454+
assertThat(hiddenIdentifiers).containsAtLeast(
455+
IdentifierSpec.Line1,
456+
IdentifierSpec.City,
457+
IdentifierSpec.State,
458+
)
459+
}
460+
}
461+
462+
@Test
463+
fun `createFormElements shows PR tax fields without state`() = runTest {
464+
val cardBillingElement = createAutomaticCardBillingAddressElement(
465+
checkoutSessionResponse = automaticTaxCheckoutSessionResponse(),
466+
)
467+
468+
cardBillingElement.hiddenIdentifiers.test {
469+
cardBillingElement.countryElement.controller.onRawValueChange("PR")
470+
471+
val hiddenIdentifiers = expectMostRecentItem()
472+
assertThat(hiddenIdentifiers).contains(IdentifierSpec.State)
473+
assertThat(hiddenIdentifiers).containsNoneOf(
474+
IdentifierSpec.Line1,
475+
IdentifierSpec.City,
476+
IdentifierSpec.PostalCode,
477+
)
478+
}
479+
}
480+
440481
@Test
441482
fun `createFormElements shows line1, city, state, and postal code for US when automatic tax billing is required`() {
442483
val cardBillingElement = createAutomaticCardBillingAddressElement(
@@ -464,16 +505,47 @@ class CardDefinitionTest {
464505
}
465506

466507
@Test
467-
fun `createFormElements does not union tax fields when requiresBillingAddressForAutomaticTax is false`() {
508+
fun `createFormElements does not show tax fields when automatic tax is disabled`() = runTest {
468509
val cardBillingElement = createAutomaticCardBillingAddressElement(
469510
checkoutSessionResponse = CheckoutSessionResponseFactory.create(
470511
automaticTaxEnabled = false,
471512
taxAddressSource = CheckoutSessionResponse.TaxAddressSource.BILLING,
472513
),
473514
)
474-
cardBillingElement.countryElement.controller.onRawValueChange("FR")
475515

476-
assertThat(cardBillingElement.shownIdentifierParamPaths()).containsExactly("billing_details[address][country]")
516+
cardBillingElement.hiddenIdentifiers.test {
517+
cardBillingElement.countryElement.controller.onRawValueChange("US")
518+
519+
val hiddenIdentifiers = expectMostRecentItem()
520+
assertThat(hiddenIdentifiers).doesNotContain(IdentifierSpec.PostalCode)
521+
assertThat(hiddenIdentifiers).containsAtLeast(
522+
IdentifierSpec.Line1,
523+
IdentifierSpec.City,
524+
IdentifierSpec.State,
525+
)
526+
}
527+
}
528+
529+
@Test
530+
fun `createFormElements does not show tax fields when tax uses shipping address`() = runTest {
531+
val cardBillingElement = createAutomaticCardBillingAddressElement(
532+
checkoutSessionResponse = CheckoutSessionResponseFactory.create(
533+
automaticTaxEnabled = true,
534+
taxAddressSource = CheckoutSessionResponse.TaxAddressSource.SHIPPING,
535+
),
536+
)
537+
538+
cardBillingElement.hiddenIdentifiers.test {
539+
cardBillingElement.countryElement.controller.onRawValueChange("US")
540+
541+
val hiddenIdentifiers = expectMostRecentItem()
542+
assertThat(hiddenIdentifiers).doesNotContain(IdentifierSpec.PostalCode)
543+
assertThat(hiddenIdentifiers).containsAtLeast(
544+
IdentifierSpec.Line1,
545+
IdentifierSpec.City,
546+
IdentifierSpec.State,
547+
)
548+
}
477549
}
478550

479551
@Test

0 commit comments

Comments
 (0)