Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add last_payment_error field to PaymentIntent #1378

Merged
merged 1 commit into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 227 additions & 4 deletions stripe/src/main/java/com/stripe/android/model/PaymentIntent.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.stripe.android.ObjectBuilder;
import com.stripe.android.utils.ObjectUtils;

import org.json.JSONException;
Expand Down Expand Up @@ -39,6 +40,7 @@ public final class PaymentIntent extends StripeModel implements StripeIntent {
private static final String FIELD_CONFIRMATION_METHOD = "confirmation_method";
private static final String FIELD_CURRENCY = "currency";
private static final String FIELD_DESCRIPTION = "description";
private static final String FIELD_LAST_PAYMENT_ERROR = "last_payment_error";
private static final String FIELD_LIVEMODE = "livemode";
private static final String FIELD_NEXT_ACTION = "next_action";
private static final String FIELD_PAYMENT_METHOD_TYPES = "payment_method_types";
Expand Down Expand Up @@ -67,6 +69,7 @@ public final class PaymentIntent extends StripeModel implements StripeIntent {
@Nullable private final String mSource;
@Nullable private final Status mStatus;
@Nullable private final Usage mSetupFutureUsage;
@Nullable private final Error mLastPaymentError;

@Nullable
@Override
Expand Down Expand Up @@ -208,6 +211,14 @@ public Status getStatus() {
return mStatus;
}

/**
* @return The payment error encountered in the previous PaymentIntent confirmation.
*/
@Nullable
public Error getLastPaymentError() {
return mLastPaymentError;
}

private PaymentIntent(
@Nullable String id,
@Nullable String objectType,
Expand All @@ -225,7 +236,8 @@ private PaymentIntent(
@Nullable String receiptEmail,
@Nullable String source,
@Nullable Status status,
@Nullable Usage setupFutureUsage) {
@Nullable Usage setupFutureUsage,
@Nullable Error lastPaymentError) {
mId = id;
mObjectType = objectType;
mPaymentMethodTypes = paymentMethodTypes;
Expand All @@ -245,6 +257,7 @@ private PaymentIntent(
mSetupFutureUsage = setupFutureUsage;
mNextActionType = mNextAction != null ?
NextActionType.fromCode((String) mNextAction.get(FIELD_NEXT_ACTION_TYPE)) : null;
mLastPaymentError = lastPaymentError;
}

@NonNull
Expand Down Expand Up @@ -287,6 +300,8 @@ public static PaymentIntent fromJson(@Nullable JSONObject jsonObject) {
Usage.fromCode(optString(jsonObject, FIELD_SETUP_FUTURE_USAGE));
final Map<String, Object> nextAction = optMap(jsonObject, FIELD_NEXT_ACTION);
final String source = optString(jsonObject, FIELD_SOURCE);
final Error lastPaymentError =
Error.fromJson(jsonObject.optJSONObject(FIELD_LAST_PAYMENT_ERROR));

return new PaymentIntent(
id,
Expand All @@ -305,7 +320,9 @@ public static PaymentIntent fromJson(@Nullable JSONObject jsonObject) {
receiptEmail,
source,
status,
setupFutureUsage);
setupFutureUsage,
lastPaymentError
);
}

@Override
Expand All @@ -331,15 +348,221 @@ private boolean typedEquals(@NonNull PaymentIntent paymentIntent) {
&& ObjectUtils.equals(mSetupFutureUsage, paymentIntent.mSetupFutureUsage)
&& ObjectUtils.equals(mPaymentMethodTypes, paymentIntent.mPaymentMethodTypes)
&& ObjectUtils.equals(mNextAction, paymentIntent.mNextAction)
&& ObjectUtils.equals(mNextActionType, paymentIntent.mNextActionType);
&& ObjectUtils.equals(mNextActionType, paymentIntent.mNextActionType)
&& ObjectUtils.equals(mLastPaymentError, paymentIntent.mLastPaymentError);
}

@Override
public int hashCode() {
return ObjectUtils.hash(mId, mObjectType, mAmount, mCanceledAt, mCaptureMethod,
mClientSecret, mConfirmationMethod, mCreated, mCurrency, mDescription, mLiveMode,
mReceiptEmail, mSource, mStatus, mPaymentMethodTypes, mNextAction,
mNextActionType, mSetupFutureUsage);
mNextActionType, mSetupFutureUsage, mLastPaymentError);
}

/**
* The payment error encountered in the previous PaymentIntent confirmation.
*
* See <a href="https://stripe.com/docs/api/payment_intents/object#payment_intent_object-last_payment_error">last_payment_error</a>.
*/
public static final class Error {
private static final String FIELD_CHARGE = "charge";
private static final String FIELD_CODE = "code";
private static final String FIELD_DECLINE_CODE = "decline_code";
private static final String FIELD_DOC_URL = "doc_url";
private static final String FIELD_MESSAGE = "message";
private static final String FIELD_PARAM = "param";
private static final String FIELD_PAYMENT_METHOD = "payment_method";
private static final String FIELD_TYPE = "type";

/**
* For card errors, the ID of the failed charge.
*/
@Nullable public final String charge;

/**
* For some errors that could be handled programmatically, a short string indicating the
* <a href="https://stripe.com/docs/error-codes">error code</a> reported.
*/
@Nullable public final String code;

/**
* For card errors resulting from a card issuer decline, a short string indicating the
* <a href="https://stripe.com/docs/declines#issuer-declines">card issuer’s reason for the decline</a>
* if they provide one.
*/
@Nullable public final String declineCode;

/**
* A URL to more information about the
* <a href="https://stripe.com/docs/error-codes">error code</a> reported.
*/
@Nullable public final String docUrl;

/**
* A human-readable message providing more details about the error. For card errors,
* these messages can be shown to your users.
*/
@Nullable public final String message;

/**
* If the error is parameter-specific, the parameter related to the error.
* For example, you can use this to display a message near the correct form field.
*/
@Nullable public final String param;

/**
* The PaymentMethod object for errors returned on a request involving a PaymentMethod.
*/
@Nullable public final PaymentMethod paymentMethod;

/**
* The type of error returned.
*/
@Nullable public final Type type;

private Error(@NonNull Builder builder) {
charge = builder.mCharge;
code = builder.mCode;
declineCode = builder.mDeclineCode;
docUrl = builder.mDocUrl;
message = builder.mMessage;
param = builder.mParam;
paymentMethod = builder.mPaymentMethod;
type = builder.mType;
}

@Nullable
private static Error fromJson(@Nullable JSONObject errorJson) {
if (errorJson == null) {
return null;
}

return new Builder()
.setCharge(StripeJsonUtils.optString(errorJson, FIELD_CHARGE))
.setCode(StripeJsonUtils.optString(errorJson, FIELD_CODE))
.setDeclineCode(StripeJsonUtils.optString(errorJson, FIELD_DECLINE_CODE))
.setDocUrl(StripeJsonUtils.optString(errorJson, FIELD_DOC_URL))
.setMessage(StripeJsonUtils.optString(errorJson, FIELD_MESSAGE))
.setParam(StripeJsonUtils.optString(errorJson, FIELD_PARAM))
.setPaymentMethod(
PaymentMethod.fromJson(errorJson.optJSONObject(FIELD_PAYMENT_METHOD)))
.setType(Type.fromCode(StripeJsonUtils.optString(errorJson, FIELD_TYPE)))
.build();
}

@Override
public int hashCode() {
return ObjectUtils.hash(charge, code, declineCode, docUrl, message, param,
paymentMethod, type);
}

@Override
public boolean equals(@Nullable Object obj) {
return super.equals(obj) || (obj instanceof Error && typedEquals((Error) obj));
}

private boolean typedEquals(@NonNull Error error) {
return ObjectUtils.equals(charge, error.charge) &&
ObjectUtils.equals(code, error.code) &&
ObjectUtils.equals(declineCode, error.declineCode) &&
ObjectUtils.equals(docUrl, error.docUrl) &&
ObjectUtils.equals(message, error.message) &&
ObjectUtils.equals(param, error.param) &&
ObjectUtils.equals(paymentMethod, error.paymentMethod) &&
ObjectUtils.equals(type, error.type);
}

private static final class Builder implements ObjectBuilder<Error> {
@Nullable private String mCharge;
@Nullable private String mCode;
@Nullable private String mDeclineCode;
@Nullable private String mDocUrl;
@Nullable private String mMessage;
@Nullable private String mParam;
@Nullable private PaymentMethod mPaymentMethod;
@Nullable private Type mType;

@NonNull
private Builder setCharge(@Nullable String charge) {
this.mCharge = charge;
return this;
}

@NonNull
private Builder setCode(@Nullable String code) {
this.mCode = code;
return this;
}

@NonNull
private Builder setDeclineCode(@Nullable String declineCode) {
this.mDeclineCode = declineCode;
return this;
}

@NonNull
private Builder setDocUrl(@Nullable String docUrl) {
this.mDocUrl = docUrl;
return this;
}

@NonNull
private Builder setMessage(@Nullable String message) {
this.mMessage = message;
return this;
}

@NonNull
private Builder setParam(@Nullable String mParam) {
this.mParam = mParam;
return this;
}

@NonNull
private Builder setPaymentMethod(@Nullable PaymentMethod paymentMethod) {
this.mPaymentMethod = paymentMethod;
return this;
}

@NonNull
private Builder setType(@Nullable Type type) {
this.mType = type;
return this;
}

@NonNull
@Override
public Error build() {
return new Error(this);
}
}

public enum Type {
ApiConnectionError("api_connection_error"),
ApiError("api_error"),
AuthenticationError("authentication_error"),
CardError("card_error"),
IdempotencyError("idempotency_error"),
InvalidRequestError("invalid_request_error"),
RateLimitError("rate_limit_error");

@NonNull public final String code;

Type(@NonNull String code) {
this.code = code;
}

@Nullable
private static Type fromCode(@Nullable String typeCode) {
for (Type type : values()) {
if (type.code.equals(typeCode)) {
return type;
}
}

return null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,78 @@ public final class PaymentIntentFixtures {
"}"
));

public static final PaymentIntent PI_WITH_LAST_PAYMENT_ERROR =
Objects.requireNonNull(PaymentIntent.fromString("{\n" +
"\t\"id\": \"pi_1F7J1aCRMbs6FrXfaJcvbxF6\",\n" +
"\t\"object\": \"payment_intent\",\n" +
"\t\"amount\": 1000,\n" +
"\t\"canceled_at\": null,\n" +
"\t\"cancellation_reason\": null,\n" +
"\t\"capture_method\": \"manual\",\n" +
"\t\"client_secret\": \"pi_1F7J1aCRMbs6FrXfaJcvbxF6_secret_mIuDLsSfoo1m6s\",\n" +
"\t\"confirmation_method\": \"automatic\",\n" +
"\t\"created\": 1565775850,\n" +
"\t\"currency\": \"usd\",\n" +
"\t\"description\": \"Example PaymentIntent\",\n" +
"\t\"last_payment_error\": {\n" +
"\t\t\"code\": \"payment_intent_authentication_failure\",\n" +
"\t\t\"doc_url\": \"https://stripe.com/docs/error-codes/payment-intent-authentication-failure\",\n" +
"\t\t\"message\": \"The provided PaymentMethod has failed authentication. You can provide payment_method_data or a new PaymentMethod to attempt to fulfill this PaymentIntent again.\",\n" +
"\t\t\"payment_method\": {\n" +
"\t\t\t\"id\": \"pm_1F7J1bCRMbs6FrXfQKsYwO3U\",\n" +
"\t\t\t\"object\": \"payment_method\",\n" +
"\t\t\t\"billing_details\": {\n" +
"\t\t\t\t\"address\": {\n" +
"\t\t\t\t\t\"city\": null,\n" +
"\t\t\t\t\t\"country\": null,\n" +
"\t\t\t\t\t\"line1\": null,\n" +
"\t\t\t\t\t\"line2\": null,\n" +
"\t\t\t\t\t\"postal_code\": null,\n" +
"\t\t\t\t\t\"state\": null\n" +
"\t\t\t\t},\n" +
"\t\t\t\t\"email\": null,\n" +
"\t\t\t\t\"name\": null,\n" +
"\t\t\t\t\"phone\": null\n" +
"\t\t\t},\n" +
"\t\t\t\"card\": {\n" +
"\t\t\t\t\"brand\": \"visa\",\n" +
"\t\t\t\t\"checks\": {\n" +
"\t\t\t\t\t\"address_line1_check\": null,\n" +
"\t\t\t\t\t\"address_postal_code_check\": null,\n" +
"\t\t\t\t\t\"cvc_check\": null\n" +
"\t\t\t\t},\n" +
"\t\t\t\t\"country\": null,\n" +
"\t\t\t\t\"exp_month\": 8,\n" +
"\t\t\t\t\"exp_year\": 2020,\n" +
"\t\t\t\t\"funding\": \"credit\",\n" +
"\t\t\t\t\"generated_from\": null,\n" +
"\t\t\t\t\"last4\": \"3220\",\n" +
"\t\t\t\t\"three_d_secure_usage\": {\n" +
"\t\t\t\t\t\"supported\": true\n" +
"\t\t\t\t},\n" +
"\t\t\t\t\"wallet\": null\n" +
"\t\t\t},\n" +
"\t\t\t\"created\": 1565775851,\n" +
"\t\t\t\"customer\": null,\n" +
"\t\t\t\"livemode\": false,\n" +
"\t\t\t\"metadata\": {},\n" +
"\t\t\t\"type\": \"card\"\n" +
"\t\t},\n" +
"\t\t\"type\": \"invalid_request_error\"\n" +
"\t},\n" +
"\t\"livemode\": false,\n" +
"\t\"next_action\": null,\n" +
"\t\"payment_method\": null,\n" +
"\t\"payment_method_types\": [\n" +
"\t\t\"card\"\n" +
"\t],\n" +
"\t\"receipt_email\": null,\n" +
"\t\"setup_future_usage\": null,\n" +
"\t\"shipping\": null,\n" +
"\t\"source\": null,\n" +
"\t\"status\": \"requires_payment_method\"\n" +
"}"));

public static final PaymentIntent.RedirectData REDIRECT_DATA =
new PaymentIntent.RedirectData("https://example.com",
"yourapp://post-authentication-return-url");
Expand Down
Loading