From ac5bf0bc47190f0266ad24ab41197f4a39ac406d Mon Sep 17 00:00:00 2001 From: Michael Shafrir Date: Thu, 29 Aug 2019 11:14:50 -0400 Subject: [PATCH] Add support for cancellation_reason attribute to PaymentIntent https://stripe.com/docs/api/payment_intents/object#payment_intent_object-cancellation_reason --- .../stripe/android/model/PaymentIntent.java | 56 ++++++++++-- .../android/model/PaymentIntentFixtures.java | 68 ++++++++++++++ .../android/model/PaymentIntentTest.java | 89 ++----------------- 3 files changed, 127 insertions(+), 86 deletions(-) diff --git a/stripe/src/main/java/com/stripe/android/model/PaymentIntent.java b/stripe/src/main/java/com/stripe/android/model/PaymentIntent.java index 9d9957d285d..19db58f1e2d 100644 --- a/stripe/src/main/java/com/stripe/android/model/PaymentIntent.java +++ b/stripe/src/main/java/com/stripe/android/model/PaymentIntent.java @@ -35,7 +35,8 @@ public final class PaymentIntent extends StripeModel implements StripeIntent { private static final String FIELD_OBJECT = "object"; private static final String FIELD_AMOUNT = "amount"; private static final String FIELD_CREATED = "created"; - private static final String FIELD_CANCELED = "canceled_at"; + private static final String FIELD_CANCELED_AT = "canceled_at"; + private static final String FIELD_CANCELLATION_REASON = "cancellation_reason"; private static final String FIELD_CAPTURE_METHOD = "capture_method"; private static final String FIELD_CLIENT_SECRET = "client_secret"; private static final String FIELD_CONFIRMATION_METHOD = "confirmation_method"; @@ -58,6 +59,7 @@ public final class PaymentIntent extends StripeModel implements StripeIntent { @NonNull private final List mPaymentMethodTypes; @Nullable private final Long mAmount; private final long mCanceledAt; + @Nullable private final CancellationReason mCancellationReason; @Nullable private final String mCaptureMethod; @Nullable private final String mClientSecret; @Nullable private final String mConfirmationMethod; @@ -108,6 +110,14 @@ public long getCanceledAt() { return mCanceledAt; } + /** + * @return Reason for cancellation of this PaymentIntent + */ + @Nullable + public CancellationReason getCancellationReason() { + return mCancellationReason; + } + /** * @return One of automatic (default) or manual. * @@ -311,6 +321,7 @@ private PaymentIntent( @NonNull List paymentMethodTypes, @Nullable Long amount, long canceledAt, + @Nullable CancellationReason cancellationReason, @Nullable String captureMethod, @Nullable String clientSecret, @Nullable String confirmationMethod, @@ -330,6 +341,7 @@ private PaymentIntent( mPaymentMethodTypes = paymentMethodTypes; mAmount = amount; mCanceledAt = canceledAt; + mCancellationReason = cancellationReason; mCaptureMethod = captureMethod; mClientSecret = clientSecret; mConfirmationMethod = confirmationMethod; @@ -374,7 +386,9 @@ public static PaymentIntent fromJson(@Nullable JSONObject jsonObject) { final List paymentMethodTypes = jsonArrayToList( jsonObject.optJSONArray(FIELD_PAYMENT_METHOD_TYPES)); final Long amount = optLong(jsonObject, FIELD_AMOUNT); - final Long canceledAt = jsonObject.optLong(FIELD_CANCELED); + final long canceledAt = jsonObject.optLong(FIELD_CANCELED_AT); + final CancellationReason cancellationReason = + CancellationReason.fromCode(optString(jsonObject, FIELD_CANCELLATION_REASON)); final String captureMethod = optString(jsonObject, FIELD_CAPTURE_METHOD); final String clientSecret = optString(jsonObject, FIELD_CLIENT_SECRET); final String confirmationMethod = optString(jsonObject, FIELD_CONFIRMATION_METHOD); @@ -398,6 +412,7 @@ public static PaymentIntent fromJson(@Nullable JSONObject jsonObject) { paymentMethodTypes, amount, canceledAt, + cancellationReason, captureMethod, clientSecret, confirmationMethod, @@ -425,6 +440,7 @@ private boolean typedEquals(@NonNull PaymentIntent paymentIntent) { && ObjectUtils.equals(mObjectType, paymentIntent.mObjectType) && ObjectUtils.equals(mAmount, paymentIntent.mAmount) && ObjectUtils.equals(mCanceledAt, paymentIntent.mCanceledAt) + && ObjectUtils.equals(mCancellationReason, paymentIntent.mCancellationReason) && ObjectUtils.equals(mCaptureMethod, paymentIntent.mCaptureMethod) && ObjectUtils.equals(mClientSecret, paymentIntent.mClientSecret) && ObjectUtils.equals(mConfirmationMethod, paymentIntent.mConfirmationMethod) @@ -445,10 +461,11 @@ private boolean typedEquals(@NonNull PaymentIntent paymentIntent) { @Override public int hashCode() { - return ObjectUtils.hash(mId, mObjectType, mAmount, mCanceledAt, mCaptureMethod, - mClientSecret, mConfirmationMethod, mCreated, mCurrency, mDescription, mLiveMode, - mReceiptEmail, mSource, mStatus, mPaymentMethodTypes, mNextAction, mNextActionType, - mPaymentMethodId, mSetupFutureUsage, mLastPaymentError); + return ObjectUtils.hash(mId, mObjectType, mAmount, mCanceledAt, mCancellationReason, + mCaptureMethod, mClientSecret, mConfirmationMethod, mCreated, mCurrency, + mDescription, mLiveMode, mReceiptEmail, mSource, mStatus, mPaymentMethodTypes, + mNextAction, mNextActionType, mPaymentMethodId, mSetupFutureUsage, + mLastPaymentError); } /** @@ -657,4 +674,31 @@ private static Type fromCode(@Nullable String typeCode) { } } } + + enum CancellationReason { + Duplicate("duplicate"), + Fraudulent("fraudulent"), + RequestedByCustomer("requested_by_customer"), + Abandoned("abandoned"), + FailedInvoice("failed_invoice"), + VoidInvoice("void_invoice"), + Automatic("automatic"); + + @NonNull private final String code; + + CancellationReason(@NonNull String code) { + this.code = code; + } + + @Nullable + static CancellationReason fromCode(@Nullable String code) { + for (CancellationReason cancellationReason : values()) { + if (cancellationReason.code.equals(code)) { + return cancellationReason; + } + } + + return null; + } + } } diff --git a/stripe/src/test/java/com/stripe/android/model/PaymentIntentFixtures.java b/stripe/src/test/java/com/stripe/android/model/PaymentIntentFixtures.java index b5f060c6ff8..262f87fc4c2 100644 --- a/stripe/src/test/java/com/stripe/android/model/PaymentIntentFixtures.java +++ b/stripe/src/test/java/com/stripe/android/model/PaymentIntentFixtures.java @@ -335,6 +335,74 @@ public final class PaymentIntentFixtures { "\t\"status\": \"requires_payment_method\"\n" + "}")); + public static final PaymentIntent CANCELLED = Objects.requireNonNull(PaymentIntent.fromString( + "{\n" + + " \"id\": \"pi_1FCpMECRMbs6FrXfVulorSf5\",\n" + + " \"object\": \"payment_intent\",\n" + + " \"amount\": 4000,\n" + + " \"amount_capturable\": 0,\n" + + " \"amount_received\": 0,\n" + + " \"application\": null,\n" + + " \"application_fee_amount\": null,\n" + + " \"canceled_at\": 1567091866,\n" + + " \"cancellation_reason\": \"abandoned\",\n" + + " \"capture_method\": \"automatic\",\n" + + " \"charges\": {\n" + + " \"object\": \"list\",\n" + + " \"data\": [],\n" + + " \"has_more\": false,\n" + + " \"total_count\": 0,\n" + + " \"url\": \"/v1/charges?payment_intent=pi_1FCpMECRMbs6FrXfVulorSf5\"\n" + + " },\n" + + " \"client_secret\": \"pi_1FCpMECRMbs6FrXfVulorSf5_secret_oSppt5A\",\n" + + " \"confirmation_method\": \"manual\",\n" + + " \"created\": 1567091778,\n" + + " \"currency\": \"usd\",\n" + + " \"customer\": \"cus_FWhpaTLIPWLhpJ\",\n" + + " \"description\": \"Example PaymentIntent\",\n" + + " \"invoice\": null,\n" + + " \"last_payment_error\": null,\n" + + " \"livemode\": false,\n" + + " \"metadata\": {\n" + + " \"order_id\": \"5278735C-1F40-407D-933A-286E463E72D8\"\n" + + " },\n" + + " \"next_action\": null,\n" + + " \"on_behalf_of\": null,\n" + + " \"payment_method\": null,\n" + + " \"payment_method_options\": {\n" + + " \"card\": {\n" + + " \"request_three_d_secure\": \"automatic\"\n" + + " }\n" + + " },\n" + + " \"payment_method_types\": [\n" + + " \"card\"\n" + + " ],\n" + + " \"receipt_email\": null,\n" + + " \"review\": null,\n" + + " \"setup_future_usage\": null,\n" + + " \"shipping\": {\n" + + " \"address\": {\n" + + " \"city\": \"San Francisco\",\n" + + " \"country\": \"US\",\n" + + " \"line1\": \"123 Market St\",\n" + + " \"line2\": \"#345\",\n" + + " \"postal_code\": \"94107\",\n" + + " \"state\": \"CA\"\n" + + " },\n" + + " \"carrier\": null,\n" + + " \"name\": \"Fake Name\",\n" + + " \"phone\": \"(555) 555-5555\",\n" + + " \"tracking_number\": null\n" + + " },\n" + + " \"source\": null,\n" + + " \"statement_descriptor\": null,\n" + + " \"statement_descriptor_suffix\": null,\n" + + " \"status\": \"canceled\",\n" + + " \"transfer_data\": null,\n" + + " \"transfer_group\": null\n" + + "}" + )); + public static final PaymentIntent.RedirectData REDIRECT_DATA = new PaymentIntent.RedirectData("https://example.com", "yourapp://post-authentication-return-url"); diff --git a/stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.java b/stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.java index 8211529578c..2a758969f57 100644 --- a/stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.java +++ b/stripe/src/test/java/com/stripe/android/model/PaymentIntentTest.java @@ -14,28 +14,6 @@ @RunWith(RobolectricTestRunner.class) public class PaymentIntentTest { - private static final String PAYMENT_INTENT_WITH_SOURCE_JSON = "{\n" + - " \"id\": \"pi_1CkiBMLENEVhOs7YMtUehLau\",\n" + - " \"object\": \"payment_intent\",\n" + - " \"payment_method_types\": [\n" + - " \"card\"\n" + - " ],\n" + - " \"amount\": 1000,\n" + - " \"canceled_at\": 1530839340,\n" + - " \"capture_method\": \"automatic\",\n" + - " \"client_secret\": \"pi_1CkiBMLENEVhOs7YMtUehLau_secret_s4O8SDh7s6spSmHDw1VaYPGZA\",\n" + - " \"confirmation_method\": \"publishable\",\n" + - " \"created\": 1530838340,\n" + - " \"currency\": \"usd\",\n" + - " \"description\": \"Example PaymentIntent charge\",\n" + - " \"livemode\": false,\n" + - " \"next_action\": null,\n" + - " \"receipt_email\": null,\n" + - " \"shipping\": null,\n" + - " \"source\": \"src_1CkiC3LENEVhOs7YMSa4yx4G\",\n" + - " \"status\": \"succeeded\"\n" + - "}\n"; - private static final String BAD_URL = "nonsense-blahblah"; private static final String PAYMENT_INTENT_WITH_SOURCE_WITH_BAD_AUTH_URL_JSON = "{\n" + @@ -63,64 +41,6 @@ public class PaymentIntentTest { " \"status\": \"requires_action\"\n" + "}\n"; - private static final String PAYMENT_INTENT_WITH_PAYMENT_METHODS_JSON = "{\n" + - " \"id\": \"pi_Aabcxyz01aDfoo\",\n" + - " \"object\": \"payment_intent\",\n" + - " \"amount\": 750,\n" + - " \"amount_capturable\": 0,\n" + - " \"amount_received\": 750,\n" + - " \"application\": null,\n" + - " \"application_fee_amount\": null,\n" + - " \"canceled_at\": null,\n" + - " \"cancellation_reason\": null,\n" + - " \"capture_method\": \"automatic\",\n" + - " \"charges\": {\n" + - " \"object\": \"list\",\n" + - " \"data\": [],\n" + - " \"has_more\": false,\n" + - " \"total_count\": 0,\n" + - " \"url\": \"/v1/charges?payment_intent=pi_Aabcxyz01aDfoo\"\n" + - " },\n" + - " \"client_secret\": null,\n" + - " \"confirmation_method\": \"publishable\",\n" + - " \"created\": 123456789,\n" + - " \"currency\": \"usd\",\n" + - " \"customer\": null,\n" + - " \"description\": \"PaymentIntent Description\",\n" + - " \"last_payment_error\": null,\n" + - " \"livemode\": false,\n" + - " \"metadata\": {\n" + - " \"order_id\": \"123456789\"\n" + - " },\n" + - " \"next_action\": null,\n" + - " \"on_behalf_of\": null,\n" + - " \"payment_method\": null,\n" + - " \"payment_method_types\": [\n" + - " \"card\"\n" + - " ],\n" + - " \"receipt_email\": \"jenny@example.com\",\n" + - " \"review\": null,\n" + - " \"shipping\": {\n" + - " \"address\": {\n" + - " \"city\": \"Stockholm\",\n" + - " \"country\": \"Sweden\",\n" + - " \"line1\": \"Mega street 5\",\n" + - " \"line2\": \"Mega street 5\",\n" + - " \"postal_code\": \"12233JJHH\",\n" + - " \"state\": \"NYC\"\n" + - " },\n" + - " \"carrier\": null,\n" + - " \"name\": \"Mohit Name\",\n" + - " \"phone\": null,\n" + - " \"tracking_number\": null\n" + - " },\n" + - " \"source\": \"src_1E884r2eZvKYlo2CTft0qEyY\",\n" + - " \"statement_descriptor\": \"PaymentIntent Statement Descriptor\",\n" + - " \"status\": \"succeeded\",\n" + - " \"transfer_data\": null,\n" + - " \"transfer_group\": null\n" + - "}"; - private static final String PARTIAL_PAYMENT_INTENT_WITH_REDIRECT_URL_JSON = "{\n" + "\t\"id\": \"pi_Aabcxyz01aDfoo\",\n" + "\t\"object\": \"payment_intent\",\n" + @@ -198,6 +118,7 @@ public void parsePaymentIntentWithPaymentMethods() { assertEquals("manual", paymentIntent.getConfirmationMethod()); assertNotNull(paymentIntent.getNextAction()); assertEquals("jenny@example.com", paymentIntent.getReceiptEmail()); + assertNull(paymentIntent.getCancellationReason()); } @Test @@ -246,4 +167,12 @@ public void getLastPaymentError_parsesCorrectly() { lastPaymentError.message ); } + + @Test + public void testCanceled() { + assertEquals(PaymentIntent.CancellationReason.Abandoned, + PaymentIntentFixtures.CANCELLED.getCancellationReason()); + assertEquals(1567091866L, + PaymentIntentFixtures.CANCELLED.getCanceledAt()); + } }