Skip to content

Commit a35942b

Browse files
cttsai-stripecodex
andcommitted
Use shared billing address for LPM country collection (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 bca787f commit a35942b

12 files changed

Lines changed: 294 additions & 112 deletions

File tree

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

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ package com.stripe.android.ui.core.elements
22

33
import androidx.annotation.RestrictTo
44
import com.stripe.android.core.model.CountryUtils
5+
import com.stripe.android.core.strings.resolvableString
6+
import com.stripe.android.ui.core.BillingDetailsCollectionConfiguration
7+
import com.stripe.android.ui.core.R
8+
import com.stripe.android.uicore.elements.AutocompleteAddressInteractor
59
import com.stripe.android.uicore.elements.CountryConfig
6-
import com.stripe.android.uicore.elements.CountryElement
710
import com.stripe.android.uicore.elements.DropdownFieldController
11+
import com.stripe.android.uicore.elements.FormElement
812
import com.stripe.android.uicore.elements.IdentifierSpec
13+
import com.stripe.android.uicore.elements.SameAsShippingController
14+
import com.stripe.android.uicore.elements.SameAsShippingElement
15+
import com.stripe.android.uicore.elements.SectionElement
916
import kotlinx.parcelize.Parcelize
1017
import kotlinx.serialization.SerialName
1118
import kotlinx.serialization.Serializable
@@ -26,13 +33,70 @@ data class CountrySpec(
2633
) : FormItemSpec() {
2734
fun transform(
2835
initialValues: Map<IdentifierSpec, String?>
29-
) = createSectionElement(
30-
CountryElement(
31-
this.apiPath,
32-
DropdownFieldController(
33-
CountryConfig(this.allowedCountryCodes),
34-
initialValue = initialValues[this.apiPath]
35-
)
36+
): SectionElement {
37+
return transform(
38+
initialValues = initialValues,
39+
shippingValues = null,
40+
autocompleteAddressInteractorFactory = null,
41+
addressCollectionMode = BillingAddressCollectionMode.Country(emptyMap()),
42+
).single() as SectionElement
43+
}
44+
45+
fun transform(
46+
initialValues: Map<IdentifierSpec, String?>,
47+
shippingValues: Map<IdentifierSpec, String?>?,
48+
autocompleteAddressInteractorFactory: AutocompleteAddressInteractor.Factory?,
49+
addressCollectionMode: BillingAddressCollectionMode,
50+
): List<FormElement> {
51+
val sameAsShippingElement = shippingValues
52+
?.get(IdentifierSpec.SameAsShipping)
53+
?.toBooleanStrictOrNull()
54+
?.let { isSameAsShipping ->
55+
SameAsShippingElement(
56+
identifier = IdentifierSpec.SameAsShipping,
57+
controller = SameAsShippingController(isSameAsShipping),
58+
)
59+
}
60+
?.takeIf { addressCollectionMode == BillingAddressCollectionMode.Full }
61+
val addressIdentifier = if (addressCollectionMode == BillingAddressCollectionMode.Full) {
62+
IdentifierSpec.BillingAddress
63+
} else {
64+
apiPath
65+
}
66+
val addressElement = BillingAddressElement(
67+
identifier = addressIdentifier,
68+
rawValuesMap = initialValues,
69+
countryCodes = allowedCountryCodes,
70+
countryDropdownFieldController = DropdownFieldController(
71+
CountryConfig(allowedCountryCodes),
72+
initialValue = initialValues[apiPath],
73+
),
74+
countryElementIdentifier = apiPath,
75+
autocompleteAddressInteractorFactory = autocompleteAddressInteractorFactory,
76+
sameAsShippingElement = sameAsShippingElement,
77+
shippingValuesMap = shippingValues,
78+
addressCollectionMode = addressCollectionMode,
79+
collectionConfiguration = BillingDetailsCollectionConfiguration(
80+
address = if (addressCollectionMode == BillingAddressCollectionMode.Full) {
81+
BillingDetailsCollectionConfiguration.AddressCollectionMode.Full
82+
} else {
83+
BillingDetailsCollectionConfiguration.AddressCollectionMode.Automatic
84+
},
85+
allowedCountries = allowedCountryCodes,
86+
),
87+
)
88+
val label = if (addressCollectionMode == BillingAddressCollectionMode.Full) {
89+
R.string.stripe_billing_details.resolvableString
90+
} else {
91+
null
92+
}
93+
94+
return listOfNotNull(
95+
createSectionElement(
96+
sectionFieldElement = addressElement,
97+
label = label,
98+
),
99+
sameAsShippingElement,
36100
)
37-
)
101+
}
38102
}

paymentsheet/src/main/java/com/stripe/android/lpmfoundations/luxe/TransformSpecToElements.kt

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

33
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
44
import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory
5+
import com.stripe.android.lpmfoundations.paymentmethod.transformToFormElements
56
import com.stripe.android.paymentsheet.PaymentSheet
67
import com.stripe.android.paymentsheet.forms.PlaceholderHelper.specsForConfiguration
78
import com.stripe.android.paymentsheet.model.currency
@@ -76,7 +77,10 @@ internal class TransformSpecToElements(
7677
is IbanSpec -> listOf(spec.transform(arguments.initialValues))
7778
is KlarnaHeaderStaticTextSpec -> listOf(spec.transform())
7879
is DropdownSpec -> listOf(spec.transform(arguments.initialValues))
79-
is CountrySpec -> listOf(spec.transform(arguments.initialValues))
80+
is CountrySpec -> spec.transformToFormElements(
81+
arguments = arguments,
82+
initialCountry = arguments.initialValues[spec.apiPath],
83+
)
8084
is AddressSpec -> spec.transform(
8185
arguments.initialValues,
8286
arguments.shippingValues,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.stripe.android.lpmfoundations.paymentmethod
2+
3+
import com.stripe.android.paymentsheet.PaymentSheet
4+
import com.stripe.android.ui.core.elements.BillingAddressCollectionMode
5+
import com.stripe.android.ui.core.elements.CountrySpec
6+
import com.stripe.android.uicore.elements.FormElement
7+
8+
internal fun CountrySpec.transformToFormElements(
9+
arguments: UiDefinitionFactory.Arguments,
10+
initialCountry: String?,
11+
): List<FormElement> {
12+
val initialValues = arguments.initialValues.toMutableMap().apply {
13+
initialCountry?.let { put(apiPath, it) }
14+
}
15+
val addressCollectionMode = when (arguments.billingDetailsCollectionConfiguration.address) {
16+
PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Full -> {
17+
BillingAddressCollectionMode.Full
18+
}
19+
PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Automatic,
20+
PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Never -> {
21+
BillingAddressCollectionMode.Country(emptyMap())
22+
}
23+
}
24+
25+
return transform(
26+
initialValues = initialValues,
27+
shippingValues = arguments.shippingValues,
28+
autocompleteAddressInteractorFactory = arguments.autocompleteAddressInteractorFactory,
29+
addressCollectionMode = addressCollectionMode,
30+
)
31+
}

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

Lines changed: 15 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,17 @@ import com.stripe.android.lpmfoundations.paymentmethod.AddPaymentMethodRequireme
99
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodDefinition
1010
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
1111
import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory
12+
import com.stripe.android.lpmfoundations.paymentmethod.transformToFormElements
1213
import com.stripe.android.model.PaymentMethod
1314
import com.stripe.android.model.SetupIntent
1415
import com.stripe.android.paymentsheet.PaymentSheet
1516
import com.stripe.android.ui.core.R
16-
import com.stripe.android.ui.core.elements.AddressSpec
17+
import com.stripe.android.ui.core.elements.CountrySpec
1718
import com.stripe.android.ui.core.elements.MandateTextElement
1819
import com.stripe.android.ui.core.elements.PaymentMethodMessageHeaderElement
1920
import com.stripe.android.ui.core.elements.StaticTextElement
20-
import com.stripe.android.uicore.elements.CountryConfig
21-
import com.stripe.android.uicore.elements.CountryElement
22-
import com.stripe.android.uicore.elements.DropdownFieldController
2321
import com.stripe.android.uicore.elements.FormElement
2422
import com.stripe.android.uicore.elements.IdentifierSpec
25-
import com.stripe.android.uicore.elements.SectionElement
2623

2724
internal object KlarnaDefinition : PaymentMethodDefinition {
2825
override val type: PaymentMethod.Type = PaymentMethod.Type.Klarna
@@ -67,36 +64,13 @@ private object KlarnaUiDefinitionFactory : UiDefinitionFactory.Simple() {
6764
.overrideContactInformationPosition(ContactInformationCollectionMode.Email)
6865
.overrideContactInformationPosition(ContactInformationCollectionMode.Phone)
6966
.ignoreBillingAddressRequirements()
70-
.element(
71-
formElement = SectionElement.wrap(
72-
sectionFieldElement = CountryElement(
73-
identifier = IdentifierSpec.Country,
74-
controller = DropdownFieldController(
75-
config = CountryConfig(
76-
onlyShowCountryCodes =
77-
metadata.billingDetailsCollectionConfiguration.allowedBillingCountries,
78-
),
79-
initialValue = arguments.initialValues[IdentifierSpec.Country],
80-
)
81-
)
82-
)
83-
)
8467
.apply {
85-
if (
86-
metadata.billingDetailsCollectionConfiguration.address ==
87-
PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Full
88-
) {
89-
AddressSpec(
90-
allowedCountryCodes = arguments.billingDetailsCollectionConfiguration.allowedBillingCountries,
91-
hideCountry = true,
92-
).transform(
93-
initialValues = arguments.initialValues,
94-
shippingValues = arguments.shippingValues,
95-
autocompleteAddressInteractorFactory = arguments.autocompleteAddressInteractorFactory,
96-
).forEach {
97-
element(it)
98-
}
99-
}
68+
CountrySpec(
69+
allowedCountryCodes = metadata.billingDetailsCollectionConfiguration.allowedBillingCountries,
70+
).transformToFormElements(
71+
arguments = arguments,
72+
initialCountry = arguments.initialValues[IdentifierSpec.Country],
73+
).forEach { element(it) }
10074

10175
if (KlarnaDefinition.requiresMandate(metadata)) {
10276
builder.footer(
@@ -156,12 +130,14 @@ private object KlarnaRemovedFormUiDefinitionFactory : UiDefinitionFactory.Simple
156130
stringResId = R.string.stripe_klarna_buy_now_pay_later
157131
)
158132
)
159-
.element(
160-
getKlarnaCountryElement(
133+
.apply {
134+
CountrySpec(
161135
allowedCountryCodes = arguments.billingDetailsCollectionConfiguration.allowedBillingCountries,
162-
initialValue = metadata.stripeIntent.countryCode
163-
)
164-
)
136+
).transformToFormElements(
137+
arguments = arguments,
138+
initialCountry = metadata.stripeIntent.countryCode,
139+
).forEach { element(it) }
140+
}
165141
}
166142

167143
if (KlarnaDefinition.requiresMandate(metadata)) {
@@ -173,21 +149,4 @@ private object KlarnaRemovedFormUiDefinitionFactory : UiDefinitionFactory.Simple
173149
)
174150
}
175151
}
176-
177-
private fun getKlarnaCountryElement(
178-
allowedCountryCodes: Set<String>,
179-
initialValue: String?
180-
): FormElement {
181-
val identifier = IdentifierSpec.Generic("billing_details[address][country]")
182-
183-
return SectionElement.wrap(
184-
CountryElement(
185-
identifier = identifier,
186-
controller = DropdownFieldController(
187-
CountryConfig(allowedCountryCodes),
188-
initialValue = initialValue
189-
)
190-
)
191-
)
192-
}
193152
}

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

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ import com.stripe.android.lpmfoundations.paymentmethod.AddPaymentMethodRequireme
77
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodDefinition
88
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata
99
import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory
10+
import com.stripe.android.lpmfoundations.paymentmethod.transformToFormElements
1011
import com.stripe.android.model.PaymentMethod
1112
import com.stripe.android.ui.core.R
12-
import com.stripe.android.uicore.elements.CountryConfig
13-
import com.stripe.android.uicore.elements.CountryElement
14-
import com.stripe.android.uicore.elements.DropdownFieldController
13+
import com.stripe.android.ui.core.elements.CountrySpec
1514
import com.stripe.android.uicore.elements.IdentifierSpec
16-
import com.stripe.android.uicore.elements.SectionElement
1715

1816
internal object WeroDefinition : PaymentMethodDefinition {
1917
override val type: PaymentMethod.Type = PaymentMethod.Type.Wero
@@ -47,19 +45,15 @@ private object WeroUiDefinitionFactory : UiDefinitionFactory.Simple() {
4745
builder: FormElementsBuilder,
4846
) {
4947
builder
50-
.element(
51-
formElement = SectionElement.wrap(
52-
sectionFieldElement = CountryElement(
53-
identifier = IdentifierSpec.Country,
54-
controller = DropdownFieldController(
55-
config = CountryConfig(
56-
onlyShowCountryCodes = setOf("DE", "BE", "FR"),
57-
),
58-
initialValue = arguments.initialValues[IdentifierSpec.Country],
59-
)
60-
)
61-
)
62-
)
48+
.ignoreBillingAddressRequirements()
49+
.apply {
50+
CountrySpec(
51+
allowedCountryCodes = setOf("DE", "BE", "FR"),
52+
).transformToFormElements(
53+
arguments = arguments,
54+
initialCountry = arguments.initialValues[IdentifierSpec.Country],
55+
).forEach { element(it) }
56+
}
6357
.overrideContactInformationPosition(ContactInformationCollectionMode.Name)
6458
.overrideContactInformationPosition(ContactInformationCollectionMode.Email)
6559
.overrideContactInformationPosition(ContactInformationCollectionMode.Phone)

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.stripe.android.paymentsheet.PaymentSheet.BillingDetailsCollectionConf
77
import com.stripe.android.ui.core.elements.AddressSpec
88
import com.stripe.android.ui.core.elements.AuBecsDebitMandateTextSpec
99
import com.stripe.android.ui.core.elements.CashAppPayMandateTextSpec
10+
import com.stripe.android.ui.core.elements.CountrySpec
1011
import com.stripe.android.ui.core.elements.EmailSpec
1112
import com.stripe.android.ui.core.elements.FormItemSpec
1213
import com.stripe.android.ui.core.elements.MandateTextSpec
@@ -34,6 +35,7 @@ internal object PlaceholderHelper {
3435
configuration: PaymentSheet.BillingDetailsCollectionConfiguration,
3536
termsDisplay: PaymentSheet.TermsDisplay,
3637
): List<FormItemSpec> {
38+
val countrySpecOwnsBillingAddress = specs.any { it is CountrySpec }
3739
val billingDetailsPlaceholders = mutableListOf(
3840
PlaceholderField.Name,
3941
PlaceholderField.Email,
@@ -60,8 +62,9 @@ internal object PlaceholderHelper {
6062
configuration.address == AddressCollectionMode.Never
6163
}
6264

63-
is PlaceholderSpec -> specForPlaceholderField(
64-
field = it.field,
65+
is PlaceholderSpec -> configuredPlaceholderSpec(
66+
spec = it,
67+
countrySpecOwnsBillingAddress = countrySpecOwnsBillingAddress,
6568
placeholderOverrideList = placeholderOverrideList,
6669
requiresMandate = requiresMandate,
6770
configuration = configuration,
@@ -102,6 +105,29 @@ internal object PlaceholderHelper {
102105
}
103106
}
104107

108+
private fun configuredPlaceholderSpec(
109+
spec: PlaceholderSpec,
110+
countrySpecOwnsBillingAddress: Boolean,
111+
placeholderOverrideList: List<IdentifierSpec>,
112+
requiresMandate: Boolean,
113+
configuration: PaymentSheet.BillingDetailsCollectionConfiguration,
114+
termsDisplay: PaymentSheet.TermsDisplay,
115+
): FormItemSpec? {
116+
if (
117+
countrySpecOwnsBillingAddress &&
118+
spec.field == PlaceholderField.BillingAddressWithoutCountry
119+
) {
120+
return null
121+
}
122+
return specForPlaceholderField(
123+
field = spec.field,
124+
placeholderOverrideList = placeholderOverrideList,
125+
requiresMandate = requiresMandate,
126+
configuration = configuration,
127+
termsDisplay = termsDisplay,
128+
)
129+
}
130+
105131
@VisibleForTesting
106132
internal fun removeCorrespondingPlaceholder(
107133
placeholderFields: MutableList<PlaceholderField>,
@@ -114,6 +140,9 @@ internal object PlaceholderHelper {
114140
is AddressSpec ->
115141
placeholderFields.remove(PlaceholderField.BillingAddress)
116142

143+
is CountrySpec ->
144+
placeholderFields.remove(PlaceholderField.BillingAddress)
145+
117146
is SepaMandateTextSpec -> placeholderFields.remove(PlaceholderField.SepaMandate)
118147
is PlaceholderSpec -> when (spec.field) {
119148
PlaceholderField.BillingAddressWithoutCountry ->

0 commit comments

Comments
 (0)