Skip to content

Commit

Permalink
feat(android): support usb and bluetooth reader
Browse files Browse the repository at this point in the history
  • Loading branch information
rdlabo committed Oct 7, 2023
1 parent a54f932 commit 2a28694
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 65 deletions.
12 changes: 9 additions & 3 deletions demo/angular/src/app/terminal/terminal.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@
<ion-content>
<ion-list>
<ion-list-header><ion-label>PROCESS</ion-label></ion-list-header>
<ion-item (click)="create('happyPath', 0)" button="true" detail="true"><ion-label>Expect Happy Path - Tap To Pay</ion-label></ion-item>
<ion-item (click)="create('happyPath', 1)" button="true" detail="true"><ion-label>Expect Happy Path - Internet</ion-label></ion-item>
<ion-item (click)="create('cancelPath', 1)" button="true" detail="true"><ion-label>Expect Cancel Path - Internet</ion-label></ion-item>
<ion-item-divider><ion-label>Happy Path</ion-label></ion-item-divider>
<ion-item (click)="create('happyPath', terminalConnectTypes.TapToPay)" button="true" detail="true"><ion-label>Expect Happy Path - Tap To Pay</ion-label></ion-item>
<ion-item (click)="create('happyPath', terminalConnectTypes.Internet)" button="true" detail="true"><ion-label>Expect Happy Path - Internet</ion-label></ion-item>
<ion-item (click)="create('happyPath', terminalConnectTypes.Bluetooth)" button="true" detail="true"><ion-label>Expect Happy Path - Bluetooth</ion-label></ion-item>
<ion-item (click)="create('happyPath', terminalConnectTypes.Usb)" button="true" detail="true"><ion-label>Expect Happy Path - Usb</ion-label></ion-item>
<ion-item-divider><ion-label>Cancel Path</ion-label></ion-item-divider>
<ion-item (click)="create('cancelPath', terminalConnectTypes.Internet)" button="true" detail="true"><ion-label>Expect Cancel Path - Internet</ion-label></ion-item>
<ion-item (click)="create('cancelPath', terminalConnectTypes.Bluetooth)" button="true" detail="true"><ion-label>Expect Cancel Path - Bluetooth</ion-label></ion-item>
<ion-item (click)="create('cancelPath', terminalConnectTypes.Usb)" button="true" detail="true"><ion-label>Expect Cancel Path - Usb</ion-label></ion-item>
<ion-item (click)="checkDiscoverMethod()" button="true" detail="true"><ion-label>checkDiscoverMethod</ion-label></ion-item>
</ion-list>

Expand Down
25 changes: 13 additions & 12 deletions demo/angular/src/app/terminal/terminal.page.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { HttpClient } from '@angular/common/http';
import { HelperService } from '../shared/helper.service';
import { StripeTerminal, TerminalConnectTypes, TerminalEventsEnum } from '@capacitor-community/stripe-terminal';
import { environment } from '../../environments/environment';
import { firstValueFrom } from 'rxjs';
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {IonicModule} from '@ionic/angular';
import {HttpClient} from '@angular/common/http';
import {HelperService} from '../shared/helper.service';
import {StripeTerminal, TerminalConnectTypes, TerminalEventsEnum} from '@capacitor-community/stripe-terminal';
import {environment} from '../../environments/environment';
import {firstValueFrom} from 'rxjs';
import {ITestItems} from '../shared/interfaces';
import {PluginListenerHandle} from '@capacitor/core';

Expand Down Expand Up @@ -141,13 +141,14 @@ const checkDiscoverMethodItems: ITestItems [] = [
})
export class TerminalPage {
public eventItems: ITestItems [] = [];
public terminalConnectTypes = TerminalConnectTypes;
private readonly listenerHandlers: PluginListenerHandle[] = [];
constructor(
private http: HttpClient,
private helper: HelperService,
) { }

async create(type: 'happyPath' | 'cancelPath', readerType: number) {
async create(type: 'happyPath' | 'cancelPath', readerType: TerminalConnectTypes) {
const eventKeys = Object.keys(TerminalEventsEnum);
eventKeys.forEach(key => {
const handler = StripeTerminal.addListener(TerminalEventsEnum[key], () => {
Expand All @@ -162,12 +163,12 @@ export class TerminalPage {
this.eventItems = JSON.parse(JSON.stringify(cancelPathItems));
}

await StripeTerminal.initialize({ tokenProviderEndpoint: environment.api + 'connection/token', isTest: readerType === 0 })
await StripeTerminal.initialize({ tokenProviderEndpoint: environment.api + 'connection/token', isTest: readerType === TerminalConnectTypes.TapToPay })
.then(() => this.helper.updateItem(this.eventItems,'initialize', true))
.catch(() => this.helper.updateItem(this.eventItems,'initialize', false));

const result = await StripeTerminal.discoverReaders({
type: readerType === 0 ? TerminalConnectTypes.TapToPay : TerminalConnectTypes.Internet,
type: readerType,
locationId: "tml_FOUOdQVIxvVdvN",
})
.catch((e) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.util.Supplier;

Expand All @@ -24,19 +25,26 @@
import com.stripe.stripeterminal.external.callable.DiscoveryListener;
import com.stripe.stripeterminal.external.callable.PaymentIntentCallback;
import com.stripe.stripeterminal.external.callable.ReaderCallback;
import com.stripe.stripeterminal.external.callable.ReaderListener;
import com.stripe.stripeterminal.external.callable.TerminalListener;
import com.stripe.stripeterminal.external.models.CardPresentDetails;
import com.stripe.stripeterminal.external.models.CollectConfiguration;
import com.stripe.stripeterminal.external.models.ConnectionConfiguration;
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.DiscoveryConfiguration;
import com.stripe.stripeterminal.external.models.PaymentIntent;
import com.stripe.stripeterminal.external.models.PaymentMethod;
import com.stripe.stripeterminal.external.models.PaymentStatus;
import com.stripe.stripeterminal.external.models.Reader;
import com.stripe.stripeterminal.external.models.ReaderSoftwareUpdate;
import com.stripe.stripeterminal.external.models.TerminalException;
import com.stripe.stripeterminal.log.LogLevel;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
Expand All @@ -48,7 +56,7 @@ public class StripeTerminal extends Executor {
private List<Reader> readers;
private String locationId;
private PluginCall collectCall;
private final JSObject emptyObject = new JSObject();
private final JSObject emptyObject = this.emptyObject;
private Boolean isTest;
private TerminalConnectTypes terminalConnectType;

Expand Down Expand Up @@ -108,7 +116,7 @@ public void onPaymentStatusChange(@NonNull PaymentStatus status) {
Terminal.getInstance();
}

public void onDiscoverReaders(final PluginCall call) {
public void onDiscoverReaders(final PluginCall call) {
if (ActivityCompat.checkSelfPermission(this.contextSupplier.get(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.d(this.logTag, "android.permission.ACCESS_FINE_LOCATION permission is not granted.");
call.reject("android.permission.ACCESS_FINE_LOCATION permission is not granted.");
Expand All @@ -123,6 +131,12 @@ public void onDiscoverReaders(final PluginCall call) {
} else if (Objects.equals(call.getString("type"), TerminalConnectTypes.Internet.getWebEventName())) {
config = new DiscoveryConfiguration.InternetDiscoveryConfiguration(this.locationId, this.isTest);
this.terminalConnectType = TerminalConnectTypes.Internet;
} else if (Objects.equals(call.getString("type"), TerminalConnectTypes.Usb.getWebEventName())) {
config = new DiscoveryConfiguration.UsbDiscoveryConfiguration(0, this.isTest);
this.terminalConnectType = TerminalConnectTypes.Usb;
} else if (Objects.equals(call.getString("type"), TerminalConnectTypes.Bluetooth.getWebEventName()) || Objects.equals(call.getString("type"), TerminalConnectTypes.Simulated.getWebEventName())) {
config = new DiscoveryConfiguration.BluetoothDiscoveryConfiguration(0, this.isTest);
this.terminalConnectType = TerminalConnectTypes.Bluetooth;
} else {
call.unimplemented(call.getString("type") + " is not support now");
return;
Expand All @@ -136,10 +150,10 @@ public void onDiscoverReaders(final PluginCall call) {

int i = 0;
for (Reader reader : this.readers) {
readersJSObject.put(new JSObject().put("index", String.valueOf(i)).put("serialNumber", reader.getSerialNumber()));
readersJSObject.put(this.emptyObject.put("index", String.valueOf(i)).put("serialNumber", reader.getSerialNumber()));
}
this.notifyListeners(TerminalEnumEvent.DiscoveredReaders.getWebEventName(), new JSObject().put("readers", readersJSObject));
call.resolve(new JSObject().put("readers", readersJSObject));
this.notifyListeners(TerminalEnumEvent.DiscoveredReaders.getWebEventName(), this.emptyObject.put("readers", readersJSObject));
call.resolve(this.emptyObject.put("readers", readersJSObject));
};
discoveryCancelable =
Terminal.getInstance()
Expand All @@ -164,15 +178,21 @@ public void connectReader(final PluginCall call) {
this.connectLocalMobileReader(call);
} else if (this.terminalConnectType == TerminalConnectTypes.Internet) {
this.connectInternetReader(call);
} else if (this.terminalConnectType == TerminalConnectTypes.Usb) {
this.connectUsbReader(call);
} else if (this.terminalConnectType == TerminalConnectTypes.Bluetooth) {
this.connectBluetoothReader(call);
} else {
call.reject("type is not defined.");
}
}

public void getConnectedReader(final PluginCall call) {
Reader reader = Terminal.getInstance().getConnectedReader();
if (reader == null) {
call.resolve(new JSObject().put("reader", JSObject.NULL));
call.resolve(this.emptyObject.put("reader", JSObject.NULL));
} else {
call.resolve(new JSObject().put("reader", new JSObject().put("serialNumber", reader.getSerialNumber())));
call.resolve(this.emptyObject.put("reader", this.emptyObject.put("serialNumber", reader.getSerialNumber())));
}
}

Expand Down Expand Up @@ -208,54 +228,56 @@ private void connectLocalMobileReader(final PluginCall call) {
return;
}

ConnectionConfiguration.LocalMobileConnectionConfiguration config = new ConnectionConfiguration.LocalMobileConnectionConfiguration(
LocalMobileConnectionConfiguration config = new LocalMobileConnectionConfiguration(
this.locationId
);
Terminal
.getInstance()
.connectLocalMobileReader(
this.readers.get(reader.getInteger("index")),
config,
new ReaderCallback() {
@Override
public void onSuccess(@NonNull Reader r) {
notifyListeners(TerminalEnumEvent.ConnectedReader.getWebEventName(), emptyObject);
call.resolve();
}

@Override
public void onFailure(@NonNull TerminalException ex) {
ex.printStackTrace();
call.reject(ex.getLocalizedMessage(), ex);
}
}
this.readerCallback(call)
);
}

private void connectInternetReader(final PluginCall call) {
JSObject reader = call.getObject("reader");
ConnectionConfiguration.InternetConnectionConfiguration config = new ConnectionConfiguration.InternetConnectionConfiguration();
InternetConnectionConfiguration config = new InternetConnectionConfiguration();
Terminal
.getInstance()
.connectInternetReader(
this.readers.get(reader.getInteger("index")),
config,
new ReaderCallback() {
@Override
public void onSuccess(@NonNull Reader r) {
notifyListeners(TerminalEnumEvent.ConnectedReader.getWebEventName(), emptyObject);
call.resolve();
}

@Override
public void onFailure(@NonNull TerminalException ex) {
ex.printStackTrace();
call.reject(ex.getLocalizedMessage(), ex);
}
}
this.readerCallback(call)
);
}

private void connectUsbReader(final PluginCall call) {
JSObject reader = call.getObject("reader");
UsbConnectionConfiguration config = new UsbConnectionConfiguration(this.locationId);
Terminal
.getInstance()
.connectUsbReader(
this.readers.get(reader.getInteger("index")),
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)
);
}

public void cancelDiscoverReaders(final PluginCall call) {
if (discoveryCancelable != null) {
discoveryCancelable.cancel(
Expand Down Expand Up @@ -337,21 +359,25 @@ public void onSuccess(PaymentIntent paymentIntent) {
? pm.getCardPresentDetails()
: pm.getInteracPresentDetails();

if (card != null) {
collectCall.resolve(emptyObject
.put("brand", card.getBrand())
.put("cardholderName", card.getCardholderName())
.put("country", card.getCountry())
.put("emvAuthData", card.getEmvAuthData())
.put("expMonth", card.getExpMonth())
.put("expYear", card.getExpYear())
.put("funding", card.getFunding())
.put("generatedCard", card.getGeneratedCard())
.put("incrementalAuthorizationStatus", card.getIncrementalAuthorizationStatus())
.put("last4", card.getLast4())
.put("networks", card.getNetworks())
.put("readMethod", card.getReadMethod())

collectCall.resolve(emptyObject
.put("brand", card.getBrand())
.put("cardholderName", card.getCardholderName())
.put("country", card.getCountry())
.put("emvAuthData", card.getEmvAuthData())
.put("expMonth", card.getExpMonth())
.put("expYear", card.getExpYear())
.put("funding", card.getFunding())
.put("generatedCard", card.getGeneratedCard())
.put("incrementalAuthorizationStatus", card.getIncrementalAuthorizationStatus())
.put("last4", card.getLast4())
.put("networks", card.getNetworks())
.put("readMethod", card.getReadMethod())
);
);
} else {
collectCall.resolve();
}
}

@Override
Expand All @@ -361,4 +387,50 @@ public void onFailure(@NonNull TerminalException ex) {
collectCall.reject(ex.getLocalizedMessage(), ex);
}
};

private ReaderCallback readerCallback(final PluginCall call) {
return new ReaderCallback() {
@Override
public void onSuccess(@NonNull Reader r) {
notifyListeners(TerminalEnumEvent.ConnectedReader.getWebEventName(), emptyObject);
call.resolve();
}

@Override
public void onFailure(@NonNull TerminalException ex) {
ex.printStackTrace();
call.reject(ex.getLocalizedMessage(), ex);
}
};
}

private ReaderListener readerListener() {
return new ReaderListener() {
@Override
public void onStartInstallingUpdate(@NotNull ReaderSoftwareUpdate update, @NotNull Cancelable cancelable) {
// Show UI communicating that a required update has started installing
}

@Override
public void onReportReaderSoftwareUpdateProgress(float progress) {
// Update the progress of the install
}

@Override
public void onFinishInstallingUpdate(@Nullable ReaderSoftwareUpdate update, @Nullable TerminalException e) {
// Report success or failure of the update
}

@Override
public void onReportLowBatteryWarning() {

}

@Override
public void onReportAvailableUpdate(@NotNull ReaderSoftwareUpdate update) {
// An update is available for the connected reader. Show this update in your application.
// This update can be installed using `Terminal.getInstance().installAvailableUpdate`.
}
};
}
}

0 comments on commit 2a28694

Please sign in to comment.