Skip to content

Commit c379bd3

Browse files
cttsai-stripeclaude
andcommitted
Filter SPMs lacking automatic-tax-required billing fields on Checkout Session (MOBILESDK-4697)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Committed-By-Agent: claude
1 parent 418e559 commit c379bd3

3 files changed

Lines changed: 90 additions & 1 deletion

File tree

paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/CustomerState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ internal class CreateCustomerState @Inject constructor(
7070
cardFundingFilter = metadata.cardFundingFilter,
7171
remoteDefaultPaymentMethodId = state.defaultPaymentMethodId,
7272
localSavedSelection = savedSelection,
73+
requiresBillingAddressForAutomaticTax = metadata.requiresBillingAddressForAutomaticTax,
7374
)
7475
)
7576
)

paymentsheet/src/main/java/com/stripe/android/paymentsheet/state/PaymentMethodFilter.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.stripe.android.paymentsheet.state
22

33
import com.stripe.android.CardBrandFilter
44
import com.stripe.android.CardFundingFilter
5+
import com.stripe.android.common.validation.hasSufficientBillingDetailsForAutomaticTax
56
import com.stripe.android.common.validation.isSupportedWithBillingConfig
67
import com.stripe.android.lpmfoundations.paymentmethod.CustomerMetadata
78
import com.stripe.android.lpmfoundations.paymentmethod.IS_PAYMENT_METHOD_SET_AS_DEFAULT_ENABLED_DEFAULT_VALUE
@@ -26,6 +27,7 @@ internal interface PaymentMethodFilter {
2627
val cardBrandFilter: CardBrandFilter,
2728
val cardFundingFilter: CardFundingFilter,
2829
val localSavedSelection: Deferred<SavedSelection>,
30+
val requiresBillingAddressForAutomaticTax: Boolean,
2931
)
3032
}
3133

@@ -45,7 +47,11 @@ internal class DefaultPaymentMethodFilter @Inject constructor() : PaymentMethodF
4547
} ?: true
4648
params.cardBrandFilter.isAccepted(paymentMethod) &&
4749
fundingAccepted &&
48-
paymentMethod.isSupportedWithBillingConfig(params.billingDetailsCollectionConfiguration)
50+
paymentMethod.isSupportedWithBillingConfig(params.billingDetailsCollectionConfiguration) &&
51+
(
52+
!params.requiresBillingAddressForAutomaticTax ||
53+
paymentMethod.hasSufficientBillingDetailsForAutomaticTax()
54+
)
4955
}
5056
}
5157
}

paymentsheet/src/test/java/com/stripe/android/paymentsheet/state/DefaultPaymentMethodFilterTest.kt

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,94 @@ class DefaultPaymentMethodFilterTest {
190190
assertThat(observedElements).containsExactlyElementsIn(expectedElements).inOrder()
191191
}
192192

193+
private fun usCard(id: String, state: String?): PaymentMethod = PaymentMethodFactory.card(id = id).update(
194+
last4 = "1000",
195+
addCbcNetworks = false,
196+
brand = CardBrand.Visa,
197+
).copy(
198+
billingDetails = PaymentMethod.BillingDetails(
199+
address = Address(
200+
line1 = "123 Main St",
201+
city = "San Francisco",
202+
state = state,
203+
postalCode = "94111",
204+
country = "US",
205+
),
206+
),
207+
)
208+
209+
@Test
210+
fun `Drops SPM lacking automatic-tax-required fields when automatic tax requires a billing address`() = runTest {
211+
val complete = usCard(id = "pm_complete", state = "CA")
212+
val incomplete = usCard(id = "pm_incomplete", state = null)
213+
214+
val observed = filter(
215+
paymentMethods = listOf(complete, incomplete),
216+
requiresBillingAddressForAutomaticTax = true,
217+
)
218+
219+
assertThat(observed).containsExactly(complete)
220+
}
221+
222+
@Test
223+
fun `Does not filter by automatic-tax fields when automatic tax does not require a billing address`() = runTest {
224+
val complete = usCard(id = "pm_complete", state = "CA")
225+
val incomplete = usCard(id = "pm_incomplete", state = null)
226+
227+
val observed = filter(
228+
paymentMethods = listOf(complete, incomplete),
229+
requiresBillingAddressForAutomaticTax = false,
230+
)
231+
232+
assertThat(observed).containsExactly(complete, incomplete)
233+
}
234+
235+
@Test
236+
fun `Keeps a country-only-sufficient SPM when automatic tax requires a billing address`() = runTest {
237+
// FR has no additional automatic-tax fields, so country alone is sufficient. Proves the
238+
// clause does not over-drop valid cards.
239+
val frCard = PaymentMethodFactory.card(id = "pm_fr").update(
240+
last4 = "1000",
241+
addCbcNetworks = false,
242+
brand = CardBrand.Visa,
243+
).copy(
244+
billingDetails = PaymentMethod.BillingDetails(
245+
address = Address(country = "FR"),
246+
),
247+
)
248+
249+
val observed = filter(
250+
paymentMethods = listOf(frCard),
251+
requiresBillingAddressForAutomaticTax = true,
252+
)
253+
254+
assertThat(observed).containsExactly(frCard)
255+
}
256+
257+
@Test
258+
fun `Drops SPM with null billing address when automatic tax requires a billing address`() = runTest {
259+
val noAddress = PaymentMethodFactory.card(id = "pm_no_addr").update(
260+
last4 = "1000",
261+
addCbcNetworks = false,
262+
brand = CardBrand.Visa,
263+
).copy(billingDetails = PaymentMethod.BillingDetails(address = null))
264+
265+
val observed = filter(
266+
paymentMethods = listOf(noAddress),
267+
requiresBillingAddressForAutomaticTax = true,
268+
)
269+
270+
assertThat(observed).isEmpty()
271+
}
272+
193273
private suspend fun filter(
194274
paymentMethods: List<PaymentMethod>,
195275
billingDetailsCollectionConfiguration: PaymentSheet.BillingDetailsCollectionConfiguration =
196276
PaymentSheet.BillingDetailsCollectionConfiguration(),
197277
isPaymentMethodSetAsDefaultEnabled: Boolean = false,
198278
remoteDefaultPaymentMethodId: String? = null,
199279
localSavedSelection: SavedSelection = SavedSelection.None,
280+
requiresBillingAddressForAutomaticTax: Boolean = false,
200281
cardBrandFilter: PaymentSheetCardBrandFilter = PaymentSheetCardBrandFilter(
201282
cardBrandAcceptance = PaymentSheet.CardBrandAcceptance.all(),
202283
),
@@ -222,6 +303,7 @@ class DefaultPaymentMethodFilterTest {
222303
localSavedSelection = CompletableDeferred(localSavedSelection),
223304
cardBrandFilter = cardBrandFilter,
224305
cardFundingFilter = cardFundingFilter,
306+
requiresBillingAddressForAutomaticTax = requiresBillingAddressForAutomaticTax,
225307
)
226308
)
227309
}

0 commit comments

Comments
 (0)