From f11f8dc6c50b184009834f6eb13d3096ce1f8a56 Mon Sep 17 00:00:00 2001 From: charliecruzan-stripe <97612659+charliecruzan-stripe@users.noreply.github.com> Date: Mon, 18 Apr 2022 17:29:53 -0400 Subject: [PATCH] fix: onBlur callback on CardField (#894) --- CHANGELOG.md | 1 + .../AuBECSDebitFormViewManager.kt | 2 - .../com/reactnativestripesdk/CardFieldView.kt | 82 +++++++++++-------- .../CardFieldViewManager.kt | 5 -- .../CardFormViewManager.kt | 4 - .../GooglePayButtonManager.kt | 1 - .../PaymentLauncherFragment.kt | 9 +- .../StripeContainerManager.kt | 1 - .../reactnativestripesdk/StripeSdkModule.kt | 18 ---- 9 files changed, 53 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 992461853..cb935330d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # CHANGELOG +- [#894](https://github.com/stripe/stripe-react-native/pull/894) Fix: `` `onBlur` callback will now be called appropriately on Android - [#846](https://github.com/stripe/stripe-react-native/pull/846) Fix: Avoid crashes when `currentActivity` is null - [#879](https://github.com/stripe/stripe-react-native/pull/879) Feat: Add support for ACHv2 payments on Android (already existed on iOS). - [#879](https://github.com/stripe/stripe-react-native/pull/879) Chore: Upgraded `stripe-android` from v19.3.+ to v20.0.+ diff --git a/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt b/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt index 7012c5e99..ea5a98bcf 100644 --- a/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/AuBECSDebitFormViewManager.kt @@ -15,13 +15,11 @@ class AuBECSDebitFormViewManager : SimpleViewManager() { } @ReactProp(name = "companyName") - @SuppressWarnings("unused") fun setCompanyName(view: AuBECSDebitFormView, name: String?) { view.setCompanyName(name) } @ReactProp(name = "formStyle") - @SuppressWarnings("unused") fun setFormStyle(view: AuBECSDebitFormView, style: ReadableMap) { view.setFormStyle(style) } diff --git a/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt b/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt index 7641deabb..9a49b48fd 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt @@ -23,18 +23,18 @@ import com.stripe.android.view.CardValidCallback class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { private var mCardWidget: CardInputWidget = CardInputWidget(context) + private val cardInputWidgetBinding = CardInputWidgetBinding.bind(mCardWidget) val cardDetails: MutableMap = mutableMapOf("brand" to "", "last4" to "", "expiryMonth" to null, "expiryYear" to null, "postalCode" to "", "validNumber" to "Unknown", "validCVC" to "Unknown", "validExpiryDate" to "Unknown") var cardParams: PaymentMethodCreateParams.Card? = null var cardAddress: Address? = null private var mEventDispatcher: EventDispatcher? = context.getNativeModule(UIManagerModule::class.java)?.eventDispatcher private var dangerouslyGetFullCardDetails: Boolean = false + private var currentFocusedField: String? = null init { - - val binding = CardInputWidgetBinding.bind(mCardWidget) - binding.container.isFocusable = true - binding.container.isFocusableInTouchMode = true - binding.container.requestFocus() + cardInputWidgetBinding.container.isFocusable = true + cardInputWidgetBinding.container.isFocusableInTouchMode = true + cardInputWidgetBinding.container.requestFocus() addView(mCardWidget) setListeners() @@ -44,37 +44,37 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { fun setAutofocus(value: Boolean) { if (value) { - val binding = CardInputWidgetBinding.bind(mCardWidget) - binding.cardNumberEditText.requestFocus() - binding.cardNumberEditText.showSoftKeyboard() + cardInputWidgetBinding.cardNumberEditText.requestFocus() + cardInputWidgetBinding.cardNumberEditText.showSoftKeyboard() } } fun requestFocusFromJS() { - val binding = CardInputWidgetBinding.bind(mCardWidget) - binding.cardNumberEditText.requestFocus() - binding.cardNumberEditText.showSoftKeyboard() + cardInputWidgetBinding.cardNumberEditText.requestFocus() + cardInputWidgetBinding.cardNumberEditText.showSoftKeyboard() } fun requestBlurFromJS() { - val binding = CardInputWidgetBinding.bind(mCardWidget) - binding.cardNumberEditText.hideSoftKeyboard() - binding.cardNumberEditText.clearFocus() - binding.container.requestFocus() + cardInputWidgetBinding.cardNumberEditText.hideSoftKeyboard() + cardInputWidgetBinding.cardNumberEditText.clearFocus() + cardInputWidgetBinding.container.requestFocus() } fun requestClearFromJS() { - val binding = CardInputWidgetBinding.bind(mCardWidget) - binding.cardNumberEditText.setText("") - binding.cvcEditText.setText("") - binding.expiryDateEditText.setText("") + cardInputWidgetBinding.cardNumberEditText.setText("") + cardInputWidgetBinding.cvcEditText.setText("") + cardInputWidgetBinding.expiryDateEditText.setText("") if (mCardWidget.postalCodeEnabled) { - binding.postalCodeEditText.setText("") + cardInputWidgetBinding.postalCodeEditText.setText("") } } + private fun onChangeFocus() { + mEventDispatcher?.dispatchEvent( + CardFocusEvent(id, currentFocusedField)) + } + fun setCardStyle(value: ReadableMap) { - val binding = CardInputWidgetBinding.bind(mCardWidget) val borderWidth = getIntOrNull(value, "borderWidth") val backgroundColor = getValOr(value, "backgroundColor", null) val borderColor = getValOr(value, "borderColor", null) @@ -85,7 +85,11 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { val placeholderColor = getValOr(value, "placeholderColor", null) val textErrorColor = getValOr(value, "textErrorColor", null) val cursorColor = getValOr(value, "cursorColor", null) - val bindings = setOf(binding.cardNumberEditText, binding.cvcEditText, binding.expiryDateEditText, binding.postalCodeEditText) + val bindings = setOf( + cardInputWidgetBinding.cardNumberEditText, + cardInputWidgetBinding.cvcEditText, + cardInputWidgetBinding.expiryDateEditText, + cardInputWidgetBinding.postalCodeEditText) textColor?.let { for (editTextBinding in bindings) { @@ -148,23 +152,22 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } fun setPlaceHolders(value: ReadableMap) { - val binding = CardInputWidgetBinding.bind(mCardWidget) val numberPlaceholder = getValOr(value, "number", null) val expirationPlaceholder = getValOr(value, "expiration", null) val cvcPlaceholder = getValOr(value, "cvc", null) val postalCodePlaceholder = getValOr(value, "postalCode", null) numberPlaceholder?.let { - binding.cardNumberEditText.hint = it + cardInputWidgetBinding.cardNumberEditText.hint = it } expirationPlaceholder?.let { - binding.expiryDateEditText.hint = it + cardInputWidgetBinding.expiryDateEditText.hint = it } cvcPlaceholder?.let { mCardWidget.setCvcLabel(it) } postalCodePlaceholder?.let { - binding.postalCodeEditText.hint = it + cardInputWidgetBinding.postalCodeEditText.hint = it } } @@ -180,7 +183,7 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { return cardDetails } - fun onValidCardChange() { + private fun onValidCardChange() { mCardWidget.paymentMethodCard?.let { cardParams = it cardAddress = Address.Builder() @@ -207,6 +210,23 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } private fun setListeners() { + cardInputWidgetBinding.cardNumberEditText.setOnFocusChangeListener { _, hasFocus -> + currentFocusedField = if (hasFocus) CardInputListener.FocusField.CardNumber.name else null + onChangeFocus() + } + cardInputWidgetBinding.expiryDateEditText.setOnFocusChangeListener { _, hasFocus -> + currentFocusedField = if (hasFocus) CardInputListener.FocusField.ExpiryDate.name else null + onChangeFocus() + } + cardInputWidgetBinding.cvcEditText.setOnFocusChangeListener { _, hasFocus -> + currentFocusedField = if (hasFocus) CardInputListener.FocusField.Cvc.name else null + onChangeFocus() + } + cardInputWidgetBinding.postalCodeEditText.setOnFocusChangeListener { _, hasFocus -> + currentFocusedField = if (hasFocus) CardInputListener.FocusField.PostalCode.name else null + onChangeFocus() + } + mCardWidget.setCardValidCallback { isValid, invalidFields -> cardDetails["validNumber"] = if (invalidFields.contains(CardValidCallback.Fields.Number)) "Invalid" else "Valid" cardDetails["validCVC"] = if (invalidFields.contains(CardValidCallback.Fields.Cvc)) "Invalid" else "Valid" @@ -224,13 +244,7 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { override fun onExpirationComplete() {} override fun onCvcComplete() {} override fun onPostalCodeComplete() {} - - override fun onFocusChange(focusField: CardInputListener.FocusField) { - if (mEventDispatcher != null) { - mEventDispatcher?.dispatchEvent( - CardFocusEvent(id, focusField.name)) - } - } + override fun onFocusChange(focusField: CardInputListener.FocusField) {} }) mCardWidget.setExpiryDateTextWatcher(object : TextWatcher { diff --git a/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt b/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt index beeff2d19..5952a38ea 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt @@ -27,31 +27,26 @@ class CardFieldViewManager : SimpleViewManager() { } @ReactProp(name = "dangerouslyGetFullCardDetails") - @SuppressWarnings("unused") fun setDangerouslyGetFullCardDetails(view: CardFieldView, dangerouslyGetFullCardDetails: Boolean = false) { view.setDangerouslyGetFullCardDetails(dangerouslyGetFullCardDetails) } @ReactProp(name = "postalCodeEnabled") - @SuppressWarnings("unused") fun setPostalCodeEnabled(view: CardFieldView, postalCodeEnabled: Boolean = true) { view.setPostalCodeEnabled(postalCodeEnabled) } @ReactProp(name = "autofocus") - @SuppressWarnings("unused") fun setAutofocus(view: CardFieldView, autofocus: Boolean = false) { view.setAutofocus(autofocus) } @ReactProp(name = "cardStyle") - @SuppressWarnings("unused") fun setCardStyle(view: CardFieldView, cardStyle: ReadableMap) { view.setCardStyle(cardStyle) } @ReactProp(name = "placeholder") - @SuppressWarnings("unused") fun setPlaceHolders(view: CardFieldView, placeholder: ReadableMap) { view.setPlaceHolders(placeholder) } diff --git a/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt b/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt index bbcf15306..96c59e663 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt @@ -27,13 +27,11 @@ class CardFormViewManager : SimpleViewManager() { } @ReactProp(name = "dangerouslyGetFullCardDetails") - @SuppressWarnings("unused") fun setDangerouslyGetFullCardDetails(view: CardFormView, dangerouslyGetFullCardDetails: Boolean = false) { view.setDangerouslyGetFullCardDetails(dangerouslyGetFullCardDetails) } @ReactProp(name = "postalCodeEnabled") - @SuppressWarnings("unused") fun setPostalCodeEnabled(view: CardFormView, postalCodeEnabled: Boolean = false) { view.setPostalCodeEnabled(postalCodeEnabled) } @@ -44,13 +42,11 @@ class CardFormViewManager : SimpleViewManager() { // } @ReactProp(name = "autofocus") - @SuppressWarnings("unused") fun setAutofocus(view: CardFormView, autofocus: Boolean = false) { view.setAutofocus(autofocus) } @ReactProp(name = "cardStyle") - @SuppressWarnings("unused") fun setCardStyle(view: CardFormView, cardStyle: ReadableMap) { view.setCardStyle(cardStyle) } diff --git a/android/src/main/java/com/reactnativestripesdk/GooglePayButtonManager.kt b/android/src/main/java/com/reactnativestripesdk/GooglePayButtonManager.kt index 89c9bf65b..19d87e49d 100644 --- a/android/src/main/java/com/reactnativestripesdk/GooglePayButtonManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/GooglePayButtonManager.kt @@ -16,7 +16,6 @@ class GooglePayButtonManager : SimpleViewManager() { } @ReactProp(name = "buttonType") - @SuppressWarnings("unused") fun buttonType(view: GooglePayButtonView, buttonType: String) { view.setType(buttonType) } diff --git a/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt b/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt index 2ccf4142a..ce990a409 100644 --- a/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/PaymentLauncherFragment.kt @@ -18,11 +18,10 @@ class PaymentLauncherFragment( private val publishableKey: String, private val stripeAccountId: String?, ) : Fragment() { - lateinit var paymentLauncher: PaymentLauncher - - var clientSecret: String? = null - var promise: Promise? = null - var isPaymentIntent: Boolean = true + private lateinit var paymentLauncher: PaymentLauncher + private var clientSecret: String? = null + private var promise: Promise? = null + private var isPaymentIntent: Boolean = true override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { diff --git a/android/src/main/java/com/reactnativestripesdk/StripeContainerManager.kt b/android/src/main/java/com/reactnativestripesdk/StripeContainerManager.kt index d297ca376..8d9efcef3 100644 --- a/android/src/main/java/com/reactnativestripesdk/StripeContainerManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/StripeContainerManager.kt @@ -8,7 +8,6 @@ class StripeContainerManager : ViewGroupManager() { override fun getName() = "StripeContainer" @ReactProp(name = "keyboardShouldPersistTaps") - @SuppressWarnings("unused") fun setKeyboardShouldPersistTaps(view: StripeContainerView, keyboardShouldPersistTaps: Boolean) { view.setKeyboardShouldPersistTaps(keyboardShouldPersistTaps) } diff --git a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt index aa30463bd..1d9820dbf 100644 --- a/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +++ b/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt @@ -190,7 +190,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun initialise(params: ReadableMap, promise: Promise) { val publishableKey = getValOr(params, "publishableKey", null) as String val appInfo = getMapOrNull(params, "appInfo") as ReadableMap @@ -236,7 +235,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun initPaymentSheet(params: ReadableMap, promise: Promise) { getCurrentActivityOrResolveWithError(promise)?.let { activity -> this.initPaymentSheetPromise = promise @@ -252,14 +250,12 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun presentPaymentSheet(promise: Promise) { this.presentPaymentSheetPromise = promise paymentSheetFragment?.present() } @ReactMethod - @SuppressWarnings("unused") fun confirmPaymentSheetPayment(promise: Promise) { this.confirmPaymentSheetPaymentPromise = promise paymentSheetFragment?.confirmPayment() @@ -304,7 +300,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun createPaymentMethod(data: ReadableMap, options: ReadableMap, promise: Promise) { val cardParams = (cardFieldView?.cardParams ?: cardFormView?.cardParams) ?: run { promise.resolve(createError("Failed", "Card details not complete")) @@ -330,7 +325,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun createToken(params: ReadableMap, promise: Promise) { val type = getValOr(params, "type", null) if (type == null) { @@ -411,7 +405,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun createTokenForCVCUpdate(cvc: String, promise: Promise) { stripe.createCvcUpdateToken( cvc, @@ -431,7 +424,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun handleNextAction(paymentIntentClientSecret: String, promise: Promise) { paymentLauncherFragment.handleNextActionForPaymentIntent( paymentIntentClientSecret, @@ -459,7 +451,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React // } @ReactMethod - @SuppressWarnings("unused") fun confirmPayment(paymentIntentClientSecret: String, params: ReadableMap, options: ReadableMap, promise: Promise) { val paymentMethodType = getValOr(params, "type")?.let { mapToPaymentMethodType(it) } ?: run { promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), "You must provide paymentMethodType")) @@ -504,7 +495,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun retrievePaymentIntent(clientSecret: String, promise: Promise) { CoroutineScope(Dispatchers.IO).launch { val paymentIntent = stripe.retrievePaymentIntentSynchronous(clientSecret) @@ -517,7 +507,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun retrieveSetupIntent(clientSecret: String, promise: Promise) { CoroutineScope(Dispatchers.IO).launch { val setupIntent = stripe.retrieveSetupIntentSynchronous(clientSecret) @@ -530,7 +519,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun confirmSetupIntent(setupIntentClientSecret: String, params: ReadableMap, options: ReadableMap, promise: Promise) { val paymentMethodType = getValOr(params, "type")?.let { mapToPaymentMethodType(it) } ?: run { promise.resolve(createError(ConfirmPaymentErrorType.Failed.toString(), "You must provide paymentMethodType")) @@ -555,7 +543,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun isGooglePaySupported(params: ReadableMap?, promise: Promise) { val fragment = GooglePayPaymentMethodLauncherFragment( reactContext, @@ -572,7 +559,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun initGooglePay(params: ReadableMap, promise: Promise) { googlePayFragment = GooglePayFragment().also { val bundle = toBundleObject(params) @@ -589,7 +575,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun presentGooglePay(params: ReadableMap, promise: Promise) { val clientSecret = getValOr(params, "clientSecret") ?: run { promise.resolve(createError(GooglePayErrorType.Failed.toString(), "you must provide clientSecret")) @@ -608,7 +593,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun createGooglePayPaymentMethod(params: ReadableMap, promise: Promise) { val currencyCode = getValOr(params, "currencyCode", null) ?: run { promise.resolve(createError(GooglePayErrorType.Failed.toString(), "you must provide currencyCode")) @@ -623,7 +607,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun collectBankAccount(isPaymentIntent: Boolean, clientSecret: String, params: ReadableMap, promise: Promise) { val paymentMethodType = mapToPaymentMethodType(getValOr(params, "type", null)) if (paymentMethodType != PaymentMethod.Type.USBankAccount) { @@ -660,7 +643,6 @@ class StripeSdkModule(private val reactContext: ReactApplicationContext) : React } @ReactMethod - @SuppressWarnings("unused") fun verifyMicrodeposits(isPaymentIntent: Boolean, clientSecret: String, params: ReadableMap, promise: Promise) { val amounts = params.getArray("amounts") val descriptorCode = params.getString("descriptorCode")