From f4d7c70f2c0550b920c3bdd4ca40481828a960d0 Mon Sep 17 00:00:00 2001 From: BreX900 Date: Tue, 10 Oct 2023 23:34:21 +0200 Subject: [PATCH] feat: Mapped more exception codecs: Android (`collectInputsUnsupported`), IOS (`readerConnectionOfflineNeedsUpdate`, `encryptionKeyFailure`, `encryptionKeyStillInitializing`) feat: Allow to customize tipping on `Terminal.collectPaymentMethod` method feat: Allow to update payment intent on `Terminal.collectPaymentMethod` method feat: Beta: Allow customer-initiated cancellation for PaymentIntent, SetupIntent, and Refund payment method collection with internet readers. See customerCancellationEnabled: on collectPaymentMethod, collectSetupIntentPaymentMethod, and collectRefundPaymentMethod Terminal methods. Note: This feature requires reader software version 2.17 or later to be installed on your internet reader. Please contact us if you want to support customer-initiated cancellation. fix(android): Fixes TapToPay error "Must have a country code to connect to reader" [#29](https://github.com/BreX900/mek-packages/issues/29) feat: Bumped [Android](https://github.com/stripe/stripe-terminal-android/blob/master/CHANGELOG.md#310---2023-10-10) and [IOS](https://github.com/stripe/stripe-terminal-ios/blob/master/CHANGELOG.md#310-2023-10-10) sdks versions to `3.1.0` --- .../workflows/stripe_terminal_integration.yml | 36 ++++++++++- .../src/codecs/plataforms/dart_codecs.dart | 7 +- stripe_terminal/CHANGELOG.md | 12 +++- stripe_terminal/README.md | 4 +- stripe_terminal/ROADMAP.md | 64 +++++-------------- .../mek/stripeterminal/TerminalPlugin.kt | 48 ++++++++------ .../mek/stripeterminal/api/TerminalApi.kt | 29 +++++++-- .../mek/stripeterminal/api/ToApiExtensions.kt | 8 +-- .../stripeterminal/api/ToHostExtensions.kt | 6 ++ stripe_terminal/example/ios/Podfile.lock | 10 +-- stripe_terminal/example/lib/main.dart | 36 +++++++---- .../example/lib/models/discovery_method.dart | 16 +++-- stripe_terminal/example/pubspec.lock | 2 +- .../ios/Classes/Api/TerminalApi.swift | 32 ++++++++-- .../ios/Classes/Api/ToApiExtensions.swift | 29 ++++++--- .../ios/Classes/Api/ToHostExtensions.swift | 8 +++ .../ios/Classes/TerminalPlugin.swift | 39 ++++++++--- .../ios/mek_stripe_terminal.podspec | 22 ++++--- stripe_terminal/lib/mek_stripe_terminal.dart | 1 + .../lib/src/models/payment_intent.g.dart | 8 +-- .../lib/src/models/setup_intent.g.dart | 2 +- stripe_terminal/lib/src/models/tipping.dart | 14 ++++ stripe_terminal/lib/src/models/tipping.g.dart | 28 ++++++++ .../src/platform/terminal_platform.api.dart | 23 +++++-- .../lib/src/platform/terminal_platform.dart | 8 ++- stripe_terminal/lib/src/terminal.dart | 42 ++++++++---- .../lib/src/terminal_exception.dart | 23 ++++++- stripe_terminal/pubspec.yaml | 3 +- 28 files changed, 387 insertions(+), 173 deletions(-) create mode 100644 stripe_terminal/lib/src/models/tipping.dart create mode 100644 stripe_terminal/lib/src/models/tipping.g.dart diff --git a/.github/workflows/stripe_terminal_integration.yml b/.github/workflows/stripe_terminal_integration.yml index 0fa9afe..786117b 100644 --- a/.github/workflows/stripe_terminal_integration.yml +++ b/.github/workflows/stripe_terminal_integration.yml @@ -8,15 +8,17 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -defaults: - run: - working-directory: stripe_terminal + jobs: integration: runs-on: ubuntu-latest timeout-minutes: 5 + defaults: + run: + working-directory: stripe_terminal + steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 @@ -34,3 +36,31 @@ jobs: - name: Analyze code run: flutter analyze --no-fatal-infos + + integration-example: + runs-on: macos-latest + timeout-minutes: 5 + + defaults: + run: + working-directory: stripe_terminal/example + + steps: + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 + with: + flutter-version: '3.13.x' + + - name: Resolve dependencies + run: flutter pub get --enforce-lockfile + timeout-minutes: 2 + + - name: Check code formatting + run: >- + dart format --line-length 100 --set-exit-if-changed --output none + $(find . ! -path "./.dart_tool/**" ! -path "./build/**" -name "*.dart" ! -name "*.g.dart") + + - name: Analyze code + run: flutter analyze --no-fatal-infos + + - run: pod install --deployment --project-directory=ios diff --git a/one_for_all_generator/lib/src/codecs/plataforms/dart_codecs.dart b/one_for_all_generator/lib/src/codecs/plataforms/dart_codecs.dart index 774a3e9..826214c 100644 --- a/one_for_all_generator/lib/src/codecs/plataforms/dart_codecs.dart +++ b/one_for_all_generator/lib/src/codecs/plataforms/dart_codecs.dart @@ -54,6 +54,7 @@ class DartApiCodes extends ApiCodecs { if (type is VoidType) throw StateError('Void type no supported'); final questionOrEmpty = type.isNullable ? '?' : ''; + final exclamationOrEmpty = varAccess.contains('.') ? '!' : ''; if (type.isPrimitive) return varAccess; if (type.isDartCoreList) { @@ -75,13 +76,15 @@ class DartApiCodes extends ApiCodecs { if (!type.isNullable || codec.hasNullSafeSerialization) { return codec.encodeSerialization(this, type, varAccess); } else { - return '$varAccess != null ? ${codec.encodeSerialization(this, type, '$varAccess!')} : null'; + return '$varAccess != null ' + '? ${codec.encodeSerialization(this, type, '$varAccess$exclamationOrEmpty')} ' + ': null'; } } final serializer = '_\$serialize${type.displayName}'; return type.isNullable - ? '$varAccess != null ? $serializer($varAccess!) : null' + ? '$varAccess != null ? $serializer($varAccess$exclamationOrEmpty) : null' : '$serializer($varAccess)'; } } diff --git a/stripe_terminal/CHANGELOG.md b/stripe_terminal/CHANGELOG.md index 172c1af..1c384c3 100644 --- a/stripe_terminal/CHANGELOG.md +++ b/stripe_terminal/CHANGELOG.md @@ -1,7 +1,17 @@ -## 3.0.1 +## 3.1.0 +- feat: Mapped more exception codecs: Android (`collectInputsUnsupported`), IOS (`readerConnectionOfflineNeedsUpdate`, + `encryptionKeyFailure`, `encryptionKeyStillInitializing`) +- feat: Allow to customize tipping on `Terminal.collectPaymentMethod` method +- feat: Allow to update payment intent on `Terminal.collectPaymentMethod` method +- feat: Beta: Allow customer-initiated cancellation for PaymentIntent, SetupIntent, and Refund payment method collection + with internet readers. See customerCancellationEnabled: on collectPaymentMethod, collectSetupIntentPaymentMethod, and + collectRefundPaymentMethod Terminal methods. Note: This feature requires reader software version 2.17 or later to be + installed on your internet reader. Please contact us if you want to support customer-initiated cancellation. - fix(android): Fixes TapToPay error "Must have a country code to connect to reader" [#29](https://github.com/BreX900/mek-packages/issues/29) +- feat: Bumped [Android](https://github.com/stripe/stripe-terminal-android/blob/master/CHANGELOG.md#310---2023-10-10) + and [IOS](https://github.com/stripe/stripe-terminal-ios/blob/master/CHANGELOG.md#310-2023-10-10) sdks versions to `3.1.0` ## 3.0.0 - fix: Attached the delegate reader before trying the connection diff --git a/stripe_terminal/README.md b/stripe_terminal/README.md index e02d03b..17a1d78 100644 --- a/stripe_terminal/README.md +++ b/stripe_terminal/README.md @@ -12,8 +12,8 @@ more simply by supporting streams instead of callbacks for listeners ## Features All features of android and ios sdk are supported (Also the TapToPay feature) -- [Android sdk](https://github.com/stripe/stripe-terminal-android) version: 3.0.0 -- [IOS sdk](https://github.com/stripe/stripe-terminal-ios) version: 3.0.0 +- [Android sdk](https://github.com/stripe/stripe-terminal-android) version: 3.1.0 +- [IOS sdk](https://github.com/stripe/stripe-terminal-ios) version: 3.1.0 > Offline mode is not supported diff --git a/stripe_terminal/ROADMAP.md b/stripe_terminal/ROADMAP.md index c4eed8e..9458061 100644 --- a/stripe_terminal/ROADMAP.md +++ b/stripe_terminal/ROADMAP.md @@ -1,67 +1,35 @@ -#### Ready -- Update [README.md](./README.md) +# ROADMAP -#### Done -- https://github.com/BreX900/mek-packages/issues/11 +- *Backlog*: No plans to add support, try opening an issue. +- *Ready*: They will be implemented as soon as possible. +- *In progress*: They are being implemented +- *Done*: Implemented, you will find them in the next release -# Android - -## 3.0.0 - 2023-09-08 - -### Done -- Update: `Terminal.processPayment` has been renamed to [`Terminal.confirmPaymentIntent`](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/confirm-payment-intent.html). -- Update: `Terminal.processRefund` has been renamed to [`Terminal.confirmRefund`](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/confirm-refund.html). -- Update: The `minSdkVersion` has been updated to 26. This means that the SDK will no longer support devices running Android 7.1.2 (Nougat) or earlier. Older devices can continue to use the 2.x versions of the SDK while on the maintenance schedule. -- Update: [`TerminalListener.onUnexpectedReaderDisconnect()`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.callable/-terminal-listener/on-unexpected-reader-disconnect.html) will be invoked if a command cannot be sent to an internet reader. Previously, this callback was only invoked when a periodic status check failed. -- Update: [`Terminal.collectPaymentMethod`](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/collect-payment-method.html) now takes an optional non-null [`CollectConfiguration`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-collect-configuration/index.html) parameter. -- Update: [`Terminal.collectSetupIntentPaymentMethod`](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/collect-setup-intent-payment-method.html) now takes an optional non-null [`SetupIntentConfiguration`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-setup-intent-configuration/index.html) parameter. -- Update: For readers that require updates to be installed upon connecting, [`TerminalListener.onConnectionStatusChange()`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.callable/-terminal-listener/on-connection-status-change.html) will now be called with [`CONNECTED`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-connection-status/-c-o-n-n-e-c-t-e-d/index.html) _after_ the updates complete successfully, not before. -- Update: Deprecated classes and members have been replaced or removed: - - `CardDetails.fingerprint` and `CardPresentDetails.fingerprint` have been removed from mobile SDKs. You will still be able to access the fingerprint server-side. - - `TerminalApplicationDelegate.onTrimMemory()` has been removed. It is automatically managed by the SDK. - - The `locationId` parameter from the [`HandoffConnectionConfiguration`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-connection-configuration/-handoff-connection-configuration/-handoff-connection-configuration.html) constructor has been removed. - - `EmvBlob` has been marked as an internal class. - - `ConnectConfiguration.registerToLocation` has been removed and replaced with [`ConnectConfiguration.locationId`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-connection-configuration/location-id.html). - - `Reader.registeredLocation` has been removed and replaced with [`Reader.location`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-reader/location.html). - - The `CollectConfiguration` constructor has been removed. Use [`CollectConfiguration.Builder`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-collect-configuration/-builder/index.html) instead. - - [`CollectConfiguration.moto`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-collect-configuration/moto.html) is no longer mutable. - - `CaptureMethod.getManual()` has been removed. Use [`CaptureMethod.MANUAL`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-capture-method/-companion/-manual.html) instead. -- Update: `Terminal.readReusableCard` has been removed. This functionality is replaced by [Setup Intents](https://stripe.com/docs/terminal/features/saving-cards/save-cards-directly?terminal-sdk-platform=android). -- Update: [`DiscoveryConfiguration`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-discovery-configuration/index.html) has been converted to a sealed type, instead of relying on the `DiscoveryMethod` enum to disambiguate different discovery methods. - -### In progress - -### Ready -- Update: The [`PaymentIntent::id`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-payment-intent/id.html) is now nullable to support creating Payment Intents while offline. This feature is in an invite-only beta. See [Collect payments while offline](https://stripe.com/docs/terminal/features/operate-offline/collect-payments) for details. +## Android ### Backlog +- Update: The [`PaymentIntent::id`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-payment-intent/id.html) is now nullable to support creating Payment Intents while offline. This feature is in an invite-only beta. See [Collect payments while offline](https://stripe.com/docs/terminal/features/operate-offline/collect-payments) for details. - Update: Runtime permission checks have been moved from [`Terminal.initTerminal()`](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/-companion/init-terminal.html) to [`Terminal.discoverReaders()`](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/discover-readers.html). - Bluetooth permissions are now only required when discovering readers via [`BluetoothDiscoveryConfiguration`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-discovery-configuration/-bluetooth-discovery-configuration/index.html). - Location permissions will continue to be required for all [`DiscoveryConfigurations`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-discovery-configuration/index.html). Location services will also need to be enabled on the device at the time of discovery. -- `BluetoothReaderListener` and `UsbReaderListener` have been removed and replaced with [`ReaderListener`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.callable/-reader-listener/index.html). - (Not exist on ios) `Reader.device` has been removed and replaced with [`Reader.bluetoothDevice`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-reader/bluetooth-device.html) and [`Reader.usbDevice`](https://stripe.dev/stripe-terminal-android/external/com.stripe.stripeterminal.external.models/-reader/usb-device.html). -# Ios +### Ready + +### In progress + +### Done + +## Ios ### Backlog -* Update: Canceling `discoverReaders` now completes with an `SCPErrorCanceled` error. Previously no error was provided when canceled. +* Update: `SCPPaymentIntent.stripeId` is now nullable to support offline payments. * New: Private beta support for offline payments. * See [Collect payments while offline](https://stripe.com/docs/terminal/features/operate-offline/collect-payments) for details. ### Ready -* Update: `SCPPaymentIntent.stripeId` is now nullable to support offline payments. -* Update: Removed the `SCPErrorBusy` error. The SDK will now queue incoming commands if another command is already running. ### In progress ### Done -* Update: Configuration and parameter classes are now immutable and need to be built with builders. Example: To create `SCPPaymentIntentParameters` use `SCPPaymentIntentParametersBuilder` which has setters for all the parameters and a `build:` method to create the `SCPPaymentIntentParameters` instance. -* Update: `DiscoveryConfiguration` is now a protocol with concrete classes for each discovery method: `BluetoothScanDiscoveryConfiguration`, `BluetoothProximityDiscoveryConfiguration`, `InternetDiscoveryConfiguration`, and `LocalMobileDiscoveryConfiguration`. Each class has a `Builder` exposing only the configuration values that apply to that discovery method. -* Update: Removed `SCPErrorCannotConnectToUndiscoveredReader` and `SCPErrorMustBeDiscoveringToConnect` errors. The SDK now supports connecting to an `SCPReader` instance that was previously discovered without needing to restart discovery. -* Update: Removed `Terminal.readReusableCard`. This functionality is replaced by [SetupIntents](https://stripe.com/docs/terminal/features/saving-cards/save-cards-directly?terminal-sdk-platform=ios). -* Update: `ReconnectionDelegate` methods now provide the instance of the `Reader` that is being reconnected to instead of the `Terminal` instance. -* Update: Minimum deployment target updated from iOS 11.0 to iOS 13.0. -* Update: `Terminal.processPayment` has been renamed to `Terminal.confirmPaymentIntent`. -* Update: `Terminal.processRefund` has been renamed to `Terminal.confirmRefund`. -* Update: `discoverReaders` is now completed when `connectReader` is called. This is a behavior change from 2.x where `discoverReaders` would continue running until connect succeeded. If connect fails you can retry connecting to a previously discovered `SCPReader` or restart `discoverReaders`. -* Update: Removed `CardDetails.fingerprint` and `CardPresentDetails.fingerprint`. You will still be able to access the fingerprint server-side using [Stripe server-side SDKs](https://stripe.com/docs/libraries#server-side-libraries). + diff --git a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/TerminalPlugin.kt b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/TerminalPlugin.kt index 162a3c0..896e48b 100644 --- a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/TerminalPlugin.kt +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/TerminalPlugin.kt @@ -55,6 +55,7 @@ import mek.stripeterminal.api.RefundApi import mek.stripeterminal.api.SetupIntentApi import mek.stripeterminal.api.SetupIntentUsageApi import mek.stripeterminal.api.TerminalExceptionCodeApi +import mek.stripeterminal.api.TippingConfigurationApi import mek.stripeterminal.api.toPlatformError import mek.stripeterminal.plugin.DiscoverReadersSubject import mek.stripeterminal.plugin.TerminalDelegatePlugin @@ -82,7 +83,8 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { } if (permissionStatus.contains(PackageManager.PERMISSION_DENIED)) { - throw createApiError(TerminalExceptionCodeApi.UNKNOWN, + throw createApiError( + TerminalExceptionCodeApi.UNKNOWN, "You have declined the necessary permission, please allow from settings to continue.", ).toPlatformError() } @@ -312,12 +314,21 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { operationId: Long, paymentIntentId: String, skipTipping: Boolean, + tippingConfiguration: TippingConfigurationApi?, + shouldUpdatePaymentIntent: Boolean, + customerCancellationEnabled: Boolean, ) { val paymentIntent = findPaymentIntent(paymentIntentId) + val config = CollectConfiguration.Builder() + .skipTipping(skipTipping) + .setTippingConfiguration(tippingConfiguration?.toHost()) + .updatePaymentIntent(shouldUpdatePaymentIntent) + .setEnableCustomerCancellation(customerCancellationEnabled) _cancelablesCollectPaymentMethod[operationId] = _terminal.collectPaymentMethod( paymentIntent, - object : TerminalErrorHandler(result::error), PaymentIntentCallback { + config = config.build(), + callback = object : TerminalErrorHandler(result::error), PaymentIntentCallback { override fun onFailure(e: TerminalException) { _cancelablesCollectPaymentMethod.remove(operationId) super.onFailure(e) @@ -328,11 +339,7 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { result.success(paymentIntent.toApi()) _paymentIntents[paymentIntent.id!!] = paymentIntent } - }, - CollectConfiguration.Builder() - .skipTipping(skipTipping) - .build(), - ) + }) } override fun onStopCollectPaymentMethod(result: Result, operationId: Long) { @@ -357,6 +364,7 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { } super.onFailure(e) } + override fun onSuccess(paymentIntent: PaymentIntent) { result.success(paymentIntent.toApi()) _paymentIntents.remove(paymentIntent.id) @@ -422,19 +430,18 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { operationId: Long, setupIntentId: String, customerConsentCollected: Boolean, - isCustomerCancellationEnabled: Boolean?, + customerCancellationEnabled: Boolean, ) { val setupIntent = findSetupIntent(setupIntentId) - - val config = SetupIntentConfiguration.Builder(); - isCustomerCancellationEnabled?.let(config::setEnableCustomerCancellation) + val config = SetupIntentConfiguration.Builder() + .setEnableCustomerCancellation(customerCancellationEnabled) _cancelablesCollectSetupIntentPaymentMethod[operationId] = _terminal.collectSetupIntentPaymentMethod( setupIntent, - customerConsentCollected, + customerConsentCollected = customerConsentCollected, config = config.build(), - object : TerminalErrorHandler(result::error), SetupIntentCallback { + callback = object : TerminalErrorHandler(result::error), SetupIntentCallback { override fun onFailure(e: TerminalException) { _cancelablesCollectSetupIntentPaymentMethod.remove(operationId) super.onFailure(e) @@ -493,10 +500,10 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { metadata: HashMap?, reverseTransfer: Boolean?, refundApplicationFee: Boolean?, - isCustomerCancellationEnabled: Boolean?, + customerCancellationEnabled: Boolean, ) { val config = RefundConfiguration.Builder() - isCustomerCancellationEnabled?.let(config::setEnableCustomerCancellation) + .setEnableCustomerCancellation(customerCancellationEnabled) _cancelablesCollectRefundPaymentMethod[operationId] = _terminal.collectRefundPaymentMethod( RefundParameters.Builder( @@ -510,7 +517,7 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { it.build() }, config = config.build(), - object : TerminalErrorHandler(result::error), Callback { + callback = object : TerminalErrorHandler(result::error), Callback { override fun onFailure(e: TerminalException) { _cancelablesCollectRefundPaymentMethod.remove(operationId) super.onFailure(e) @@ -589,17 +596,20 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { private fun findActiveReader(serialNumber: String): Reader { val reader = _discoveredReaders.firstOrNull { it.serialNumber == serialNumber } - return reader ?: throw createApiError(TerminalExceptionCodeApi.READER_NOT_RECOVERED).toPlatformError() + return reader + ?: throw createApiError(TerminalExceptionCodeApi.READER_NOT_RECOVERED).toPlatformError() } private fun findPaymentIntent(paymentIntentId: String): PaymentIntent { val paymentIntent = _paymentIntents[paymentIntentId] - return paymentIntent ?: throw createApiError(TerminalExceptionCodeApi.PAYMENT_INTENT_NOT_RECOVERED).toPlatformError() + return paymentIntent + ?: throw createApiError(TerminalExceptionCodeApi.PAYMENT_INTENT_NOT_RECOVERED).toPlatformError() } private fun findSetupIntent(setupIntentId: String): SetupIntent { val setupIntent = _setupIntents[setupIntentId] - return setupIntent ?: throw createApiError(TerminalExceptionCodeApi.SETUP_INTENT_NOT_RECOVERED).toPlatformError() + return setupIntent + ?: throw createApiError(TerminalExceptionCodeApi.SETUP_INTENT_NOT_RECOVERED).toPlatformError() } private fun clean() { diff --git a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/TerminalApi.kt b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/TerminalApi.kt index 172b476..57b2a9c 100644 --- a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/TerminalApi.kt +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/TerminalApi.kt @@ -138,6 +138,9 @@ interface TerminalPlatformApi { operationId: Long, paymentIntentId: String, skipTipping: Boolean, + tippingConfiguration: TippingConfigurationApi?, + shouldUpdatePaymentIntent: Boolean, + customerCancellationEnabled: Boolean, ) fun onStopCollectPaymentMethod( @@ -174,7 +177,7 @@ interface TerminalPlatformApi { operationId: Long, setupIntentId: String, customerConsentCollected: Boolean, - isCustomerCancellationEnabled: Boolean?, + customerCancellationEnabled: Boolean, ) fun onStopCollectSetupIntentPaymentMethod( @@ -201,7 +204,7 @@ interface TerminalPlatformApi { metadata: HashMap?, reverseTransfer: Boolean?, refundApplicationFee: Boolean?, - isCustomerCancellationEnabled: Boolean?, + customerCancellationEnabled: Boolean, ) fun onStopCollectRefundPaymentMethod( @@ -309,7 +312,7 @@ interface TerminalPlatformApi { } "startCollectPaymentMethod" -> { val res = Result(result) { it.serialize() } - onStartCollectPaymentMethod(res, (args[0] as Number).toLong(), args[1] as String, args[2] as Boolean) + onStartCollectPaymentMethod(res, (args[0] as Number).toLong(), args[1] as String, args[2] as Boolean, (args[3] as List?)?.let { TippingConfigurationApi.deserialize(it) }, args[4] as Boolean, args[5] as Boolean) } "stopCollectPaymentMethod" -> { val res = Result(result) { null } @@ -333,7 +336,7 @@ interface TerminalPlatformApi { } "startCollectSetupIntentPaymentMethod" -> { val res = Result(result) { it.serialize() } - onStartCollectSetupIntentPaymentMethod(res, (args[0] as Number).toLong(), args[1] as String, args[2] as Boolean, args[3] as Boolean?) + onStartCollectSetupIntentPaymentMethod(res, (args[0] as Number).toLong(), args[1] as String, args[2] as Boolean, args[3] as Boolean) } "stopCollectSetupIntentPaymentMethod" -> { val res = Result(result) { null } @@ -349,7 +352,7 @@ interface TerminalPlatformApi { } "startCollectRefundPaymentMethod" -> { val res = Result(result) { null } - onStartCollectRefundPaymentMethod(res, (args[0] as Number).toLong(), args[1] as String, (args[2] as Number).toLong(), args[3] as String, args[4]?.let { hashMapOf(*(it as HashMap<*, *>).map { (k, v) -> k as String to v as String }.toTypedArray()) }, args[5] as Boolean?, args[6] as Boolean?, args[7] as Boolean?) + onStartCollectRefundPaymentMethod(res, (args[0] as Number).toLong(), args[1] as String, (args[2] as Number).toLong(), args[3] as String, args[4]?.let { hashMapOf(*(it as HashMap<*, *>).map { (k, v) -> k as String to v as String }.toTypedArray()) }, args[5] as Boolean?, args[6] as Boolean?, args[7] as Boolean) } "stopCollectRefundPaymentMethod" -> { val res = Result(result) { null } @@ -1186,7 +1189,21 @@ data class TerminalExceptionApi( } enum class TerminalExceptionCodeApi { - UNKNOWN, READER_NOT_RECOVERED, PAYMENT_INTENT_NOT_RECOVERED, SETUP_INTENT_NOT_RECOVERED, CANCEL_FAILED, NOT_CONNECTED_TO_READER, ALREADY_CONNECTED_TO_READER, BLUETOOTH_DISABLED, BLUETOOTH_PERMISSION_DENIED, CONFIRM_INVALID_PAYMENT_INTENT, INVALID_CLIENT_SECRET, INVALID_READER_FOR_UPDATE, UNSUPPORTED_OPERATION, UNEXPECTED_OPERATION, UNSUPPORTED_SDK, FEATURE_NOT_AVAILABLE_WITH_CONNECTED_READER, USB_PERMISSION_DENIED, USB_DISCOVERY_TIMED_OUT, INVALID_PARAMETER, INVALID_REQUIRED_PARAMETER, INVALID_TIP_PARAMETER, LOCAL_MOBILE_UNSUPPORTED_DEVICE, LOCAL_MOBILE_UNSUPPORTED_OPERATING_SYSTEM_VERSION, LOCAL_MOBILE_DEVICE_TAMPERED, LOCAL_MOBILE_DEBUG_NOT_SUPPORTED, OFFLINE_MODE_UNSUPPORTED_OPERATING_SYSTEM_VERSION, CANCELED, LOCATION_SERVICES_DISABLED, BLUETOOTH_SCAN_TIMED_OUT, BLUETOOTH_LOW_ENERGY_UNSUPPORTED, READER_SOFTWARE_UPDATE_FAILED_BATTERY_LOW, READER_SOFTWARE_UPDATE_FAILED_INTERRUPTED, READER_SOFTWARE_UPDATE_FAILED_EXPIRED_UPDATE, BLUETOOTH_CONNECTION_FAILED_BATTERY_CRITICALLY_LOW, CARD_INSERT_NOT_READ, CARD_SWIPE_NOT_READ, CARD_READ_TIMED_OUT, CARD_REMOVED, CUSTOMER_CONSENT_REQUIRED, CARD_LEFT_IN_READER, FEATURE_NOT_ENABLED_ON_ACCOUNT, PASSCODE_NOT_ENABLED, COMMAND_NOT_ALLOWED_DURING_CALL, INVALID_AMOUNT, INVALID_CURRENCY, APPLE_BUILT_IN_READER_T_O_S_ACCEPTANCE_REQUIRESI_CLOUD_SIGN_IN, APPLE_BUILT_IN_READER_T_O_S_ACCEPTANCE_CANCELED, APPLE_BUILT_IN_READER_FAILED_TO_PREPARE, APPLE_BUILT_IN_READER_DEVICE_BANNED, APPLE_BUILT_IN_READER_T_O_S_NOT_YET_ACCEPTED, APPLE_BUILT_IN_READER_T_O_S_ACCEPTANCE_FAILED, APPLE_BUILT_IN_READER_MERCHANT_BLOCKED, APPLE_BUILT_IN_READER_INVALID_MERCHANT, READER_BUSY, INCOMPATIBLE_READER, READER_COMMUNICATION_ERROR, UNKNOWN_READER_IP_ADDRESS, INTERNET_CONNECT_TIME_OUT, CONNECT_FAILED_READER_IS_IN_USE, READER_NOT_ACCESSIBLE_IN_BACKGROUND, BLUETOOTH_ERROR, BLUETOOTH_CONNECT_TIMED_OUT, BLUETOOTH_DISCONNECTED, BLUETOOTH_PEER_REMOVED_PAIRING_INFORMATION, BLUETOOTH_ALREADY_PAIRED_WITH_ANOTHER_DEVICE, BLUETOOTH_RECONNECT_STARTED, USB_DISCONNECTED, USB_RECONNECT_STARTED, READER_CONNECTED_TO_ANOTHER_DEVICE, READER_SOFTWARE_UPDATE_FAILED, READER_SOFTWARE_UPDATE_FAILED_READER_ERROR, READER_SOFTWARE_UPDATE_FAILED_SERVER_ERROR, NFC_DISABLED, UNSUPPORTED_READER_VERSION, UNEXPECTED_SDK_ERROR, UNEXPECTED_READER_ERROR, DECLINED_BY_STRIPE_API, DECLINED_BY_READER, NOT_CONNECTED_TO_INTERNET, REQUEST_TIMED_OUT, STRIPE_API_CONNECTION_ERROR, STRIPE_API_ERROR, STRIPE_API_RESPONSE_DECODING_ERROR, INTERNAL_NETWORK_ERROR, CONNECTION_TOKEN_PROVIDER_ERROR, SESSION_EXPIRED, UNSUPPORTED_MOBILE_DEVICE_CONFIGURATION, COMMAND_NOT_ALLOWED, AMOUNT_EXCEEDS_MAX_OFFLINE_AMOUNT, OFFLINE_PAYMENTS_DATABASE_TOO_LARGE, READER_CONNECTION_NOT_AVAILABLE_OFFLINE, READER_CONNECTION_OFFLINE_LOCATION_MISMATCH, LOCATION_CONNECTION_NOT_AVAILABLE_OFFLINE, NO_LAST_SEEN_ACCOUNT, INVALID_OFFLINE_CURRENCY, REFUND_FAILED, CARD_SWIPE_NOT_AVAILABLE, INTERAC_NOT_SUPPORTED_OFFLINE, ONLINE_PIN_NOT_SUPPORTED_OFFLINE, OFFLINE_AND_CARD_EXPIRED, OFFLINE_TRANSACTION_DECLINED, OFFLINE_COLLECT_AND_CONFIRM_MISMATCH, FORWARDING_TEST_MODE_PAYMENT_IN_LIVE_MODE, FORWARDING_LIVE_MODE_PAYMENT_IN_TEST_MODE, OFFLINE_PAYMENT_INTENT_NOT_FOUND, UPDATE_PAYMENT_INTENT_UNAVAILABLE_WHILE_OFFLINE, UPDATE_PAYMENT_INTENT_UNAVAILABLE_WHILE_OFFLINE_MODE_ENABLED, MISSING_EMV_DATA, CONNECTION_TOKEN_PROVIDER_ERROR_WHILE_FORWARDING, CONNECTION_TOKEN_PROVIDER_TIMED_OUT, ACCOUNT_ID_MISMATCH_WHILE_FORWARDING, OFFLINE_BEHAVIOR_FORCE_OFFLINE_WITH_FEATURE_DISABLED, NOT_CONNECTED_TO_INTERNET_AND_OFFLINE_BEHAVIOR_REQUIRE_ONLINE, TEST_CARD_IN_LIVE_MODE, COLLECT_INPUTS_APPLICATION_ERROR, COLLECT_INPUTS_TIMED_OUT; + UNKNOWN, READER_NOT_RECOVERED, PAYMENT_INTENT_NOT_RECOVERED, SETUP_INTENT_NOT_RECOVERED, CANCEL_FAILED, NOT_CONNECTED_TO_READER, ALREADY_CONNECTED_TO_READER, BLUETOOTH_DISABLED, BLUETOOTH_PERMISSION_DENIED, CONFIRM_INVALID_PAYMENT_INTENT, INVALID_CLIENT_SECRET, INVALID_READER_FOR_UPDATE, UNSUPPORTED_OPERATION, UNEXPECTED_OPERATION, UNSUPPORTED_SDK, FEATURE_NOT_AVAILABLE_WITH_CONNECTED_READER, USB_PERMISSION_DENIED, USB_DISCOVERY_TIMED_OUT, INVALID_PARAMETER, INVALID_REQUIRED_PARAMETER, INVALID_TIP_PARAMETER, LOCAL_MOBILE_UNSUPPORTED_DEVICE, LOCAL_MOBILE_UNSUPPORTED_OPERATING_SYSTEM_VERSION, LOCAL_MOBILE_DEVICE_TAMPERED, LOCAL_MOBILE_DEBUG_NOT_SUPPORTED, OFFLINE_MODE_UNSUPPORTED_OPERATING_SYSTEM_VERSION, CANCELED, LOCATION_SERVICES_DISABLED, BLUETOOTH_SCAN_TIMED_OUT, BLUETOOTH_LOW_ENERGY_UNSUPPORTED, READER_SOFTWARE_UPDATE_FAILED_BATTERY_LOW, READER_SOFTWARE_UPDATE_FAILED_INTERRUPTED, READER_SOFTWARE_UPDATE_FAILED_EXPIRED_UPDATE, BLUETOOTH_CONNECTION_FAILED_BATTERY_CRITICALLY_LOW, CARD_INSERT_NOT_READ, CARD_SWIPE_NOT_READ, CARD_READ_TIMED_OUT, CARD_REMOVED, CUSTOMER_CONSENT_REQUIRED, CARD_LEFT_IN_READER, FEATURE_NOT_ENABLED_ON_ACCOUNT, PASSCODE_NOT_ENABLED, COMMAND_NOT_ALLOWED_DURING_CALL, INVALID_AMOUNT, INVALID_CURRENCY, APPLE_BUILT_IN_READER_T_O_S_ACCEPTANCE_REQUIRESI_CLOUD_SIGN_IN, APPLE_BUILT_IN_READER_T_O_S_ACCEPTANCE_CANCELED, APPLE_BUILT_IN_READER_FAILED_TO_PREPARE, APPLE_BUILT_IN_READER_DEVICE_BANNED, APPLE_BUILT_IN_READER_T_O_S_NOT_YET_ACCEPTED, APPLE_BUILT_IN_READER_T_O_S_ACCEPTANCE_FAILED, APPLE_BUILT_IN_READER_MERCHANT_BLOCKED, APPLE_BUILT_IN_READER_INVALID_MERCHANT, READER_BUSY, INCOMPATIBLE_READER, READER_COMMUNICATION_ERROR, UNKNOWN_READER_IP_ADDRESS, INTERNET_CONNECT_TIME_OUT, CONNECT_FAILED_READER_IS_IN_USE, READER_NOT_ACCESSIBLE_IN_BACKGROUND, BLUETOOTH_ERROR, BLUETOOTH_CONNECT_TIMED_OUT, BLUETOOTH_DISCONNECTED, BLUETOOTH_PEER_REMOVED_PAIRING_INFORMATION, BLUETOOTH_ALREADY_PAIRED_WITH_ANOTHER_DEVICE, BLUETOOTH_RECONNECT_STARTED, USB_DISCONNECTED, USB_RECONNECT_STARTED, READER_CONNECTED_TO_ANOTHER_DEVICE, READER_SOFTWARE_UPDATE_FAILED, READER_SOFTWARE_UPDATE_FAILED_READER_ERROR, READER_SOFTWARE_UPDATE_FAILED_SERVER_ERROR, NFC_DISABLED, UNSUPPORTED_READER_VERSION, UNEXPECTED_SDK_ERROR, UNEXPECTED_READER_ERROR, ENCRYPTION_KEY_FAILURE, ENCRYPTION_KEY_STILL_INITIALIZING, DECLINED_BY_STRIPE_API, DECLINED_BY_READER, OFFLINE_TEST_CARD_IN_LIVEMODE, NOT_CONNECTED_TO_INTERNET, REQUEST_TIMED_OUT, STRIPE_API_CONNECTION_ERROR, STRIPE_API_ERROR, STRIPE_API_RESPONSE_DECODING_ERROR, INTERNAL_NETWORK_ERROR, CONNECTION_TOKEN_PROVIDER_ERROR, SESSION_EXPIRED, UNSUPPORTED_MOBILE_DEVICE_CONFIGURATION, COMMAND_NOT_ALLOWED, AMOUNT_EXCEEDS_MAX_OFFLINE_AMOUNT, OFFLINE_PAYMENTS_DATABASE_TOO_LARGE, READER_CONNECTION_NOT_AVAILABLE_OFFLINE, READER_CONNECTION_OFFLINE_LOCATION_MISMATCH, READER_CONNECTION_OFFLINE_NEEDS_UPDATE, LOCATION_CONNECTION_NOT_AVAILABLE_OFFLINE, NO_LAST_SEEN_ACCOUNT, INVALID_OFFLINE_CURRENCY, REFUND_FAILED, CARD_SWIPE_NOT_AVAILABLE, INTERAC_NOT_SUPPORTED_OFFLINE, ONLINE_PIN_NOT_SUPPORTED_OFFLINE, OFFLINE_AND_CARD_EXPIRED, OFFLINE_TRANSACTION_DECLINED, OFFLINE_COLLECT_AND_CONFIRM_MISMATCH, FORWARDING_TEST_MODE_PAYMENT_IN_LIVE_MODE, FORWARDING_LIVE_MODE_PAYMENT_IN_TEST_MODE, OFFLINE_PAYMENT_INTENT_NOT_FOUND, UPDATE_PAYMENT_INTENT_UNAVAILABLE_WHILE_OFFLINE, UPDATE_PAYMENT_INTENT_UNAVAILABLE_WHILE_OFFLINE_MODE_ENABLED, MISSING_EMV_DATA, CONNECTION_TOKEN_PROVIDER_ERROR_WHILE_FORWARDING, CONNECTION_TOKEN_PROVIDER_TIMED_OUT, ACCOUNT_ID_MISMATCH_WHILE_FORWARDING, OFFLINE_BEHAVIOR_FORCE_OFFLINE_WITH_FEATURE_DISABLED, NOT_CONNECTED_TO_INTERNET_AND_OFFLINE_BEHAVIOR_REQUIRE_ONLINE, TEST_CARD_IN_LIVE_MODE, COLLECT_INPUTS_APPLICATION_ERROR, COLLECT_INPUTS_TIMED_OUT, COLLECT_INPUTS_UNSUPPORTED; +} + +data class TippingConfigurationApi( + val eligibleAmount: Long, +) { + companion object { + fun deserialize( + serialized: List, + ): TippingConfigurationApi { + return TippingConfigurationApi( + eligibleAmount = (serialized[0] as Number).toLong(), + ) + } + } } enum class UpdateComponentApi { diff --git a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/ToApiExtensions.kt b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/ToApiExtensions.kt index 183dd37..59747f1 100644 --- a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/ToApiExtensions.kt +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/ToApiExtensions.kt @@ -19,12 +19,8 @@ fun TerminalExceptionApi.toPlatformError(): PlatformError { fun TerminalException.toApi(): TerminalExceptionApi { val code = errorCode.toApiCode() - ?: return createApiError( - TerminalExceptionCodeApi.UNKNOWN, - "Unsupported Terminal exception code: $errorCode" - ) return TerminalExceptionApi( - code = code, + code = code ?: TerminalExceptionCodeApi.UNKNOWN, message = errorMessage, stackTrace = stackTraceToString(), paymentIntent = paymentIntent?.toApi(), @@ -44,6 +40,7 @@ private fun TerminalErrorCode.toApiCode(): TerminalExceptionCodeApi? { TerminalErrorCode.UNEXPECTED_OPERATION -> TerminalExceptionCodeApi.UNEXPECTED_OPERATION TerminalErrorCode.UNSUPPORTED_SDK -> TerminalExceptionCodeApi.UNSUPPORTED_SDK TerminalErrorCode.USB_PERMISSION_DENIED -> TerminalExceptionCodeApi.USB_PERMISSION_DENIED + TerminalErrorCode.MISSING_PREREQUISITE -> null TerminalErrorCode.MISSING_REQUIRED_PARAMETER -> TerminalExceptionCodeApi.INVALID_PARAMETER TerminalErrorCode.INVALID_REQUIRED_PARAMETER -> TerminalExceptionCodeApi.INVALID_REQUIRED_PARAMETER TerminalErrorCode.INVALID_TIP_PARAMETER -> TerminalExceptionCodeApi.INVALID_TIP_PARAMETER @@ -114,6 +111,7 @@ private fun TerminalErrorCode.toApiCode(): TerminalExceptionCodeApi? { TerminalErrorCode.COLLECT_INPUTS_APPLICATION_ERROR -> TerminalExceptionCodeApi.COLLECT_INPUTS_APPLICATION_ERROR TerminalErrorCode.COLLECT_INPUTS_TIMED_OUT -> TerminalExceptionCodeApi.COLLECT_INPUTS_TIMED_OUT TerminalErrorCode.COLLECT_INPUTS_INVALID_PARAMETER -> TerminalExceptionCodeApi.INVALID_PARAMETER + TerminalErrorCode.COLLECT_INPUTS_UNSUPPORTED -> TerminalExceptionCodeApi.COLLECT_INPUTS_UNSUPPORTED } } diff --git a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/ToHostExtensions.kt b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/ToHostExtensions.kt index 0d63bbd..ad25099 100644 --- a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/ToHostExtensions.kt +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/api/ToHostExtensions.kt @@ -108,6 +108,12 @@ fun PaymentMethodOptionsParametersApi.toHost(): PaymentMethodOptionsParameters { .build() } +fun TippingConfigurationApi.toHost(): TippingConfiguration { + return TippingConfiguration.Builder() + .setEligibleAmount(eligibleAmount) + .build() +} + fun CardPresentParametersApi.toHost(): CardPresentParameters { val b = CardPresentParameters.Builder() captureMethod?.let { b.setCaptureMethod(it.toHost()) } diff --git a/stripe_terminal/example/ios/Podfile.lock b/stripe_terminal/example/ios/Podfile.lock index a3177d4..69b9628 100644 --- a/stripe_terminal/example/ios/Podfile.lock +++ b/stripe_terminal/example/ios/Podfile.lock @@ -1,11 +1,11 @@ PODS: - Flutter (1.0.0) - - mek_stripe_terminal (0.0.1): + - mek_stripe_terminal (3.1.0): - Flutter - - StripeTerminal (~> 3.0.0) + - StripeTerminal (~> 3.1.0) - permission_handler_apple (9.1.1): - Flutter - - StripeTerminal (3.0.0) + - StripeTerminal (3.1.0) DEPENDENCIES: - Flutter (from `Flutter`) @@ -26,9 +26,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - mek_stripe_terminal: a779842af706144f9781a8929a52ba98fb70c634 + mek_stripe_terminal: 83c57b917dc236752c47758613dc9c249c146883 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 - StripeTerminal: e0b204eefa71559d35f864f6a421f2a5202579a0 + StripeTerminal: fa064cf68a7a3df7c2ee1b2e6d33c8866d1e6aef PODFILE CHECKSUM: cf4dc61115723a2c178f9ac6bcd06534dfb5b047 diff --git a/stripe_terminal/example/lib/main.dart b/stripe_terminal/example/lib/main.dart index 8969acb..84e3d3e 100644 --- a/stripe_terminal/example/lib/main.dart +++ b/stripe_terminal/example/lib/main.dart @@ -84,11 +84,16 @@ class _MyAppState extends State { setState(() => _terminal = terminal); _onConnectionStatusChangeSub = terminal.onConnectionStatusChange.listen((status) { print('Connection Status Changed: ${status.name}'); - setState(() => _connectionStatus = status); + setState(() { + _connectionStatus = status; + if (_connectionStatus == ConnectionStatus.notConnected) { + _readers = const []; + _reader = null; + } + }); }); _onUnexpectedReaderDisconnectSub = terminal.onUnexpectedReaderDisconnect.listen((reader) { print('Reader Unexpected Disconnected: ${reader.label}'); - setState(() => _reader = null); }); _onPaymentStatusChangeSub = terminal.onPaymentStatusChange.listen((status) { print('Payment Status Changed: ${status.name}'); @@ -201,14 +206,20 @@ class _MyAppState extends State { _discoverReaderSub = discoverReaderStream.listen((readers) { setState(() => _readers = readers); }, onDone: () { - setState(() => _discoverReaderSub = null); + setState(() { + _discoverReaderSub = null; + _readers = const []; + }); }); }); } void _stopDiscoverReaders() { unawaited(_discoverReaderSub?.cancel()); - setState(() => _discoverReaderSub = null); + setState(() { + _discoverReaderSub = null; + _readers = const []; + }); } void _createPaymentIntent(Terminal terminal) async { @@ -260,7 +271,7 @@ class _MyAppState extends State { await cancelable.cancel(); } - void _processPayment(Terminal terminal, PaymentIntent paymentIntent) async { + void _confirmPaymentIntent(Terminal terminal, PaymentIntent paymentIntent) async { final processedPaymentIntent = await terminal.confirmPaymentIntent(paymentIntent); setState(() => _paymentIntent = processedPaymentIntent); _showSnackBar('Payment processed!'); @@ -310,11 +321,6 @@ class _MyAppState extends State { onPressed: terminal != null ? () => _checkStatus(terminal) : null, child: Text('Check status (${_connectionStatus.name})'), ), - ListTile( - onTap: _changeMode, - title: const Text('Scanning mode'), - trailing: Text(_isSimulated ? 'Simulator' : 'Real'), - ), DropdownButton( value: _discoveringMethod, onChanged: _changeDiscoveryMethod, @@ -325,6 +331,12 @@ class _MyAppState extends State { ); }).toList(), ), + if (_discoveringMethod.canSimulate) + ListTile( + onTap: _changeMode, + title: const Text('Scanning mode'), + trailing: Text(_isSimulated ? 'Simulator' : 'Real'), + ), if (_connectionStatus != ConnectionStatus.notConnected) TextButton( onPressed: terminal != null && _connectionStatus == ConnectionStatus.connected @@ -391,9 +403,9 @@ class _MyAppState extends State { onPressed: terminal != null && paymentIntent != null && paymentIntent.status == PaymentIntentStatus.requiresConfirmation - ? () => _processPayment(terminal, paymentIntent) + ? () => _confirmPaymentIntent(terminal, paymentIntent) : null, - child: const Text('Process Payment'), + child: const Text('Confirm PaymentIntent'), ), const Divider(), if (paymentIntent != null) diff --git a/stripe_terminal/example/lib/models/discovery_method.dart b/stripe_terminal/example/lib/models/discovery_method.dart index ea7b70f..33f3243 100644 --- a/stripe_terminal/example/lib/models/discovery_method.dart +++ b/stripe_terminal/example/lib/models/discovery_method.dart @@ -3,20 +3,24 @@ import 'package:mek_stripe_terminal/mek_stripe_terminal.dart'; /// [DiscoveryConfiguration] enum DiscoveryMethod { /// [BluetoothDiscoveryConfiguration] - bluetoothScan, + bluetoothScan(canSimulate: true), /// [BluetoothProximityDiscoveryConfiguration] - bluetoothProximity, + bluetoothProximity(canSimulate: true), /// [HandoffDiscoveryConfiguration] - handOff, + handOff(), /// [InternetDiscoveryConfiguration] - internet, + internet(canSimulate: true), /// [LocalMobileDiscoveryConfiguration] - localMobile, + localMobile(canSimulate: true), /// [UsbDiscoveryConfiguration] - usb, + usb(canSimulate: true); + + final bool canSimulate; + + const DiscoveryMethod({this.canSimulate = false}); } diff --git a/stripe_terminal/example/pubspec.lock b/stripe_terminal/example/pubspec.lock index c78af4e..dc9d77a 100644 --- a/stripe_terminal/example/pubspec.lock +++ b/stripe_terminal/example/pubspec.lock @@ -140,7 +140,7 @@ packages: path: ".." relative: true source: path - version: "3.0.1" + version: "3.1.0" meta: dependency: transitive description: diff --git a/stripe_terminal/ios/Classes/Api/TerminalApi.swift b/stripe_terminal/ios/Classes/Api/TerminalApi.swift index bcb2d2c..a8c2987 100644 --- a/stripe_terminal/ios/Classes/Api/TerminalApi.swift +++ b/stripe_terminal/ios/Classes/Api/TerminalApi.swift @@ -160,7 +160,10 @@ protocol TerminalPlatformApi { _ result: Result, _ operationId: Int, _ paymentIntentId: String, - _ skipTipping: Bool + _ skipTipping: Bool, + _ tippingConfiguration: TippingConfigurationApi?, + _ shouldUpdatePaymentIntent: Bool, + _ customerCancellationEnabled: Bool ) throws func onStopCollectPaymentMethod( @@ -192,7 +195,7 @@ protocol TerminalPlatformApi { _ operationId: Int, _ setupIntentId: String, _ customerConsentCollected: Bool, - _ isCustomerCancellationEnabled: Bool? + _ customerCancellationEnabled: Bool ) throws func onStopCollectSetupIntentPaymentMethod( @@ -216,7 +219,7 @@ protocol TerminalPlatformApi { _ metadata: [String: String]?, _ reverseTransfer: Bool?, _ refundApplicationFee: Bool?, - _ isCustomerCancellationEnabled: Bool? + _ customerCancellationEnabled: Bool ) throws func onStopCollectRefundPaymentMethod( @@ -363,7 +366,7 @@ func setTerminalPlatformApiHandler( } case "startCollectPaymentMethod": let res = Result(result) { $0.serialize() } - try hostApi.onStartCollectPaymentMethod(res, args[0] as! Int, args[1] as! String, args[2] as! Bool) + try hostApi.onStartCollectPaymentMethod(res, args[0] as! Int, args[1] as! String, args[2] as! Bool, !(args[3] is NSNull) ? TippingConfigurationApi.deserialize(args[3] as! [Any?]) : nil, args[4] as! Bool, args[5] as! Bool) case "stopCollectPaymentMethod": runAsync { try await hostApi.onStopCollectPaymentMethod(args[0] as! Int) @@ -391,7 +394,7 @@ func setTerminalPlatformApiHandler( } case "startCollectSetupIntentPaymentMethod": let res = Result(result) { $0.serialize() } - try hostApi.onStartCollectSetupIntentPaymentMethod(res, args[0] as! Int, args[1] as! String, args[2] as! Bool, args[3] as? Bool) + try hostApi.onStartCollectSetupIntentPaymentMethod(res, args[0] as! Int, args[1] as! String, args[2] as! Bool, args[3] as! Bool) case "stopCollectSetupIntentPaymentMethod": runAsync { try await hostApi.onStopCollectSetupIntentPaymentMethod(args[0] as! Int) @@ -409,7 +412,7 @@ func setTerminalPlatformApiHandler( } case "startCollectRefundPaymentMethod": let res = Result(result) { nil } - try hostApi.onStartCollectRefundPaymentMethod(res, args[0] as! Int, args[1] as! String, args[2] as! Int, args[3] as! String, !(args[4] is NSNull) ? Dictionary(uniqueKeysWithValues: (args[4] as! [AnyHashable?: Any?]).map { k, v in (k as! String, v as! String) }) : nil, args[5] as? Bool, args[6] as? Bool, args[7] as? Bool) + try hostApi.onStartCollectRefundPaymentMethod(res, args[0] as! Int, args[1] as! String, args[2] as! Int, args[3] as! String, !(args[4] is NSNull) ? Dictionary(uniqueKeysWithValues: (args[4] as! [AnyHashable?: Any?]).map { k, v in (k as! String, v as! String) }) : nil, args[5] as? Bool, args[6] as? Bool, args[7] as! Bool) case "stopCollectRefundPaymentMethod": runAsync { try await hostApi.onStopCollectRefundPaymentMethod(args[0] as! Int) @@ -1349,8 +1352,11 @@ enum TerminalExceptionCodeApi: Int { case unsupportedReaderVersion case unexpectedSdkError case unexpectedReaderError + case encryptionKeyFailure + case encryptionKeyStillInitializing case declinedByStripeApi case declinedByReader + case offlineTestCardInLivemode case notConnectedToInternet case requestTimedOut case stripeApiConnectionError @@ -1365,6 +1371,7 @@ enum TerminalExceptionCodeApi: Int { case offlinePaymentsDatabaseTooLarge case readerConnectionNotAvailableOffline case readerConnectionOfflineLocationMismatch + case readerConnectionOfflineNeedsUpdate case locationConnectionNotAvailableOffline case noLastSeenAccount case invalidOfflineCurrency @@ -1389,6 +1396,19 @@ enum TerminalExceptionCodeApi: Int { case testCardInLiveMode case collectInputsApplicationError case collectInputsTimedOut + case collectInputsUnsupported +} + +struct TippingConfigurationApi { + let eligibleAmount: Int + + static func deserialize( + _ serialized: [Any?] + ) -> TippingConfigurationApi { + return TippingConfigurationApi( + eligibleAmount: serialized[0] as! Int + ) + } } enum UpdateComponentApi: Int { diff --git a/stripe_terminal/ios/Classes/Api/ToApiExtensions.swift b/stripe_terminal/ios/Classes/Api/ToApiExtensions.swift index 8b566be..b14617c 100644 --- a/stripe_terminal/ios/Classes/Api/ToApiExtensions.swift +++ b/stripe_terminal/ios/Classes/Api/ToApiExtensions.swift @@ -566,16 +566,13 @@ extension NSError { func toApi(apiError: Error? = nil, paymentIntent: PaymentIntent? = nil) -> TerminalExceptionApi { let code = self.toApiCode(); - if let code { - return TerminalExceptionApi( - code: code, - message: localizedDescription, - stackTrace: nil, - paymentIntent: paymentIntent?.toApi(), - apiError: apiError?.localizedDescription - ) - } - return createApiException(TerminalExceptionCodeApi.unknown, "Unsupported Terminal exception code: \(self.code)") + return TerminalExceptionApi( + code: code ?? TerminalExceptionCodeApi.unknown, + message: localizedDescription, + stackTrace: nil, + paymentIntent: paymentIntent?.toApi(), + apiError: apiError?.localizedDescription + ) } private func toApiCode() -> TerminalExceptionCodeApi? { @@ -621,6 +618,8 @@ extension NSError { return .invalidParameter case .invalidRequiredParameter: return .invalidRequiredParameter + case .invalidRequiredParameterOnBehalfOf: + return .invalidParameter case .accountIdMismatchWhileForwarding: return .accountIdMismatchWhileForwarding case .updatePaymentIntentUnavailableWhileOffline: @@ -673,6 +672,8 @@ extension NSError { return .readerConnectionNotAvailableOffline case .readerConnectionOfflineLocationMismatch: return .readerConnectionOfflineLocationMismatch + case .readerConnectionOfflineNeedsUpdate: + return .readerConnectionOfflineNeedsUpdate case .noLastSeenAccount: return .noLastSeenAccount case .amountExceedsMaxOfflineAmount: @@ -749,6 +750,10 @@ extension NSError { return .unexpectedSdkError case .unexpectedReaderError: return .unexpectedReaderError + case .encryptionKeyFailure: + return .encryptionKeyFailure + case .encryptionKeyStillInitializing: + return .encryptionKeyStillInitializing case .declinedByStripeAPI: return .declinedByStripeApi case .declinedByReader: @@ -767,6 +772,10 @@ extension NSError { return .offlineTransactionDeclined case .offlineCollectAndConfirmMismatch: return .offlineCollectAndConfirmMismatch + case .onlinePinNotSupportedOffline: + return .onlinePinNotSupportedOffline + case .offlineTestCardInLivemode: + return .testCardInLiveMode case .notConnectedToInternet: return .notConnectedToInternet case .requestTimedOut: diff --git a/stripe_terminal/ios/Classes/Api/ToHostExtensions.swift b/stripe_terminal/ios/Classes/Api/ToHostExtensions.swift index d4d115a..ffb91ab 100644 --- a/stripe_terminal/ios/Classes/Api/ToHostExtensions.swift +++ b/stripe_terminal/ios/Classes/Api/ToHostExtensions.swift @@ -25,6 +25,14 @@ extension PaymentIntentParametersApi { } } +extension TippingConfigurationApi { + func toHost() throws -> TippingConfiguration { + return try TippingConfigurationBuilder() + .setEligibleAmount(eligibleAmount) + .build() + } +} + extension PaymentMethodOptionsParametersApi { func toHost() throws -> PaymentMethodOptionsParameters { return try PaymentMethodOptionsParametersBuilder( diff --git a/stripe_terminal/ios/Classes/TerminalPlugin.swift b/stripe_terminal/ios/Classes/TerminalPlugin.swift index de34ae4..4a1b7d5 100644 --- a/stripe_terminal/ios/Classes/TerminalPlugin.swift +++ b/stripe_terminal/ios/Classes/TerminalPlugin.swift @@ -224,10 +224,22 @@ public class TerminalPlugin: NSObject, FlutterPlugin, TerminalPlatformApi { _ result: Result, _ operationId: Int, _ paymentIntentId: String, - _ skipTipping: Bool + _ skipTipping: Bool, + _ tippingConfiguration: TippingConfigurationApi?, + _ shouldUpdatePaymentIntent: Bool, + _ customerCancellationEnabled: Bool ) throws { let paymentIntent = try _findPaymentIntent(paymentIntentId) - self._cancelablesCollectPaymentMethod[operationId] = Terminal.shared.collectPaymentMethod(paymentIntent) { paymentIntent, error in + let config = CollectConfigurationBuilder() + .setSkipTipping(skipTipping) + .setTippingConfiguration(try tippingConfiguration?.toHost()) + .setUpdatePaymentIntent(shouldUpdatePaymentIntent) + .setEnableCustomerCancellation(customerCancellationEnabled) + + self._cancelablesCollectPaymentMethod[operationId] = Terminal.shared.collectPaymentMethod( + paymentIntent, + collectConfig: try config.build(), + completion: { paymentIntent, error in self._cancelablesCollectPaymentMethod.removeValue(forKey: operationId) if let error = error as? NSError { result.error(error.toPlatformError()) @@ -235,7 +247,7 @@ public class TerminalPlugin: NSObject, FlutterPlugin, TerminalPlatformApi { } self._paymentIntents[paymentIntent!.stripeId!] = paymentIntent! result.success(paymentIntent!.toApi()) - } + }) } func onStopCollectPaymentMethod( @@ -315,12 +327,16 @@ public class TerminalPlugin: NSObject, FlutterPlugin, TerminalPlatformApi { _ operationId: Int, _ setupIntentId: String, _ customerConsentCollected: Bool, - _ isCustomerCancellationEnabled: Bool? + _ customerCancellationEnabled: Bool ) throws { let setupIntent = try _findSetupIntent(setupIntentId) + let config = SetupIntentConfigurationBuilder() + .setEnableCustomerCancellation(customerCancellationEnabled) + _cancelablesCollectSetupIntentPaymentMethod[operationId] = Terminal.shared.collectSetupIntentPaymentMethod( setupIntent, customerConsentCollected: customerConsentCollected, + setupConfig: try config.build(), completion: { setupIntent, error in self._cancelablesCollectSetupIntentPaymentMethod.removeValue(forKey: operationId) if let error = error as? NSError { @@ -329,8 +345,7 @@ public class TerminalPlugin: NSObject, FlutterPlugin, TerminalPlatformApi { } self._setupIntents[setupIntent!.stripeId] = setupIntent! result.success(setupIntent!.toApi()) - } - ) + }) } func onStopCollectSetupIntentPaymentMethod(_ operationId: Int) async throws { @@ -366,17 +381,22 @@ public class TerminalPlugin: NSObject, FlutterPlugin, TerminalPlatformApi { _ chargeId: String, _ amount: Int, _ currency: String, - _ metadata: [String : String]?, + _ metadata: [String: String]?, _ reverseTransfer: Bool?, _ refundApplicationFee: Bool?, - _ isCustomerCancellationEnabled: Bool? + _ customerCancellationEnabled: Bool ) throws { let params = RefundParametersBuilder(chargeId: chargeId, amount: UInt(amount), currency: currency) params.setMetadata(metadata) reverseTransfer.apply(params.setReverseTransfer) params.setMetadata(metadata) + + let config = RefundConfigurationBuilder() + .setEnableCustomerCancellation(customerCancellationEnabled) + _cancelablesCollectRefundPaymentMethod[operationId] = Terminal.shared.collectRefundPaymentMethod( try params.build(), + refundConfig: try config.build(), completion: { error in self._cancelablesCollectRefundPaymentMethod.removeValue(forKey: operationId) if let error = error as? NSError { @@ -384,8 +404,7 @@ public class TerminalPlugin: NSObject, FlutterPlugin, TerminalPlatformApi { return } result.success(()) - } - ) + }) } func onStopCollectRefundPaymentMethod(_ operationId: Int) async throws { diff --git a/stripe_terminal/ios/mek_stripe_terminal.podspec b/stripe_terminal/ios/mek_stripe_terminal.podspec index 587feca..31ee101 100644 --- a/stripe_terminal/ios/mek_stripe_terminal.podspec +++ b/stripe_terminal/ios/mek_stripe_terminal.podspec @@ -2,16 +2,20 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. # Run `pod lib lint stripe_terminal.podspec` to validate before publishing. # + +require 'yaml' + +pubspec = YAML.load_file(File.join('..', 'pubspec.yaml')) +library_version = pubspec['version'].gsub('+', '-') + Pod::Spec.new do |s| - s.name = 'mek_stripe_terminal' - s.version = '0.0.1' - s.summary = 'A new Flutter project.' - s.description = <<-DESC -A new Flutter project. - DESC - s.homepage = 'http://example.com' + s.name = pubspec['name'] + s.version = library_version + s.summary = pubspec['description'] + s.description = pubspec['description'] + s.homepage = pubspec['homepage'] s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'BreX900' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' @@ -21,5 +25,5 @@ A new Flutter project. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.swift_version = '5.0' - s.dependency 'StripeTerminal', '~> 3.0.0' + s.dependency 'StripeTerminal', '~> 3.1.0' end diff --git a/stripe_terminal/lib/mek_stripe_terminal.dart b/stripe_terminal/lib/mek_stripe_terminal.dart index d991ea4..e61fa40 100644 --- a/stripe_terminal/lib/mek_stripe_terminal.dart +++ b/stripe_terminal/lib/mek_stripe_terminal.dart @@ -13,6 +13,7 @@ export 'src/models/reader.dart'; export 'src/models/reader_software_update.dart'; export 'src/models/refund.dart'; export 'src/models/setup_intent.dart'; +export 'src/models/tipping.dart'; export 'src/reader_delegates.dart'; export 'src/terminal.dart'; export 'src/terminal_exception.dart'; diff --git a/stripe_terminal/lib/src/models/payment_intent.g.dart b/stripe_terminal/lib/src/models/payment_intent.g.dart index c0fc6dc..ddcacf0 100644 --- a/stripe_terminal/lib/src/models/payment_intent.g.dart +++ b/stripe_terminal/lib/src/models/payment_intent.g.dart @@ -88,17 +88,17 @@ mixin _$PaymentIntent { ..add('statementDescriptorSuffix', _self.statementDescriptorSuffix) ..add('amountCapturable', _self.amountCapturable) ..add('amountReceived', _self.amountReceived) - ..add('application', _self.applicationId) + ..add('applicationId', _self.applicationId) ..add('applicationFeeAmount', _self.applicationFeeAmount) ..add('cancellationReason', _self.cancellationReason) ..add('canceledAt', _self.canceledAt) ..add('clientSecret', _self.clientSecret) ..add('confirmationMethod', _self.confirmationMethod) - ..add('customer', _self.customerId) + ..add('customerId', _self.customerId) ..add('description', _self.description) - ..add('invoice', _self.invoiceId) + ..add('invoiceId', _self.invoiceId) ..add('onBehalfOf', _self.onBehalfOf) - ..add('review', _self.reviewId) + ..add('reviewId', _self.reviewId) ..add('receiptEmail', _self.receiptEmail) ..add('setupFutureUsage', _self.setupFutureUsage) ..add('transferGroup', _self.transferGroup)) diff --git a/stripe_terminal/lib/src/models/setup_intent.g.dart b/stripe_terminal/lib/src/models/setup_intent.g.dart index 46b3b2f..5397a11 100644 --- a/stripe_terminal/lib/src/models/setup_intent.g.dart +++ b/stripe_terminal/lib/src/models/setup_intent.g.dart @@ -82,7 +82,7 @@ mixin _$SetupAttempt { ..add('applicationId', _self.applicationId) ..add('created', _self.created) ..add('customerId', _self.customerId) - ..add('onBehalfOfId', _self.onBehalfOf) + ..add('onBehalfOf', _self.onBehalfOf) ..add('paymentMethodId', _self.paymentMethodId) ..add('paymentMethodDetails', _self.paymentMethodDetails) ..add('setupIntentId', _self.setupIntentId) diff --git a/stripe_terminal/lib/src/models/tipping.dart b/stripe_terminal/lib/src/models/tipping.dart new file mode 100644 index 0000000..6e39bc4 --- /dev/null +++ b/stripe_terminal/lib/src/models/tipping.dart @@ -0,0 +1,14 @@ +import 'package:mek_data_class/mek_data_class.dart'; + +part 'tipping.g.dart'; + +/// The [TippingConfiguration] contains configuration information relevant to collecting tips. +@DataClass() +class TippingConfiguration with _$TippingConfiguration { + /// The amount of the payment total eligible for tips. + final int eligibleAmount; + + const TippingConfiguration({ + required this.eligibleAmount, + }); +} diff --git a/stripe_terminal/lib/src/models/tipping.g.dart b/stripe_terminal/lib/src/models/tipping.g.dart new file mode 100644 index 0000000..72452a3 --- /dev/null +++ b/stripe_terminal/lib/src/models/tipping.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tipping.dart'; + +// ************************************************************************** +// DataClassGenerator +// ************************************************************************** + +mixin _$TippingConfiguration { + TippingConfiguration get _self => this as TippingConfiguration; + @override + bool operator ==(Object other) => + identical(this, other) || + other is TippingConfiguration && + runtimeType == other.runtimeType && + _self.eligibleAmount == other.eligibleAmount; + @override + int get hashCode { + var hashCode = 0; + hashCode = $hashCombine(hashCode, _self.eligibleAmount.hashCode); + return $hashFinish(hashCode); + } + + @override + String toString() => + (ClassToString('TippingConfiguration')..add('eligibleAmount', _self.eligibleAmount)) + .toString(); +} diff --git a/stripe_terminal/lib/src/platform/terminal_platform.api.dart b/stripe_terminal/lib/src/platform/terminal_platform.api.dart index 30b5f93..ea598d4 100644 --- a/stripe_terminal/lib/src/platform/terminal_platform.api.dart +++ b/stripe_terminal/lib/src/platform/terminal_platform.api.dart @@ -222,10 +222,19 @@ class _$TerminalPlatform { required int operationId, required String paymentIntentId, required bool skipTipping, + required TippingConfiguration? tippingConfiguration, + required bool shouldUpdatePaymentIntent, + required bool customerCancellationEnabled, }) async { try { - final result = await _$channel - .invokeMethod('startCollectPaymentMethod', [operationId, paymentIntentId, skipTipping]); + final result = await _$channel.invokeMethod('startCollectPaymentMethod', [ + operationId, + paymentIntentId, + skipTipping, + tippingConfiguration != null ? _$serializeTippingConfiguration(tippingConfiguration) : null, + shouldUpdatePaymentIntent, + customerCancellationEnabled + ]); return _$deserializePaymentIntent(result as List); } on PlatformException catch (exception) { TerminalPlatform._throwIfIsHostException(exception); @@ -298,11 +307,11 @@ class _$TerminalPlatform { required int operationId, required String setupIntentId, required bool customerConsentCollected, - required bool? isCustomerCancellationEnabled, + required bool customerCancellationEnabled, }) async { try { final result = await _$channel.invokeMethod('startCollectSetupIntentPaymentMethod', - [operationId, setupIntentId, customerConsentCollected, isCustomerCancellationEnabled]); + [operationId, setupIntentId, customerConsentCollected, customerCancellationEnabled]); return _$deserializeSetupIntent(result as List); } on PlatformException catch (exception) { TerminalPlatform._throwIfIsHostException(exception); @@ -347,7 +356,7 @@ class _$TerminalPlatform { required Map? metadata, required bool? reverseTransfer, required bool? refundApplicationFee, - required bool? isCustomerCancellationEnabled, + required bool customerCancellationEnabled, }) async { try { await _$channel.invokeMethod('startCollectRefundPaymentMethod', [ @@ -358,7 +367,7 @@ class _$TerminalPlatform { metadata?.map((k, v) => MapEntry(k, v)), reverseTransfer, refundApplicationFee, - isCustomerCancellationEnabled + customerCancellationEnabled ]); } on PlatformException catch (exception) { TerminalPlatform._throwIfIsHostException(exception); @@ -659,3 +668,5 @@ TerminalException _$deserializeTerminalException(List serialized) => Te stackTrace: serialized[2] as String?, paymentIntent: serialized[3] != null ? _$deserializePaymentIntent(serialized[3] as List) : null, apiError: serialized[4]); +List _$serializeTippingConfiguration(TippingConfiguration deserialized) => + [deserialized.eligibleAmount]; diff --git a/stripe_terminal/lib/src/platform/terminal_platform.dart b/stripe_terminal/lib/src/platform/terminal_platform.dart index 2afbfaa..685b405 100644 --- a/stripe_terminal/lib/src/platform/terminal_platform.dart +++ b/stripe_terminal/lib/src/platform/terminal_platform.dart @@ -13,6 +13,7 @@ import 'package:mek_stripe_terminal/src/models/reader.dart'; import 'package:mek_stripe_terminal/src/models/reader_software_update.dart'; import 'package:mek_stripe_terminal/src/models/refund.dart'; import 'package:mek_stripe_terminal/src/models/setup_intent.dart'; +import 'package:mek_stripe_terminal/src/models/tipping.dart'; import 'package:mek_stripe_terminal/src/reader_delegates.dart'; import 'package:mek_stripe_terminal/src/terminal_exception.dart'; import 'package:one_for_all/one_for_all.dart'; @@ -121,6 +122,9 @@ class TerminalPlatform extends _$TerminalPlatform { required int operationId, required String paymentIntentId, required bool skipTipping, + required TippingConfiguration? tippingConfiguration, + required bool shouldUpdatePaymentIntent, + required bool customerCancellationEnabled, }); @override @@ -152,7 +156,7 @@ class TerminalPlatform extends _$TerminalPlatform { required int operationId, required String setupIntentId, required bool customerConsentCollected, - required bool? isCustomerCancellationEnabled, + required bool customerCancellationEnabled, }); @override @@ -176,7 +180,7 @@ class TerminalPlatform extends _$TerminalPlatform { required Map? metadata, required bool? reverseTransfer, required bool? refundApplicationFee, - required bool? isCustomerCancellationEnabled, + required bool customerCancellationEnabled, }); @override diff --git a/stripe_terminal/lib/src/terminal.dart b/stripe_terminal/lib/src/terminal.dart index 2a26371..9e9a9ee 100644 --- a/stripe_terminal/lib/src/terminal.dart +++ b/stripe_terminal/lib/src/terminal.dart @@ -11,8 +11,10 @@ import 'package:mek_stripe_terminal/src/models/payment_intent.dart'; import 'package:mek_stripe_terminal/src/models/reader.dart'; import 'package:mek_stripe_terminal/src/models/refund.dart'; import 'package:mek_stripe_terminal/src/models/setup_intent.dart'; +import 'package:mek_stripe_terminal/src/models/tipping.dart'; import 'package:mek_stripe_terminal/src/platform/terminal_platform.dart'; import 'package:mek_stripe_terminal/src/reader_delegates.dart'; +import 'package:mek_stripe_terminal/src/terminal_exception.dart'; @Deprecated('Use Terminal. The name has been aligned with the native SDKs.') typedef StripeTerminal = Terminal; @@ -285,26 +287,38 @@ class Terminal { /// Collects a payment method for the given [PaymentIntent]. /// - /// Note: [collectPaymentMethod] does not apply any changes to the [PaymentIntent] API object. - /// Updates to the [PaymentIntent] are local to the SDK, and persisted in-memory. + /// Note: [collectPaymentMethod] does not apply any changes to the [PaymentIntent] API object. Updates + /// to the [PaymentIntent] are local to the SDK, and persisted in-memory. /// - /// After resolving the error, you may call collectPaymentMethod again to either try the same card again, or try a different card. + /// After resolving the error, you may call [collectPaymentMethod] again to either try the same + /// card again, or try a different card. /// /// If collecting a payment method succeeds, the method complete with a [PaymentIntent] with status /// [PaymentIntentStatus.requiresConfirmation], indicating that you should call [confirmPaymentIntent] to finish the payment. /// - /// Note that if [collectPaymentMethod] is canceled, the future will be complete with a Canceled error. + /// Note that if [collectPaymentMethod] is canceled, the future will be complete with a [TerminalExceptionCode.canceled] error. /// - /// Only supports `swipe`, `tap` and `insert` method + /// - [skipTipping] Bypass tipping selection if it would have otherwise been shown. + /// - [tippingConfiguration] The tipping configuration for this payment collection. + /// - [shouldUpdatePaymentIntent] Whether or not to update the [PaymentIntent] server side during + /// [collectPaymentMethod]. Attempting to collect with [shouldUpdatePaymentIntent] enabled and + /// a [PaymentIntent] created while offline will error with SCPErrorUpdatePaymentIntentUnavailableWhileOffline. + /// - [customerCancellationEnabled] Whether to show a cancel button in transaction UI on Stripe smart readers. CancelableFuture collectPaymentMethod( PaymentIntent paymentIntent, { bool skipTipping = false, + TippingConfiguration? tippingConfiguration, + bool shouldUpdatePaymentIntent = false, + bool customerCancellationEnabled = false, }) { return CancelableFuture(_platform.stopCollectPaymentMethod, (id) async { return await _platform.startCollectPaymentMethod( operationId: id, paymentIntentId: paymentIntent.id, skipTipping: skipTipping, + tippingConfiguration: tippingConfiguration, + shouldUpdatePaymentIntent: shouldUpdatePaymentIntent, + customerCancellationEnabled: customerCancellationEnabled, ); }); } @@ -394,7 +408,7 @@ class Terminal { /// [SetupIntentStatus.requiresConfirmation], indicating that you should call [confirmSetupIntent] /// to finish the payment. /// - /// Note that if [collectSetupIntentPaymentMethod] is canceled returns Canceled error. + /// Note that if [collectSetupIntentPaymentMethod] is canceled returns [TerminalExceptionCode.canceled] error. /// /// Collecting cardholder consent /// Card networks require that you collect consent from the customer before saving and reusing @@ -402,18 +416,19 @@ class Terminal { /// /// The payment method will not be collected without the cardholder’s consent. /// - /// - [isCustomerCancellationEnabled] Only available on Android + /// - [customerCancellationEnabled] Whether to show a cancel button in transaction UI on Stripe smart readers. CancelableFuture collectSetupIntentPaymentMethod( SetupIntent setupIntent, { required bool customerConsentCollected, - bool? isCustomerCancellationEnabled, + bool customerCancellationEnabled = false, + @Deprecated('Please use [customerCancellationEnabled]') bool? isCustomerCancellationEnabled, }) { return CancelableFuture(_platform.stopCollectSetupIntentPaymentMethod, (id) async { return await _platform.startCollectSetupIntentPaymentMethod( operationId: id, setupIntentId: setupIntent.id, customerConsentCollected: customerConsentCollected, - isCustomerCancellationEnabled: isCustomerCancellationEnabled, + customerCancellationEnabled: isCustomerCancellationEnabled ?? customerCancellationEnabled, ); }); } @@ -465,7 +480,7 @@ class Terminal { /// Calling any other SDK methods between [collectRefundPaymentMethod] and [confirmRefund] /// will result in undefined behavior. /// - /// Note that if [collectRefundPaymentMethod] is canceled, this method throw a Canceled error. + /// Note that if [collectRefundPaymentMethod] is canceled, this method throw a [TerminalExceptionCode.canceled] error. /// /// - [chargeId] The ID of the charge to be refunded. /// - [amount] The amount of the refund, provided in the currency’s smallest unit. @@ -479,7 +494,7 @@ class Terminal { /// fee should be refunded when refunding this charge. If a full charge refund is given, /// the full application fee will be refunded. Otherwise, the application fee will be refunded /// in an amount proportional to the amount of the charge refunded. - /// - [isCustomerCancellationEnabled] Only available on Android + /// - [customerCancellationEnabled] Whether to show a cancel button in transaction UI on Stripe smart readers. CancelableFuture collectRefundPaymentMethod({ required String chargeId, required int amount, @@ -487,7 +502,8 @@ class Terminal { Map? metadata, bool? reverseTransfer, bool? refundApplicationFee, - bool? isCustomerCancellationEnabled, + bool customerCancellationEnabled = false, + @Deprecated('Please use [customerCancellationEnabled]') bool? isCustomerCancellationEnabled, }) { return CancelableFuture(_platform.stopCollectRefundPaymentMethod, (id) async { return await _platform.startCollectRefundPaymentMethod( @@ -498,7 +514,7 @@ class Terminal { metadata: metadata, reverseTransfer: reverseTransfer, refundApplicationFee: refundApplicationFee, - isCustomerCancellationEnabled: isCustomerCancellationEnabled, + customerCancellationEnabled: isCustomerCancellationEnabled ?? customerCancellationEnabled, ); }); } diff --git a/stripe_terminal/lib/src/terminal_exception.dart b/stripe_terminal/lib/src/terminal_exception.dart index 5aaac80..45cd96e 100644 --- a/stripe_terminal/lib/src/terminal_exception.dart +++ b/stripe_terminal/lib/src/terminal_exception.dart @@ -86,6 +86,8 @@ enum TerminalExceptionCode { /// BluetoothConnectionConfiguration is required but a valid one was not provided. /// - InvalidLocationIdParameter: The provided location ID parameter was invalid. /// - ReaderConnectionConfigurationInvalid: An invalid ConnectionConfiguration was passed through connect. + /// - invalidRequiredParameterOnBehalfOf: The [PaymentIntent] uses on_behalf_of but the Connected + /// Account ID was not set in LocalMobileConnectionConfiguration invalidParameter, /// A required parameter was invalid or missing. @@ -295,6 +297,16 @@ enum TerminalExceptionCode { /// Only IOS. Unexpected reader error. unexpectedReaderError, + /// Only IOS. Encryption key failed to initialize. Offline payments not available. + /// The encryption key needed to decrypt the payment records is not available. This can happen if + /// an iOS backup that included offline payment records was restored on a new device. Those records + /// must be forwarded from the original device and the records must be deleted from this device. + /// Please contact support at https://support.stripe.com/ for more help. + encryptionKeyFailure, + + /// Only IOS. Encryption key still initializing. Offline payments are not yet available, please try again. + encryptionKeyStillInitializing, + /// The Stripe API declined the transaction. Inspect the error’s requestError property for more /// information about the decline, including the decline code. declinedByStripeApi, @@ -367,6 +379,10 @@ enum TerminalExceptionCode { /// to a different location while online. readerConnectionOfflineLocationMismatch, + /// Only IOS. The device software version running on this reader is out of date. You must connect + /// to this reader while online to install required updates before this reader can be used for offline payments. + readerConnectionOfflineNeedsUpdate, + /// Only Android. Connecting to the reader at this location failed. To connect a reader at a /// specified location while offline, a reader must have been connected online at that location /// within the last several weeks. @@ -464,7 +480,12 @@ enum TerminalExceptionCode { collectInputsApplicationError, /// Error reported when a timeout occurs while processing a collect inputs operation. - collectInputsTimedOut; + collectInputsTimedOut, + + /// Only Android. Error reported when the connected account does not have access to this feature, + /// or the reader/SDK version is not compatible with the collect inputs operation. + collectInputsUnsupported, + ; final String? message; diff --git a/stripe_terminal/pubspec.yaml b/stripe_terminal/pubspec.yaml index edbe7cc..1a9e462 100644 --- a/stripe_terminal/pubspec.yaml +++ b/stripe_terminal/pubspec.yaml @@ -1,7 +1,8 @@ name: mek_stripe_terminal description: A StripeTerminal plugin to discover readers, connect to them and process payments. -version: 3.0.1 +version: 3.1.0 repository: https://github.com/BreX900/mek-packages/tree/main/stripe_terminal +homepage: https://github.com/BreX900/mek-packages/tree/main/stripe_terminal topics: - stripe-terminal - mek-packages