From 53b18764a0381b4bc0d4624c266cf358a8fa8241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:00:16 +0100 Subject: [PATCH] fix(payments): align retry and customer retry types with the spec customer_retry on POST /hosted-payments and POST /payment-links only accepts max_attempts (0 to 5), so it now uses CustomerRetry instead of PaymentRetryRequest. PaymentRetryRequest.enabled is no longer deprecated: it is the required switch for asynchronous retries on POST /payments. The payment retry response gains attemptsMade, and the request payment retry response no longer reports a max_attempts the API did not return. --- .../flow/entities/CustomerRetry.java | 9 +- .../requests/PaymentSessionCreateRequest.java | 2 +- .../retry/Retry.java | 15 +-- .../payments/hosted/HostedPaymentRequest.java | 6 +- .../payments/links/PaymentLinkRequest.java | 6 +- .../request/DowntimeRetryRequest.java | 7 +- .../payments/request/DunningRetryRequest.java | 13 +- .../payments/request/PaymentRequest.java | 2 +- .../payments/request/PaymentRetryRequest.java | 33 +++-- .../payments/response/GetPaymentResponse.java | 4 + .../payments/response/PaymentResponse.java | 4 + .../response/PaymentRetryResponse.java | 28 ++++- src/test/java/com/checkout/TestHelper.java | 6 +- .../CustomerRetrySerializationTest.java | 90 ++++++++++++++ .../retry/RetrySerializationTest.java | 48 ++++++++ .../PaymentRetryRequestSerializationTest.java | 114 +++++++++--------- ...PaymentRetryResponseSerializationTest.java | 60 +++++++++ 17 files changed, 351 insertions(+), 96 deletions(-) create mode 100644 src/test/java/com/checkout/handlepaymentsandpayouts/flow/entities/CustomerRetrySerializationTest.java create mode 100644 src/test/java/com/checkout/handlepaymentsandpayouts/payments/postpayments/responses/requestapaymentorpayoutresponsecreated/retry/RetrySerializationTest.java create mode 100644 src/test/java/com/checkout/payments/response/PaymentRetryResponseSerializationTest.java diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/flow/entities/CustomerRetry.java b/src/main/java/com/checkout/handlepaymentsandpayouts/flow/entities/CustomerRetry.java index bcc2384e2..7ffc83fdf 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/flow/entities/CustomerRetry.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/flow/entities/CustomerRetry.java @@ -6,7 +6,9 @@ import lombok.NoArgsConstructor; /** - * Configuration for asynchronous retries + * Configures the parameters for customer retries. A customer retry is a payment attempt performed by the + * customer on the checkout page. This differs from authorization request retries performed in the back end + * by Checkout.com. Used by hosted payments, payment links and payment sessions. */ @Data @Builder @@ -15,8 +17,9 @@ public final class CustomerRetry { /** - * The maximum number of authorization retry attempts, excluding the initial authorization. Default: 5 - * [Optional] + * The maximum number of customer retry attempts, excluding the initial payment attempt. + * Minimum 0, maximum 5. Default: 5. The SDK sends 5 when not set. + * [Optional] for hosted payments and payment links; [Required] for payment sessions. */ @Builder.Default private Integer maxAttempts = 5; diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/flow/requests/PaymentSessionCreateRequest.java b/src/main/java/com/checkout/handlepaymentsandpayouts/flow/requests/PaymentSessionCreateRequest.java index 781a9dbb9..1edfcdd4b 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/flow/requests/PaymentSessionCreateRequest.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/flow/requests/PaymentSessionCreateRequest.java @@ -48,7 +48,7 @@ public final class PaymentSessionCreateRequest extends PaymentSessionCreateBase private PaymentMethodConfiguration paymentMethodConfiguration; /** - * Configuration for asynchronous retries. + * Configuration for customer retries. When sent, max_attempts is required. * [Optional] */ private CustomerRetry customerRetry; diff --git a/src/main/java/com/checkout/handlepaymentsandpayouts/payments/postpayments/responses/requestapaymentorpayoutresponsecreated/retry/Retry.java b/src/main/java/com/checkout/handlepaymentsandpayouts/payments/postpayments/responses/requestapaymentorpayoutresponsecreated/retry/Retry.java index 83ff9b159..ad2cfb1f5 100644 --- a/src/main/java/com/checkout/handlepaymentsandpayouts/payments/postpayments/responses/requestapaymentorpayoutresponsecreated/retry/Retry.java +++ b/src/main/java/com/checkout/handlepaymentsandpayouts/payments/postpayments/responses/requestapaymentorpayoutresponsecreated/retry/Retry.java @@ -1,16 +1,14 @@ package com.checkout.handlepaymentsandpayouts.payments.postpayments.responses.requestapaymentorpayoutresponsecreated.retry; -import lombok.Builder; - import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.time.Instant; /** - * retry - * Configuration relating to asynchronous retries + * Configuration relating to asynchronous retries. */ @Data @Builder @@ -20,17 +18,16 @@ public final class Retry { /** * Indicates whether asynchronous retries are enabled for the payment. - * [Optional] + * [Required] */ private Boolean enabled; /** - * Default: 6 The maximum number of authorization retry attempts, excluding the initial authorization. + * The maximum number of authorization retry attempts, excluding the initial authorization. + * Minimum 1, maximum 15. Default: 6. * [Optional] - * [ 1 .. 15 ] */ - @Builder.Default - private Integer maxAttempts = 6; + private Integer maxAttempts; /** * A timestamp that details the date on which the retry schedule expires, in ISO 8601 format. diff --git a/src/main/java/com/checkout/payments/hosted/HostedPaymentRequest.java b/src/main/java/com/checkout/payments/hosted/HostedPaymentRequest.java index 0cbb4d44f..469356686 100644 --- a/src/main/java/com/checkout/payments/hosted/HostedPaymentRequest.java +++ b/src/main/java/com/checkout/payments/hosted/HostedPaymentRequest.java @@ -5,6 +5,7 @@ import com.checkout.common.CustomerRequest; import com.checkout.common.PaymentSourceType; import com.checkout.common.Product; +import com.checkout.handlepaymentsandpayouts.flow.entities.CustomerRetry; import com.checkout.payments.AuthorizationType; import com.checkout.payments.BillingDescriptor; import com.checkout.payments.BillingInformation; @@ -17,7 +18,6 @@ import com.checkout.payments.RiskRequest; import com.checkout.payments.ShippingDetails; import com.checkout.payments.ThreeDSRequest; -import com.checkout.payments.request.PaymentRetryRequest; import com.checkout.payments.sender.Sender; import com.checkout.payments.PaymentMethodConfiguration; import com.google.gson.annotations.SerializedName; @@ -177,10 +177,10 @@ public final class HostedPaymentRequest { private RiskRequest risk; /** - * The customer retry configuration for failed payments. + * Configures the parameters for customer retries (payment attempts performed by the customer on the checkout page). * [Optional] */ - private PaymentRetryRequest customerRetry; + private CustomerRetry customerRetry; /** * The sender of the payment. Required for financial regulations in some jurisdictions. diff --git a/src/main/java/com/checkout/payments/links/PaymentLinkRequest.java b/src/main/java/com/checkout/payments/links/PaymentLinkRequest.java index 37ea56804..ee689f43d 100644 --- a/src/main/java/com/checkout/payments/links/PaymentLinkRequest.java +++ b/src/main/java/com/checkout/payments/links/PaymentLinkRequest.java @@ -5,6 +5,7 @@ import com.checkout.common.CustomerRequest; import com.checkout.common.PaymentSourceType; import com.checkout.common.Product; +import com.checkout.handlepaymentsandpayouts.flow.entities.CustomerRetry; import com.checkout.payments.AuthorizationType; import com.checkout.payments.BillingDescriptor; import com.checkout.payments.BillingInformation; @@ -18,7 +19,6 @@ import com.checkout.payments.ShippingDetails; import com.checkout.payments.ThreeDSRequest; import com.checkout.payments.PaymentInstruction; -import com.checkout.payments.request.PaymentRetryRequest; import com.checkout.payments.sender.PaymentSender; import com.google.gson.annotations.SerializedName; import lombok.Builder; @@ -171,10 +171,10 @@ public final class PaymentLinkRequest { private RiskRequest risk; /** - * The customer retry configuration for failed payments. + * Configures the parameters for customer retries (payment attempts performed by the customer on the checkout page). * [Optional] */ - private PaymentRetryRequest customerRetry; + private CustomerRetry customerRetry; /** * The sender of the payment. Required for financial regulations in some jurisdictions. diff --git a/src/main/java/com/checkout/payments/request/DowntimeRetryRequest.java b/src/main/java/com/checkout/payments/request/DowntimeRetryRequest.java index 422ace8da..52942d662 100644 --- a/src/main/java/com/checkout/payments/request/DowntimeRetryRequest.java +++ b/src/main/java/com/checkout/payments/request/DowntimeRetryRequest.java @@ -5,6 +5,9 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * Configuration of Downtime retries. + */ @Data @Builder @AllArgsConstructor @@ -12,8 +15,8 @@ public final class DowntimeRetryRequest { /** - * Whether downtime retries are enabled. - * [Optional] + * Indicates if Checkout.com retries the payment when it's declined due to issuer or acquirer downtime. + * [Required] */ private Boolean enabled; diff --git a/src/main/java/com/checkout/payments/request/DunningRetryRequest.java b/src/main/java/com/checkout/payments/request/DunningRetryRequest.java index ee2d22263..61c677e59 100644 --- a/src/main/java/com/checkout/payments/request/DunningRetryRequest.java +++ b/src/main/java/com/checkout/payments/request/DunningRetryRequest.java @@ -5,6 +5,9 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * Configuration of Dunning retries. + */ @Data @Builder @AllArgsConstructor @@ -12,19 +15,21 @@ public final class DunningRetryRequest { /** - * Whether dunning retries are enabled. - * [Optional] + * Indicates if Checkout.com retries the payment when it's declined with a supported decline code. + * [Required] */ private Boolean enabled; /** - * The maximum number of retry attempts. + * The maximum number of authorization retry attempts, excluding the initial authorization. + * Minimum 1, maximum 15. Default: 6. * [Optional] */ private Integer maxAttempts; /** - * The number of days after which retries stop. + * The maximum number of days between the initial request and the final retry attempt. + * Minimum 1, maximum 60. Default: 30. * [Optional] */ private Integer endAfterDays; diff --git a/src/main/java/com/checkout/payments/request/PaymentRequest.java b/src/main/java/com/checkout/payments/request/PaymentRequest.java index 78913fcb9..6c93a7f1e 100644 --- a/src/main/java/com/checkout/payments/request/PaymentRequest.java +++ b/src/main/java/com/checkout/payments/request/PaymentRequest.java @@ -253,7 +253,7 @@ public final class PaymentRequest { private List items; /** - * The retry configuration for failed payment attempts. + * Configuration of asynchronous retries. * [Optional] */ private PaymentRetryRequest retry; diff --git a/src/main/java/com/checkout/payments/request/PaymentRetryRequest.java b/src/main/java/com/checkout/payments/request/PaymentRetryRequest.java index 0603d8664..8ef213368 100644 --- a/src/main/java/com/checkout/payments/request/PaymentRetryRequest.java +++ b/src/main/java/com/checkout/payments/request/PaymentRetryRequest.java @@ -5,12 +5,21 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * Configuration of asynchronous retries. + */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public final class PaymentRetryRequest { + /** + * Indicates whether asynchronous retries are enabled for the payment. + * [Required] + */ + private Boolean enabled; + /** * Configuration of asynchronous Dunning retries. * [Optional] @@ -23,24 +32,24 @@ public final class PaymentRetryRequest { */ private DowntimeRetryRequest downtime; - // ======================================== - // Deprecated — use dunning.enabled instead - // ======================================== - - /** - * @deprecated Use {@link DunningRetryRequest#enabled} via {@link #dunning} instead. - */ - @Deprecated - private Boolean enabled; - /** - * @deprecated Use {@link DunningRetryRequest#maxAttempts} via {@link #dunning} instead. + * Not part of the retry object in the API. If set, it is still sent at the top level of retry, + * where the API does not read it. + * [Optional] + * + * @deprecated Use {@link DunningRetryRequest#getMaxAttempts()} via {@link #dunning} instead. + * This property will be removed in the next major version. */ @Deprecated private Integer maxAttempts; /** - * @deprecated Use {@link DunningRetryRequest#endAfterDays} via {@link #dunning} instead. + * Not part of the retry object in the API. If set, it is still sent at the top level of retry, + * where the API does not read it. + * [Optional] + * + * @deprecated Use {@link DunningRetryRequest#getEndAfterDays()} via {@link #dunning} instead. + * This property will be removed in the next major version. */ @Deprecated private Integer endAfterDays; diff --git a/src/main/java/com/checkout/payments/response/GetPaymentResponse.java b/src/main/java/com/checkout/payments/response/GetPaymentResponse.java index 65056d394..193aeba55 100644 --- a/src/main/java/com/checkout/payments/response/GetPaymentResponse.java +++ b/src/main/java/com/checkout/payments/response/GetPaymentResponse.java @@ -116,6 +116,10 @@ public final class GetPaymentResponse extends Resource { private List actions; + /** + * Configuration relating to asynchronous retries. + * [Optional] + */ private PaymentRetryResponse retry; private PanProcessedType panTypeProcessed; diff --git a/src/main/java/com/checkout/payments/response/PaymentResponse.java b/src/main/java/com/checkout/payments/response/PaymentResponse.java index 35ca5d6c2..1d63dcfa1 100644 --- a/src/main/java/com/checkout/payments/response/PaymentResponse.java +++ b/src/main/java/com/checkout/payments/response/PaymentResponse.java @@ -74,6 +74,10 @@ public final class PaymentResponse extends Resource implements Serializable { private String schemeId; + /** + * Configuration relating to asynchronous retries. + * [Optional] + */ private PaymentRetryResponse retry; private String paymentIp; diff --git a/src/main/java/com/checkout/payments/response/PaymentRetryResponse.java b/src/main/java/com/checkout/payments/response/PaymentRetryResponse.java index 574257771..e810c96d4 100644 --- a/src/main/java/com/checkout/payments/response/PaymentRetryResponse.java +++ b/src/main/java/com/checkout/payments/response/PaymentRetryResponse.java @@ -1,24 +1,50 @@ package com.checkout.payments.response; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.time.Instant; +/** + * Configuration relating to asynchronous retries. Returned by request a payment and by get payment details. + */ @Data @Builder +@NoArgsConstructor +@AllArgsConstructor public final class PaymentRetryResponse { /** * Indicates whether asynchronous retries are enabled for the payment. - * [Optional] + * [Required] */ private Boolean enabled; + /** + * The number of retries attempted so far. Minimum 0. Returned by get payment details only. + * [Optional] + */ + private Integer attemptsMade; + + /** + * The maximum number of authorization retry attempts, excluding the initial authorization. + * Minimum 1, maximum 15. Default: 6. + * [Optional] + */ private Integer maxAttempts; + /** + * A timestamp that details the date on which the retry schedule expires, in ISO 8601 format. + * [Optional] + */ private Instant endsOn; + /** + * A timestamp that details the date on which the next authorization attempt will take place, in ISO 8601 format. + * [Optional] + */ private Instant nextAttemptOn; } diff --git a/src/test/java/com/checkout/TestHelper.java b/src/test/java/com/checkout/TestHelper.java index e3ebe0e01..10dec700d 100644 --- a/src/test/java/com/checkout/TestHelper.java +++ b/src/test/java/com/checkout/TestHelper.java @@ -13,6 +13,7 @@ import com.checkout.common.PaymentSourceType; import com.checkout.common.Phone; import com.checkout.common.Product; +import com.checkout.handlepaymentsandpayouts.flow.entities.CustomerRetry; import com.checkout.instruments.previous.InstrumentCustomerRequest; import com.checkout.payments.Applepay; import com.checkout.payments.BillingDescriptor; @@ -36,7 +37,6 @@ import com.checkout.payments.hosted.HostedPaymentRequest; import com.checkout.payments.PaymentPurposeType; import com.checkout.payments.links.PaymentLinkRequest; -import com.checkout.payments.request.PaymentRetryRequest; import com.checkout.payments.request.source.contexts.PaymentContextsKlarnaSource; import com.checkout.payments.request.source.contexts.PaymentContextsPayPalSource; import com.checkout.payments.request.source.contexts.PaymentContextsStcpaySource; @@ -169,7 +169,7 @@ public static PaymentLinkRequest createPaymentLinksRequest(final String referenc .metadata(Collections.singletonMap("key", "value")) .threeDS(createThreeDS()) .risk(RiskRequest.builder().enabled(false).build()) - .customerRetry(PaymentRetryRequest.builder() + .customerRetry(CustomerRetry.builder() .maxAttempts(3) .build()) .sender(PaymentInstrumentSender.builder() @@ -265,7 +265,7 @@ public static HostedPaymentRequest createHostedPaymentRequest(final String refer .risk(RiskRequest.builder() .enabled(false) .build()) - .customerRetry(PaymentRetryRequest.builder() + .customerRetry(CustomerRetry.builder() .maxAttempts(2) .build()) .sender(PaymentInstrumentSender.builder() diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/flow/entities/CustomerRetrySerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/flow/entities/CustomerRetrySerializationTest.java new file mode 100644 index 000000000..cca9997c7 --- /dev/null +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/flow/entities/CustomerRetrySerializationTest.java @@ -0,0 +1,90 @@ +package com.checkout.handlepaymentsandpayouts.flow.entities; + +import com.checkout.GsonSerializer; +import com.checkout.handlepaymentsandpayouts.flow.requests.PaymentSessionCreateRequest; +import com.checkout.payments.hosted.HostedPaymentRequest; +import com.checkout.payments.links.PaymentLinkRequest; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CustomerRetrySerializationTest { + + private final GsonSerializer serializer = new GsonSerializer(); + + @Test + void shouldSerializeSwaggerExample() { + final String json = serializer.toJson(CustomerRetry.builder().maxAttempts(2).build()); + + assertEquals("{\"max_attempts\":2}", json); + } + + @Test + void shouldSendDefaultFiveWhenNotSet() { + assertEquals("{\"max_attempts\":5}", serializer.toJson(CustomerRetry.builder().build())); + } + + @Test + void shouldSerializeZeroAttempts() { + final String json = serializer.toJson(CustomerRetry.builder().maxAttempts(0).build()); + + assertEquals("{\"max_attempts\":0}", json); + } + + @Test + void shouldRoundTrip() { + final CustomerRetry original = CustomerRetry.builder().maxAttempts(3).build(); + + final CustomerRetry deserialized = serializer.fromJson(serializer.toJson(original), CustomerRetry.class); + + assertEquals(original, deserialized); + assertEquals(3, deserialized.getMaxAttempts()); + } + + @Test + void shouldDeserializeSwaggerExample() { + final CustomerRetry retry = serializer.fromJson("{\"max_attempts\":2}", CustomerRetry.class); + + assertEquals(2, retry.getMaxAttempts()); + } + + @Test + void shouldSendOnlyMaxAttemptsFromHostedPayments() { + final HostedPaymentRequest request = HostedPaymentRequest.builder() + .customerRetry(CustomerRetry.builder().maxAttempts(2).build()) + .build(); + + final String json = serializer.toJson(request); + + assertCustomerRetryIsExactly(json); + } + + @Test + void shouldSendOnlyMaxAttemptsFromPaymentLinks() { + final PaymentLinkRequest request = PaymentLinkRequest.builder() + .customerRetry(CustomerRetry.builder().maxAttempts(2).build()) + .build(); + + final String json = serializer.toJson(request); + + assertCustomerRetryIsExactly(json); + } + + @Test + void shouldSendOnlyMaxAttemptsFromPaymentSessions() { + final PaymentSessionCreateRequest request = PaymentSessionCreateRequest.builder() + .customerRetry(CustomerRetry.builder().maxAttempts(2).build()) + .build(); + + final String json = serializer.toJson(request); + + assertCustomerRetryIsExactly(json); + } + + private static void assertCustomerRetryIsExactly(final String json) { + final JsonObject customerRetry = JsonParser.parseString(json).getAsJsonObject().getAsJsonObject("customer_retry"); + assertEquals("{\"max_attempts\":2}", customerRetry.toString()); + } +} diff --git a/src/test/java/com/checkout/handlepaymentsandpayouts/payments/postpayments/responses/requestapaymentorpayoutresponsecreated/retry/RetrySerializationTest.java b/src/test/java/com/checkout/handlepaymentsandpayouts/payments/postpayments/responses/requestapaymentorpayoutresponsecreated/retry/RetrySerializationTest.java new file mode 100644 index 000000000..1cb9910f7 --- /dev/null +++ b/src/test/java/com/checkout/handlepaymentsandpayouts/payments/postpayments/responses/requestapaymentorpayoutresponsecreated/retry/RetrySerializationTest.java @@ -0,0 +1,48 @@ +package com.checkout.handlepaymentsandpayouts.payments.postpayments.responses.requestapaymentorpayoutresponsecreated.retry; + +import com.checkout.GsonSerializer; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RetrySerializationTest { + + private final GsonSerializer serializer = new GsonSerializer(); + + @Test + void shouldDeserializeAllProperties() { + final String json = "{\"enabled\":true,\"max_attempts\":6," + + "\"ends_on\":\"2019-08-24T14:15:22Z\",\"next_attempt_on\":\"2019-08-25T14:15:22Z\"}"; + + final Retry retry = serializer.fromJson(json, Retry.class); + + assertTrue(retry.getEnabled()); + assertEquals(6, retry.getMaxAttempts()); + assertEquals(Instant.parse("2019-08-24T14:15:22Z"), retry.getEndsOn()); + assertEquals(Instant.parse("2019-08-25T14:15:22Z"), retry.getNextAttemptOn()); + } + + @Test + void shouldNotInventMaxAttemptsWhenAbsent() { + assertNull(serializer.fromJson("{\"enabled\":true}", Retry.class).getMaxAttempts()); + } + + @Test + void shouldRoundTrip() { + final Retry original = Retry.builder() + .enabled(false) + .maxAttempts(1) + .endsOn(Instant.parse("2019-08-24T14:15:22Z")) + .nextAttemptOn(Instant.parse("2019-08-25T14:15:22Z")) + .build(); + + final String json = serializer.toJson(original); + + assertTrue(json.contains("\"enabled\":false")); + assertEquals(original, serializer.fromJson(json, Retry.class)); + } +} diff --git a/src/test/java/com/checkout/payments/request/PaymentRetryRequestSerializationTest.java b/src/test/java/com/checkout/payments/request/PaymentRetryRequestSerializationTest.java index 2922a5ad3..9e59b0451 100644 --- a/src/test/java/com/checkout/payments/request/PaymentRetryRequestSerializationTest.java +++ b/src/test/java/com/checkout/payments/request/PaymentRetryRequestSerializationTest.java @@ -3,8 +3,8 @@ import com.checkout.GsonSerializer; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -16,6 +16,7 @@ class PaymentRetryRequestSerializationTest { @Test void shouldSerializeWithDunningConfig() { final PaymentRetryRequest request = PaymentRetryRequest.builder() + .enabled(true) .dunning(DunningRetryRequest.builder() .enabled(true) .maxAttempts(3) @@ -23,104 +24,109 @@ void shouldSerializeWithDunningConfig() { .build()) .build(); - final String json = serializer.toJson(request); - - assertNotNull(json); - assertTrue(json.contains("\"dunning\"")); - assertTrue(json.contains("\"enabled\":true")); - assertTrue(json.contains("\"max_attempts\":3")); - assertTrue(json.contains("\"end_after_days\":7")); + assertEquals("{\"enabled\":true,\"dunning\":{\"enabled\":true,\"max_attempts\":3,\"end_after_days\":7}}", + serializer.toJson(request)); } @Test void shouldSerializeWithDowntimeConfig() { final PaymentRetryRequest request = PaymentRetryRequest.builder() - .downtime(DowntimeRetryRequest.builder() - .enabled(true) - .build()) + .enabled(true) + .downtime(DowntimeRetryRequest.builder().enabled(true).build()) .build(); - final String json = serializer.toJson(request); - - assertNotNull(json); - assertTrue(json.contains("\"downtime\"")); - assertTrue(json.contains("\"enabled\":true")); + assertEquals("{\"enabled\":true,\"downtime\":{\"enabled\":true}}", serializer.toJson(request)); } @Test void shouldSerializeWithBothDunningAndDowntime() { final PaymentRetryRequest request = PaymentRetryRequest.builder() + .enabled(true) .dunning(DunningRetryRequest.builder() .enabled(true) .maxAttempts(5) .endAfterDays(14) .build()) - .downtime(DowntimeRetryRequest.builder() - .enabled(false) - .build()) + .downtime(DowntimeRetryRequest.builder().enabled(false).build()) .build(); - assertDoesNotThrow(() -> serializer.toJson(request)); + assertEquals("{\"enabled\":true," + + "\"dunning\":{\"enabled\":true,\"max_attempts\":5,\"end_after_days\":14}," + + "\"downtime\":{\"enabled\":false}}", + serializer.toJson(request)); } @Test - void shouldDeserializeDunningFromJson() { - final String json = "{" - + "\"dunning\":{" - + " \"enabled\":true," - + " \"max_attempts\":3," - + " \"end_after_days\":7" - + "}," - + "\"downtime\":{" - + " \"enabled\":false" - + "}" - + "}"; + void shouldSerializeEnabledFalseEverywhere() { + final PaymentRetryRequest request = PaymentRetryRequest.builder() + .enabled(false) + .dunning(DunningRetryRequest.builder().enabled(false).build()) + .downtime(DowntimeRetryRequest.builder().enabled(false).build()) + .build(); + + assertEquals("{\"enabled\":false,\"dunning\":{\"enabled\":false},\"downtime\":{\"enabled\":false}}", + serializer.toJson(request)); + } + + @Test + @SuppressWarnings("deprecation") + void shouldStillSendDeprecatedTopLevelFields() { + final PaymentRetryRequest request = PaymentRetryRequest.builder() + .enabled(true) + .maxAttempts(4) + .endAfterDays(10) + .build(); + + assertEquals("{\"enabled\":true,\"max_attempts\":4,\"end_after_days\":10}", serializer.toJson(request)); + } + + @Test + void shouldDeserializeFromJson() { + final String json = "{\"enabled\":true," + + "\"dunning\":{\"enabled\":true,\"max_attempts\":3,\"end_after_days\":7}," + + "\"downtime\":{\"enabled\":false}}"; final PaymentRetryRequest request = serializer.fromJson(json, PaymentRetryRequest.class); - assertNotNull(request); - assertNotNull(request.getDunning()); + assertTrue(request.getEnabled()); assertTrue(request.getDunning().getEnabled()); assertEquals(3, request.getDunning().getMaxAttempts()); assertEquals(7, request.getDunning().getEndAfterDays()); - assertNotNull(request.getDowntime()); - assertEquals(false, request.getDowntime().getEnabled()); + assertFalse(request.getDowntime().getEnabled()); } @Test - void shouldRoundTripSerialize() { + @SuppressWarnings("deprecation") + void shouldRoundTripAllProperties() { final PaymentRetryRequest original = PaymentRetryRequest.builder() + .enabled(true) + .maxAttempts(2) + .endAfterDays(3) .dunning(DunningRetryRequest.builder() .enabled(true) - .maxAttempts(3) - .endAfterDays(7) - .build()) - .downtime(DowntimeRetryRequest.builder() - .enabled(true) + .maxAttempts(15) + .endAfterDays(60) .build()) + .downtime(DowntimeRetryRequest.builder().enabled(true).build()) .build(); final String json = serializer.toJson(original); - final PaymentRetryRequest deserialized = serializer.fromJson(json, PaymentRetryRequest.class); - - assertNotNull(deserialized); - assertNotNull(deserialized.getDunning()); - assertEquals(true, deserialized.getDunning().getEnabled()); - assertEquals(3, deserialized.getDunning().getMaxAttempts()); - assertEquals(7, deserialized.getDunning().getEndAfterDays()); - assertNotNull(deserialized.getDowntime()); - assertEquals(true, deserialized.getDowntime().getEnabled()); + + assertEquals("{\"enabled\":true," + + "\"dunning\":{\"enabled\":true,\"max_attempts\":15,\"end_after_days\":60}," + + "\"downtime\":{\"enabled\":true}," + + "\"max_attempts\":2,\"end_after_days\":3}", json); + assertEquals(original, serializer.fromJson(json, PaymentRetryRequest.class)); } @Test void shouldHandleAbsentOptionalFields() { - final String json = "{}"; - - final PaymentRetryRequest request = serializer.fromJson(json, PaymentRetryRequest.class); + final PaymentRetryRequest request = serializer.fromJson("{}", PaymentRetryRequest.class); assertNotNull(request); - assertDoesNotThrow(() -> serializer.toJson(request)); + assertNull(request.getEnabled()); assertNull(request.getDunning()); assertNull(request.getDowntime()); + assertEquals("{}", serializer.toJson(request)); } } diff --git a/src/test/java/com/checkout/payments/response/PaymentRetryResponseSerializationTest.java b/src/test/java/com/checkout/payments/response/PaymentRetryResponseSerializationTest.java new file mode 100644 index 000000000..df90cf10f --- /dev/null +++ b/src/test/java/com/checkout/payments/response/PaymentRetryResponseSerializationTest.java @@ -0,0 +1,60 @@ +package com.checkout.payments.response; + +import com.checkout.GsonSerializer; +import org.junit.jupiter.api.Test; + +import java.time.Instant; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PaymentRetryResponseSerializationTest { + + private final GsonSerializer serializer = new GsonSerializer(); + + @Test + void shouldDeserializeGetPaymentRetry() { + final String json = "{\"enabled\":true,\"attempts_made\":1,\"max_attempts\":6," + + "\"ends_on\":\"2019-08-24T14:15:22Z\",\"next_attempt_on\":\"2019-08-25T14:15:22Z\"}"; + + final PaymentRetryResponse retry = serializer.fromJson(json, PaymentRetryResponse.class); + + assertTrue(retry.getEnabled()); + assertEquals(1, retry.getAttemptsMade()); + assertEquals(6, retry.getMaxAttempts()); + assertEquals(Instant.parse("2019-08-24T14:15:22Z"), retry.getEndsOn()); + assertEquals(Instant.parse("2019-08-25T14:15:22Z"), retry.getNextAttemptOn()); + } + + @Test + void shouldDeserializePostPaymentRetryWithoutAttemptsMade() { + final PaymentRetryResponse retry = serializer.fromJson("{\"enabled\":false}", PaymentRetryResponse.class); + + assertFalse(retry.getEnabled()); + assertNull(retry.getAttemptsMade()); + assertNull(retry.getMaxAttempts()); + } + + @Test + void shouldRoundTrip() { + final PaymentRetryResponse original = PaymentRetryResponse.builder() + .enabled(true) + .attemptsMade(0) + .maxAttempts(15) + .endsOn(Instant.parse("2019-08-24T14:15:22Z")) + .nextAttemptOn(Instant.parse("2019-08-25T14:15:22Z")) + .build(); + + assertEquals(original, serializer.fromJson(serializer.toJson(original), PaymentRetryResponse.class)); + } + + @Test + void shouldDeserializeRetryInsidePaymentResponses() { + final String retry = "{\"retry\":{\"enabled\":true,\"attempts_made\":2,\"max_attempts\":6}}"; + + assertEquals(2, serializer.fromJson(retry, GetPaymentResponse.class).getRetry().getAttemptsMade()); + assertEquals(6, serializer.fromJson(retry, PaymentResponse.class).getRetry().getMaxAttempts()); + } +}