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

fix: postal code inputs no longer accept characters that are never present in postal codes #1027

Merged
merged 6 commits into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ lib/typescript/example
**/__tests__
**/__fixtures__
**/__mocks__

/android/src/androidTest/
/android/src/test/
/android/build/
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

### Fixes

- Fixed behavior of `CardField` and `CardForm` on Android to match that on iOS; postal code input no longer accepts characters that are never present in postal codes (anything besides 0-9, a-z, A-Z, hyphens, and whitespace). [#1027](https://github.com/stripe/stripe-react-native/pull/1027).

## 0.14.0 - 2022-06-30

### Breaking changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.facebook.react.uimanager.events.EventDispatcher
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.reactnativestripesdk.utils.getIntOrNull
import com.reactnativestripesdk.utils.getValOr
import com.stripe.android.databinding.BecsDebitWidgetBinding
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.view.BecsDebitWidget
Expand Down
33 changes: 27 additions & 6 deletions android/src/main/java/com/reactnativestripesdk/CardFieldView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.graphics.Color
import android.graphics.Typeface
import android.os.Build
import android.text.Editable
import android.text.InputFilter
import android.text.TextWatcher
import android.util.Log
import android.widget.FrameLayout
Expand All @@ -16,6 +17,8 @@ import com.facebook.react.uimanager.events.EventDispatcher
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.reactnativestripesdk.utils.*
import com.reactnativestripesdk.utils.mapCardBrand
import com.stripe.android.core.model.CountryCode
import com.stripe.android.core.model.CountryUtils
import com.stripe.android.databinding.CardInputWidgetBinding
Expand All @@ -25,7 +28,6 @@ import com.stripe.android.view.CardInputListener
import com.stripe.android.view.CardInputWidget
import com.stripe.android.view.CardValidCallback
import com.stripe.android.view.StripeEditText
import java.lang.Exception

class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
private var mCardWidget: CardInputWidget = CardInputWidget(context)
Expand Down Expand Up @@ -205,12 +207,11 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
* We can reliable assume that setPostalCodeEnabled is called before
* setCountryCode because of the order of the props in CardField.tsx
*/
fun setCountryCode(countryCode: String?) {
fun setCountryCode(countryString: String?) {
if (mCardWidget.postalCodeEnabled) {
val doesCountryUsePostalCode = CountryUtils.doesCountryUsePostalCode(
CountryCode.create(value = countryCode ?: LocaleListCompat.getAdjustedDefault()[0].country)
)
mCardWidget.postalCodeRequired = doesCountryUsePostalCode
val countryCode = CountryCode.create(value = countryString ?: LocaleListCompat.getAdjustedDefault()[0].country)
mCardWidget.postalCodeRequired = CountryUtils.doesCountryUsePostalCode(countryCode)
setPostalCodeFilter(countryCode)
}
}

Expand Down Expand Up @@ -336,6 +337,26 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
})
}

private fun setPostalCodeFilter(countryCode: CountryCode) {
cardInputWidgetBinding.postalCodeEditText.filters = arrayOf(
*cardInputWidgetBinding.postalCodeEditText.filters,
createPostalCodeInputFilter(countryCode)
)
}

private fun createPostalCodeInputFilter(countryCode: CountryCode): InputFilter {
return InputFilter { charSequence, start, end, _, _, _ ->
for (i in start until end) {
val isValidCharacter = (countryCode == CountryCode.US && PostalCodeUtilities.isValidUsPostalCodeCharacter(charSequence[i])) ||
(countryCode != CountryCode.US && PostalCodeUtilities.isValidGlobalPostalCodeCharacter(charSequence[i]))
if (!isValidCharacter) {
return@InputFilter ""
}
}
return@InputFilter null
}
}

override fun requestLayout() {
super.requestLayout()
post(mLayoutRunnable)
Expand Down
37 changes: 34 additions & 3 deletions android/src/main/java/com/reactnativestripesdk/CardFormView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.Typeface
import android.os.Build
import android.text.InputFilter
import android.view.View
import android.view.View.OnFocusChangeListener
import android.widget.FrameLayout
Expand All @@ -14,6 +15,8 @@ import com.facebook.react.uimanager.events.EventDispatcher
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.reactnativestripesdk.utils.*
import com.reactnativestripesdk.utils.mapCardBrand
import com.stripe.android.core.model.CountryCode
import com.stripe.android.databinding.CardMultilineWidgetBinding
import com.stripe.android.databinding.StripeCardFormViewBinding
Expand Down Expand Up @@ -50,10 +53,15 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) {
}

fun setDefaultValues(defaults: ReadableMap) {
defaults.getString("countryCode")?.let {
cardFormViewBinding.countryLayout.setSelectedCountryCode(CountryCode(it))
cardFormViewBinding.countryLayout.updateUiForCountryEntered(CountryCode(it))
setCountry(defaults.getString("countryCode"))
}

private fun setCountry(countryString: String?) {
if (countryString != null) {
cardFormViewBinding.countryLayout.setSelectedCountryCode(CountryCode(countryString))
cardFormViewBinding.countryLayout.updateUiForCountryEntered(CountryCode(countryString))
}
setPostalCodeFilter()
}

fun setPlaceHolders(value: ReadableMap) {
Expand Down Expand Up @@ -254,6 +262,29 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) {
}
}

private fun setPostalCodeFilter() {
cardFormViewBinding.postalCode.filters = arrayOf(
*cardFormViewBinding.postalCode.filters,
createPostalCodeInputFilter()
)
}

private fun createPostalCodeInputFilter(): InputFilter {
return InputFilter { charSequence, start, end, _, _, _ ->
if (cardFormViewBinding.countryLayout.getSelectedCountryCode() == CountryCode.US) {
// Rely on CardFormView's built-in US postal code filter
return@InputFilter null
}

for (i in start until end) {
if (!PostalCodeUtilities.isValidGlobalPostalCodeCharacter(charSequence[i])) {
return@InputFilter ""
}
}
return@InputFilter null
}
}

override fun requestLayout() {
super.requestLayout()
post(mLayoutRunnable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.reactnativestripesdk.utils.*
import com.reactnativestripesdk.utils.createError
import com.reactnativestripesdk.utils.createResult
import com.reactnativestripesdk.utils.mapFromPaymentIntentResult
import com.reactnativestripesdk.utils.mapFromSetupIntentResult
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.StripeIntent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.WritableNativeMap
import com.reactnativestripesdk.utils.GooglePayErrorType
import com.reactnativestripesdk.utils.createError
import com.reactnativestripesdk.utils.createResult
import com.reactnativestripesdk.utils.mapFromPaymentMethod
import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayLauncher
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.reactnativestripesdk.utils.*
import com.reactnativestripesdk.utils.createError
import com.reactnativestripesdk.utils.createMissingActivityError
import com.stripe.android.ApiResultCallback
import com.stripe.android.Stripe
import com.stripe.android.model.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.reactnativestripesdk

import com.facebook.react.bridge.ReadableMap
import com.reactnativestripesdk.utils.*
import com.reactnativestripesdk.utils.mapToBillingDetails
import com.reactnativestripesdk.utils.mapToUSBankAccountHolderType
import com.reactnativestripesdk.utils.mapToUSBankAccountType
import com.stripe.android.model.*

class PaymentMethodCreateParamsFactory(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.reactnativestripesdk

import android.graphics.Color
import android.os.Bundle
import com.reactnativestripesdk.utils.PaymentSheetAppearanceException
import com.stripe.android.paymentsheet.PaymentSheet

fun PaymentSheetFragment.buildPaymentSheetAppearance(userParams: Bundle?): PaymentSheet.Appearance {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.reactnativestripesdk.utils.*
import com.reactnativestripesdk.utils.createError
import com.reactnativestripesdk.utils.createResult
import com.stripe.android.paymentsheet.PaymentOptionCallback
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import androidx.appcompat.app.AppCompatActivity
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule
import com.reactnativestripesdk.pushprovisioning.PushProvisioningProxy
import com.reactnativestripesdk.utils.*
import com.reactnativestripesdk.utils.createError
import com.reactnativestripesdk.utils.createMissingActivityError
import com.stripe.android.*
import com.stripe.android.core.ApiVersion
import com.stripe.android.core.AppInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerModule
import com.facebook.react.uimanager.events.EventDispatcher
import com.reactnativestripesdk.createError
import com.reactnativestripesdk.utils.createError


class AddToWalletButtonView(private val context: ThemedReactContext, private val requestManager: RequestManager) : AppCompatImageView(context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import android.util.Log
import com.facebook.react.bridge.BaseActivityEventListener
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap
import com.reactnativestripesdk.createError
import com.reactnativestripesdk.mapError
import com.reactnativestripesdk.utils.createError
import com.reactnativestripesdk.utils.mapError
import com.stripe.android.pushProvisioning.PushProvisioningActivity
import com.stripe.android.pushProvisioning.PushProvisioningActivityStarter

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import android.util.Log
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.reactnativestripesdk.createError
import com.reactnativestripesdk.utils.createError
import com.google.android.gms.tasks.Task

typealias TokenCheckHandler = (isCardInWallet: Boolean, token: WritableMap?, error: WritableMap?) -> Unit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.reactnativestripesdk
package com.reactnativestripesdk.utils

import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.reactnativestripesdk
package com.reactnativestripesdk.utils

import android.content.Context
import android.view.View
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.reactnativestripesdk
package com.reactnativestripesdk.utils

import android.os.Bundle
import android.util.Log
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.reactnativestripesdk.utils

class PostalCodeUtilities {

companion object {
internal fun isValidGlobalPostalCodeCharacter(c: Char): Boolean {
return Character.isLetterOrDigit(c)
|| c.isWhitespace()
|| c == '-'
}

internal fun isValidUsPostalCodeCharacter(c: Char): Boolean {
return Character.isDigit(c)
|| c.isWhitespace()
|| c == '-'
}
}
}