feat(android): add android implementation
This commit is contained in:
parent
27e83b8149
commit
96d1d9177c
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cordova-plugin-stripe-payments",
|
||||
"description": "Stripe Card Entry plugin for Cordova. Available for Android and iOS.",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"homepage": "https://github.com/rolamix/cordova-plugin-stripe-payments#readme",
|
||||
"author": "Rolamix <contact@rolamix.com> (https://rolamix.com)",
|
||||
"license": "MIT",
|
||||
|
25
plugin.xml
25
plugin.xml
@ -31,9 +31,9 @@
|
||||
|
||||
<!-- Android -->
|
||||
<platform name="android">
|
||||
<config-file parent="/*/application" target="AndroidManifest.xml">
|
||||
<meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true" />
|
||||
</config-file>
|
||||
<!-- <config-file parent="/*/application" target="AndroidManifest.xml">
|
||||
<meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true" />
|
||||
</config-file> -->
|
||||
|
||||
<config-file target="res/xml/config.xml" parent="/*">
|
||||
<feature name="StripePaymentsPlugin">
|
||||
@ -41,8 +41,25 @@
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/android/StripePaymentsPlugin.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
<config-file target="AndroidManifest.xml" parent="/manifest/application/activity[@android:name='MainActivity']">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:host="stripe3ds" android:scheme="stripe"/>
|
||||
</intent-filter>
|
||||
</config-file>
|
||||
|
||||
<framework src="com.stripe:stripe-android:8.7.0" />
|
||||
<framework src="src/android/StripePaymentsPlugin.gradle" custom="true" type="gradleReference" />
|
||||
|
||||
<source-file src="src/android/RetrofitFactory.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
<source-file src="src/android/StripePaymentConfig.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
<source-file src="src/android/StripePaymentsPlugin.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
<source-file src="src/android/StripePluginConfig.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
<source-file src="src/android/StripePluginEphemeralKeyProvider.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
<source-file src="src/android/StripePluginUtils.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
<source-file src="src/android/StripeService.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
</platform>
|
||||
|
||||
|
||||
|
48
src/android/RetrofitFactory.java
Normal file
48
src/android/RetrofitFactory.java
Normal file
@ -0,0 +1,48 @@
|
||||
package com.rolamix.plugins.stripe;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
/**
|
||||
* Factory instance to keep our Retrofit instance.
|
||||
*/
|
||||
public class RetrofitFactory {
|
||||
|
||||
// This is not used in this implementation, but is required by Retrofit.
|
||||
// We give the api calls the full URL so that we don't require any specific URL format.
|
||||
private static final String BASE_URL = "https://nowhere.com";
|
||||
private static Retrofit mInstance = null;
|
||||
|
||||
public static Retrofit getInstance() {
|
||||
if (mInstance == null) {
|
||||
|
||||
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
|
||||
// Set your desired log level. Use Level.BODY for debugging errors.
|
||||
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
|
||||
|
||||
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
|
||||
httpClient.addInterceptor(logging);
|
||||
|
||||
Gson gson = new GsonBuilder()
|
||||
.setLenient()
|
||||
.create();
|
||||
|
||||
// Adding Rx so the calls can be Observable, and adding a Gson converter with
|
||||
// leniency to make parsing the results simple.
|
||||
mInstance = new Retrofit.Builder()
|
||||
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.baseUrl(BASE_URL)
|
||||
.client(httpClient.build())
|
||||
.build();
|
||||
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
}
|
20
src/android/StripePaymentConfig.java
Normal file
20
src/android/StripePaymentConfig.java
Normal file
@ -0,0 +1,20 @@
|
||||
package com.rolamix.plugins.stripe;
|
||||
|
||||
public class StripePaymentConfig {
|
||||
private static final StripePaymentConfig mInstance = new StripePaymentConfig();
|
||||
|
||||
public static StripePaymentConfig getInstance() {
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
public Long price = 0L;
|
||||
public String currency = "USD";
|
||||
public String country = "US";
|
||||
|
||||
public boolean validate() {
|
||||
return price >= 0 && !currency.isEmpty() && !country.isEmpty();
|
||||
}
|
||||
|
||||
private StripePaymentConfig() {
|
||||
}
|
||||
}
|
@ -1,27 +1,29 @@
|
||||
dependencies {
|
||||
implementation 'com.stripe:stripe-android:8.5.0'
|
||||
compile 'com.google.android.gms:play-services-wallet:16.0.1'
|
||||
implementation 'com.stripe:stripe-android:8.7.0'
|
||||
// Not integrating Google Pay just yet due to Google's requirements for launching
|
||||
// We can do this later.
|
||||
// implementation '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.+'
|
||||
compile 'com.android.support:appcompat-v7:28.+'
|
||||
/* Cordova doesn't support AndroidX support libraries yet */
|
||||
implementation 'com.android.support:support-v4:28.+'
|
||||
implementation 'com.android.support:appcompat-v7:28.+'
|
||||
|
||||
/* Needed for RxAndroid */
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
implementation 'io.reactivex:rxjava:1.3.0'
|
||||
/* Needed for RxAndroid */
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
implementation 'io.reactivex:rxjava:1.3.8'
|
||||
|
||||
/* Needed for Rx Bindings on views */
|
||||
implementation 'com.jakewharton.rxbinding:rxbinding:0.4.0'
|
||||
/* Needed for Rx Bindings on views */
|
||||
implementation 'com.jakewharton.rxbinding:rxbinding:1.0.1'
|
||||
|
||||
/* Used for server calls */
|
||||
implementation 'com.squareup.okio:okio:1.15.0'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
|
||||
/* Used for server calls */
|
||||
implementation 'com.squareup.okio:okio:2.2.2'
|
||||
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 make Retrofit easier and GSON & Rx-compatible*/
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'com.squareup.retrofit2:adapter-rxjava:2.5.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'
|
||||
}
|
||||
/* Used to debug your Retrofit connections */
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
|
||||
}
|
||||
|
@ -1,85 +1,126 @@
|
||||
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 java.util.concurrent.Callable;
|
||||
|
||||
import org.apache.cordova.CordovaActivity;
|
||||
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.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
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.CardUtils;
|
||||
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;
|
||||
import com.stripe.android.model.Customer;
|
||||
import com.stripe.android.model.CustomerSource;
|
||||
import com.stripe.android.model.Source;
|
||||
import com.stripe.android.model.SourceCardData;
|
||||
import com.stripe.android.Stripe;
|
||||
import com.stripe.android.StripeError;
|
||||
import com.stripe.android.CustomerSession;
|
||||
import com.stripe.android.PaymentConfiguration;
|
||||
import com.stripe.android.PaymentResultListener;
|
||||
import com.stripe.android.PaymentSession;
|
||||
import com.stripe.android.PaymentSessionConfig;
|
||||
import com.stripe.android.PaymentSessionData;
|
||||
|
||||
// https://stripe.com/docs/mobile/android
|
||||
import rx.Observable;
|
||||
import rx.android.schedulers.AndroidSchedulers;
|
||||
import rx.functions.Action1;
|
||||
import rx.schedulers.Schedulers;
|
||||
import rx.subscriptions.CompositeSubscription;
|
||||
|
||||
|
||||
// https://stripe.com/docs/mobile/android/standard
|
||||
// https://github.com/stripe/stripe-android
|
||||
// https://github.com/stripe/stripe-android/blob/master/example/
|
||||
// https://github.com/stripe/stripe-android/tree/master/samplestore/
|
||||
// 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
|
||||
// Integrating Google Pay.
|
||||
// https://developers.google.com/pay/api/android/overview
|
||||
// https://stripe.com/docs/mobile/android/google-pay
|
||||
// https://github.com/jack828/cordova-plugin-stripe-google-apple-pay
|
||||
|
||||
public class StripePaymentsPlugin extends CordovaPlugin {
|
||||
|
||||
private CallbackContext callbackContext;
|
||||
private static final String LOG_TAG = "StripePaymentsPlugin";
|
||||
|
||||
private String publishableKey;
|
||||
@NonNull private final CompositeSubscription mCompositeSubscription = new CompositeSubscription();
|
||||
private CallbackContext paymentStatusCallback;
|
||||
private PaymentSession mPaymentSession;
|
||||
private Stripe stripeInstance;
|
||||
private Source mRedirectSource; // used for 3DS verifications
|
||||
|
||||
private static final int LOAD_PAYMENT_DATA_REQUEST_CODE = 9972;
|
||||
private static final String ACTION_INIT_PLUGIN = "beginStripe";
|
||||
private static final String ACTION_ADD_STATUS_OBSERVER = "addPaymentStatusObserver";
|
||||
private static final String ACTION_SHOW_PAYMENT_DIALOG = "showPaymentDialog";
|
||||
private static final String ACTION_REQUEST_PAYMENT = "requestPayment";
|
||||
|
||||
public static final String ACTION_SET_KEY = "setKey";
|
||||
private static final String RETURN_SCHEMA = "stripe://";
|
||||
private static final String RETURN_HOST_SYNC = "stripe3ds"; // matches the value in plugin.xml
|
||||
private static final String QUERY_CLIENT_SECRET = "client_secret";
|
||||
private static final String QUERY_SOURCE_ID = "source";
|
||||
|
||||
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());
|
||||
@Override()
|
||||
protected void pluginInitialize() {
|
||||
super.pluginInitialize();
|
||||
stripeInstance = new Stripe(this.cordova.getActivity().getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
mPaymentSession.handlePaymentData(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
if (intent.getData() != null && intent.getData().getQuery() != null) {
|
||||
// The client secret and source ID found here is identical to
|
||||
// that of the source used to get the redirect URL.
|
||||
String clientSecret = intent.getData().getQueryParameter(QUERY_CLIENT_SECRET);
|
||||
String sourceId = intent.getData().getQueryParameter(QUERY_SOURCE_ID);
|
||||
|
||||
if (clientSecret != null
|
||||
&& sourceId != null
|
||||
&& clientSecret.equals(mRedirectSource.getClientSecret())
|
||||
&& sourceId.equals(mRedirectSource.getId())) {
|
||||
|
||||
Log.i(LOG_TAG, "[StripePaymentsPlugin].requestPayment 3DS source verified:" + mRedirectSource.getId());
|
||||
HashMap<String, Object> message = new HashMap<>();
|
||||
message.put("status", "PAYMENT_CREATED");
|
||||
message.put("source", mRedirectSource.getId());
|
||||
successCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
mRedirectSource = null;
|
||||
}
|
||||
// if we had a progress dialog, we'd dismiss it here.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mPaymentSession.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,161 +132,386 @@ public class StripePaymentsPlugin extends CordovaPlugin {
|
||||
* @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;
|
||||
|
||||
switch (action) {
|
||||
case ACTION_INIT_PLUGIN:
|
||||
args.optJSONObject(0);
|
||||
initPluginConfig(args.getJSONObject(0), callbackContext);
|
||||
break;
|
||||
|
||||
case ACTION_ADD_STATUS_OBSERVER:
|
||||
addStatusObserver(callbackContext);
|
||||
break;
|
||||
|
||||
case ACTION_SHOW_PAYMENT_DIALOG:
|
||||
showPaymentDialog(args.getJSONObject(0), callbackContext);
|
||||
break;
|
||||
|
||||
case ACTION_REQUEST_PAYMENT:
|
||||
requestPayment(callbackContext);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void initPluginConfig(JSONObject pluginConfig, CallbackContext callbackContext) {
|
||||
HashMap<String, Object> message = new HashMap<>();
|
||||
message.put("status", "INIT_ERROR");
|
||||
message.put("error", "[CONFIG]: The Stripe Publishable Key and ephemeral key generation URL are required");
|
||||
|
||||
if (pluginConfig == null || pluginConfig.length() == 0) {
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasPermission() {
|
||||
return cordova.hasPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
}
|
||||
StripePluginConfig.getInstance().publishableKey = pluginConfig.optString("publishableKey", "");
|
||||
StripePluginConfig.getInstance().ephemeralKeyUrl = pluginConfig.optString("ephemeralKeyUrl", "");
|
||||
StripePluginConfig.getInstance().companyName = pluginConfig.optString("companyName", "");
|
||||
|
||||
private void requestPermission() {
|
||||
cordova.requestPermission(this, 0, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
}
|
||||
JSONObject headers = pluginConfig.optJSONObject("extraHTTPHeaders");
|
||||
StripePluginConfig.getInstance().extraHTTPHeaders = StripePluginUtils.parseExtraHeaders(headers, new HashMap<>());
|
||||
|
||||
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;
|
||||
}
|
||||
if (!StripePluginConfig.getInstance().validate()) {
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
return;
|
||||
}
|
||||
execute();
|
||||
|
||||
PaymentConfiguration.init(StripePluginConfig.getInstance().publishableKey);
|
||||
stripeInstance.setDefaultPublishableKey(StripePluginConfig.getInstance().publishableKey);
|
||||
|
||||
message.put("status", "INIT_SUCCESS");
|
||||
message.remove("error");
|
||||
successCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
public void addStatusObserver(CallbackContext callbackContext) {
|
||||
paymentStatusCallback = callbackContext;
|
||||
|
||||
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());
|
||||
HashMap<String, Object> message = new HashMap<>();
|
||||
message.put("status", "LISTENER_ADDED");
|
||||
successCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
}
|
||||
|
||||
public void showPaymentDialog(JSONObject paymentConfig, CallbackContext callbackContext) {
|
||||
HashMap<String, Object> message = new HashMap<>();
|
||||
message.put("status", "PAYMENT_DIALOG_ERROR");
|
||||
message.put("error", "[CONFIG]: Error parsing payment options or they were not provided");
|
||||
|
||||
if (paymentConfig == null || paymentConfig.length() == 0) {
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!StripePluginConfig.getInstance().validate()) {
|
||||
message.put("error", "[CONFIG]: Config is not set, init() must be called before using plugin");
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
}
|
||||
|
||||
JSONObject headers = paymentConfig.optJSONObject("extraHTTPHeaders");
|
||||
StripePluginConfig.getInstance().extraHTTPHeaders = StripePluginUtils.parseExtraHeaders(headers, new HashMap<>());
|
||||
|
||||
setupCustomerSession();
|
||||
setupPaymentSession();
|
||||
|
||||
StripePaymentConfig.getInstance().price = paymentConfig.optLong("price", 0L);
|
||||
StripePaymentConfig.getInstance().currency = paymentConfig.optString("currency", "USD");
|
||||
StripePaymentConfig.getInstance().country = paymentConfig.optString("country", "US");
|
||||
|
||||
mPaymentSession.setCartTotal(StripePaymentConfig.getInstance().price);
|
||||
mPaymentSession.presentPaymentMethodSelection();
|
||||
|
||||
message.clear();
|
||||
message.put("status", "PAYMENT_DIALOG_SHOWN");
|
||||
successCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
}
|
||||
|
||||
// Android does in 1 step what requires 2 steps on iOS. Android saves the payment method
|
||||
// to the customer as soon as one is entered; on iOS the source is not created until AFTER
|
||||
// you requestPayment from the payment context (requiring the 2nd step).
|
||||
// However, Android still requires verifying 3DSecure so we will try to do that here.
|
||||
public void requestPayment(CallbackContext callbackContext) {
|
||||
HashMap<String, Object> message = new HashMap<>();
|
||||
message.put("status", "REQUEST_PAYMENT_ERROR");
|
||||
message.put("error", "[CONFIG]: Config is not set, init() must be called before using plugin");
|
||||
|
||||
if (!StripePluginConfig.getInstance().validate()) {
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
}
|
||||
|
||||
if (mPaymentSession == null) {
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
return;
|
||||
}
|
||||
|
||||
PaymentSessionData data = mPaymentSession.getPaymentSessionData();
|
||||
final String selectedPaymentMethodId = data.getSelectedPaymentMethodId();
|
||||
|
||||
if (data.isPaymentReadyToCharge() && data.getPaymentResult() == PaymentResultListener.INCOMPLETE && selectedPaymentMethodId != null) {
|
||||
CustomerSession.getInstance().retrieveCurrentCustomer(
|
||||
new CustomerSession.CustomerRetrievalListener() {
|
||||
@Override
|
||||
public void onCustomerRetrieved(@NonNull Customer customer) {
|
||||
CustomerSource source = customer.getSourceById(selectedPaymentMethodId);
|
||||
if (source == null) {
|
||||
message.put("error", "Error: No valid payment source is available to complete payment");
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
return;
|
||||
}
|
||||
|
||||
Source src = source.asSource();
|
||||
if (src == null) {
|
||||
message.put("error", "Error: No valid payment source is available to complete payment");
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
return;
|
||||
}
|
||||
|
||||
String sourceType = src.getType();
|
||||
if (Source.CARD.equals(sourceType)) {
|
||||
// Before we complete, we need to check if this transaction requires 3DSecure
|
||||
SourceCardData cardData = (SourceCardData) src.getSourceTypeModel();
|
||||
if (SourceCardData.REQUIRED.equals(cardData.getThreeDSecureStatus())) {
|
||||
// In this case, you would need to ask the user to verify the purchase.
|
||||
createThreeDSecureSource(src.getId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Either this is not a card, and it's Stripe's job to return the source;
|
||||
// or it is a card, and 3DS is not required. In either case we can immediately
|
||||
// return the Source for charging.
|
||||
Log.i(LOG_TAG, "[StripePaymentsPlugin].requestPayment source retrieved:" + src.getId());
|
||||
message.put("status", "PAYMENT_CREATED");
|
||||
message.remove("error");
|
||||
message.put("source", src.getId());
|
||||
successCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
}
|
||||
cordova.startActivityForResult(cdvPlugin, intent, REQUEST_FILESTACK);
|
||||
|
||||
@Override
|
||||
public void onError(int httpCode, @Nullable String errorMessage, @Nullable StripeError stripeError) {
|
||||
displayError(errorMessage);
|
||||
message.put("error", errorMessage);
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
message.put("error", "Error: No valid payment source is available to complete payment");
|
||||
errorCallback(callbackContext, StripePluginUtils.mapToJSON(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Implementation methods
|
||||
*
|
||||
*/
|
||||
|
||||
private void setupCustomerSession() {
|
||||
// CustomerSession only needs to be initialized once per app.
|
||||
CustomerSession.initCustomerSession(
|
||||
new StripePluginEphemeralKeyProvider(
|
||||
new StripePluginEphemeralKeyProvider.ProgressListener() {
|
||||
@Override
|
||||
public void onStringResponse(@NonNull String string) {
|
||||
if (string.startsWith("Error: ")) {
|
||||
new AlertDialog.Builder(getApplicationContext())
|
||||
.setMessage(string)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void setupPaymentSession() {
|
||||
mPaymentSession = new PaymentSession(getActivity());
|
||||
|
||||
mPaymentSession.init(new PaymentSession.PaymentSessionListener() {
|
||||
@Override
|
||||
public void onCommunicatingStateChanged(boolean isCommunicating) { }
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, @Nullable String errorMessage) {
|
||||
HashMap<String, Object> message = new HashMap<>();
|
||||
message.put("status", "PAYMENT_STATUS_ERROR");
|
||||
message.put("error", errorMessage);
|
||||
errorCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
displayError(errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaymentSessionDataChanged(@NonNull PaymentSessionData data) {
|
||||
HashMap<String, Object> message = new HashMap<>();
|
||||
message.put("status", "PAYMENT_STATUS_ERROR");
|
||||
|
||||
final String selectedPaymentMethodId = data.getSelectedPaymentMethodId();
|
||||
|
||||
if (selectedPaymentMethodId != null) {
|
||||
CustomerSession.getInstance().retrieveCurrentCustomer(
|
||||
new CustomerSession.CustomerRetrievalListener() {
|
||||
@Override
|
||||
public void onCustomerRetrieved(@NonNull Customer customer) {
|
||||
// This is how you'd do it if you wanted to use the Customer's default source.
|
||||
// However, we want to use the one they selected in the dialog.
|
||||
// String sourceId = customer.getDefaultSource();
|
||||
// if (sourceId == null) { return; }
|
||||
CustomerSource source = customer.getSourceById(selectedPaymentMethodId);
|
||||
|
||||
if (source == null) {
|
||||
message.put("error", "Error: No valid payment source is available to complete payment");
|
||||
errorCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
return;
|
||||
}
|
||||
|
||||
Source src = source.asSource();
|
||||
if (src == null) {
|
||||
message.put("error", "Error: No valid payment source is available to complete payment");
|
||||
errorCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Report if this transaction requires 3DSecure so that client has an opportunity
|
||||
// to prompt the user to verify.
|
||||
SourceCardData cardData = (SourceCardData) src.getSourceTypeModel();
|
||||
boolean is3ds = SourceCardData.REQUIRED.equals(cardData.getThreeDSecureStatus());
|
||||
String sourceId = src.getId();
|
||||
|
||||
message.put("status", "PAYMENT_STATUS_CHANGED");
|
||||
message.put("isPaymentReady", data.isPaymentReadyToCharge());
|
||||
message.put("isLoading", !data.isPaymentReadyToCharge());
|
||||
message.put("label", StripePluginUtils.formatSourceDescription(src));
|
||||
message.put("image", null); // Not supported on this platform... yet.
|
||||
message.put("is3DSRequired", is3ds);
|
||||
message.put("source", sourceId);
|
||||
|
||||
successCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int httpCode, @Nullable String errorMessage, @Nullable StripeError stripeError) {
|
||||
displayError(errorMessage);
|
||||
message.put("error", errorMessage);
|
||||
errorCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
message.put("status", "PAYMENT_STATUS_CHANGED");
|
||||
message.put("isPaymentReady", false);
|
||||
message.put("isLoading", true);
|
||||
message.put("label", "");
|
||||
message.put("image", null); // Not supported on this platform... yet.
|
||||
successCallback(paymentStatusCallback, StripePluginUtils.mapToJSON(message), true);
|
||||
}
|
||||
}
|
||||
catch(JSONException exception) {
|
||||
cdvPlugin.getCallbackContext().error("cannot parse json");
|
||||
}
|
||||
});
|
||||
}, new PaymentSessionConfig.Builder()
|
||||
.setShippingInfoRequired(false)
|
||||
.setShippingMethodsRequired(false)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == REQUEST_FILESTACK) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
ArrayList<Selection> 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);
|
||||
}
|
||||
/**
|
||||
* Create the 3DS Source as a separate call to the API. This is what is needed
|
||||
* to verify the third-party approval. The only information from the Card source
|
||||
* that is used is the ID field.
|
||||
*
|
||||
* @param sourceId the {@link Source#mId} from the {@link Card}-created {@link Source}.
|
||||
*/
|
||||
void createThreeDSecureSource(String sourceId) {
|
||||
// This represents a request for a 3DS purchase.
|
||||
final SourceParams threeDParams = SourceParams.createThreeDSecureParams(
|
||||
StripePaymentConfig.getInstance().price,
|
||||
StripePaymentConfig.getInstance().currency,
|
||||
RETURN_SCHEMA + RETURN_HOST_SYNC,
|
||||
sourceId);
|
||||
|
||||
Observable<Source> threeDSecureObservable = Observable.fromCallable(
|
||||
new Callable<Source>() {
|
||||
@Override
|
||||
public Source call() throws Exception {
|
||||
return stripeInstance.createSourceSynchronous(
|
||||
threeDParams,
|
||||
PaymentConfiguration.getInstance().getPublishableKey());
|
||||
}
|
||||
});
|
||||
|
||||
mCompositeSubscription.add(threeDSecureObservable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
// Because we've made the mapping above, we're now subscribing
|
||||
// to the result of creating a 3DS Source
|
||||
new Action1<Source>() {
|
||||
@Override
|
||||
public void call(Source source) {
|
||||
// Once a 3DS Source is created, that is used
|
||||
// to initiate the third-party verification
|
||||
mRedirectSource = source;
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(source.getRedirect().getUrl()));
|
||||
getActivity().startActivity(browserIntent);
|
||||
}
|
||||
},
|
||||
new Action1<Throwable>() {
|
||||
@Override
|
||||
public void call(Throwable throwable) {
|
||||
displayError(throwable.getMessage());
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
private void displayError(String errorMessage) {
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(getApplicationContext()).create();
|
||||
alertDialog.setTitle("Error");
|
||||
alertDialog.setMessage(errorMessage);
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
private Context getContext() {
|
||||
return this.cordova.getContext();
|
||||
}
|
||||
|
||||
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;
|
||||
private Activity getActivity() {
|
||||
return this.cordova.getActivity();
|
||||
}
|
||||
|
||||
public JSONArray toJSON(ArrayList<Selection> 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;
|
||||
private Context getApplicationContext() {
|
||||
return this.getActivity().getApplicationContext();
|
||||
// Other useful lines
|
||||
// cordova.startActivityForResult(this, intent, REQUEST_SOMETHING);
|
||||
// this.cordova.getThreadPool().execute(() -> { });
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return this.action;
|
||||
private PluginResult successCallback(CallbackContext context, JSONObject message) {
|
||||
return successCallback(context, message, false);
|
||||
}
|
||||
|
||||
public JSONArray getArgs() {
|
||||
return this.executeArgs;
|
||||
private PluginResult successCallback(CallbackContext context, JSONObject message, boolean keepCallback) {
|
||||
PluginResult result = new PluginResult(PluginResult.Status.OK, message);
|
||||
result.setKeepCallback(keepCallback);
|
||||
context.sendPluginResult(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public CallbackContext getCallbackContext() {
|
||||
return this.callbackContext;
|
||||
private PluginResult errorCallback(CallbackContext context, JSONObject message) {
|
||||
return errorCallback(context, message, false);
|
||||
}
|
||||
|
||||
private PluginResult errorCallback(CallbackContext context, JSONObject message, boolean keepCallback) {
|
||||
PluginResult result = new PluginResult(PluginResult.Status.ERROR, message);
|
||||
result.setKeepCallback(keepCallback);
|
||||
context.sendPluginResult(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
24
src/android/StripePluginConfig.java
Normal file
24
src/android/StripePluginConfig.java
Normal file
@ -0,0 +1,24 @@
|
||||
package com.rolamix.plugins.stripe;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class StripePluginConfig {
|
||||
private static final StripePluginConfig mInstance = new StripePluginConfig();
|
||||
|
||||
public static StripePluginConfig getInstance() {
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
public String publishableKey = "";
|
||||
public String ephemeralKeyUrl = "";
|
||||
public String companyName = "";
|
||||
|
||||
public HashMap<String, String> extraHTTPHeaders = new HashMap<>();
|
||||
|
||||
public boolean validate() {
|
||||
return !publishableKey.isEmpty() && !ephemeralKeyUrl.isEmpty() && StripePluginUtils.validateStripeKey(publishableKey) >= 0;
|
||||
}
|
||||
|
||||
private StripePluginConfig() {
|
||||
}
|
||||
}
|
71
src/android/StripePluginEphemeralKeyProvider.java
Normal file
71
src/android/StripePluginEphemeralKeyProvider.java
Normal file
@ -0,0 +1,71 @@
|
||||
package com.rolamix.plugins.stripe;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Size;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.stripe.android.EphemeralKeyProvider;
|
||||
import com.stripe.android.EphemeralKeyUpdateListener;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Retrofit;
|
||||
import rx.android.schedulers.AndroidSchedulers;
|
||||
import rx.functions.Action1;
|
||||
import rx.schedulers.Schedulers;
|
||||
import rx.subscriptions.CompositeSubscription;
|
||||
|
||||
public class StripePluginEphemeralKeyProvider implements EphemeralKeyProvider {
|
||||
|
||||
@NonNull private final CompositeSubscription mCompositeSubscription;
|
||||
@NonNull private final StripeService mStripeService;
|
||||
@NonNull private final ProgressListener mProgressListener;
|
||||
|
||||
public StripePluginEphemeralKeyProvider(@NonNull ProgressListener progressListener) {
|
||||
final 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) {
|
||||
final Map<String, String> apiParamMap = new HashMap<>();
|
||||
apiParamMap.put("api_version", apiVersion);
|
||||
|
||||
final Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Accept", "application/json");
|
||||
headers.putAll(StripePluginConfig.getInstance().extraHTTPHeaders);
|
||||
|
||||
HttpUrl url = HttpUrl.get(StripePluginConfig.getInstance().ephemeralKeyUrl);
|
||||
|
||||
mCompositeSubscription.add(
|
||||
mStripeService.createEphemeralKey(url, apiParamMap, headers)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Action1<ResponseBody>() {
|
||||
@Override
|
||||
public void call(ResponseBody response) {
|
||||
try {
|
||||
String rawKey = response.string();
|
||||
keyUpdateListener.onKeyUpdate(rawKey);
|
||||
mProgressListener.onStringResponse(rawKey);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}, new Action1<Throwable>() {
|
||||
@Override
|
||||
public void call(Throwable throwable) {
|
||||
mProgressListener.onStringResponse("Error: " + throwable.getMessage());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public interface ProgressListener {
|
||||
void onStringResponse(@NonNull String string);
|
||||
}
|
||||
}
|
65
src/android/StripePluginUtils.java
Normal file
65
src/android/StripePluginUtils.java
Normal file
@ -0,0 +1,65 @@
|
||||
package com.rolamix.plugins.stripe;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import com.stripe.android.model.Source;
|
||||
import com.stripe.android.model.SourceCardData;
|
||||
|
||||
|
||||
public class StripePluginUtils {
|
||||
static final String LOG_TAG = "StripePluginUtils";
|
||||
|
||||
static int validateStripeKey(String stripeKey) {
|
||||
if (stripeKey.contains("pk_test")) {
|
||||
return 1;
|
||||
} else if (stripeKey.contains("pk_live")) {
|
||||
return 2;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static JSONObject mapToJSON(HashMap<String, Object> map) {
|
||||
JSONObject message = new JSONObject();
|
||||
for (Map.Entry<String, Object> pairs : map.entrySet()) {
|
||||
try {
|
||||
message.put(pairs.getKey(), pairs.getValue());
|
||||
} catch (JSONException e) { }
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
static HashMap<String, String> parseExtraHeaders(JSONObject headers, HashMap<String, String> fallback) {
|
||||
if (headers != null && headers.length() > 0) {
|
||||
HashMap<String, String> storedHeaders = new HashMap<>();
|
||||
Iterator<String> headerIterator = headers.keys();
|
||||
|
||||
while(headerIterator.hasNext()) {
|
||||
String key = headerIterator.next();
|
||||
String value = headers.optString(key, "");
|
||||
storedHeaders.put(key, value);
|
||||
Log.v(LOG_TAG, "Storing header:" + key + ", " + value);
|
||||
}
|
||||
|
||||
return storedHeaders;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
static String formatSourceDescription(Source source) {
|
||||
if (Source.CARD.equals(source.getType())) {
|
||||
final SourceCardData sourceCardData = (SourceCardData) source.getSourceTypeModel();
|
||||
return sourceCardData.getBrand() + " " + sourceCardData.getLast4();
|
||||
}
|
||||
return source.getType();
|
||||
}
|
||||
|
||||
}
|
22
src/android/StripeService.java
Normal file
22
src/android/StripeService.java
Normal file
@ -0,0 +1,22 @@
|
||||
package com.rolamix.plugins.stripe;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.http.FieldMap;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.HeaderMap;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Url;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* The {@link retrofit2.Retrofit} interface that creates our API service.
|
||||
*/
|
||||
public interface StripeService {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST()
|
||||
Observable<ResponseBody> createEphemeralKey(@Url() HttpUrl url, @FieldMap Map<String, String> apiVersionMap, @HeaderMap Map<String, String> headers);
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
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<String, String> apiParamMap = new HashMap<>();
|
||||
apiParamMap.put("api_version", apiVersion);
|
||||
|
||||
mCompositeSubscription.add(
|
||||
mStripeService.createEphemeralKey(apiParamMap)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Action1<ResponseBody>() {
|
||||
@Override
|
||||
public void call(ResponseBody response) {
|
||||
try {
|
||||
String rawKey = response.string();
|
||||
keyUpdateListener.onKeyUpdate(rawKey);
|
||||
mProgressListener.onStringResponse(rawKey);
|
||||
} catch (IOException iox) {
|
||||
|
||||
}
|
||||
}
|
||||
}, new Action1<Throwable>() {
|
||||
@Override
|
||||
public void call(Throwable throwable) {
|
||||
mProgressListener.onStringResponse(throwable.getMessage());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public interface ProgressListener {
|
||||
void onStringResponse(String string);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.stripe.example.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.http.FieldMap;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.POST;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* A Retrofit service used to communicate with a server.
|
||||
*/
|
||||
public interface StripeService {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("ephemeral_keys")
|
||||
Observable<ResponseBody> createEphemeralKey(@FieldMap Map<String, String> apiVersionMap);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("create_intent")
|
||||
Observable<ResponseBody> createPaymentIntent(@FieldMap Map<String, Object> params);
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
<com.stripe.android.view.CardInputWidget
|
||||
android:id="@+id/card_input_widget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
Loading…
x
Reference in New Issue
Block a user