diff --git a/CHANGELOG.md b/CHANGELOG.md index dbb5b234c..c4972a53f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +**Features** + +- Use `preferredNetworks` on `CardField`, `CardForm`, `CustomerSheet`, and `PaymentSheet` to set the list of preferred networks that should be used to process payments made with a co-branded card. +- Set the Google Pay button type that is used in PaymentSheet with the `googlePay.buttonType` parameter. + +**Fixes** + +- Fixed an issue on Android where `CardField`'s `placeholderColor` wasn't being applied to the card brand icon. + ## 0.35.1 - 2024-01-16 **Fixes** @@ -9,7 +18,7 @@ - Fixed a build error on Android when using React Native 0.73.0 and higher [#1579](https://github.com/stripe/stripe-react-native/pull/1579) - Fixed the test mock for `useStripe` [#1559](https://github.com/stripe/stripe-react-native/pull/1559) - Fixed a build error on Android that would only occur when using the `nx` build tool [#1586](https://github.com/stripe/stripe-react-native/pull/1586) - + ## 0.35.0 - 2023-11-02 **Features** diff --git a/android/gradle.properties b/android/gradle.properties index 0a1677391..1dbf4e8cc 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,3 @@ StripeSdk_kotlinVersion=1.8.0 # Keep StripeSdk_stripeVersion in sync with https://github.com/stripe/stripe-identity-react-native/blob/main/android/gradle.properties -StripeSdk_stripeVersion=20.34.+ +StripeSdk_stripeVersion=20.36.+ diff --git a/android/src/androidTest/java/com/reactnativestripesdk/paymentsheet/PaymentSheetFragmentTest.kt b/android/src/androidTest/java/com/reactnativestripesdk/paymentsheet/PaymentSheetFragmentTest.kt index a2b39d8c0..8108a6670 100644 --- a/android/src/androidTest/java/com/reactnativestripesdk/paymentsheet/PaymentSheetFragmentTest.kt +++ b/android/src/androidTest/java/com/reactnativestripesdk/paymentsheet/PaymentSheetFragmentTest.kt @@ -15,7 +15,8 @@ class PaymentSheetFragmentTest { bundleOf( "merchantCountryCode" to "US", "currencyCode" to "USD", - "testEnv" to true + "testEnv" to true, + "buttonType" to 4 ) ) Assert.assertEquals( @@ -23,7 +24,8 @@ class PaymentSheetFragmentTest { PaymentSheet.GooglePayConfiguration( environment = PaymentSheet.GooglePayConfiguration.Environment.Test, countryCode = "US", - currencyCode = "USD" + currencyCode = "USD", + buttonType = PaymentSheet.GooglePayConfiguration.ButtonType.Donate ) ) } @@ -46,7 +48,8 @@ class PaymentSheetFragmentTest { PaymentSheet.GooglePayConfiguration( environment = PaymentSheet.GooglePayConfiguration.Environment.Production, countryCode = "", - currencyCode = "" + currencyCode = "", + buttonType = PaymentSheet.GooglePayConfiguration.ButtonType.Pay ) ) } diff --git a/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt b/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt index 6b77d1545..f15d123f9 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFieldView.kt @@ -167,10 +167,11 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { private fun setCardBrandTint(color: Int) { try { - cardInputWidgetBinding.cardBrandView::class.java.getDeclaredField("tintColorInt").let { internalTintColor -> - internalTintColor.isAccessible = true - internalTintColor.set(cardInputWidgetBinding.cardBrandView, color) - } + cardInputWidgetBinding.cardBrandView::class.java + .getDeclaredMethod("setTintColorInt\$payments_core_release", Int::class.java) + .let { + it(cardInputWidgetBinding.cardBrandView, color) + } } catch (e: Exception) { Log.e( "StripeReactNative", @@ -214,6 +215,10 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { mCardWidget.isEnabled = !isDisabled } + fun setPreferredNetworks(preferredNetworks: ArrayList?) { + mCardWidget.setPreferredNetworks(mapToPreferredNetworks(preferredNetworks)) + } + /** * We can reliable assume that setPostalCodeEnabled is called before * setCountryCode because of the order of the props in CardField.tsx diff --git a/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt b/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt index 0daaa60d5..91cf98933 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFieldViewManager.kt @@ -61,6 +61,12 @@ class CardFieldViewManager : SimpleViewManager() { view.setDisabled(isDisabled) } + @ReactProp(name = "preferredNetworks") + fun setPreferredNetworks(view: CardFieldView, preferredNetworks: ReadableArray?) { + val networks = preferredNetworks?.toArrayList()?.filterIsInstance()?.let { ArrayList(it) } + view.setPreferredNetworks(networks) + } + override fun createViewInstance(reactContext: ThemedReactContext): CardFieldView { val stripeSdkModule: StripeSdkModule? = reactContext.getNativeModule(StripeSdkModule::class.java) val view = CardFieldView(reactContext) diff --git a/android/src/main/java/com/reactnativestripesdk/CardFormView.kt b/android/src/main/java/com/reactnativestripesdk/CardFormView.kt index 46fe00b23..e7a23949a 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFormView.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFormView.kt @@ -63,6 +63,10 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { cardForm.isEnabled = !isDisabled } + fun setPreferredNetworks(preferredNetworks: ArrayList?) { + cardForm.setPreferredNetworks(mapToPreferredNetworks(preferredNetworks)) + } + @SuppressLint("RestrictedApi") private fun setCountry(countryString: String?) { if (countryString != null) { diff --git a/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt b/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt index 0659f27f5..0b0141807 100644 --- a/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt +++ b/android/src/main/java/com/reactnativestripesdk/CardFormViewManager.kt @@ -61,6 +61,12 @@ class CardFormViewManager : SimpleViewManager() { view.setDisabled(isDisabled) } + @ReactProp(name = "preferredNetworks") + fun setPreferredNetworks(view: CardFormView, preferredNetworks: ReadableArray?) { + val networks = preferredNetworks?.toArrayList()?.filterIsInstance()?.let { ArrayList(it) } + view.setPreferredNetworks(networks) + } + override fun createViewInstance(reactContext: ThemedReactContext): CardFormView { val stripeSdkModule: StripeSdkModule? = reactContext.getNativeModule(StripeSdkModule::class.java) val view = CardFormView(reactContext) diff --git a/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt b/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt index b6da5681e..75fabe128 100644 --- a/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt @@ -194,7 +194,8 @@ class PaymentSheetFragment( appearance = appearance, shippingDetails = shippingDetails, primaryButtonLabel = primaryButtonLabel, - billingDetailsCollectionConfiguration = billingDetailsConfig + billingDetailsCollectionConfiguration = billingDetailsConfig, + preferredNetworks = mapToPreferredNetworks(arguments?.getIntegerArrayList("preferredNetworks")) ) if (arguments?.getBoolean("customFlow") == true) { @@ -342,6 +343,17 @@ class PaymentSheetFragment( companion object { internal const val TAG = "payment_sheet_launch_fragment" + private val mapIntToButtonType = mapOf( + 1 to PaymentSheet.GooglePayConfiguration.ButtonType.Buy, + 6 to PaymentSheet.GooglePayConfiguration.ButtonType.Book, + 5 to PaymentSheet.GooglePayConfiguration.ButtonType.Checkout, + 4 to PaymentSheet.GooglePayConfiguration.ButtonType.Donate, + 11 to PaymentSheet.GooglePayConfiguration.ButtonType.Order, + 1000 to PaymentSheet.GooglePayConfiguration.ButtonType.Pay, + 7 to PaymentSheet.GooglePayConfiguration.ButtonType.Subscribe, + 1001 to PaymentSheet.GooglePayConfiguration.ButtonType.Plain, + ) + internal fun createMissingInitError(): WritableMap { return createError(PaymentSheetErrorType.Failed.toString(), "No payment sheet has been initialized yet. You must call `initPaymentSheet` before `presentPaymentSheet`.") } @@ -356,13 +368,16 @@ class PaymentSheetFragment( val testEnv = params.getBoolean("testEnv") val amount = params.getString("amount")?.toLongOrNull() val label = params.getString("label") + val buttonType = mapIntToButtonType.get(params.getInt("buttonType")) ?: PaymentSheet.GooglePayConfiguration.ButtonType.Pay + return PaymentSheet.GooglePayConfiguration( environment = if (testEnv) PaymentSheet.GooglePayConfiguration.Environment.Test else PaymentSheet.GooglePayConfiguration.Environment.Production, countryCode = countryCode, currencyCode = currencyCode, amount = amount, - label = label + label = label, + buttonType = buttonType ) } diff --git a/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt b/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt index f70fb99e7..78ea3402f 100644 --- a/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt +++ b/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt @@ -84,11 +84,11 @@ class CustomerSheetFragment : Fragment() { return } - val configuration = CustomerSheet.Configuration.builder() + val configuration = CustomerSheet.Configuration.builder(merchantDisplayName ?: "") .appearance(appearance) .googlePayEnabled(googlePayEnabled) - .merchantDisplayName(merchantDisplayName) .headerTextForSelectionScreen(headerTextForSelectionScreen) + .preferredNetworks(mapToPreferredNetworks(arguments?.getIntegerArrayList("preferredNetworks"))) billingDetailsBundle?.let { configuration.defaultBillingDetails(createDefaultBillingDetails(billingDetailsBundle)) diff --git a/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt b/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt index 05e77e01f..6b91a18ca 100644 --- a/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +++ b/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt @@ -912,7 +912,7 @@ internal fun mapFromShippingContact(googlePayResult: GooglePayResult): WritableM googlePayResult.shippingInformation?.phone?.let { map.putString("phoneNumber", it) } ?: run { - map.putString("phoneNumber", googlePayResult?.phoneNumber) + map.putString("phoneNumber", googlePayResult.phoneNumber) } val postalAddress = WritableNativeMap() postalAddress.putString("city", googlePayResult.shippingInformation?.address?.city) @@ -929,3 +929,25 @@ internal fun mapFromShippingContact(googlePayResult: GooglePayResult): WritableM map.putMap("postalAddress", postalAddress) return map } + +internal fun mapToPreferredNetworks(networksAsInts: ArrayList?): List { + if (networksAsInts == null) { + return emptyList() + } + + val intToCardBrand = mapOf( + 0 to CardBrand.JCB, + 1 to CardBrand.AmericanExpress, + 2 to CardBrand.CartesBancaires, + 3 to CardBrand.DinersClub, + 4 to CardBrand.Discover, + 5 to CardBrand.MasterCard, + 6 to CardBrand.UnionPay, + 7 to CardBrand.Visa, + 8 to CardBrand.Unknown, + ) + + return networksAsInts.mapNotNull { + intToCardBrand[it] + } +} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 89bc320cc..0dd8d687f 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -364,50 +364,50 @@ PODS: - React-Core - React-RCTImage - SocketRocket (0.6.0) - - Stripe (23.18.0): - - StripeApplePay (= 23.18.0) - - StripeCore (= 23.18.0) - - StripePayments (= 23.18.0) - - StripePaymentsUI (= 23.18.0) - - StripeUICore (= 23.18.0) - - stripe-react-native (0.35.0): + - Stripe (23.21.1): + - StripeApplePay (= 23.21.1) + - StripeCore (= 23.21.1) + - StripePayments (= 23.21.1) + - StripePaymentsUI (= 23.21.1) + - StripeUICore (= 23.21.1) + - stripe-react-native (0.35.1): - React-Core - - Stripe (~> 23.18.0) - - StripeApplePay (~> 23.18.0) - - StripeFinancialConnections (~> 23.18.0) - - StripePayments (~> 23.18.0) - - StripePaymentSheet (~> 23.18.0) - - StripePaymentsUI (~> 23.18.0) - - stripe-react-native/Tests (0.35.0): + - Stripe (~> 23.21.0) + - StripeApplePay (~> 23.21.0) + - StripeFinancialConnections (~> 23.21.0) + - StripePayments (~> 23.21.0) + - StripePaymentSheet (~> 23.21.0) + - StripePaymentsUI (~> 23.21.0) + - stripe-react-native/Tests (0.35.1): - React-Core - - Stripe (~> 23.18.0) - - StripeApplePay (~> 23.18.0) - - StripeFinancialConnections (~> 23.18.0) - - StripePayments (~> 23.18.0) - - StripePaymentSheet (~> 23.18.0) - - StripePaymentsUI (~> 23.18.0) - - StripeApplePay (23.18.0): - - StripeCore (= 23.18.0) - - StripeCore (23.18.0) - - StripeFinancialConnections (23.18.0): - - StripeCore (= 23.18.0) - - StripeUICore (= 23.18.0) - - StripePayments (23.18.0): - - StripeCore (= 23.18.0) - - StripePayments/Stripe3DS2 (= 23.18.0) - - StripePayments/Stripe3DS2 (23.18.0): - - StripeCore (= 23.18.0) - - StripePaymentSheet (23.18.0): - - StripeApplePay (= 23.18.0) - - StripeCore (= 23.18.0) - - StripePayments (= 23.18.0) - - StripePaymentsUI (= 23.18.0) - - StripePaymentsUI (23.18.0): - - StripeCore (= 23.18.0) - - StripePayments (= 23.18.0) - - StripeUICore (= 23.18.0) - - StripeUICore (23.18.0): - - StripeCore (= 23.18.0) + - Stripe (~> 23.21.0) + - StripeApplePay (~> 23.21.0) + - StripeFinancialConnections (~> 23.21.0) + - StripePayments (~> 23.21.0) + - StripePaymentSheet (~> 23.21.0) + - StripePaymentsUI (~> 23.21.0) + - StripeApplePay (23.21.1): + - StripeCore (= 23.21.1) + - StripeCore (23.21.1) + - StripeFinancialConnections (23.21.1): + - StripeCore (= 23.21.1) + - StripeUICore (= 23.21.1) + - StripePayments (23.21.1): + - StripeCore (= 23.21.1) + - StripePayments/Stripe3DS2 (= 23.21.1) + - StripePayments/Stripe3DS2 (23.21.1): + - StripeCore (= 23.21.1) + - StripePaymentSheet (23.21.1): + - StripeApplePay (= 23.21.1) + - StripeCore (= 23.21.1) + - StripePayments (= 23.21.1) + - StripePaymentsUI (= 23.21.1) + - StripePaymentsUI (23.21.1): + - StripeCore (= 23.21.1) + - StripePayments (= 23.21.1) + - StripeUICore (= 23.21.1) + - StripeUICore (23.21.1): + - StripeCore (= 23.21.1) - Yoga (1.14.0) - YogaKit (1.18.1): - Yoga (~> 1.14) @@ -628,15 +628,15 @@ SPEC CHECKSUMS: RNCPicker: 0bf8ef8f7800524f32d2bb2a8bcadd53eda0ecd1 RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 - Stripe: 4092bc51f41ca1758166aef921aa0dd2f0fbc639 - stripe-react-native: 2e2dad1734c1c2b71564b66bae4395bfd8419af4 - StripeApplePay: aedbcb53f5324d527a52a5888bd0eeee25b3ca36 - StripeCore: f86db23fb3f984808e6f5d3876397b953bf58a52 - StripeFinancialConnections: 0aaddb3593a7cc76b5f01eab185f16ef60798b15 - StripePayments: 050a2c5e2cc6f9492d80fddbc9bb3e7d00ddb0c8 - StripePaymentSheet: 7a1e68dacbd280bb67ff299a9f9e3337710bbcaf - StripePaymentsUI: cec7249a59f0031050cb08e1bf27cad30654d73c - StripeUICore: 1b23b5c211091c3675f1cc446df9c14f8836630a + Stripe: d55e7338dabc599eddeee3b09eacd5c13457e1a2 + stripe-react-native: 7ce5a0451633125e69dae95dd601e6f678ff447e + StripeApplePay: 48973391091a96e30e6ef6b9b331dbaa31449dcb + StripeCore: 2ad2bf779ed5b34c1af0800d8304222d3a98440f + StripeFinancialConnections: d75c000ffe2f09b14f9aebf02a571d91658ae73b + StripePayments: 9961532054e6d4176f54d681cab876cefe60de14 + StripePaymentSheet: 2ef84d80a938e8c6c59ba0639007b4108d0a6bfb + StripePaymentsUI: 1f8addf41697d0f298fd8a15e4edf3999fbc66bc + StripeUICore: 95c5235db01a2d90b5bb72c85a804eaed4e64f17 Yoga: 7a4d48cfb35dfa542151e615fa73c1a0d88caf21 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a diff --git a/example/src/screens/PaymentsUICompleteScreen.tsx b/example/src/screens/PaymentsUICompleteScreen.tsx index a402b4243..00956c149 100644 --- a/example/src/screens/PaymentsUICompleteScreen.tsx +++ b/example/src/screens/PaymentsUICompleteScreen.tsx @@ -8,6 +8,7 @@ import { PaymentSheetError, AddressSheet, AddressSheetError, + CardBrand, } from '@stripe/stripe-react-native'; import Button from '../components/Button'; import PaymentScreen from '../components/PaymentScreen'; @@ -111,6 +112,7 @@ export default function PaymentsUICompleteScreen() { appearance, primaryButtonLabel: 'purchase!', removeSavedPaymentMethodMessage: 'remove this payment method?', + preferredNetworks: [CardBrand.Amex, CardBrand.Visa], }); if (!error) { setPaymentSheetEnabled(true); diff --git a/example/src/screens/WebhookPaymentScreen.tsx b/example/src/screens/WebhookPaymentScreen.tsx index ac1d0353c..3efe1241e 100644 --- a/example/src/screens/WebhookPaymentScreen.tsx +++ b/example/src/screens/WebhookPaymentScreen.tsx @@ -4,7 +4,11 @@ import type { } from '@stripe/stripe-react-native'; import React, { useState } from 'react'; import { Alert, StyleSheet, Text, TextInput, View, Switch } from 'react-native'; -import { CardField, useConfirmPayment } from '@stripe/stripe-react-native'; +import { + CardBrand, + CardField, + useConfirmPayment, +} from '@stripe/stripe-react-native'; import Button from '../components/Button'; import PaymentScreen from '../components/PaymentScreen'; import { API_URL } from '../Config'; @@ -108,6 +112,7 @@ export default function WebhookPaymentScreen() { }} cardStyle={inputStyles} style={styles.cardField} + preferredNetworks={[CardBrand.Amex]} /> ? { + didSet { + if let preferredNetworks = preferredNetworks { + cardField.preferredNetworks = preferredNetworks.map(Mappers.intToCardBrand).compactMap { $0 } + } + } + } + @objc var placeholders: NSDictionary = NSDictionary() { didSet { if let numberPlaceholder = placeholders["number"] as? String { diff --git a/ios/CardFormManager.m b/ios/CardFormManager.m index 6eadf6be3..0ca31cda3 100644 --- a/ios/CardFormManager.m +++ b/ios/CardFormManager.m @@ -8,6 +8,7 @@ @interface RCT_EXTERN_MODULE(CardFormManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(autofocus, BOOL) RCT_EXPORT_VIEW_PROPERTY(cardStyle, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(disabled, BOOL) +RCT_EXPORT_VIEW_PROPERTY(preferredNetworks, NSArray) RCT_EXTERN_METHOD(focus:(nonnull NSNumber*) reactTag) RCT_EXTERN_METHOD(blur:(nonnull NSNumber*) reactTag) @end diff --git a/ios/CardFormView.swift b/ios/CardFormView.swift index 5d68322fb..49cff21ea 100644 --- a/ios/CardFormView.swift +++ b/ios/CardFormView.swift @@ -11,6 +11,13 @@ class CardFormView: UIView, STPCardFormViewDelegate { @objc var onFormComplete: RCTDirectEventBlock? @objc var autofocus: Bool = false @objc var disabled: Bool = false + @objc var preferredNetworks: Array? { + didSet { + if let preferredNetworks = preferredNetworks { + cardForm?.preferredNetworks = preferredNetworks.map(Mappers.intToCardBrand).compactMap { $0 } + } + } + } override func didSetProps(_ changedProps: [String]!) { if let cardForm = self.cardForm { diff --git a/ios/CustomerSheet/CustomerSheetUtils.swift b/ios/CustomerSheet/CustomerSheetUtils.swift index 2e72d2d3b..d764cd9d0 100644 --- a/ios/CustomerSheet/CustomerSheetUtils.swift +++ b/ios/CustomerSheet/CustomerSheetUtils.swift @@ -18,7 +18,8 @@ class CustomerSheetUtils { applePayEnabled: Bool?, merchantDisplayName: String?, billingDetailsCollectionConfiguration: NSDictionary?, - defaultBillingDetails: NSDictionary? + defaultBillingDetails: NSDictionary?, + preferredNetworks: Array? ) -> CustomerSheet.Configuration { var config = CustomerSheet.Configuration() config.appearance = appearance @@ -30,6 +31,9 @@ class CustomerSheetUtils { if let merchantDisplayName = merchantDisplayName { config.merchantDisplayName = merchantDisplayName } + if let preferredNetworks = preferredNetworks { + config.preferredNetworks = preferredNetworks.map(Mappers.intToCardBrand).compactMap { $0 } + } if let billingConfigParams = billingDetailsCollectionConfiguration { config.billingDetailsCollectionConfiguration.name = StripeSdk.mapToCollectionMode(str: billingConfigParams["name"] as? String) config.billingDetailsCollectionConfiguration.phone = StripeSdk.mapToCollectionMode(str: billingConfigParams["phone"] as? String) diff --git a/ios/Mappers.swift b/ios/Mappers.swift index 0f6218419..1909f776c 100644 --- a/ios/Mappers.swift +++ b/ios/Mappers.swift @@ -1025,4 +1025,29 @@ class Mappers { default: return STPPaymentMethodUSBankAccountType.checking } } + + class func intToCardBrand(int: Int) -> STPCardBrand? { + switch int { + case 0: + return STPCardBrand.JCB + case 1: + return STPCardBrand.amex + case 2: + return STPCardBrand.cartesBancaires + case 3: + return STPCardBrand.dinersClub + case 4: + return STPCardBrand.discover + case 5: + return STPCardBrand.mastercard + case 6: + return STPCardBrand.unionPay + case 7: + return STPCardBrand.visa + case 8: + return STPCardBrand.unknown + default: + return nil + } + } } diff --git a/ios/StripeSdk+CustomerSheet.swift b/ios/StripeSdk+CustomerSheet.swift index 2afc2d1ff..a616c9801 100644 --- a/ios/StripeSdk+CustomerSheet.swift +++ b/ios/StripeSdk+CustomerSheet.swift @@ -22,7 +22,9 @@ extension StripeSdk { applePayEnabled: params["applePayEnabled"] as? Bool, merchantDisplayName: params["merchantDisplayName"] as? String, billingDetailsCollectionConfiguration: params["billingDetailsCollectionConfiguration"] as? NSDictionary, - defaultBillingDetails: params["defaultBillingDetails"] as? NSDictionary) + defaultBillingDetails: params["defaultBillingDetails"] as? NSDictionary, + preferredNetworks: params["preferredNetworks"] as? Array + ) } catch { resolve( Errors.createError(ErrorType.Failed, error.localizedDescription) diff --git a/ios/StripeSdk+PaymentSheet.swift b/ios/StripeSdk+PaymentSheet.swift index b7dd3b1ff..c39247aef 100644 --- a/ios/StripeSdk+PaymentSheet.swift +++ b/ios/StripeSdk+PaymentSheet.swift @@ -99,6 +99,10 @@ extension StripeSdk { } } + if let preferredNetworksAsInts = params["preferredNetworks"] as? Array { + configuration.preferredNetworks = preferredNetworksAsInts.map(Mappers.intToCardBrand).compactMap { $0 } + } + return (nil, configuration) } diff --git a/src/components/CardField.tsx b/src/components/CardField.tsx index a9de75d74..61bc5e2d5 100644 --- a/src/components/CardField.tsx +++ b/src/components/CardField.tsx @@ -1,4 +1,4 @@ -import type { CardFieldInput } from '../types'; +import type { CardFieldInput, CardBrand } from '../types'; import React, { forwardRef, useCallback, @@ -43,6 +43,9 @@ export interface Props extends AccessibilityProps { onBlur?(): void; onFocus?(focusedField: CardFieldInput.FieldName | null): void; testID?: string; + /** The list of preferred networks that should be used to process payments made with a co-branded card. + * This value will only be used if your user hasn't selected a network themselves. */ + preferredNetworks?: Array; /** * WARNING: If set to `true` the full card number will be returned in the `onCardChange` handler. * Only do this if you're certain that you fulfill the necessary PCI compliance requirements. diff --git a/src/components/CardForm.tsx b/src/components/CardForm.tsx index cf5c7da62..a26ac5b3a 100644 --- a/src/components/CardForm.tsx +++ b/src/components/CardForm.tsx @@ -1,4 +1,4 @@ -import type { CardFormView } from '../types'; +import type { CardFormView, CardBrand } from '../types'; import React, { forwardRef, useCallback, @@ -36,7 +36,9 @@ export interface Props extends AccessibilityProps { disabled?: boolean; /** All styles except backgroundColor, cursorColor, borderColor, and borderRadius are Android only */ cardStyle?: CardFormView.Styles; - + /** The list of preferred networks that should be used to process payments made with a co-branded card. + * This value will only be used if your user hasn't selected a network themselves. */ + preferredNetworks?: Array; // TODO: will make it public when iOS SDK allows for this // postalCodeEnabled?: boolean; diff --git a/src/types/Common.ts b/src/types/Common.ts index f124d40b2..efb08b65b 100644 --- a/src/types/Common.ts +++ b/src/types/Common.ts @@ -26,3 +26,15 @@ export type AddressDetails = { * */ isCheckboxSelected?: boolean; }; + +export enum CardBrand { + JCB = 0, + Amex = 1, + CartesBancaires = 2, + DinersClub = 3, + Discover = 4, + Mastercard = 5, + UnionPay = 6, + Visa = 7, + Unknown = 8, +} diff --git a/src/types/CustomerSheet.ts b/src/types/CustomerSheet.ts index 65c87d0b5..6aead6571 100644 --- a/src/types/CustomerSheet.ts +++ b/src/types/CustomerSheet.ts @@ -4,6 +4,7 @@ import type { CustomerSheetError, BillingDetails, PaymentMethod, + CardBrand, } from '../types'; export type CustomerSheetInitParams = { @@ -33,6 +34,9 @@ export type CustomerSheetInitParams = { applePayEnabled?: boolean; /** Whether to show Google Pay as an option. Defaults to false. */ googlePayEnabled?: boolean; + /** The list of preferred networks that should be used to process payments made with a co-branded card. + * This value will only be used if your user hasn't selected a network themselves. */ + preferredNetworks?: Array; /** Optional override. It is generally recommended to rely on the default behavior, but- provide a CustomerAdapter here if * you would prefer retrieving and updating your Stripe customer object via your own backend instead. * WARNING: When implementing your own CustomerAdapter, ensure your application complies with all applicable laws and regulations, including data privacy and consumer protection. diff --git a/src/types/PaymentSheet.ts b/src/types/PaymentSheet.ts index 878388128..f155c052e 100644 --- a/src/types/PaymentSheet.ts +++ b/src/types/PaymentSheet.ts @@ -1,4 +1,4 @@ -import type { BillingDetails, AddressDetails } from './Common'; +import type { BillingDetails, AddressDetails, CardBrand } from './Common'; import type { CartSummaryItem } from './ApplePay'; import type { ButtonType, @@ -52,6 +52,9 @@ export type SetupParams = IntentParams & { primaryButtonLabel?: string; /** Optional configuration to display a custom message when a saved payment method is removed. iOS only. */ removeSavedPaymentMethodMessage?: string; + /** The list of preferred networks that should be used to process payments made with a co-branded card. + * This value will only be used if your user hasn't selected a network themselves. */ + preferredNetworks?: Array; }; export type IntentParams = @@ -110,6 +113,10 @@ export type GooglePayParams = { label?: string; /** An optional amount to display for setup intents. Google Pay may or may not display this amount depending on its own internal logic. Defaults to 0 if none is provided. */ amount?: string; + /** The Google Pay button type to use. Set to "Pay" by default. See + * [Google's documentation](https://developers.google.com/android/reference/com/google/android/gms/wallet/Wallet.WalletOptions#environment) + * for more information on button types. */ + buttonType?: ButtonType; }; /** diff --git a/src/types/index.ts b/src/types/index.ts index 47b7287e4..6cb711239 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -45,6 +45,7 @@ export * from './PushProvisioning'; export * from './Errors'; export * from './CustomerSheet'; export type { Address, BillingDetails, AddressDetails } from './Common'; +export { CardBrand } from './Common'; /** * @ignore diff --git a/stripe-react-native.podspec b/stripe-react-native.podspec index 385ae50fc..a54c8530c 100644 --- a/stripe-react-native.podspec +++ b/stripe-react-native.podspec @@ -2,7 +2,7 @@ require 'json' package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) # Keep stripe_version in sync with https://github.com/stripe/stripe-identity-react-native/blob/main/stripe-identity-react-native.podspec -stripe_version = '~> 23.18.0' +stripe_version = '~> 23.21.0' Pod::Spec.new do |s| s.name = 'stripe-react-native'