Skip to content
This repository has been archived by the owner on Jan 3, 2024. It is now read-only.

Commit

Permalink
Merge pull request #345 from tipsi/feature/RNA-1485_common-error-codes
Browse files Browse the repository at this point in the history
[5.6.0] Common Error Codes
  • Loading branch information
igor-lemon committed Aug 22, 2018
2 parents 8124b8c + 2e5071a commit d9c8be0
Show file tree
Hide file tree
Showing 53 changed files with 577 additions and 180 deletions.
1 change: 1 addition & 0 deletions .npmignore
@@ -1,3 +1,4 @@
.github
.travis
docs
example
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog

## [5.6.0] - 2018-08-22
### Added
- [Common error codes](https://tipsi.github.io/tipsi-stripe/docs/errorcodes.html). Part of them provided by `tipsi-stripe`, another part by `Stripe` itself.

## [5.5.1] - 2018-08-10
### Added
- `paymentRequestWithAndroidPay` now supports _boolean_ `phone_number_required` field to ask a user for phone number
Expand Down
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -6,6 +6,12 @@
React Native Stripe binding for iOS/Android platforms

## Changelog
[[5.6.0] - 2018-08-22](/CHANGELOG.md#560---2018-08-22)

[[5.5.1] - 2018-08-10](/CHANGELOG.md#551---2018-08-10)

[[5.5.0] - 2018-08-08](/CHANGELOG.md#550---2018-08-08)

[[5.4.0] - 2018-07-27](/CHANGELOG.md#540---2018-07-27)

[[5.3.0] - 2018-07-23](/CHANGELOG.md#530---2018-07-23)
Expand Down
47 changes: 47 additions & 0 deletions android/src/main/java/com/gettipsi/stripe/Errors.java
@@ -0,0 +1,47 @@
package com.gettipsi.stripe;

import android.support.annotation.NonNull;

import com.facebook.react.bridge.ReadableMap;
import com.gettipsi.stripe.util.ArgCheck;

import java.util.HashMap;
import java.util.Map;

/**
* Created by ngoriachev on 30/07/2018.
*/

public final class Errors {

private static final Map<String, String> exceptionNameToErrorCode = new HashMap<>();

static {
exceptionNameToErrorCode.put("APIConnectionException", "apiConnection");
exceptionNameToErrorCode.put("StripeException", "stripe");
exceptionNameToErrorCode.put("CardException", "card");
exceptionNameToErrorCode.put("AuthenticationException", "authentication");
exceptionNameToErrorCode.put("PermissionException", "permission");
exceptionNameToErrorCode.put("InvalidRequestException", "invalidRequest");
exceptionNameToErrorCode.put("RateLimitException", "rateLimit");
exceptionNameToErrorCode.put("APIException", "api");
}

static String toErrorCode(@NonNull Exception exception) {
ArgCheck.nonNull(exception);
String simpleName = exception.getClass().getSimpleName();
String errorCode = exceptionNameToErrorCode.get(simpleName);
ArgCheck.nonNull(errorCode, simpleName);

return errorCode;
}

static String getErrorCode(@NonNull ReadableMap errorCodes, @NonNull String errorKey) {
return errorCodes.getMap(errorKey).getString("errorCode");
}

static String getDescription(@NonNull ReadableMap errorCodes, @NonNull String errorKey) {
return errorCodes.getMap(errorKey).getString("description");
}

}
Expand Up @@ -30,6 +30,7 @@
import java.util.Arrays;
import java.util.Collection;

import static com.gettipsi.stripe.Errors.toErrorCode;
import static com.gettipsi.stripe.util.Converters.convertTokenToWritableMap;
import static com.gettipsi.stripe.util.Converters.getAllowedShippingCountryCodes;
import static com.gettipsi.stripe.util.Converters.getBillingAddress;
Expand Down Expand Up @@ -81,7 +82,7 @@ public void onComplete(Task<Boolean> task) {
boolean result = task.getResult(ApiException.class);
promise.resolve(result);
} catch (ApiException exception) {
promise.reject(TAG, String.format("Error, statusCode: %d", exception.getStatusCode()));
promise.reject(toErrorCode(exception), exception.getMessage());
}
}
});
Expand Down Expand Up @@ -179,7 +180,10 @@ public void paymentRequestWithAndroidPay(@NonNull ReadableMap payParams, @NonNul

Activity activity = activityProvider.call();
if (activity == null) {
promise.reject(TAG, NO_CURRENT_ACTIVITY_MSG);
promise.reject(
getErrorCode("activityUnavailable"),
getErrorDescription("activityUnavailable")
);
return;
}

Expand All @@ -191,12 +195,18 @@ public void paymentRequestWithAndroidPay(@NonNull ReadableMap payParams, @NonNul
public void deviceSupportsAndroidPay(boolean isExistingPaymentMethodRequired, @NonNull Promise promise) {
Activity activity = activityProvider.call();
if (activity == null) {
promise.reject(TAG, NO_CURRENT_ACTIVITY_MSG);
promise.reject(
getErrorCode("activityUnavailable"),
getErrorDescription("activityUnavailable")
);
return;
}

if (!isPlayServicesAvailable(activity)) {
promise.reject(TAG, PLAY_SERVICES_ARE_NOT_AVAILABLE_MSG);
promise.reject(
getErrorCode("playServicesUnavailable"),
getErrorDescription("playServicesUnavailable")
);
return;
}

Expand All @@ -217,7 +227,10 @@ public boolean onActivityResult(Activity activity, int requestCode, int resultCo
String tokenJson = paymentData.getPaymentMethodToken().getToken();
Token token = Token.fromString(tokenJson);
if (token == null) {
payPromise.reject(TAG, JSON_PARSING_ERROR_MSG);
payPromise.reject(
getErrorCode("parseResponse"),
getErrorDescription("parseResponse")
);
} else {
payPromise.resolve(putExtraToTokenMap(
convertTokenToWritableMap(token),
Expand All @@ -226,14 +239,20 @@ public boolean onActivityResult(Activity activity, int requestCode, int resultCo
}
break;
case Activity.RESULT_CANCELED:
payPromise.reject(TAG, PURCHASE_CANCELLED_MSG);
payPromise.reject(
getErrorCode("purchaseCancelled"),
getErrorDescription("purchaseCancelled")
);
break;
case AutoResolveHelper.RESULT_ERROR:
Status status = AutoResolveHelper.getStatusFromIntent(data);
// Log the status for debugging.
// Generally, there is no need to show an error to
// the user as the Google Pay API will do that.
payPromise.reject(TAG, status.getStatusMessage());
payPromise.reject(
getErrorCode("stripe"),
status.getStatusMessage()
);
break;

default:
Expand Down
29 changes: 19 additions & 10 deletions android/src/main/java/com/gettipsi/stripe/PayFlow.java
Expand Up @@ -15,19 +15,10 @@

public abstract class PayFlow {

public static final String NO_CURRENT_ACTIVITY_MSG = "Cannot start process with no current activity";
public static final String PURCHASE_CANCELLED_MSG = "Purchase was cancelled";
public static final String PURCHASE_LOAD_MASKED_WALLET_ERROR_MSG = "Purchase masked wallet error";
public static final String PURCHASE_LOAD_FULL_WALLET_ERROR_MSG = "Purchase full wallet error";
public static final String ANDROID_PAY_UNAVAILABLE_ERROR_MSG = "Android Pay is unavailable";
public static final String MAKING_IS_READY_TO_PAY_CALL_ERROR_MSG = "Error making isReadyToPay call";
public static final String JSON_PARSING_ERROR_MSG = "Failed to create token from JSON string";
public static final String PLAY_SERVICES_ARE_NOT_AVAILABLE_MSG = "Play services are not available!";

protected final @NonNull Fun0<Activity> activityProvider;
private String publishableKey; // invalid value by default
private int environment; // invalid value by default

private ReadableMap errorCodes; // invalid value by default, set in runtime

public PayFlow(@NonNull Fun0<Activity> activityProvider) {
ArgCheck.nonNull(activityProvider);
Expand Down Expand Up @@ -69,6 +60,24 @@ public void setPublishableKey(@NonNull String publishableKey) {
this.publishableKey = ArgCheck.notEmptyString(publishableKey);
}

public void setErrorCodes(ReadableMap errorCodes) {
if (this.errorCodes == null) {
this.errorCodes = errorCodes;
}
}

protected ReadableMap getErrorCodes() {
return ArgCheck.nonNull(errorCodes);
}

protected String getErrorCode(String key) {
return Errors.getErrorCode(getErrorCodes(), key);
}

protected String getErrorDescription(String key) {
return Errors.getDescription(getErrorCodes(), key);
}

abstract void paymentRequestWithAndroidPay(final ReadableMap payParams, final Promise promise);

abstract void deviceSupportsAndroidPay(boolean isExistingPaymentMethodRequired, final Promise promise);
Expand Down
62 changes: 44 additions & 18 deletions android/src/main/java/com/gettipsi/stripe/StripeModule.java
Expand Up @@ -27,21 +27,20 @@
import com.stripe.android.model.SourceParams;
import com.stripe.android.model.Token;

import static com.gettipsi.stripe.PayFlow.NO_CURRENT_ACTIVITY_MSG;
import static com.gettipsi.stripe.Errors.*;
import static com.gettipsi.stripe.util.Converters.convertSourceToWritableMap;
import static com.gettipsi.stripe.util.Converters.convertTokenToWritableMap;
import static com.gettipsi.stripe.util.Converters.createBankAccount;
import static com.gettipsi.stripe.util.Converters.createCard;
import static com.gettipsi.stripe.util.Converters.getStringOrNull;
import static com.gettipsi.stripe.util.InitializationOptions.ANDROID_PAY_MODE_KEY;
import static com.gettipsi.stripe.util.InitializationOptions.ANDROID_PAY_MODE_PRODUCTION;
import static com.gettipsi.stripe.util.InitializationOptions.PUBLISHABLE_KEY;
import static com.gettipsi.stripe.util.InitializationOptions.ANDROID_PAY_MODE_TEST;
import static com.gettipsi.stripe.util.InitializationOptions.PUBLISHABLE_KEY;

public class StripeModule extends ReactContextBaseJavaModule {

private static final String MODULE_NAME = StripeModule.class.getSimpleName();
private static final String TAG = "### " + MODULE_NAME + ": ";

private static StripeModule sInstance = null;

Expand All @@ -62,7 +61,7 @@ public Stripe getStripe() {
private String mPublicKey;
private Stripe mStripe;
private PayFlow mPayFlow;

private ReadableMap mErrorCodes;

private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {

Expand Down Expand Up @@ -91,7 +90,7 @@ public String getName() {
}

@ReactMethod
public void init(@NonNull ReadableMap options) {
public void init(@NonNull ReadableMap options, @NonNull ReadableMap errorCodes) {
ArgCheck.nonNull(options);

String newPubKey = Converters.getStringOrNull(options, PUBLISHABLE_KEY);
Expand All @@ -110,6 +109,11 @@ public void init(@NonNull ReadableMap options) {

getPayFlow().setEnvironment(androidPayModeToEnvironment(newAndroidPayMode));
}

if (mErrorCodes == null) {
mErrorCodes = errorCodes;
getPayFlow().setErrorCodes(errorCodes);
}
}

private PayFlow getPayFlow() {
Expand Down Expand Up @@ -154,11 +158,11 @@ public void onSuccess(Token token) {
}
public void onError(Exception error) {
error.printStackTrace();
promise.reject(TAG, error.getMessage());
promise.reject(toErrorCode(error), error.getMessage());
}
});
} catch (Exception e) {
promise.reject(TAG, e.getMessage());
promise.reject(toErrorCode(e), e.getMessage());
}
}

Expand All @@ -178,11 +182,11 @@ public void onSuccess(Token token) {
}
public void onError(Exception error) {
error.printStackTrace();
promise.reject(TAG, error.getMessage());
promise.reject(toErrorCode(error), error.getMessage());
}
});
} catch (Exception e) {
promise.reject(TAG, e.getMessage());
promise.reject(toErrorCode(e), e.getMessage());
}
}

Expand All @@ -193,11 +197,15 @@ public void paymentRequestWithCardForm(ReadableMap unused, final Promise promise
ArgCheck.nonNull(currentActivity);
ArgCheck.notEmptyString(mPublicKey);

final AddCardDialogFragment cardDialog = AddCardDialogFragment.newInstance(mPublicKey);
final AddCardDialogFragment cardDialog = AddCardDialogFragment.newInstance(
mPublicKey,
getErrorCode(mErrorCodes, "cancelled"),
getDescription(mErrorCodes, "cancelled")
);
cardDialog.setPromise(promise);
cardDialog.show(currentActivity.getFragmentManager(), "AddNewCard");
} catch (Exception e) {
promise.reject(TAG, e.getMessage());
promise.reject(toErrorCode(e), e.getMessage());
}
}

Expand Down Expand Up @@ -275,15 +283,18 @@ public void createSourceWithParams(final ReadableMap options, final Promise prom
mStripe.createSource(sourceParams, new SourceCallback() {
@Override
public void onError(Exception error) {
promise.reject(error);
promise.reject(toErrorCode(error));
}

@Override
public void onSuccess(Source source) {
if (Source.REDIRECT.equals(source.getFlow())) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject(TAG, NO_CURRENT_ACTIVITY_MSG);
promise.reject(
getErrorCode(mErrorCodes, "activityUnavailable"),
getDescription(mErrorCodes, "activityUnavailable")
);
} else {
mCreateSourcePromise = promise;
mCreatedSource = source;
Expand All @@ -308,23 +319,32 @@ void processRedirect(@Nullable Uri redirectData) {

if (redirectData == null) {

mCreateSourcePromise.reject(TAG, "Cancelled");
mCreateSourcePromise.reject(
getErrorCode(mErrorCodes, "redirectCancelled"),
getDescription(mErrorCodes, "redirectCancelled")
);
mCreatedSource = null;
mCreateSourcePromise = null;
return;
}

final String clientSecret = redirectData.getQueryParameter("client_secret");
if (!mCreatedSource.getClientSecret().equals(clientSecret)) {
mCreateSourcePromise.reject(TAG, "Received redirect uri but there is no source to process");
mCreateSourcePromise.reject(
getErrorCode(mErrorCodes, "redirectNoSource"),
getDescription(mErrorCodes, "redirectNoSource")
);
mCreatedSource = null;
mCreateSourcePromise = null;
return;
}

final String sourceId = redirectData.getQueryParameter("source");
if (!mCreatedSource.getId().equals(sourceId)) {
mCreateSourcePromise.reject(TAG, "Received wrong source id in redirect uri");
mCreateSourcePromise.reject(
getErrorCode(mErrorCodes, "redirectWrongSourceId"),
getDescription(mErrorCodes, "redirectWrongSourceId")
);
mCreatedSource = null;
mCreateSourcePromise = null;
return;
Expand Down Expand Up @@ -353,12 +373,18 @@ protected Void doInBackground(Void... voids) {
promise.resolve(convertSourceToWritableMap(source));
break;
case Source.CANCELED:
promise.reject(TAG, "User cancelled source redirect");
promise.reject(
getErrorCode(mErrorCodes, "redirectCancelled"),
getDescription(mErrorCodes, "redirectCancelled")
);
break;
case Source.PENDING:
case Source.FAILED:
case Source.UNKNOWN:
promise.reject(TAG, "Source redirect failed");
promise.reject(
getErrorCode(mErrorCodes, "redirectFailed"),
getDescription(mErrorCodes, "redirectFailed")
);
}
return null;
}
Expand Down

0 comments on commit d9c8be0

Please sign in to comment.