diff --git a/src/main/java/com/checkout/issuing/IssuingClient.java b/src/main/java/com/checkout/issuing/IssuingClient.java index 7c8e2b5d1..c38bd669f 100644 --- a/src/main/java/com/checkout/issuing/IssuingClient.java +++ b/src/main/java/com/checkout/issuing/IssuingClient.java @@ -92,10 +92,44 @@ 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} + */ + default CompletableFuture revokeCard(String cardId) { + return revokeCard(cardId, null); + } + + /** + * 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} + */ + default CompletableFuture suspendCard(String cardId) { + return suspendCard(cardId, null); + } + /** * Creates a card control (POST /issuing/controls). * @@ -189,16 +223,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. + * 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 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 CompletableFuture updateCard(String cardId, UpdateCardRequest updateCardRequest, CardUpdateHeaders headers); CompletableFuture renewCard(final String cardId, final RenewCardRequest renewCardRequest); @@ -266,10 +301,44 @@ 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} + */ + default VoidResponse revokeCardSync(String cardId) { + return revokeCardSync(cardId, null); + } + + /** + * 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} + */ + default VoidResponse suspendCardSync(String cardId) { + return suspendCardSync(cardId, null); + } + /** * Creates a card control (POST /issuing/controls). * @@ -363,16 +432,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. + * 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 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 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..7177ad35e 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(), @@ -296,7 +296,7 @@ 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(), @@ -587,16 +587,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. + * 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 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 @Override public CompletableFuture updateCard(final String cardId, final UpdateCardRequest updateCardRequest, final CardUpdateHeaders headers) { @@ -872,7 +873,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(), @@ -887,7 +888,7 @@ 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(), @@ -1177,16 +1178,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. + * 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 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 @Override public UpdateCardResponse updateCardSync(final String cardId, final UpdateCardRequest updateCardRequest, final CardUpdateHeaders headers) { @@ -1344,14 +1346,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/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 c3fbc405c..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 @@ -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 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 @Builder @NoArgsConstructor @@ -21,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. This header is no longer + * documented by the API. 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. This header is no longer documented + * by the API. 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/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..a905888b5 100644 --- a/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java +++ b/src/test/java/com/checkout/issuing/cards/CardUpdateResponseSerializationTest.java @@ -9,17 +9,18 @@ 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 - void shouldExposeTheUpdateHeadersUsingTheExactSwaggerHeaderNames() { + void shouldStillSendTheDeprecatedUpdateHeadersWhenSet() { final CardUpdateHeaders headers = CardUpdateHeaders.builder() .returnEncryptedCvv(true) .encryptionKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A") 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()); + } +}