From 7866c4dbc0d9de177d8fc81a1380249b513dfe5e Mon Sep 17 00:00:00 2001 From: jonasbark Date: Tue, 15 Nov 2022 06:08:39 -0600 Subject: [PATCH] Sync (#1015) * Android: Sync with React Native v0.20.0 * Sync iOS with React Native v0.20.0 * resetPaymentSheetCustomer for iOS * fix integration tests * add missing type * increase timeout for github action * sync dart layer * BREAKING add onDidSetShippingContact to presentapplepay * add onDidSetShippingMethod callback for handling shipping methods * chore: add changelog, increase version Co-authored-by: Remon --- .github/workflows/all_plugins.yaml | 2 +- example/android/app/build.gradle | 5 +- .../com/flutter/stripe/example/BridgeTest.kt | 2 +- example/android/build.gradle | 2 +- example/ios/Podfile | 2 +- .../lib/screens/wallets/apple_pay_screen.dart | 30 +- packages/stripe/CHANGELOG.md | 15 + packages/stripe/lib/src/stripe.dart | 28 +- packages/stripe/pubspec.yaml | 8 +- packages/stripe_android/CHANGELOG.md | 21 +- packages/stripe_android/android/build.gradle | 4 +- .../facebook/react/bridge/ReadableArray.java | 8 +- .../react/uimanager/DisplayMetricsHolder.java | 89 ++ .../facebook/react/uimanager/PixelUtil.java | 58 ++ .../uimanager/ReactBaseTextShadowNode.java | 5 + .../react/views/text/ReactFontManager.java | 179 ++++ .../react/views/text/ReactTypefaceUtils.java | 169 ++++ .../react/views/text/TypefaceStyle.java | 72 ++ .../com/flutter/stripe/StripeAndroidPlugin.kt | 6 + .../stripe/StripeSdkModuleExtensions.kt | 3 +- .../com/reactnativestripesdk/CardFieldView.kt | 10 +- .../com/reactnativestripesdk/CardFormView.kt | 30 +- .../kotlin/com/reactnativestripesdk/Errors.kt | 113 --- .../com/reactnativestripesdk/Extensions.kt | 21 - .../FinancialConnectionsSheetFragment.kt | 1 + .../PaymentLauncherFragment.kt | 1 + .../PaymentSheetFragment.kt | 4 +- .../reactnativestripesdk/StripeSdkModule.kt | 7 + .../pushprovisioning/AddToWalletButtonView.kt | 1 - .../com/reactnativestripesdk/utils/Mappers.kt | 2 + packages/stripe_android/pubspec.yaml | 2 +- packages/stripe_ios/CHANGELOG.md | 23 +- .../ios/Classes/CardFieldFactory.swift | 16 +- .../Classes/Stripe Sdk/ApplePayUtils.swift | 2 +- .../Classes/Stripe Sdk/CardFieldView.swift | 6 +- .../Stripe Sdk/FinancialConnections.swift | 45 +- .../ios/Classes/Stripe Sdk/Mappers.swift | 12 +- .../Stripe Sdk/PaymentMethodFactory.swift | 13 +- .../Stripe Sdk/PaymentSheetAppearance.swift | 10 +- .../ios/Classes/Stripe Sdk/StripeSdk.swift | 96 +- .../stripe_ios/ios/Classes/StripePlugin.swift | 7 + packages/stripe_ios/ios/stripe_ios.podspec | 11 +- packages/stripe_ios/pubspec.yaml | 2 +- .../stripe_platform_interface/CHANGELOG.md | 21 +- .../lib/src/method_channel_stripe.dart | 51 +- .../lib/src/models/apple_pay.dart | 67 +- .../lib/src/models/apple_pay.freezed.dart | 838 +++++++++++++++++- .../lib/src/models/apple_pay.g.dart | 77 +- .../lib/src/models/payment_methods.dart | 6 + .../src/models/payment_methods.freezed.dart | 79 +- .../lib/src/models/payment_methods.g.dart | 6 + .../lib/src/stripe_platform_interface.dart | 9 +- .../stripe_platform_interface/pubspec.yaml | 2 +- .../test/method_channel_stripe_test.dart | 29 +- packages/stripe_web/lib/src/web_stripe.dart | 11 +- packages/stripe_web/pubspec.yaml | 2 +- 56 files changed, 2041 insertions(+), 300 deletions(-) create mode 100644 packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/DisplayMetricsHolder.java create mode 100644 packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/PixelUtil.java create mode 100644 packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/ReactBaseTextShadowNode.java create mode 100644 packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/ReactFontManager.java create mode 100644 packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/ReactTypefaceUtils.java create mode 100644 packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/TypefaceStyle.java delete mode 100644 packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/Errors.kt delete mode 100644 packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/Extensions.kt diff --git a/.github/workflows/all_plugins.yaml b/.github/workflows/all_plugins.yaml index 6de6e4d9d..033328206 100644 --- a/.github/workflows/all_plugins.yaml +++ b/.github/workflows/all_plugins.yaml @@ -75,7 +75,7 @@ jobs: integration_test: name: Integration test - timeout-minutes: 30 + timeout-minutes: 60 runs-on: macos-latest steps: - uses: actions/checkout@v2 diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 3aa5877b3..c2ff6f1f5 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -59,7 +59,6 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:6.7.0' implementation 'com.google.android.gms:play-services-wallet:19.1.0' - implementation 'com.stripe:stripe-wechatpay:17.1.0' - androidTestImplementation 'androidx.test:runner:1.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/example/android/app/src/androidTest/java/com/flutter/stripe/example/BridgeTest.kt b/example/android/app/src/androidTest/java/com/flutter/stripe/example/BridgeTest.kt index 49588e74c..349a85e02 100644 --- a/example/android/app/src/androidTest/java/com/flutter/stripe/example/BridgeTest.kt +++ b/example/android/app/src/androidTest/java/com/flutter/stripe/example/BridgeTest.kt @@ -6,7 +6,7 @@ class BridgeTest { @Test fun testGetDouble() { val map = ReadableMap(mapOf("test" to 1.1)) - assert(map.getDouble("test") == 1.1) + assert(map.getDouble("test") == 1.1f) } @Test fun testGetIntShouldFail() { diff --git a/example/android/build.gradle b/example/android/build.gradle index d4af3565b..68b05e9cc 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:7.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/example/ios/Podfile b/example/ios/Podfile index 164df5349..3e44f9c6f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/lib/screens/wallets/apple_pay_screen.dart b/example/lib/screens/wallets/apple_pay_screen.dart index 56d43d4e9..b50650c27 100644 --- a/example/lib/screens/wallets/apple_pay_screen.dart +++ b/example/lib/screens/wallets/apple_pay_screen.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_stripe/flutter_stripe.dart'; import 'package:http/http.dart' as http; @@ -50,16 +51,43 @@ class _ApplePayScreenState extends State { try { // 1. Present Apple Pay sheet await Stripe.instance.presentApplePay( - ApplePayPresentParams( + params: ApplePayPresentParams( cartItems: [ ApplePayCartSummaryItem.immediate( label: 'Product Test', amount: '0.01', ), ], + requiredShippingAddressFields: [ + ApplePayContactFieldsType.postalAddress, + ], + shippingMethods: [ + ApplePayShippingMethod( + identifier: 'free', + detail: 'Arrives by July 2', + label: 'Free Shipping', + amount: '0.0', + ), + ApplePayShippingMethod( + identifier: 'standard', + detail: 'Arrives by June 29', + label: 'Standard Shipping', + amount: '3.21', + ), + ], country: 'Es', currency: 'EUR', ), + onDidSetShippingContact: (contact) { + if (kDebugMode) { + print('shipping contact provided $contact '); + } + }, + onDidSetShippingMethod: (method) { + if (kDebugMode) { + print('shipping method provided $method '); + } + }, ); // 2. fetch Intent Client Secret from backend diff --git a/packages/stripe/CHANGELOG.md b/packages/stripe/CHANGELOG.md index 9000a6090..c90e622af 100644 --- a/packages/stripe/CHANGELOG.md +++ b/packages/stripe/CHANGELOG.md @@ -1,3 +1,18 @@ +## 7.0.0 +**Breaking Changes** +- This library now supports iOS 13 and up, due to stripe-ios increasing the deployment target + +**Features** +- Added Link support in Payment Sheet. +- Added the resetPaymentSheetCustomer method to clear persisted authentication state in the PaymentSheet. +- Added preferredNetwork and availableNetworks fields to the CardResult payment method. +- Added support for custom fonts to CardForm and CardView on Android. +- Added support for customizing the call to action button label in Payment Sheet by providing the primaryButtonLabel property to initPaymentSheet(). + +- **Fixes** +- Several fixes by the Stripe sdk [v.0.20.0](https://github.com/stripe/stripe-react-native/releases/tag/v0.20.0). + + ## 6.0.0 **Breaking Changes** diff --git a/packages/stripe/lib/src/stripe.dart b/packages/stripe/lib/src/stripe.dart index bc2156f99..d9767cfd4 100644 --- a/packages/stripe/lib/src/stripe.dart +++ b/packages/stripe/lib/src/stripe.dart @@ -208,16 +208,22 @@ class Stripe { /// configuration. See [ApplePayPresentParams] for more details. /// /// Throws an [StripeError] in case presenting the payment sheet fails. - Future presentApplePay( - ApplePayPresentParams params, - ) async { + Future presentApplePay({ + required ApplePayPresentParams params, + OnDidSetShippingContact? onDidSetShippingContact, + OnDidSetShippingMethod? onDidSetShippingMethod, + }) async { await _awaitForSettings(); if (!isApplePaySupported.value) { //throw StripeError //(ApplePayError.canceled, 'APPLE_PAY_NOT_SUPPORTED_MESSAGE'); } try { - await _platform.presentApplePay(params); + await _platform.presentApplePay( + params, + onDidSetShippingContact, + onDidSetShippingMethod, + ); } on StripeError { rethrow; } @@ -356,14 +362,20 @@ class Stripe { /// See [PresentPaymentSheetPameters] for more details /// /// throws [StripeException] in case of a failure - Future presentPaymentSheet({ - @Deprecated('Params are now inherited from initPaymentSheet so this `parameters` can be removed') - dynamic parameters, - }) async { + Future presentPaymentSheet() async { await _awaitForSettings(); return await _platform.presentPaymentSheet(); } + /// Call this method when the user logs out from your app. + /// + /// This will ensur ethat any persisted authentication state in the + /// paymentsheet, such as authentication cookies are cleared during logout. + Future resetPaymentSheetCustomer() async { + await _awaitForSettings(); + return await _platform.resetPaymentSheetCustomer(); + } + /// Confirms the paymentsheet payment /// /// throws [StripeException] in case of a failure diff --git a/packages/stripe/pubspec.yaml b/packages/stripe/pubspec.yaml index 8ae67494e..71fb263ae 100644 --- a/packages/stripe/pubspec.yaml +++ b/packages/stripe/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_stripe description: Flutter library for Stripe. Supports PaymentSheets, Apple & Google Pay, SCA, PSD2 and much more. -version: 6.0.0 +version: 7.0.0 homepage: https://github.com/flutter-stripe/flutter_stripe repository: https://github.com/flutter-stripe/flutter_stripe @@ -21,9 +21,9 @@ flutter: dependencies: flutter: sdk: flutter - stripe_android: ^6.0.0 - stripe_ios: ^6.0.0 - stripe_platform_interface: ^6.0.0 + stripe_android: ^7.0.0 + stripe_ios: ^7.0.0 + stripe_platform_interface: ^7.0.0 dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/stripe_android/CHANGELOG.md b/packages/stripe_android/CHANGELOG.md index 172695354..c90e622af 100644 --- a/packages/stripe_android/CHANGELOG.md +++ b/packages/stripe_android/CHANGELOG.md @@ -1,6 +1,21 @@ +## 7.0.0 +**Breaking Changes** +- This library now supports iOS 13 and up, due to stripe-ios increasing the deployment target + +**Features** +- Added Link support in Payment Sheet. +- Added the resetPaymentSheetCustomer method to clear persisted authentication state in the PaymentSheet. +- Added preferredNetwork and availableNetworks fields to the CardResult payment method. +- Added support for custom fonts to CardForm and CardView on Android. +- Added support for customizing the call to action button label in Payment Sheet by providing the primaryButtonLabel property to initPaymentSheet(). + +- **Fixes** +- Several fixes by the Stripe sdk [v.0.20.0](https://github.com/stripe/stripe-react-native/releases/tag/v0.20.0). + + ## 6.0.0 -** Breaking Changes ** +**Breaking Changes** - Move `PaymentMethodOptions` out of `PaymentMethodparams` so interface is similar with Stripe sdk. @@ -18,6 +33,7 @@ await Stripe.instance.confirmPayment( ), ), ); +``` Now @@ -42,6 +58,9 @@ await Stripe.instance.confirmPayment( - Several fixes by the Stripe sdk [v.0.19.0](https://github.com/stripe/stripe-react-native/releases/tag/v0.19.0). - Fix for #462, added `handleURLCallback` method for iOS to handle `returnUrl` when iDeal payment is successful. This will close the in-app webview of Safari. +## 5.0.1 + - Fix for #462, added `handleURLCallback` method for iOS to handle `returnUrl` when iDeal payment is successful. This will close the in-app webview of Safari + ## 5.0.0 Breaking changes - Your compileSdkVersion (in android/build.gradle) now must be at least 32. Changing your compileSdkVersion does not change runtime behavior. diff --git a/packages/stripe_android/android/build.gradle b/packages/stripe_android/android/build.gradle index 8d9cbafc5..b1221e1e8 100644 --- a/packages/stripe_android/android/build.gradle +++ b/packages/stripe_android/android/build.gradle @@ -41,8 +41,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" - implementation 'com.stripe:stripe-android:20.12.+' - implementation "com.stripe:financial-connections:20.12.+" + implementation 'com.stripe:stripe-android:20.15.+' + implementation "com.stripe:financial-connections:20.15.+" implementation 'com.google.android.material:material:1.6.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' diff --git a/packages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReadableArray.java b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReadableArray.java index d9689ced7..7e71ff2fa 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReadableArray.java +++ b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/bridge/ReadableArray.java @@ -18,8 +18,12 @@ public ReadableArray(JSONArray array) { this.array = array; } - public String getString(int index) throws JSONException { - return array.getString(index); + public String getString(int index) { + try { + return array.getString(index); + } catch (JSONException e) { + return null; + } } public int getInt(int index) throws JSONException { diff --git a/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/DisplayMetricsHolder.java b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/DisplayMetricsHolder.java new file mode 100644 index 000000000..700a4fe3a --- /dev/null +++ b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/DisplayMetricsHolder.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.WindowManager; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeMap; + +/** + * Holds an instance of the current DisplayMetrics so we don't have to thread it through all the + * classes that need it. + */ +public class DisplayMetricsHolder { + + private static @Nullable DisplayMetrics sWindowDisplayMetrics; + private static @Nullable DisplayMetrics sScreenDisplayMetrics; + + public static void setWindowDisplayMetrics(DisplayMetrics displayMetrics) { + sWindowDisplayMetrics = displayMetrics; + } + + public static void initDisplayMetricsIfNotInitialized(Context context) { + if (DisplayMetricsHolder.getScreenDisplayMetrics() != null) { + return; + } + initDisplayMetrics(context); + } + + public static void initDisplayMetrics(Context context) { + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics); + + DisplayMetrics screenDisplayMetrics = new DisplayMetrics(); + screenDisplayMetrics.setTo(displayMetrics); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + + // Get the real display metrics if we are using API level 17 or higher. + // The real metrics include system decor elements (e.g. soft menu bar). + // + // See: + // http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics) + display.getRealMetrics(screenDisplayMetrics); + DisplayMetricsHolder.setScreenDisplayMetrics(screenDisplayMetrics); + } + + /** Returns the metrics of the window associated to the Context used to initialize ReactNative */ + public static DisplayMetrics getWindowDisplayMetrics() { + return sWindowDisplayMetrics; + } + + public static void setScreenDisplayMetrics(DisplayMetrics screenDisplayMetrics) { + sScreenDisplayMetrics = screenDisplayMetrics; + } + + /** Screen metrics returns the metrics of the default screen on the device. */ + public static DisplayMetrics getScreenDisplayMetrics() { + return sScreenDisplayMetrics; + } + + public static WritableMap getDisplayMetricsWritableMap(double fontScale) { + final WritableNativeMap result = new WritableNativeMap(); + result.putMap( + "windowPhysicalPixels", getPhysicalPixelsWritableMap(sWindowDisplayMetrics, fontScale)); + result.putMap( + "screenPhysicalPixels", getPhysicalPixelsWritableMap(sScreenDisplayMetrics, fontScale)); + return result; + } + + private static WritableMap getPhysicalPixelsWritableMap( + DisplayMetrics displayMetrics, double fontScale) { + final WritableNativeMap result = new WritableNativeMap(); + result.putInt("width", displayMetrics.widthPixels); + result.putInt("height", displayMetrics.heightPixels); + result.putDouble("scale", displayMetrics.density); + result.putDouble("fontScale", fontScale); + result.putDouble("densityDpi", displayMetrics.densityDpi); + return result; + } +} \ No newline at end of file diff --git a/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/PixelUtil.java b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/PixelUtil.java new file mode 100644 index 000000000..d012b3257 --- /dev/null +++ b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/PixelUtil.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager; + +import android.util.DisplayMetrics; +import android.util.TypedValue; + +/** Android dp to pixel manipulation */ +public class PixelUtil { + + /** Convert from DIP to PX */ + public static float toPixelFromDIP(float value) { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, value, DisplayMetricsHolder.getWindowDisplayMetrics()); + } + + /** Convert from DIP to PX */ + public static float toPixelFromDIP(double value) { + return toPixelFromDIP((float) value); + } + + /** Convert from SP to PX */ + public static float toPixelFromSP(float value) { + return toPixelFromSP(value, Float.NaN); + } + + /** Convert from SP to PX */ + public static float toPixelFromSP(float value, float maxFontScale) { + DisplayMetrics displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics(); + float scaledDensity = displayMetrics.scaledDensity; + float currentFontScale = scaledDensity / displayMetrics.density; + if (maxFontScale >= 1 && maxFontScale < currentFontScale) { + scaledDensity = displayMetrics.density * maxFontScale; + } + + return value * scaledDensity; + } + + /** Convert from SP to PX */ + public static float toPixelFromSP(double value) { + return toPixelFromSP((float) value); + } + + /** Convert from PX to DP */ + public static float toDIPFromPixel(float value) { + return value / DisplayMetricsHolder.getWindowDisplayMetrics().density; + } + + /** @return {@link float} that represents the density of the display metrics for device screen. */ + public static float getDisplayMetricDensity() { + return DisplayMetricsHolder.getWindowDisplayMetrics().density; + } +} \ No newline at end of file diff --git a/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/ReactBaseTextShadowNode.java b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/ReactBaseTextShadowNode.java new file mode 100644 index 000000000..758c75144 --- /dev/null +++ b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/uimanager/ReactBaseTextShadowNode.java @@ -0,0 +1,5 @@ +package com.facebook.react.uimanager; + +public class ReactBaseTextShadowNode { + public static final int UNSET = -1; +} diff --git a/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/ReactFontManager.java b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/ReactFontManager.java new file mode 100644 index 000000000..180bf0340 --- /dev/null +++ b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/ReactFontManager.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.text; + +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Typeface; +import android.util.SparseArray; +import androidx.annotation.Nullable; +import androidx.core.content.res.ResourcesCompat; +import java.util.HashMap; +import java.util.Map; + +/** + * Responsible for loading and caching Typeface objects. + * + *

This will first try to load a typeface from the assets/fonts folder. If one is not found in + * that folder, this will fallback to the best matching system typeface. + * + *

Custom fonts support the extensions `.ttf` and `.otf` and the variants `bold`, `italic`, and + * `bold_italic`. For example, given a font named "ExampleFontFamily", the following are supported: + * + *

    + *
  • ExampleFontFamily.ttf (or .otf) + *
  • ExampleFontFamily_bold.ttf (or .otf) + *
  • ExampleFontFamily_italic.ttf (or .otf) + *
  • ExampleFontFamily_bold_italic.ttf (or .otf) + */ +public class ReactFontManager { + + // NOTE: Indices in `EXTENSIONS` correspond to the `TypeFace` style constants. + private static final String[] EXTENSIONS = {"", "_bold", "_italic", "_bold_italic"}; + private static final String[] FILE_EXTENSIONS = {".ttf", ".otf"}; + private static final String FONTS_ASSET_PATH = "fonts/"; + + private static ReactFontManager sReactFontManagerInstance; + + private final Map mFontCache; + private final Map mCustomTypefaceCache; + + private ReactFontManager() { + mFontCache = new HashMap<>(); + mCustomTypefaceCache = new HashMap<>(); + } + + public static ReactFontManager getInstance() { + if (sReactFontManagerInstance == null) { + sReactFontManagerInstance = new ReactFontManager(); + } + return sReactFontManagerInstance; + } + + public Typeface getTypeface(String fontFamilyName, int style, AssetManager assetManager) { + return getTypeface(fontFamilyName, new TypefaceStyle(style), assetManager); + } + + public Typeface getTypeface( + String fontFamilyName, int weight, boolean italic, AssetManager assetManager) { + return getTypeface(fontFamilyName, new TypefaceStyle(weight, italic), assetManager); + } + + public Typeface getTypeface( + String fontFamilyName, int style, int weight, AssetManager assetManager) { + return getTypeface(fontFamilyName, new TypefaceStyle(style, weight), assetManager); + } + + public Typeface getTypeface( + String fontFamilyName, TypefaceStyle typefaceStyle, AssetManager assetManager) { + if (mCustomTypefaceCache.containsKey(fontFamilyName)) { + // Apply `typefaceStyle` because custom fonts configure variants using `app:fontStyle` and + // `app:fontWeight` in their resource XML configuration file. + return typefaceStyle.apply(mCustomTypefaceCache.get(fontFamilyName)); + } + + AssetFontFamily assetFontFamily = mFontCache.get(fontFamilyName); + if (assetFontFamily == null) { + assetFontFamily = new AssetFontFamily(); + mFontCache.put(fontFamilyName, assetFontFamily); + } + + int style = typefaceStyle.getNearestStyle(); + + Typeface assetTypeface = assetFontFamily.getTypefaceForStyle(style); + if (assetTypeface == null) { + assetTypeface = createAssetTypeface(fontFamilyName, style, assetManager); + assetFontFamily.setTypefaceForStyle(style, assetTypeface); + } + // Do not apply `typefaceStyle` because asset font files already incorporate the style. + return assetTypeface; + } + + /* + * This method allows you to load custom fonts from res/font folder as provided font family name. + * Fonts may be one of .ttf, .otf or XML (https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml). + * To support multiple font styles or weights, you must provide a font in XML format. + * + * ReactFontManager.getInstance().addCustomFont(this, "Srisakdi", R.font.srisakdi); + */ + public void addCustomFont(Context context, String fontFamily, int fontId) { + Typeface font = ResourcesCompat.getFont(context, fontId); + if (font != null) { + mCustomTypefaceCache.put(fontFamily, font); + } + } + + /** + * Equivalent method to {@see addCustomFont(Context, String, int)} which accepts a Typeface + * object. + */ + public void addCustomFont(String fontFamily, @Nullable Typeface font) { + if (font != null) { + mCustomTypefaceCache.put(fontFamily, font); + } + } + + /** + * Add additional font family, or replace the exist one in the font memory cache. + * + * @param style + * @see {@link Typeface#DEFAULT} + * @see {@link Typeface#BOLD} + * @see {@link Typeface#ITALIC} + * @see {@link Typeface#BOLD_ITALIC} + */ + public void setTypeface(String fontFamilyName, int style, Typeface typeface) { + if (typeface != null) { + AssetFontFamily assetFontFamily = mFontCache.get(fontFamilyName); + if (assetFontFamily == null) { + assetFontFamily = new AssetFontFamily(); + mFontCache.put(fontFamilyName, assetFontFamily); + } + assetFontFamily.setTypefaceForStyle(style, typeface); + } + } + + private static Typeface createAssetTypeface( + String fontFamilyName, int style, AssetManager assetManager) { + String extension = EXTENSIONS[style]; + for (String fileExtension : FILE_EXTENSIONS) { + String fileName = + new StringBuilder() + .append(FONTS_ASSET_PATH) + .append(fontFamilyName) + .append(extension) + .append(fileExtension) + .toString(); + try { + return Typeface.createFromAsset(assetManager, fileName); + } catch (RuntimeException e) { + // If the typeface asset does not exist, try another extension. + continue; + } + } + return Typeface.create(fontFamilyName, style); + } + + /** Responsible for caching typefaces for each custom font family. */ + private static class AssetFontFamily { + + private SparseArray mTypefaceSparseArray; + + private AssetFontFamily() { + mTypefaceSparseArray = new SparseArray<>(4); + } + + public @Nullable Typeface getTypefaceForStyle(int style) { + return mTypefaceSparseArray.get(style); + } + + public void setTypefaceForStyle(int style, Typeface typeface) { + mTypefaceSparseArray.put(style, typeface); + } + } +} \ No newline at end of file diff --git a/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/ReactTypefaceUtils.java b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/ReactTypefaceUtils.java new file mode 100644 index 000000000..114b72d69 --- /dev/null +++ b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/ReactTypefaceUtils.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.text; + +import android.content.res.AssetManager; +import android.graphics.Typeface; +import android.text.TextUtils; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.uimanager.ReactBaseTextShadowNode; + +import java.util.ArrayList; +import java.util.List; + +public class ReactTypefaceUtils { + + public static int parseFontWeight(@Nullable String fontWeightString) { + if (fontWeightString != null) { + switch (fontWeightString) { + case "100": + return 100; + case "200": + return 200; + case "300": + return 300; + case "normal": + case "400": + return 400; + case "500": + return 500; + case "600": + return 600; + case "bold": + case "700": + return 700; + case "800": + return 800; + case "900": + return 900; + } + } + return ReactBaseTextShadowNode.UNSET; + } + + public static int parseFontStyle(@Nullable String fontStyleString) { + if (fontStyleString != null) { + if ("italic".equals(fontStyleString)) { + return Typeface.ITALIC; + } + if ("normal".equals(fontStyleString)) { + return Typeface.NORMAL; + } + } + return ReactBaseTextShadowNode.UNSET; + } + + public static @Nullable String parseFontVariant(@Nullable ReadableArray fontVariantArray) { + if (fontVariantArray == null || fontVariantArray.size() == 0) { + return null; + } + + List features = new ArrayList<>(); + for (int i = 0; i < fontVariantArray.size(); i++) { + // see https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + String fontVariant = fontVariantArray.getString(i); + if (fontVariant != null) { + switch (fontVariant) { + case "small-caps": + features.add("'smcp'"); + break; + case "oldstyle-nums": + features.add("'onum'"); + break; + case "lining-nums": + features.add("'lnum'"); + break; + case "tabular-nums": + features.add("'tnum'"); + break; + case "proportional-nums": + features.add("'pnum'"); + break; + case "stylistic-one": + features.add("'ss01'"); + break; + case "stylistic-two": + features.add("'ss02'"); + break; + case "stylistic-three": + features.add("'ss03'"); + break; + case "stylistic-four": + features.add("'ss04'"); + break; + case "stylistic-five": + features.add("'ss05'"); + break; + case "stylistic-six": + features.add("'ss06'"); + break; + case "stylistic-seven": + features.add("'ss07'"); + break; + case "stylistic-eight": + features.add("'ss08'"); + break; + case "stylistic-nine": + features.add("'ss09'"); + break; + case "stylistic-ten": + features.add("'ss10'"); + break; + case "stylistic-eleven": + features.add("'ss11'"); + break; + case "stylistic-twelve": + features.add("'ss12'"); + break; + case "stylistic-thirteen": + features.add("'ss13'"); + break; + case "stylistic-fourteen": + features.add("'ss14'"); + break; + case "stylistic-fifteen": + features.add("'ss15'"); + break; + case "stylistic-sixteen": + features.add("'ss16'"); + break; + case "stylistic-seventeen": + features.add("'ss17'"); + break; + case "stylistic-eighteen": + features.add("'ss18'"); + break; + case "stylistic-nineteen": + features.add("'ss19'"); + break; + case "stylistic-twenty": + features.add("'ss20'"); + break; + } + } + } + + return TextUtils.join(", ", features); + } + + public static Typeface applyStyles( + @Nullable Typeface typeface, + int style, + int weight, + @Nullable String fontFamilyName, + AssetManager assetManager) { + TypefaceStyle typefaceStyle = new TypefaceStyle(style, weight); + if (fontFamilyName == null) { + return typefaceStyle.apply(typeface == null ? Typeface.DEFAULT : typeface); + } else { + return ReactFontManager.getInstance() + .getTypeface(fontFamilyName, typefaceStyle, assetManager); + } + } +} \ No newline at end of file diff --git a/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/TypefaceStyle.java b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/TypefaceStyle.java new file mode 100644 index 000000000..fd5667b43 --- /dev/null +++ b/packages/stripe_android/android/src/main/kotlin/com/facebook/react/views/text/TypefaceStyle.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.text; + +import android.graphics.Typeface; +import android.os.Build; + +import com.facebook.react.uimanager.ReactBaseTextShadowNode; + +/** Responsible for normalizing style and numeric weight for backward compatibility. */ +class TypefaceStyle { + + public static final int BOLD = 700; + public static final int NORMAL = 400; + + private static final int MIN_WEIGHT = 1; + private static final int MAX_WEIGHT = 1000; + + private final boolean mItalic; + private final int mWeight; + + public TypefaceStyle(int weight, boolean italic) { + mItalic = italic; + mWeight = weight == ReactBaseTextShadowNode.UNSET ? NORMAL : weight; + } + + public TypefaceStyle(int style) { + if (style == ReactBaseTextShadowNode.UNSET) { + style = Typeface.NORMAL; + } + + mItalic = (style & Typeface.ITALIC) != 0; + mWeight = (style & Typeface.BOLD) != 0 ? BOLD : NORMAL; + } + + /** + * If `weight` is supplied, it will be combined with the italic bit from `style`. Otherwise, any + * existing weight bit in `style` will be used. + */ + public TypefaceStyle(int style, int weight) { + if (style == ReactBaseTextShadowNode.UNSET) { + style = Typeface.NORMAL; + } + + mItalic = (style & Typeface.ITALIC) != 0; + mWeight = + weight == ReactBaseTextShadowNode.UNSET + ? (style & Typeface.BOLD) != 0 ? BOLD : NORMAL + : weight; + } + + public int getNearestStyle() { + if (mWeight < BOLD) { + return mItalic ? Typeface.ITALIC : Typeface.NORMAL; + } else { + return mItalic ? Typeface.BOLD_ITALIC : Typeface.BOLD; + } + } + + public Typeface apply(Typeface typeface) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + return Typeface.create(typeface, getNearestStyle()); + } else { + return Typeface.create(typeface, mWeight, mItalic); + } + } +} \ No newline at end of file diff --git a/packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeAndroidPlugin.kt b/packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeAndroidPlugin.kt index ec3a87d0a..5441418ae 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeAndroidPlugin.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeAndroidPlugin.kt @@ -5,6 +5,7 @@ import androidx.annotation.NonNull import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap +import com.facebook.react.uimanager.DisplayMetricsHolder import com.facebook.react.uimanager.ThemedReactContext import com.google.android.material.internal.ThemeEnforcement import com.reactnativestripesdk.* @@ -49,6 +50,8 @@ class StripeAndroidPlugin : FlutterPlugin, MethodCallHandler, ActivityAware { } override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(flutterPluginBinding.applicationContext) + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter.stripe/payments", JSONMethodCodec.INSTANCE) channel.setMethodCallHandler(this) flutterPluginBinding @@ -181,6 +184,9 @@ If you continue to have trouble, follow this discussion to get some support http clientSecret = call.requiredArgument("clientSecret"), promise = Promise(result) ) + "resetPaymentSheetCustomer" -> stripeSdk.resetPaymentSheetCustomer( + promise = Promise(result) + ) else -> result.notImplemented() } } diff --git a/packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkModuleExtensions.kt b/packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkModuleExtensions.kt index 8defc8296..8da044474 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkModuleExtensions.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeSdkModuleExtensions.kt @@ -3,8 +3,7 @@ package com.flutter.stripe import androidx.fragment.app.FragmentActivity import com.facebook.react.bridge.Promise import com.reactnativestripesdk.StripeSdkModule -import com.reactnativestripesdk.createMissingActivityError -import io.flutter.embedding.android.FlutterFragmentActivity +import com.reactnativestripesdk.utils.createMissingActivityError operator fun Int.invoke(): Int { diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFieldView.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFieldView.kt index 36afa27cf..3fd7ade05 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFieldView.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFieldView.kt @@ -2,7 +2,6 @@ package com.reactnativestripesdk import android.content.res.ColorStateList import android.graphics.Color -import android.graphics.Typeface import android.os.Build import android.text.Editable import android.text.InputFilter @@ -11,9 +10,11 @@ import android.util.Log import android.widget.FrameLayout import androidx.core.os.LocaleListCompat import com.facebook.react.bridge.ReadableMap +import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerModule import com.facebook.react.uimanager.events.EventDispatcher +import com.facebook.react.views.text.ReactTypefaceUtils import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel @@ -125,7 +126,8 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { } fontFamily?.let { for (editTextBinding in bindings) { - editTextBinding.typeface = Typeface.create(it, Typeface.NORMAL) + // Load custom font from assets, and fallback to default system font + editTextBinding.typeface = ReactTypefaceUtils.applyStyles(null, -1, -1, it.takeIf { it.isNotEmpty() }, context.assets) } } cursorColor?.let { @@ -145,14 +147,14 @@ class CardFieldView(context: ThemedReactContext) : FrameLayout(context) { mCardWidget.background = MaterialShapeDrawable( ShapeAppearanceModel() .toBuilder() - .setAllCorners(CornerFamily.ROUNDED, (borderRadius * 2).toFloat()) + .setAllCorners(CornerFamily.ROUNDED, PixelUtil.toPixelFromDIP(borderRadius.toDouble())) .build() ).also { shape -> shape.strokeWidth = 0.0f shape.strokeColor = ColorStateList.valueOf(Color.parseColor("#000000")) shape.fillColor = ColorStateList.valueOf(Color.parseColor("#FFFFFF")) borderWidth?.let { - shape.strokeWidth = (it * 2).toFloat() + shape.strokeWidth = PixelUtil.toPixelFromDIP(it.toDouble()) } borderColor?.let { shape.strokeColor = ColorStateList.valueOf(Color.parseColor(it)) diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFormView.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFormView.kt index 0fbfba9a1..f9cc4e87c 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFormView.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/CardFormView.kt @@ -2,16 +2,17 @@ package com.reactnativestripesdk 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 import com.facebook.react.bridge.ReadableMap +import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.UIManagerModule import com.facebook.react.uimanager.events.EventDispatcher +import com.facebook.react.views.text.ReactTypefaceUtils import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel @@ -136,6 +137,12 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { cardFormViewBinding.cardMultilineWidget.expiryDateEditText, cardFormViewBinding.postalCode ) + val placeholderTextBindings = setOf( + multilineWidgetBinding.tlExpiry, + multilineWidgetBinding.tlCardNumber, + multilineWidgetBinding.tlCvc, + cardFormViewBinding.postalCodeContainer, + ) textColor?.let { for (binding in editTextBindings) { @@ -150,10 +157,9 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { } } placeholderColor?.let { - multilineWidgetBinding.tlExpiry.defaultHintTextColor = ColorStateList.valueOf(Color.parseColor(it)) - multilineWidgetBinding.tlCardNumber.defaultHintTextColor = ColorStateList.valueOf(Color.parseColor(it)) - multilineWidgetBinding.tlCvc.defaultHintTextColor = ColorStateList.valueOf(Color.parseColor(it)) - cardFormViewBinding.postalCodeContainer.defaultHintTextColor = ColorStateList.valueOf(Color.parseColor(it)) + for (binding in placeholderTextBindings) { + binding.defaultHintTextColor = ColorStateList.valueOf(Color.parseColor(it)) + } } fontSize?.let { for (binding in editTextBindings) { @@ -161,9 +167,17 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { } } fontFamily?.let { + // Load custom font from assets, and fallback to default system font + val typeface = ReactTypefaceUtils.applyStyles(null, -1, -1, it.takeIf { it.isNotEmpty() }, context.assets) for (binding in editTextBindings) { - binding.typeface = Typeface.create(it, Typeface.NORMAL) + binding.typeface = typeface + } + for (binding in placeholderTextBindings) { + binding.typeface = typeface } + cardFormViewBinding.countryLayout.typeface = typeface + cardFormViewBinding.countryLayout.countryAutocomplete.typeface = typeface + cardFormViewBinding.errors.typeface = typeface } cursorColor?.let { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -182,14 +196,14 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) { cardFormViewBinding.cardMultilineWidgetContainer.background = MaterialShapeDrawable( ShapeAppearanceModel() .toBuilder() - .setAllCorners(CornerFamily.ROUNDED, (borderRadius * 2).toFloat()) + .setAllCorners(CornerFamily.ROUNDED, PixelUtil.toPixelFromDIP(borderRadius.toDouble())) .build() ).also { shape -> shape.strokeWidth = 0.0f shape.strokeColor = ColorStateList.valueOf(Color.parseColor("#000000")) shape.fillColor = ColorStateList.valueOf(Color.parseColor("#FFFFFF")) borderWidth?.let { - shape.strokeWidth = (it * 2).toFloat() + shape.strokeWidth = PixelUtil.toPixelFromDIP(it.toDouble()) } borderColor?.let { shape.strokeColor = ColorStateList.valueOf(Color.parseColor(it)) diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/Errors.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/Errors.kt deleted file mode 100644 index 72b222ed9..000000000 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/Errors.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.reactnativestripesdk - -import com.facebook.react.bridge.WritableMap -import com.facebook.react.bridge.WritableNativeMap -import com.stripe.android.core.exception.APIException -import com.stripe.android.core.exception.AuthenticationException -import com.stripe.android.core.exception.InvalidRequestException -import com.stripe.android.exception.CardException -import com.stripe.android.model.PaymentIntent -import com.stripe.android.model.SetupIntent - -enum class ErrorType { - Failed, Canceled, Unknown -} - -enum class ConfirmPaymentErrorType { - Failed, Canceled, Unknown -} - -enum class CreateTokenErrorType { - Failed -} - -enum class ConfirmSetupIntentErrorType { - Failed, Canceled, Unknown -} - -enum class RetrievePaymentIntentErrorType { - Unknown -} - -enum class RetrieveSetupIntentErrorType { - Unknown -} - -enum class PaymentSheetErrorType { - Failed, Canceled -} - -enum class GooglePayErrorType { - Failed, Canceled -} - -class PaymentSheetAppearanceException(message: String) : Exception(message) - -internal fun mapError(code: String, message: String?, localizedMessage: String?, declineCode: String?, type: String?, stripeErrorCode: String?): WritableMap { - val map: WritableMap = WritableNativeMap() - val details: WritableMap = WritableNativeMap() - details.putString("code", code) - details.putString("message", message) - details.putString("localizedMessage", localizedMessage) - details.putString("declineCode", declineCode) - details.putString("type", type) - details.putString("stripeErrorCode", stripeErrorCode) - - map.putMap("error", details) - return map -} - -internal fun createError(code: String, message: String?): WritableMap { - return mapError(code, message, message, null, null, null) -} - -internal fun createMissingActivityError(): WritableMap { - return mapError( - "Failed", - "Activity doesn't exist yet. You can safely retry this method.", - null, - null, - null, - null) -} - -internal fun createError(code: String, error: PaymentIntent.Error?): WritableMap { - return mapError(code, error?.message, error?.message, error?.declineCode, error?.type?.code, error?.code) -} - -internal fun createError(code: String, error: SetupIntent.Error?): WritableMap { - return mapError(code, error?.message, error?.message, error?.declineCode, error?.type?.code, error?.code) -} - -internal fun createError(code: String, error: Exception): WritableMap { - return when (error) { - is CardException -> { - mapError(code, error.message, error.localizedMessage, error.declineCode, error.stripeError?.type, error.stripeError?.code) - } - is InvalidRequestException -> { - mapError(code, error.message, error.localizedMessage, error.stripeError?.declineCode, error.stripeError?.type, error.stripeError?.code) - } - is AuthenticationException -> { - mapError(code, error.message, error.localizedMessage, error.stripeError?.declineCode, error.stripeError?.type, error.stripeError?.code) - } - is APIException -> { - mapError(code, error.message, error.localizedMessage, error.stripeError?.declineCode, error.stripeError?.type, error.stripeError?.code) - } - else -> mapError(code, error.message, error.localizedMessage.orEmpty(), null, null, null) - } -} - -internal fun createError(code: String, error: Throwable): WritableMap { - (error as? Exception)?.let { - return createError( - code, - it) - } - return mapError( - code, - error.message, - error.localizedMessage, - null, - null, - null) -} diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/Extensions.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/Extensions.kt deleted file mode 100644 index b6fd05dfc..000000000 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/Extensions.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.reactnativestripesdk - -import android.content.Context -import android.view.View -import android.view.inputmethod.InputMethodManager - -fun View.showSoftKeyboard() { - post { - if (this.requestFocus()) { - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? - imm?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) - } - } -} - -fun View.hideSoftKeyboard() { - if (this.requestFocus()) { - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? - imm?.hideSoftInputFromWindow(windowToken, 0) - } -} diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt index 29c318e1b..d37eea82a 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/FinancialConnectionsSheetFragment.kt @@ -238,6 +238,7 @@ class FinancialConnectionsSheetFragment : Fragment() { FinancialConnectionsAccount.Permissions.BALANCES -> "balances" FinancialConnectionsAccount.Permissions.OWNERSHIP -> "ownership" FinancialConnectionsAccount.Permissions.TRANSACTIONS -> "transactions" + FinancialConnectionsAccount.Permissions.ACCOUNT_NUMBERS -> "accountNumbers" FinancialConnectionsAccount.Permissions.UNKNOWN -> "unparsable" } } diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentLauncherFragment.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentLauncherFragment.kt index 999a5a036..90eac5e8e 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentLauncherFragment.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentLauncherFragment.kt @@ -261,6 +261,7 @@ class PaymentLauncherFragment( StripeIntent.NextActionType.AlipayRedirect, StripeIntent.NextActionType.BlikAuthorize, StripeIntent.NextActionType.WeChatPayRedirect, + StripeIntent.NextActionType.UpiAwaitNotification, null -> false } } diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetFragment.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetFragment.kt index cc72e884a..5baf412f1 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetFragment.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentSheetFragment.kt @@ -56,6 +56,7 @@ class PaymentSheetFragment( initPromise.resolve(createError(ErrorType.Failed.toString(), "merchantDisplayName cannot be empty or null.")) return } + val primaryButtonLabel = arguments?.getString("primaryButtonLabel") val customerId = arguments?.getString("customerId").orEmpty() val customerEphemeralKeySecret = arguments?.getString("customerEphemeralKeySecret").orEmpty() val googlePayConfig = buildGooglePayConfig(arguments?.getBundle("googlePay")) @@ -126,7 +127,8 @@ class PaymentSheetFragment( ephemeralKeySecret = customerEphemeralKeySecret ) else null, googlePay = googlePayConfig, - appearance = appearance + appearance = appearance, + primaryButtonLabel = primaryButtonLabel ) if (arguments?.getBoolean("customFlow") == true) { diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt index 28b3e9dc8..b1e376473 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/StripeSdkModule.kt @@ -19,6 +19,7 @@ import com.stripe.android.core.ApiVersion import com.stripe.android.core.AppInfo import com.stripe.android.model.* import com.stripe.android.payments.bankaccount.CollectBankAccountConfiguration +import com.stripe.android.paymentsheet.PaymentSheet import com.stripe.android.view.AddPaymentMethodActivityStarter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -162,6 +163,12 @@ class StripeSdkModule(internal val reactContext: ReactApplicationContext) : Reac paymentSheetFragment?.confirmPayment(promise) } + @ReactMethod + fun resetPaymentSheetCustomer(promise: Promise) { + PaymentSheet.resetCustomer(context = reactApplicationContext) + promise.resolve(null) + } + private fun payWithFpx() { getCurrentActivityOrResolveWithError(confirmPromise)?.let { AddPaymentMethodActivityStarter(it) diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt index 5ad6c0d66..d75684a61 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt @@ -76,7 +76,6 @@ class AddToWalletButtonView(private val context: ThemedReactContext, private val val scale = sourceMap?.getDouble("scale")?.toDouble() ?: 1.0 requestManager - .asDrawable() .load(sourceToLoad) .addListener(object : RequestListener { override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { diff --git a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt index 5697d4840..464ed4ba1 100644 --- a/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt +++ b/packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/utils/Mappers.kt @@ -347,6 +347,8 @@ internal fun mapFromPaymentMethod(paymentMethod: PaymentMethod): WritableMap { card.putString("funding", paymentMethod.card?.funding) card.putString("last4", paymentMethod.card?.last4) card.putString("fingerprint", paymentMethod.card?.fingerprint) + card.putString("preferredNetwork", paymentMethod.card?.networks?.preferred) + card.putArray("availableNetworks", paymentMethod.card?.networks?.available?.toList() as? ReadableArray) sepaDebit.putString("bankCode", paymentMethod.sepaDebit?.bankCode) sepaDebit.putString("country", paymentMethod.sepaDebit?.country) diff --git a/packages/stripe_android/pubspec.yaml b/packages/stripe_android/pubspec.yaml index a8dce614f..bdb02f0a5 100644 --- a/packages/stripe_android/pubspec.yaml +++ b/packages/stripe_android/pubspec.yaml @@ -1,6 +1,6 @@ name: stripe_android description: Stripe platform implementation for Android -version: 6.0.0 +version: 7.0.0 repository: https://github.com/flutter-stripe/flutter_stripe homepage: https://pub.dev/packages/flutter_stripe diff --git a/packages/stripe_ios/CHANGELOG.md b/packages/stripe_ios/CHANGELOG.md index 2c10e3190..c90e622af 100644 --- a/packages/stripe_ios/CHANGELOG.md +++ b/packages/stripe_ios/CHANGELOG.md @@ -1,6 +1,21 @@ +## 7.0.0 +**Breaking Changes** +- This library now supports iOS 13 and up, due to stripe-ios increasing the deployment target + +**Features** +- Added Link support in Payment Sheet. +- Added the resetPaymentSheetCustomer method to clear persisted authentication state in the PaymentSheet. +- Added preferredNetwork and availableNetworks fields to the CardResult payment method. +- Added support for custom fonts to CardForm and CardView on Android. +- Added support for customizing the call to action button label in Payment Sheet by providing the primaryButtonLabel property to initPaymentSheet(). + +- **Fixes** +- Several fixes by the Stripe sdk [v.0.20.0](https://github.com/stripe/stripe-react-native/releases/tag/v0.20.0). + + ## 6.0.0 -** Breaking Changes ** +**Breaking Changes** - Move `PaymentMethodOptions` out of `PaymentMethodparams` so interface is similar with Stripe sdk. @@ -18,6 +33,7 @@ await Stripe.instance.confirmPayment( ), ), ); +``` Now @@ -35,13 +51,16 @@ await Stripe.instance.confirmPayment( ); ``` -- Deprecate support for Flutter 2. +- Deprecate support for Flutter 2 in order to use the new expensive Androidviews. This improves the overall experience on Android. ## 5.1.0 - Several fixes by the Stripe sdk [v.0.19.0](https://github.com/stripe/stripe-react-native/releases/tag/v0.19.0). - Fix for #462, added `handleURLCallback` method for iOS to handle `returnUrl` when iDeal payment is successful. This will close the in-app webview of Safari. +## 5.0.1 + - Fix for #462, added `handleURLCallback` method for iOS to handle `returnUrl` when iDeal payment is successful. This will close the in-app webview of Safari + ## 5.0.0 Breaking changes - Your compileSdkVersion (in android/build.gradle) now must be at least 32. Changing your compileSdkVersion does not change runtime behavior. diff --git a/packages/stripe_ios/ios/Classes/CardFieldFactory.swift b/packages/stripe_ios/ios/Classes/CardFieldFactory.swift index 9473721ef..1515562e0 100644 --- a/packages/stripe_ios/ios/Classes/CardFieldFactory.swift +++ b/packages/stripe_ios/ios/Classes/CardFieldFactory.swift @@ -144,12 +144,16 @@ class CardFieldPlatformView: NSObject, FlutterPlatformView, STPPaymentCardTextFi extension CardFieldView { func dangerouslyUpdateCardDetails(params: NSDictionary ) { - let cardParams = STPPaymentMethodCardParams() - cardParams.cvc = params["cvc"] as? String - cardParams.number = params["number"] as? String - cardParams.expYear = params["expirationYear"] as? NSNumber ?? params["expiryYear"] as? NSNumber - cardParams.expMonth = params["expirationMonth"] as? NSNumber ?? params["expiryMonth"] as? NSNumber - self.cardField.cardParams = cardParams + let cardMethodParams = STPPaymentMethodCardParams() + cardMethodParams.cvc = params["cvc"] as? String + cardMethodParams.number = params["number"] as? String + cardMethodParams.expYear = params["expirationYear"] as? NSNumber ?? params["expiryYear"] as? NSNumber + cardMethodParams.expMonth = params["expirationMonth"] as? NSNumber ?? params["expiryMonth"] as? NSNumber + let cardParams = STPPaymentMethodParams() + cardParams.type = STPPaymentMethodType.card + cardParams.card = cardMethodParams + + self.cardField.paymentMethodParams.card = cardMethodParams self.cardField.postalCode = params["postalCode"] as? String self.cardParams = cardParams self.cardPostalCode = params["postalCode"] as? String diff --git a/packages/stripe_ios/ios/Classes/Stripe Sdk/ApplePayUtils.swift b/packages/stripe_ios/ios/Classes/Stripe Sdk/ApplePayUtils.swift index e8dc6fb18..bdc73f953 100644 --- a/packages/stripe_ios/ios/Classes/Stripe Sdk/ApplePayUtils.swift +++ b/packages/stripe_ios/ios/Classes/Stripe Sdk/ApplePayUtils.swift @@ -6,7 +6,7 @@ // import Foundation -import Stripe +import StripePaymentSheet class ApplePayUtils { diff --git a/packages/stripe_ios/ios/Classes/Stripe Sdk/CardFieldView.swift b/packages/stripe_ios/ios/Classes/Stripe Sdk/CardFieldView.swift index fc97f6651..65b8b7cac 100644 --- a/packages/stripe_ios/ios/Classes/Stripe Sdk/CardFieldView.swift +++ b/packages/stripe_ios/ios/Classes/Stripe Sdk/CardFieldView.swift @@ -9,7 +9,7 @@ class CardFieldView: UIView, STPPaymentCardTextFieldDelegate { internal var cardField = STPPaymentCardTextField() - public var cardParams: STPPaymentMethodCardParams? = nil + public var cardParams: STPPaymentMethodParams? = nil public var cardPostalCode: String? = nil @objc var postalCodeEnabled: Bool = true { @@ -144,7 +144,7 @@ class CardFieldView: UIView, STPPaymentCardTextFieldDelegate { "expiryYear": textField.expirationYear, "complete": textField.isValid, "brand": Mappers.mapFromCardBrand(brand) ?? NSNull(), - "last4": textField.cardParams.last4 ?? "", + "last4": textField.paymentMethodParams.card!.last4 ?? "", "validExpiryDate": Mappers.mapFromCardValidationState(state: validExpiryDate), "validCVC": Mappers.mapFromCardValidationState(state: validCVC), "validNumber": Mappers.mapFromCardValidationState(state: validNumber) @@ -159,7 +159,7 @@ class CardFieldView: UIView, STPPaymentCardTextFieldDelegate { onCardChange!(cardData as [AnyHashable : Any]) } if (textField.isValid) { - self.cardParams = textField.cardParams + self.cardParams = textField.paymentMethodParams self.cardPostalCode = textField.postalCode } else { self.cardParams = nil diff --git a/packages/stripe_ios/ios/Classes/Stripe Sdk/FinancialConnections.swift b/packages/stripe_ios/ios/Classes/Stripe Sdk/FinancialConnections.swift index 8b44a8866..260baaf85 100644 --- a/packages/stripe_ios/ios/Classes/Stripe Sdk/FinancialConnections.swift +++ b/packages/stripe_ios/ios/Classes/Stripe Sdk/FinancialConnections.swift @@ -10,13 +10,14 @@ import StripeFinancialConnections import Stripe class FinancialConnections { - + internal static func present( withClientSecret: String, + returnURL: String? = nil, resolve: @escaping RCTPromiseResolveBlock ) -> Void { DispatchQueue.main.async { - FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret).present( + FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret, returnURL: returnURL).present( from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()), completion: { result in switch result { @@ -30,13 +31,14 @@ class FinancialConnections { }) } } - + internal static func presentForToken( withClientSecret: String, + returnURL: String? = nil, resolve: @escaping RCTPromiseResolveBlock ) -> Void { DispatchQueue.main.async { - FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret).presentForToken( + FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret, returnURL: returnURL).presentForToken( from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()), completion: { result in switch result { @@ -55,7 +57,7 @@ class FinancialConnections { }) } } - + internal static func mapFromSessionResult( _ session: StripeAPI.FinancialConnectionsSession ) -> NSDictionary { @@ -66,7 +68,7 @@ class FinancialConnections { "accounts": mapFromAccountsList(accounts: session.accounts) ] } - + internal static func mapFromTokenResult( _ token: StripeAPI.BankAccountToken? ) -> NSDictionary { @@ -79,7 +81,7 @@ class FinancialConnections { "created": NSNull(), // Doesn't exist on StripeAPI.BankAccountToken ] } - + internal static func mapFromBankAccount( bankAccount: StripeAPI.BankAccountToken.BankAccount? ) -> NSDictionary? { @@ -100,12 +102,12 @@ class FinancialConnections { "status": bankAccount.status.prefix(1).uppercased() + bankAccount.status.lowercased().dropFirst(), // stripe-ios returns a string, not STPBankAccountStatus ] } - + internal static func mapFromAccountsList( accounts: StripeAPI.FinancialConnectionsSession.AccountList ) -> [[String: Any]] { var result = [[String: Any]]() - + for account in accounts.data { result.append([ "id": account.id, @@ -123,7 +125,7 @@ class FinancialConnections { "supportedPaymentMethodTypes": account.supportedPaymentMethodTypes.map { mapFromSupportedPaymentMethodTypes($0) }, ]) } - + return result } @@ -133,13 +135,12 @@ class FinancialConnections { guard let balance = balance else { return nil } - + return [ "asOf": balance.asOf * 1000, "type": mapFromBalanceType(balance.type), -// TODO: Protected by internal on iOS only. PR is out to fix - "cash": ["available": NSNull()], // balance.cash?.available - "credit": ["used": NSNull()], // balance.credit?.used + "cash": ["available": balance.cash?.available], + "credit": ["used": balance.credit?.used], "current": balance.current, ] } @@ -150,13 +151,13 @@ class FinancialConnections { guard let balanceRefresh = balanceRefresh else { return nil } - + return [ "status": mapFromBalanceRefreshStatus(balanceRefresh.status), "lastAttemptedAt": balanceRefresh.lastAttemptedAt * 1000, ] } - + internal static func mapFromStatus( _ status: StripeAPI.FinancialConnectionsAccount.Status) -> String { switch status { case .active: @@ -169,7 +170,7 @@ class FinancialConnections { return "unparsable" } } - + internal static func mapFromCategory( _ category: StripeAPI.FinancialConnectionsAccount.Category) -> String { switch category { case .cash: @@ -184,7 +185,7 @@ class FinancialConnections { return "unparsable" } } - + internal static func mapFromSubcategory( _ subcategory: StripeAPI.FinancialConnectionsAccount.Subcategory) -> String { switch subcategory { case .savings: @@ -203,7 +204,7 @@ class FinancialConnections { return "unparsable" } } - + internal static func mapFromPermission( _ permission: StripeAPI.FinancialConnectionsAccount.Permissions) -> String { switch permission { case .transactions: @@ -220,7 +221,7 @@ class FinancialConnections { return "unparsable" } } - + internal static func mapFromSupportedPaymentMethodTypes( _ type: StripeAPI.FinancialConnectionsAccount.SupportedPaymentMethodTypes) -> String { switch type { case .usBankAccount: @@ -231,7 +232,7 @@ class FinancialConnections { return "unparsable" } } - + internal static func mapFromBalanceType( _ type: StripeAPI.FinancialConnectionsAccount.Balance.ModelType) -> String { switch type { case .cash: @@ -242,7 +243,7 @@ class FinancialConnections { return "unparsable" } } - + internal static func mapFromBalanceRefreshStatus( _ status: StripeAPI.FinancialConnectionsAccount.BalanceRefresh.Status) -> String { switch status { case .succeeded: diff --git a/packages/stripe_ios/ios/Classes/Stripe Sdk/Mappers.swift b/packages/stripe_ios/ios/Classes/Stripe Sdk/Mappers.swift index 25af05059..d9eeeaf4b 100644 --- a/packages/stripe_ios/ios/Classes/Stripe Sdk/Mappers.swift +++ b/packages/stripe_ios/ios/Classes/Stripe Sdk/Mappers.swift @@ -1,4 +1,5 @@ import Stripe +import StripePaymentSheet class Mappers { class func createResult(_ key: String, _ value: NSDictionary?) -> NSDictionary { @@ -53,7 +54,7 @@ class Mappers { guard let bankAccount = bankAccount else { return nil } - + let result: NSDictionary = [ "id": bankAccount.stripeID, "bankName": bankAccount.bankName ?? NSNull(), @@ -594,8 +595,11 @@ class Mappers { "expMonth": paymentMethod.card?.expMonth ?? NSNull(), "fingerprint": paymentMethod.card?.fingerprint ?? NSNull(), "funding": paymentMethod.card?.funding ?? NSNull(), - "last4": paymentMethod.card?.last4 ?? NSNull() + "last4": paymentMethod.card?.last4 ?? NSNull(), + "preferredNetwork": paymentMethod.card?.networks?.preferred ?? NSNull(), + "availableNetworks": paymentMethod.card?.networks?.available ?? NSNull(), ] + let sepaDebit: NSDictionary = [ "bankCode": paymentMethod.sepaDebit?.bankCode ?? NSNull(), "country": paymentMethod.sepaDebit?.country ?? NSNull(), @@ -743,6 +747,10 @@ class Mappers { return urlScheme + "://safepay" } + class func mapToFinancialConnectionsReturnURL(urlScheme: String) -> String { + return urlScheme + "://financial_connections_redirect" + } + class func mapUICustomization(_ params: NSDictionary) -> STPThreeDSUICustomization { let uiCustomization = STPThreeDSUICustomization() if let labelSettings = params["label"] as? Dictionary { diff --git a/packages/stripe_ios/ios/Classes/Stripe Sdk/PaymentMethodFactory.swift b/packages/stripe_ios/ios/Classes/Stripe Sdk/PaymentMethodFactory.swift index 4370cbb17..608988d51 100644 --- a/packages/stripe_ios/ios/Classes/Stripe Sdk/PaymentMethodFactory.swift +++ b/packages/stripe_ios/ios/Classes/Stripe Sdk/PaymentMethodFactory.swift @@ -162,11 +162,7 @@ class PaymentMethodFactory { return STPPaymentMethodParams(card: methodParams, billingDetails: billingDetailsParams, metadata: nil) } - guard let cardParams = cardFieldView?.cardParams ?? cardFormView?.cardParams else { - throw PaymentMethodError.cardPaymentMissingParams - } - - if cardFieldView?.cardParams != nil { + if let params = cardFieldView?.cardParams as? STPPaymentMethodParams { if let postalCode = cardFieldView?.cardPostalCode{ if (billingDetailsParams == nil) { let bd = STPPaymentMethodBillingDetails() @@ -177,8 +173,10 @@ class PaymentMethodFactory { billingDetailsParams?.address?.postalCode = postalCode } } + params.billingDetails = billingDetailsParams + return params } - if cardFormView?.cardParams != nil { + if let params = cardFormView?.cardParams as? STPPaymentMethodCardParams { if let address = cardFormView?.cardForm?.cardParams?.billingDetails?.address { if (billingDetailsParams == nil) { let bd = STPPaymentMethodBillingDetails() @@ -191,9 +189,10 @@ class PaymentMethodFactory { billingDetailsParams?.address?.country = address.country } } + return STPPaymentMethodParams(card: params, billingDetails: billingDetailsParams, metadata: nil) } - return STPPaymentMethodParams(card: cardParams, billingDetails: billingDetailsParams, metadata: nil) + throw PaymentMethodError.cardPaymentMissingParams } diff --git a/packages/stripe_ios/ios/Classes/Stripe Sdk/PaymentSheetAppearance.swift b/packages/stripe_ios/ios/Classes/Stripe Sdk/PaymentSheetAppearance.swift index ad5121fb4..467a9c576 100644 --- a/packages/stripe_ios/ios/Classes/Stripe Sdk/PaymentSheetAppearance.swift +++ b/packages/stripe_ios/ios/Classes/Stripe Sdk/PaymentSheetAppearance.swift @@ -4,7 +4,7 @@ // // Created by Charles Cruzan on 5/11/22. // -import Stripe +import StripePaymentSheet extension StripeSdk { func buildPaymentSheetAppearance(userParams: NSDictionary) throws -> PaymentSheet.Appearance { @@ -30,8 +30,8 @@ extension StripeSdk { return appearance } - private func buildFont(params: NSDictionary) throws -> Stripe.PaymentSheet.Appearance.Font { - var font = Stripe.PaymentSheet.Appearance.Font() + private func buildFont(params: NSDictionary) throws -> PaymentSheet.Appearance.Font { + var font = PaymentSheet.Appearance.Font() if let fontName = params[PaymentSheetAppearanceKeys.FAMILY] as? String { guard let customFont = UIFont(name: fontName, size: UIFont.systemFontSize) else { throw PaymentSheetAppearanceError.missingFont(fontName) @@ -42,8 +42,8 @@ extension StripeSdk { return font } - private func buildColors(params: NSDictionary) throws -> Stripe.PaymentSheet.Appearance.Colors { - var colors = Stripe.PaymentSheet.Appearance.Colors() + private func buildColors(params: NSDictionary) throws -> PaymentSheet.Appearance.Colors { + var colors = PaymentSheet.Appearance.Colors() if (params.object(forKey: PaymentSheetAppearanceKeys.LIGHT) != nil && params.object(forKey: PaymentSheetAppearanceKeys.DARK) == nil || params.object(forKey: PaymentSheetAppearanceKeys.DARK) != nil && params.object(forKey: PaymentSheetAppearanceKeys.LIGHT) == nil) { diff --git a/packages/stripe_ios/ios/Classes/Stripe Sdk/StripeSdk.swift b/packages/stripe_ios/ios/Classes/Stripe Sdk/StripeSdk.swift index 72589b049..fcee219e4 100644 --- a/packages/stripe_ios/ios/Classes/Stripe Sdk/StripeSdk.swift +++ b/packages/stripe_ios/ios/Classes/Stripe Sdk/StripeSdk.swift @@ -1,5 +1,6 @@ import PassKit import Stripe +import StripePaymentSheet import StripeFinancialConnections @objc(StripeSdk) @@ -59,6 +60,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi self.urlScheme = urlScheme STPAPIClient.shared.publishableKey = publishableKey + StripeAPI.defaultPublishableKey = publishableKey STPAPIClient.shared.stripeAccount = stripeAccountId let name = RCTConvert.nsString(appInfo["name"]) ?? "" @@ -76,6 +78,8 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { var configuration = PaymentSheet.Configuration() self.paymentSheetFlowController = nil + + configuration.primaryButtonLabel = params["primaryButtonLabel"] as? String if let appearanceParams = params["appearance"] as? NSDictionary { do { @@ -218,6 +222,13 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } } } + + @objc(resetPaymentSheetCustomer:rejecter:) + func resetPaymentSheetCustomer(resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock) -> Void { + PaymentSheet.resetCustomer() + resolve(nil) + } @objc(presentPaymentSheet:rejecter:) func presentPaymentSheet(resolver resolve: @escaping RCTPromiseResolveBlock, @@ -298,12 +309,17 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi return STPSetupIntentConfirmParams(clientSecret: setupIntentClientSecret, paymentMethodType: .USBankAccount) } else { let parameters = STPSetupIntentConfirmParams(clientSecret: setupIntentClientSecret) - let factory = PaymentMethodFactory.init(paymentMethodData: paymentMethodData, options: options, cardFieldView: cardFieldView, cardFormView: cardFormView) - do { - let paymentMethodParams = try factory.createParams(paymentMethodType: paymentMethodType) - parameters.paymentMethodParams = paymentMethodParams - } catch { - err = Errors.createError(ErrorType.Failed, error as NSError?) + + if let paymentMethodId = paymentMethodData?["paymentMethodId"] as? String { + parameters.paymentMethodID = paymentMethodId + } else { + let factory = PaymentMethodFactory.init(paymentMethodData: paymentMethodData, options: options, cardFieldView: cardFieldView, cardFormView: cardFormView) + do { + let paymentMethodParams = try factory.createParams(paymentMethodType: paymentMethodType) + parameters.paymentMethodParams = paymentMethodParams + } catch { + err = Errors.createError(ErrorType.Failed, error as NSError?) + } } return parameters @@ -348,7 +364,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolve(Errors.createError(ErrorType.Failed, "You can use this method only after either onDidSetShippingMethod or onDidSetShippingContact events emitted")) return } - + var paymentSummaryItems : [PKPaymentSummaryItem] = [] do { paymentSummaryItems = try ApplePayUtils.buildPaymentSummaryItems(items: summaryItems as? [[String : Any]]) @@ -356,7 +372,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolve(Errors.createError(ErrorType.Failed, error.localizedDescription)) return } - + var shippingAddressErrors: [Error] = [] for item in errorAddressFields { let field = item["field"] as! String @@ -454,7 +470,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } @objc(handleURLCallback:resolver:rejecter:) - func handleURLCallback(url: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { + func handleURLCallback(url: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { guard let url = url else { resolve(false) return; @@ -660,17 +676,22 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) -> Void { - guard let cardParams = cardFieldView?.cardParams ?? cardFormView?.cardParams else { + let address = params["address"] as? NSDictionary + let cardSourceParams = STPCardParams() + if let params = cardFieldView?.cardParams as? STPPaymentMethodParams { + cardSourceParams.number = params.card!.number + cardSourceParams.cvc = params.card!.cvc + cardSourceParams.expMonth = UInt(truncating: params.card!.expMonth ?? 0) + cardSourceParams.expYear = UInt(truncating: params.card!.expYear ?? 0) + } else if let params = cardFormView?.cardParams as? STPPaymentMethodCardParams { + cardSourceParams.number = params.number + cardSourceParams.cvc = params.cvc + cardSourceParams.expMonth = UInt(truncating: params.expMonth ?? 0) + cardSourceParams.expYear = UInt(truncating: params.expYear ?? 0) + } else { resolve(Errors.createError(ErrorType.Failed, "Card details not complete")) return } - - let address = params["address"] as? NSDictionary - let cardSourceParams = STPCardParams() - cardSourceParams.number = cardParams.number - cardSourceParams.cvc = cardParams.cvc - cardSourceParams.expMonth = UInt(truncating: cardParams.expMonth ?? 0) - cardSourceParams.expYear = UInt(truncating: cardParams.expYear ?? 0) cardSourceParams.address = Mappers.mapToAddress(address: address) cardSourceParams.name = params["name"] as? String cardSourceParams.currency = params["currency"] as? String @@ -746,10 +767,18 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi email: billingDetails["email"] as? String ) + let connectionsReturnURL: String? + if let urlScheme = urlScheme { + connectionsReturnURL = Mappers.mapToFinancialConnectionsReturnURL(urlScheme: urlScheme) + } else { + connectionsReturnURL = nil + } + if (isPaymentIntent) { DispatchQueue.main.async { STPBankAccountCollector().collectBankAccountForPayment( clientSecret: clientSecret as String, + returnURL: connectionsReturnURL, params: collectParams, from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()) ) { intent, error in @@ -775,6 +804,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi DispatchQueue.main.async { STPBankAccountCollector().collectBankAccountForSetup( clientSecret: clientSecret as String, + returnURL: connectionsReturnURL, params: collectParams, from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()) ) { intent, error in @@ -816,7 +846,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolve(missingPaymentMethodError) return } - + if (paymentMethodType == .FPX) { let testOfflineBank = paymentMethodData?["testOfflineBank"] as? Bool if (testOfflineBank == false || testOfflineBank == nil) { @@ -833,7 +863,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi STPPaymentHandler.shared().confirmPayment(paymentIntentParams, with: self, completion: onCompleteConfirmPayment) } } - + func getPaymentMethodType( params: NSDictionary? ) -> (NSDictionary?, STPPaymentMethodType?) { @@ -863,7 +893,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi return STPPaymentIntentParams(clientSecret: paymentIntentClientSecret, paymentMethodType: .USBankAccount) } else { guard let paymentMethodType = paymentMethodType else { return STPPaymentIntentParams(clientSecret: paymentIntentClientSecret) } - + let paymentMethodId = paymentMethodData?["paymentMethodId"] as? String let parameters = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret) @@ -1009,7 +1039,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } } } - + @objc(canAddCardToWallet:resolver:rejecter:) func canAddCardToWallet( params: NSDictionary, @@ -1028,7 +1058,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi "details": ["status": status?.rawValue], ]) } - + @objc(isCardInWallet:resolver:rejecter:) func isCardInWallet( params: NSDictionary, @@ -1041,7 +1071,7 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi } resolve(["isInWallet": PushProvisioningUtils.passExistsWith(last4: last4)]) } - + @objc(collectBankAccountToken:resolver:rejecter:) func collectBankAccountToken( clientSecret: String, @@ -1052,9 +1082,15 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolve(Errors.MISSING_INIT_ERROR) return } - FinancialConnections.presentForToken(withClientSecret: clientSecret, resolve: resolve) + let returnURL: String? + if let urlScheme = urlScheme { + returnURL = Mappers.mapToFinancialConnectionsReturnURL(urlScheme: urlScheme) + } else { + returnURL = nil + } + FinancialConnections.presentForToken(withClientSecret: clientSecret, returnURL: returnURL, resolve: resolve) } - + @objc(collectFinancialConnectionsAccounts:resolver:rejecter:) func collectFinancialConnectionsAccounts( clientSecret: String, @@ -1065,9 +1101,15 @@ class StripeSdk: RCTEventEmitter, STPApplePayContextDelegate, STPBankSelectionVi resolve(Errors.MISSING_INIT_ERROR) return } - FinancialConnections.present(withClientSecret: clientSecret, resolve: resolve) + let returnURL: String? + if let urlScheme = urlScheme { + returnURL = Mappers.mapToFinancialConnectionsReturnURL(urlScheme: urlScheme) + } else { + returnURL = nil + } + FinancialConnections.present(withClientSecret: clientSecret, returnURL: returnURL, resolve: resolve) } - + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { confirmPaymentResolver?(Errors.createError(ErrorType.Canceled, "FPX Payment has been canceled")) } diff --git a/packages/stripe_ios/ios/Classes/StripePlugin.swift b/packages/stripe_ios/ios/Classes/StripePlugin.swift index fc17f4928..5e788b1db 100644 --- a/packages/stripe_ios/ios/Classes/StripePlugin.swift +++ b/packages/stripe_ios/ios/Classes/StripePlugin.swift @@ -102,6 +102,8 @@ class StripePlugin: StripeSdk, FlutterPlugin, ViewManagerDelegate { return collectBankAccountToken(call, result: result) case "collectFinancialConnectionsAccounts": return collectFinancialConnectionsAccounts(call, result: result) + case "resetPaymentSheetCustomer": + return resetPaymentSheetCustomer(call, result: result) default: result(FlutterMethodNotImplemented) } @@ -427,6 +429,11 @@ extension StripePlugin { rejecter: rejecter(for: result)) } + func resetPaymentSheetCustomer(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + resetPaymentSheetCustomer(resolver: resolver(for: result), + rejecter: rejecter(for: result)) + } + func dangerouslyUpdateCardDetails(_ call: FlutterMethodCall, result: @escaping FlutterResult) { guard let arguments = call.arguments as? FlutterMap, let params = arguments["params"] as? NSDictionary else { diff --git a/packages/stripe_ios/ios/stripe_ios.podspec b/packages/stripe_ios/ios/stripe_ios.podspec index ee3430105..4b93178b6 100644 --- a/packages/stripe_ios/ios/stripe_ios.podspec +++ b/packages/stripe_ios/ios/stripe_ios.podspec @@ -2,6 +2,7 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint stripe_ios.podspec' to validate before publishing. # +stripe_version = '~> 23.1.0' Pod::Spec.new do |s| s.name = 'stripe_ios' s.version = '0.0.1' @@ -15,9 +16,13 @@ A new flutter plugin project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'Stripe', '~> 22.8.1' - s.dependency 'StripeFinancialConnections', '~> 22.8.1' - s.platform = :ios, '12.0' + s.dependency 'Stripe', stripe_version + s.dependency 'StripePaymentSheet', stripe_version + s.dependency 'StripePayments', stripe_version + s.dependency 'StripePaymentsUI', stripe_version + s.dependency 'StripeApplePay', stripe_version + s.dependency 'StripeFinancialConnections', stripe_version + s.platform = :ios, '13.0' # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } diff --git a/packages/stripe_ios/pubspec.yaml b/packages/stripe_ios/pubspec.yaml index 95739aaa8..8ef85f4c4 100644 --- a/packages/stripe_ios/pubspec.yaml +++ b/packages/stripe_ios/pubspec.yaml @@ -1,6 +1,6 @@ name: stripe_ios description: Stripe platform implementation for iOS -version: 6.0.0 +version: 7.0.0 repository: https://github.com/flutter-stripe/flutter_stripe homepage: https://pub.dev/packages/flutter_stripe diff --git a/packages/stripe_platform_interface/CHANGELOG.md b/packages/stripe_platform_interface/CHANGELOG.md index 172695354..c90e622af 100644 --- a/packages/stripe_platform_interface/CHANGELOG.md +++ b/packages/stripe_platform_interface/CHANGELOG.md @@ -1,6 +1,21 @@ +## 7.0.0 +**Breaking Changes** +- This library now supports iOS 13 and up, due to stripe-ios increasing the deployment target + +**Features** +- Added Link support in Payment Sheet. +- Added the resetPaymentSheetCustomer method to clear persisted authentication state in the PaymentSheet. +- Added preferredNetwork and availableNetworks fields to the CardResult payment method. +- Added support for custom fonts to CardForm and CardView on Android. +- Added support for customizing the call to action button label in Payment Sheet by providing the primaryButtonLabel property to initPaymentSheet(). + +- **Fixes** +- Several fixes by the Stripe sdk [v.0.20.0](https://github.com/stripe/stripe-react-native/releases/tag/v0.20.0). + + ## 6.0.0 -** Breaking Changes ** +**Breaking Changes** - Move `PaymentMethodOptions` out of `PaymentMethodparams` so interface is similar with Stripe sdk. @@ -18,6 +33,7 @@ await Stripe.instance.confirmPayment( ), ), ); +``` Now @@ -42,6 +58,9 @@ await Stripe.instance.confirmPayment( - Several fixes by the Stripe sdk [v.0.19.0](https://github.com/stripe/stripe-react-native/releases/tag/v0.19.0). - Fix for #462, added `handleURLCallback` method for iOS to handle `returnUrl` when iDeal payment is successful. This will close the in-app webview of Safari. +## 5.0.1 + - Fix for #462, added `handleURLCallback` method for iOS to handle `returnUrl` when iDeal payment is successful. This will close the in-app webview of Safari + ## 5.0.0 Breaking changes - Your compileSdkVersion (in android/build.gradle) now must be at least 32. Changing your compileSdkVersion does not change runtime behavior. diff --git a/packages/stripe_platform_interface/lib/src/method_channel_stripe.dart b/packages/stripe_platform_interface/lib/src/method_channel_stripe.dart index 7a140cdc6..09ffe9a23 100644 --- a/packages/stripe_platform_interface/lib/src/method_channel_stripe.dart +++ b/packages/stripe_platform_interface/lib/src/method_channel_stripe.dart @@ -32,7 +32,9 @@ class MethodChannelStripe extends StripePlatform { required bool platformIsAndroid, }) : _methodChannel = methodChannel, _platformIsAndroid = platformIsAndroid, - _platformIsIos = platformIsIos; + _platformIsIos = platformIsIos { + _init(); + } final MethodChannel _methodChannel; final bool _platformIsIos; @@ -58,6 +60,8 @@ class MethodChannelStripe extends StripePlatform { }); } + void _init() {} + @override Future createPaymentMethod( PaymentMethodParams data, [ @@ -160,11 +164,37 @@ class MethodChannelStripe extends StripePlatform { } @override - Future presentApplePay(ApplePayPresentParams params) async { + Future presentApplePay( + ApplePayPresentParams params, + OnDidSetShippingContact? onDidSetShippingContact, + OnDidSetShippingMethod? onDidSetShippingMethod, + ) async { if (!_platformIsIos) { throw UnsupportedError('Apple Pay is only available for iOS devices'); } - await _methodChannel.invokeMethod('presentApplePay', params.toJson()); + final paramsJson = params.toJson(); + + _methodChannel.setMethodCallHandler((call) async { + if (call.method == 'onDidSetShippingContact') { + final contact = + ApplePayShippingContact.fromJson(call.arguments['shippingContact']); + _methodChannel + .invokeMethod('updateApplePaySummaryItems', { + 'summaryItems': paramsJson['cartItems'], + }); + onDidSetShippingContact?.call(contact); + } else if (call.method == 'onDidSetShippingMethod') { + final method = + ApplePayShippingMethod.fromJson(call.arguments['shippingMethod']); + _methodChannel + .invokeMethod('updateApplePaySummaryItems', { + 'summaryItems': paramsJson['cartItems'], + }); + onDidSetShippingMethod?.call(method); + } + }); + + await _methodChannel.invokeMethod('presentApplePay', paramsJson); } @override @@ -217,6 +247,21 @@ class MethodChannelStripe extends StripePlatform { } } + @override + Future resetPaymentSheetCustomer() async { + final result = await _methodChannel.invokeMethod( + 'resetPaymentSheetCustomer', + {'params': {}}, + ); + + // iOS returns empty list on success + if (result is List) { + return; + } else { + return _parsePaymentSheetResult(result); + } + } + @override Future confirmPaymentSheetPayment() async { final result = await _methodChannel diff --git a/packages/stripe_platform_interface/lib/src/models/apple_pay.dart b/packages/stripe_platform_interface/lib/src/models/apple_pay.dart index c9f212d9f..149b81466 100644 --- a/packages/stripe_platform_interface/lib/src/models/apple_pay.dart +++ b/packages/stripe_platform_interface/lib/src/models/apple_pay.dart @@ -43,7 +43,7 @@ class ApplePayShippingMethod with _$ApplePayShippingMethod { required String label, required String amount, required String identifier, - ApplePayShippingMethodType? type, + bool? isPending, String? detail, }) = _ApplePayShippingMethod; @@ -162,3 +162,68 @@ class ApplePayErrorAddressField with _$ApplePayErrorAddressField { factory ApplePayErrorAddressField.fromJson(Map json) => _$ApplePayErrorAddressFieldFromJson(json); } + +@freezed + +/// Entered Shipping contact data +class ApplePayShippingContact with _$ApplePayShippingContact { + @JsonSerializable(explicitToJson: true) + const factory ApplePayShippingContact({ + /// Email address of the shipping contact + String? emailAddress, + + /// Name of shipping contact + required ApplePayContactName name, + + /// Postal address of shipping contact + required ApplePayPostalAddress postalAddress, + + ///Phone Number of the shipping contact + String? phoneNumber, + }) = _ApplePayShippingContact; + + factory ApplePayShippingContact.fromJson(Map json) => + _$ApplePayShippingContactFromJson(json); +} + +@freezed + +/// Contact name data for Apple pay +class ApplePayContactName with _$ApplePayContactName { + @JsonSerializable(explicitToJson: true) + const factory ApplePayContactName({ + String? familyName, + String? namePrefix, + String? nameSuffix, + String? givenName, + String? middleName, + String? nickname, + }) = _ApplePayContactName; + + factory ApplePayContactName.fromJson(Map json) => + _$ApplePayContactNameFromJson(json); +} + +@freezed + +/// Postal address data for Apple pay +class ApplePayPostalAddress with _$ApplePayPostalAddress { + @JsonSerializable(explicitToJson: true) + const factory ApplePayPostalAddress({ + String? city, + String? country, + String? postalCode, + String? state, + String? street, + String? isoCountryCode, + String? subAdministrativeArea, + String? subLocality, + }) = _ApplePayPostalAddress; + + factory ApplePayPostalAddress.fromJson(Map json) => + _$ApplePayPostalAddressFromJson(json); +} + +typedef OnDidSetShippingContact = void Function( + ApplePayShippingContact contact); +typedef OnDidSetShippingMethod = void Function(ApplePayShippingMethod method); diff --git a/packages/stripe_platform_interface/lib/src/models/apple_pay.freezed.dart b/packages/stripe_platform_interface/lib/src/models/apple_pay.freezed.dart index 7413a40ef..d93386ef4 100644 --- a/packages/stripe_platform_interface/lib/src/models/apple_pay.freezed.dart +++ b/packages/stripe_platform_interface/lib/src/models/apple_pay.freezed.dart @@ -24,7 +24,7 @@ mixin _$ApplePayShippingMethod { String get label => throw _privateConstructorUsedError; String get amount => throw _privateConstructorUsedError; String get identifier => throw _privateConstructorUsedError; - ApplePayShippingMethodType? get type => throw _privateConstructorUsedError; + bool? get isPending => throw _privateConstructorUsedError; String? get detail => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @@ -42,7 +42,7 @@ abstract class $ApplePayShippingMethodCopyWith<$Res> { {String label, String amount, String identifier, - ApplePayShippingMethodType? type, + bool? isPending, String? detail}); } @@ -60,7 +60,7 @@ class _$ApplePayShippingMethodCopyWithImpl<$Res> Object? label = freezed, Object? amount = freezed, Object? identifier = freezed, - Object? type = freezed, + Object? isPending = freezed, Object? detail = freezed, }) { return _then(_value.copyWith( @@ -76,10 +76,10 @@ class _$ApplePayShippingMethodCopyWithImpl<$Res> ? _value.identifier : identifier // ignore: cast_nullable_to_non_nullable as String, - type: type == freezed - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as ApplePayShippingMethodType?, + isPending: isPending == freezed + ? _value.isPending + : isPending // ignore: cast_nullable_to_non_nullable + as bool?, detail: detail == freezed ? _value.detail : detail // ignore: cast_nullable_to_non_nullable @@ -99,7 +99,7 @@ abstract class _$$_ApplePayShippingMethodCopyWith<$Res> {String label, String amount, String identifier, - ApplePayShippingMethodType? type, + bool? isPending, String? detail}); } @@ -120,7 +120,7 @@ class __$$_ApplePayShippingMethodCopyWithImpl<$Res> Object? label = freezed, Object? amount = freezed, Object? identifier = freezed, - Object? type = freezed, + Object? isPending = freezed, Object? detail = freezed, }) { return _then(_$_ApplePayShippingMethod( @@ -136,10 +136,10 @@ class __$$_ApplePayShippingMethodCopyWithImpl<$Res> ? _value.identifier : identifier // ignore: cast_nullable_to_non_nullable as String, - type: type == freezed - ? _value.type - : type // ignore: cast_nullable_to_non_nullable - as ApplePayShippingMethodType?, + isPending: isPending == freezed + ? _value.isPending + : isPending // ignore: cast_nullable_to_non_nullable + as bool?, detail: detail == freezed ? _value.detail : detail // ignore: cast_nullable_to_non_nullable @@ -156,7 +156,7 @@ class _$_ApplePayShippingMethod implements _ApplePayShippingMethod { {required this.label, required this.amount, required this.identifier, - this.type, + this.isPending, this.detail}); factory _$_ApplePayShippingMethod.fromJson(Map json) => @@ -169,13 +169,13 @@ class _$_ApplePayShippingMethod implements _ApplePayShippingMethod { @override final String identifier; @override - final ApplePayShippingMethodType? type; + final bool? isPending; @override final String? detail; @override String toString() { - return 'ApplePayShippingMethod(label: $label, amount: $amount, identifier: $identifier, type: $type, detail: $detail)'; + return 'ApplePayShippingMethod(label: $label, amount: $amount, identifier: $identifier, isPending: $isPending, detail: $detail)'; } @override @@ -187,7 +187,7 @@ class _$_ApplePayShippingMethod implements _ApplePayShippingMethod { const DeepCollectionEquality().equals(other.amount, amount) && const DeepCollectionEquality() .equals(other.identifier, identifier) && - const DeepCollectionEquality().equals(other.type, type) && + const DeepCollectionEquality().equals(other.isPending, isPending) && const DeepCollectionEquality().equals(other.detail, detail)); } @@ -198,7 +198,7 @@ class _$_ApplePayShippingMethod implements _ApplePayShippingMethod { const DeepCollectionEquality().hash(label), const DeepCollectionEquality().hash(amount), const DeepCollectionEquality().hash(identifier), - const DeepCollectionEquality().hash(type), + const DeepCollectionEquality().hash(isPending), const DeepCollectionEquality().hash(detail)); @JsonKey(ignore: true) @@ -220,7 +220,7 @@ abstract class _ApplePayShippingMethod implements ApplePayShippingMethod { {required final String label, required final String amount, required final String identifier, - final ApplePayShippingMethodType? type, + final bool? isPending, final String? detail}) = _$_ApplePayShippingMethod; factory _ApplePayShippingMethod.fromJson(Map json) = @@ -233,7 +233,7 @@ abstract class _ApplePayShippingMethod implements ApplePayShippingMethod { @override String get identifier; @override - ApplePayShippingMethodType? get type; + bool? get isPending; @override String? get detail; @override @@ -1657,3 +1657,801 @@ abstract class _ApplePayErrorAddressField implements ApplePayErrorAddressField { _$$_ApplePayErrorAddressFieldCopyWith<_$_ApplePayErrorAddressField> get copyWith => throw _privateConstructorUsedError; } + +ApplePayShippingContact _$ApplePayShippingContactFromJson( + Map json) { + return _ApplePayShippingContact.fromJson(json); +} + +/// @nodoc +mixin _$ApplePayShippingContact { + /// Email address of the shipping contact + String? get emailAddress => throw _privateConstructorUsedError; + + /// Name of shipping contact + ApplePayContactName get name => throw _privateConstructorUsedError; + + /// Postal address of shipping contact + ApplePayPostalAddress get postalAddress => throw _privateConstructorUsedError; + + ///Phone Number of the shipping contact + String? get phoneNumber => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ApplePayShippingContactCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ApplePayShippingContactCopyWith<$Res> { + factory $ApplePayShippingContactCopyWith(ApplePayShippingContact value, + $Res Function(ApplePayShippingContact) then) = + _$ApplePayShippingContactCopyWithImpl<$Res>; + $Res call( + {String? emailAddress, + ApplePayContactName name, + ApplePayPostalAddress postalAddress, + String? phoneNumber}); + + $ApplePayContactNameCopyWith<$Res> get name; + $ApplePayPostalAddressCopyWith<$Res> get postalAddress; +} + +/// @nodoc +class _$ApplePayShippingContactCopyWithImpl<$Res> + implements $ApplePayShippingContactCopyWith<$Res> { + _$ApplePayShippingContactCopyWithImpl(this._value, this._then); + + final ApplePayShippingContact _value; + // ignore: unused_field + final $Res Function(ApplePayShippingContact) _then; + + @override + $Res call({ + Object? emailAddress = freezed, + Object? name = freezed, + Object? postalAddress = freezed, + Object? phoneNumber = freezed, + }) { + return _then(_value.copyWith( + emailAddress: emailAddress == freezed + ? _value.emailAddress + : emailAddress // ignore: cast_nullable_to_non_nullable + as String?, + name: name == freezed + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as ApplePayContactName, + postalAddress: postalAddress == freezed + ? _value.postalAddress + : postalAddress // ignore: cast_nullable_to_non_nullable + as ApplePayPostalAddress, + phoneNumber: phoneNumber == freezed + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String?, + )); + } + + @override + $ApplePayContactNameCopyWith<$Res> get name { + return $ApplePayContactNameCopyWith<$Res>(_value.name, (value) { + return _then(_value.copyWith(name: value)); + }); + } + + @override + $ApplePayPostalAddressCopyWith<$Res> get postalAddress { + return $ApplePayPostalAddressCopyWith<$Res>(_value.postalAddress, (value) { + return _then(_value.copyWith(postalAddress: value)); + }); + } +} + +/// @nodoc +abstract class _$$_ApplePayShippingContactCopyWith<$Res> + implements $ApplePayShippingContactCopyWith<$Res> { + factory _$$_ApplePayShippingContactCopyWith(_$_ApplePayShippingContact value, + $Res Function(_$_ApplePayShippingContact) then) = + __$$_ApplePayShippingContactCopyWithImpl<$Res>; + @override + $Res call( + {String? emailAddress, + ApplePayContactName name, + ApplePayPostalAddress postalAddress, + String? phoneNumber}); + + @override + $ApplePayContactNameCopyWith<$Res> get name; + @override + $ApplePayPostalAddressCopyWith<$Res> get postalAddress; +} + +/// @nodoc +class __$$_ApplePayShippingContactCopyWithImpl<$Res> + extends _$ApplePayShippingContactCopyWithImpl<$Res> + implements _$$_ApplePayShippingContactCopyWith<$Res> { + __$$_ApplePayShippingContactCopyWithImpl(_$_ApplePayShippingContact _value, + $Res Function(_$_ApplePayShippingContact) _then) + : super(_value, (v) => _then(v as _$_ApplePayShippingContact)); + + @override + _$_ApplePayShippingContact get _value => + super._value as _$_ApplePayShippingContact; + + @override + $Res call({ + Object? emailAddress = freezed, + Object? name = freezed, + Object? postalAddress = freezed, + Object? phoneNumber = freezed, + }) { + return _then(_$_ApplePayShippingContact( + emailAddress: emailAddress == freezed + ? _value.emailAddress + : emailAddress // ignore: cast_nullable_to_non_nullable + as String?, + name: name == freezed + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as ApplePayContactName, + postalAddress: postalAddress == freezed + ? _value.postalAddress + : postalAddress // ignore: cast_nullable_to_non_nullable + as ApplePayPostalAddress, + phoneNumber: phoneNumber == freezed + ? _value.phoneNumber + : phoneNumber // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _$_ApplePayShippingContact implements _ApplePayShippingContact { + const _$_ApplePayShippingContact( + {this.emailAddress, + required this.name, + required this.postalAddress, + this.phoneNumber}); + + factory _$_ApplePayShippingContact.fromJson(Map json) => + _$$_ApplePayShippingContactFromJson(json); + + /// Email address of the shipping contact + @override + final String? emailAddress; + + /// Name of shipping contact + @override + final ApplePayContactName name; + + /// Postal address of shipping contact + @override + final ApplePayPostalAddress postalAddress; + + ///Phone Number of the shipping contact + @override + final String? phoneNumber; + + @override + String toString() { + return 'ApplePayShippingContact(emailAddress: $emailAddress, name: $name, postalAddress: $postalAddress, phoneNumber: $phoneNumber)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_ApplePayShippingContact && + const DeepCollectionEquality() + .equals(other.emailAddress, emailAddress) && + const DeepCollectionEquality().equals(other.name, name) && + const DeepCollectionEquality() + .equals(other.postalAddress, postalAddress) && + const DeepCollectionEquality() + .equals(other.phoneNumber, phoneNumber)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(emailAddress), + const DeepCollectionEquality().hash(name), + const DeepCollectionEquality().hash(postalAddress), + const DeepCollectionEquality().hash(phoneNumber)); + + @JsonKey(ignore: true) + @override + _$$_ApplePayShippingContactCopyWith<_$_ApplePayShippingContact> + get copyWith => + __$$_ApplePayShippingContactCopyWithImpl<_$_ApplePayShippingContact>( + this, _$identity); + + @override + Map toJson() { + return _$$_ApplePayShippingContactToJson( + this, + ); + } +} + +abstract class _ApplePayShippingContact implements ApplePayShippingContact { + const factory _ApplePayShippingContact( + {final String? emailAddress, + required final ApplePayContactName name, + required final ApplePayPostalAddress postalAddress, + final String? phoneNumber}) = _$_ApplePayShippingContact; + + factory _ApplePayShippingContact.fromJson(Map json) = + _$_ApplePayShippingContact.fromJson; + + @override + + /// Email address of the shipping contact + String? get emailAddress; + @override + + /// Name of shipping contact + ApplePayContactName get name; + @override + + /// Postal address of shipping contact + ApplePayPostalAddress get postalAddress; + @override + + ///Phone Number of the shipping contact + String? get phoneNumber; + @override + @JsonKey(ignore: true) + _$$_ApplePayShippingContactCopyWith<_$_ApplePayShippingContact> + get copyWith => throw _privateConstructorUsedError; +} + +ApplePayContactName _$ApplePayContactNameFromJson(Map json) { + return _ApplePayContactName.fromJson(json); +} + +/// @nodoc +mixin _$ApplePayContactName { + String? get familyName => throw _privateConstructorUsedError; + String? get namePrefix => throw _privateConstructorUsedError; + String? get nameSuffix => throw _privateConstructorUsedError; + String? get givenName => throw _privateConstructorUsedError; + String? get middleName => throw _privateConstructorUsedError; + String? get nickname => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ApplePayContactNameCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ApplePayContactNameCopyWith<$Res> { + factory $ApplePayContactNameCopyWith( + ApplePayContactName value, $Res Function(ApplePayContactName) then) = + _$ApplePayContactNameCopyWithImpl<$Res>; + $Res call( + {String? familyName, + String? namePrefix, + String? nameSuffix, + String? givenName, + String? middleName, + String? nickname}); +} + +/// @nodoc +class _$ApplePayContactNameCopyWithImpl<$Res> + implements $ApplePayContactNameCopyWith<$Res> { + _$ApplePayContactNameCopyWithImpl(this._value, this._then); + + final ApplePayContactName _value; + // ignore: unused_field + final $Res Function(ApplePayContactName) _then; + + @override + $Res call({ + Object? familyName = freezed, + Object? namePrefix = freezed, + Object? nameSuffix = freezed, + Object? givenName = freezed, + Object? middleName = freezed, + Object? nickname = freezed, + }) { + return _then(_value.copyWith( + familyName: familyName == freezed + ? _value.familyName + : familyName // ignore: cast_nullable_to_non_nullable + as String?, + namePrefix: namePrefix == freezed + ? _value.namePrefix + : namePrefix // ignore: cast_nullable_to_non_nullable + as String?, + nameSuffix: nameSuffix == freezed + ? _value.nameSuffix + : nameSuffix // ignore: cast_nullable_to_non_nullable + as String?, + givenName: givenName == freezed + ? _value.givenName + : givenName // ignore: cast_nullable_to_non_nullable + as String?, + middleName: middleName == freezed + ? _value.middleName + : middleName // ignore: cast_nullable_to_non_nullable + as String?, + nickname: nickname == freezed + ? _value.nickname + : nickname // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +abstract class _$$_ApplePayContactNameCopyWith<$Res> + implements $ApplePayContactNameCopyWith<$Res> { + factory _$$_ApplePayContactNameCopyWith(_$_ApplePayContactName value, + $Res Function(_$_ApplePayContactName) then) = + __$$_ApplePayContactNameCopyWithImpl<$Res>; + @override + $Res call( + {String? familyName, + String? namePrefix, + String? nameSuffix, + String? givenName, + String? middleName, + String? nickname}); +} + +/// @nodoc +class __$$_ApplePayContactNameCopyWithImpl<$Res> + extends _$ApplePayContactNameCopyWithImpl<$Res> + implements _$$_ApplePayContactNameCopyWith<$Res> { + __$$_ApplePayContactNameCopyWithImpl(_$_ApplePayContactName _value, + $Res Function(_$_ApplePayContactName) _then) + : super(_value, (v) => _then(v as _$_ApplePayContactName)); + + @override + _$_ApplePayContactName get _value => super._value as _$_ApplePayContactName; + + @override + $Res call({ + Object? familyName = freezed, + Object? namePrefix = freezed, + Object? nameSuffix = freezed, + Object? givenName = freezed, + Object? middleName = freezed, + Object? nickname = freezed, + }) { + return _then(_$_ApplePayContactName( + familyName: familyName == freezed + ? _value.familyName + : familyName // ignore: cast_nullable_to_non_nullable + as String?, + namePrefix: namePrefix == freezed + ? _value.namePrefix + : namePrefix // ignore: cast_nullable_to_non_nullable + as String?, + nameSuffix: nameSuffix == freezed + ? _value.nameSuffix + : nameSuffix // ignore: cast_nullable_to_non_nullable + as String?, + givenName: givenName == freezed + ? _value.givenName + : givenName // ignore: cast_nullable_to_non_nullable + as String?, + middleName: middleName == freezed + ? _value.middleName + : middleName // ignore: cast_nullable_to_non_nullable + as String?, + nickname: nickname == freezed + ? _value.nickname + : nickname // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _$_ApplePayContactName implements _ApplePayContactName { + const _$_ApplePayContactName( + {this.familyName, + this.namePrefix, + this.nameSuffix, + this.givenName, + this.middleName, + this.nickname}); + + factory _$_ApplePayContactName.fromJson(Map json) => + _$$_ApplePayContactNameFromJson(json); + + @override + final String? familyName; + @override + final String? namePrefix; + @override + final String? nameSuffix; + @override + final String? givenName; + @override + final String? middleName; + @override + final String? nickname; + + @override + String toString() { + return 'ApplePayContactName(familyName: $familyName, namePrefix: $namePrefix, nameSuffix: $nameSuffix, givenName: $givenName, middleName: $middleName, nickname: $nickname)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_ApplePayContactName && + const DeepCollectionEquality() + .equals(other.familyName, familyName) && + const DeepCollectionEquality() + .equals(other.namePrefix, namePrefix) && + const DeepCollectionEquality() + .equals(other.nameSuffix, nameSuffix) && + const DeepCollectionEquality().equals(other.givenName, givenName) && + const DeepCollectionEquality() + .equals(other.middleName, middleName) && + const DeepCollectionEquality().equals(other.nickname, nickname)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(familyName), + const DeepCollectionEquality().hash(namePrefix), + const DeepCollectionEquality().hash(nameSuffix), + const DeepCollectionEquality().hash(givenName), + const DeepCollectionEquality().hash(middleName), + const DeepCollectionEquality().hash(nickname)); + + @JsonKey(ignore: true) + @override + _$$_ApplePayContactNameCopyWith<_$_ApplePayContactName> get copyWith => + __$$_ApplePayContactNameCopyWithImpl<_$_ApplePayContactName>( + this, _$identity); + + @override + Map toJson() { + return _$$_ApplePayContactNameToJson( + this, + ); + } +} + +abstract class _ApplePayContactName implements ApplePayContactName { + const factory _ApplePayContactName( + {final String? familyName, + final String? namePrefix, + final String? nameSuffix, + final String? givenName, + final String? middleName, + final String? nickname}) = _$_ApplePayContactName; + + factory _ApplePayContactName.fromJson(Map json) = + _$_ApplePayContactName.fromJson; + + @override + String? get familyName; + @override + String? get namePrefix; + @override + String? get nameSuffix; + @override + String? get givenName; + @override + String? get middleName; + @override + String? get nickname; + @override + @JsonKey(ignore: true) + _$$_ApplePayContactNameCopyWith<_$_ApplePayContactName> get copyWith => + throw _privateConstructorUsedError; +} + +ApplePayPostalAddress _$ApplePayPostalAddressFromJson( + Map json) { + return _ApplePayPostalAddress.fromJson(json); +} + +/// @nodoc +mixin _$ApplePayPostalAddress { + String? get city => throw _privateConstructorUsedError; + String? get country => throw _privateConstructorUsedError; + String? get postalCode => throw _privateConstructorUsedError; + String? get state => throw _privateConstructorUsedError; + String? get street => throw _privateConstructorUsedError; + String? get isoCountryCode => throw _privateConstructorUsedError; + String? get subAdministrativeArea => throw _privateConstructorUsedError; + String? get subLocality => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $ApplePayPostalAddressCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ApplePayPostalAddressCopyWith<$Res> { + factory $ApplePayPostalAddressCopyWith(ApplePayPostalAddress value, + $Res Function(ApplePayPostalAddress) then) = + _$ApplePayPostalAddressCopyWithImpl<$Res>; + $Res call( + {String? city, + String? country, + String? postalCode, + String? state, + String? street, + String? isoCountryCode, + String? subAdministrativeArea, + String? subLocality}); +} + +/// @nodoc +class _$ApplePayPostalAddressCopyWithImpl<$Res> + implements $ApplePayPostalAddressCopyWith<$Res> { + _$ApplePayPostalAddressCopyWithImpl(this._value, this._then); + + final ApplePayPostalAddress _value; + // ignore: unused_field + final $Res Function(ApplePayPostalAddress) _then; + + @override + $Res call({ + Object? city = freezed, + Object? country = freezed, + Object? postalCode = freezed, + Object? state = freezed, + Object? street = freezed, + Object? isoCountryCode = freezed, + Object? subAdministrativeArea = freezed, + Object? subLocality = freezed, + }) { + return _then(_value.copyWith( + city: city == freezed + ? _value.city + : city // ignore: cast_nullable_to_non_nullable + as String?, + country: country == freezed + ? _value.country + : country // ignore: cast_nullable_to_non_nullable + as String?, + postalCode: postalCode == freezed + ? _value.postalCode + : postalCode // ignore: cast_nullable_to_non_nullable + as String?, + state: state == freezed + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String?, + street: street == freezed + ? _value.street + : street // ignore: cast_nullable_to_non_nullable + as String?, + isoCountryCode: isoCountryCode == freezed + ? _value.isoCountryCode + : isoCountryCode // ignore: cast_nullable_to_non_nullable + as String?, + subAdministrativeArea: subAdministrativeArea == freezed + ? _value.subAdministrativeArea + : subAdministrativeArea // ignore: cast_nullable_to_non_nullable + as String?, + subLocality: subLocality == freezed + ? _value.subLocality + : subLocality // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +abstract class _$$_ApplePayPostalAddressCopyWith<$Res> + implements $ApplePayPostalAddressCopyWith<$Res> { + factory _$$_ApplePayPostalAddressCopyWith(_$_ApplePayPostalAddress value, + $Res Function(_$_ApplePayPostalAddress) then) = + __$$_ApplePayPostalAddressCopyWithImpl<$Res>; + @override + $Res call( + {String? city, + String? country, + String? postalCode, + String? state, + String? street, + String? isoCountryCode, + String? subAdministrativeArea, + String? subLocality}); +} + +/// @nodoc +class __$$_ApplePayPostalAddressCopyWithImpl<$Res> + extends _$ApplePayPostalAddressCopyWithImpl<$Res> + implements _$$_ApplePayPostalAddressCopyWith<$Res> { + __$$_ApplePayPostalAddressCopyWithImpl(_$_ApplePayPostalAddress _value, + $Res Function(_$_ApplePayPostalAddress) _then) + : super(_value, (v) => _then(v as _$_ApplePayPostalAddress)); + + @override + _$_ApplePayPostalAddress get _value => + super._value as _$_ApplePayPostalAddress; + + @override + $Res call({ + Object? city = freezed, + Object? country = freezed, + Object? postalCode = freezed, + Object? state = freezed, + Object? street = freezed, + Object? isoCountryCode = freezed, + Object? subAdministrativeArea = freezed, + Object? subLocality = freezed, + }) { + return _then(_$_ApplePayPostalAddress( + city: city == freezed + ? _value.city + : city // ignore: cast_nullable_to_non_nullable + as String?, + country: country == freezed + ? _value.country + : country // ignore: cast_nullable_to_non_nullable + as String?, + postalCode: postalCode == freezed + ? _value.postalCode + : postalCode // ignore: cast_nullable_to_non_nullable + as String?, + state: state == freezed + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String?, + street: street == freezed + ? _value.street + : street // ignore: cast_nullable_to_non_nullable + as String?, + isoCountryCode: isoCountryCode == freezed + ? _value.isoCountryCode + : isoCountryCode // ignore: cast_nullable_to_non_nullable + as String?, + subAdministrativeArea: subAdministrativeArea == freezed + ? _value.subAdministrativeArea + : subAdministrativeArea // ignore: cast_nullable_to_non_nullable + as String?, + subLocality: subLocality == freezed + ? _value.subLocality + : subLocality // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _$_ApplePayPostalAddress implements _ApplePayPostalAddress { + const _$_ApplePayPostalAddress( + {this.city, + this.country, + this.postalCode, + this.state, + this.street, + this.isoCountryCode, + this.subAdministrativeArea, + this.subLocality}); + + factory _$_ApplePayPostalAddress.fromJson(Map json) => + _$$_ApplePayPostalAddressFromJson(json); + + @override + final String? city; + @override + final String? country; + @override + final String? postalCode; + @override + final String? state; + @override + final String? street; + @override + final String? isoCountryCode; + @override + final String? subAdministrativeArea; + @override + final String? subLocality; + + @override + String toString() { + return 'ApplePayPostalAddress(city: $city, country: $country, postalCode: $postalCode, state: $state, street: $street, isoCountryCode: $isoCountryCode, subAdministrativeArea: $subAdministrativeArea, subLocality: $subLocality)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_ApplePayPostalAddress && + const DeepCollectionEquality().equals(other.city, city) && + const DeepCollectionEquality().equals(other.country, country) && + const DeepCollectionEquality() + .equals(other.postalCode, postalCode) && + const DeepCollectionEquality().equals(other.state, state) && + const DeepCollectionEquality().equals(other.street, street) && + const DeepCollectionEquality() + .equals(other.isoCountryCode, isoCountryCode) && + const DeepCollectionEquality() + .equals(other.subAdministrativeArea, subAdministrativeArea) && + const DeepCollectionEquality() + .equals(other.subLocality, subLocality)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(city), + const DeepCollectionEquality().hash(country), + const DeepCollectionEquality().hash(postalCode), + const DeepCollectionEquality().hash(state), + const DeepCollectionEquality().hash(street), + const DeepCollectionEquality().hash(isoCountryCode), + const DeepCollectionEquality().hash(subAdministrativeArea), + const DeepCollectionEquality().hash(subLocality)); + + @JsonKey(ignore: true) + @override + _$$_ApplePayPostalAddressCopyWith<_$_ApplePayPostalAddress> get copyWith => + __$$_ApplePayPostalAddressCopyWithImpl<_$_ApplePayPostalAddress>( + this, _$identity); + + @override + Map toJson() { + return _$$_ApplePayPostalAddressToJson( + this, + ); + } +} + +abstract class _ApplePayPostalAddress implements ApplePayPostalAddress { + const factory _ApplePayPostalAddress( + {final String? city, + final String? country, + final String? postalCode, + final String? state, + final String? street, + final String? isoCountryCode, + final String? subAdministrativeArea, + final String? subLocality}) = _$_ApplePayPostalAddress; + + factory _ApplePayPostalAddress.fromJson(Map json) = + _$_ApplePayPostalAddress.fromJson; + + @override + String? get city; + @override + String? get country; + @override + String? get postalCode; + @override + String? get state; + @override + String? get street; + @override + String? get isoCountryCode; + @override + String? get subAdministrativeArea; + @override + String? get subLocality; + @override + @JsonKey(ignore: true) + _$$_ApplePayPostalAddressCopyWith<_$_ApplePayPostalAddress> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/packages/stripe_platform_interface/lib/src/models/apple_pay.g.dart b/packages/stripe_platform_interface/lib/src/models/apple_pay.g.dart index 5926624ec..72e4975b7 100644 --- a/packages/stripe_platform_interface/lib/src/models/apple_pay.g.dart +++ b/packages/stripe_platform_interface/lib/src/models/apple_pay.g.dart @@ -12,8 +12,7 @@ _$_ApplePayShippingMethod _$$_ApplePayShippingMethodFromJson( label: json['label'] as String, amount: json['amount'] as String, identifier: json['identifier'] as String, - type: $enumDecodeNullable( - _$ApplePayShippingMethodTypeEnumMap, json['type']), + isPending: json['isPending'] as bool?, detail: json['detail'] as String?, ); @@ -23,15 +22,10 @@ Map _$$_ApplePayShippingMethodToJson( 'label': instance.label, 'amount': instance.amount, 'identifier': instance.identifier, - 'type': _$ApplePayShippingMethodTypeEnumMap[instance.type], + 'isPending': instance.isPending, 'detail': instance.detail, }; -const _$ApplePayShippingMethodTypeEnumMap = { - ApplePayShippingMethodType.ready: 'ready', - ApplePayShippingMethodType.pending: 'pending', -}; - _$_ImmediateCartSummaryItem _$$_ImmediateCartSummaryItemFromJson( Map json) => _$_ImmediateCartSummaryItem( @@ -163,3 +157,70 @@ Map _$$_ApplePayErrorAddressFieldToJson( 'field': _$ApplePayContactFieldsTypeEnumMap[instance.field]!, 'message': instance.message, }; + +_$_ApplePayShippingContact _$$_ApplePayShippingContactFromJson( + Map json) => + _$_ApplePayShippingContact( + emailAddress: json['emailAddress'] as String?, + name: ApplePayContactName.fromJson(json['name'] as Map), + postalAddress: ApplePayPostalAddress.fromJson( + json['postalAddress'] as Map), + phoneNumber: json['phoneNumber'] as String?, + ); + +Map _$$_ApplePayShippingContactToJson( + _$_ApplePayShippingContact instance) => + { + 'emailAddress': instance.emailAddress, + 'name': instance.name.toJson(), + 'postalAddress': instance.postalAddress.toJson(), + 'phoneNumber': instance.phoneNumber, + }; + +_$_ApplePayContactName _$$_ApplePayContactNameFromJson( + Map json) => + _$_ApplePayContactName( + familyName: json['familyName'] as String?, + namePrefix: json['namePrefix'] as String?, + nameSuffix: json['nameSuffix'] as String?, + givenName: json['givenName'] as String?, + middleName: json['middleName'] as String?, + nickname: json['nickname'] as String?, + ); + +Map _$$_ApplePayContactNameToJson( + _$_ApplePayContactName instance) => + { + 'familyName': instance.familyName, + 'namePrefix': instance.namePrefix, + 'nameSuffix': instance.nameSuffix, + 'givenName': instance.givenName, + 'middleName': instance.middleName, + 'nickname': instance.nickname, + }; + +_$_ApplePayPostalAddress _$$_ApplePayPostalAddressFromJson( + Map json) => + _$_ApplePayPostalAddress( + city: json['city'] as String?, + country: json['country'] as String?, + postalCode: json['postalCode'] as String?, + state: json['state'] as String?, + street: json['street'] as String?, + isoCountryCode: json['isoCountryCode'] as String?, + subAdministrativeArea: json['subAdministrativeArea'] as String?, + subLocality: json['subLocality'] as String?, + ); + +Map _$$_ApplePayPostalAddressToJson( + _$_ApplePayPostalAddress instance) => + { + 'city': instance.city, + 'country': instance.country, + 'postalCode': instance.postalCode, + 'state': instance.state, + 'street': instance.street, + 'isoCountryCode': instance.isoCountryCode, + 'subAdministrativeArea': instance.subAdministrativeArea, + 'subLocality': instance.subLocality, + }; diff --git a/packages/stripe_platform_interface/lib/src/models/payment_methods.dart b/packages/stripe_platform_interface/lib/src/models/payment_methods.dart index 72b9073bf..aad1f491e 100644 --- a/packages/stripe_platform_interface/lib/src/models/payment_methods.dart +++ b/packages/stripe_platform_interface/lib/src/models/payment_methods.dart @@ -152,6 +152,12 @@ class Card with _$Card { /// last four digits of the card. String? last4, + + /// The preffered card brand for payment + String? preferredNetwork, + + /// The available networks the card can run. + List? availableNetworks, }) = _Card; factory Card.fromJson(Map json) => _$CardFromJson(json); diff --git a/packages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dart b/packages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dart index 909c43ccb..b486a969e 100644 --- a/packages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dart +++ b/packages/stripe_platform_interface/lib/src/models/payment_methods.freezed.dart @@ -1279,6 +1279,12 @@ mixin _$Card { /// last four digits of the card. String? get last4 => throw _privateConstructorUsedError; + /// The preffered card brand for payment + String? get preferredNetwork => throw _privateConstructorUsedError; + + /// The available networks the card can run. + List? get availableNetworks => throw _privateConstructorUsedError; + Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) $CardCopyWith get copyWith => throw _privateConstructorUsedError; @@ -1294,7 +1300,9 @@ abstract class $CardCopyWith<$Res> { int? expYear, int? expMonth, String? funding, - String? last4}); + String? last4, + String? preferredNetwork, + List? availableNetworks}); } /// @nodoc @@ -1313,6 +1321,8 @@ class _$CardCopyWithImpl<$Res> implements $CardCopyWith<$Res> { Object? expMonth = freezed, Object? funding = freezed, Object? last4 = freezed, + Object? preferredNetwork = freezed, + Object? availableNetworks = freezed, }) { return _then(_value.copyWith( brand: brand == freezed @@ -1339,6 +1349,14 @@ class _$CardCopyWithImpl<$Res> implements $CardCopyWith<$Res> { ? _value.last4 : last4 // ignore: cast_nullable_to_non_nullable as String?, + preferredNetwork: preferredNetwork == freezed + ? _value.preferredNetwork + : preferredNetwork // ignore: cast_nullable_to_non_nullable + as String?, + availableNetworks: availableNetworks == freezed + ? _value.availableNetworks + : availableNetworks // ignore: cast_nullable_to_non_nullable + as List?, )); } } @@ -1354,7 +1372,9 @@ abstract class _$$_CardCopyWith<$Res> implements $CardCopyWith<$Res> { int? expYear, int? expMonth, String? funding, - String? last4}); + String? last4, + String? preferredNetwork, + List? availableNetworks}); } /// @nodoc @@ -1374,6 +1394,8 @@ class __$$_CardCopyWithImpl<$Res> extends _$CardCopyWithImpl<$Res> Object? expMonth = freezed, Object? funding = freezed, Object? last4 = freezed, + Object? preferredNetwork = freezed, + Object? availableNetworks = freezed, }) { return _then(_$_Card( brand: brand == freezed @@ -1400,6 +1422,14 @@ class __$$_CardCopyWithImpl<$Res> extends _$CardCopyWithImpl<$Res> ? _value.last4 : last4 // ignore: cast_nullable_to_non_nullable as String?, + preferredNetwork: preferredNetwork == freezed + ? _value.preferredNetwork + : preferredNetwork // ignore: cast_nullable_to_non_nullable + as String?, + availableNetworks: availableNetworks == freezed + ? _value._availableNetworks + : availableNetworks // ignore: cast_nullable_to_non_nullable + as List?, )); } } @@ -1414,7 +1444,10 @@ class _$_Card implements _Card { this.expYear, this.expMonth, this.funding, - this.last4}); + this.last4, + this.preferredNetwork, + final List? availableNetworks}) + : _availableNetworks = availableNetworks; factory _$_Card.fromJson(Map json) => _$$_CardFromJson(json); @@ -1442,9 +1475,25 @@ class _$_Card implements _Card { @override final String? last4; + /// The preffered card brand for payment + @override + final String? preferredNetwork; + + /// The available networks the card can run. + final List? _availableNetworks; + + /// The available networks the card can run. + @override + List? get availableNetworks { + final value = _availableNetworks; + if (value == null) return null; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + @override String toString() { - return 'Card(brand: $brand, country: $country, expYear: $expYear, expMonth: $expMonth, funding: $funding, last4: $last4)'; + return 'Card(brand: $brand, country: $country, expYear: $expYear, expMonth: $expMonth, funding: $funding, last4: $last4, preferredNetwork: $preferredNetwork, availableNetworks: $availableNetworks)'; } @override @@ -1457,7 +1506,11 @@ class _$_Card implements _Card { const DeepCollectionEquality().equals(other.expYear, expYear) && const DeepCollectionEquality().equals(other.expMonth, expMonth) && const DeepCollectionEquality().equals(other.funding, funding) && - const DeepCollectionEquality().equals(other.last4, last4)); + const DeepCollectionEquality().equals(other.last4, last4) && + const DeepCollectionEquality() + .equals(other.preferredNetwork, preferredNetwork) && + const DeepCollectionEquality() + .equals(other._availableNetworks, _availableNetworks)); } @JsonKey(ignore: true) @@ -1469,7 +1522,9 @@ class _$_Card implements _Card { const DeepCollectionEquality().hash(expYear), const DeepCollectionEquality().hash(expMonth), const DeepCollectionEquality().hash(funding), - const DeepCollectionEquality().hash(last4)); + const DeepCollectionEquality().hash(last4), + const DeepCollectionEquality().hash(preferredNetwork), + const DeepCollectionEquality().hash(_availableNetworks)); @JsonKey(ignore: true) @override @@ -1491,7 +1546,9 @@ abstract class _Card implements Card { final int? expYear, final int? expMonth, final String? funding, - final String? last4}) = _$_Card; + final String? last4, + final String? preferredNetwork, + final List? availableNetworks}) = _$_Card; factory _Card.fromJson(Map json) = _$_Card.fromJson; @@ -1520,6 +1577,14 @@ abstract class _Card implements Card { /// last four digits of the card. String? get last4; @override + + /// The preffered card brand for payment + String? get preferredNetwork; + @override + + /// The available networks the card can run. + List? get availableNetworks; + @override @JsonKey(ignore: true) _$$_CardCopyWith<_$_Card> get copyWith => throw _privateConstructorUsedError; } diff --git a/packages/stripe_platform_interface/lib/src/models/payment_methods.g.dart b/packages/stripe_platform_interface/lib/src/models/payment_methods.g.dart index ef9a9c01a..9eea034fc 100644 --- a/packages/stripe_platform_interface/lib/src/models/payment_methods.g.dart +++ b/packages/stripe_platform_interface/lib/src/models/payment_methods.g.dart @@ -97,6 +97,10 @@ _$_Card _$$_CardFromJson(Map json) => _$_Card( expMonth: json['expMonth'] as int?, funding: json['funding'] as String?, last4: json['last4'] as String?, + preferredNetwork: json['preferredNetwork'] as String?, + availableNetworks: (json['availableNetworks'] as List?) + ?.map((e) => e as String) + .toList(), ); Map _$$_CardToJson(_$_Card instance) => { @@ -106,6 +110,8 @@ Map _$$_CardToJson(_$_Card instance) => { 'expMonth': instance.expMonth, 'funding': instance.funding, 'last4': instance.last4, + 'preferredNetwork': instance.preferredNetwork, + 'availableNetworks': instance.availableNetworks, }; _$_Fpx _$$_FpxFromJson(Map json) => _$_Fpx( diff --git a/packages/stripe_platform_interface/lib/src/stripe_platform_interface.dart b/packages/stripe_platform_interface/lib/src/stripe_platform_interface.dart index d4f7a50e2..3b194e1b1 100644 --- a/packages/stripe_platform_interface/lib/src/stripe_platform_interface.dart +++ b/packages/stripe_platform_interface/lib/src/stripe_platform_interface.dart @@ -56,11 +56,18 @@ abstract class StripePlatform extends PlatformInterface { /// Display the payment sheet. Future presentPaymentSheet(); + /// Reset the payment sheet. + Future resetPaymentSheetCustomer(); + /// Confirm the payment on a payment sheet. Future confirmPaymentSheetPayment(); Future openApplePaySetup(); - Future presentApplePay(ApplePayPresentParams params); + Future presentApplePay( + ApplePayPresentParams params, + OnDidSetShippingContact? onDidSetShippingContact, + OnDidSetShippingMethod? onDidSetShippingMethod, + ); Future confirmApplePayPayment(String clientSecret); Future createApplePayToken(Map payment); Future updateApplePaySummaryItems({ diff --git a/packages/stripe_platform_interface/pubspec.yaml b/packages/stripe_platform_interface/pubspec.yaml index b0740de82..a065f5807 100644 --- a/packages/stripe_platform_interface/pubspec.yaml +++ b/packages/stripe_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: stripe_platform_interface description: Platform interface for stripe sdk -version: 6.0.0 +version: 7.0.0 repository: https://github.com/flutter-stripe/flutter_stripe homepage: https://pub.dev/packages/flutter_stripe diff --git a/packages/stripe_platform_interface/test/method_channel_stripe_test.dart b/packages/stripe_platform_interface/test/method_channel_stripe_test.dart index 133a328bf..1e7d440d2 100644 --- a/packages/stripe_platform_interface/test/method_channel_stripe_test.dart +++ b/packages/stripe_platform_interface/test/method_channel_stripe_test.dart @@ -158,7 +158,7 @@ void main() { const PaymentMethodParams.card( paymentMethodData: PaymentMethodData(), ), - const PaymentMethodOptions( + const PaymentMethodOptions( setupFutureUsage: PaymentIntentsFutureUsage.OffSession, )); }); @@ -396,6 +396,8 @@ void main() { country: 'country', currency: 'currency', ), + null, + null, ) .then((_) => completer.complete()); }); @@ -426,6 +428,8 @@ void main() { country: 'country', currency: 'currency', ), + null, + null, ), throwsA(const TypeMatcher()), ); @@ -501,6 +505,29 @@ void main() { }); }); + group('Reset payment sheet', () { + late Completer completer; + + setUp(() async { + completer = Completer(); + + sut = MethodChannelStripe( + platformIsIos: false, + platformIsAndroid: true, + methodChannel: MethodChannelMock( + channelName: methodChannelName, + method: 'resetPaymentSheetCustomer', + result: {}, + ).methodChannel, + ); + await sut.resetPaymentSheetCustomer().then((_) => completer.complete()); + }); + + test('It completes operation', () { + expect(completer.isCompleted, true); + }); + }); + group('presentPaymentSheet', () { late Completer completer; diff --git a/packages/stripe_web/lib/src/web_stripe.dart b/packages/stripe_web/lib/src/web_stripe.dart index 2ca2801b4..4eafcbf37 100644 --- a/packages/stripe_web/lib/src/web_stripe.dart +++ b/packages/stripe_web/lib/src/web_stripe.dart @@ -221,7 +221,11 @@ class WebStripe extends StripePlatform { } @override - Future presentApplePay(ApplePayPresentParams params) async { + Future presentApplePay( + ApplePayPresentParams params, + OnDidSetShippingContact? onDidSetShippingContact, + OnDidSetShippingMethod? onDidSetShippingMethod, + ) async { throw WebUnsupportedError.method('presentApplePay'); } @@ -364,6 +368,11 @@ class WebStripe extends StripePlatform { // TODO: implement handleURLCallback throw UnimplementedError(); } + + @override + Future resetPaymentSheetCustomer() { + throw WebUnsupportedError.method('resetPaymentSheet'); + } } class WebUnsupportedError extends Error implements UnsupportedError { diff --git a/packages/stripe_web/pubspec.yaml b/packages/stripe_web/pubspec.yaml index 4002cd398..6ac3b76cb 100644 --- a/packages/stripe_web/pubspec.yaml +++ b/packages/stripe_web/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_stripe_web description: Stripe sdk for Flutter web platform. -version: 2.0.0 +version: 2.0.1 homepage: https://github.com/flutter-stripe/flutter_stripe environment: