Skip to content

Commit caa9349

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> Committed-By-Agent: codex Co-authored-by: codex <noreply@openai.com>
1 parent 0dc291c commit caa9349

7 files changed

Lines changed: 161 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: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.stripe.android.ui.core.R
88
import com.stripe.android.ui.core.elements.BillingAddressCollectionMode
99
import com.stripe.android.ui.core.elements.BillingAddressElement
1010
import com.stripe.android.ui.core.elements.additionalAutomaticTaxFieldsByCountry
11+
import com.stripe.android.uicore.elements.AddressElement
1112
import com.stripe.android.uicore.elements.AddressFieldsElement
1213
import com.stripe.android.uicore.elements.CountryConfig
1314
import com.stripe.android.uicore.elements.DropdownFieldController
@@ -35,10 +36,13 @@ internal object AutomaticTaxFormFinalizer {
3536
val addressFields = formElements.filterIsInstance<SectionElement>()
3637
.flatMap { it.fields }
3738
.filterIsInstance<AddressFieldsElement>()
38-
assert(addressFields.size <= 1) { "A payment method form must not contain multiple billing addresses." }
39+
check(addressFields.size <= 1) { "A payment method form must not contain multiple billing addresses." }
3940

4041
val addressField = addressFields.firstOrNull()
4142
if (addressField != null && addressField !is BillingAddressElement) {
43+
check(addressField is AddressElement) {
44+
"A specialized automatic-tax address must use AddressElement."
45+
}
4246
return formElements
4347
}
4448

@@ -65,7 +69,7 @@ internal object AutomaticTaxFormFinalizer {
6569

6670
return formElements + listOfNotNull(
6771
SectionElement.wrap(addressElement, R.string.stripe_billing_details.resolvableString),
68-
sameAsShippingElement,
72+
sameAsShippingElement.takeIf { existingSameAsShippingElement == null },
6973
)
7074
}
7175
}

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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.stripe.android.lpmfoundations.paymentmethod
33
import com.google.common.truth.Truth.assertThat
44
import com.google.common.truth.Truth.assertWithMessage
55
import com.stripe.android.core.utils.FeatureFlags
6+
import com.stripe.android.lpmfoundations.paymentmethod.definitions.BacsDebitDefinition
67
import com.stripe.android.lpmfoundations.paymentmethod.definitions.BlikDefinition
78
import com.stripe.android.lpmfoundations.paymentmethod.definitions.KlarnaDefinition
89
import com.stripe.android.lpmfoundations.paymentmethod.definitions.WeroDefinition
@@ -24,12 +25,14 @@ import com.stripe.android.uicore.elements.AddressElement
2425
import com.stripe.android.uicore.elements.AddressFieldsElement
2526
import com.stripe.android.uicore.elements.FormElement
2627
import com.stripe.android.uicore.elements.IdentifierSpec
28+
import com.stripe.android.uicore.elements.SameAsShippingController
2729
import com.stripe.android.uicore.elements.SameAsShippingElement
2830
import com.stripe.android.uicore.elements.SectionElement
2931
import kotlinx.coroutines.flow.first
3032
import kotlinx.coroutines.flow.flowOf
3133
import kotlinx.coroutines.flow.map
3234
import kotlinx.coroutines.test.runTest
35+
import org.junit.Assert.assertThrows
3336
import org.junit.Rule
3437
import org.junit.Test
3538
import org.junit.runner.RunWith
@@ -157,6 +160,52 @@ class AutomaticTaxBillingAddressTest {
157160
assertThat(formElements.sectionFields().filterIsInstance<AddressFieldsElement>()).isEmpty()
158161
}
159162

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

264+
@Test
265+
fun `specialized full address is preserved by automatic tax finalization`() {
266+
val baseMetadata = createMetadata(
267+
paymentMethodCode = PaymentMethod.Type.BacsDebit.code,
268+
checkoutSessionResponse = null,
269+
)
270+
val baseFormElements = requireNotNull(
271+
baseMetadata.formElementsForCode(
272+
code = PaymentMethod.Type.BacsDebit.code,
273+
uiDefinitionFactoryArgumentsFactory = TestUiDefinitionFactoryArgumentsFactory.create(),
274+
)
275+
)
276+
val specializedAddress = baseFormElements.sectionFields().filterIsInstance<AddressElement>().single()
277+
val taxMetadata = createMetadata(
278+
paymentMethodCode = PaymentMethod.Type.BacsDebit.code,
279+
checkoutSessionResponse = automaticTaxCheckoutSessionResponse,
280+
)
281+
282+
val finalizedFormElements = AutomaticTaxFormFinalizer.finalize(
283+
formElements = baseFormElements,
284+
paymentMethodCode = PaymentMethod.Type.BacsDebit.code,
285+
arguments = TestUiDefinitionFactoryArgumentsFactory.create().create(
286+
metadata = taxMetadata,
287+
requiresMandate = BacsDebitDefinition.requiresMandate(taxMetadata),
288+
),
289+
)
290+
291+
assertThat(finalizedFormElements.sectionFields().filterIsInstance<AddressElement>())
292+
.containsExactly(specializedAddress)
293+
}
294+
215295
@Test
216296
fun `Klarna country section is widened without duplication`() {
217297
val formElements = KlarnaDefinition.formElements(

0 commit comments

Comments
 (0)