diff --git a/README.md b/README.md index a07a94a..e77cdcb 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,48 @@ Follow all Stripe instructions under ["Configure your app"](https://stripe.com/d ### Android -(not supported yet) +Add the plugin to your `MainActivity.java`: + +```java +// import it at the top +import io.event1.capacitorstripeterminal.StripeTerminal; + +this.init(savedInstanceState, new ArrayList>() {{ + // Additional plugins you've installed go here + // Ex: add(TotallyAwesomePlugin.class); + add(StripeTerminal.class); +}}); +``` + +Add the `ACCESS_FINE_LOCATION`, `BLUETOOTH`, and `BLUETOOTH_ADMIN` permissions to your app's manifest: + +```xml + + + + + + + +``` + +On Android, you must also make sure that Location permission has been granted by the user: + +```javascript +const response = await StripeTerminalPlugin.getPermissions() + +if (!response.granted) { + throw new Error('Location permission is required.') +} + +const terminal = new StripeTerminalPlugin() +``` + +If the user does not grant permission, `StripeTerminalPlugin` will throw an error when you try to initialize it so you will have to handle that. + +_Hint: If the user denies Location permission the first time you ask for it, Android will not display a prompt to the user on subsequent requests for permission and `response.granted` will always be `false`. You will have to ask the user to go into the app's settings to allow Location permission._ ## Usage diff --git a/android/build.gradle b/android/build.gradle index a73bc99..9622a2a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,23 +1,29 @@ +ext { + junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.12' + androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.1' + androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.2.0' +} + buildscript { repositories { - jcenter() google() + jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.6.1' } } apply plugin: 'com.android.library' android { - compileSdkVersion 28 + compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 29 defaultConfig { - minSdkVersion 21 - targetSdkVersion 28 + minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 21 + targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 29 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { @@ -40,8 +46,8 @@ repositories { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':capacitor-android') - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' -} - + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" + implementation "com.stripe:stripeterminal:1.0.17" +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index aac7c9b..bb8b131 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -15,3 +15,10 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar index 13372ae..5c2d1cf 100644 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index f715fac..674bdda 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Dec 01 12:41:00 CST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java b/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java index 1d58c77..c98096b 100644 --- a/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java +++ b/android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java @@ -1,14 +1,13 @@ package com.getcapacitor.android; -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; +import static org.junit.Assert.*; +import android.content.Context; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.*; - /** * Instrumented test, which will execute on an Android device. * @@ -16,11 +15,14 @@ */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - assertEquals("com.getcapacitor.android", appContext.getPackageName()); - } + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry + .getInstrumentation() + .getTargetContext(); + + assertEquals("com.getcapacitor.android", appContext.getPackageName()); + } } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 2b1dd16..1fbc637 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="io.event1.capacitorstripeterminal.capacitorstripeterminal"> \ No newline at end of file diff --git a/android/src/main/java/io/event1/capacitor-stripe-terminal/StripeTerminal.java b/android/src/main/java/io/event1/capacitor-stripe-terminal/StripeTerminal.java deleted file mode 100644 index 7134aea..0000000 --- a/android/src/main/java/io/event1/capacitor-stripe-terminal/StripeTerminal.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.event1.capacitor-stripe-terminal; - -import com.getcapacitor.JSObject; -import com.getcapacitor.NativePlugin; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; - -@NativePlugin() -public class StripeTerminal extends Plugin { - - @PluginMethod() - public void echo(PluginCall call) { - String value = call.getString("value"); - - JSObject ret = new JSObject(); - ret.put("value", value); - call.success(ret); - } -} diff --git a/android/src/main/java/io/event1/capacitorstripeterminal/StripeTerminal.java b/android/src/main/java/io/event1/capacitorstripeterminal/StripeTerminal.java new file mode 100644 index 0000000..5b7df54 --- /dev/null +++ b/android/src/main/java/io/event1/capacitorstripeterminal/StripeTerminal.java @@ -0,0 +1,677 @@ +package io.event1.capacitorstripeterminal; + +import android.Manifest; +import android.bluetooth.BluetoothAdapter; +import com.getcapacitor.JSArray; +import com.getcapacitor.JSObject; +import com.getcapacitor.NativePlugin; +import com.getcapacitor.Plugin; +import com.getcapacitor.PluginCall; +import com.getcapacitor.PluginMethod; +import com.stripe.stripeterminal.Terminal; +import com.stripe.stripeterminal.callable.Callback; +import com.stripe.stripeterminal.callable.Cancelable; +import com.stripe.stripeterminal.callable.ConnectionTokenCallback; +import com.stripe.stripeterminal.callable.ConnectionTokenProvider; +import com.stripe.stripeterminal.callable.DiscoveryListener; +import com.stripe.stripeterminal.callable.PaymentIntentCallback; +import com.stripe.stripeterminal.callable.ReaderCallback; +import com.stripe.stripeterminal.callable.ReaderDisplayListener; +import com.stripe.stripeterminal.callable.ReaderSoftwareUpdateCallback; +import com.stripe.stripeterminal.callable.ReaderSoftwareUpdateListener; +import com.stripe.stripeterminal.callable.TerminalListener; +import com.stripe.stripeterminal.log.LogLevel; +import com.stripe.stripeterminal.model.external.ConnectionStatus; +import com.stripe.stripeterminal.model.external.ConnectionTokenException; +import com.stripe.stripeterminal.model.external.DeviceType; +import com.stripe.stripeterminal.model.external.DiscoveryConfiguration; +import com.stripe.stripeterminal.model.external.PaymentIntent; +import com.stripe.stripeterminal.model.external.PaymentIntentParameters; +import com.stripe.stripeterminal.model.external.PaymentStatus; +import com.stripe.stripeterminal.model.external.Reader; +import com.stripe.stripeterminal.model.external.ReaderDisplayMessage; +import com.stripe.stripeterminal.model.external.ReaderEvent; +import com.stripe.stripeterminal.model.external.ReaderInputOptions; +import com.stripe.stripeterminal.model.external.ReaderSoftwareUpdate; +import com.stripe.stripeterminal.model.external.TerminalException; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + +@NativePlugin( + permissionRequestCode = StripeTerminal.REQUEST_CODE, + permissions = { Manifest.permission.ACCESS_FINE_LOCATION } +) +public class StripeTerminal + extends Plugin + implements + ConnectionTokenProvider, + TerminalListener, + DiscoveryListener, + ReaderDisplayListener, + ReaderSoftwareUpdateListener { + public static final int REQUEST_CODE = 0x6424; // Unique request code + Cancelable pendingDiscoverReaders = null; + Cancelable pendingCollectPaymentMethod = null; + ConnectionTokenCallback pendingConnectionTokenCallback = null; + String lastCurrency = null; + + PaymentIntent currentPaymentIntent = null; + ReaderEvent lastReaderEvent = ReaderEvent.CARD_REMOVED; + List discoveredReadersList = null; + ReaderSoftwareUpdate readerSoftwareUpdate; + Cancelable pendingInstallUpdate = null; + + @PluginMethod + public void getPermissions(PluginCall call) { + if (!hasRequiredPermissions()) { + requestPermissions(call); + } else { + JSObject result = new JSObject(); + result.put("granted", true); + call.success(result); + } + } + + @Override + protected void handleRequestPermissionsResult( + int requestCode, + String[] permissions, + int[] grantResults + ) { + super.handleRequestPermissionsResult( + requestCode, + permissions, + grantResults + ); + + PluginCall savedCall = getSavedCall(); + JSObject result = new JSObject(); + + if (!hasRequiredPermissions()) { + result.put("granted", false); + savedCall.success(result); + } else { + result.put("granted", true); + savedCall.success(result); + } + } + + @PluginMethod + public void initialize(PluginCall call) { + if (!hasRequiredPermissions()) { + call.error("Location permission is required."); + } + + // turn on bluetooth + BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter(); + if (bluetooth.isEnabled() == false) { + bluetooth.enable(); + } + + try { + // Check if stripe is initialized + Terminal.getInstance(); + + JSObject ret = new JSObject(); + ret.put("isInitialized", true); + + call.resolve(ret); + return; + } catch (IllegalStateException e) {} + + pendingConnectionTokenCallback = null; + abortDiscoverReaders(); + abortInstallUpdate(); + + LogLevel logLevel = LogLevel.VERBOSE; + ConnectionTokenProvider tokenProvider = this; + TerminalListener terminalListener = this; + + String err = ""; + boolean isInitialized = false; + try { + Terminal.initTerminal( + getBridge().getActivity(), + logLevel, + tokenProvider, + terminalListener + ); + lastReaderEvent = ReaderEvent.CARD_REMOVED; + isInitialized = true; + } catch (TerminalException e) { + e.printStackTrace(); + err = e.getErrorMessage(); + isInitialized = false; + } catch (IllegalStateException ex) { + ex.printStackTrace(); + err = ex.getMessage(); + isInitialized = true; + } + + JSObject ret = new JSObject(); + ret.put("isInitialized", isInitialized); + + if (!isInitialized) { + ret.put("error", err); + call.error(err); + return; + } + + call.resolve(ret); + } + + @PluginMethod + public void setConnectionToken(PluginCall call) { + String token = call.getString("token"); + String errorMessage = call.getString("errorMessage"); + + if (pendingConnectionTokenCallback != null) { + if (errorMessage != null && !errorMessage.trim().isEmpty()) { + pendingConnectionTokenCallback.onFailure( + new ConnectionTokenException(errorMessage) + ); + } else { + pendingConnectionTokenCallback.onSuccess(token); + } + + call.resolve(); + } + + pendingConnectionTokenCallback = null; + } + + @PluginMethod + public void discoverReaders(final PluginCall call) { + // Attempt to abort any pending discoverReader calls first. + abortDiscoverReaders(); + + Boolean simulated = call.getBoolean("simulated"); + DeviceType deviceType = DeviceType.values()[call.getInt("deviceType")]; + + DiscoveryConfiguration discoveryConfiguration = new DiscoveryConfiguration( + 0, + deviceType, + simulated + ); + Callback statusCallback = new Callback() { + + @Override + public void onSuccess() { + pendingDiscoverReaders = null; + call.resolve(); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + pendingDiscoverReaders = null; + call.error(e.getErrorMessage()); + } + }; + + abortDiscoverReaders(); + pendingDiscoverReaders = + Terminal + .getInstance() + .discoverReaders(discoveryConfiguration, this, statusCallback); + } + + @PluginMethod + public void abortDiscoverReaders(final PluginCall call) { + if ( + pendingDiscoverReaders != null && !pendingDiscoverReaders.isCompleted() + ) { + pendingDiscoverReaders.cancel( + new Callback() { + + @Override + public void onSuccess() { + pendingDiscoverReaders = null; + call.resolve(); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + call.error(e.getErrorMessage()); + } + } + ); + } else { + call.resolve(); + } + } + + public void abortDiscoverReaders() { + if ( + pendingDiscoverReaders != null && !pendingDiscoverReaders.isCompleted() + ) { + pendingDiscoverReaders.cancel( + new Callback() { + + @Override + public void onSuccess() { + pendingDiscoverReaders = null; + } + + @Override + public void onFailure(@Nonnull TerminalException e) {} + } + ); + } + } + + @PluginMethod + public void connectReader(final PluginCall call) { + String serialNumber = call.getString("serialNumber"); + Reader selectedReader = null; + if (discoveredReadersList != null && discoveredReadersList.size() > 0) { + for (Reader reader : discoveredReadersList) { + if (reader != null) { + if (reader.getSerialNumber().equals(serialNumber)) { + selectedReader = reader; + } + } + } + } + + if (selectedReader != null) { + Terminal + .getInstance() + .connectReader( + selectedReader, + new ReaderCallback() { + + @Override + public void onSuccess(@Nonnull Reader reader) { + JSObject ret = new JSObject(); + ret.put("reader", TerminalUtils.serializeReader(reader)); + call.resolve(ret); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + call.error(e.getErrorMessage(), e); + } + } + ); + } else { + call.error("No reader found with provided serial number"); + } + } + + @PluginMethod + public void disconnectReader(final PluginCall call) { + if (Terminal.getInstance().getConnectedReader() == null) { + call.resolve(); + } else { + Terminal + .getInstance() + .disconnectReader( + new Callback() { + + @Override + public void onSuccess() { + call.resolve(); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + call.error(e.getErrorMessage(), e); + } + } + ); + } + } + + @PluginMethod + public void getConnectedReader(PluginCall call) { + Reader reader = Terminal.getInstance().getConnectedReader(); + JSObject ret = new JSObject(); + ret.put("reader", TerminalUtils.serializeReader(reader)); + call.resolve(ret); + } + + @PluginMethod + public void getConnectionStatus(PluginCall call) { + ConnectionStatus status = Terminal.getInstance().getConnectionStatus(); + + JSObject ret = new JSObject(); + ret.put("status", status.ordinal()); + ret.put("isAndroid", true); + call.resolve(ret); + } + + @PluginMethod + public void retrievePaymentIntent(final PluginCall call) { + String clientSecret = call.getString("clientSecret"); + + if (clientSecret != null) { + Terminal + .getInstance() + .retrievePaymentIntent( + clientSecret, + new PaymentIntentCallback() { + + @Override + public void onSuccess(@Nonnull PaymentIntent paymentIntent) { + currentPaymentIntent = paymentIntent; + JSObject ret = new JSObject(); + ret.put( + "intent", + TerminalUtils.serializePaymentIntent(paymentIntent, "") + ); + call.resolve(ret); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + currentPaymentIntent = null; + call.error(e.getErrorMessage(), e); + } + } + ); + } else { + call.reject("Client secret cannot be null"); + } + } + + @PluginMethod + public void collectPaymentMethod(final PluginCall call) { + if (currentPaymentIntent != null) { + pendingCollectPaymentMethod = + Terminal + .getInstance() + .collectPaymentMethod( + currentPaymentIntent, + this, + new PaymentIntentCallback() { + + @Override + public void onSuccess(@Nonnull PaymentIntent paymentIntent) { + pendingCollectPaymentMethod = null; + currentPaymentIntent = paymentIntent; + + JSObject ret = new JSObject(); + ret.put( + "intent", + TerminalUtils.serializePaymentIntent( + paymentIntent, + lastCurrency + ) + ); + + call.resolve(ret); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + pendingCollectPaymentMethod = null; + call.error(e.getErrorMessage(), e.getErrorCode().toString(), e); + } + } + ); + } else { + call.reject( + "There is no active payment intent. Make sure you called retrievePaymentIntent first" + ); + } + } + + @PluginMethod + public void abortCollectPaymentMethod(final PluginCall call) { + if ( + pendingCollectPaymentMethod != null && + !pendingCollectPaymentMethod.isCompleted() + ) { + pendingCollectPaymentMethod.cancel( + new Callback() { + + @Override + public void onSuccess() { + pendingCollectPaymentMethod = null; + call.resolve(); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + call.error(e.getErrorMessage()); + } + } + ); + } + } + + @PluginMethod + public void processPayment(final PluginCall call) { + if (currentPaymentIntent != null) { + Terminal + .getInstance() + .processPayment( + currentPaymentIntent, + new PaymentIntentCallback() { + + @Override + public void onSuccess(@Nonnull PaymentIntent paymentIntent) { + currentPaymentIntent = paymentIntent; + + JSObject ret = new JSObject(); + ret.put( + "intent", + TerminalUtils.serializePaymentIntent( + paymentIntent, + lastCurrency + ) + ); + call.resolve(ret); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + call.error(e.getErrorMessage(), e.getErrorCode().toString(), e); + } + } + ); + } else { + call.reject( + "There is no active payment intent. Make sure you called retrievePaymentIntent first" + ); + } + } + + @PluginMethod + public void clearCachedCredentials(PluginCall call) { + Terminal.getInstance().clearCachedCredentials(); + call.resolve(); + } + + @PluginMethod + public void checkForUpdate(final PluginCall call) { + Terminal + .getInstance() + .checkForUpdate( + new ReaderSoftwareUpdateCallback() { + + @Override + public void onSuccess( + @Nullable ReaderSoftwareUpdate readerSoftwareUpdate + ) { + StripeTerminal.this.readerSoftwareUpdate = readerSoftwareUpdate; + + JSObject ret = new JSObject(); + ret.put( + "update", + TerminalUtils.serializeUpdate(readerSoftwareUpdate) + ); + + call.resolve(ret); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + call.error(e.getErrorMessage(), e); + } + } + ); + } + + @PluginMethod + public void installUpdate(final PluginCall call) { + pendingInstallUpdate = + Terminal + .getInstance() + .installUpdate( + readerSoftwareUpdate, + this, + new Callback() { + + @Override + public void onSuccess() { + readerSoftwareUpdate = null; + pendingInstallUpdate = null; + call.resolve(); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + call.error(e.getErrorMessage(), e); + } + } + ); + } + + @PluginMethod + public void abortInstallUpdate(final PluginCall call) { + if (pendingInstallUpdate != null && !pendingInstallUpdate.isCompleted()) { + pendingInstallUpdate.cancel( + new Callback() { + + @Override + public void onSuccess() { + pendingInstallUpdate = null; + call.resolve(); + } + + @Override + public void onFailure(@Nonnull TerminalException e) { + call.error(e.getErrorMessage(), e); + } + } + ); + } else { + call.resolve(); + } + } + + public void abortInstallUpdate() { + if (pendingInstallUpdate != null && !pendingInstallUpdate.isCompleted()) { + pendingInstallUpdate.cancel( + new Callback() { + + @Override + public void onSuccess() { + pendingInstallUpdate = null; + } + + @Override + public void onFailure(@Nonnull TerminalException e) {} + } + ); + } + } + + @Override + public void fetchConnectionToken( + @NotNull ConnectionTokenCallback connectionTokenCallback + ) { + pendingConnectionTokenCallback = connectionTokenCallback; + + JSObject ret = new JSObject(); + notifyListeners("requestConnectionToken", ret); + } + + @Override + public void onConnectionStatusChange( + @NotNull ConnectionStatus connectionStatus + ) { + JSObject ret = new JSObject(); + ret.put("status", connectionStatus.ordinal()); + ret.put("isAndroid", true); + notifyListeners("didChangeConnectionStatus", ret); + } + + @Override + public void onPaymentStatusChange(@NotNull PaymentStatus paymentStatus) { + JSObject ret = new JSObject(); + ret.put("status", paymentStatus.ordinal()); + + notifyListeners("didChangePaymentStatus", ret); + } + + @Override + public void onReportLowBatteryWarning() { + notifyListeners("didReportLowBatteryWarning", new JSObject()); + } + + @Override + public void onReportReaderEvent(@NotNull ReaderEvent readerEvent) { + lastReaderEvent = readerEvent; + JSObject ret = new JSObject(); + ret.put("event", readerEvent.ordinal()); + notifyListeners("didReportReaderEvent", ret); + } + + @Override + public void onUnexpectedReaderDisconnect(@NotNull Reader reader) { + JSObject ret = new JSObject(); + ret.put("reader", TerminalUtils.serializeReader(reader)); + notifyListeners("didReportUnexpectedReaderDisconnect", ret); + } + + @Override + public void onUpdateDiscoveredReaders(@NotNull List list) { + discoveredReadersList = list; + + JSArray readersDiscoveredArr = new JSArray(); + for (Reader reader : list) { + if (reader != null) { + readersDiscoveredArr.put(TerminalUtils.serializeReader(reader)); + } + } + + JSObject ret = new JSObject(); + ret.put("readers", readersDiscoveredArr); + + notifyListeners("readersDiscovered", ret); + } + + @Override + public void onRequestReaderDisplayMessage( + @NotNull ReaderDisplayMessage readerDisplayMessage + ) { + JSObject ret = new JSObject(); + ret.put("value", readerDisplayMessage.ordinal()); + ret.put("text", readerDisplayMessage.toString()); + + notifyListeners("didRequestReaderDisplayMessage", ret); + } + + @Override + public void onRequestReaderInput( + @NotNull ReaderInputOptions readerInputOptions + ) { + JSObject ret = new JSObject(); + ret.put("value", readerInputOptions.toString()); + ret.put("isAndroid", true); + + notifyListeners("didRequestReaderInput", ret); + } + + @Override + public void onReportReaderSoftwareUpdateProgress(float v) { + JSObject ret = new JSObject(); + ret.put("progress", v); + + notifyListeners("didReportReaderSoftwareUpdateProgress", ret); + } +} diff --git a/android/src/main/java/io/event1/capacitorstripeterminal/TerminalUtils.java b/android/src/main/java/io/event1/capacitorstripeterminal/TerminalUtils.java new file mode 100644 index 0000000..e5f3fbd --- /dev/null +++ b/android/src/main/java/io/event1/capacitorstripeterminal/TerminalUtils.java @@ -0,0 +1,74 @@ +package io.event1.capacitorstripeterminal; + +import com.getcapacitor.JSObject; +import com.stripe.stripeterminal.model.external.PaymentIntent; +import com.stripe.stripeterminal.model.external.Reader; +import com.stripe.stripeterminal.model.external.ReaderSoftwareUpdate; + +public class TerminalUtils { + + public static JSObject serializeReader(Reader reader) { + JSObject object = new JSObject(); + + if (reader != null) { + double batteryLevel = 0; + if (reader.getBatteryLevel() != null) batteryLevel = + (double) reader.getBatteryLevel(); + object.put("batteryLevel", batteryLevel); + + int readerType = 0; + if (reader.getDeviceType() != null) readerType = + reader.getDeviceType().ordinal(); + object.put("deviceType", readerType); + + String serial = ""; + + if (reader.getSerialNumber() != null) serial = reader.getSerialNumber(); + object.put("serialNumber", serial); + + String softwareVersion = ""; + if (reader.getSoftwareVersion() != null) softwareVersion = + reader.getSoftwareVersion(); + object.put("deviceSoftwareVersion", softwareVersion); + } + return object; + } + + public static JSObject serializePaymentIntent( + PaymentIntent paymentIntent, + String currency + ) { + JSObject object = new JSObject(); + + object.put("stripeId", paymentIntent.getId()); + object.put("created", paymentIntent.getCreated()); + object.put("status", paymentIntent.getStatus().ordinal()); + object.put("amount", paymentIntent.getAmount()); + object.put("currency", currency); + + JSObject metaData = new JSObject(); + if (paymentIntent.getMetadata() != null) { + for (String key : paymentIntent.getMetadata().keySet()) { + metaData.put(key, String.valueOf(paymentIntent.getMetadata().get(key))); + } + } + object.put("metadata", metaData); + + return object; + } + + public static JSObject serializeUpdate( + ReaderSoftwareUpdate readerSoftwareUpdate + ) { + JSObject object = new JSObject(); + + if (readerSoftwareUpdate != null) { + ReaderSoftwareUpdate.UpdateTimeEstimate updateTimeEstimate = readerSoftwareUpdate.getTimeEstimate(); + + object.put("estimatedUpdateTime", updateTimeEstimate.getDescription()); + object.put("deviceSoftwareVersion", readerSoftwareUpdate.getVersion()); + } + + return object; + } +} diff --git a/android/src/main/res/layout/bridge_layout_main.xml b/android/src/main/res/layout/bridge_layout_main.xml index b69e589..3f165bf 100644 --- a/android/src/main/res/layout/bridge_layout_main.xml +++ b/android/src/main/res/layout/bridge_layout_main.xml @@ -1,5 +1,5 @@ - - + \ No newline at end of file diff --git a/ios/Plugin/Plugin.m b/ios/Plugin/Plugin.m index 39af398..4e50a30 100644 --- a/ios/Plugin/Plugin.m +++ b/ios/Plugin/Plugin.m @@ -20,6 +20,7 @@ CAP_PLUGIN_METHOD(collectPaymentMethod, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(processPayment, CAPPluginReturnPromise); CAP_PLUGIN_METHOD(clearCachedCredentials, CAPPluginReturnPromise); + CAP_PLUGIN_METHOD(getPermissions, CAPPluginReturnPromise); ) diff --git a/ios/Plugin/Plugin.swift b/ios/Plugin/Plugin.swift index c50478d..13a2618 100644 --- a/ios/Plugin/Plugin.swift +++ b/ios/Plugin/Plugin.swift @@ -25,6 +25,10 @@ public class StripeTerminal: CAPPlugin, ConnectionTokenProvider, DiscoveryDelega // self.notifyListeners("log", data: ["logline": logline]) } + @objc func getPermissions(_ call: CAPPluginCall) { + call.resolve(["granted": true]) + } + @objc func initialize(_ call: CAPPluginCall) { DispatchQueue.main.async { Terminal.setTokenProvider(self) diff --git a/package-lock.json b/package-lock.json index 0f05c5d..bf276bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -648,13 +648,13 @@ "optional": true }, "compare-func": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.4.tgz", - "integrity": "sha512-sq2sWtrqKPkEXAC8tEJA1+BqAH9GbFkGBtUOqrUX57VSfwp8xyktctk+uLoRy5eccTdxzDcVIztlYDpKs3Jv1Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "requires": { "array-ify": "^1.0.0", - "dot-prop": "^3.0.0" + "dot-prop": "^5.1.0" } }, "compare-versions": { @@ -670,22 +670,49 @@ "dev": true }, "conventional-changelog-angular": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", - "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz", + "integrity": "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "q": "^1.5.1" + }, + "dependencies": { + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + } } }, "conventional-changelog-writer": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", - "integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz", + "integrity": "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==", "dev": true, "requires": { - "compare-func": "^1.3.1", + "compare-func": "^2.0.0", "conventional-commits-filter": "^2.0.6", "dateformat": "^3.0.0", "handlebars": "^4.7.6", @@ -837,12 +864,12 @@ } }, "dot-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", - "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "dev": true, "requires": { - "is-obj": "^1.0.0" + "is-obj": "^2.0.0" } }, "duplexer2": { @@ -1417,9 +1444,9 @@ "dev": true }, "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, "is-plain-obj": { @@ -1491,6 +1518,14 @@ "requires": { "chevrotain": "6.5.0", "lodash": "4.17.15" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "java-properties": { @@ -1578,9 +1613,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash.capitalize": { @@ -1916,9 +1951,9 @@ "dev": true }, "npm": { - "version": "6.14.5", - "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.5.tgz", - "integrity": "sha512-CDwa3FJd0XJpKDbWCST484H+mCNjF26dPrU+xnREW+upR0UODjMEfXPl3bxWuAwZIX6c2ASg1plLO7jP8ehWeA==", + "version": "6.14.8", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.8.tgz", + "integrity": "sha512-HBZVBMYs5blsj94GTeQZel7s9odVuuSUHy1+AlZh7rPVux1os2ashvEGLy/STNK7vUjbrCg5Kq9/GXisJgdf6A==", "dev": true, "requires": { "JSONStream": "^1.3.5", @@ -1927,7 +1962,7 @@ "ansistyles": "~0.1.3", "aproba": "^2.0.0", "archy": "~1.0.0", - "bin-links": "^1.1.7", + "bin-links": "^1.1.8", "bluebird": "^3.5.5", "byte-size": "^5.0.1", "cacache": "^12.0.3", @@ -1948,7 +1983,7 @@ "find-npm-prefix": "^1.0.2", "fs-vacuum": "~1.2.10", "fs-write-stream-atomic": "~1.0.10", - "gentle-fs": "^2.3.0", + "gentle-fs": "^2.3.1", "glob": "^7.1.6", "graceful-fs": "^4.2.4", "has-unicode": "~2.0.1", @@ -1963,14 +1998,14 @@ "is-cidr": "^3.0.0", "json-parse-better-errors": "^1.0.2", "lazy-property": "~1.0.0", - "libcipm": "^4.0.7", + "libcipm": "^4.0.8", "libnpm": "^3.0.1", "libnpmaccess": "^3.0.2", "libnpmhook": "^5.0.3", "libnpmorg": "^1.0.1", "libnpmsearch": "^2.0.2", "libnpmteam": "^1.0.2", - "libnpx": "^10.2.2", + "libnpx": "^10.2.4", "lock-verify": "^2.1.0", "lockfile": "^1.0.4", "lodash._baseindexof": "*", @@ -1985,22 +2020,22 @@ "lodash.uniq": "~4.5.0", "lodash.without": "~4.4.0", "lru-cache": "^5.1.1", - "meant": "~1.0.1", + "meant": "^1.0.2", "mississippi": "^3.0.0", "mkdirp": "^0.5.5", "move-concurrently": "^1.0.1", "node-gyp": "^5.1.0", "nopt": "^4.0.3", "normalize-package-data": "^2.5.0", - "npm-audit-report": "^1.3.2", + "npm-audit-report": "^1.3.3", "npm-cache-filename": "~1.0.2", "npm-install-checks": "^3.0.2", - "npm-lifecycle": "^3.1.4", + "npm-lifecycle": "^3.1.5", "npm-package-arg": "^6.1.1", "npm-packlist": "^1.4.8", "npm-pick-manifest": "^3.0.2", "npm-profile": "^4.0.4", - "npm-registry-fetch": "^4.0.4", + "npm-registry-fetch": "^4.0.7", "npm-user-validate": "~1.0.0", "npmlog": "~4.1.2", "once": "~1.4.0", @@ -2209,7 +2244,7 @@ } }, "bin-links": { - "version": "1.1.7", + "version": "1.1.8", "bundled": true, "dev": true, "requires": { @@ -2364,26 +2399,41 @@ } }, "cliui": { - "version": "4.1.0", + "version": "5.0.0", "bundled": true, "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", "bundled": true, "dev": true }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "strip-ansi": { - "version": "4.0.0", + "version": "5.2.0", "bundled": true, "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -2498,11 +2548,11 @@ } }, "configstore": { - "version": "3.1.2", + "version": "3.1.5", "bundled": true, "dev": true, "requires": { - "dot-prop": "^4.1.0", + "dot-prop": "^4.2.1", "graceful-fs": "^4.1.2", "make-dir": "^1.0.0", "unique-string": "^1.0.0", @@ -2678,7 +2728,7 @@ } }, "dot-prop": { - "version": "4.2.0", + "version": "4.2.1", "bundled": true, "dev": true, "requires": { @@ -2745,6 +2795,11 @@ "bundled": true, "dev": true }, + "emoji-regex": { + "version": "7.0.3", + "bundled": true, + "dev": true + }, "encoding": { "version": "0.1.12", "bundled": true, @@ -2870,14 +2925,6 @@ "bundled": true, "dev": true }, - "find-up": { - "version": "2.1.0", - "bundled": true, - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, "flush-write-stream": { "version": "1.0.3", "bundled": true, @@ -3076,7 +3123,7 @@ "dev": true }, "gentle-fs": { - "version": "2.3.0", + "version": "2.3.1", "bundled": true, "dev": true, "requires": { @@ -3106,7 +3153,7 @@ } }, "get-caller-file": { - "version": "1.0.3", + "version": "2.0.5", "bundled": true, "dev": true }, @@ -3330,11 +3377,6 @@ "validate-npm-package-name": "^3.0.0" } }, - "invert-kv": { - "version": "2.0.0", - "bundled": true, - "dev": true - }, "ip": { "version": "1.1.5", "bundled": true, @@ -3519,16 +3561,8 @@ "bundled": true, "dev": true }, - "lcid": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, "libcipm": { - "version": "4.0.7", + "version": "4.0.8", "bundled": true, "dev": true, "requires": { @@ -3538,7 +3572,7 @@ "find-npm-prefix": "^1.0.2", "graceful-fs": "^4.1.11", "ini": "^1.3.5", - "lock-verify": "^2.0.2", + "lock-verify": "^2.1.0", "mkdirp": "^0.5.1", "npm-lifecycle": "^3.0.0", "npm-logical-tree": "^1.2.1", @@ -3697,7 +3731,7 @@ } }, "libnpx": { - "version": "10.2.2", + "version": "10.2.4", "bundled": true, "dev": true, "requires": { @@ -3708,16 +3742,7 @@ "update-notifier": "^2.3.0", "which": "^1.3.0", "y18n": "^4.0.0", - "yargs": "^11.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "yargs": "^14.2.3" } }, "lock-verify": { @@ -3848,36 +3873,11 @@ "ssri": "^6.0.0" } }, - "map-age-cleaner": { - "version": "0.1.3", - "bundled": true, - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, "meant": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true, "dev": true }, - "mem": { - "version": "4.3.0", - "bundled": true, - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "2.1.0", - "bundled": true, - "dev": true - } - } - }, "mime-db": { "version": "1.35.0", "bundled": true, @@ -3899,6 +3899,11 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + }, "minizlib": { "version": "1.3.3", "bundled": true, @@ -3980,11 +3985,6 @@ "bundled": true, "dev": true }, - "nice-try": { - "version": "1.0.5", - "bundled": true, - "dev": true - }, "node-fetch-npm": { "version": "2.0.2", "bundled": true, @@ -4044,7 +4044,7 @@ } }, "npm-audit-report": { - "version": "1.3.2", + "version": "1.3.3", "bundled": true, "dev": true, "requires": { @@ -4074,7 +4074,7 @@ } }, "npm-lifecycle": { - "version": "3.1.4", + "version": "3.1.5", "bundled": true, "dev": true, "requires": { @@ -4140,7 +4140,7 @@ } }, "npm-registry-fetch": { - "version": "4.0.4", + "version": "4.0.7", "bundled": true, "dev": true, "requires": { @@ -4154,7 +4154,7 @@ }, "dependencies": { "safe-buffer": { - "version": "5.2.0", + "version": "5.2.1", "bundled": true, "dev": true } @@ -4231,44 +4231,6 @@ "bundled": true, "dev": true }, - "os-locale": { - "version": "3.1.0", - "bundled": true, - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "bundled": true, - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - } - } - }, "os-tmpdir": { "version": "1.0.2", "bundled": true, @@ -4283,42 +4245,11 @@ "os-tmpdir": "^1.0.0" } }, - "p-defer": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, "p-finally": { "version": "1.0.0", "bundled": true, "dev": true }, - "p-is-promise": { - "version": "2.1.0", - "bundled": true, - "dev": true - }, - "p-limit": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, "package-json": { "version": "4.0.1", "bundled": true, @@ -4583,13 +4514,6 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "bundled": true, - "dev": true - } } }, "read": { @@ -4715,7 +4639,7 @@ "dev": true }, "require-main-filename": { - "version": "1.0.1", + "version": "2.0.0", "bundled": true, "dev": true }, @@ -4914,7 +4838,7 @@ } }, "spdx-license-ids": { - "version": "3.0.3", + "version": "3.0.5", "bundled": true, "dev": true }, @@ -5365,22 +5289,41 @@ } }, "wrap-ansi": { - "version": "2.1.0", + "version": "5.1.0", "bundled": true, "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, "string-width": { - "version": "1.0.2", + "version": "3.1.0", "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" } } } @@ -5421,37 +5364,105 @@ "dev": true }, "yargs": { - "version": "11.1.1", + "version": "14.2.3", "bundled": true, "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" }, "dependencies": { - "y18n": { - "version": "3.2.1", + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", "bundled": true, "dev": true + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, "yargs-parser": { - "version": "9.0.2", + "version": "15.0.1", "bundled": true, "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "bundled": true, + "dev": true + } } } } @@ -5731,6 +5742,12 @@ "prettier": "2.0.4" }, "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, "prettier": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz", diff --git a/package.json b/package.json index 68d72a6..674ec9e 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,9 @@ "capacitor": { "ios": { "src": "ios" + }, + "android": { + "src": "android" } }, "repository": { diff --git a/src/definitions.ts b/src/definitions.ts index b0ad762..96e7e73 100644 --- a/src/definitions.ts +++ b/src/definitions.ts @@ -310,7 +310,10 @@ export interface StripeTerminalInterface extends Plugin { getConnectedReader(): Promise<{ reader: Reader }> - getConnectionStatus(): Promise<{ status: ConnectionStatus }> + getConnectionStatus(): Promise<{ + status: ConnectionStatus + isAndroid?: boolean + }> disconnectReader(): Promise @@ -331,4 +334,6 @@ export interface StripeTerminalInterface extends Plugin { processPayment(): Promise<{ intent: PaymentIntent }> clearCachedCredentials(): Promise + + getPermissions(): Promise<{ granted: boolean }> } diff --git a/src/plugin.ts b/src/plugin.ts index bceaf00..a792521 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -14,7 +14,16 @@ import { const { StripeTerminal } = Plugins +// The Android connection status enum is different from iOS, this maps Android to iOS +const AndroidConnectionStatusMap = { + 0: ConnectionStatus.NotConnected, + 1: ConnectionStatus.Connecting, + 2: ConnectionStatus.Connected +} + export class StripeTerminalPlugin { + public isInitialized = false + private _fetchConnectionToken: () => Promise = () => Promise.reject('You must initialize StripeTerminalPlugin first.') @@ -23,29 +32,49 @@ export class StripeTerminalPlugin { constructor(options: StripeTerminalConfig) { this._fetchConnectionToken = options.fetchConnectionToken - StripeTerminal.initialize().then(() => { - this.listeners['connectionTokenListener'] = StripeTerminal.addListener( - 'requestConnectionToken', - () => { - this._fetchConnectionToken() - .then(token => { - if (token) { - StripeTerminal.setConnectionToken({ token }, null) - } else { - throw new Error( - 'User-supplied `fetchConnectionToken` resolved successfully, but no token was returned.' - ) - } - }) - .catch(err => - StripeTerminal.setConnectionToken( - null, - err.message || 'Error in user-supplied `fetchConnectionToken`.' + this.init() + } + + private async init() { + this.listeners['connectionTokenListener'] = StripeTerminal.addListener( + 'requestConnectionToken', + () => { + this._fetchConnectionToken() + .then(token => { + if (token) { + StripeTerminal.setConnectionToken({ token }, null) + } else { + throw new Error( + 'User-supplied `fetchConnectionToken` resolved successfully, but no token was returned.' ) + } + }) + .catch(err => + StripeTerminal.setConnectionToken( + null, + err.message || 'Error in user-supplied `fetchConnectionToken`.' ) - } - ) - }) + ) + } + ) + + await StripeTerminal.initialize() + + this.isInitialized = true + } + + private translateConnectionStatus(data: { + status: ConnectionStatus + isAndroid?: boolean + }): ConnectionStatus { + let status: ConnectionStatus = data.status + + if (data.isAndroid) { + // the connection status on android is different than on iOS so we have to translate it + status = AndroidConnectionStatusMap[data.status] + } + + return status } private _listenerToObservable( @@ -69,9 +98,19 @@ export class StripeTerminalPlugin { }) } + private ensureInitialized() { + if (!this.isInitialized) { + throw new Error( + 'StripeTerminalPlugin must be initialized before you can use any methods.' + ) + } + } + public discoverReaders( options: DiscoveryConfiguration ): Observable { + this.ensureInitialized() + return new Observable(subscriber => { // start discovery StripeTerminal.discoverReaders(options) @@ -99,59 +138,55 @@ export class StripeTerminalPlugin { } public async connectReader(reader: Reader): Promise { - try { - const data = await StripeTerminal.connectReader(reader) + this.ensureInitialized() - return data.reader - } catch (err) { - throw err - } + const data = await StripeTerminal.connectReader(reader) + + return data.reader } public async getConnectedReader(): Promise { - try { - const data = await StripeTerminal.getConnectedReader() + this.ensureInitialized() - return data.reader - } catch (err) { - throw err - } + const data = await StripeTerminal.getConnectedReader() + + return data.reader } public async getConnectionStatus(): Promise { - try { - const data = await StripeTerminal.getConnectionStatus() + this.ensureInitialized() - return data.status - } catch (err) { - throw err - } + const data = await StripeTerminal.getConnectionStatus() + + return this.translateConnectionStatus(data) } public async disconnectReader(): Promise { + this.ensureInitialized() + return StripeTerminal.disconnectReader() } public async checkForUpdate(): Promise { - try { - const data = await StripeTerminal.checkForUpdate() + this.ensureInitialized() - return data && data.update - } catch (err) { - throw err - } + const data = await StripeTerminal.checkForUpdate() + + return data && data.update } public connectionStatus(): Observable { + this.ensureInitialized() + return new Observable(subscriber => { let hasSentEvent = false // get current value StripeTerminal.getConnectionStatus() .then((status: any) => { - // only send the inital value if the event listner hasn't already + // only send the initial value if the event listener hasn't already if (!hasSentEvent) { - subscriber.next(status.status) + subscriber.next(this.translateConnectionStatus(status)) } }) .catch((err: any) => { @@ -163,7 +198,7 @@ export class StripeTerminalPlugin { 'didChangeConnectionStatus', (status: any) => { hasSentEvent = true - subscriber.next(status.status) + subscriber.next(this.translateConnectionStatus(status)) } ) @@ -176,6 +211,8 @@ export class StripeTerminalPlugin { } public installUpdate(): Observable { + this.ensureInitialized() + return new Observable(subscriber => { // initiate the installation StripeTerminal.installUpdate() @@ -205,6 +242,10 @@ export class StripeTerminalPlugin { public readerInput(): Observable { return this._listenerToObservable('didRequestReaderInput', (data: any) => { + if (data.isAndroid) { + return data.value + } + return parseFloat(data.value) }) } @@ -221,43 +262,45 @@ export class StripeTerminalPlugin { public async retrievePaymentIntent( clientSecret: string ): Promise { - try { - const data = await StripeTerminal.retrievePaymentIntent({ clientSecret }) + this.ensureInitialized() - return data && data.intent - } catch (err) { - throw err - } + const data = await StripeTerminal.retrievePaymentIntent({ clientSecret }) + + return data && data.intent } public async collectPaymentMethod(): Promise { - try { - const data = await StripeTerminal.collectPaymentMethod() + this.ensureInitialized() - return data && data.intent - } catch (err) { - throw err - } + const data = await StripeTerminal.collectPaymentMethod() + + return data && data.intent } public async abortCollectPaymentMethod(): Promise { + this.ensureInitialized() + return StripeTerminal.abortCollectPaymentMethod() } public async processPayment(): Promise { - try { - const data = await StripeTerminal.processPayment() + this.ensureInitialized() - return data && data.intent - } catch (err) { - throw err - } + const data = await StripeTerminal.processPayment() + + return data && data.intent } public async clearCachedCredentials(): Promise { + this.ensureInitialized() + return StripeTerminal.clearCachedCredentials() } + public static async getPermissions(): Promise<{ granted: boolean }> { + return StripeTerminal.getPermissions() + } + public addListener(eventName: string, listenerFunc: Function) { return StripeTerminal.addListener(eventName, listenerFunc) }