diff --git a/CHANGELOG.md b/CHANGELOG.md index 501ebea5d..ac6f05196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,13 @@ ### New features - Added a `defaultValues` prop to the `CardForm` component. Currently only accepts `countryCode`, and is Android-only. [#974](https://github.com/stripe/stripe-react-native/pull/974) +- Added the `countryCode` prop to the `CardField` component. [#989](https://github.com/stripe/stripe-react-native/pull/989) - Added option to create a PII token (represents the details of personally identifiable information) to the `createToken` method. [#976](https://github.com/stripe/stripe-react-native/pull/976) ### Fixes - Resolve with an Error (of type `Canceled`) if no payment option is selected in the Payment Sheet custom flow (i.e., the `x` button is clicked to close the Payment Sheet). [#975](https://github.com/stripe/stripe-react-native/pull/975) +- Fixed an issue on Android where the `complete` field in the `onCardChange` callback would incorrectly be set to `true` even if the postal code wasn't filled out. [#989](https://github.com/stripe/stripe-react-native/pull/989) - Make `SetupIntent.lastSetupError` and `PaymentIntent.lastPaymentError` object shape consistent on iOS and Android.[#990](https://github.com/stripe/stripe-react-native/pull/990) ## 0.12.0 diff --git a/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt b/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt index 7788a4688..b4011659f 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt @@ -8,6 +8,7 @@ import android.text.Editable import android.text.TextWatcher import android.util.Log import android.widget.FrameLayout +import androidx.core.os.LocaleListCompat import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerModule @@ -15,6 +16,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.stripe.android.core.model.CountryCode +import com.stripe.android.core.model.CountryUtils import com.stripe.android.databinding.CardInputWidgetBinding import com.stripe.android.model.Address import com.stripe.android.model.PaymentMethodCreateParams @@ -33,6 +36,7 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { private var mEventDispatcher: EventDispatcher? = context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher private var dangerouslyGetFullCardDetails: Boolean = false private var currentFocusedField: String? = null + private var isCardValid = false init { cardInputWidgetBinding.container.isFocusable = true @@ -197,6 +201,14 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { mCardWidget.postalCodeEnabled = isEnabled } + fun setCountryCode(countryCode: String?) { + val doesCountryUsePostalCode = CountryUtils.doesCountryUsePostalCode( + CountryCode.create(value = countryCode ?: LocaleListCompat.getAdjustedDefault()[0].country) + ) + mCardWidget.postalCodeRequired = doesCountryUsePostalCode + mCardWidget.postalCodeEnabled = doesCountryUsePostalCode + } + fun getValue(): MutableMap { return cardDetails } @@ -224,7 +236,7 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { private fun sendCardDetailsEvent() { mEventDispatcher?.dispatchEvent( - CardChangedEvent(id, cardDetails, mCardWidget.postalCodeEnabled, cardParams != null, dangerouslyGetFullCardDetails)) + CardChangedEvent(id, cardDetails, mCardWidget.postalCodeEnabled, isCardValid, dangerouslyGetFullCardDetails)) } private fun setListeners() { @@ -246,6 +258,7 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } mCardWidget.setCardValidCallback { isValid, invalidFields -> + isCardValid = isValid fun getCardValidationState(field: CardValidCallback.Fields, editTextField: StripeEditText): String { if (invalidFields.contains(field)) { return if (editTextField.shouldShowError) "Invalid" @@ -263,6 +276,7 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } else { cardParams = null cardAddress = null + sendCardDetailsEvent() } } @@ -284,8 +298,6 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { if (splitText.size == 2) { cardDetails["expiryYear"] = var1.toString().split("/")[1].toIntOrNull() } - - sendCardDetailsEvent() } }) @@ -294,7 +306,6 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { override fun afterTextChanged(p0: Editable?) {} override fun onTextChanged(var1: CharSequence?, var2: Int, var3: Int, var4: Int) { cardDetails["postalCode"] = var1.toString() - sendCardDetailsEvent() } }) @@ -305,7 +316,6 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { if (dangerouslyGetFullCardDetails) { cardDetails["number"] = var1.toString().replace(" ", "") } - sendCardDetailsEvent() } }) @@ -316,7 +326,6 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { if (dangerouslyGetFullCardDetails) { cardDetails["cvc"] = var1.toString() } - sendCardDetailsEvent() } }) } diff --git a/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt b/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt index 8d8b231fb..c67f89435 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt @@ -51,6 +51,11 @@ class CardFieldViewManager : SimpleViewManager() { view.setPlaceHolders(placeholders) } + @ReactProp(name = "countryCode") + fun setPlaceHolders(view: CardFieldView, countryCode: String?) { + view.setCountryCode(countryCode) + } + override fun createViewInstance(reactContext: ThemedReactContext): CardFieldView { val stripeSdkModule: StripeSdkModule? = reactContext.getNativeModule(StripeSdkModule::class.java) val view = CardFieldView(reactContext) diff --git a/ios/CardFieldManager.m b/ios/CardFieldManager.m index 4f25e6ef9..6ea66092b 100644 --- a/ios/CardFieldManager.m +++ b/ios/CardFieldManager.m @@ -4,6 +4,7 @@ @interface RCT_EXTERN_MODULE(CardFieldManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(postalCodeEnabled, BOOL) +RCT_EXPORT_VIEW_PROPERTY(countryCode, NSString) RCT_EXPORT_VIEW_PROPERTY(onCardChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onFocusChange, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(cardStyle, NSDictionary) diff --git a/ios/CardFieldView.swift b/ios/CardFieldView.swift index f64367b78..ebbe37c8f 100644 --- a/ios/CardFieldView.swift +++ b/ios/CardFieldView.swift @@ -18,6 +18,12 @@ class CardFieldView: UIView, STPPaymentCardTextFieldDelegate { } } + @objc var countryCode: String? { + didSet { + cardField.countryCode = countryCode + } + } + @objc var placeholders: NSDictionary = NSDictionary() { didSet { if let numberPlaceholder = placeholders["number"] as? String { diff --git a/src/components/CardField.tsx b/src/components/CardField.tsx index 96c1dccc6..90c92bb10 100644 --- a/src/components/CardField.tsx +++ b/src/components/CardField.tsx @@ -30,7 +30,10 @@ const CardFieldNative = */ export interface Props extends AccessibilityProps { style?: StyleProp; + /** Controls if a postal code entry field can be displayed to the user. Defaults to false. If true, the type of code entry shown is controlled by the set countryCode prop. Some country codes may result in no postal code entry being shown if those countries do not commonly use postal codes. If false, no postal code entry will ever be displayed. */ postalCodeEnabled?: boolean; + /** Controls the postal code entry shown (if the postalCodeEnabled prop is set to true). Defaults to the device's default locale. */ + countryCode?: string; cardStyle?: CardFieldInput.Styles; placeholders?: CardFieldInput.Placeholders; autofocus?: boolean; @@ -74,6 +77,7 @@ export const CardField = forwardRef( cardStyle, placeholders, postalCodeEnabled, + countryCode, ...props }, ref @@ -175,6 +179,7 @@ export const CardField = forwardRef( onCardChange={onCardChangeHandler} onFocusChange={onFocusHandler} postalCodeEnabled={postalCodeEnabled ?? true} + countryCode={countryCode ?? null} cardStyle={{ backgroundColor: cardStyle?.backgroundColor, borderColor: cardStyle?.borderColor, diff --git a/src/types/components/CardFieldInput.ts b/src/types/components/CardFieldInput.ts index 94c7ddd00..26226f166 100644 --- a/src/types/components/CardFieldInput.ts +++ b/src/types/components/CardFieldInput.ts @@ -59,6 +59,7 @@ export interface NativeProps { value?: Partial
; postalCodeEnabled?: boolean; autofocus?: boolean; + countryCode: string | null; onCardChange(event: NativeSyntheticEvent
): void; onFocusChange( event: NativeSyntheticEvent<{ focusedField: FieldName | null }>