Skip to content

Commit 64e9804

Browse files
cttsai-stripeclaude
andcommitted
Add hasSufficientBillingDetailsForAutomaticTax validator (MOBILESDK-4697)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Committed-By-Agent: claude
1 parent 4889e25 commit 64e9804

2 files changed

Lines changed: 102 additions & 0 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.stripe.android.common.validation
2+
3+
import com.stripe.android.model.PaymentMethod
4+
import com.stripe.android.ui.core.elements.automaticTaxRequiredFields
5+
import com.stripe.android.uicore.elements.IdentifierSpec
6+
7+
/**
8+
* Does this saved payment method's billing address have what Stripe Tax needs to compute
9+
* automatic tax for its country? See automaticTaxRequiredFields for the per-country field set.
10+
*/
11+
internal fun PaymentMethod.hasSufficientBillingDetailsForAutomaticTax(): Boolean {
12+
val address = billingDetails?.address ?: return false
13+
// Saved-PM billing details come from the API and are not guaranteed uppercase, unlike the
14+
// CountryConfig-sourced value the CardBillingAddressElement form passes. Uppercase before the
15+
// map lookup; do not "simplify" this away.
16+
val country = address.country?.uppercase()
17+
if (country.isNullOrBlank()) return false
18+
19+
return automaticTaxRequiredFields(country).all { field ->
20+
when (field) {
21+
IdentifierSpec.Line1 -> !address.line1.isNullOrBlank()
22+
IdentifierSpec.City -> !address.city.isNullOrBlank()
23+
IdentifierSpec.State -> !address.state.isNullOrBlank()
24+
IdentifierSpec.PostalCode -> !address.postalCode.isNullOrBlank()
25+
// additionalAutomaticTaxFieldsByCountry only emits the four specs above today. If that
26+
// map ever adds another (e.g. Line2), add its case here — else it silently over-filters.
27+
else -> false
28+
}
29+
}
30+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.stripe.android.common.validation
2+
3+
import com.google.common.truth.Truth.assertThat
4+
import com.stripe.android.model.Address
5+
import com.stripe.android.model.PaymentMethod
6+
import com.stripe.android.testing.PaymentMethodFactory
7+
import kotlin.test.Test
8+
9+
class BillingDetailsValidationTest {
10+
11+
private fun card(
12+
line1: String? = "123 Main St",
13+
city: String? = "San Francisco",
14+
state: String? = "CA",
15+
postalCode: String? = "94111",
16+
country: String? = "US",
17+
hasAddress: Boolean = true,
18+
): PaymentMethod = PaymentMethodFactory.card(id = "pm_test").copy(
19+
billingDetails = PaymentMethod.BillingDetails(
20+
address = if (hasAddress) {
21+
Address(
22+
line1 = line1,
23+
city = city,
24+
state = state,
25+
postalCode = postalCode,
26+
country = country,
27+
)
28+
} else {
29+
null
30+
},
31+
),
32+
)
33+
34+
@Test
35+
fun `null address is insufficient`() {
36+
assertThat(card(hasAddress = false).hasSufficientBillingDetailsForAutomaticTax()).isFalse()
37+
}
38+
39+
@Test
40+
fun `missing country is insufficient`() {
41+
assertThat(card(country = null).hasSufficientBillingDetailsForAutomaticTax()).isFalse()
42+
}
43+
44+
@Test
45+
fun `country with no additional tax requirement needs only the country`() {
46+
val pm = card(country = "FR", line1 = null, city = null, state = null, postalCode = null)
47+
assertThat(pm.hasSufficientBillingDetailsForAutomaticTax()).isTrue()
48+
}
49+
50+
@Test
51+
fun `CA GB IN require postal code only`() {
52+
assertThat(card(country = "CA", postalCode = null).hasSufficientBillingDetailsForAutomaticTax()).isFalse()
53+
assertThat(card(country = "CA", postalCode = "K1A0B1").hasSufficientBillingDetailsForAutomaticTax()).isTrue()
54+
assertThat(card(country = "GB", postalCode = null).hasSufficientBillingDetailsForAutomaticTax()).isFalse()
55+
assertThat(card(country = "IN", postalCode = null).hasSufficientBillingDetailsForAutomaticTax()).isFalse()
56+
}
57+
58+
@Test
59+
fun `PR requires line1, city, and postal code but not state`() {
60+
val missingCity = card(country = "PR", city = null)
61+
assertThat(missingCity.hasSufficientBillingDetailsForAutomaticTax()).isFalse()
62+
63+
val completeWithoutState = card(country = "PR", state = null)
64+
assertThat(completeWithoutState.hasSufficientBillingDetailsForAutomaticTax()).isTrue()
65+
}
66+
67+
@Test
68+
fun `US requires line1, city, state, and postal code`() {
69+
assertThat(card(country = "US", state = null).hasSufficientBillingDetailsForAutomaticTax()).isFalse()
70+
assertThat(card(country = "US").hasSufficientBillingDetailsForAutomaticTax()).isTrue()
71+
}
72+
}

0 commit comments

Comments
 (0)