diff --git a/example/src/main/java/com/stripe/example/activity/CustomerSessionActivity.java b/example/src/main/java/com/stripe/example/activity/CustomerSessionActivity.java index 065d6c00d20..2185ee3ad7d 100644 --- a/example/src/main/java/com/stripe/example/activity/CustomerSessionActivity.java +++ b/example/src/main/java/com/stripe/example/activity/CustomerSessionActivity.java @@ -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 @@ -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(); } diff --git a/stripe/src/main/java/com/stripe/android/ErrorParser.java b/stripe/src/main/java/com/stripe/android/ErrorParser.java index 51cc88e6572..712f9d7c12d 100644 --- a/stripe/src/main/java/com/stripe/android/ErrorParser.java +++ b/stripe/src/main/java/com/stripe/android/ErrorParser.java @@ -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; - } - } } diff --git a/stripe/src/main/java/com/stripe/android/StripeApiHandler.java b/stripe/src/main/java/com/stripe/android/StripeApiHandler.java index c2979420b9d..117bc43e256 100644 --- a/stripe/src/main/java/com/stripe/android/StripeApiHandler.java +++ b/stripe/src/main/java/com/stripe/android/StripeApiHandler.java @@ -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); } } @@ -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); } } @@ -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); } } @@ -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); } } @@ -996,7 +984,7 @@ private static List 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, "")); @@ -1039,17 +1027,16 @@ private static byte[] getOutputBytes( if (jsonData == null) { throw new InvalidRequestException("Unable to create JSON data from parameters. " + "Please contact support@stripe.com 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 support@stripe.com for assistance.", - null, null, 0, null, null, e); + throw new InvalidRequestException("Unable to encode parameters to " + CHARSET + "." + + " Please contact support@stripe.com for assistance.", + null, null, 0, null, null, null, e); } } @@ -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( @@ -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); } } } @@ -1196,7 +1178,7 @@ private static StripeResponse requestData( throw new AuthenticationException("No API key provided. (HINT: set your API key using" + " 'Stripe.apiKey = '. You can generate API keys from the Stripe" + " web interface. See https://stripe.com/api for details or email " + - "support@stripe.com if you have questions.", null, 0); + "support@stripe.com if you have questions.", null, 0, null); } final StripeResponse response = getStripeResponse(method, url, params, options); diff --git a/stripe/src/main/java/com/stripe/android/StripeError.java b/stripe/src/main/java/com/stripe/android/StripeError.java new file mode 100644 index 00000000000..d55217eb9f6 --- /dev/null +++ b/stripe/src/main/java/com/stripe/android/StripeError.java @@ -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; + } +} diff --git a/stripe/src/main/java/com/stripe/android/exception/APIConnectionException.java b/stripe/src/main/java/com/stripe/android/exception/APIConnectionException.java index 8ba3ca91c89..b76438f4ac2 100644 --- a/stripe/src/main/java/com/stripe/android/exception/APIConnectionException.java +++ b/stripe/src/main/java/com/stripe/android/exception/APIConnectionException.java @@ -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); } } diff --git a/stripe/src/main/java/com/stripe/android/exception/APIException.java b/stripe/src/main/java/com/stripe/android/exception/APIException.java index 752fe5c4387..cc9affc176e 100644 --- a/stripe/src/main/java/com/stripe/android/exception/APIException.java +++ b/stripe/src/main/java/com/stripe/android/exception/APIException.java @@ -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); } } diff --git a/stripe/src/main/java/com/stripe/android/exception/AuthenticationException.java b/stripe/src/main/java/com/stripe/android/exception/AuthenticationException.java index 1b6e0ebc031..8ff0da28a4b 100644 --- a/stripe/src/main/java/com/stripe/android/exception/AuthenticationException.java +++ b/stripe/src/main/java/com/stripe/android/exception/AuthenticationException.java @@ -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); } } diff --git a/stripe/src/main/java/com/stripe/android/exception/CardException.java b/stripe/src/main/java/com/stripe/android/exception/CardException.java index 96726aad335..51651de396f 100644 --- a/stripe/src/main/java/com/stripe/android/exception/CardException.java +++ b/stripe/src/main/java/com/stripe/android/exception/CardException.java @@ -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. @@ -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; diff --git a/stripe/src/main/java/com/stripe/android/exception/InvalidRequestException.java b/stripe/src/main/java/com/stripe/android/exception/InvalidRequestException.java index 8c3fe60abaf..568601ce045 100644 --- a/stripe/src/main/java/com/stripe/android/exception/InvalidRequestException.java +++ b/stripe/src/main/java/com/stripe/android/exception/InvalidRequestException.java @@ -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. */ @@ -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; diff --git a/stripe/src/main/java/com/stripe/android/exception/PermissionException.java b/stripe/src/main/java/com/stripe/android/exception/PermissionException.java index 64473c843f2..c2d67fc4b13 100644 --- a/stripe/src/main/java/com/stripe/android/exception/PermissionException.java +++ b/stripe/src/main/java/com/stripe/android/exception/PermissionException.java @@ -2,6 +2,8 @@ import androidx.annotation.Nullable; +import com.stripe.android.StripeError; + /** * A type of {@link AuthenticationException} resulting from incorrect permissions * to perform the requested action. @@ -9,7 +11,7 @@ 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); } } diff --git a/stripe/src/main/java/com/stripe/android/exception/RateLimitException.java b/stripe/src/main/java/com/stripe/android/exception/RateLimitException.java index a585e419ed8..a2a07085736 100644 --- a/stripe/src/main/java/com/stripe/android/exception/RateLimitException.java +++ b/stripe/src/main/java/com/stripe/android/exception/RateLimitException.java @@ -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. */ @@ -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); } } diff --git a/stripe/src/main/java/com/stripe/android/exception/StripeException.java b/stripe/src/main/java/com/stripe/android/exception/StripeException.java index 7fc113069f4..c957e0bee13 100644 --- a/stripe/src/main/java/com/stripe/android/exception/StripeException.java +++ b/stripe/src/main/java/com/stripe/android/exception/StripeException.java @@ -3,6 +3,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.stripe.android.StripeError; + /** * A base class for Stripe-related {@link Exception Exceptions}. */ @@ -10,37 +12,55 @@ public abstract class StripeException extends Exception { protected static final long serialVersionUID = 1L; - @Nullable private final String requestId; - @Nullable private final Integer statusCode; + @Nullable private final String mRequestId; + @Nullable private final Integer mStatusCode; + @Nullable private final StripeError mStripeError; public StripeException(@Nullable String message, @Nullable String requestId, @Nullable Integer statusCode) { - this(message, requestId, statusCode, null); + this(null, message, requestId, statusCode); + } + + public StripeException(@Nullable StripeError stripeError, @Nullable String message, + @Nullable String requestId, @Nullable Integer statusCode) { + this(stripeError, message, requestId, statusCode, null); } public StripeException(@Nullable String message, @Nullable String requestId, @Nullable Integer statusCode, @Nullable Throwable e) { + this(null, message, requestId, statusCode, e); + } + + public StripeException(@Nullable StripeError stripeError, @Nullable String message, + @Nullable String requestId, @Nullable Integer statusCode, + @Nullable Throwable e) { super(message, e); - this.statusCode = statusCode; - this.requestId = requestId; + mStripeError = stripeError; + mStatusCode = statusCode; + mRequestId = requestId; } @Nullable public String getRequestId() { - return requestId; + return mRequestId; } @Nullable public Integer getStatusCode() { - return statusCode; + return mStatusCode; + } + + @Nullable + public StripeError getStripeError() { + return mStripeError; } @NonNull @Override public String toString() { final String reqIdStr; - if (requestId != null) { - reqIdStr = "; request-id: " + requestId; + if (mRequestId != null) { + reqIdStr = "; request-id: " + mRequestId; } else { reqIdStr = ""; } diff --git a/stripe/src/test/java/com/stripe/android/CustomerSessionTest.java b/stripe/src/test/java/com/stripe/android/CustomerSessionTest.java index db5f3c5c9bb..cb0af662a90 100644 --- a/stripe/src/test/java/com/stripe/android/CustomerSessionTest.java +++ b/stripe/src/test/java/com/stripe/android/CustomerSessionTest.java @@ -207,10 +207,7 @@ public void setup() { anyString(), anyString(), anyString())) - .thenThrow(new APIException( - "The card is invalid", - "request_123", - 404, + .thenThrow(new APIException("The card is invalid", "request_123", 404, null, null)); when(mStripeApiProxy.deleteCustomerSourceWithKey( any(Context.class), @@ -227,10 +224,7 @@ public void setup() { ArgumentMatchers.anyList(), anyString(), anyString())) - .thenThrow(new APIException( - "The card does not exist", - "request_123", - 404, + .thenThrow(new APIException("The card does not exist", "request_123", 404, null, null)); when(mStripeApiProxy.setDefaultCustomerSourceWithKey( any(Context.class), @@ -250,7 +244,7 @@ public void setup() { anyString(), anyString(), anyString())) - .thenThrow(new APIException("auth error", "reqId", 405, null)); + .thenThrow(new APIException("auth error", "reqId", 405, null, null)); } catch (StripeException exception) { fail("Exception when accessing mock api proxy: " + exception.getMessage()); } diff --git a/stripe/src/test/java/com/stripe/android/ErrorParserTest.java b/stripe/src/test/java/com/stripe/android/ErrorParserTest.java index cbae1d437fc..0f98f6ae5f1 100644 --- a/stripe/src/test/java/com/stripe/android/ErrorParserTest.java +++ b/stripe/src/test/java/com/stripe/android/ErrorParserTest.java @@ -43,7 +43,7 @@ public class ErrorParserTest { @Test public void parseError_withInvalidRequestError_createsCorrectObject() { - final ErrorParser.StripeError parsedStripeError = + final StripeError parsedStripeError = ErrorParser.parseError(RAW_INVALID_REQUEST_ERROR); String errorMessage = "The Stripe API is only accessible over HTTPS. " + "Please see for more information."; @@ -54,7 +54,7 @@ public void parseError_withInvalidRequestError_createsCorrectObject() { @Test public void parseError_withNoErrorMessage_addsInvalidResponseMessage() { - final ErrorParser.StripeError badStripeError = + final StripeError badStripeError = ErrorParser.parseError(RAW_INCORRECT_FORMAT_ERROR); assertEquals(ErrorParser.MALFORMED_RESPONSE_MESSAGE, badStripeError.message); assertNull(badStripeError.type); @@ -62,7 +62,7 @@ public void parseError_withNoErrorMessage_addsInvalidResponseMessage() { @Test public void parseError_withAllFields_parsesAllFields() { - final ErrorParser.StripeError error = ErrorParser.parseError(RAW_ERROR_WITH_ALL_FIELDS); + final StripeError error = ErrorParser.parseError(RAW_ERROR_WITH_ALL_FIELDS); assertEquals("code_value", error.code); assertEquals("param_value", error.param); assertEquals("charge_value", error.charge); diff --git a/stripe/src/test/java/com/stripe/android/StripeErrorFixtures.java b/stripe/src/test/java/com/stripe/android/StripeErrorFixtures.java new file mode 100644 index 00000000000..07bc8c55175 --- /dev/null +++ b/stripe/src/test/java/com/stripe/android/StripeErrorFixtures.java @@ -0,0 +1,11 @@ +package com.stripe.android; + +public class StripeErrorFixtures { + public final static StripeError INVALID_REQUEST_ERROR = new StripeError( + "invalid_request_error", + "This payment method (bancontact) is not activated for your account.", + "payment_method_unactivated", + "type", + "", + ""); +} diff --git a/stripe/src/test/java/com/stripe/android/exception/InvalidRequestExceptionTest.java b/stripe/src/test/java/com/stripe/android/exception/InvalidRequestExceptionTest.java new file mode 100644 index 00000000000..a634737cada --- /dev/null +++ b/stripe/src/test/java/com/stripe/android/exception/InvalidRequestExceptionTest.java @@ -0,0 +1,17 @@ +package com.stripe.android.exception; + +import com.stripe.android.StripeErrorFixtures; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class InvalidRequestExceptionTest { + + @Test + public void getStripeError_shouldReturnStripeError() { + final StripeException stripeException = new InvalidRequestException(null, null, null, null, + null, null, StripeErrorFixtures.INVALID_REQUEST_ERROR, null); + assertEquals(StripeErrorFixtures.INVALID_REQUEST_ERROR, stripeException.getStripeError()); + } +} diff --git a/stripe/src/test/java/com/stripe/android/view/PaymentFlowActivityTest.java b/stripe/src/test/java/com/stripe/android/view/PaymentFlowActivityTest.java index 0537b9354c5..a3f2670d2ff 100644 --- a/stripe/src/test/java/com/stripe/android/view/PaymentFlowActivityTest.java +++ b/stripe/src/test/java/com/stripe/android/view/PaymentFlowActivityTest.java @@ -159,7 +159,7 @@ public void onErrorBroadcast_displaysAlertDialog() { Bundle bundle = new Bundle(); bundle.putSerializable(EXTRA_EXCEPTION, - new APIException("Something's wrong", "ID123", 400, null)); + new APIException("Something's wrong", "ID123", 400, null, null)); Intent errorIntent = new Intent(ACTION_API_EXCEPTION); errorIntent.putExtras(bundle); LocalBroadcastManager.getInstance(paymentFlowActivity)