From 2962a436ba6d8f7a39581049355986a4dd485b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Thu, 22 Oct 2026 15:08:09 +0200 Subject: [PATCH 1/3] deprecate(issuing): deprecate the update-card headers and allow revoke and suspend without a body The API no longer documents the return-encrypted-cvv and Encryption-Key headers on the card update operation, so CardUpdateHeaders and the update overloads that take it are deprecated. They keep sending the headers when set and will be removed in the next major version. The revoke and suspend request bodies are optional in the API, so both operations now accept a null request and gain overloads without one. --- .../com/checkout/issuing/IssuingClient.java | 78 +++++++++++-- .../checkout/issuing/IssuingClientImpl.java | 54 +++++---- .../requests/update/CardUpdateHeaders.java | 7 +- .../issuing/IssuingClientImplTest.java | 103 ++++++++++++++++++ .../CardUpdateResponseSerializationTest.java | 9 +- 5 files changed, 217 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/checkout/issuing/IssuingClient.java b/src/main/java/com/checkout/issuing/IssuingClient.java index 7c8e2b5d1..c39008c3c 100644 --- a/src/main/java/com/checkout/issuing/IssuingClient.java +++ b/src/main/java/com/checkout/issuing/IssuingClient.java @@ -92,10 +92,40 @@ public interface IssuingClient { CompletableFuture getCardCredentials(final String cardId, final CardCredentialsQuery queryFilter); + /** + * Revokes a card (POST /issuing/cards/{cardId}/revoke). + * + * @param cardId the card ID + * @param revokeCardRequest the optional request body; when {@code null} no body is sent + * @return a {@link CompletableFuture} containing the {@link VoidResponse} + */ CompletableFuture revokeCard(final String cardId, final RevokeCardRequest revokeCardRequest); + /** + * Revokes a card without a request body (POST /issuing/cards/{cardId}/revoke). + * + * @param cardId the card ID + * @return a {@link CompletableFuture} containing the {@link VoidResponse} + */ + CompletableFuture revokeCard(String cardId); + + /** + * Suspends a card (POST /issuing/cards/{cardId}/suspend). + * + * @param cardId the card ID + * @param suspendCardRequest the optional request body; when {@code null} no body is sent + * @return a {@link CompletableFuture} containing the {@link VoidResponse} + */ CompletableFuture suspendCard(final String cardId, final SuspendCardRequest suspendCardRequest); + /** + * Suspends a card without a request body (POST /issuing/cards/{cardId}/suspend). + * + * @param cardId the card ID + * @return a {@link CompletableFuture} containing the {@link VoidResponse} + */ + CompletableFuture suspendCard(String cardId); + /** * Creates a card control (POST /issuing/controls). * @@ -189,16 +219,17 @@ CompletableFuture simulateReversal( CompletableFuture updateCard(final String cardId, final UpdateCardRequest updateCardRequest); /** - * Update a card's details, sending the optional return-encrypted-cvv and Encryption-Key - * headers. Set return-encrypted-cvv to true to receive the card's encrypted CVV in the - * response; the API returns a 422 with error code encryption_key_required if Encryption-Key - * is not also supplied. + * Update a card's details, sending the return-encrypted-cvv and Encryption-Key headers. + * The API no longer documents these headers; they are still sent when set. * * @param cardId the card ID * @param updateCardRequest the card fields to update * @param headers the optional return-encrypted-cvv and Encryption-Key headers * @return a {@link CompletableFuture} containing the {@link UpdateCardResponse} + * @deprecated The API no longer supports these headers; use + * {@link #updateCard(String, UpdateCardRequest)} instead. Will be removed in the next major version. */ + @Deprecated CompletableFuture updateCard(String cardId, UpdateCardRequest updateCardRequest, CardUpdateHeaders headers); CompletableFuture renewCard(final String cardId, final RenewCardRequest renewCardRequest); @@ -266,10 +297,40 @@ CompletableFuture simulateReversal( CardCredentialsResponse getCardCredentialsSync(String cardId, CardCredentialsQuery queryFilter); + /** + * Revokes a card (POST /issuing/cards/{cardId}/revoke). + * + * @param cardId the card ID + * @param revokeCardRequest the optional request body; when {@code null} no body is sent + * @return the {@link VoidResponse} + */ VoidResponse revokeCardSync(String cardId, RevokeCardRequest revokeCardRequest); + /** + * Revokes a card without a request body (POST /issuing/cards/{cardId}/revoke). + * + * @param cardId the card ID + * @return the {@link VoidResponse} + */ + VoidResponse revokeCardSync(String cardId); + + /** + * Suspends a card (POST /issuing/cards/{cardId}/suspend). + * + * @param cardId the card ID + * @param suspendCardRequest the optional request body; when {@code null} no body is sent + * @return the {@link VoidResponse} + */ VoidResponse suspendCardSync(String cardId, SuspendCardRequest suspendCardRequest); + /** + * Suspends a card without a request body (POST /issuing/cards/{cardId}/suspend). + * + * @param cardId the card ID + * @return the {@link VoidResponse} + */ + VoidResponse suspendCardSync(String cardId); + /** * Creates a card control (POST /issuing/controls). * @@ -363,16 +424,17 @@ CardAuthorizationReversalResponse simulateReversalSync( UpdateCardResponse updateCardSync(String cardId, UpdateCardRequest updateCardRequest); /** - * Update a card's details, sending the optional return-encrypted-cvv and Encryption-Key - * headers. Set return-encrypted-cvv to true to receive the card's encrypted CVV in the - * response; the API returns a 422 with error code encryption_key_required if Encryption-Key - * is not also supplied. + * Update a card's details, sending the return-encrypted-cvv and Encryption-Key headers. + * The API no longer documents these headers; they are still sent when set. * * @param cardId the card ID * @param updateCardRequest the card fields to update * @param headers the optional return-encrypted-cvv and Encryption-Key headers * @return the {@link UpdateCardResponse} + * @deprecated The API no longer supports these headers; use + * {@link #updateCardSync(String, UpdateCardRequest)} instead. Will be removed in the next major version. */ + @Deprecated UpdateCardResponse updateCardSync(String cardId, UpdateCardRequest updateCardRequest, CardUpdateHeaders headers); RenewCardResponse renewCardSync(String cardId, RenewCardRequest renewCardRequest); diff --git a/src/main/java/com/checkout/issuing/IssuingClientImpl.java b/src/main/java/com/checkout/issuing/IssuingClientImpl.java index ea477e3cc..db0438c79 100644 --- a/src/main/java/com/checkout/issuing/IssuingClientImpl.java +++ b/src/main/java/com/checkout/issuing/IssuingClientImpl.java @@ -281,7 +281,7 @@ public CompletableFuture getCardCredentials( @Override public CompletableFuture revokeCard(final String cardId, final RevokeCardRequest revokeCardRequest) { - validateParams("cardId", cardId, "revokeCardRequest", revokeCardRequest); + validateParams("cardId", cardId); return apiClient.postAsync( buildPath(ISSUING_PATH, CARDS_PATH, cardId, REVOKE_PATH), sdkAuthorization(), @@ -291,12 +291,17 @@ public CompletableFuture revokeCard(final String cardId, final Rev ); } + @Override + public CompletableFuture revokeCard(final String cardId) { + return revokeCard(cardId, null); + } + @Override public CompletableFuture suspendCard( final String cardId, final SuspendCardRequest suspendCardRequest ) { - validateParams("cardId", cardId, "suspendCardRequest", suspendCardRequest); + validateParams("cardId", cardId); return apiClient.postAsync( buildPath(ISSUING_PATH, CARDS_PATH, cardId, SUSPEND_PATH), sdkAuthorization(), @@ -306,6 +311,11 @@ public CompletableFuture suspendCard( ); } + @Override + public CompletableFuture suspendCard(final String cardId) { + return suspendCard(cardId, null); + } + @Override public CompletableFuture createControl(final CardControlRequest cardControlRequest) { return createControl(cardControlRequest, null); @@ -587,16 +597,17 @@ public CompletableFuture updateCard(final String cardId, fin } /** - * Update a card's details, sending the optional return-encrypted-cvv and Encryption-Key - * headers. Set return-encrypted-cvv to true to receive the card's encrypted CVV in the - * response; the API returns a 422 with error code encryption_key_required if Encryption-Key - * is not also supplied. + * Update a card's details, sending the return-encrypted-cvv and Encryption-Key headers. + * The API no longer documents these headers; they are still sent when set. * * @param cardId the card ID * @param updateCardRequest the card fields to update * @param headers the optional return-encrypted-cvv and Encryption-Key headers * @return a {@link CompletableFuture} containing the {@link UpdateCardResponse} + * @deprecated The API no longer supports these headers; use + * {@link #updateCard(String, UpdateCardRequest)} instead. Will be removed in the next major version. */ + @Deprecated @Override public CompletableFuture updateCard(final String cardId, final UpdateCardRequest updateCardRequest, final CardUpdateHeaders headers) { @@ -872,7 +883,7 @@ public CardCredentialsResponse getCardCredentialsSync( @Override public VoidResponse revokeCardSync(final String cardId, final RevokeCardRequest revokeCardRequest) { - validateCardIdAndRevokeRequest(cardId, revokeCardRequest); + validateParams("cardId", cardId); return apiClient.post( buildPath(ISSUING_PATH, CARDS_PATH, cardId, REVOKE_PATH), sdkAuthorization(), @@ -882,12 +893,17 @@ public VoidResponse revokeCardSync(final String cardId, final RevokeCardRequest ); } + @Override + public VoidResponse revokeCardSync(final String cardId) { + return revokeCardSync(cardId, null); + } + @Override public VoidResponse suspendCardSync( final String cardId, final SuspendCardRequest suspendCardRequest ) { - validateCardIdAndSuspendRequest(cardId, suspendCardRequest); + validateParams("cardId", cardId); return apiClient.post( buildPath(ISSUING_PATH, CARDS_PATH, cardId, SUSPEND_PATH), sdkAuthorization(), @@ -897,6 +913,11 @@ public VoidResponse suspendCardSync( ); } + @Override + public VoidResponse suspendCardSync(final String cardId) { + return suspendCardSync(cardId, null); + } + @Override public CardControlResponse createControlSync(final CardControlRequest cardControlRequest) { return createControlSync(cardControlRequest, null); @@ -1177,16 +1198,17 @@ public UpdateCardResponse updateCardSync(final String cardId, final UpdateCardRe } /** - * Update a card's details, sending the optional return-encrypted-cvv and Encryption-Key - * headers. Set return-encrypted-cvv to true to receive the card's encrypted CVV in the - * response; the API returns a 422 with error code encryption_key_required if Encryption-Key - * is not also supplied. + * Update a card's details, sending the return-encrypted-cvv and Encryption-Key headers. + * The API no longer documents these headers; they are still sent when set. * * @param cardId the card ID * @param updateCardRequest the card fields to update * @param headers the optional return-encrypted-cvv and Encryption-Key headers * @return the {@link UpdateCardResponse} + * @deprecated The API no longer supports these headers; use + * {@link #updateCardSync(String, UpdateCardRequest)} instead. Will be removed in the next major version. */ + @Deprecated @Override public UpdateCardResponse updateCardSync(final String cardId, final UpdateCardRequest updateCardRequest, final CardUpdateHeaders headers) { @@ -1344,14 +1366,6 @@ private void validateCardIdAndCredentialsQuery(final String cardId, final CardCr validateParams("cardId", cardId, "queryFilter", queryFilter); } - private void validateCardIdAndRevokeRequest(final String cardId, final RevokeCardRequest revokeCardRequest) { - validateParams("cardId", cardId, "revokeCardRequest", revokeCardRequest); - } - - private void validateCardIdAndSuspendRequest(final String cardId, final SuspendCardRequest suspendCardRequest) { - validateParams("cardId", cardId, "suspendCardRequest", suspendCardRequest); - } - private void validateCardControlRequest(final CardControlRequest cardControlRequest) { validateParams("cardControlRequest", cardControlRequest); } diff --git a/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java b/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java index c3fbc405c..709f4ea0d 100644 --- a/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java +++ b/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java @@ -10,10 +10,13 @@ import java.util.Map; /** - * The optional HTTP headers accepted when updating a card's details. + * The return-encrypted-cvv and Encryption-Key HTTP headers for updating a card's details + * (PATCH /issuing/cards/{cardId}). They are still sent when set. * - * @see PATCH /issuing/cards/{cardId} + * @deprecated The API no longer supports these headers; use the method without headers. Will be + * removed in the next major version. */ +@Deprecated @Data @Builder @NoArgsConstructor diff --git a/src/test/java/com/checkout/issuing/IssuingClientImplTest.java b/src/test/java/com/checkout/issuing/IssuingClientImplTest.java index 5548605a9..c88b877cf 100644 --- a/src/test/java/com/checkout/issuing/IssuingClientImplTest.java +++ b/src/test/java/com/checkout/issuing/IssuingClientImplTest.java @@ -22,6 +22,7 @@ import com.checkout.issuing.cards.requests.renew.RenewCardRequest; import com.checkout.issuing.cards.requests.revoke.RevokeCardRequest; import com.checkout.issuing.cards.requests.suspend.SuspendCardRequest; +import com.checkout.issuing.cards.requests.update.CardUpdateHeaders; import com.checkout.issuing.cards.requests.update.UpdateCardRequest; import com.checkout.issuing.cards.responses.CardDetailsResponse; import com.checkout.issuing.cards.responses.CardResponse; @@ -375,6 +376,57 @@ void shouldSuspendCard() throws ExecutionException, InterruptedException { validateVoidResponse(response, future.get()); } + @Test + void shouldRevokeCardWithoutBody() throws ExecutionException, InterruptedException { + final VoidResponse response = createVoidResponse(); + + when(apiClient.postAsync( + "issuing/cards/card_id/revoke", + authorization, + VoidResponse.class, + null, + null + )).thenReturn(CompletableFuture.completedFuture(response)); + + validateVoidResponse(response, client.revokeCard("card_id").get()); + validateVoidResponse(response, client.revokeCard("card_id", null).get()); + } + + @Test + void shouldSuspendCardWithoutBody() throws ExecutionException, InterruptedException { + final VoidResponse response = createVoidResponse(); + + when(apiClient.postAsync( + "issuing/cards/card_id/suspend", + authorization, + VoidResponse.class, + null, + null + )).thenReturn(CompletableFuture.completedFuture(response)); + + validateVoidResponse(response, client.suspendCard("card_id").get()); + validateVoidResponse(response, client.suspendCard("card_id", null).get()); + } + + @Test + @SuppressWarnings("deprecation") + void shouldUpdateCardWithDeprecatedHeaders() throws ExecutionException, InterruptedException { + final UpdateCardRequest request = createUpdateCardRequest(); + final UpdateCardResponse response = createUpdateCardResponse(); + final CardUpdateHeaders headers = CardUpdateHeaders.builder().returnEncryptedCvv(true).encryptionKey("key").build(); + + when(apiClient.patchAsync( + "issuing/cards/card_id", + authorization, + UpdateCardResponse.class, + request, + null, + headers + )).thenReturn(CompletableFuture.completedFuture(response)); + + assertEquals(response, client.updateCard("card_id", request, headers).get()); + } + @Test void shouldUpdateCard() throws ExecutionException, InterruptedException { final UpdateCardRequest request = createUpdateCardRequest(); @@ -1204,6 +1256,57 @@ void shouldSuspendCardSync() { validateVoidResponse(expectedResponse, actualResponse); } + @Test + void shouldRevokeCardSyncWithoutBody() { + final VoidResponse expectedResponse = createVoidResponse(); + + when(apiClient.post( + "issuing/cards/card_id/revoke", + authorization, + VoidResponse.class, + null, + null + )).thenReturn(expectedResponse); + + validateVoidResponse(expectedResponse, client.revokeCardSync("card_id")); + validateVoidResponse(expectedResponse, client.revokeCardSync("card_id", null)); + } + + @Test + void shouldSuspendCardSyncWithoutBody() { + final VoidResponse expectedResponse = createVoidResponse(); + + when(apiClient.post( + "issuing/cards/card_id/suspend", + authorization, + VoidResponse.class, + null, + null + )).thenReturn(expectedResponse); + + validateVoidResponse(expectedResponse, client.suspendCardSync("card_id")); + validateVoidResponse(expectedResponse, client.suspendCardSync("card_id", null)); + } + + @Test + @SuppressWarnings("deprecation") + void shouldUpdateCardSyncWithDeprecatedHeaders() { + final UpdateCardRequest request = createUpdateCardRequest(); + final UpdateCardResponse expectedResponse = createUpdateCardResponse(); + final CardUpdateHeaders headers = CardUpdateHeaders.builder().returnEncryptedCvv(true).encryptionKey("key").build(); + + when(apiClient.patch( + "issuing/cards/card_id", + authorization, + UpdateCardResponse.class, + request, + null, + headers + )).thenReturn(expectedResponse); + + assertEquals(expectedResponse, client.updateCardSync("card_id", request, headers)); + } + @Test void shouldUpdateCardSync() { final UpdateCardRequest request = createUpdateCardRequest(); diff --git a/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java b/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java index beb6e818c..a39fc8ef8 100644 --- a/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java +++ b/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java @@ -9,13 +9,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; /** - * Tests for CardUpdateHeaders. + * Tests for the deprecated CardUpdateHeaders, which must keep sending the headers when set. * * Swagger reference: PATCH /issuing/cards/{cardId} - * Schema: update-card-response. The 2026-09-02 delta added encrypted_cvv here; the 2026-09-17 - * delta (INT-1700) removed it again, so the current spec never includes it. Coverage for the - * current update-card-response field set lives in CardScheduledRevocationAndActivationSerializationTest. + * The current spec documents neither these headers nor encrypted_cvv in update-card-response. + * Coverage for the update-card-response field set lives in + * CardScheduledRevocationAndActivationSerializationTest. */ +@SuppressWarnings("deprecation") class CardUpdateResponseSerializationTest { @Test From eb71a88f54eb957a7469e16697514dc2fb24dd20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Thu, 22 Oct 2026 18:01:13 +0200 Subject: [PATCH 2/3] test(issuing): cover the revoke and suspend requests and copy the spec wording --- .../requests/revoke/RevokeCardRequest.java | 11 +++ .../cards/requests/revoke/RevokeReason.java | 3 + .../requests/suspend/SuspendCardRequest.java | 11 +++ .../cards/requests/suspend/SuspendReason.java | 3 + .../requests/update/CardUpdateHeaders.java | 11 +-- .../responses/update/UpdateCardResponse.java | 7 +- ...keSuspendCardRequestSerializationTest.java | 73 +++++++++++++++++++ 7 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 src/test/java/com/checkout/issuing/cards/RevokeSuspendCardRequestSerializationTest.java diff --git a/src/main/java/com/checkout/issuing/cards/requests/revoke/RevokeCardRequest.java b/src/main/java/com/checkout/issuing/cards/requests/revoke/RevokeCardRequest.java index 90b164a84..607215a11 100644 --- a/src/main/java/com/checkout/issuing/cards/requests/revoke/RevokeCardRequest.java +++ b/src/main/java/com/checkout/issuing/cards/requests/revoke/RevokeCardRequest.java @@ -3,9 +3,20 @@ import lombok.Builder; import lombok.Data; +/** + * The optional request body for POST /issuing/cards/{cardId}/revoke. The body itself is optional: + * the client methods accept {@code null} and then send no body. + */ @Data @Builder public final class RevokeCardRequest { + /** + * The reason why the card is being revoked. + *

+ * [Optional] + *

+ * Enum: "expired" "reported_lost" "reported_stolen" + */ private RevokeReason reason; } diff --git a/src/main/java/com/checkout/issuing/cards/requests/revoke/RevokeReason.java b/src/main/java/com/checkout/issuing/cards/requests/revoke/RevokeReason.java index 38e3a4e69..8f2c03ad3 100644 --- a/src/main/java/com/checkout/issuing/cards/requests/revoke/RevokeReason.java +++ b/src/main/java/com/checkout/issuing/cards/requests/revoke/RevokeReason.java @@ -2,6 +2,9 @@ import com.google.gson.annotations.SerializedName; +/** + * The reason why the card is being revoked. + */ public enum RevokeReason { @SerializedName("expired") diff --git a/src/main/java/com/checkout/issuing/cards/requests/suspend/SuspendCardRequest.java b/src/main/java/com/checkout/issuing/cards/requests/suspend/SuspendCardRequest.java index 8664bbf16..70e3e7871 100644 --- a/src/main/java/com/checkout/issuing/cards/requests/suspend/SuspendCardRequest.java +++ b/src/main/java/com/checkout/issuing/cards/requests/suspend/SuspendCardRequest.java @@ -3,9 +3,20 @@ import lombok.Builder; import lombok.Data; +/** + * The optional request body for POST /issuing/cards/{cardId}/suspend. The body itself is optional: + * the client methods accept {@code null} and then send no body. + */ @Data @Builder public final class SuspendCardRequest { + /** + * The reason why the card is being suspended. + *

+ * [Optional] + *

+ * Enum: "suspected_lost" "suspected_stolen" + */ private SuspendReason reason; } diff --git a/src/main/java/com/checkout/issuing/cards/requests/suspend/SuspendReason.java b/src/main/java/com/checkout/issuing/cards/requests/suspend/SuspendReason.java index a6590c45c..971cdb85e 100644 --- a/src/main/java/com/checkout/issuing/cards/requests/suspend/SuspendReason.java +++ b/src/main/java/com/checkout/issuing/cards/requests/suspend/SuspendReason.java @@ -2,6 +2,9 @@ import com.google.gson.annotations.SerializedName; +/** + * The reason why the card is being suspended. + */ public enum SuspendReason { @SerializedName("suspected_lost") diff --git a/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java b/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java index 709f4ea0d..f1d775de8 100644 --- a/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java +++ b/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java @@ -24,18 +24,15 @@ public class CardUpdateHeaders implements IHeaders { /** - * The return-encrypted-cvv HTTP header. Set to true to retrieve the card's encrypted - * credentials in the response. Requires an RSA public key to be provided in the - * Encryption-Key header. + * The value sent in the return-encrypted-cvv HTTP header. The API no longer documents this + * header and ignores it; no encrypted CVV is returned. Still sent when set. * [Optional] */ private Boolean returnEncryptedCvv; /** - * The Encryption-Key HTTP header. The RSA public key used to encrypt returned credentials. - * Required when the return-encrypted-cvv header is set to true. Provide the public key with - * the BEGIN PUBLIC KEY and END PUBLIC KEY headers and any newline characters removed, - * encoded as Base64. + * The value sent in the Encryption-Key HTTP header. The API no longer documents this header + * and ignores it. Still sent when set. * [Optional] */ private String encryptionKey; diff --git a/src/main/java/com/checkout/issuing/cards/responses/update/UpdateCardResponse.java b/src/main/java/com/checkout/issuing/cards/responses/update/UpdateCardResponse.java index 19cf09e7d..176c75dc2 100644 --- a/src/main/java/com/checkout/issuing/cards/responses/update/UpdateCardResponse.java +++ b/src/main/java/com/checkout/issuing/cards/responses/update/UpdateCardResponse.java @@ -17,10 +17,9 @@ /** * Response returned when updating a card. *

- * As of the 2026-09-17 API change, {@code update-card-response} is defined as the full - * {@code get-card-response} field set (see {@link com.checkout.issuing.cards.responses.CardDetailsResponse}) - * with {@code lastModifiedDate} required. It no longer includes {@code encrypted_cvv}: that field - * was added by the 2026-09-02 change and removed again by this one. + * {@code update-card-response} is the full {@code get-card-response} field set (see + * {@link com.checkout.issuing.cards.responses.CardDetailsResponse}) with {@code lastModifiedDate} + * required. It does not include {@code encrypted_cvv}. */ @Data @EqualsAndHashCode(callSuper = true) diff --git a/src/test/java/com/checkout/issuing/cards/RevokeSuspendCardRequestSerializationTest.java b/src/test/java/com/checkout/issuing/cards/RevokeSuspendCardRequestSerializationTest.java new file mode 100644 index 000000000..fd798ee1e --- /dev/null +++ b/src/test/java/com/checkout/issuing/cards/RevokeSuspendCardRequestSerializationTest.java @@ -0,0 +1,73 @@ +package com.checkout.issuing.cards; + +import com.checkout.GsonSerializer; +import com.checkout.issuing.cards.requests.revoke.RevokeCardRequest; +import com.checkout.issuing.cards.requests.revoke.RevokeReason; +import com.checkout.issuing.cards.requests.suspend.SuspendCardRequest; +import com.checkout.issuing.cards.requests.suspend.SuspendReason; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Serialization tests for the revoke-card-request and suspend-card-request schemas. + * + * Swagger reference: POST /issuing/cards/{cardId}/revoke and POST /issuing/cards/{cardId}/suspend. + * Both bodies are optional and their only property, reason, is optional. + */ +class RevokeSuspendCardRequestSerializationTest { + + private final GsonSerializer serializer = new GsonSerializer(); + + @ParameterizedTest + @CsvSource({ + "EXPIRED,expired", + "REPORTED_LOST,reported_lost", + "REPORTED_STOLEN,reported_stolen" + }) + void shouldRoundtripRevokeCardRequestForEveryReason(final RevokeReason reason, final String wire) { + final RevokeCardRequest request = RevokeCardRequest.builder().reason(reason).build(); + + final String json = serializer.toJson(request); + assertEquals("{\"reason\":\"" + wire + "\"}", json); + + final RevokeCardRequest roundtrip = serializer.fromJson(json, RevokeCardRequest.class); + assertEquals(request, roundtrip); + assertEquals(reason, roundtrip.getReason()); + } + + @ParameterizedTest + @CsvSource({ + "SUSPECTED_LOST,suspected_lost", + "SUSPECTED_STOLEN,suspected_stolen" + }) + void shouldRoundtripSuspendCardRequestForEveryReason(final SuspendReason reason, final String wire) { + final SuspendCardRequest request = SuspendCardRequest.builder().reason(reason).build(); + + final String json = serializer.toJson(request); + assertEquals("{\"reason\":\"" + wire + "\"}", json); + + final SuspendCardRequest roundtrip = serializer.fromJson(json, SuspendCardRequest.class); + assertEquals(request, roundtrip); + assertEquals(reason, roundtrip.getReason()); + } + + @Test + void shouldDeserializeTheSwaggerExamples() { + assertEquals(RevokeReason.REPORTED_LOST, + serializer.fromJson("{\"reason\":\"reported_lost\"}", RevokeCardRequest.class).getReason()); + assertEquals(SuspendReason.SUSPECTED_LOST, + serializer.fromJson("{\"reason\":\"suspected_lost\"}", SuspendCardRequest.class).getReason()); + } + + @Test + void shouldSerializeEmptyRequestsAsEmptyObjects() { + assertEquals("{}", serializer.toJson(RevokeCardRequest.builder().build())); + assertEquals("{}", serializer.toJson(SuspendCardRequest.builder().build())); + assertNull(serializer.fromJson("{}", RevokeCardRequest.class).getReason()); + assertNull(serializer.fromJson("{}", SuspendCardRequest.class).getReason()); + } +} From 9ca7e0f9b223a5107e4e86b63cde4814598ce5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Armando=20Rodr=C3=ADguez?= <127134616+armando-rodriguez-cko@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:16:19 +0200 Subject: [PATCH 3/3] fix(issuing): keep the new revoke and suspend overloads source compatible --- .../com/checkout/issuing/IssuingClient.java | 24 ++++++++++------ .../checkout/issuing/IssuingClientImpl.java | 28 +++---------------- .../requests/update/CardUpdateHeaders.java | 12 ++++---- .../CardUpdateResponseSerializationTest.java | 2 +- 4 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/checkout/issuing/IssuingClient.java b/src/main/java/com/checkout/issuing/IssuingClient.java index c39008c3c..c38bd669f 100644 --- a/src/main/java/com/checkout/issuing/IssuingClient.java +++ b/src/main/java/com/checkout/issuing/IssuingClient.java @@ -107,7 +107,9 @@ public interface IssuingClient { * @param cardId the card ID * @return a {@link CompletableFuture} containing the {@link VoidResponse} */ - CompletableFuture revokeCard(String cardId); + default CompletableFuture revokeCard(String cardId) { + return revokeCard(cardId, null); + } /** * Suspends a card (POST /issuing/cards/{cardId}/suspend). @@ -124,7 +126,9 @@ public interface IssuingClient { * @param cardId the card ID * @return a {@link CompletableFuture} containing the {@link VoidResponse} */ - CompletableFuture suspendCard(String cardId); + default CompletableFuture suspendCard(String cardId) { + return suspendCard(cardId, null); + } /** * Creates a card control (POST /issuing/controls). @@ -220,13 +224,13 @@ CompletableFuture simulateReversal( /** * Update a card's details, sending the return-encrypted-cvv and Encryption-Key headers. - * The API no longer documents these headers; they are still sent when set. + * These headers are no longer documented by the API; they are still sent when set. * * @param cardId the card ID * @param updateCardRequest the card fields to update * @param headers the optional return-encrypted-cvv and Encryption-Key headers * @return a {@link CompletableFuture} containing the {@link UpdateCardResponse} - * @deprecated The API no longer supports these headers; use + * @deprecated These headers are no longer documented by the API and are still sent when set; use * {@link #updateCard(String, UpdateCardRequest)} instead. Will be removed in the next major version. */ @Deprecated @@ -312,7 +316,9 @@ CompletableFuture simulateReversal( * @param cardId the card ID * @return the {@link VoidResponse} */ - VoidResponse revokeCardSync(String cardId); + default VoidResponse revokeCardSync(String cardId) { + return revokeCardSync(cardId, null); + } /** * Suspends a card (POST /issuing/cards/{cardId}/suspend). @@ -329,7 +335,9 @@ CompletableFuture simulateReversal( * @param cardId the card ID * @return the {@link VoidResponse} */ - VoidResponse suspendCardSync(String cardId); + default VoidResponse suspendCardSync(String cardId) { + return suspendCardSync(cardId, null); + } /** * Creates a card control (POST /issuing/controls). @@ -425,13 +433,13 @@ CardAuthorizationReversalResponse simulateReversalSync( /** * Update a card's details, sending the return-encrypted-cvv and Encryption-Key headers. - * The API no longer documents these headers; they are still sent when set. + * These headers are no longer documented by the API; they are still sent when set. * * @param cardId the card ID * @param updateCardRequest the card fields to update * @param headers the optional return-encrypted-cvv and Encryption-Key headers * @return the {@link UpdateCardResponse} - * @deprecated The API no longer supports these headers; use + * @deprecated These headers are no longer documented by the API and are still sent when set; use * {@link #updateCardSync(String, UpdateCardRequest)} instead. Will be removed in the next major version. */ @Deprecated diff --git a/src/main/java/com/checkout/issuing/IssuingClientImpl.java b/src/main/java/com/checkout/issuing/IssuingClientImpl.java index db0438c79..7177ad35e 100644 --- a/src/main/java/com/checkout/issuing/IssuingClientImpl.java +++ b/src/main/java/com/checkout/issuing/IssuingClientImpl.java @@ -291,11 +291,6 @@ public CompletableFuture revokeCard(final String cardId, final Rev ); } - @Override - public CompletableFuture revokeCard(final String cardId) { - return revokeCard(cardId, null); - } - @Override public CompletableFuture suspendCard( final String cardId, @@ -311,11 +306,6 @@ public CompletableFuture suspendCard( ); } - @Override - public CompletableFuture suspendCard(final String cardId) { - return suspendCard(cardId, null); - } - @Override public CompletableFuture createControl(final CardControlRequest cardControlRequest) { return createControl(cardControlRequest, null); @@ -598,13 +588,13 @@ public CompletableFuture updateCard(final String cardId, fin /** * Update a card's details, sending the return-encrypted-cvv and Encryption-Key headers. - * The API no longer documents these headers; they are still sent when set. + * These headers are no longer documented by the API; they are still sent when set. * * @param cardId the card ID * @param updateCardRequest the card fields to update * @param headers the optional return-encrypted-cvv and Encryption-Key headers * @return a {@link CompletableFuture} containing the {@link UpdateCardResponse} - * @deprecated The API no longer supports these headers; use + * @deprecated These headers are no longer documented by the API and are still sent when set; use * {@link #updateCard(String, UpdateCardRequest)} instead. Will be removed in the next major version. */ @Deprecated @@ -893,11 +883,6 @@ public VoidResponse revokeCardSync(final String cardId, final RevokeCardRequest ); } - @Override - public VoidResponse revokeCardSync(final String cardId) { - return revokeCardSync(cardId, null); - } - @Override public VoidResponse suspendCardSync( final String cardId, @@ -913,11 +898,6 @@ public VoidResponse suspendCardSync( ); } - @Override - public VoidResponse suspendCardSync(final String cardId) { - return suspendCardSync(cardId, null); - } - @Override public CardControlResponse createControlSync(final CardControlRequest cardControlRequest) { return createControlSync(cardControlRequest, null); @@ -1199,13 +1179,13 @@ public UpdateCardResponse updateCardSync(final String cardId, final UpdateCardRe /** * Update a card's details, sending the return-encrypted-cvv and Encryption-Key headers. - * The API no longer documents these headers; they are still sent when set. + * These headers are no longer documented by the API; they are still sent when set. * * @param cardId the card ID * @param updateCardRequest the card fields to update * @param headers the optional return-encrypted-cvv and Encryption-Key headers * @return the {@link UpdateCardResponse} - * @deprecated The API no longer supports these headers; use + * @deprecated These headers are no longer documented by the API and are still sent when set; use * {@link #updateCardSync(String, UpdateCardRequest)} instead. Will be removed in the next major version. */ @Deprecated diff --git a/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java b/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java index f1d775de8..f792ef633 100644 --- a/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java +++ b/src/main/java/com/checkout/issuing/cards/requests/update/CardUpdateHeaders.java @@ -13,8 +13,8 @@ * The return-encrypted-cvv and Encryption-Key HTTP headers for updating a card's details * (PATCH /issuing/cards/{cardId}). They are still sent when set. * - * @deprecated The API no longer supports these headers; use the method without headers. Will be - * removed in the next major version. + * @deprecated These headers are no longer documented by the API and are still sent when set; + * use the method without headers. Will be removed in the next major version. */ @Deprecated @Data @@ -24,15 +24,15 @@ public class CardUpdateHeaders implements IHeaders { /** - * The value sent in the return-encrypted-cvv HTTP header. The API no longer documents this - * header and ignores it; no encrypted CVV is returned. Still sent when set. + * The value sent in the return-encrypted-cvv HTTP header. This header is no longer + * documented by the API. Still sent when set. * [Optional] */ private Boolean returnEncryptedCvv; /** - * The value sent in the Encryption-Key HTTP header. The API no longer documents this header - * and ignores it. Still sent when set. + * The value sent in the Encryption-Key HTTP header. This header is no longer documented + * by the API. Still sent when set. * [Optional] */ private String encryptionKey; diff --git a/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java b/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java index a39fc8ef8..a905888b5 100644 --- a/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java +++ b/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java @@ -20,7 +20,7 @@ class CardUpdateResponseSerializationTest { @Test - void shouldExposeTheUpdateHeadersUsingTheExactSwaggerHeaderNames() { + void shouldStillSendTheDeprecatedUpdateHeadersWhenSet() { final CardUpdateHeaders headers = CardUpdateHeaders.builder() .returnEncryptedCvv(true) .encryptionKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A")