Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localize the list of countries #3570

Merged
merged 38 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
432e85c
Localize the list of countries.
michelleb-stripe Apr 6, 2021
c057a75
Fix up unit test.
michelleb-stripe Apr 6, 2021
9e085aa
Localize all the country names.
michelleb-stripe Apr 8, 2021
3dc2636
Merge branch 'master' into michelleb/localize-country-list
michelleb-stripe Apr 12, 2021
6366fc0
Change to use countryCode
michelleb-stripe Apr 14, 2021
eba1486
Change to use countryCode
michelleb-stripe Apr 14, 2021
a8df283
Merge branch 'master' into michelleb/localize-country-list
michelleb-stripe Apr 14, 2021
1be2104
Fix tests and running well.
michelleb-stripe Apr 15, 2021
cd8da1b
Use a more strongly-typed, language independent type: country code.
michelleb-stripe Apr 15, 2021
cfe7bb1
Merge branch 'master' into michelleb/countryCode
michelleb-stripe Apr 15, 2021
7685721
Revert change to Address. Update CountryCode based on feedback.
michelleb-stripe Apr 16, 2021
aa7d6a9
Merge branch 'master' into michelleb/localize-country-list
michelleb-stripe Apr 16, 2021
e893f2e
Merge with base countrycode changes.
michelleb-stripe Apr 16, 2021
789329a
Cleanup unit tests
michelleb-stripe Apr 16, 2021
39f9138
Update unit tests.
michelleb-stripe Apr 16, 2021
dfe358e
Cleanup unit tests
michelleb-stripe Apr 16, 2021
15c3167
Update test case
michelleb-stripe Apr 16, 2021
e0a7c33
Merge with countryCodes
michelleb-stripe Apr 16, 2021
726d315
Minimize impact on ShippingInfoWidget.kt and shared PostalCodeValidat…
michelleb-stripe Apr 16, 2021
c8bce9f
Code review feedback.
michelleb-stripe Apr 16, 2021
5f0644a
Update stripe/src/main/java/com/stripe/android/paymentsheet/ui/Billin…
michelleb-stripe Apr 16, 2021
722988a
List country code as deprecated.
michelleb-stripe Apr 16, 2021
a1ef3c0
Merge with country code branch
michelleb-stripe Apr 16, 2021
9b7a0dc
Fix unit test import.
michelleb-stripe Apr 19, 2021
0c5032e
Update the api.
michelleb-stripe Apr 19, 2021
f0b7536
Merge branch 'master' into michelleb/localize-country-list
michelleb-stripe Apr 19, 2021
ca94add
Merge branch 'master' into michelleb/countryCode
michelleb-stripe Apr 19, 2021
a099cc1
Make country code internal
michelleb-stripe Apr 19, 2021
78dcbd7
Merge branch 'michelleb/countryCode' into michelleb/localize-country-…
michelleb-stripe Apr 19, 2021
a285099
Update the api
michelleb-stripe Apr 19, 2021
22efa31
Update the api
michelleb-stripe Apr 19, 2021
92fd4d5
Merge branch 'michelleb/countryCode' into michelleb/localize-country-…
michelleb-stripe Apr 19, 2021
048da73
Remove deprecated annotation
michelleb-stripe Apr 20, 2021
eb6cadb
Require not null on the selected country code.
michelleb-stripe Apr 20, 2021
edd036d
Update countryCode to use value and move the Locale.getCountryCode in…
michelleb-stripe Apr 20, 2021
8ccccd9
Remove all occurrences of twoLetter
michelleb-stripe Apr 20, 2021
5ea3810
Merge with country code.
michelleb-stripe Apr 20, 2021
1e6cfd5
Merge with master.
michelleb-stripe Apr 20, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions stripe/src/main/java/com/stripe/android/model/Address.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ data class Address internal constructor(
val postalCode: String? = null,
val state: String? = null
) : StripeModel, StripeParamsModel {
internal val countryCode: CountryCode?
get() = country?.takeUnless { it.isBlank() }?.let { CountryCode.create(it) }

override fun toParamMap(): Map<String, Any> {
return mapOf(
Expand All @@ -43,10 +45,21 @@ data class Address internal constructor(
this.city = city
}

@Deprecated(
message = "This will be removed in future version",
replaceWith = ReplaceWith(
expression = "setCountryCode(CountryCode.create(country))",
imports = ["com.stripe.android.model.CountryCode"]
)
)
fun setCountry(country: String?): Builder = apply {
this.country = country?.toUpperCase(Locale.ROOT)
}

internal fun setCountryCode(country: CountryCode?): Builder = apply {
this.country = country?.twoLetters
}

fun setLine1(line1: String?): Builder = apply {
this.line1 = line1
}
Expand Down Expand Up @@ -77,6 +90,7 @@ data class Address internal constructor(

companion object {
private const val PARAM_CITY = "city"

// 2 Character Country Code
private const val PARAM_COUNTRY = "country"
private const val PARAM_LINE_1 = "line1"
Expand Down
18 changes: 18 additions & 0 deletions stripe/src/main/java/com/stripe/android/model/CountryCode.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.stripe.android.model

import java.util.Locale

internal data class CountryCode private constructor(
val twoLetters: String,
) {
companion object {
val US = CountryCode("US")
val CA = CountryCode("CA")
val GB = CountryCode("GB")
fun isUS(countryCode: CountryCode?) = countryCode == US
fun isCA(countryCode: CountryCode?) = countryCode == CA
fun isGB(countryCode: CountryCode?) = countryCode == GB

fun create(value: String) = CountryCode(value.toUpperCase(Locale.ROOT))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.stripe.android.databinding.FragmentPaymentsheetAddCardBinding
import com.stripe.android.databinding.StripeHorizontalDividerBinding
import com.stripe.android.databinding.StripeVerticalDividerBinding
import com.stripe.android.model.CardBrand
import com.stripe.android.model.CountryCode
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.paymentsheet.analytics.EventReporter
import com.stripe.android.paymentsheet.model.FragmentConfig
Expand Down Expand Up @@ -287,7 +288,7 @@ internal abstract class BaseAddCardFragment(
val shouldToggleBillingError =
!isPostalValid && !billingAddressView.postalCodeView.text.isNullOrEmpty()
billingErrors.text = if (shouldToggleBillingError) {
if (country == null || country.code == "US") {
if (country == null || CountryCode.isUS(country.code)) {
getString(R.string.address_zip_invalid)
} else {
getString(R.string.address_postal_code_invalid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import android.view.View.OnFocusChangeListener
import android.widget.EditText
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.core.os.ConfigurationCompat
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.stripe.android.R
import com.stripe.android.databinding.StripeBillingAddressLayoutBinding
import com.stripe.android.model.Address
import com.stripe.android.model.CountryCode
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.view.Country
import com.stripe.android.view.CountryUtils
Expand Down Expand Up @@ -98,7 +100,7 @@ internal class BillingAddressView @JvmOverloads constructor(
@VisibleForTesting
internal var postalCodeViewListener: PostalCodeViewListener? = null

private val isUnitedStates: Boolean get() = countryLayout.selectedCountry?.code == Locale.US.country
private val isUnitedStates: Boolean get() = CountryCode.isUS(countryLayout.selectedCountryCode)

private var postalCodeConfig: PostalCodeConfig by Delegates.observable(
PostalCodeConfig.Global
Expand All @@ -108,16 +110,19 @@ internal class BillingAddressView @JvmOverloads constructor(
postalCodeView.inputType = config.inputType
}

private val newCountryCallback = { newCountry: Country ->
updateStateView(newCountry)
updatePostalCodeView(newCountry)
private val newCountryCodeCallback = { newCountryCode: CountryCode ->
updateStateView(newCountryCode)
updatePostalCodeView(newCountryCode)
_address.value = createAddress()

postalCodeValidator.isValid(
postalCode = postalCodeView.value.orEmpty(),
countryCode = newCountry.code
countryCode = newCountryCode.twoLetters
).let { isPostalValid ->
postalCodeViewListener?.onCountryChanged(newCountry, isPostalValid)
postalCodeViewListener?.onCountryChanged(
CountryUtils.getCountryByCode(newCountryCode, getLocale()),
isPostalValid
)
postalCodeView.shouldShowError = !isPostalValid
}
}
Expand Down Expand Up @@ -149,11 +154,11 @@ internal class BillingAddressView @JvmOverloads constructor(
)

init {
countryLayout.countryChangeCallback = newCountryCallback
countryLayout.countryCodeChangeCallback = newCountryCodeCallback
// Since the callback is set after CountryAutoCompleteTextView is fully initialized,
// need to manually trigger the callback once to pick up the initial country
countryLayout.selectedCountry?.let {
newCountryCallback(it)
countryLayout.selectedCountryCode?.let { it ->
newCountryCodeCallback(it)
}

configureForLevel()
Expand All @@ -171,10 +176,10 @@ internal class BillingAddressView @JvmOverloads constructor(
}

postalCodeView.internalFocusChangeListeners.add { _, hasFocus ->
val isPostalValid = countryLayout.selectedCountry?.code?.let { countryCode ->
val isPostalValid = countryLayout.selectedCountryCode?.let { countryCode ->
postalCodeValidator.isValid(
postalCode = postalCodeView.value.orEmpty(),
countryCode = countryCode
countryCode = countryCode.twoLetters
)
} ?: false

Expand All @@ -183,12 +188,16 @@ internal class BillingAddressView @JvmOverloads constructor(

if (hasFocus) {
postalCodeViewListener?.onGainingFocus(
countryLayout.selectedCountry,
countryLayout.selectedCountryCode?.let {
CountryUtils.getCountryByCode(it, getLocale())
},
isPostalValid
)
} else {
postalCodeViewListener?.onLosingFocus(
countryLayout.selectedCountry,
countryLayout.selectedCountryCode?.let {
CountryUtils.getCountryByCode(it, getLocale())
},
isPostalValid
)
postalCodeView.shouldShowError =
Expand All @@ -201,19 +210,19 @@ internal class BillingAddressView @JvmOverloads constructor(
* An [Address] if the country and postal code are valid; otherwise `null`.
*/
private fun createAddress(): Address? {
return countryLayout.selectedCountry?.code?.let { countryCode ->
return countryLayout.selectedCountryCode?.let { countryCode ->
val postalCode = postalCodeView.value
val isPostalCodeValid = postalCodeValidator.isValid(
postalCode = postalCode.orEmpty(),
countryCode = countryCode
countryCode = countryCode.twoLetters
)
if (isPostalCodeValid) {
when (level) {
PaymentSheet.BillingAddressCollectionLevel.Automatic -> {
Address(
country = countryCode,
postalCode = postalCode
)
Address.Builder()
.setCountryCode(countryCode)
.setPostalCode(postalCode)
.build()
}
PaymentSheet.BillingAddressCollectionLevel.Required -> {
createRequiredAddress(countryCode, postalCode)
Expand All @@ -226,7 +235,7 @@ internal class BillingAddressView @JvmOverloads constructor(
}

private fun createRequiredAddress(
countryCode: String,
countryCode: CountryCode,
postalCode: String?
): Address? {
val line1 = address1View.value
Expand All @@ -236,22 +245,22 @@ internal class BillingAddressView @JvmOverloads constructor(

return if (line1 != null && city != null) {
if (!isUnitedStates) {
Address(
country = countryCode,
postalCode = postalCode,
line1 = line1,
line2 = line2,
city = city
)
Address.Builder()
.setCountryCode(countryCode)
.setPostalCode(postalCode)
.setLine1(line1)
.setLine2(line2)
.setCity(city)
.build()
} else if (state != null) {
Address(
country = countryCode,
postalCode = postalCode,
line1 = line1,
line2 = line2,
city = city,
state = state
)
Address.Builder()
.setCountryCode(countryCode)
.setPostalCode(postalCode)
.setLine1(line1)
.setLine2(line2)
.setCity(city)
.setState(state)
.build()
} else {
null
}
Expand All @@ -260,15 +269,15 @@ internal class BillingAddressView @JvmOverloads constructor(
}
}

private fun updateStateView(country: Country?) {
when (country?.code?.toUpperCase(Locale.ROOT)) {
"US" -> {
private fun updateStateView(countryCode: CountryCode?) {
when {
CountryCode.isUS(countryCode) -> {
R.string.address_label_state
}
"CA" -> {
CountryCode.isCA(countryCode) -> {
R.string.address_label_province
}
"GB" -> {
CountryCode.isGB(countryCode) -> {
R.string.address_label_county
}
else -> {
Expand All @@ -279,24 +288,24 @@ internal class BillingAddressView @JvmOverloads constructor(
}
}

private fun updatePostalCodeView(country: Country?) {
val shouldShowPostalCode = country == null ||
CountryUtils.doesCountryUsePostalCode(country.code)
private fun updatePostalCodeView(countryCode: CountryCode?) {
val shouldShowPostalCode = countryCode == null ||
CountryUtils.doesCountryUsePostalCode(CountryCode.create(countryCode.twoLetters))
postalCodeLayout.isVisible = shouldShowPostalCode

val shouldShowPostalCodeContainer =
level == PaymentSheet.BillingAddressCollectionLevel.Required || shouldShowPostalCode
viewBinding.cityPostalDivider.isVisible = shouldShowPostalCodeContainer
viewBinding.cityPostalContainer.isVisible = shouldShowPostalCodeContainer

postalCodeConfig = if (country?.code == "US") {
postalCodeConfig = if (CountryCode.isUS(countryCode)) {
PostalCodeConfig.UnitedStates
} else {
PostalCodeConfig.Global
}

viewBinding.postalCodeLayout.hint = resources.getString(
if (country?.code == "US") {
if (CountryCode.isUS(countryCode)) {
R.string.acc_label_zip_short
} else {
R.string.address_label_postal_code
Expand Down Expand Up @@ -348,9 +357,10 @@ internal class BillingAddressView @JvmOverloads constructor(
// country will trigger a validation of the postal code, which will be
// invalid if not set first.
this.postalCodeView.setText(it.postalCode)
it.country?.let { countryCode ->
countryLayout.selectedCountry = CountryUtils.getCountryByCode(countryCode)
this.countryView.setText(CountryUtils.getDisplayCountry(countryCode))

it.countryCode?.let {
this.countryLayout.selectedCountryCode = it
this.countryView.setText(CountryUtils.getDisplayCountry(it, getLocale()))
}
this.address1View.setText(it.line1)
this.address2View.setText(it.line2)
Expand All @@ -366,6 +376,10 @@ internal class BillingAddressView @JvmOverloads constructor(
}
}

private fun getLocale(): Locale {
return ConfigurationCompat.getLocales(context.resources.configuration)[0]
}

internal sealed class PostalCodeConfig {
abstract val maxLength: Int
abstract val inputType: Int
Expand Down
17 changes: 9 additions & 8 deletions stripe/src/main/java/com/stripe/android/view/CardFormView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.stripe.android.databinding.StripeVerticalDividerBinding
import com.stripe.android.model.Address
import com.stripe.android.model.CardBrand
import com.stripe.android.model.CardParams
import com.stripe.android.model.CountryCode
import com.stripe.android.view.CardFormView.Style
import com.stripe.android.view.CardValidCallback.Fields

Expand Down Expand Up @@ -127,7 +128,7 @@ internal class CardFormView @JvmOverloads constructor(
expYear = expirationDate.year,
cvc = cardMultilineWidget.cvcEditText.text?.toString(),
address = Address.Builder()
.setCountry(countryLayout.selectedCountry.toString())
.setCountryCode(countryLayout.selectedCountryCode)
.setPostalCode(postalCodeView.text?.toString())
.build()
)
Expand Down Expand Up @@ -177,7 +178,7 @@ internal class CardFormView @JvmOverloads constructor(

private fun setupCountryAndPostal() {
// wire up postal code and country
postalCodeView.config = if (countryLayout.selectedCountry?.code == "US") {
postalCodeView.config = if (CountryCode.isUS(countryLayout.selectedCountryCode)) {
PostalCodeEditText.Config.US
} else {
PostalCodeEditText.Config.Global
Expand Down Expand Up @@ -205,30 +206,30 @@ internal class CardFormView @JvmOverloads constructor(
onFieldError(Fields.Postal, null)
}

countryLayout.countryChangeCallback = { country ->
postalCodeView.config = if (country.code == "US") {
countryLayout.countryCodeChangeCallback = { countryCode ->
postalCodeView.config = if (CountryCode.isUS(countryCode)) {
PostalCodeEditText.Config.US
} else {
PostalCodeEditText.Config.Global
}
postalCodeContainer.isVisible = CountryUtils.doesCountryUsePostalCode(country.code)
postalCodeContainer.isVisible = CountryUtils.doesCountryUsePostalCode(countryCode)
postalCodeView.shouldShowError = false
postalCodeView.text = null
}
}

private fun isPostalValid() =
countryLayout.selectedCountry?.code?.let { countryCode ->
countryLayout.selectedCountryCode?.let { countryCode ->
postalCodeValidator.isValid(
postalCode = postalCodeView.postalCode.orEmpty(),
countryCode = countryCode
countryCode = countryCode.twoLetters
)
} ?: false

private fun showPostalError() {
onFieldError(
Fields.Postal,
if (countryLayout.selectedCountry == null || countryLayout.selectedCountry!!.code == "US") {
if (countryLayout.selectedCountryCode == null || CountryCode.isUS(countryLayout.selectedCountryCode!!)) {
resources.getString(R.string.address_zip_invalid)
} else {
resources.getString(R.string.address_postal_code_invalid)
Expand Down
5 changes: 4 additions & 1 deletion stripe/src/main/java/com/stripe/android/view/Country.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.stripe.android.view

import com.stripe.android.model.CountryCode

internal data class Country(
val code: String,
val code: CountryCode,
val name: String
) {
constructor(twoLetter: String, name: String) : this(CountryCode.create(twoLetter), name)

/**
* @return display value for [CountryTextInputLayout] text view
Expand Down
Loading