diff --git a/plugin.xml b/plugin.xml
index 4b88506..2dbc595 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -7,9 +7,10 @@
Stripe Payments
Cordova plugin for Stripe payments using the native Android/iOS SDKs. Supports Apple Pay and card payments.
- https://github.com/rolamix/cordova-plugin-stripe-payments
+ Rolamix, Inc.
MIT
- cordova,stripe,payments,apple pay,credit cards,checkout
+ cordova,stripe,payments,apple pay,ach,google pay,cards,credit cards,checkout
+ https://github.com/rolamix/cordova-plugin-stripe-payments
https://github.com/rolamix/cordova-plugin-stripe-payments/issues
@@ -30,6 +31,10 @@
+
+
+
+
diff --git a/src/android/StripePaymentsPlugin.gradle b/src/android/StripePaymentsPlugin.gradle
new file mode 100644
index 0000000..fd4c44b
--- /dev/null
+++ b/src/android/StripePaymentsPlugin.gradle
@@ -0,0 +1,27 @@
+dependencies {
+ implementation 'com.stripe:stripe-android:8.5.0'
+ compile 'com.google.android.gms:play-services-wallet:16.0.1'
+
+ /* Cordova doesn't support AndroidX support libraries yet */
+ compile 'com.android.support:support-v4:28.0.0'
+ compile 'com.android.support:appcompat-v7:28.0.0'
+
+ /* Needed for RxAndroid */
+ implementation 'io.reactivex:rxandroid:1.2.1'
+ implementation 'io.reactivex:rxjava:1.3.0'
+
+ /* Needed for Rx Bindings on views */
+ implementation 'com.jakewharton.rxbinding:rxbinding:0.4.0'
+
+ /* Used for server calls */
+ implementation 'com.squareup.okio:okio:1.15.0'
+ implementation 'com.squareup.retrofit2:retrofit:2.5.0'
+
+ /* Used to make Retrofit easier and GSON & Rx-compatible*/
+ implementation 'com.google.code.gson:gson:2.8.5'
+ implementation 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
+ implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
+
+ /* Used to debug your Retrofit connections */
+ implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
+}
\ No newline at end of file
diff --git a/src/android/StripePaymentsPlugin.java b/src/android/StripePaymentsPlugin.java
new file mode 100644
index 0000000..1e65dc3
--- /dev/null
+++ b/src/android/StripePaymentsPlugin.java
@@ -0,0 +1,251 @@
+package com.rolamix.plugins.stripe;
+
+import java.util.ArrayList;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.support.annotation.NonNull;
+import android.os.Build;
+import com.google.gson.reflect.TypeToken;
+
+import com.google.android.gms.common.api.ApiException;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.tasks.Task;
+import com.google.android.gms.wallet.AutoResolveHelper;
+import com.google.android.gms.wallet.CardRequirements;
+import com.google.android.gms.wallet.IsReadyToPayRequest;
+import com.google.android.gms.wallet.PaymentData;
+import com.google.android.gms.wallet.PaymentDataRequest;
+import com.google.android.gms.wallet.PaymentMethodTokenizationParameters;
+import com.google.android.gms.wallet.PaymentsClient;
+import com.google.android.gms.wallet.TransactionInfo;
+import com.google.android.gms.wallet.Wallet;
+import com.google.android.gms.wallet.WalletConstants;
+import com.stripe.android.CardUtils;
+import com.stripe.android.SourceCallback;
+import com.stripe.android.Stripe;
+import com.stripe.android.TokenCallback;
+import com.stripe.android.model.AccountParams;
+import com.stripe.android.model.BankAccount;
+import com.stripe.android.model.Card;
+import com.stripe.android.model.Source;
+import com.stripe.android.model.SourceParams;
+import com.stripe.android.model.Token;
+import com.stripe.android.view.CardInputWidget;
+
+// https://stripe.com/docs/mobile/android
+// https://github.com/stripe/stripe-android
+// https://github.com/zyra/cordova-plugin-stripe/blob/v2/src/android/CordovaStripe.java
+// https://github.com/stripe/stripe-connect-rocketrides/blob/master/server/routes/api/rides.js
+
+public class StripePaymentsPlugin extends CordovaPlugin {
+
+ private CallbackContext callbackContext;
+
+ private String publishableKey;
+
+ private static final int LOAD_PAYMENT_DATA_REQUEST_CODE = 9972;
+
+ public static final String ACTION_SET_KEY = "setKey";
+
+ public static final String ACTION_SET_NAME = "setName";
+
+ public static final String ACTION_PICK = "pick";
+
+ public static final String ACTION_PICK_AND_STORE = "pickAndStore";
+
+ public static final String ACTION_HAS_PERMISSION = "hasPermission";
+
+ private static final String LOG_TAG = "FileStackPlugin";
+
+ public StripePaymentsPlugin() {}
+
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+ super.initialize(cordova, webView);
+ stripeInstance = new Stripe(webView.getContext());
+ }
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArray of arguments for the plugin.
+ * @param callbackContext The callback context used when calling back into JavaScript.
+ * @return True if the action was valid, false otherwise.
+ */
+ public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ this.callbackContext = callbackContext;
+ this.executeArgs = args;
+ this.action = action;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || action.equals(ACTION_HAS_PERMISSION)) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, hasPermission()));
+ return true;
+ }
+ else {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || action.equals(ACTION_SET_KEY) || action.equals(ACTION_SET_NAME)) {
+ execute();
+ return true;
+ }
+ else {
+ if (hasPermission()) {
+ execute();
+ } else {
+ requestPermission();
+ }
+ return true;
+ }
+ }
+ }
+
+ private boolean hasPermission() {
+ return cordova.hasPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+
+ private void requestPermission() {
+ cordova.requestPermission(this, 0, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+
+ public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException {
+ for (int r : grantResults) {
+ if (r == PackageManager.PERMISSION_DENIED) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "User has denied permission"));
+ return;
+ }
+ }
+ execute();
+ }
+
+ public void execute() {
+ final FileStackPlugin cdvPlugin = this;
+ this.cordova.getThreadPool().execute(() -> {
+ try {
+ if (ACTION_SET_KEY.equals(cdvPlugin.getAction())) {
+ this.apiKey = cdvPlugin.getArgs().getString(0);
+ return;
+ }
+
+ Context context = cordova.getActivity().getApplicationContext();
+ Intent intent = new Intent(context, FsActivity.class);
+ Config config = new Config(this.apiKey);
+ intent.putExtra(FsConstants.EXTRA_CONFIG, config);
+ intent.putExtra(FsConstants.EXTRA_AUTO_UPLOAD, true);
+ if (ACTION_PICK.equals(cdvPlugin.getAction()) || ACTION_PICK_AND_STORE.equals(cdvPlugin.getAction())) {
+ parseGlobalArgs(intent, cdvPlugin.getArgs());
+ if (ACTION_PICK_AND_STORE.equals(cdvPlugin.getAction())) {
+ parseStoreArgs(intent, cdvPlugin.getArgs());
+ }
+ cordova.startActivityForResult(cdvPlugin, intent, REQUEST_FILESTACK);
+ }
+ }
+ catch(JSONException exception) {
+ cdvPlugin.getCallbackContext().error("cannot parse json");
+ }
+ });
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_FILESTACK) {
+ if (resultCode == Activity.RESULT_OK) {
+ ArrayList selections = data.getParcelableArrayListExtra(FsConstants.EXTRA_SELECTION_LIST);
+ try{
+ callbackContext.success(toJSON(selections));
+ }
+ catch(JSONException exception) {
+ callbackContext.error("json exception");
+ }
+ } else {
+ callbackContext.error("nok");
+ }
+ }
+ else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ public void parseGlobalArgs(Intent intent, JSONArray args) throws JSONException {
+ if (!args.isNull(0)) {
+ intent.putExtra("mimetype", parseJSONStringArray(args.getJSONArray(0)));
+ }
+ if (!args.isNull(1)) {
+ intent.putExtra("services", parseJSONStringArray(args.getJSONArray(1)));
+ }
+ if (!args.isNull(2)) {
+ intent.putExtra("multiple", args.getBoolean(2));
+ }
+ if (!args.isNull(3)) {
+ intent.putExtra("maxFiles", args.getInt(3));
+ }
+ if (!args.isNull(4)) {
+ intent.putExtra("maxSize", args.getInt(4));
+ }
+ }
+
+ public void parseStoreArgs(Intent intent, JSONArray args) throws JSONException {
+ if (!args.isNull(5)) {
+ intent.putExtra("location", args.getString(5));
+ }
+ if (!args.isNull(6)) {
+ intent.putExtra("path", args.getString(6));
+ }
+ if (!args.isNull(7)) {
+ intent.putExtra("container", args.getString(7));
+ }
+ if (!args.isNull(8)) {
+ intent.putExtra("access", args.getString(8));
+ }
+ }
+
+ public String[] parseJSONStringArray(JSONArray jSONArray) throws JSONException {
+ String[] a = new String[jSONArray.length()];
+ for(int i = 0; i < jSONArray.length(); i++){
+ a[i] = jSONArray.getString(i);
+ }
+ return a;
+ }
+
+ public JSONArray toJSON(ArrayList selections) throws JSONException {
+ JSONArray res = new JSONArray();
+ for (Selection selection : selections) {
+ JSONObject f = new JSONObject();
+ f.put("provider", selection.getProvider());
+ f.put("url", selection.getUri());
+ f.put("filename", selection.getName());
+ f.put("mimetype", selection.getMimeType());
+ f.put("localPath", selection.getPath());
+ f.put("size", selection.getSize());
+
+ res.put(f);
+ }
+ return res;
+ }
+
+ public String getAction() {
+ return this.action;
+ }
+
+ public JSONArray getArgs() {
+ return this.executeArgs;
+ }
+
+ public CallbackContext getCallbackContext() {
+ return this.callbackContext;
+ }
+}
diff --git a/src/android/service/EphemeralKeyProvider.java b/src/android/service/EphemeralKeyProvider.java
new file mode 100644
index 0000000..fef93bc
--- /dev/null
+++ b/src/android/service/EphemeralKeyProvider.java
@@ -0,0 +1,70 @@
+package com.stripe.example.service;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Size;
+
+import com.stripe.android.EphemeralKeyProvider;
+import com.stripe.android.EphemeralKeyUpdateListener;
+import com.stripe.example.module.RetrofitFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import okhttp3.ResponseBody;
+import retrofit2.Retrofit;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.functions.Action1;
+import rx.schedulers.Schedulers;
+import rx.subscriptions.CompositeSubscription;
+
+/**
+ * An implementation of {@link EphemeralKeyProvider} that can be used to generate
+ * ephemeral keys on the backend.
+ */
+public class ExampleEphemeralKeyProvider implements EphemeralKeyProvider {
+
+ private @NonNull CompositeSubscription mCompositeSubscription;
+ private @NonNull StripeService mStripeService;
+ private @NonNull ProgressListener mProgressListener;
+
+ public ExampleEphemeralKeyProvider(@NonNull ProgressListener progressListener) {
+ Retrofit retrofit = RetrofitFactory.getInstance();
+ mStripeService = retrofit.create(StripeService.class);
+ mCompositeSubscription = new CompositeSubscription();
+ mProgressListener = progressListener;
+ }
+
+ @Override
+ public void createEphemeralKey(@NonNull @Size(min = 4) String apiVersion,
+ @NonNull final EphemeralKeyUpdateListener keyUpdateListener) {
+ Map apiParamMap = new HashMap<>();
+ apiParamMap.put("api_version", apiVersion);
+
+ mCompositeSubscription.add(
+ mStripeService.createEphemeralKey(apiParamMap)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Action1() {
+ @Override
+ public void call(ResponseBody response) {
+ try {
+ String rawKey = response.string();
+ keyUpdateListener.onKeyUpdate(rawKey);
+ mProgressListener.onStringResponse(rawKey);
+ } catch (IOException iox) {
+
+ }
+ }
+ }, new Action1() {
+ @Override
+ public void call(Throwable throwable) {
+ mProgressListener.onStringResponse(throwable.getMessage());
+ }
+ }));
+ }
+
+ public interface ProgressListener {
+ void onStringResponse(String string);
+ }
+}
\ No newline at end of file
diff --git a/src/android/service/TokenIntentService.java b/src/android/service/TokenIntentService.java
new file mode 100644
index 0000000..83ceb97
--- /dev/null
+++ b/src/android/service/TokenIntentService.java
@@ -0,0 +1,83 @@
+package com.stripe.example.service;
+
+import android.app.Activity;
+import android.app.IntentService;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.LocalBroadcastManager;
+
+import com.stripe.android.PaymentConfiguration;
+import com.stripe.android.Stripe;
+import com.stripe.android.exception.StripeException;
+import com.stripe.android.model.Card;
+import com.stripe.android.model.Token;
+
+/**
+ * An {@link IntentService} subclass for handling the creation of a {@link Token} from
+ * input {@link Card} information.
+ */
+public class TokenIntentService extends IntentService {
+
+ public static final String TOKEN_ACTION = "com.stripe.example.service.tokenAction";
+ public static final String STRIPE_CARD_LAST_FOUR = "com.stripe.example.service.cardLastFour";
+ public static final String STRIPE_CARD_TOKEN_ID = "com.stripe.example.service.cardTokenId";
+ public static final String STRIPE_ERROR_MESSAGE = "com.stripe.example.service.errorMessage";
+
+ private static final String EXTRA_CARD_NUMBER = "com.stripe.example.service.extra.cardNumber";
+ private static final String EXTRA_MONTH = "com.stripe.example.service.extra.month";
+ private static final String EXTRA_YEAR = "com.stripe.example.service.extra.year";
+ private static final String EXTRA_CVC = "com.stripe.example.service.extra.cvc";
+
+ public static Intent createTokenIntent(
+ @NonNull Activity launchingActivity,
+ @Nullable String cardNumber,
+ @Nullable Integer month,
+ @Nullable Integer year,
+ @Nullable String cvc) {
+ return new Intent(launchingActivity, TokenIntentService.class)
+ .putExtra(EXTRA_CARD_NUMBER, cardNumber)
+ .putExtra(EXTRA_MONTH, month)
+ .putExtra(EXTRA_YEAR, year)
+ .putExtra(EXTRA_CVC, cvc);
+ }
+
+ public TokenIntentService() {
+ super("TokenIntentService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ String errorMessage = null;
+ Token token = null;
+ if (intent != null) {
+ final String cardNumber = intent.getStringExtra(EXTRA_CARD_NUMBER);
+ final Integer month = (Integer) intent.getExtras().get(EXTRA_MONTH);
+ final Integer year = (Integer) intent.getExtras().get(EXTRA_YEAR);
+ final String cvc = intent.getStringExtra(EXTRA_CVC);
+
+ final Card card = new Card(cardNumber, month, year, cvc);
+
+ final Stripe stripe = new Stripe(getApplicationContext());
+ try {
+ token = stripe.createTokenSynchronous(card,
+ PaymentConfiguration.getInstance().getPublishableKey());
+ } catch (StripeException stripeEx) {
+ errorMessage = stripeEx.getLocalizedMessage();
+ }
+ }
+
+ final Intent localIntent = new Intent(TOKEN_ACTION);
+ if (token != null) {
+ localIntent.putExtra(STRIPE_CARD_LAST_FOUR, token.getCard().getLast4());
+ localIntent.putExtra(STRIPE_CARD_TOKEN_ID, token.getId());
+ }
+
+ if (errorMessage != null) {
+ localIntent.putExtra(STRIPE_ERROR_MESSAGE, errorMessage);
+ }
+
+ // Broadcasts the Intent to receivers in this app.
+ LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
+ }
+}
diff --git a/src/android/xml/activity_layout.xml b/src/android/xml/activity_layout.xml
new file mode 100644
index 0000000..d3b2450
--- /dev/null
+++ b/src/android/xml/activity_layout.xml
@@ -0,0 +1,5 @@
+
\ No newline at end of file