From b7dfa48bf79b6af38b68a6b4b62cbcfc84134a4a Mon Sep 17 00:00:00 2001 From: BreX900 Date: Sun, 10 Mar 2024 15:27:04 +0100 Subject: [PATCH] feat(stripe_terminal): updated package to full support sdk v. 3.4.0 --- stripe_terminal/CHANGELOG.md | 8 +++++ stripe_terminal/ROADMAP.md | 21 +++++++++++- .../mek/stripeterminal/TerminalPlugin.kt | 15 +++++++-- .../mek/stripeterminal/api/TerminalApi.kt | 24 ++++++++++++-- .../mappings/DisconnectReasonMappings.kt | 16 +++++++++ .../mappings/PaymentIntentMappings.kt | 2 +- .../plugin/ReaderDelegatePlugin.kt | 5 +++ .../ReaderReconnectionListenerPlugin.kt | 5 +-- .../ios/Classes/Api/TerminalApi.swift | 33 ++++++++++++++++--- .../Mappings/DisconnectReasonMappings.swift | 25 ++++++++++++++ .../Classes/Plugin/ReaderDelegatePlugin.swift | 4 +++ .../ReaderReconnectionDelegatePlugin.swift | 4 +-- .../ios/Classes/TerminalPlugin.swift | 13 +++++++- stripe_terminal/lib/mek_stripe_terminal.dart | 1 + .../lib/src/models/disconnect_reason.dart | 25 ++++++++++++++ .../lib/src/platform/terminal_handlers.dart | 12 +++++-- .../src/platform/terminal_platform.api.dart | 20 ++++++++--- .../lib/src/platform/terminal_platform.dart | 4 +++ stripe_terminal/lib/src/reader_delegates.dart | 17 ++++++++-- stripe_terminal/lib/src/terminal.dart | 13 ++++++-- 20 files changed, 242 insertions(+), 25 deletions(-) create mode 100644 stripe_terminal/android/src/main/kotlin/mek/stripeterminal/mappings/DisconnectReasonMappings.kt create mode 100644 stripe_terminal/ios/Classes/Mappings/DisconnectReasonMappings.swift create mode 100644 stripe_terminal/lib/src/models/disconnect_reason.dart diff --git a/stripe_terminal/CHANGELOG.md b/stripe_terminal/CHANGELOG.md index 1e18e7a..93e7d7c 100644 --- a/stripe_terminal/CHANGELOG.md +++ b/stripe_terminal/CHANGELOG.md @@ -2,6 +2,14 @@ - chore: Bumped [Android](https://github.com/stripe/stripe-terminal-android/blob/master/CHANGELOG.md#340---2024-03-04) and [IOS](https://github.com/stripe/stripe-terminal-ios/blob/master/CHANGELOG.md#340-2024-03-04) sdks versions to `3.4.0` - refactor: Renamed `TerminalExceptionCode.bluetoothConnectionFailedBatteryCriticallyLow` to `TerminalExceptionCode.readerBatteryCriticallyLow` +- feat: Added new `TerminalExceptionCode.readerMissingEncryptionKeys`. Returned in a rare condition + where the reader is missing the required keys to encrypt payment method data. The reader will + disconnect if this error is hit. Reconnecting to the reader should re-install the keys. +- feat: Added a `DisconnectReason` to the `ReaderReconnectionDelegate.onReaderReconnectStarted2` callback. +- build(android): Increased the minimum API version requirement to 30 (Android 11). +- build(android): SDKs have been updated to depend on [Kotlin 1.9.10](https://github.com/JetBrains/kotlin/releases/tag/v1.9.10). +- build(ios): The SDK now requires that a `NSBluetoothAlwaysUsageDescription` key be present in your + app's Info.plist instead of a `NSBluetoothPeripheralUsageDescription` key. ## 3.2.1 - chore: Bumped [Android](https://github.com/stripe/stripe-terminal-android/blob/master/CHANGELOG.md#321---2023-12-18) diff --git a/stripe_terminal/ROADMAP.md b/stripe_terminal/ROADMAP.md index 7830082..8735786 100644 --- a/stripe_terminal/ROADMAP.md +++ b/stripe_terminal/ROADMAP.md @@ -14,8 +14,20 @@ - 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. - (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). +#### 3.4.0 - 2024-03-04 + +- Update: The [`Terminal.collectInputs`](https://stripe.com/docs/terminal/features/collect-inputs) method can now display optional toggles in each form. + +#### 3.3.0 - 2024-01-30 + +- Beta: Added a [`Terminal.collectInputs`](https://stripe.com/docs/terminal/features/collect-inputs) method to display forms and collect information from customers. It requires the use of a new `@OptIn` annotation; `@CollectInputs`. Note that this feature is in beta. + - If you are interested in joining this beta, please email stripe-terminal-betas@stripe.com +- Beta: Added support for retrieving and updating reader settings on WisePOS E and Stripe S700 by calling [`Terminal.getReaderSettings`](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/get-reader-settings.html) and [`Terminal.setReaderSettings`](https://stripe.dev/stripe-terminal-android/core/com.stripe.stripeterminal/-terminal/set-reader-settings.html). Accessibility settings are provided at this time, allowing text-to-speech via speakers to be turned on and off as needed. + - If you are interested in joining this beta, please email stripe-terminal-betas@stripe.com + - _Note: this feature requires [reader software version](https://stripe.com/docs/terminal/readers/bbpos-wisepos-e#reader-software-version) `2.20` or later to be installed on your reader._ + + ### Ready -- Feat: Added support to `Terminal.updateSimulatorConfiguration` ### In progress @@ -28,6 +40,13 @@ * New: Private beta support for offline payments. * See [Collect payments while offline](https://stripe.com/docs/terminal/features/operate-offline/collect-payments) for details. +#### 3.3.0 2024-02-02 +* New: Added support for retrieving and updating reader settings on WisePOS E and Stripe S700 by calling `retrieveReaderSettings` and `setReaderSettings` on `SCPTerminal`. + * Beta: Accessibility settings are provided at this time, allowing text-to-speech via speakers to be turned on and off as needed. + * Please [contact us](mailto:stripe-terminal-betas@stripe.com) if you are interested in joining this beta. +* Beta: Added a [`collectInputs`](https://stripe.com/docs/terminal/features/collect-inputs) method to display forms and collect information from customers. + * If you are interested in joining this beta, please email stripe-terminal-betas@stripe.com. + ### Ready ### In progress 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 7cf8b28..fe9789e 100644 --- a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/TerminalPlugin.kt +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/TerminalPlugin.kt @@ -201,13 +201,16 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { override fun onConnectMobileReader( result: Result, serialNumber: String, - locationId: String + locationId: String, + autoReconnectOnUnexpectedDisconnect: Boolean ) { val reader = findActiveReader(serialNumber) val config = ConnectionConfiguration.LocalMobileConnectionConfiguration( - locationId = locationId + locationId = locationId, + autoReconnectOnUnexpectedDisconnect = autoReconnectOnUnexpectedDisconnect, + localMobileReaderReconnectionListener = readerReconnectionDelegate ) terminal.connectLocalMobileReader( reader, @@ -285,6 +288,14 @@ class TerminalPlugin : FlutterPlugin, ActivityAware, TerminalPlatformApi { ) } + override fun onRebootReader(result: Result) { + terminal.rebootReader( + object : TerminalErrorHandler(result::error), Callback { + override fun onSuccess() = result.success(Unit) + } + ) + } + override fun onDisconnectReader(result: Result) { terminal.disconnectReader( object : TerminalErrorHandler(result::error), Callback { 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 531cb78..4bac9f8 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 @@ -89,6 +89,7 @@ interface TerminalPlatformApi { result: Result, serialNumber: String, locationId: String, + autoReconnectOnUnexpectedDisconnect: Boolean, ) fun onConnectUsbReader( @@ -117,6 +118,10 @@ interface TerminalPlatformApi { result: Result, ) + fun onRebootReader( + result: Result, + ) + fun onDisconnectReader( result: Result, ) @@ -272,7 +277,7 @@ interface TerminalPlatformApi { } "connectMobileReader" -> { val res = Result(result) { it.serialize() } - onConnectMobileReader(res, args[0] as String, args[1] as String) + onConnectMobileReader(res, args[0] as String, args[1] as String, args[2] as Boolean) } "connectUsbReader" -> { val res = Result(result) { it.serialize() } @@ -298,6 +303,10 @@ interface TerminalPlatformApi { val res = Result(result) { null } onCancelReaderUpdate(res) } + "rebootReader" -> { + val res = Result(result) { null } + onRebootReader(res) + } "disconnectReader" -> { val res = Result(result) { null } onDisconnectReader(res) @@ -522,6 +531,12 @@ class TerminalHandlersApi( channel.invokeMethod("_onReaderFinishInstallingUpdate", listOf(update?.serialize(), exception?.serialize())) } + fun disconnect( + reason: DisconnectReasonApi, + ) { + channel.invokeMethod("_onDisconnect", listOf(reason.ordinal)) + } + fun readerReconnectFailed( reader: ReaderApi, ) { @@ -530,8 +545,9 @@ class TerminalHandlersApi( fun readerReconnectStarted( reader: ReaderApi, + reason: DisconnectReasonApi, ) { - channel.invokeMethod("_onReaderReconnectStarted", listOf(reader.serialize())) + channel.invokeMethod("_onReaderReconnectStarted", listOf(reader.serialize(), reason.ordinal)) } fun readerReconnectSucceeded( @@ -761,6 +777,10 @@ enum class DeviceTypeApi { CHIPPER1_X, CHIPPER2_X, STRIPE_M2, COTS_DEVICE, VERIFONE_P400, WISE_CUBE, WISE_PAD3, WISE_PAD3S, WISE_POS_E, WISE_POS_E_DEVKIT, ETNA, STRIPE_S700, STRIPE_S700_DEVKIT, APPLE_BUILT_IN; } +enum class DisconnectReasonApi { + UNKNOWN, DISCONNECT_REQUESTED, REBOOT_REQUESTED, SECURITY_REBOOT, CRITICALLY_LOW_BATTERY, POWERED_OFF, BLUETOOTH_DISABLED; +} + sealed class DiscoveryConfigurationApi { companion object { fun deserialize( diff --git a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/mappings/DisconnectReasonMappings.kt b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/mappings/DisconnectReasonMappings.kt new file mode 100644 index 0000000..ee4d80c --- /dev/null +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/mappings/DisconnectReasonMappings.kt @@ -0,0 +1,16 @@ +package mek.stripeterminal.mappings + +import com.stripe.stripeterminal.external.models.DisconnectReason +import mek.stripeterminal.api.DisconnectReasonApi + +fun DisconnectReason.toApi(): DisconnectReasonApi { + return when (this) { + DisconnectReason.UNKNOWN -> DisconnectReasonApi.UNKNOWN + DisconnectReason.DISCONNECT_REQUESTED -> DisconnectReasonApi.DISCONNECT_REQUESTED + DisconnectReason.REBOOT_REQUESTED -> DisconnectReasonApi.REBOOT_REQUESTED + DisconnectReason.SECURITY_REBOOT -> DisconnectReasonApi.SECURITY_REBOOT + DisconnectReason.CRITICALLY_LOW_BATTERY -> DisconnectReasonApi.CRITICALLY_LOW_BATTERY + DisconnectReason.POWERED_OFF -> DisconnectReasonApi.POWERED_OFF + DisconnectReason.BLUETOOTH_DISABLED -> DisconnectReasonApi.BLUETOOTH_DISABLED + } +} diff --git a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/mappings/PaymentIntentMappings.kt b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/mappings/PaymentIntentMappings.kt index 68ff739..3bb356c 100644 --- a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/mappings/PaymentIntentMappings.kt +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/mappings/PaymentIntentMappings.kt @@ -23,7 +23,7 @@ import mek.stripeterminal.toHashMap fun PaymentIntent.toApi(): PaymentIntentApi { return PaymentIntentApi( id = id!!, - created = created, + created = created * 1000, status = status!!.toApi(), amount = amount.toDouble(), captureMethod = when (captureMethod!!) { diff --git a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/plugin/ReaderDelegatePlugin.kt b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/plugin/ReaderDelegatePlugin.kt index 462011e..9bf5488 100644 --- a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/plugin/ReaderDelegatePlugin.kt +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/plugin/ReaderDelegatePlugin.kt @@ -4,6 +4,7 @@ import com.stripe.stripeterminal.external.callable.Cancelable import com.stripe.stripeterminal.external.callable.HandoffReaderListener import com.stripe.stripeterminal.external.callable.ReaderListener import com.stripe.stripeterminal.external.models.BatteryStatus +import com.stripe.stripeterminal.external.models.DisconnectReason import com.stripe.stripeterminal.external.models.ReaderDisplayMessage import com.stripe.stripeterminal.external.models.ReaderEvent import com.stripe.stripeterminal.external.models.ReaderInputOptions @@ -64,4 +65,8 @@ class ReaderDelegatePlugin(private val _handlers: TerminalHandlersApi) : cancelUpdate = null _handlers.readerFinishInstallingUpdate(update?.toApi(), e?.toApi()) } + + override fun onDisconnect(reason: DisconnectReason) { + _handlers.disconnect(reason.toApi()) + } } diff --git a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/plugin/ReaderReconnectionListenerPlugin.kt b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/plugin/ReaderReconnectionListenerPlugin.kt index cf7a59e..bf4b068 100644 --- a/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/plugin/ReaderReconnectionListenerPlugin.kt +++ b/stripe_terminal/android/src/main/kotlin/mek/stripeterminal/plugin/ReaderReconnectionListenerPlugin.kt @@ -2,6 +2,7 @@ package mek.stripeterminal.plugin import com.stripe.stripeterminal.external.callable.Cancelable import com.stripe.stripeterminal.external.callable.ReaderReconnectionListener +import com.stripe.stripeterminal.external.models.DisconnectReason import com.stripe.stripeterminal.external.models.Reader import mek.stripeterminal.api.TerminalHandlersApi import mek.stripeterminal.mappings.toApi @@ -11,10 +12,10 @@ class ReaderReconnectionListenerPlugin(private val _handlers: TerminalHandlersAp ReaderReconnectionListener { var cancelReconnect: Cancelable? = null - override fun onReaderReconnectStarted(reader: Reader, cancelReconnect: Cancelable) = + override fun onReaderReconnectStarted(reader: Reader, cancelReconnect: Cancelable, reason: DisconnectReason) = runOnMainThread { this.cancelReconnect = cancelReconnect - _handlers.readerReconnectStarted(reader.toApi()) + _handlers.readerReconnectStarted(reader.toApi(), reason.toApi()) } override fun onReaderReconnectFailed(reader: Reader) = runOnMainThread { diff --git a/stripe_terminal/ios/Classes/Api/TerminalApi.swift b/stripe_terminal/ios/Classes/Api/TerminalApi.swift index 008d0fb..ccff365 100644 --- a/stripe_terminal/ios/Classes/Api/TerminalApi.swift +++ b/stripe_terminal/ios/Classes/Api/TerminalApi.swift @@ -121,7 +121,8 @@ protocol TerminalPlatformApi { func onConnectMobileReader( _ serialNumber: String, - _ locationId: String + _ locationId: String, + _ autoReconnectOnUnexpectedDisconnect: Bool ) async throws -> ReaderApi func onConnectUsbReader( @@ -144,6 +145,8 @@ protocol TerminalPlatformApi { func onCancelReaderUpdate() async throws -> Void + func onRebootReader() async throws -> Void + func onDisconnectReader() async throws -> Void func onSetSimulatorConfiguration( @@ -321,7 +324,7 @@ func setTerminalPlatformApiHandler( } case "connectMobileReader": runAsync { - let res = try await hostApi.onConnectMobileReader(args[0] as! String, args[1] as! String) + let res = try await hostApi.onConnectMobileReader(args[0] as! String, args[1] as! String, args[2] as! Bool) return res.serialize() } case "connectUsbReader": @@ -350,6 +353,11 @@ func setTerminalPlatformApiHandler( try await hostApi.onCancelReaderUpdate() return nil } + case "rebootReader": + runAsync { + try await hostApi.onRebootReader() + return nil + } case "disconnectReader": runAsync { try await hostApi.onDisconnectReader() @@ -554,6 +562,12 @@ class TerminalHandlersApi { channel.invokeMethod("_onReaderFinishInstallingUpdate", arguments: [update?.serialize(), exception?.serialize()]) } + func disconnect( + reason: DisconnectReasonApi + ) { + channel.invokeMethod("_onDisconnect", arguments: [reason.rawValue]) + } + func readerReconnectFailed( reader: ReaderApi ) { @@ -561,9 +575,10 @@ class TerminalHandlersApi { } func readerReconnectStarted( - reader: ReaderApi + reader: ReaderApi, + reason: DisconnectReasonApi ) { - channel.invokeMethod("_onReaderReconnectStarted", arguments: [reader.serialize()]) + channel.invokeMethod("_onReaderReconnectStarted", arguments: [reader.serialize(), reason.rawValue]) } func readerReconnectSucceeded( @@ -819,6 +834,16 @@ enum DeviceTypeApi: Int { case appleBuiltIn } +enum DisconnectReasonApi: Int { + case unknown + case disconnectRequested + case rebootRequested + case securityReboot + case criticallyLowBattery + case poweredOff + case bluetoothDisabled +} + protocol DiscoveryConfigurationApi {} func deserializeDiscoveryConfigurationApi( diff --git a/stripe_terminal/ios/Classes/Mappings/DisconnectReasonMappings.swift b/stripe_terminal/ios/Classes/Mappings/DisconnectReasonMappings.swift new file mode 100644 index 0000000..e3fe3aa --- /dev/null +++ b/stripe_terminal/ios/Classes/Mappings/DisconnectReasonMappings.swift @@ -0,0 +1,25 @@ +import Foundation +import StripeTerminal + +extension DisconnectReason { + func toApi() -> DisconnectReasonApi { + switch self { + case .unknown: + return .unknown + case .disconnectRequested: + return .disconnectRequested + case .rebootRequested: + return .rebootRequested + case .securityReboot: + return .securityReboot + case .criticallyLowBattery: + return .criticallyLowBattery + case .poweredOff: + return .poweredOff + case .bluetoothDisabled: + return .bluetoothDisabled + @unknown default: + fatalError("WTF") + } + } +} diff --git a/stripe_terminal/ios/Classes/Plugin/ReaderDelegatePlugin.swift b/stripe_terminal/ios/Classes/Plugin/ReaderDelegatePlugin.swift index e8c7181..1a99c47 100644 --- a/stripe_terminal/ios/Classes/Plugin/ReaderDelegatePlugin.swift +++ b/stripe_terminal/ios/Classes/Plugin/ReaderDelegatePlugin.swift @@ -96,4 +96,8 @@ class ReaderDelegatePlugin: NSObject, BluetoothReaderDelegate, LocalMobileReader func localMobileReader(_ reader: Reader, didFinishInstallingUpdate update: ReaderSoftwareUpdate?, error: Error?) { self.reader(reader, didFinishInstallingUpdate: update, error: error) } + + func reader(_ reader: Reader, didDisconnect reason: DisconnectReason) { + self._handlers.disconnect(reason: reason.toApi()) + } } diff --git a/stripe_terminal/ios/Classes/Plugin/ReaderReconnectionDelegatePlugin.swift b/stripe_terminal/ios/Classes/Plugin/ReaderReconnectionDelegatePlugin.swift index 166bdb8..c78077e 100644 --- a/stripe_terminal/ios/Classes/Plugin/ReaderReconnectionDelegatePlugin.swift +++ b/stripe_terminal/ios/Classes/Plugin/ReaderReconnectionDelegatePlugin.swift @@ -9,10 +9,10 @@ class ReaderReconnectionDelegatePlugin: NSObject, ReconnectionDelegate { self._handlers = handlers } - func reader(_ reader: Reader, didStartReconnect cancelable: Cancelable) { + func reader(_ reader: Reader, didStartReconnect cancelable: Cancelable, reason: DisconnectReason) { self.cancelable = cancelable DispatchQueue.main.async { - self._handlers.readerReconnectStarted(reader: reader.toApi()) + self._handlers.readerReconnectStarted(reader: reader.toApi(), reason: reason.toApi()) } } diff --git a/stripe_terminal/ios/Classes/TerminalPlugin.swift b/stripe_terminal/ios/Classes/TerminalPlugin.swift index 31cd6b2..cd90b85 100644 --- a/stripe_terminal/ios/Classes/TerminalPlugin.swift +++ b/stripe_terminal/ios/Classes/TerminalPlugin.swift @@ -134,9 +134,12 @@ public class TerminalPlugin: NSObject, FlutterPlugin, TerminalPlatformApi { func onConnectMobileReader( _ serialNumber: String, - _ locationId: String + _ locationId: String, + _ autoReconnectOnUnexpectedDisconnect: Bool ) async throws -> ReaderApi { let config = LocalMobileConnectionConfigurationBuilder(locationId: locationId) + .setAutoReconnectOnUnexpectedDisconnect(autoReconnectOnUnexpectedDisconnect) + .setAutoReconnectionDelegate(_readerReconnectionDelegate) do { let reader = try await Terminal.shared.connectLocalMobileReader( _findReader(serialNumber), @@ -181,6 +184,14 @@ public class TerminalPlugin: NSObject, FlutterPlugin, TerminalPlatformApi { try await _readerDelegate.cancellableUpdate?.cancel() } + func onRebootReader() async throws { + do { + try await Terminal.shared.rebootReader() + } catch let error as NSError { + throw error.toPlatformError() + } + } + func onDisconnectReader() async throws { do { try await Terminal.shared.disconnectReader() diff --git a/stripe_terminal/lib/mek_stripe_terminal.dart b/stripe_terminal/lib/mek_stripe_terminal.dart index b392ae2..2446e61 100644 --- a/stripe_terminal/lib/mek_stripe_terminal.dart +++ b/stripe_terminal/lib/mek_stripe_terminal.dart @@ -6,6 +6,7 @@ export 'src/cancellable_future.dart'; export 'src/models/card.dart'; export 'src/models/cart.dart'; export 'src/models/charge.dart'; +export 'src/models/disconnect_reason.dart'; export 'src/models/discovery_configuration.dart'; export 'src/models/location.dart'; export 'src/models/payment.dart'; diff --git a/stripe_terminal/lib/src/models/disconnect_reason.dart b/stripe_terminal/lib/src/models/disconnect_reason.dart new file mode 100644 index 0000000..25501e8 --- /dev/null +++ b/stripe_terminal/lib/src/models/disconnect_reason.dart @@ -0,0 +1,25 @@ +/// Possible reasons for Bluetooth reader disconnects. +enum DisconnectReason { + /// Unexpected disconnect. + unknown, + + /// Terminal.disconnectReader was called. + disconnectRequested, + + /// Terminal.rebootReader was called. + rebootRequested, + + /// Reader disconnected after performing its required security reboot. This will + /// happen if the reader has been running for 24 hours. To control this you can + /// call Terminal.rebootReader which will reset the 24 hour timer. + securityReboot, + + /// Reader disconnected because its battery was critically low and needs to be charged before it can be used. + criticallyLowBattery, + + /// Reader was powered off. + poweredOff, + + /// Bluetooth was disabled on the device. + bluetoothDisabled, +} diff --git a/stripe_terminal/lib/src/platform/terminal_handlers.dart b/stripe_terminal/lib/src/platform/terminal_handlers.dart index a994995..228d62f 100644 --- a/stripe_terminal/lib/src/platform/terminal_handlers.dart +++ b/stripe_terminal/lib/src/platform/terminal_handlers.dart @@ -129,6 +129,14 @@ class TerminalHandlers { await delegate.onFinishInstallingUpdate(update, exception); }); } + + void _onDisconnect(DisconnectReason reason) { + _runInZone(_readerDelegate, (delegate) async { + if (delegate is! PhysicalReaderDelegate) return; + await delegate.onDisconnect(reason); + }); + } + //endregion //region Reader reconnection delegate @@ -138,9 +146,9 @@ class TerminalHandlers { }); } - void _onReaderReconnectStarted(Reader reader) { + void _onReaderReconnectStarted(Reader reader, DisconnectReason reason) { _runInZone(_readerReconnectionDelegate, (delegate) async { - await delegate.onReaderReconnectStarted(reader, _platform.cancelReaderReconnection); + await delegate.onReaderReconnectStarted2(reader, _platform.cancelReaderReconnection, reason); }); } diff --git a/stripe_terminal/lib/src/platform/terminal_platform.api.dart b/stripe_terminal/lib/src/platform/terminal_platform.api.dart index e481f04..d347a4a 100644 --- a/stripe_terminal/lib/src/platform/terminal_platform.api.dart +++ b/stripe_terminal/lib/src/platform/terminal_platform.api.dart @@ -115,10 +115,11 @@ class _$TerminalPlatform implements TerminalPlatform { Future connectMobileReader( String serialNumber, { required String locationId, + required bool autoReconnectOnUnexpectedDisconnect, }) async { try { - final result = - await _$channel.invokeMethod('connectMobileReader', [serialNumber, locationId]); + final result = await _$channel.invokeMethod( + 'connectMobileReader', [serialNumber, locationId, autoReconnectOnUnexpectedDisconnect]); return _$deserializeReader(result as List); } on PlatformException catch (exception) { TerminalPlatform._throwIfIsHostException(exception); @@ -199,6 +200,16 @@ class _$TerminalPlatform implements TerminalPlatform { } } + @override + Future rebootReader() async { + try { + await _$channel.invokeMethod('rebootReader', []); + } on PlatformException catch (exception) { + TerminalPlatform._throwIfIsHostException(exception); + rethrow; + } + } + @override Future disconnectReader() async { try { @@ -493,10 +504,11 @@ void _$setupTerminalHandlers(TerminalHandlers hostApi) { '_onReaderFinishInstallingUpdate' => hostApi._onReaderFinishInstallingUpdate( args[0] != null ? _$deserializeReaderSoftwareUpdate(args[0] as List) : null, args[1] != null ? _$deserializeTerminalException(args[1] as List) : null), + '_onDisconnect' => hostApi._onDisconnect(DisconnectReason.values[args[0] as int]), '_onReaderReconnectFailed' => hostApi._onReaderReconnectFailed(_$deserializeReader(args[0] as List)), - '_onReaderReconnectStarted' => - hostApi._onReaderReconnectStarted(_$deserializeReader(args[0] as List)), + '_onReaderReconnectStarted' => hostApi._onReaderReconnectStarted( + _$deserializeReader(args[0] as List), DisconnectReason.values[args[1] as int]), '_onReaderReconnectSucceeded' => hostApi._onReaderReconnectSucceeded(_$deserializeReader(args[0] as List)), _ => throw UnsupportedError('TerminalHandlers#Flutter.${call.method} method'), diff --git a/stripe_terminal/lib/src/platform/terminal_platform.dart b/stripe_terminal/lib/src/platform/terminal_platform.dart index 81a28cf..3de09dc 100644 --- a/stripe_terminal/lib/src/platform/terminal_platform.dart +++ b/stripe_terminal/lib/src/platform/terminal_platform.dart @@ -6,6 +6,7 @@ import 'package:flutter/services.dart'; import 'package:mek_stripe_terminal/src/models/card.dart'; import 'package:mek_stripe_terminal/src/models/cart.dart'; import 'package:mek_stripe_terminal/src/models/charge.dart'; +import 'package:mek_stripe_terminal/src/models/disconnect_reason.dart'; import 'package:mek_stripe_terminal/src/models/discovery_configuration.dart'; import 'package:mek_stripe_terminal/src/models/location.dart'; import 'package:mek_stripe_terminal/src/models/payment.dart'; @@ -67,6 +68,7 @@ abstract class TerminalPlatform { Future connectMobileReader( String serialNumber, { required String locationId, + required bool autoReconnectOnUnexpectedDisconnect, }); Future connectUsbReader( @@ -91,6 +93,8 @@ abstract class TerminalPlatform { Future cancelReaderUpdate(); + Future rebootReader(); + Future disconnectReader(); @MethodApi(kotlin: MethodApiType.sync, swift: MethodApiType.sync) diff --git a/stripe_terminal/lib/src/reader_delegates.dart b/stripe_terminal/lib/src/reader_delegates.dart index 632b181..7ba66fb 100644 --- a/stripe_terminal/lib/src/reader_delegates.dart +++ b/stripe_terminal/lib/src/reader_delegates.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:mek_stripe_terminal/src/models/disconnect_reason.dart'; 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/terminal_exception.dart'; @@ -37,12 +38,24 @@ mixin class PhysicalReaderDelegate implements ReaderDelegate { ReaderSoftwareUpdate? update, TerminalException? exception, ) {} + + FutureOr onDisconnect(DisconnectReason reason) {} } -abstract interface class ReaderReconnectionDelegate { +abstract mixin class ReaderReconnectionDelegate { FutureOr onReaderReconnectFailed(Reader reader); - FutureOr onReaderReconnectStarted(Reader reader, Cancellable cancelReconnect); + @Deprecated('In favour of onReaderReconnectStarted2. Removed in next major.') + FutureOr onReaderReconnectStarted(Reader reader, Cancellable cancelReconnect) {} + + FutureOr onReaderReconnectStarted2( + Reader reader, + Cancellable cancelReconnect, + DisconnectReason reason, + ) { + // ignore: deprecated_member_use_from_same_package + return onReaderReconnectStarted(reader, cancelReconnect); + } FutureOr onReaderReconnectSucceeded(Reader reader); } diff --git a/stripe_terminal/lib/src/terminal.dart b/stripe_terminal/lib/src/terminal.dart index ed2b122..5b1d894 100644 --- a/stripe_terminal/lib/src/terminal.dart +++ b/stripe_terminal/lib/src/terminal.dart @@ -72,7 +72,7 @@ class Terminal { /// Get the current [ConnectionStatus] Future getConnectionStatus() async => await _platform.getConnectionStatus(); - /// The reader disconnected unexpectedly (that is, without your app explicitly calling disconnectReader). + /// The reader disconnected unexpectedly (that is, without your app explicitly calling [disconnectReader]). /// /// In your implementation of this method, you should notify your user that the reader disconnected. /// You may also want to call discoverReaders to begin scanning for readers. Your app can attempt @@ -189,12 +189,16 @@ class Terminal { Future connectMobileReader( Reader reader, { required String locationId, + bool autoReconnectOnUnexpectedDisconnect = false, PhysicalReaderDelegate? delegate, + ReaderReconnectionDelegate? reconnectionDelegate, }) async { - return await _handleReaderConnection(delegate, null, () async { + assert(!autoReconnectOnUnexpectedDisconnect || reconnectionDelegate == null); + return await _handleReaderConnection(delegate, reconnectionDelegate, () async { return await _platform.connectMobileReader( reader.serialNumber, locationId: locationId, + autoReconnectOnUnexpectedDisconnect: autoReconnectOnUnexpectedDisconnect, ); }); } @@ -260,6 +264,11 @@ class Terminal { /// Note: It is an error to call this method when the SDK is connected to the Verifone P400 or WisePOS E readers. Future installAvailableUpdate() async => await _platform.installAvailableUpdate(); + /// Reboots the connected reader. + /// + /// Note: This method is only available for Bluetooth and USB readers. + Future rebootReader() async => await _platform.rebootReader(); + /// Attempts to disconnect from the currently connected reader. Future disconnectReader() async => await _platform.disconnectReader();