diff --git a/android/src/main/java/com/stripeterminalreactnative/Mappers.kt b/android/src/main/java/com/stripeterminalreactnative/Mappers.kt
index 3371e0bf..da9baa86 100644
--- a/android/src/main/java/com/stripeterminalreactnative/Mappers.kt
+++ b/android/src/main/java/com/stripeterminalreactnative/Mappers.kt
@@ -199,6 +199,13 @@ internal fun mapFromReaderInputOptions(options: ReaderInputOptions): WritableArr
return mappedOptions
}
+internal fun mapFromReaderEvent(event: ReaderEvent): String {
+ return when (event) {
+ ReaderEvent.CARD_INSERTED -> "cardInserted"
+ ReaderEvent.CARD_REMOVED -> "cardRemoved"
+ }
+}
+
internal fun mapFromReaderDisplayMessage(message: ReaderDisplayMessage): String {
return when (message) {
ReaderDisplayMessage.CHECK_MOBILE_DEVICE -> "checkMobileDevice"
diff --git a/android/src/main/java/com/stripeterminalreactnative/StripeTerminalReactNativeModule.kt b/android/src/main/java/com/stripeterminalreactnative/StripeTerminalReactNativeModule.kt
index 3db65f80..aa3718ee 100644
--- a/android/src/main/java/com/stripeterminalreactnative/StripeTerminalReactNativeModule.kt
+++ b/android/src/main/java/com/stripeterminalreactnative/StripeTerminalReactNativeModule.kt
@@ -20,6 +20,7 @@ import com.stripe.stripeterminal.external.callable.BluetoothReaderListener
import com.stripe.stripeterminal.external.callable.Callback
import com.stripe.stripeterminal.external.callable.Cancelable
import com.stripe.stripeterminal.external.callable.DiscoveryListener
+import com.stripe.stripeterminal.external.callable.HandoffReaderListener
import com.stripe.stripeterminal.external.callable.LocationListCallback
import com.stripe.stripeterminal.external.callable.PaymentIntentCallback
import com.stripe.stripeterminal.external.callable.PaymentMethodCallback
@@ -41,6 +42,7 @@ import com.stripe.stripeterminal.external.models.PaymentStatus
import com.stripe.stripeterminal.external.models.ReadReusableCardParameters
import com.stripe.stripeterminal.external.models.Reader
import com.stripe.stripeterminal.external.models.ReaderDisplayMessage
+import com.stripe.stripeterminal.external.models.ReaderEvent
import com.stripe.stripeterminal.external.models.ReaderInputOptions
import com.stripe.stripeterminal.external.models.ReaderSoftwareUpdate
import com.stripe.stripeterminal.external.models.Refund
@@ -263,6 +265,110 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
})
}
+ @ReactMethod
+ fun connectLocalMobileReader(params: ReadableMap, promise: Promise) {
+ val readerId = requireParam(params.getString("readerId")) {
+ "You must provide readerId"
+ }
+
+ val selectedReader = requireParam(discoveredReadersList.find {
+ it.serialNumber == readerId
+ }) {
+ "Could not find reader with id $readerId"
+ }
+
+ val locationId = params.getString("locationId") ?: selectedReader.location?.id.orEmpty()
+
+ Terminal.getInstance().connectLocalMobileReader(
+ selectedReader,
+ ConnectionConfiguration.LocalMobileConnectionConfiguration(locationId),
+ object : ReaderCallback {
+ override fun onSuccess(reader: Reader) {
+ promise.resolve(nativeMapOf {
+ putMap("reader", mapFromReader(reader))
+ })
+ }
+
+ override fun onFailure(e: TerminalException) {
+ promise.resolve(createError(e))
+ }
+ }
+ )
+ }
+
+ @ReactMethod
+ fun connectEmbeddedReader(params: ReadableMap, promise: Promise) =
+ withExceptionResolver(promise) {
+ val readerId = requireParam(params.getString("readerId")) {
+ "You must provide readerId"
+ }
+
+ val selectedReader = requireParam(discoveredReadersList.find {
+ it.serialNumber == readerId
+ }) {
+ "Could not find reader with id $readerId"
+ }
+
+ val locationId = params.getString("locationId") ?: selectedReader.location?.id.orEmpty()
+
+ Terminal.getInstance().connectEmbeddedReader(
+ selectedReader,
+ ConnectionConfiguration.EmbeddedConnectionConfiguration(locationId),
+ object : ReaderCallback {
+ override fun onSuccess(reader: Reader) {
+ promise.resolve(nativeMapOf {
+ putMap("reader", mapFromReader(reader))
+ })
+ }
+
+ override fun onFailure(e: TerminalException) {
+ promise.resolve(e)
+ }
+ }
+ )
+ }
+
+ @ReactMethod
+ fun connectHandoffReader(params: ReadableMap, promise: Promise) =
+ withExceptionResolver(promise) {
+ val readerId = requireParam(params.getString("readerId")) {
+ "You must provide readerId"
+ }
+
+ val selectedReader = requireParam(discoveredReadersList.find {
+ it.serialNumber == readerId
+ }) {
+ "Could not find reader with id $readerId"
+ }
+
+ val locationId = params.getString("locationId") ?: selectedReader.location?.id.orEmpty()
+
+ val listener: HandoffReaderListener = object : HandoffReaderListener {
+ override fun onReportReaderEvent(event: ReaderEvent) {
+ sendEvent("didRequestReaderInput", nativeMapOf {
+ putString("event", mapFromReaderEvent(event))
+ })
+ }
+ }
+
+ Terminal.getInstance().connectHandoffReader(
+ selectedReader,
+ ConnectionConfiguration.HandoffConnectionConfiguration(locationId),
+ listener,
+ object : ReaderCallback {
+ override fun onSuccess(reader: Reader) {
+ promise.resolve(nativeMapOf {
+ putMap("reader", mapFromReader(reader))
+ })
+ }
+
+ override fun onFailure(e: TerminalException) {
+ promise.resolve(createError(e))
+ }
+ }
+ )
+ }
+
@ReactMethod
@Suppress("unused")
fun connectBluetoothReader(params: ReadableMap, promise: Promise) =
@@ -283,10 +389,6 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
"You must provide a locationId"
}
- val connectionConfig = ConnectionConfiguration.BluetoothConnectionConfiguration(
- locationId
- )
-
val listener: BluetoothReaderListener = object : BluetoothReaderListener {
override fun onReportAvailableUpdate(update: ReaderSoftwareUpdate) {
sendEvent(REPORT_AVAILABLE_UPDATE.listenerName) {
@@ -340,7 +442,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
terminal.connectBluetoothReader(
selectedReader,
- connectionConfig,
+ ConnectionConfiguration.BluetoothConnectionConfiguration(locationId),
listener,
object : ReaderCallback {
override fun onSuccess(reader: Reader) {
@@ -461,9 +563,6 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
sendEvent(REQUEST_READER_DISPLAY_MESSAGE.listenerName) {
putString("result", mapFromReaderDisplayMessage(message))
}
- sendEvent(REQUEST_READER_DISPLAY_MESSAGE.listenerName) {
- putString("result", mapFromReaderDisplayMessage(message))
- }
}
}
diff --git a/e2e/app.e2e.js b/e2e/app.e2e.js
index 95c25e6c..e131b1d0 100644
--- a/e2e/app.e2e.js
+++ b/e2e/app.e2e.js
@@ -50,6 +50,9 @@ describe('Payments', () => {
});
it('Change discovery method to bluetooth proximity', async () => {
+ if (device.getPlatform() !== 'ios') {
+ return;
+ }
await changeDiscoveryMethod('Bluetooth Proximity');
});
@@ -57,6 +60,27 @@ describe('Payments', () => {
await changeDiscoveryMethod('Internet');
});
+ it('Change discovery method to Embedded', async () => {
+ if (device.getPlatform() !== 'android') {
+ return;
+ }
+ await changeDiscoveryMethod('Embedded');
+ });
+
+ it('Change discovery method to LocalMobile', async () => {
+ if (device.getPlatform() !== 'android') {
+ return;
+ }
+ await changeDiscoveryMethod('Local mobile');
+ });
+
+ it('Change discovery method to Handoff', async () => {
+ if (device.getPlatform() !== 'android') {
+ return;
+ }
+ await changeDiscoveryMethod('Handoff');
+ });
+
// temporary skipped due to bug in stripe-termina-ios that connects the device despite an error.
//
it('Required update impossible due to low battery', async () => {
diff --git a/example/src/screens/DiscoverReadersScreen.tsx b/example/src/screens/DiscoverReadersScreen.tsx
index f1ebbd51..7b527802 100644
--- a/example/src/screens/DiscoverReadersScreen.tsx
+++ b/example/src/screens/DiscoverReadersScreen.tsx
@@ -49,6 +49,9 @@ export default function DiscoverReadersScreen() {
connectInternetReader,
connectUsbReader,
simulateReaderUpdate,
+ connectEmbeddedReader,
+ connectLocalMobileReader,
+ connectHandoffReader,
} = useStripeTerminal({
onFinishDiscoveringReaders: (finishError) => {
if (finishError) {
@@ -154,6 +157,15 @@ export default function DiscoverReadersScreen() {
) {
const result = await handleConnectBluetoothReader(reader);
error = result.error;
+ } else if (discoveryMethod === 'localMobile') {
+ const result = await handleConnectLocalMobileReader(reader);
+ error = result.error;
+ } else if (discoveryMethod === 'handoff') {
+ const result = await handleConnectHandoffReader(reader);
+ error = result.error;
+ } else if (discoveryMethod === 'embedded') {
+ const result = await handleConnectEmbeddedReader(reader);
+ error = result.error;
} else if (discoveryMethod === 'usb') {
const result = await handleConnectUsbReader(reader);
error = result.error;
@@ -166,6 +178,54 @@ export default function DiscoverReadersScreen() {
}
};
+ const handleConnectEmbeddedReader = async (reader: Reader.Type) => {
+ setConnectingReader(reader);
+
+ const { reader: connectedReader, error } = await connectEmbeddedReader({
+ reader,
+ locationId: selectedLocation?.id,
+ });
+
+ if (error) {
+ console.log('connectEmbeddedReader error:', error);
+ } else {
+ console.log('Reader connected successfully', connectedReader);
+ }
+ return { error };
+ };
+
+ const handleConnectHandoffReader = async (reader: Reader.Type) => {
+ setConnectingReader(reader);
+
+ const { reader: connectedReader, error } = await connectHandoffReader({
+ reader,
+ locationId: selectedLocation?.id,
+ });
+
+ if (error) {
+ console.log('connectHandoffReader error:', error);
+ } else {
+ console.log('Reader connected successfully', connectedReader);
+ }
+ return { error };
+ };
+
+ const handleConnectLocalMobileReader = async (reader: Reader.Type) => {
+ setConnectingReader(reader);
+
+ const { reader: connectedReader, error } = await connectLocalMobileReader({
+ reader,
+ locationId: selectedLocation?.id,
+ });
+
+ if (error) {
+ console.log('connectLocalMobileReader error:', error);
+ } else {
+ console.log('Reader connected successfully', connectedReader);
+ }
+ return { error };
+ };
+
const handleConnectBluetoothReader = async (reader: Reader.Type) => {
setConnectingReader(reader);
diff --git a/example/src/screens/DiscoveryMethodScreen.tsx b/example/src/screens/DiscoveryMethodScreen.tsx
index 3b1f99a2..bfbee63c 100644
--- a/example/src/screens/DiscoveryMethodScreen.tsx
+++ b/example/src/screens/DiscoveryMethodScreen.tsx
@@ -1,6 +1,6 @@
import { RouteProp, useNavigation, useRoute } from '@react-navigation/core';
import React from 'react';
-import { StyleSheet, Text, View } from 'react-native';
+import { Platform, StyleSheet, Text, View } from 'react-native';
import type { Reader } from 'stripe-terminal-react-native';
import { colors } from '../colors';
import ListItem from '../components/ListItem';
@@ -28,14 +28,7 @@ export default function DiscoveryMethodScreen() {
Discover a reader by scanning for Bluetooth or Bluetooth LE devices.
- onSelect('bluetoothProximity')}
- title="Bluetooth Proximity"
- />
-
- Discover a reader by holding it next to the iOS device (only supported
- for the BBPOS Chipper 2X BT).
-
+
{
'Note: the Stripe Terminal SDK can discover supported readers automatically - you should not connect to the reader in the iOS Settings > Bluetooth page.'
@@ -51,6 +44,28 @@ export default function DiscoveryMethodScreen() {
Discover a reader connected to this device via USB.
+
+ {Platform.OS === 'android' ? (
+ <>
+ onSelect('embedded')} title="Embedded" />
+ onSelect('handoff')} title="Handoff" />
+ onSelect('localMobile')}
+ title="Local mobile"
+ />
+ >
+ ) : (
+ <>
+ onSelect('bluetoothProximity')}
+ title="Bluetooth Proximity"
+ />
+
+ Discover a reader by holding it next to the iOS device (only
+ supported for the BBPOS Chipper 2X BT).
+
+ >
+ )}
);
}
diff --git a/example/src/screens/HomeScreen.tsx b/example/src/screens/HomeScreen.tsx
index 394241d7..28591d5c 100644
--- a/example/src/screens/HomeScreen.tsx
+++ b/example/src/screens/HomeScreen.tsx
@@ -153,6 +153,12 @@ function mapFromDiscoveryMethod(method: Reader.DiscoveryMethod) {
return 'Bluetooth Proximity';
case 'internet':
return 'Internet';
+ case 'embedded':
+ return 'Embedded';
+ case 'handoff':
+ return 'Handoff';
+ case 'localMobile':
+ return 'Local mobile';
case 'usb':
return 'USB';
default:
diff --git a/src/StripeTerminalSdk.tsx b/src/StripeTerminalSdk.tsx
index 9c9a652e..84d53269 100644
--- a/src/StripeTerminalSdk.tsx
+++ b/src/StripeTerminalSdk.tsx
@@ -5,11 +5,9 @@ import type {
DiscoverReadersParams,
DiscoverReadersResultType,
CancelDiscoveringResultType,
- ConnectBluetoothReaderResultType,
ConnectBluetoothReaderParams,
DisconnectReaderResultType,
Reader,
- ConnectInternetResultType,
ConnectInternetReaderParams,
ConnectUsbReaderResultType,
ConnectUsbReaderParams,
@@ -28,6 +26,10 @@ import type {
ReadReusableCardParamsType,
PaymentMethodResultType,
SetConnectionTokenParams,
+ ConnectHandoffParams,
+ ConnectEmbeddedParams,
+ ConnectLocalMobileParams,
+ ConnectReaderResultType,
} from './types';
const { StripeTerminalReactNative } = NativeModules;
@@ -49,11 +51,20 @@ type StripeTerminalSdkType = {
// Connect to reader via bluetooth
connectBluetoothReader(
params: ConnectBluetoothReaderParams
- ): Promise;
+ ): Promise;
// Connect to reader via internet
connectInternetReader(
params: ConnectInternetReaderParams
- ): Promise;
+ ): Promise;
+ connectHandoffReader(
+ params: ConnectHandoffParams
+ ): Promise;
+ connectEmbeddedReader(
+ params: ConnectEmbeddedParams
+ ): Promise;
+ connectLocalMobileReader(
+ params: ConnectLocalMobileParams
+ ): Promise;
// Connect to reader via USB
connectUsbReader(
params: ConnectUsbReaderParams
diff --git a/src/functions.ts b/src/functions.ts
index b282ac12..de45910d 100644
--- a/src/functions.ts
+++ b/src/functions.ts
@@ -6,10 +6,8 @@ import type {
DiscoverReadersResultType,
ConnectBluetoothReaderParams,
CancelDiscoveringResultType,
- ConnectBluetoothReaderResultType,
DisconnectReaderResultType,
ConnectInternetReaderParams,
- ConnectInternetResultType,
ConnectUsbReaderParams,
ConnectUsbReaderResultType,
CreatePaymentIntentParams,
@@ -27,6 +25,10 @@ import type {
PaymentMethodResultType,
ReadReusableCardParamsType,
ProcessRefundResultType,
+ ConnectLocalMobileParams,
+ ConnectReaderResultType,
+ ConnectHandoffParams,
+ ConnectEmbeddedParams,
} from './types';
export async function initialize(
@@ -96,7 +98,7 @@ export async function cancelDiscovering(): Promise
export async function connectBluetoothReader(
params: ConnectBluetoothReaderParams
-): Promise {
+): Promise {
try {
const { error, reader } = await StripeTerminalSdk.connectBluetoothReader(
params
@@ -119,9 +121,84 @@ export async function connectBluetoothReader(
}
}
+export async function connectHandoffReader(
+ params: ConnectHandoffParams
+): Promise {
+ try {
+ const { error, reader } = await StripeTerminalSdk.connectHandoffReader(
+ params
+ );
+
+ if (error) {
+ return {
+ error,
+ reader: undefined,
+ };
+ }
+ return {
+ reader: reader!,
+ error: undefined,
+ };
+ } catch (error) {
+ return {
+ error: error as any,
+ };
+ }
+}
+
+export async function connectEmbeddedReader(
+ params: ConnectEmbeddedParams
+): Promise {
+ try {
+ const { error, reader } = await StripeTerminalSdk.connectEmbeddedReader(
+ params
+ );
+
+ if (error) {
+ return {
+ error,
+ reader: undefined,
+ };
+ }
+ return {
+ reader: reader!,
+ error: undefined,
+ };
+ } catch (error) {
+ return {
+ error: error as any,
+ };
+ }
+}
+
+export async function connectLocalMobileReader(
+ params: ConnectLocalMobileParams
+): Promise {
+ try {
+ const { error, reader } = await StripeTerminalSdk.connectLocalMobileReader(
+ params
+ );
+
+ if (error) {
+ return {
+ error,
+ reader: undefined,
+ };
+ }
+ return {
+ reader: reader!,
+ error: undefined,
+ };
+ } catch (error) {
+ return {
+ error: error as any,
+ };
+ }
+}
+
export async function connectInternetReader(
params: ConnectInternetReaderParams
-): Promise {
+): Promise {
try {
const { error, reader } = await StripeTerminalSdk.connectInternetReader(
params
diff --git a/src/hooks/useStripeTerminal.tsx b/src/hooks/useStripeTerminal.tsx
index 597d439c..09fff13c 100644
--- a/src/hooks/useStripeTerminal.tsx
+++ b/src/hooks/useStripeTerminal.tsx
@@ -13,6 +13,8 @@ import type {
RefundParams,
ReadReusableCardParamsType,
InitParams,
+ ConnectEmbeddedParams,
+ ConnectLocalMobileParams,
UserCallbacks,
} from '../types';
import {
@@ -45,6 +47,9 @@ import {
cancelCollectPaymentMethod,
cancelCollectSetupIntent,
cancelReadReusableCard,
+ connectEmbeddedReader,
+ connectHandoffReader,
+ connectLocalMobileReader,
} from '../functions';
import { StripeTerminalContext } from '../components/StripeTerminalContext';
import { useListener } from './useListener';
@@ -211,6 +216,54 @@ export function useStripeTerminal(props?: Props) {
[setConnectedReader, setLoading]
);
+ const _connectEmbeddedReader = useCallback(
+ async (params: ConnectEmbeddedParams) => {
+ setLoading(true);
+
+ const response = await connectEmbeddedReader(params);
+
+ if (response.reader) {
+ setConnectedReader(response.reader);
+ }
+ setLoading(false);
+
+ return response;
+ },
+ [setConnectedReader, setLoading]
+ );
+
+ const _connectLocalMobileReader = useCallback(
+ async (params: ConnectLocalMobileParams) => {
+ setLoading(true);
+
+ const response = await connectLocalMobileReader(params);
+
+ if (response.reader) {
+ setConnectedReader(response.reader);
+ }
+ setLoading(false);
+
+ return response;
+ },
+ [setConnectedReader, setLoading]
+ );
+
+ const _connectHandoffReader = useCallback(
+ async (params: ConnectEmbeddedParams) => {
+ setLoading(true);
+
+ const response = await connectHandoffReader(params);
+
+ if (response.reader) {
+ setConnectedReader(response.reader);
+ }
+ setLoading(false);
+
+ return response;
+ },
+ [setConnectedReader, setLoading]
+ );
+
const _disconnectReader = useCallback(async () => {
setLoading(true);
@@ -528,6 +581,9 @@ export function useStripeTerminal(props?: Props) {
cancelCollectPaymentMethod: _cancelCollectPaymentMethod,
cancelCollectSetupIntent: _cancelCollectSetupIntent,
cancelReadReusableCard: _cancelReadReusableCard,
+ connectEmbeddedReader: _connectEmbeddedReader,
+ connectHandoffReader: _connectHandoffReader,
+ connectLocalMobileReader: _connectLocalMobileReader,
emitter: emitter,
discoveredReaders,
connectedReader,
diff --git a/src/types/Reader.ts b/src/types/Reader.ts
index d5887389..6f69f6bd 100644
--- a/src/types/Reader.ts
+++ b/src/types/Reader.ts
@@ -1,6 +1,8 @@
import type { Location, LocationStatus } from './';
export namespace Reader {
+ export type DiscoveryMethod = IOS.DiscoveryMethod | Android.DiscoveryMethod;
+
export type Type = IOS.Type &
Android.Type & {
id: string;
@@ -23,6 +25,11 @@ export namespace Reader {
batteryStatus: BatteryStatus;
isCharging?: number;
};
+
+ export type DiscoveryMethod =
+ | 'bluetoothProximity'
+ | 'bluetoothScan'
+ | 'internet';
}
export namespace Android {
@@ -39,6 +46,14 @@ export namespace Reader {
settingsVersion?: string;
pinKeysetId?: string;
};
+
+ export type DiscoveryMethod =
+ | 'bluetoothScan'
+ | 'internet'
+ | 'embedded'
+ | 'localMobile'
+ | 'handoff'
+ | 'usb';
}
export type BatteryStatus = 'critical' | 'low' | 'nominal' | 'unknown';
@@ -57,15 +72,6 @@ export namespace Reader {
| 'estimate5To15Minutes'
| 'estimateLessThan1Minute';
- export type DiscoveryMethod =
- | 'bluetoothProximity'
- | 'bluetoothScan'
- | 'internet'
- | 'embedded'
- | 'localMobile'
- | 'handoff'
- | 'usb';
-
export type SimulateUpdateType =
| 'random'
| 'available'
diff --git a/src/types/index.ts b/src/types/index.ts
index af9e0f45..b0259087 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -42,6 +42,21 @@ export type ConnectUsbReaderParams = {
locationId?: string;
};
+export type ConnectLocalMobileParams = {
+ reader: Reader.Type;
+ locationId?: string;
+};
+
+export type ConnectHandoffParams = {
+ reader: Reader.Type;
+ locationId?: string;
+};
+
+export type ConnectEmbeddedParams = {
+ reader: Reader.Type;
+ locationId?: string;
+};
+
export type LineItem = {
displayName: string;
quantity: number;
@@ -88,14 +103,7 @@ export type CancelDiscoveringResultType = Promise<{
error?: StripeError;
}>;
-export type ConnectBluetoothReaderResultType =
- | {
- reader: Reader.Type;
- error?: undefined;
- }
- | { reader?: undefined; error: StripeError };
-
-export type ConnectInternetResultType =
+export type ConnectReaderResultType =
| {
reader: Reader.Type;
error?: undefined;