diff --git a/demo/angular/src/app/terminal/terminal.page.html b/demo/angular/src/app/terminal/terminal.page.html
index c656f9be5..8b60598c3 100644
--- a/demo/angular/src/app/terminal/terminal.page.html
+++ b/demo/angular/src/app/terminal/terminal.page.html
@@ -37,13 +37,13 @@
Device Update - Happy Path
simulateReaderUpdate.UpdateAvailable
simulateReaderUpdate.Required
- this.helper.updateItem(this.eventItems, 'collectPaymentMethod', true),
- )
- .catch(async (e) => {
- await this.helper.updateItem(
- this.eventItems,
- 'collectPaymentMethod',
- false,
- );
- throw e;
- });
+ if (readerType === TerminalConnectTypes.Internet) {
+ await StripeTerminal.setReaderDisplay({
+ currency: 'usd',
+ tax: 0,
+ total: 1000,
+ lineItems: [{
+ displayName: 'winecode',
+ quantity: 2,
+ amount: 500
+ }] as CartLineItem[],
+ })
+
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+
+ await StripeTerminal.clearReaderDisplay();
+ }
+
+
if (type === 'cancelPath') {
+ // During Collect, cancel the payment
+ StripeTerminal.collectPaymentMethod({ paymentIntent })
+ .catch(async (e) => {
+ await this.helper.updateItem(
+ this.eventItems,
+ 'collectPaymentMethod',
+ false,
+ );
+ throw e;
+ });
+ await this.helper.updateItem(this.eventItems, 'collectPaymentMethod', true);
await new Promise((resolve) => setTimeout(resolve, 2000));
await StripeTerminal.cancelCollectPaymentMethod().catch(async (e) => {
await this.helper.updateItem(
@@ -174,6 +194,18 @@ export class TerminalPage {
true,
);
} else {
+ await StripeTerminal.collectPaymentMethod({ paymentIntent })
+ .then(() =>
+ this.helper.updateItem(this.eventItems, 'collectPaymentMethod', true),
+ )
+ .catch(async (e) => {
+ await this.helper.updateItem(
+ this.eventItems,
+ 'collectPaymentMethod',
+ false,
+ );
+ throw e;
+ });
await StripeTerminal.confirmPaymentIntent();
await this.helper.updateItem(
this.eventItems,
@@ -186,28 +218,102 @@ export class TerminalPage {
this.listenerHandlers.forEach((handler) => handler.remove());
}
- async checkUpdateDevice(readerType: TerminalConnectTypes = TerminalConnectTypes.Bluetooth, simulateReaderUpdate: SimulateReaderUpdate) {
- await this.prepareTerminalEvents(structuredClone(checkUpdateDeviceItems));
-
- switch (simulateReaderUpdate) {
- case SimulateReaderUpdate.UpdateAvailable:
- await StripeTerminal.setSimulatorConfiguration({ update: SimulateReaderUpdate.UpdateAvailable })
- .then(() => this.helper.updateItem(this.eventItems, 'setSimulatorConfiguration:UPDATE_AVAILABLE', true));
- break;
- case SimulateReaderUpdate.LowBattery:
- await StripeTerminal.setSimulatorConfiguration({ update: SimulateReaderUpdate.LowBattery })
- .then(() => this.helper.updateItem(this.eventItems, 'setSimulatorConfiguration:LOW_BATTERY', true));
- break;
- case SimulateReaderUpdate.LowBatterySucceedConnect:
- await StripeTerminal.setSimulatorConfiguration({ update: SimulateReaderUpdate.LowBatterySucceedConnect })
- .then(() => this.helper.updateItem(this.eventItems, 'setSimulatorConfiguration:LOW_BATTERY_SUCCEED_CONNECT', true));
- break;
- case SimulateReaderUpdate.Required:
- await StripeTerminal.setSimulatorConfiguration({ update: SimulateReaderUpdate.Required })
- .then(() => this.helper.updateItem(this.eventItems, 'setSimulatorConfiguration:REQUIRED', true));
- break;
+ async checkUpdateDeviceUpdate(readerType: TerminalConnectTypes = TerminalConnectTypes.Bluetooth) {
+ await this.prepareTerminalEvents(structuredClone(updateDeviceUpdateItems));
+ await StripeTerminal.setSimulatorConfiguration({ update: SimulateReaderUpdate.UpdateAvailable })
+ .then(() => this.helper.updateItem(this.eventItems, 'setSimulatorConfiguration:UPDATE_AVAILABLE', true));
+
+ const result = await StripeTerminal.discoverReaders({
+ type: readerType,
+ locationId:
+ [TerminalConnectTypes.Usb].includes(readerType)
+ ? 'tml_Ff37mAmk1XdBYT' // Auckland, New Zealand
+ : 'tml_FOUOdQVIxvVdvN', // San Francisco, CA 94110
+ }).catch((e) => {
+ this.helper.updateItem(this.eventItems, 'discoverReaders', false);
+ throw e;
+ });
+
+ await this.helper.updateItem(
+ this.eventItems,
+ 'discoverReaders',
+ result.readers.length > 0,
+ );
+
+ const selectedReader =
+ result.readers.length === 1
+ ? result.readers[0]
+ : await this.alertFilterReaders(result.readers);
+ console.log(selectedReader);
+ if (!selectedReader) {
+ alert('No reader selected');
+ return;
}
+ await StripeTerminal.connectReader({
+ reader: selectedReader,
+ }).catch((e) => {
+ alert(e);
+ this.helper.updateItem(this.eventItems, 'connectReader', false);
+ throw e;
+ });
+ await this.helper.updateItem(this.eventItems, 'connectReader', true);
+
+ await StripeTerminal.installAvailableUpdate()
+ .then(() => this.helper.updateItem(this.eventItems, 'installAvailableUpdate', true));
+
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+
+ await StripeTerminal.cancelInstallUpdate()
+ .then(() => this.helper.updateItem(this.eventItems, 'cancelInstallUpdate', true));
+
+ // await new Promise((resolve) => setTimeout(resolve, 5000));
+
+ const { paymentIntent } = await firstValueFrom(
+ this.http.post<{
+ paymentIntent: string;
+ }>(environment.api + 'connection/intent', {}),
+ ).catch(async (e) => {
+ await this.helper.updateItem(
+ this.eventItems,
+ 'HttpClientPaymentIntent',
+ false,
+ );
+ throw e;
+ });
+ await this.helper.updateItem(
+ this.eventItems,
+ 'HttpClientPaymentIntent',
+ true,
+ );
+
+ await StripeTerminal.collectPaymentMethod({ paymentIntent })
+ .then(() =>
+ this.helper.updateItem(this.eventItems, 'collectPaymentMethod', true),
+ )
+ .catch(async (e) => {
+ await this.helper.updateItem(
+ this.eventItems,
+ 'collectPaymentMethod',
+ false,
+ );
+ throw e;
+ });
+
+ await StripeTerminal.disconnectReader().catch((e) => {
+ this.helper.updateItem(this.eventItems, 'disconnectReader', false);
+ throw e;
+ });
+ await this.helper.updateItem(this.eventItems, 'disconnectReader', true);
+
+ this.listenerHandlers.forEach((handler) => handler.remove());
+ }
+
+ async checkUpdateDeviceRequired(readerType: TerminalConnectTypes = TerminalConnectTypes.Bluetooth) {
+ await this.prepareTerminalEvents(structuredClone(updateDeviceRequiredItems));
+ await StripeTerminal.setSimulatorConfiguration({ update: SimulateReaderUpdate.Required })
+ .then(() => this.helper.updateItem(this.eventItems, 'setSimulatorConfiguration:REQUIRED', true));
+
const result = await StripeTerminal.discoverReaders({
type: readerType,
locationId:
@@ -347,12 +453,14 @@ export class TerminalPage {
const alert = await this.alertCtrl.create({
header: `Select a reader`,
message: `Select a reader to connect to.`,
+ backdropDismiss: false,
inputs: readers.map((reader, index) => ({
name: 'serialNumber',
type: 'radio',
- label: reader.serialNumber,
+ label: reader.deviceType,
value: reader.serialNumber,
checked: index === 0,
+ disabled: reader.status === 'OFFLINE'
})),
buttons: [
{
diff --git a/demo/angular/src/app/terminal/updateDeviceRequiredItems.ts b/demo/angular/src/app/terminal/updateDeviceRequiredItems.ts
new file mode 100644
index 000000000..d379a682f
--- /dev/null
+++ b/demo/angular/src/app/terminal/updateDeviceRequiredItems.ts
@@ -0,0 +1,54 @@
+import {ITestItems} from '../shared/interfaces';
+import {TerminalEventsEnum} from '@capacitor-community/stripe-terminal';
+
+export const updateDeviceRequiredItems: ITestItems[] = [
+ {
+ type: 'method',
+ name: 'initialize',
+ },
+ {
+ type: 'event',
+ name: TerminalEventsEnum.Loaded,
+ },
+
+ {
+ type: 'method',
+ name: 'setSimulatorConfiguration:REQUIRED',
+ },
+ {
+ type: 'method',
+ name: 'discoverReaders',
+ },
+ {
+ type: 'event',
+ name: TerminalEventsEnum.DiscoveredReaders,
+ },
+ {
+ type: 'method',
+ name: 'connectReader',
+ },
+ {
+ type: 'event',
+ name: TerminalEventsEnum.ConnectedReader,
+ },
+ {
+ type: 'event',
+ name: TerminalEventsEnum.StartInstallingUpdate,
+ },
+ {
+ type: 'event',
+ name: TerminalEventsEnum.ReaderSoftwareUpdateProgress,
+ },
+ {
+ type: 'event',
+ name: TerminalEventsEnum.FinishInstallingUpdate,
+ },
+ {
+ type: 'method',
+ name: 'disconnectReader',
+ },
+ {
+ type: 'event',
+ name: TerminalEventsEnum.DisconnectedReader,
+ },
+];
diff --git a/demo/angular/src/app/terminal/checkUpdateDeviceItems.ts b/demo/angular/src/app/terminal/updateDeviceUpdateItems.ts
similarity index 85%
rename from demo/angular/src/app/terminal/checkUpdateDeviceItems.ts
rename to demo/angular/src/app/terminal/updateDeviceUpdateItems.ts
index c6d9a3c7d..51252cc34 100644
--- a/demo/angular/src/app/terminal/checkUpdateDeviceItems.ts
+++ b/demo/angular/src/app/terminal/updateDeviceUpdateItems.ts
@@ -1,7 +1,7 @@
import {ITestItems} from '../shared/interfaces';
import {TerminalEventsEnum} from '@capacitor-community/stripe-terminal';
-export const checkUpdateDeviceItems: ITestItems[] = [
+export const updateDeviceUpdateItems: ITestItems[] = [
{
type: 'method',
name: 'initialize',
@@ -10,47 +10,46 @@ export const checkUpdateDeviceItems: ITestItems[] = [
type: 'event',
name: TerminalEventsEnum.Loaded,
},
-
{
type: 'method',
- name: 'setSimulatorConfiguration:UPDATE_AVAILABLE',
+ name: 'discoverReaders',
},
{
type: 'event',
- name: TerminalEventsEnum.ReportAvailableUpdate,
+ name: TerminalEventsEnum.DiscoveredReaders,
},
{
type: 'method',
- name: 'setSimulatorConfiguration:REQUIRED',
+ name: 'connectReader',
},
{
type: 'event',
- name: TerminalEventsEnum.StartInstallingUpdate,
+ name: TerminalEventsEnum.ConnectedReader,
},
+
{
- type: 'event',
- name: TerminalEventsEnum.ReaderSoftwareUpdateProgress,
+ type: 'method',
+ name: 'setSimulatorConfiguration:UPDATE_AVAILABLE',
},
{
type: 'event',
- name: TerminalEventsEnum.FinishInstallingUpdate,
+ name: TerminalEventsEnum.ReportAvailableUpdate,
},
-
{
type: 'method',
- name: 'discoverReaders',
+ name: 'installAvailableUpdate',
},
{
type: 'event',
- name: TerminalEventsEnum.DiscoveredReaders,
+ name: TerminalEventsEnum.StartInstallingUpdate,
},
{
- type: 'method',
- name: 'connectReader',
+ type: 'event',
+ name: TerminalEventsEnum.ReaderSoftwareUpdateProgress,
},
{
- type: 'event',
- name: TerminalEventsEnum.ConnectedReader,
+ type: 'method',
+ name: 'cancelInstallUpdate',
},
{
type: 'method',
diff --git a/packages/identity/package-lock.json b/packages/identity/package-lock.json
index c3325aba8..9cacd84b4 100644
--- a/packages/identity/package-lock.json
+++ b/packages/identity/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@capacitor-community/stripe-identity",
- "version": "6.0.2",
+ "version": "6.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@capacitor-community/stripe-identity",
- "version": "6.0.2",
+ "version": "6.1.0",
"license": "MIT",
"dependencies": {
"@stripe/stripe-js": "^2.1.11"
diff --git a/packages/identity/package.json b/packages/identity/package.json
index 5f17bcc86..56964dbfb 100644
--- a/packages/identity/package.json
+++ b/packages/identity/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor-community/stripe-identity",
- "version": "6.0.2",
+ "version": "6.1.0",
"engines": {
"node": ">=18.0.0"
},
diff --git a/packages/payment/package-lock.json b/packages/payment/package-lock.json
index c998bdaf4..5614e28f3 100644
--- a/packages/payment/package-lock.json
+++ b/packages/payment/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@capacitor-community/stripe",
- "version": "6.0.2",
+ "version": "6.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@capacitor-community/stripe",
- "version": "6.0.2",
+ "version": "6.1.0",
"license": "MIT",
"devDependencies": {
"@capacitor/android": "^6.0.0",
diff --git a/packages/payment/package.json b/packages/payment/package.json
index 55ec8f2f8..8feeac06d 100644
--- a/packages/payment/package.json
+++ b/packages/payment/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor-community/stripe",
- "version": "6.0.2",
+ "version": "6.1.0",
"engines": {
"node": ">=18.0.0"
},
diff --git a/packages/terminal/README.md b/packages/terminal/README.md
index 29466bb57..77f7f867d 100644
--- a/packages/terminal/README.md
+++ b/packages/terminal/README.md
@@ -1,13 +1,8 @@
# @capacitor-community/stripe-terminal
-Stripe SDK bindings for Capacitor Applications. __This plugin is still in beta.__
+Stripe SDK bindings for Capacitor Applications. __This plugin is still in rc(pre-release) version.__
We have confirmed that it works well in the demo project. Please refer to https://github.com/capacitor-community/stripe/tree/main/demo/angular for the implementation.
-- [x] Tap To Pay
-- [x] Internet
-- [x] Bluetooth
-- [x] USB
-
## Install
```bash
@@ -57,7 +52,9 @@ And update minSdkVersion to 26 And compileSdkVersion to 34 in your `android/app/
## Usage
-### use native http client for getting a token
+### Simple collect payment
+
+#### Use plugin client
```typescript
(async ()=> {
@@ -81,7 +78,7 @@ And update minSdkVersion to 26 And compileSdkVersion to 34 in your `android/app/
});
```
-### set string token
+#### set string token
```typescript
(async ()=> {
@@ -109,6 +106,95 @@ And update minSdkVersion to 26 And compileSdkVersion to 34 in your `android/app/
});
````
+### Listen device update
+
+The device will **if necessary** automatically start updating itself. It is important to handle them as needed so as not to disrupt business operations.
+
+```ts
+(async ()=> {
+ StripeTerminal.addListener(TerminalEventsEnum.ReportAvailableUpdate, async ({ update }) => {
+ if (window.confirm("Will you update the device?")) {
+ await StripeTerminal.installAvailableUpdate();
+ }
+ });
+ StripeTerminal.addListener(TerminalEventsEnum.StartInstallingUpdate, async ({ update }) => {
+ console.log(update);
+ if (window.confirm("Will you interrupt the update?")) {
+ StripeTerminal.cancelInstallUpdate();
+ }
+ });
+ StripeTerminal.addListener(TerminalEventsEnum.ReaderSoftwareUpdateProgress, async ({ progress }) => {
+ // be able to use this value to create a progress bar.
+ });
+ StripeTerminal.addListener(TerminalEventsEnum.FinishInstallingUpdate, async ({ update }) => {
+ console.log(update);
+ });
+});
+```
+
+### Get terminal processing information
+
+For devices without leader screen, processing information must be retrieved and displayed on the mobile device. Get it with a listener.
+
+```ts
+/**
+ * Listen battery level. If the battery level is low, you can notify the user to charge the device.
+ */
+StripeTerminal.addListener(TerminalEventsEnum.BatteryLevel, async ({ level, charging, status }) => {
+ console.log(level, charging, status);
+});
+
+/**
+ * Listen reader event. You can get the reader's status and display it on the mobile device.
+ */
+StripeTerminal.addListener(TerminalEventsEnum.ReaderEvent, async ({ event }) => {
+ console.log(event);
+});
+
+/**
+ * Listen display message. You can get the message to be displayed on the mobile device.
+ */
+StripeTerminal.addListener(TerminalEventsEnum.RequestDisplayMessage, async ({ messageType, message }) => {
+ console.log(messageType, message);
+});
+
+/**
+ * Listen reader input. You can get the message what can be used for payment.
+ */
+StripeTerminal.addListener(TerminalEventsEnum.RequestReaderInput, async ({ options, message }) => {
+ console.log(options, message);
+});
+```
+
+### More details on the leader screen
+
+The contents of the payment can be shown on the display. This requires a leader screen on the device.
+This should be run before `collectPaymentMethod`.
+
+```ts
+await StripeTerminal.setReaderDisplay({
+ currency: 'usd',
+ tax: 0,
+ total: 1000,
+ lineItems: [{
+ displayName: 'winecode',
+ quantity: 2,
+ amount: 500
+ }] as CartLineItem[],
+})
+
+// Of course, erasure is also possible.
+await StripeTerminal.clearReaderDisplay();
+```
+
+### Simulate reader status changes for testing
+
+To implement updates, etc., we are facilitating an API to change the state of the simulator. This should be done before discoverReaders.
+
+```ts
+await StripeTerminal.setSimulatorConfiguration({ update: SimulateReaderUpdate.UpdateAvailable })
+```
+
## API
@@ -124,6 +210,12 @@ And update minSdkVersion to 26 And compileSdkVersion to 34 in your `android/app/
* [`collectPaymentMethod(...)`](#collectpaymentmethod)
* [`cancelCollectPaymentMethod()`](#cancelcollectpaymentmethod)
* [`confirmPaymentIntent()`](#confirmpaymentintent)
+* [`installAvailableUpdate()`](#installavailableupdate)
+* [`cancelInstallUpdate()`](#cancelinstallupdate)
+* [`setReaderDisplay(...)`](#setreaderdisplay)
+* [`clearReaderDisplay()`](#clearreaderdisplay)
+* [`rebootReader()`](#rebootreader)
+* [`cancelReaderReconnection()`](#cancelreaderreconnection)
* [`addListener(TerminalEventsEnum.Loaded, ...)`](#addlistenerterminaleventsenumloaded)
* [`addListener(TerminalEventsEnum.RequestedConnectionToken, ...)`](#addlistenerterminaleventsenumrequestedconnectiontoken)
* [`addListener(TerminalEventsEnum.DiscoveredReaders, ...)`](#addlistenerterminaleventsenumdiscoveredreaders)
@@ -144,6 +236,9 @@ And update minSdkVersion to 26 And compileSdkVersion to 34 in your `android/app/
* [`addListener(TerminalEventsEnum.RequestDisplayMessage, ...)`](#addlistenerterminaleventsenumrequestdisplaymessage)
* [`addListener(TerminalEventsEnum.RequestReaderInput, ...)`](#addlistenerterminaleventsenumrequestreaderinput)
* [`addListener(TerminalEventsEnum.PaymentStatusChange, ...)`](#addlistenerterminaleventsenumpaymentstatuschange)
+* [`addListener(TerminalEventsEnum.ReaderReconnectStarted, ...)`](#addlistenerterminaleventsenumreaderreconnectstarted)
+* [`addListener(TerminalEventsEnum.ReaderReconnectSucceeded, ...)`](#addlistenerterminaleventsenumreaderreconnectsucceeded)
+* [`addListener(TerminalEventsEnum.ReaderReconnectFailed, ...)`](#addlistenerterminaleventsenumreaderreconnectfailed)
* [Interfaces](#interfaces)
* [Type Aliases](#type-aliases)
* [Enums](#enums)
@@ -212,12 +307,12 @@ setSimulatorConfiguration(options: { update?: SimulateReaderUpdate; simulatedCar
### connectReader(...)
```typescript
-connectReader(options: { reader: ReaderInterface; }) => Promise
+connectReader(options: { reader: ReaderInterface; autoReconnectOnUnexpectedDisconnect?: boolean; merchantDisplayName?: string; onBehalfOf?: string; }) => Promise
```
-| Param | Type |
-| ------------- | ------------------------------------------------------------------------ |
-| **`options`** | { reader: ReaderInterface; }
|
+| Param | Type |
+| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`options`** | { reader: ReaderInterface; autoReconnectOnUnexpectedDisconnect?: boolean; merchantDisplayName?: string; onBehalfOf?: string; }
|
--------------------
@@ -282,6 +377,64 @@ confirmPaymentIntent() => Promise
--------------------
+### installAvailableUpdate()
+
+```typescript
+installAvailableUpdate() => Promise
+```
+
+--------------------
+
+
+### cancelInstallUpdate()
+
+```typescript
+cancelInstallUpdate() => Promise
+```
+
+--------------------
+
+
+### setReaderDisplay(...)
+
+```typescript
+setReaderDisplay(options: Cart) => Promise
+```
+
+| Param | Type |
+| ------------- | ------------------------------------- |
+| **`options`** | Cart
|
+
+--------------------
+
+
+### clearReaderDisplay()
+
+```typescript
+clearReaderDisplay() => Promise
+```
+
+--------------------
+
+
+### rebootReader()
+
+```typescript
+rebootReader() => Promise
+```
+
+--------------------
+
+
+### cancelReaderReconnection()
+
+```typescript
+cancelReaderReconnection() => Promise
+```
+
+--------------------
+
+
### addListener(TerminalEventsEnum.Loaded, ...)
```typescript
@@ -700,6 +853,54 @@ addListener(eventName: TerminalEventsEnum.PaymentStatusChange, listenerFunc: ({
--------------------
+### addListener(TerminalEventsEnum.ReaderReconnectStarted, ...)
+
+```typescript
+addListener(eventName: TerminalEventsEnum.ReaderReconnectStarted, listenerFunc: ({ reader, reason, }: { reader: ReaderInterface; reason: string; }) => void) => Promise
+```
+
+| Param | Type |
+| ------------------ | -------------------------------------------------------------------------------------------------------------------------- |
+| **`eventName`** | TerminalEventsEnum.ReaderReconnectStarted
|
+| **`listenerFunc`** | ({ reader, reason, }: { reader: ReaderInterface; reason: string; }) => void
|
+
+**Returns:** Promise<PluginListenerHandle>
+
+--------------------
+
+
+### addListener(TerminalEventsEnum.ReaderReconnectSucceeded, ...)
+
+```typescript
+addListener(eventName: TerminalEventsEnum.ReaderReconnectSucceeded, listenerFunc: ({ reader }: { reader: ReaderInterface; }) => void) => Promise
+```
+
+| Param | Type |
+| ------------------ | ------------------------------------------------------------------------------------------------- |
+| **`eventName`** | TerminalEventsEnum.ReaderReconnectSucceeded
|
+| **`listenerFunc`** | ({ reader }: { reader: ReaderInterface; }) => void
|
+
+**Returns:** Promise<PluginListenerHandle>
+
+--------------------
+
+
+### addListener(TerminalEventsEnum.ReaderReconnectFailed, ...)
+
+```typescript
+addListener(eventName: TerminalEventsEnum.ReaderReconnectFailed, listenerFunc: ({ reader }: { reader: ReaderInterface; }) => void) => Promise
+```
+
+| Param | Type |
+| ------------------ | ------------------------------------------------------------------------------------------------- |
+| **`eventName`** | TerminalEventsEnum.ReaderReconnectFailed
|
+| **`listenerFunc`** | ({ reader }: { reader: ReaderInterface; }) => void
|
+
+**Returns:** Promise<PluginListenerHandle>
+
+--------------------
+
+
### Interfaces
@@ -715,17 +916,91 @@ addListener(eventName: TerminalEventsEnum.PaymentStatusChange, listenerFunc: ({
#### ReaderInterface
-{ index: number; serialNumber: string; }
+{ /** * The unique serial number is primary identifier inner plugin. */ serialNumber: string; label: string; batteryLevel: number; batteryStatus: BatteryStatus; simulated: boolean; id: number; availableUpdate: ReaderSoftwareUpdateInterface; locationId: string; ipAddress: string; status: NetworkStatus; location: LocationInterface; locationStatus: LocationStatus; deviceType: DeviceType; deviceSoftwareVersion: string; /** * iOS Only properties. These properties are not available on Android. */ isCharging: number; /** * Android Only properties. These properties are not available on iOS. */ baseUrl: string; bootloaderVersion: string; configVersion: string; emvKeyProfileId: string; firmwareVersion: string; hardwareVersion: string; macKeyProfileId: string; pinKeyProfileId: string; trackKeyProfileId: string; settingsVersion: string; pinKeysetId: string; /** * @deprecated This property has been deprecated and should use the `serialNumber` property. */ index?: number; }
#### ReaderSoftwareUpdateInterface
-{ version: string; settingsVersion: string; requiredAt: number; timeEstimate: UpdateTimeEstimate; }
+{ deviceSoftwareVersion: string; estimatedUpdateTime: UpdateTimeEstimate; requiredAt: number; }
+
+
+#### LocationInterface
+
+{ id: string; displayName: string; address: { city: string; country: string; postalCode: string; line1: string; line2: string; state: string; }; ipAddress: string; }
+
+
+#### Cart
+
+{ currency: string; tax: number; total: number; lineItems: CartLineItem[]; }
+
+
+#### CartLineItem
+
+{ displayName: string; quantity: number; amount: number; }
### Enums
+#### BatteryStatus
+
+| Members | Value |
+| -------------- | ----------------------- |
+| **`Unknown`** | 'UNKNOWN'
|
+| **`Critical`** | 'CRITICAL'
|
+| **`Low`** | 'LOW'
|
+| **`Nominal`** | 'NOMINAL'
|
+
+
+#### UpdateTimeEstimate
+
+| Members | Value |
+| -------------------------- | -------------------------------------- |
+| **`LessThanOneMinute`** | 'LESS_THAN_ONE_MINUTE'
|
+| **`OneToTwoMinutes`** | 'ONE_TO_TWO_MINUTES'
|
+| **`TwoToFiveMinutes`** | 'TWO_TO_FIVE_MINUTES'
|
+| **`FiveToFifteenMinutes`** | 'FIVE_TO_FIFTEEN_MINUTES'
|
+
+
+#### NetworkStatus
+
+| Members | Value |
+| ------------- | ---------------------- |
+| **`Unknown`** | 'UNKNOWN'
|
+| **`Online`** | 'ONLINE'
|
+| **`Offline`** | 'OFFLINE'
|
+
+
+#### LocationStatus
+
+| Members | Value |
+| ------------- | ---------------------- |
+| **`NotSet`** | 'NOT_SET'
|
+| **`Set`** | 'SET'
|
+| **`Unknown`** | 'UNKNOWN'
|
+
+
+#### DeviceType
+
+| Members | Value |
+| ---------------------- | ------------------------------- |
+| **`cotsDevice`** | 'cotsDevice'
|
+| **`wisePad3s`** | 'wisePad3s'
|
+| **`appleBuiltIn`** | 'appleBuiltIn'
|
+| **`chipper1X`** | 'chipper1X'
|
+| **`chipper2X`** | 'chipper2X'
|
+| **`etna`** | 'etna'
|
+| **`stripeM2`** | 'stripeM2'
|
+| **`stripeS700`** | 'stripeS700'
|
+| **`stripeS700DevKit`** | 'stripeS700Devkit'
|
+| **`verifoneP400`** | 'verifoneP400'
|
+| **`wiseCube`** | 'wiseCube'
|
+| **`wisePad3`** | 'wisePad3'
|
+| **`wisePosE`** | 'wisePosE'
|
+| **`wisePosEDevKit`** | 'wisePosEDevkit'
|
+| **`unknown`** | 'unknown'
|
+
+
#### TerminalConnectTypes
| Members | Value |
@@ -808,6 +1083,9 @@ addListener(eventName: TerminalEventsEnum.PaymentStatusChange, listenerFunc: ({
| **`RequestDisplayMessage`** | 'terminalRequestDisplayMessage'
|
| **`RequestReaderInput`** | 'terminalRequestReaderInput'
|
| **`PaymentStatusChange`** | 'terminalPaymentStatusChange'
|
+| **`ReaderReconnectStarted`** | 'terminalReaderReconnectStarted'
|
+| **`ReaderReconnectSucceeded`** | 'terminalReaderReconnectSucceeded'
|
+| **`ReaderReconnectFailed`** | 'terminalReaderReconnectFailed'
|
#### DisconnectReason
@@ -833,26 +1111,6 @@ addListener(eventName: TerminalEventsEnum.PaymentStatusChange, listenerFunc: ({
| **`Connected`** | 'CONNECTED'
|
-#### UpdateTimeEstimate
-
-| Members | Value |
-| -------------------------- | -------------------------------------- |
-| **`LessThanOneMinute`** | 'LESS_THAN_ONE_MINUTE'
|
-| **`OneToTwoMinutes`** | 'ONE_TO_TWO_MINUTES'
|
-| **`TwoToFiveMinutes`** | 'TWO_TO_FIVE_MINUTES'
|
-| **`FiveToFifteenMinutes`** | 'FIVE_TO_FIFTEEN_MINUTES'
|
-
-
-#### BatteryStatus
-
-| Members | Value |
-| -------------- | ----------------------- |
-| **`Unknown`** | 'UNKNOWN'
|
-| **`Critical`** | 'CRITICAL'
|
-| **`Low`** | 'LOW'
|
-| **`Nominal`** | 'NOMINAL'
|
-
-
#### ReaderEvent
| Members | Value |
diff --git a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminal.java b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminal.java
index a0252d006..e7e9a84ab 100644
--- a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminal.java
+++ b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminal.java
@@ -14,6 +14,7 @@
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
+import com.getcapacitor.community.stripe.terminal.helper.TerminalMappers;
import com.getcapacitor.community.stripe.terminal.models.Executor;
import com.google.android.gms.common.util.BiConsumer;
import com.stripe.stripeterminal.Terminal;
@@ -28,14 +29,19 @@
import com.stripe.stripeterminal.external.callable.TerminalListener;
import com.stripe.stripeterminal.external.models.BatteryStatus;
import com.stripe.stripeterminal.external.models.CardPresentDetails;
+import com.stripe.stripeterminal.external.models.Cart;
+import com.stripe.stripeterminal.external.models.CartLineItem;
import com.stripe.stripeterminal.external.models.CollectConfiguration;
import com.stripe.stripeterminal.external.models.ConnectionConfiguration.BluetoothConnectionConfiguration;
import com.stripe.stripeterminal.external.models.ConnectionConfiguration.InternetConnectionConfiguration;
import com.stripe.stripeterminal.external.models.ConnectionConfiguration.LocalMobileConnectionConfiguration;
import com.stripe.stripeterminal.external.models.ConnectionConfiguration.UsbConnectionConfiguration;
import com.stripe.stripeterminal.external.models.ConnectionStatus;
+import com.stripe.stripeterminal.external.models.DeviceType;
import com.stripe.stripeterminal.external.models.DisconnectReason;
import com.stripe.stripeterminal.external.models.DiscoveryConfiguration;
+import com.stripe.stripeterminal.external.models.Location;
+import com.stripe.stripeterminal.external.models.LocationStatus;
import com.stripe.stripeterminal.external.models.PaymentIntent;
import com.stripe.stripeterminal.external.models.PaymentMethod;
import com.stripe.stripeterminal.external.models.PaymentStatus;
@@ -54,13 +60,17 @@
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
+import org.json.JSONException;
+import org.json.JSONObject;
public class StripeTerminal extends Executor {
private TokenProvider tokenProvider;
private Cancelable discoveryCancelable;
private Cancelable collectCancelable;
- private List readers;
+ private Cancelable installUpdateCancelable;
+ private Cancelable cancelReaderConnectionCancellable;
+ private List discoveredReadersList;
private String locationId;
private PluginCall collectCall;
private PluginCall confirmPaymentIntentCall;
@@ -69,6 +79,8 @@ public class StripeTerminal extends Executor {
private TerminalConnectTypes terminalConnectType;
private PaymentIntent paymentIntentInstance;
+ private final TerminalMappers terminalMappers = new TerminalMappers();
+
public StripeTerminal(
Supplier contextSupplier,
Supplier activitySupplier,
@@ -77,7 +89,7 @@ public StripeTerminal(
) {
super(contextSupplier, activitySupplier, notifyListenersFunction, pluginLogTag, "StripeTerminalExecutor");
this.contextSupplier = contextSupplier;
- this.readers = new ArrayList<>();
+ this.discoveredReadersList = new ArrayList<>();
}
public void initialize(final PluginCall call) throws TerminalException {
@@ -189,11 +201,11 @@ public void onDiscoverReaders(final PluginCall call) {
final DiscoveryListener discoveryListener = readers -> {
// 検索したReaderの一覧をListenerで渡す
Log.d(logTag, String.valueOf(readers.get(0).getSerialNumber()));
- this.readers = readers;
+ this.discoveredReadersList = readers;
JSArray readersJSObject = new JSArray();
int i = 0;
- for (Reader reader : this.readers) {
+ for (Reader reader : this.discoveredReadersList) {
readersJSObject.put(convertReaderInterface(reader).put("index", String.valueOf(i)));
}
this.notifyListeners(TerminalEnumEvent.DiscoveredReaders.getWebEventName(), new JSObject().put("readers", readersJSObject));
@@ -268,81 +280,125 @@ public void onFailure(@NonNull TerminalException ex) {
private void connectLocalMobileReader(final PluginCall call) {
JSObject reader = call.getObject("reader");
+ String serialNumber = reader.getString("serialNumber");
+
+ Reader foundReader =
+ this.discoveredReadersList.stream().filter(device -> serialNumber.equals(device.getSerialNumber())).findFirst().orElse(null);
- if (reader.getInteger("index") == null) {
+ if (serialNumber == null || foundReader == null) {
call.reject("The reader value is not set correctly.");
return;
}
+ Boolean autoReconnectOnUnexpectedDisconnect = call.getBoolean("autoReconnectOnUnexpectedDisconnect", false);
+
LocalMobileConnectionConfiguration config = new LocalMobileConnectionConfiguration(
this.locationId,
- true,
- this.localMobileReaderReconnectionListener
+ autoReconnectOnUnexpectedDisconnect,
+ this.readerReconnectionListener
);
- Terminal.getInstance().connectLocalMobileReader(this.readers.get(reader.getInteger("index")), config, this.readerCallback(call));
+ Terminal.getInstance().connectLocalMobileReader(foundReader, config, this.readerCallback(call));
}
- ReaderReconnectionListener localMobileReaderReconnectionListener = new ReaderReconnectionListener() {
+ ReaderReconnectionListener readerReconnectionListener = new ReaderReconnectionListener() {
@Override
- public void onReaderReconnectStarted(@NonNull Reader reader, @NonNull Cancelable cancelReconnect) {
- // 1. Notified at the start of a reconnection attempt
- // Use cancelable to stop reconnection at any time
+ public void onReaderReconnectStarted(@NonNull Reader reader, @NonNull Cancelable cancelable, @NonNull DisconnectReason reason) {
+ cancelReaderConnectionCancellable = cancelable;
+ notifyListeners(
+ TerminalEnumEvent.ReaderReconnectStarted.getWebEventName(),
+ new JSObject().put("reason", reason.toString()).put("reader", convertReaderInterface(reader))
+ );
}
@Override
public void onReaderReconnectSucceeded(@NonNull Reader reader) {
- // 2. Notified when reader reconnection succeeds
- // App is now connected
+ notifyListeners(
+ TerminalEnumEvent.ReaderReconnectSucceeded.getWebEventName(),
+ new JSObject().put("reader", convertReaderInterface(reader))
+ );
}
@Override
public void onReaderReconnectFailed(@NonNull Reader reader) {
- // 3. Notified when reader reconnection fails
- // App is now disconnected
+ notifyListeners(
+ TerminalEnumEvent.ReaderReconnectFailed.getWebEventName(),
+ new JSObject().put("reader", convertReaderInterface(reader))
+ );
}
};
private void connectInternetReader(final PluginCall call) {
JSObject reader = call.getObject("reader");
+ String serialNumber = reader.getString("serialNumber");
+
+ Reader foundReader =
+ this.discoveredReadersList.stream().filter(device -> serialNumber.equals(device.getSerialNumber())).findFirst().orElse(null);
+
+ if (serialNumber == null || foundReader == null) {
+ call.reject("The reader value is not set correctly.");
+ return;
+ }
+
InternetConnectionConfiguration config = new InternetConnectionConfiguration();
- Terminal.getInstance().connectInternetReader(this.readers.get(reader.getInteger("index")), config, this.readerCallback(call));
+ Terminal.getInstance().connectInternetReader(foundReader, config, this.readerCallback(call));
}
private void connectUsbReader(final PluginCall call) {
JSObject reader = call.getObject("reader");
+ String serialNumber = reader.getString("serialNumber");
+
+ Reader foundReader =
+ this.discoveredReadersList.stream().filter(device -> serialNumber.equals(device.getSerialNumber())).findFirst().orElse(null);
+
+ if (serialNumber == null || foundReader == null) {
+ call.reject("The reader value is not set correctly.");
+ return;
+ }
+
UsbConnectionConfiguration config = new UsbConnectionConfiguration(this.locationId);
- Terminal
- .getInstance()
- .connectUsbReader(this.readers.get(reader.getInteger("index")), config, this.readerListener(), this.readerCallback(call));
+ Terminal.getInstance().connectUsbReader(foundReader, config, this.readerListener(), this.readerCallback(call));
}
private void connectBluetoothReader(final PluginCall call) {
JSObject reader = call.getObject("reader");
- BluetoothConnectionConfiguration config = new BluetoothConnectionConfiguration(this.locationId);
- Terminal
- .getInstance()
- .connectBluetoothReader(this.readers.get(reader.getInteger("index")), config, this.readerListener(), this.readerCallback(call));
+ String serialNumber = reader.getString("serialNumber");
+
+ Reader foundReader =
+ this.discoveredReadersList.stream().filter(device -> serialNumber.equals(device.getSerialNumber())).findFirst().orElse(null);
+
+ if (serialNumber == null || foundReader == null) {
+ call.reject("The reader value is not set correctly.");
+ return;
+ }
+ Boolean autoReconnectOnUnexpectedDisconnect = call.getBoolean("autoReconnectOnUnexpectedDisconnect", false);
+
+ BluetoothConnectionConfiguration config = new BluetoothConnectionConfiguration(
+ this.locationId,
+ autoReconnectOnUnexpectedDisconnect,
+ this.readerReconnectionListener
+ );
+ Terminal.getInstance().connectBluetoothReader(foundReader, config, this.readerListener(), this.readerCallback(call));
}
public void cancelDiscoverReaders(final PluginCall call) {
- if (discoveryCancelable != null) {
- discoveryCancelable.cancel(
- new Callback() {
- @Override
- public void onSuccess() {
- notifyListeners(TerminalEnumEvent.CancelDiscoveredReaders.getWebEventName(), emptyObject);
- call.resolve();
- }
-
- @Override
- public void onFailure(@NonNull TerminalException ex) {
- call.reject(ex.getLocalizedMessage(), ex);
- }
- }
- );
- } else {
+ if (discoveryCancelable == null || discoveryCancelable.isCompleted()) {
call.resolve();
+ return;
}
+ discoveryCancelable.cancel(
+ new Callback() {
+ @Override
+ public void onSuccess() {
+ notifyListeners(TerminalEnumEvent.CancelDiscoveredReaders.getWebEventName(), emptyObject);
+ call.resolve();
+ }
+
+ @Override
+ public void onFailure(@NonNull TerminalException ex) {
+ call.reject(ex.getLocalizedMessage(), ex);
+ }
+ }
+ );
}
public void collectPaymentMethod(final PluginCall call) {
@@ -364,7 +420,6 @@ public void cancelCollectPaymentMethod(final PluginCall call) {
new Callback() {
@Override
public void onSuccess() {
- collectCancelable = null;
notifyListeners(TerminalEnumEvent.Canceled.getWebEventName(), emptyObject);
call.resolve();
}
@@ -394,7 +449,6 @@ public void onFailure(@NonNull TerminalException ex) {
private final PaymentIntentCallback collectPaymentMethodCallback = new PaymentIntentCallback() {
@Override
public void onSuccess(PaymentIntent paymentIntent) {
- collectCancelable = null;
paymentIntentInstance = paymentIntent;
notifyListeners(TerminalEnumEvent.CollectedPaymentIntent.getWebEventName(), emptyObject);
@@ -424,7 +478,6 @@ public void onSuccess(PaymentIntent paymentIntent) {
@Override
public void onFailure(@NonNull TerminalException ex) {
- collectCancelable = null;
notifyListeners(TerminalEnumEvent.Failed.getWebEventName(), emptyObject);
collectCall.reject(ex.getLocalizedMessage(), ex);
}
@@ -440,6 +493,149 @@ public void confirmPaymentIntent(final PluginCall call) {
Terminal.getInstance().confirmPaymentIntent(this.paymentIntentInstance, confirmPaymentMethodCallback);
}
+ public void installAvailableUpdate(final PluginCall call) {
+ Terminal.getInstance().installAvailableUpdate();
+ call.resolve(emptyObject);
+ }
+
+ public void cancelInstallUpdate(final PluginCall call) {
+ if (this.installUpdateCancelable == null || this.installUpdateCancelable.isCompleted()) {
+ call.resolve();
+ return;
+ }
+
+ this.installUpdateCancelable.cancel(
+ new Callback() {
+ @Override
+ public void onSuccess() {
+ call.resolve();
+ }
+
+ @Override
+ public void onFailure(@NonNull TerminalException e) {
+ call.reject(e.getLocalizedMessage());
+ }
+ }
+ );
+ }
+
+ public void setReaderDisplay(final PluginCall call) {
+ String currency = call.getString("currency", null);
+ if (currency == null) {
+ call.reject("You must provide a currency value");
+ return;
+ }
+
+ int tax = call.getInt("tax", 0);
+ int total = call.getInt("total", 0);
+ if (total == 0) {
+ call.reject("You must provide a total value");
+ return;
+ }
+
+ JSArray lineItems = call.getArray("lineItems");
+ List lineItemsList;
+ try {
+ lineItemsList = lineItems.toList();
+ } catch (JSONException e) {
+ call.reject(e.getLocalizedMessage());
+ return;
+ }
+
+ List cartLineItems = new ArrayList<>();
+ for (JSONObject item : lineItemsList) {
+ try {
+ JSObject itemObj = JSObject.fromJSONObject(item);
+ cartLineItems.add(
+ new CartLineItem(
+ Objects.requireNonNull(itemObj.getString("displayName")),
+ Objects.requireNonNull(itemObj.getInteger("quantity")),
+ Objects.requireNonNull(itemObj.getInteger("amount"))
+ )
+ );
+ } catch (JSONException e) {
+ call.reject(e.getLocalizedMessage());
+ return;
+ }
+ }
+
+ Cart cart = new Cart.Builder(currency, tax, total, cartLineItems).build();
+
+ Terminal
+ .getInstance()
+ .setReaderDisplay(
+ cart,
+ new Callback() {
+ @Override
+ public void onSuccess() {
+ call.resolve();
+ }
+
+ @Override
+ public void onFailure(@NonNull TerminalException e) {
+ call.reject(e.getErrorMessage());
+ }
+ }
+ );
+ }
+
+ public void clearReaderDisplay(final PluginCall call) {
+ Terminal
+ .getInstance()
+ .clearReaderDisplay(
+ new Callback() {
+ @Override
+ public void onSuccess() {
+ call.resolve();
+ }
+
+ @Override
+ public void onFailure(@NonNull TerminalException e) {
+ call.reject(e.getErrorMessage());
+ }
+ }
+ );
+ }
+
+ public void rebootReader(final PluginCall call) {
+ Terminal
+ .getInstance()
+ .rebootReader(
+ new Callback() {
+ @Override
+ public void onSuccess() {
+ paymentIntentInstance = null;
+ call.resolve(emptyObject);
+ }
+
+ @Override
+ public void onFailure(@NonNull TerminalException e) {
+ call.reject(e.getLocalizedMessage());
+ }
+ }
+ );
+ }
+
+ public void cancelReaderReconnection(final PluginCall call) {
+ if (cancelReaderConnectionCancellable == null || cancelReaderConnectionCancellable.isCompleted()) {
+ call.resolve();
+ return;
+ }
+ cancelReaderConnectionCancellable.cancel(
+ new Callback() {
+ @Override
+ public void onSuccess() {
+ call.resolve();
+ }
+
+ @Override
+ public void onFailure(@NonNull TerminalException ex) {
+ call.reject(ex.getLocalizedMessage(), ex);
+ }
+ }
+ );
+ }
+
private final PaymentIntentCallback confirmPaymentMethodCallback = new PaymentIntentCallback() {
@Override
public void onSuccess(PaymentIntent paymentIntent) {
@@ -476,6 +672,7 @@ private ReaderListener readerListener() {
@Override
public void onStartInstallingUpdate(@NotNull ReaderSoftwareUpdate update, @NotNull Cancelable cancelable) {
// Show UI communicating that a required update has started installing
+ installUpdateCancelable = cancelable;
notifyListeners(
TerminalEnumEvent.StartInstallingUpdate.getWebEventName(),
new JSObject().put("update", convertReaderSoftwareUpdate(update))
@@ -558,14 +755,35 @@ public void onDisconnect(@NotNull DisconnectReason reason) {
}
private JSObject convertReaderInterface(Reader reader) {
- return new JSObject().put("serialNumber", reader.getSerialNumber());
+ return new JSObject()
+ .put("label", reader.getLabel())
+ .put("serialNumber", reader.getSerialNumber())
+ .put("id", reader.getId())
+ .put("locationId", reader.getLocation() != null ? reader.getLocation().getId() : null)
+ .put("deviceSoftwareVersion", reader.getDeviceSwVersion$external_publish())
+ .put("simulated", reader.isSimulated())
+ .put("serialNumber", reader.getSerialNumber())
+ .put("ipAddress", reader.getIpAddress())
+ .put("baseUrl", reader.getBaseUrl())
+ .put("bootloaderVersion", reader.getBootloaderVersion())
+ .put("configVersion", reader.getConfigVersion())
+ .put("emvKeyProfileId", reader.getEmvKeyProfileId())
+ .put("firmwareVersion", reader.getFirmwareVersion())
+ .put("hardwareVersion", reader.getHardwareVersion())
+ .put("macKeyProfileId", reader.getMacKeyProfileId())
+ .put("pinKeyProfileId", reader.getPinKeyProfileId())
+ .put("trackKeyProfileId", reader.getTrackKeyProfileId())
+ .put("settingsVersion", reader.getSettingsVersion())
+ .put("pinKeysetId", reader.getPinKeysetId())
+ .put("deviceType", terminalMappers.mapFromDeviceType(reader.getDeviceType()))
+ .put("status", terminalMappers.mapFromNetworkStatus(reader.getNetworkStatus()))
+ .put("locationStatus", terminalMappers.mapFromLocationStatus(reader.getLocationStatus()))
+ .put("batteryLevel", reader.getBatteryLevel() != null ? reader.getBatteryLevel().doubleValue() : null)
+ .put("availableUpdate", terminalMappers.mapFromReaderSoftwareUpdate(reader.getAvailableUpdate()))
+ .put("location", terminalMappers.mapFromLocation(reader.getLocation()));
}
private JSObject convertReaderSoftwareUpdate(ReaderSoftwareUpdate update) {
- return new JSObject()
- .put("version", update.getVersion())
- .put("settingsVersion", update.getSettingsVersion())
- .put("requiredAt", update.getRequiredAt().getTime())
- .put("timeEstimate", update.getTimeEstimate().toString());
+ return terminalMappers.mapFromReaderSoftwareUpdate(update);
}
}
diff --git a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminalPlugin.java b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminalPlugin.java
index e52cd7a97..ba11ce81b 100644
--- a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminalPlugin.java
+++ b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/StripeTerminalPlugin.java
@@ -160,4 +160,34 @@ public void cancelCollectPaymentMethod(final PluginCall call) {
public void confirmPaymentIntent(PluginCall call) {
this.implementation.confirmPaymentIntent(call);
}
+
+ @PluginMethod
+ public void installAvailableUpdate(PluginCall call) {
+ this.implementation.installAvailableUpdate(call);
+ }
+
+ @PluginMethod
+ public void cancelInstallUpdate(PluginCall call) {
+ this.implementation.cancelInstallUpdate(call);
+ }
+
+ @PluginMethod
+ public void setReaderDisplay(PluginCall call) {
+ this.implementation.setReaderDisplay(call);
+ }
+
+ @PluginMethod
+ public void clearReaderDisplay(PluginCall call) {
+ this.implementation.clearReaderDisplay(call);
+ }
+
+ @PluginMethod
+ public void rebootReader(PluginCall call) {
+ this.implementation.rebootReader(call);
+ }
+
+ @PluginMethod
+ public void cancelReaderReconnection(PluginCall call) {
+ this.implementation.cancelReaderReconnection(call);
+ }
}
diff --git a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/TerminalEvent.kt b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/TerminalEvent.kt
index 4bbf84605..ed3839a1f 100644
--- a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/TerminalEvent.kt
+++ b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/TerminalEvent.kt
@@ -22,4 +22,7 @@ enum class TerminalEnumEvent(val webEventName: String) {
RequestDisplayMessage("terminalRequestDisplayMessage"),
RequestReaderInput("terminalRequestReaderInput"),
PaymentStatusChange("terminalPaymentStatusChange"),
+ ReaderReconnectStarted("terminalReaderReconnectStarted"),
+ ReaderReconnectSucceeded("terminalReaderReconnectSucceeded"),
+ ReaderReconnectFailed("terminalReaderReconnectFailed"),
}
diff --git a/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/helper/TerminalMappers.java b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/helper/TerminalMappers.java
new file mode 100644
index 000000000..38623649c
--- /dev/null
+++ b/packages/terminal/android/src/main/java/com/getcapacitor/community/stripe/terminal/helper/TerminalMappers.java
@@ -0,0 +1,96 @@
+package com.getcapacitor.community.stripe.terminal.helper;
+
+import com.getcapacitor.JSObject;
+import com.stripe.stripeterminal.external.models.DeviceType;
+import com.stripe.stripeterminal.external.models.Location;
+import com.stripe.stripeterminal.external.models.LocationStatus;
+import com.stripe.stripeterminal.external.models.Reader;
+import com.stripe.stripeterminal.external.models.ReaderSoftwareUpdate;
+
+public class TerminalMappers {
+
+ public JSObject mapFromLocation(Location location) {
+ if (location == null) {
+ return new JSObject();
+ }
+
+ JSObject address = new JSObject();
+
+ if (location.getAddress() != null) {
+ address
+ .put("country", location.getAddress().getCountry())
+ .put("city", location.getAddress().getCity())
+ .put("postalCode", location.getAddress().getPostalCode())
+ .put("line1", location.getAddress().getLine1())
+ .put("line2", location.getAddress().getLine2())
+ .put("state", location.getAddress().getState());
+ }
+
+ return new JSObject()
+ .put("id", location.getId())
+ .put("displayName", location.getDisplayName())
+ .put("address", address)
+ .put("livemode", location.getLivemode());
+ }
+
+ public JSObject mapFromReaderSoftwareUpdate(ReaderSoftwareUpdate update) {
+ if (update == null) {
+ return new JSObject();
+ }
+
+ return new JSObject()
+ .put("deviceSoftwareVersion", update.getVersion())
+ .put("estimatedUpdateTime", update.getTimeEstimate().toString())
+ .put("requiredAt", update.getRequiredAt().getTime());
+ }
+
+ public String mapFromLocationStatus(LocationStatus status) {
+ if (status == null) {
+ return "UNKNOWN";
+ }
+
+ return switch (status) {
+ case NOT_SET -> "NOT_SET";
+ case SET -> "SET";
+ case UNKNOWN -> "UNKNOWN";
+ default -> "UNKNOWN";
+ };
+ }
+
+ public String mapFromNetworkStatus(Reader.NetworkStatus status) {
+ if (status == null) {
+ return "unknown";
+ }
+
+ return switch (status) {
+ case OFFLINE -> "OFFLINE";
+ case ONLINE -> "ONLINE";
+ default -> "UNKNOWN";
+ };
+ }
+
+ public String mapFromDeviceType(DeviceType type) {
+ return switch (type) {
+ case CHIPPER_1X -> "chipper1X";
+ case CHIPPER_2X -> "chipper2X";
+ case COTS_DEVICE -> "cotsDevice";
+ case ETNA -> "etna";
+ case STRIPE_M2 -> "stripeM2";
+ case STRIPE_S700 -> "stripeS700";
+ case STRIPE_S700_DEVKIT -> "stripeS700Devkit";
+ // React Native has this model. deprecated?
+ // case STRIPE_S710:
+ // return "stripeS710";
+ // case STRIPE_S710_DEVKIT:
+ // return "stripeS710Devkit";
+ case UNKNOWN -> "unknown";
+ case VERIFONE_P400 -> "verifoneP400";
+ case WISECUBE -> "wiseCube";
+ case WISEPAD_3 -> "wisePad3";
+ case WISEPAD_3S -> "wisePad3s";
+ case WISEPOS_E -> "wisePosE";
+ case WISEPOS_E_DEVKIT -> "wisePosEDevkit";
+ default -> throw new IllegalArgumentException("Unknown DeviceType: " + type);
+ };
+ }
+}
diff --git a/packages/terminal/ios/Plugin/StripeTerminal.swift b/packages/terminal/ios/Plugin/StripeTerminal.swift
index 86aa9e69b..136d31ad2 100644
--- a/packages/terminal/ios/Plugin/StripeTerminal.swift
+++ b/packages/terminal/ios/Plugin/StripeTerminal.swift
@@ -2,20 +2,24 @@ import Foundation
import Capacitor
import StripeTerminal
-public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDelegate, BluetoothReaderDelegate, TerminalDelegate {
+public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDelegate, BluetoothReaderDelegate, TerminalDelegate, ReconnectionDelegate {
weak var plugin: StripeTerminalPlugin?
private let apiClient = APIClient()
+
var discoverCancelable: Cancelable?
+ var collectCancelable: Cancelable?
+ var installUpdateCancelable: Cancelable?
+ var cancelReaderConnectionCancellable: Cancelable?
+
var discoverCall: CAPPluginCall?
var locationId: String?
var isTest: Bool?
- var collectCancelable: Cancelable?
var type: DiscoveryMethod?
var isInitialize: Bool = false
var paymentIntent: PaymentIntent?
- var readers: [Reader]?
+ var discoveredReadersList: [Reader]?
@objc public func initialize(_ call: CAPPluginCall) {
self.isTest = call.getBool("isTest", true)
@@ -61,38 +65,21 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
call.reject(error.localizedDescription)
self.discoverCall = nil
} else {
+ // This call is passed to discoverCall. So not resolve.
}
}
}
- func cancelDiscoverReaders(_ call: CAPPluginCall) {
-
- if let cancelable = self.discoverCancelable {
- cancelable.cancel { error in
- if let error = error {
- call.reject(error.localizedDescription)
- } else {
- self.collectCancelable = nil
- call.resolve()
- }
- }
- return
- }
-
- call.resolve()
- }
-
public func terminal(_ terminal: Terminal, didUpdateDiscoveredReaders readers: [Reader]) {
var readersJSObject: JSArray = []
var i = 0
for reader in readers {
readersJSObject.append([
- "index": i,
- "serialNumber": reader.serialNumber
- ])
+ "index": i
+ ].merging(self.convertReaderInterface(reader: reader)) { (_, new) in new })
i += 1
}
- self.readers = readers
+ self.discoveredReadersList = readers
self.plugin?.notifyListeners(TerminalEvents.DiscoveredReaders.rawValue, data: ["readers": readersJSObject])
self.discoverCall?.resolve([
@@ -113,9 +100,7 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
public func getConnectedReader(_ call: CAPPluginCall) {
if let reader = Terminal.shared.connectedReader {
- call.resolve(["reader": [
- "serialNumber": reader.serialNumber
- ]])
+ call.resolve(["reader": self.convertReaderInterface(reader: reader)])
} else {
call.resolve(["reader": nil])
}
@@ -140,11 +125,25 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
}
private func connectLocalMobileReader(_ call: CAPPluginCall) {
- let connectionConfig = try! LocalMobileConnectionConfigurationBuilder.init(locationId: self.locationId!).build()
+ let autoReconnectOnUnexpectedDisconnect = call.getBool("autoReconnectOnUnexpectedDisconnect", false)
+ let merchantDisplayName: String? = call.getString("merchantDisplayName")
+ let onBehalfOf: String? = call.getString("onBehalfOf")
let reader: JSObject = call.getObject("reader")!
- let index: Int = reader["index"] as! Int
+ let serialNumber: String = reader["serialNumber"] as! String
- Terminal.shared.connectLocalMobileReader(self.readers![index], delegate: self, connectionConfig: connectionConfig) { reader, error in
+ let connectionConfig = try! LocalMobileConnectionConfigurationBuilder.init(locationId: self.locationId!)
+ .setMerchantDisplayName(merchantDisplayName ?? nil)
+ .setOnBehalfOf(onBehalfOf ?? nil)
+ .setAutoReconnectOnUnexpectedDisconnect(autoReconnectOnUnexpectedDisconnect)
+ .setAutoReconnectionDelegate(autoReconnectOnUnexpectedDisconnect ? self : nil)
+ .build()
+
+ guard let foundReader = self.discoveredReadersList?.first(where: { $0.serialNumber == serialNumber }) else {
+ call.reject("reader is not match from descovered readers.")
+ return
+ }
+
+ Terminal.shared.connectLocalMobileReader(foundReader, delegate: self, connectionConfig: connectionConfig) { reader, error in
if let reader = reader {
self.plugin?.notifyListeners(TerminalEvents.ConnectedReader.rawValue, data: [:])
call.resolve()
@@ -155,13 +154,19 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
}
private func connectInternetReader(_ call: CAPPluginCall) {
+ let reader: JSObject = call.getObject("reader")!
+ let serialNumber: String = reader["serialNumber"] as! String
+
+ guard let foundReader = self.discoveredReadersList?.first(where: { $0.serialNumber == serialNumber }) else {
+ call.reject("reader is not match from descovered readers.")
+ return
+ }
+
let config = try! InternetConnectionConfigurationBuilder()
.setFailIfInUse(true)
.build()
- let reader: JSObject = call.getObject("reader")!
- let index: Int = reader["index"] as! Int
- Terminal.shared.connectInternetReader(self.readers![index], connectionConfig: config) { reader, error in
+ Terminal.shared.connectInternetReader(foundReader, connectionConfig: config) { reader, error in
if let reader = reader {
self.plugin?.notifyListeners(TerminalEvents.ConnectedReader.rawValue, data: [:])
call.resolve()
@@ -172,11 +177,24 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
}
private func connectBluetoothReader(_ call: CAPPluginCall) {
- let config = try! BluetoothConnectionConfigurationBuilder(locationId: self.locationId!).build()
let reader: JSObject = call.getObject("reader")!
- let index: Int = reader["index"] as! Int
+ let serialNumber: String = reader["serialNumber"] as! String
+
+ guard let foundReader = self.discoveredReadersList?.first(where: { $0.serialNumber == serialNumber }) else {
+ call.reject("reader is not match from descovered readers.")
+ return
+ }
+
+ let autoReconnectOnUnexpectedDisconnect = call.getBool("autoReconnectOnUnexpectedDisconnect", false)
+ let merchantDisplayName: String? = call.getString("merchantDisplayName")
+ let onBehalfOf: String? = call.getString("onBehalfOf")
- Terminal.shared.connectBluetoothReader(self.readers![index], delegate: self, connectionConfig: config) { reader, error in
+ let config = try! BluetoothConnectionConfigurationBuilder(locationId: self.locationId!)
+ .setAutoReconnectOnUnexpectedDisconnect(autoReconnectOnUnexpectedDisconnect)
+ .setAutoReconnectionDelegate(autoReconnectOnUnexpectedDisconnect ? self : nil)
+ .build()
+
+ Terminal.shared.connectBluetoothReader(foundReader, delegate: self, connectionConfig: config) { reader, error in
if let reader = reader {
self.plugin?.notifyListeners(TerminalEvents.ConnectedReader.rawValue, data: [:])
call.resolve()
@@ -190,6 +208,7 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
Terminal.shared.retrievePaymentIntent(clientSecret: call.getString("paymentIntent")!) { retrieveResult, retrieveError in
if let error = retrieveError {
print("retrievePaymentIntent failed: \(error)")
+ call.reject(error.localizedDescription)
} else if let paymentIntent = retrieveResult {
self.collectCancelable = Terminal.shared.collectPaymentMethod(paymentIntent) { collectResult, collectError in
if let error = collectError {
@@ -205,23 +224,6 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
}
}
- public func cancelCollectPaymentMethod(_ call: CAPPluginCall) {
- if let cancelable = self.collectCancelable {
- cancelable.cancel { error in
- if let error = error {
- call.reject(error.localizedDescription)
- } else {
- self.plugin?.notifyListeners(TerminalEvents.Canceled.rawValue, data: [:])
- self.collectCancelable = nil
- self.paymentIntent = nil
- call.resolve()
- }
- }
- return
- }
- call.resolve()
- }
-
public func confirmPaymentIntent(_ call: CAPPluginCall) {
if let paymentIntent = self.paymentIntent {
Terminal.shared.confirmPaymentIntent(paymentIntent) { confirmResult, confirmError in
@@ -250,6 +252,151 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
call.resolve([:])
}
+ public func installAvailableUpdate(_ call: CAPPluginCall) {
+ Terminal.shared.installAvailableUpdate()
+ call.resolve([:])
+ }
+
+ public func setReaderDisplay(_ call: CAPPluginCall) {
+ guard let currency = call.getString("currency") else {
+ call.reject("You must provide a currency value")
+ return
+ }
+ guard let tax = call.getInt("tax") as? NSNumber else {
+ call.reject("You must provide a tax value")
+ return
+ }
+ guard let total = call.getInt("total") as? NSNumber else {
+ call.reject("You must provide a total value")
+ return
+ }
+
+ let cartBuilder = CartBuilder(currency: currency)
+ .setTax(Int(truncating: tax))
+ .setTotal(Int(truncating: total))
+
+ let cartLineItems = TerminalMappers.mapToCartLineItems(call.getArray("lineItems") ?? JSArray())
+
+ cartBuilder.setLineItems(cartLineItems)
+
+ let cart: Cart
+ do {
+ cart = try cartBuilder.build()
+ } catch {
+ call.reject(error.localizedDescription)
+ return
+ }
+
+ Terminal.shared.setReaderDisplay(cart) { error in
+ if let error = error as NSError? {
+ call.reject(error.localizedDescription)
+ } else {
+ call.resolve([:])
+ }
+ }
+
+ }
+
+ public func clearReaderDisplay(_ call: CAPPluginCall) {
+ Terminal.shared.clearReaderDisplay { error in
+ if let error = error as NSError? {
+ call.reject(error.localizedDescription)
+ } else {
+ call.resolve([:])
+ }
+ }
+ }
+
+ public func rebootReader(_ call: CAPPluginCall) {
+ Terminal.shared.rebootReader { error in
+ if let error = error as NSError? {
+ call.reject(error.localizedDescription)
+ } else {
+ self.paymentIntent = nil
+ call.resolve([:])
+ }
+ }
+ }
+
+ /**
+ * Cancelable
+ */
+ public func cancelInstallUpdate(_ call: CAPPluginCall) {
+ if let cancelable = self.installUpdateCancelable {
+ if cancelable.completed {
+ call.resolve()
+ return
+ }
+ cancelable.cancel { error in
+ if let error = error as NSError? {
+ call.reject(error.localizedDescription)
+ } else {
+ call.resolve([:])
+ }
+ }
+ return
+ }
+ call.resolve([:])
+ }
+
+ public func cancelCollectPaymentMethod(_ call: CAPPluginCall) {
+ if let cancelable = self.collectCancelable {
+ if cancelable.completed {
+ call.resolve()
+ return
+ }
+ cancelable.cancel { error in
+ if let error = error {
+ call.reject(error.localizedDescription)
+ } else {
+ self.plugin?.notifyListeners(TerminalEvents.Canceled.rawValue, data: [:])
+ self.paymentIntent = nil
+ call.resolve()
+ }
+ }
+ return
+ }
+ call.resolve()
+ }
+
+ func cancelDiscoverReaders(_ call: CAPPluginCall) {
+ if let cancelable = self.discoverCancelable {
+ if cancelable.completed {
+ call.resolve()
+ return
+ }
+ cancelable.cancel { error in
+ if let error = error {
+ call.reject(error.localizedDescription)
+ } else {
+ call.resolve()
+ }
+ }
+ return
+ }
+
+ call.resolve()
+ }
+
+ public func cancelReaderReconnection(_ call: CAPPluginCall) {
+ if let cancelable = self.cancelReaderConnectionCancellable {
+ if cancelable.completed {
+ call.resolve()
+ return
+ }
+ cancelable.cancel { error in
+ if let error = error as NSError? {
+ call.reject(error.localizedDescription)
+ } else {
+ call.resolve([:])
+ }
+ }
+ return
+ }
+
+ call.resolve()
+ }
+
/*
* Terminal
*/
@@ -270,7 +417,10 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
*/
public func localMobileReader(_ reader: Reader, didStartInstallingUpdate update: ReaderSoftwareUpdate, cancelable: Cancelable?) {
- self.plugin?.notifyListeners(TerminalEvents.StartInstallingUpdate.rawValue, data: self.convertReaderSoftwareUpdate(update: update))
+ self.installUpdateCancelable = cancelable
+ self.plugin?.notifyListeners(TerminalEvents.StartInstallingUpdate.rawValue, data: [
+ "update": self.convertReaderSoftwareUpdate(update: update)
+ ])
}
public func localMobileReader(_ reader: Reader, didReportReaderSoftwareUpdateProgress progress: Float) {
@@ -307,11 +457,16 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
*/
public func reader(_: Reader, didReportAvailableUpdate update: ReaderSoftwareUpdate) {
- self.plugin?.notifyListeners(TerminalEvents.ReportAvailableUpdate.rawValue, data: self.convertReaderSoftwareUpdate(update: update))
+ self.plugin?.notifyListeners(TerminalEvents.ReportAvailableUpdate.rawValue, data: [
+ "update": self.convertReaderSoftwareUpdate(update: update)
+ ])
}
public func reader(_: Reader, didStartInstallingUpdate update: ReaderSoftwareUpdate, cancelable: Cancelable?) {
- self.plugin?.notifyListeners(TerminalEvents.StartInstallingUpdate.rawValue, data: self.convertReaderSoftwareUpdate(update: update))
+ self.installUpdateCancelable = cancelable
+ self.plugin?.notifyListeners(TerminalEvents.StartInstallingUpdate.rawValue, data: [
+ "update": self.convertReaderSoftwareUpdate(update: update)
+ ])
}
public func reader(_: Reader, didReportReaderSoftwareUpdateProgress progress: Float) {
@@ -363,19 +518,49 @@ public class StripeTerminal: NSObject, DiscoveryDelegate, LocalMobileReaderDeleg
])
}
- private func convertReaderInterface(reader: Reader) -> [String: String] {
- return ["serialNumber": reader.serialNumber]
+ /*
+ * Reconnection
+ */
+ public func reader(_ reader: Reader, didStartReconnect cancelable: Cancelable, disconnectReason: DisconnectReason) {
+ self.cancelReaderConnectionCancellable = cancelable
+ self.plugin?.notifyListeners(TerminalEvents.ReaderReconnectStarted.rawValue, data: ["reader": self.convertReaderInterface(reader: reader), "reason": disconnectReason.rawValue])
+ }
+
+ public func readerDidSucceedReconnect(_ reader: Reader) {
+ self.plugin?.notifyListeners(TerminalEvents.ReaderReconnectSucceeded.rawValue, data: ["reader": self.convertReaderInterface(reader: reader)])
+ }
+
+ public func readerDidFailReconnect(_ reader: Reader) {
+ self.plugin?.notifyListeners(TerminalEvents.ReaderReconnectFailed.rawValue, data: ["reader": self.convertReaderInterface(reader: reader)])
}
- private func convertReaderSoftwareUpdate(update: ReaderSoftwareUpdate) -> [String: String] {
+ /*
+ * Private
+ */
+ private func convertReaderInterface(reader: Reader) -> JSObject {
return [
- "version": update.deviceSoftwareVersion,
- "settingsVersion": update.deviceSoftwareVersion,
- "requiredAt": update.requiredAt.description,
- "timeEstimate": TerminalMappers.mapFromUpdateTimeEstimate(update.estimatedUpdateTime)
+ "label": reader.label ?? NSNull(),
+ "batteryLevel": (reader.batteryLevel ?? 0).intValue,
+ "batteryStatus": TerminalMappers.mapFromBatteryStatus(reader.batteryStatus),
+ "simulated": reader.simulated,
+ "serialNumber": reader.serialNumber,
+ "isCharging": (reader.isCharging ?? 0).intValue,
+ "id": reader.stripeId ?? NSNull(),
+ "availableUpdate": TerminalMappers.mapFromReaderSoftwareUpdate(reader.availableUpdate),
+ "locationId": reader.locationId ?? NSNull(),
+ "ipAddress": reader.ipAddress ?? NSNull(),
+ "status": TerminalMappers.mapFromReaderNetworkStatus(reader.status),
+ "location": TerminalMappers.mapFromLocation(reader.location),
+ "locationStatus": TerminalMappers.mapFromLocationStatus(reader.locationStatus),
+ "deviceType": TerminalMappers.mapFromDeviceType(reader.deviceType),
+ "deviceSoftwareVersion": reader.deviceSoftwareVersion ?? NSNull()
]
}
+ private func convertReaderSoftwareUpdate(update: ReaderSoftwareUpdate) -> JSObject {
+ return TerminalMappers.mapFromReaderSoftwareUpdate(update)
+ }
+
}
class APIClient: ConnectionTokenProvider {
diff --git a/packages/terminal/ios/Plugin/StripeTerminalPlugin.m b/packages/terminal/ios/Plugin/StripeTerminalPlugin.m
index cd0a88768..e456baed0 100644
--- a/packages/terminal/ios/Plugin/StripeTerminalPlugin.m
+++ b/packages/terminal/ios/Plugin/StripeTerminalPlugin.m
@@ -15,4 +15,10 @@
CAP_PLUGIN_METHOD(cancelCollectPaymentMethod, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(confirmPaymentIntent, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(setSimulatorConfiguration, CAPPluginReturnPromise);
+ CAP_PLUGIN_METHOD(installAvailableUpdate, CAPPluginReturnPromise);
+ CAP_PLUGIN_METHOD(cancelInstallUpdate, CAPPluginReturnPromise);
+ CAP_PLUGIN_METHOD(setReaderDisplay, CAPPluginReturnPromise);
+ CAP_PLUGIN_METHOD(clearReaderDisplay, CAPPluginReturnPromise);
+ CAP_PLUGIN_METHOD(rebootReader, CAPPluginReturnPromise);
+ CAP_PLUGIN_METHOD(cancelReaderReconnection, CAPPluginReturnPromise);
)
diff --git a/packages/terminal/ios/Plugin/StripeTerminalPlugin.swift b/packages/terminal/ios/Plugin/StripeTerminalPlugin.swift
index 32822010b..b5637a38d 100644
--- a/packages/terminal/ios/Plugin/StripeTerminalPlugin.swift
+++ b/packages/terminal/ios/Plugin/StripeTerminalPlugin.swift
@@ -3,10 +3,6 @@ import StripeTerminal
import Capacitor
import PassKit
-/**
- * Please read the Capacitor iOS Plugin Development Guide
- * here: https://capacitorjs.com/docs/plugins/ios
- */
@objc(StripeTerminalPlugin)
public class StripeTerminalPlugin: CAPPlugin {
private let implementation = StripeTerminal()
@@ -64,4 +60,29 @@ public class StripeTerminalPlugin: CAPPlugin {
@objc func confirmPaymentIntent(_ call: CAPPluginCall) {
self.implementation.confirmPaymentIntent(call)
}
+
+ @objc func installAvailableUpdate(_ call: CAPPluginCall) {
+ self.implementation.installAvailableUpdate(call)
+ }
+
+ @objc func cancelInstallUpdate(_ call: CAPPluginCall) {
+ self.implementation.cancelInstallUpdate(call)
+ }
+
+ @objc func setReaderDisplay(_ call: CAPPluginCall) {
+ self.implementation.setReaderDisplay(call)
+ }
+
+ @objc func clearReaderDisplay(_ call: CAPPluginCall) {
+ self.implementation.clearReaderDisplay(call)
+ }
+
+ @objc func rebootReader(_ call: CAPPluginCall) {
+ self.implementation.rebootReader(call)
+ }
+
+ @objc func cancelReaderReconnection(_ call: CAPPluginCall) {
+ self.implementation.cancelReaderReconnection(call)
+ }
+
}
diff --git a/packages/terminal/ios/Plugin/TerminalEvents.swift b/packages/terminal/ios/Plugin/TerminalEvents.swift
index 2450762c6..a97f76359 100644
--- a/packages/terminal/ios/Plugin/TerminalEvents.swift
+++ b/packages/terminal/ios/Plugin/TerminalEvents.swift
@@ -19,4 +19,7 @@ public enum TerminalEvents: String {
case RequestDisplayMessage = "terminalRequestDisplayMessage"
case RequestReaderInput = "terminalRequestReaderInput"
case PaymentStatusChange = "terminalPaymentStatusChange"
+ case ReaderReconnectStarted = "terminalReaderReconnectStarted"
+ case ReaderReconnectSucceeded = "terminalReaderReconnectSucceeded"
+ case ReaderReconnectFailed = "terminalReaderReconnectFailed"
}
diff --git a/packages/terminal/ios/Plugin/TerminalMappers.swift b/packages/terminal/ios/Plugin/TerminalMappers.swift
index a63ed32e0..af942fd32 100644
--- a/packages/terminal/ios/Plugin/TerminalMappers.swift
+++ b/packages/terminal/ios/Plugin/TerminalMappers.swift
@@ -2,6 +2,133 @@ import StripeTerminal
import Capacitor
class TerminalMappers {
+ class func mapFromDeviceType(_ type: DeviceType) -> String {
+ switch type {
+ case DeviceType.appleBuiltIn: return "appleBuiltIn"
+ case DeviceType.chipper1X: return "chipper1X"
+ case DeviceType.chipper2X: return "chipper2X"
+ case DeviceType.etna: return "etna"
+ case DeviceType.stripeM2: return "stripeM2"
+ case DeviceType.stripeS700: return "stripeS700"
+ case DeviceType.stripeS700DevKit: return "stripeS700Devkit"
+ case DeviceType.verifoneP400: return "verifoneP400"
+ case DeviceType.wiseCube: return "wiseCube"
+ case DeviceType.wisePad3: return "wisePad3"
+ case DeviceType.wisePosE: return "wisePosE"
+ case DeviceType.wisePosEDevKit: return "wisePosEDevkit"
+ default: return "unknown"
+ }
+ }
+
+ class func mapFromAddress(_ address: Address?) -> JSObject {
+ if let address = address {
+ let result: JSObject = [
+ "city": address.city ?? NSNull(),
+ "country": address.country ?? NSNull(),
+ "postalCode": address.postalCode ?? NSNull(),
+ "line1": address.line1 ?? NSNull(),
+ "line2": address.line2 ?? NSNull(),
+ "state": address.state ?? NSNull()
+ ]
+ return result
+ } else {
+ return JSObject()
+ }
+ }
+
+ class func mapFromLocation(_ location: Location?) -> JSObject {
+ guard let unwrappedLocation = location else {
+ return [:]
+ }
+ let result: JSObject = [
+ "displayName": unwrappedLocation.displayName ?? NSNull(),
+ "id": unwrappedLocation.stripeId,
+ "livemode": unwrappedLocation.livemode,
+ "address": mapFromAddress(unwrappedLocation.address)
+ ]
+ return result
+ }
+
+ class func mapFromLocationStatus(_ status: LocationStatus) -> String {
+ switch status {
+ case LocationStatus.notSet: return "NOT_SET"
+ case LocationStatus.set: return "SET"
+ case LocationStatus.unknown: return "UNKNOWN"
+ default: return "UNKNOWN"
+ }
+ }
+
+ class func mapFromLocationsList(_ locations: [Location]) -> JSArray {
+ var list: JSArray = []
+
+ for location in locations {
+ let result = mapFromLocation(location)
+ if result.count != 0 {
+ list.append(result)
+ }
+ }
+
+ return list
+ }
+
+ class func mapFromReaderNetworkStatus(_ status: ReaderNetworkStatus) -> String {
+ switch status {
+ case ReaderNetworkStatus.offline: return "OFFLINE"
+ case ReaderNetworkStatus.online: return "ONLINE"
+ default: return "UNKNOWN"
+ }
+ }
+
+ class func convertDateToUnixTimestamp(date: Date?) -> String {
+ if let date = date {
+ let value = date.timeIntervalSince1970 * 1000.0
+ return String(format: "%.0f", value)
+ }
+ return ""
+ }
+
+ class func mapFromReaderSoftwareUpdate(_ update: ReaderSoftwareUpdate?) -> JSObject {
+ guard let unwrappedUpdate = update else {
+ return JSObject()
+ }
+ let result: JSObject = [
+ "deviceSoftwareVersion": unwrappedUpdate.deviceSoftwareVersion,
+ "estimatedUpdateTime": mapFromUpdateTimeEstimate(unwrappedUpdate.estimatedUpdateTime),
+ "requiredAt": convertDateToUnixTimestamp(date: unwrappedUpdate.requiredAt)
+ ]
+ return result
+ }
+
+ class func mapToCartLineItem(_ cartLineItem: NSDictionary) -> CartLineItem? {
+ guard let displayName = cartLineItem["displayName"] as? String else { return nil }
+ guard let quantity = cartLineItem["quantity"] as? NSNumber else { return nil }
+ guard let amount = cartLineItem["amount"] as? NSNumber else { return nil }
+
+ do {
+ let lineItem = try CartLineItemBuilder(displayName: displayName)
+ .setQuantity(Int(truncating: quantity))
+ .setAmount(Int(truncating: amount))
+ .build()
+ return lineItem
+ } catch {
+ print("Error wihle building CartLineItem, error:\(error)")
+ return nil
+ }
+ }
+
+ class func mapToCartLineItems(_ cartLineItems: JSArray) -> [CartLineItem] {
+ var items = [CartLineItem]()
+
+ cartLineItems.forEach {
+ if let item = $0 as? NSDictionary {
+ if let lineItem = TerminalMappers.mapToCartLineItem(item) {
+ items.append(lineItem)
+ }
+ }
+ }
+ return items
+ }
+
class func mapToSimulateReaderUpdate(_ update: String) -> SimulateReaderUpdate {
switch update {
case "UPDATE_AVAILABLE": return SimulateReaderUpdate.available
diff --git a/packages/terminal/package-lock.json b/packages/terminal/package-lock.json
index 79236838c..0a3479dac 100644
--- a/packages/terminal/package-lock.json
+++ b/packages/terminal/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@capacitor-community/stripe-terminal",
- "version": "6.0.2",
+ "version": "6.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@capacitor-community/stripe-terminal",
- "version": "6.0.2",
+ "version": "6.1.0",
"license": "MIT",
"devDependencies": {
"@capacitor/android": "^6.0.0",
diff --git a/packages/terminal/package.json b/packages/terminal/package.json
index 22494ab95..cc530418e 100644
--- a/packages/terminal/package.json
+++ b/packages/terminal/package.json
@@ -1,6 +1,6 @@
{
"name": "@capacitor-community/stripe-terminal",
- "version": "6.0.2",
+ "version": "6.1.0",
"engines": {
"node": ">=18.0.0"
},
diff --git a/packages/terminal/src/definitions.ts b/packages/terminal/src/definitions.ts
index 695679419..a1db543ee 100644
--- a/packages/terminal/src/definitions.ts
+++ b/packages/terminal/src/definitions.ts
@@ -13,18 +13,87 @@ import type {
PaymentStatus,
DisconnectReason,
ConnectionStatus,
+ NetworkStatus,
+ LocationStatus,
+ DeviceType,
} from './stripe.enum';
export type ReaderInterface = {
- index: number;
+ /**
+ * The unique serial number is primary identifier inner plugin.
+ */
serialNumber: string;
+
+ label: string;
+ batteryLevel: number;
+ batteryStatus: BatteryStatus;
+ simulated: boolean;
+ id: number;
+ availableUpdate: ReaderSoftwareUpdateInterface;
+ locationId: string;
+ ipAddress: string;
+ status: NetworkStatus;
+ location: LocationInterface;
+ locationStatus: LocationStatus;
+ deviceType: DeviceType;
+ deviceSoftwareVersion: string;
+
+ /**
+ * iOS Only properties. These properties are not available on Android.
+ */
+ isCharging: number;
+
+ /**
+ * Android Only properties. These properties are not available on iOS.
+ */
+ baseUrl: string;
+ bootloaderVersion: string;
+ configVersion: string;
+ emvKeyProfileId: string;
+ firmwareVersion: string;
+ hardwareVersion: string;
+ macKeyProfileId: string;
+ pinKeyProfileId: string;
+ trackKeyProfileId: string;
+ settingsVersion: string;
+ pinKeysetId: string;
+
+ /**
+ * @deprecated This property has been deprecated and should use the `serialNumber` property.
+ */
+ index?: number;
+};
+export type LocationInterface = {
+ id: string;
+ displayName: string;
+ address: {
+ city: string;
+ country: string;
+ postalCode: string;
+ line1: string;
+ line2: string;
+ state: string;
+ };
+ ipAddress: string;
};
export type ReaderSoftwareUpdateInterface = {
- version: string;
- settingsVersion: string;
+ deviceSoftwareVersion: string;
+ estimatedUpdateTime: UpdateTimeEstimate;
requiredAt: number;
- timeEstimate: UpdateTimeEstimate;
+};
+
+export type CartLineItem = {
+ displayName: string;
+ quantity: number;
+ amount: number;
+};
+
+export type Cart = {
+ currency: string;
+ tax: number;
+ total: number;
+ lineItems: CartLineItem[];
};
export * from './events.enum';
@@ -49,13 +118,37 @@ export interface StripeTerminalPlugin {
simulatedCard?: SimulatedCardType;
simulatedTipAmount?: number;
}): Promise;
- connectReader(options: { reader: ReaderInterface }): Promise;
+
+ /**
+ * @param options.autoReconnectOnUnexpectedDisconnect If true, the SDK will automatically attempt to reconnect to the reader. default is false.
+ */
+ connectReader(options: {
+ reader: ReaderInterface;
+ autoReconnectOnUnexpectedDisconnect?: boolean;
+
+ /**
+ * iOS and LocalMobileReader only. Android needs to be set to PaymentIntent only.
+ */
+ merchantDisplayName?: string;
+
+ /**
+ * iOS and LocalMobileReader only. Android needs to be set to PaymentIntent only.
+ * The Stripe account ID for which these funds are intended.
+ */
+ onBehalfOf?: string;
+ }): Promise;
getConnectedReader(): Promise<{ reader: ReaderInterface | null }>;
disconnectReader(): Promise;
cancelDiscoverReaders(): Promise;
collectPaymentMethod(options: { paymentIntent: string }): Promise;
cancelCollectPaymentMethod(): Promise;
confirmPaymentIntent(): Promise;
+ installAvailableUpdate(): Promise;
+ cancelInstallUpdate(): Promise;
+ setReaderDisplay(options: Cart): Promise;
+ clearReaderDisplay(): Promise;
+ rebootReader(): Promise;
+ cancelReaderReconnection(): Promise;
addListener(
eventName: TerminalEventsEnum.Loaded,
@@ -305,6 +398,27 @@ export interface StripeTerminalPlugin {
listenerFunc: ({ status }: { status: PaymentStatus }) => void,
): Promise;
+ addListener(
+ eventName: TerminalEventsEnum.ReaderReconnectStarted,
+ listenerFunc: ({
+ reader,
+ reason,
+ }: {
+ reader: ReaderInterface;
+ reason: string;
+ }) => void,
+ ): Promise;
+
+ addListener(
+ eventName: TerminalEventsEnum.ReaderReconnectSucceeded,
+ listenerFunc: ({ reader }: { reader: ReaderInterface }) => void,
+ ): Promise;
+
+ addListener(
+ eventName: TerminalEventsEnum.ReaderReconnectFailed,
+ listenerFunc: ({ reader }: { reader: ReaderInterface }) => void,
+ ): Promise;
+
/**
* @deprecated
* This method has been deprecated and replaced by the `collectPaymentMethod`.
diff --git a/packages/terminal/src/events.enum.ts b/packages/terminal/src/events.enum.ts
index 54d32a191..51725eb26 100644
--- a/packages/terminal/src/events.enum.ts
+++ b/packages/terminal/src/events.enum.ts
@@ -20,6 +20,9 @@ export enum TerminalEventsEnum {
RequestDisplayMessage = 'terminalRequestDisplayMessage',
RequestReaderInput = 'terminalRequestReaderInput',
PaymentStatusChange = 'terminalPaymentStatusChange',
+ ReaderReconnectStarted = 'terminalReaderReconnectStarted',
+ ReaderReconnectSucceeded = 'terminalReaderReconnectSucceeded',
+ ReaderReconnectFailed = 'terminalReaderReconnectFailed',
}
export type TerminalResultInterface =
diff --git a/packages/terminal/src/stripe.enum.ts b/packages/terminal/src/stripe.enum.ts
index b4e0f246b..5adba9db8 100644
--- a/packages/terminal/src/stripe.enum.ts
+++ b/packages/terminal/src/stripe.enum.ts
@@ -6,6 +6,49 @@ export enum TerminalConnectTypes {
TapToPay = 'tap-to-pay',
}
+/**
+ * Note: Don't need to use this enum. It's just for reference.
+ */
+export enum DeviceType {
+ cotsDevice = 'cotsDevice',
+ wisePad3s = 'wisePad3s',
+ appleBuiltIn = 'appleBuiltIn',
+ chipper1X = 'chipper1X',
+ chipper2X = 'chipper2X',
+ etna = 'etna',
+ stripeM2 = 'stripeM2',
+ stripeS700 = 'stripeS700',
+ stripeS700DevKit = 'stripeS700Devkit',
+ verifoneP400 = 'verifoneP400',
+ wiseCube = 'wiseCube',
+ wisePad3 = 'wisePad3',
+ wisePosE = 'wisePosE',
+ wisePosEDevKit = 'wisePosEDevkit',
+ unknown = 'unknown',
+}
+
+/**
+ * This group is useful for pick image.
+ * Reference: https://github.com/stripe/stripe-terminal-ios/blob/fc571ab441b14639243a11d19d8f62bbe93feea5/Example/Example/ReaderHeaderView.swift#L95-L113
+ */
+export enum DeviceGroup {
+ stripeM2 = 'stripe_m2',
+ chipper1X = 'chipper',
+ chipper2X = 'chipper',
+ wiseCube = 'chipper',
+ verifoneP400 = 'verifone',
+ wisePad3s = 'wisepad',
+ wisePad3 = 'wisepad',
+ wisePosEDevKit = 'wisepose',
+ etna = 'wisepose',
+ wisePosE = 'wisepose',
+ stripeS700DevKit = 's700',
+ stripeS700 = 's700',
+ appleBuiltIn = 'apple', // unknown change to apple
+ cotsDevice = 'unknown',
+ unknown = 'unknown',
+}
+
export enum UpdateTimeEstimate {
LessThanOneMinute = 'LESS_THAN_ONE_MINUTE',
OneToTwoMinutes = 'ONE_TO_TWO_MINUTES',
@@ -60,6 +103,18 @@ export enum BatteryStatus {
Nominal = 'NOMINAL',
}
+export enum LocationStatus {
+ NotSet = 'NOT_SET',
+ Set = 'SET',
+ Unknown = 'UNKNOWN',
+}
+
+export enum NetworkStatus {
+ Unknown = 'UNKNOWN',
+ Online = 'ONLINE',
+ Offline = 'OFFLINE',
+}
+
export enum ReaderEvent {
Unknown = 'UNKNOWN',
CardInserted = 'CARD_INSERTED',
diff --git a/packages/terminal/src/web.ts b/packages/terminal/src/web.ts
index d4ba0fff8..31602c861 100644
--- a/packages/terminal/src/web.ts
+++ b/packages/terminal/src/web.ts
@@ -6,6 +6,7 @@ import type {
ReaderInterface,
SimulateReaderUpdate,
SimulatedCardType,
+ Cart,
} from './definitions';
import { TerminalEventsEnum } from './events.enum';
@@ -86,6 +87,26 @@ export class StripeTerminalWeb
this.notifyListeners(TerminalEventsEnum.ConfirmedPaymentIntent, null);
}
+ async installAvailableUpdate(): Promise {
+ console.log('installAvailableUpdate');
+ }
+ async cancelInstallUpdate(): Promise {
+ console.log('cancelInstallUpdate');
+ }
+ async setReaderDisplay(options: Cart): Promise {
+ console.log('setReaderDisplay', options);
+ }
+ async clearReaderDisplay(): Promise {
+ console.log('clearReaderDisplay');
+ }
+ async rebootReader(): Promise {
+ console.log('rebootReader');
+ }
+
+ async cancelReaderReconnection(): Promise {
+ console.log('cancelReaderReconnection');
+ }
+
collect = 'deprecated';
cancelCollect = 'deprecated';
}