Skip to content

Commit

Permalink
Add StripeError field to StripeException (#793)
Browse files Browse the repository at this point in the history
**Summary**
Add a `StripeError` field to `StripeException`.
Access via `StripeException#getStripeError()`

**Motivation**
The error code is an important field for SDK users
to be able to determine why a request failed, and
allows them to better react to the failure.

For example, a `"payment_method_not_available"` code
may suggest to the developer to invite a customer to
use another payment method. Additionally, a developer
may choose to localize an user-facing error based on
this code.

Fixes #792

**Testing**
Wrote automated tests.
  • Loading branch information
mshafrir-stripe authored Feb 6, 2019
1 parent ba3f023 commit 44e1bf9
Show file tree
Hide file tree
Showing 17 changed files with 159 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ public void onClick(View view) {
}

private void launchWithCustomer() {
Intent payIntent = PaymentMethodsActivity.newIntent(this);
startActivityForResult(payIntent, REQUEST_CODE_SELECT_SOURCE);
startActivityForResult(PaymentMethodsActivity.newIntent(this), REQUEST_CODE_SELECT_SOURCE);
}

@Override
Expand All @@ -98,6 +97,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}
}

@NonNull
private String buildCardString(@NonNull SourceCardData data) {
return data.getBrand() + getString(R.string.ending_in) + data.getLast4();
}
Expand Down
22 changes: 0 additions & 22 deletions stripe/src/main/java/com/stripe/android/ErrorParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,4 @@ static StripeError parseError(@Nullable String rawError) {
return new StripeError(type, message, code, param, declineCode, charge);
}

/**
* A model for error objects sent from the server.
*/
static class StripeError {
@Nullable public final String type;
@Nullable public final String message;
@Nullable public final String code;
@Nullable public final String param;
@Nullable public final String declineCode;
@Nullable public final String charge;

StripeError(@Nullable String type, @Nullable String message, @Nullable String code,
@Nullable String param, @Nullable String declineCode,
@Nullable String charge) {
this.type = type;
this.message = message;
this.code = code;
this.param = param;
this.declineCode = declineCode;
this.charge = charge;
}
}
}
80 changes: 31 additions & 49 deletions stripe/src/main/java/com/stripe/android/StripeApiHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,8 @@ static PaymentIntent confirmPaymentIntent(
return PaymentIntent.fromString(response.getResponseBody());
} catch (CardException unexpected) {
// This particular kind of exception should not be possible from a PaymentI API endpoint
throw new APIException(
unexpected.getMessage(),
unexpected.getRequestId(),
unexpected.getStatusCode(),
unexpected);
throw new APIException(unexpected.getMessage(), unexpected.getRequestId(),
unexpected.getStatusCode(), null, unexpected);
}
}

Expand Down Expand Up @@ -200,11 +197,8 @@ static PaymentIntent retrievePaymentIntent(
return PaymentIntent.fromString(response.getResponseBody());
} catch (CardException unexpected) {
// This particular kind of exception should not be possible from a PaymentI API endpoint
throw new APIException(
unexpected.getMessage(),
unexpected.getRequestId(),
unexpected.getStatusCode(),
unexpected);
throw new APIException(unexpected.getMessage(), unexpected.getRequestId(),
unexpected.getStatusCode(), null, unexpected);
}
}

Expand Down Expand Up @@ -288,11 +282,8 @@ static Source createSource(
return Source.fromString(response.getResponseBody());
} catch (CardException unexpected) {
// This particular kind of exception should not be possible from a Source API endpoint.
throw new APIException(
unexpected.getMessage(),
unexpected.getRequestId(),
unexpected.getStatusCode(),
unexpected);
throw new APIException(unexpected.getMessage(), unexpected.getRequestId(),
unexpected.getStatusCode(), null, unexpected);
}
}

Expand Down Expand Up @@ -339,11 +330,8 @@ static Source retrieveSource(
return Source.fromString(response.getResponseBody());
} catch (CardException unexpected) {
// This particular kind of exception should not be possible from a Source API endpoint.
throw new APIException(
unexpected.getMessage(),
unexpected.getRequestId(),
unexpected.getStatusCode(),
unexpected);
throw new APIException(unexpected.getMessage(), unexpected.getRequestId(),
unexpected.getStatusCode(), null, unexpected);
}
}

Expand Down Expand Up @@ -996,7 +984,7 @@ private static List<Parameter> flattenParamsValue(@NonNull Object value,
throw new InvalidRequestException("You cannot set '" + keyPrefix + "' to an empty " +
"string. " + "We interpret empty strings as null in requests. " + "You may " +
"set '" + keyPrefix + "' to null to delete the property.", keyPrefix, null,
0, null, null, null);
0, null, null, null, null);
} else if (value == null) {
flatParams = new LinkedList<>();
flatParams.add(new Parameter(keyPrefix, ""));
Expand Down Expand Up @@ -1039,17 +1027,16 @@ private static byte[] getOutputBytes(
if (jsonData == null) {
throw new InvalidRequestException("Unable to create JSON data from parameters. "
+ "Please contact [email protected] for assistance.",
null, null, 0, null, null, null);
null, null, 0, null, null, null, null);
}
return jsonData.toString().getBytes(CHARSET);
} else {
return createQuery(params).getBytes(CHARSET);
}
} catch (UnsupportedEncodingException e) {
throw new InvalidRequestException("Unable to encode parameters to "
+ CHARSET
+ ". Please contact [email protected] for assistance.",
null, null, 0, null, null, e);
throw new InvalidRequestException("Unable to encode parameters to " + CHARSET + "." +
" Please contact [email protected] for assistance.",
null, null, 0, null, null, null, e);
}
}

Expand Down Expand Up @@ -1117,35 +1104,27 @@ private static StripeResponse getStripeResponse(
}
}

private static void handleAPIError(@Nullable String rBody, int rCode,
private static void handleAPIError(@Nullable String responseBody, int responseCode,
@Nullable String requestId)
throws InvalidRequestException, AuthenticationException,
CardException, APIException {
throws InvalidRequestException, AuthenticationException, CardException, APIException {

final ErrorParser.StripeError stripeError = ErrorParser.parseError(rBody);
switch (rCode) {
case 400: {
throw new InvalidRequestException(
stripeError.message,
stripeError.param,
requestId,
rCode,
stripeError.code,
stripeError.declineCode,
null);
}
final StripeError stripeError = ErrorParser.parseError(responseBody);
switch (responseCode) {
case 400:
case 404: {
throw new InvalidRequestException(
stripeError.message,
stripeError.param,
requestId,
rCode,
responseCode,
stripeError.code,
stripeError.declineCode,
stripeError,
null);
}
case 401: {
throw new AuthenticationException(stripeError.message, requestId, rCode);
throw new AuthenticationException(stripeError.message, requestId, responseCode,
stripeError);
}
case 402: {
throw new CardException(
Expand All @@ -1155,18 +1134,21 @@ private static void handleAPIError(@Nullable String rBody, int rCode,
stripeError.param,
stripeError.declineCode,
stripeError.charge,
rCode,
null);
responseCode,
stripeError
);
}
case 403: {
throw new PermissionException(stripeError.message, requestId, rCode);
throw new PermissionException(stripeError.message, requestId, responseCode,
stripeError);
}
case 429: {
throw new RateLimitException(stripeError.message, stripeError.param, requestId,
rCode, null);
responseCode, stripeError);
}
default: {
throw new APIException(stripeError.message, requestId, rCode, null);
throw new APIException(stripeError.message, requestId, responseCode, stripeError,
null);
}
}
}
Expand Down Expand Up @@ -1196,7 +1178,7 @@ private static StripeResponse requestData(
throw new AuthenticationException("No API key provided. (HINT: set your API key using" +
" 'Stripe.apiKey = <API-KEY>'. You can generate API keys from the Stripe" +
" web interface. See https://stripe.com/api for details or email " +
"[email protected] if you have questions.", null, 0);
"[email protected] if you have questions.", null, 0, null);
}

final StripeResponse response = getStripeResponse(method, url, params, options);
Expand Down
34 changes: 34 additions & 0 deletions stripe/src/main/java/com/stripe/android/StripeError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.stripe.android;

import androidx.annotation.Nullable;

/**
* A model for error objects sent from the Stripe API.
*
* https://stripe.com/docs/api/errors
*/
public class StripeError {

// https://stripe.com/docs/api/errors (e.g. "invalid_request_error")
@Nullable public final String type;
@Nullable public final String message;

// https://stripe.com/docs/error-codes (e.g. "payment_method_unactivated")
@Nullable public final String code;
@Nullable public final String param;

// see https://stripe.com/docs/declines/codes
@Nullable public final String declineCode;

@Nullable public final String charge;

StripeError(@Nullable String type, @Nullable String message, @Nullable String code,
@Nullable String param, @Nullable String declineCode, @Nullable String charge) {
this.type = type;
this.message = message;
this.code = code;
this.param = param;
this.declineCode = declineCode;
this.charge = charge;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public APIConnectionException(@Nullable String message) {
}

public APIConnectionException(@Nullable String message, @Nullable Throwable e) {
super(message, null, 0, e);
super(null, message, null, 0, e);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import androidx.annotation.Nullable;

import com.stripe.android.StripeError;

/**
* An {@link Exception} that represents an internal problem with Stripe's servers.
*/
public class APIException extends StripeException {

public APIException(@Nullable String message, @Nullable String requestId,
@Nullable Integer statusCode, @Nullable Throwable e) {
super(message, requestId, statusCode, e);
@Nullable Integer statusCode, @Nullable StripeError stripeError,
@Nullable Throwable e) {
super(stripeError, message, requestId, statusCode, e);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import androidx.annotation.Nullable;

import com.stripe.android.StripeError;

/**
* An {@link Exception} that represents a failure to authenticate yourself to the server.
*/
public class AuthenticationException extends StripeException {

public AuthenticationException(@Nullable String message, @Nullable String requestId,
@Nullable Integer statusCode) {
super(message, requestId, statusCode);
@Nullable Integer statusCode,
@Nullable StripeError stripeError) {
super(stripeError, message, requestId, statusCode);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.stripe.android.exception;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.stripe.android.StripeError;

/**
* An {@link Exception} indicating that there is a problem with a Card used for a request.
* Card errors are the most common type of error you should expect to handle.
Expand All @@ -17,8 +20,8 @@ public class CardException extends StripeException {
public CardException(@Nullable String message, @Nullable String requestId,
@Nullable String code, @Nullable String param,
@Nullable String declineCode, @Nullable String charge,
@Nullable Integer statusCode, @Nullable Throwable e) {
super(message, requestId, statusCode, e);
@Nullable Integer statusCode, @NonNull StripeError stripeError) {
super(stripeError, message, requestId, statusCode);
mCode = code;
mParam = param;
mDeclineCode = declineCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import androidx.annotation.Nullable;

import com.stripe.android.StripeError;

/**
* An {@link Exception} indicating that invalid parameters were used in a request.
*/
Expand All @@ -15,8 +17,8 @@ public class InvalidRequestException extends StripeException {
public InvalidRequestException(@Nullable String message, @Nullable String param,
@Nullable String requestId, @Nullable Integer statusCode,
@Nullable String errorCode, @Nullable String errorDeclineCode,
@Nullable Throwable e) {
super(message, requestId, statusCode, e);
@Nullable StripeError stripeError, @Nullable Throwable e) {
super(stripeError, message, requestId, statusCode, e);
mParam = param;
mErrorCode = errorCode;
mErrorDeclineCode = errorDeclineCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

import androidx.annotation.Nullable;

import com.stripe.android.StripeError;

/**
* A type of {@link AuthenticationException} resulting from incorrect permissions
* to perform the requested action.
*/
public class PermissionException extends AuthenticationException {

public PermissionException(@Nullable String message, @Nullable String requestId,
@Nullable Integer statusCode) {
super(message, requestId, statusCode);
@Nullable Integer statusCode, @Nullable StripeError stripeError) {
super(message, requestId, statusCode, stripeError);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import androidx.annotation.Nullable;

import com.stripe.android.StripeError;

/**
* An {@link Exception} indicating that too many requests have hit the API too quickly.
*/
Expand All @@ -12,7 +14,7 @@ public RateLimitException(
@Nullable String param,
@Nullable String requestId,
@Nullable Integer statusCode,
@Nullable Throwable e) {
super(message, param, requestId, statusCode, null, null, e);
@Nullable StripeError stripeError) {
super(message, param, requestId, statusCode, null, null, stripeError, null);
}
}
Loading

0 comments on commit 44e1bf9

Please sign in to comment.