diff --git a/src/main/java/com/checkout/GsonSerializer.java b/src/main/java/com/checkout/GsonSerializer.java index bc08eaa06..b44ab775c 100644 --- a/src/main/java/com/checkout/GsonSerializer.java +++ b/src/main/java/com/checkout/GsonSerializer.java @@ -16,6 +16,7 @@ import com.checkout.issuing.controls.requests.ControlType; import com.checkout.issuing.controls.responses.create.CardControlResponse; import com.checkout.issuing.controls.responses.create.MccCardControlResponse; +import com.checkout.issuing.controls.responses.create.MidCardControlResponse; import com.checkout.issuing.controls.responses.create.VelocityCardControlResponse; import com.checkout.payments.PaymentDestinationType; import com.checkout.payments.ProductResponse; @@ -249,7 +250,8 @@ public final class GsonSerializer implements Serializer { // Issuing CS2 - CardControlsResponse .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(CardControlResponse.class, CheckoutUtils.CONTROL_TYPE, true) .registerSubtype(VelocityCardControlResponse.class, identifier(ControlType.VELOCITY_LIMIT)) - .registerSubtype(MccCardControlResponse.class, identifier(ControlType.MCC_LIMIT))) + .registerSubtype(MccCardControlResponse.class, identifier(ControlType.MCC_LIMIT)) + .registerSubtype(MidCardControlResponse.class, identifier(ControlType.MID_LIMIT))) // Issuing CS2 - ControlGroupControl .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(com.checkout.issuing.controls.requests.controlgroup.ControlGroupControl.class, CheckoutUtils.CONTROL_TYPE, true) .registerSubtype(com.checkout.issuing.controls.requests.controlgroup.VelocityControlGroupControl.class, identifier(ControlType.VELOCITY_LIMIT)) diff --git a/src/main/java/com/checkout/issuing/IssuingClient.java b/src/main/java/com/checkout/issuing/IssuingClient.java index 0776efd9f..7c8e2b5d1 100644 --- a/src/main/java/com/checkout/issuing/IssuingClient.java +++ b/src/main/java/com/checkout/issuing/IssuingClient.java @@ -96,14 +96,46 @@ public interface IssuingClient { CompletableFuture suspendCard(final String cardId, final SuspendCardRequest suspendCardRequest); + /** + * Creates a card control (POST /issuing/controls). + * + * @param cardControlRequest the control to create + * @return the created control, a subtype of {@link CardControlResponse} chosen by control_type + */ CompletableFuture createControl(final CardControlRequest cardControlRequest); + /** + * Creates a card control (POST /issuing/controls) with an idempotency key. + * + * @param cardControlRequest the control to create + * @param idempotencyKey the idempotency key + * @return the created control, a subtype of {@link CardControlResponse} chosen by control_type + */ CompletableFuture createControl(final CardControlRequest cardControlRequest, final String idempotencyKey); + /** + * Gets the controls applied to a target (GET /issuing/controls). + * + * @param queryFilter the target filter + * @return the list of controls, each a subtype of {@link CardControlResponse} + */ CompletableFuture getCardControls(final CardControlsQuery queryFilter); + /** + * Gets a card control (GET /issuing/controls/{controlId}). + * + * @param controlId the control ID + * @return the control, a subtype of {@link CardControlResponse} chosen by control_type + */ CompletableFuture getCardControlDetails(final String controlId); + /** + * Updates a card control (PUT /issuing/controls/{controlId}). + * + * @param controlId the control ID + * @param updateCardControlRequest the fields to update + * @return the updated control: {@link com.checkout.issuing.controls.responses.create.VelocityCardControlResponse}, {@link com.checkout.issuing.controls.responses.create.MccCardControlResponse} or {@link com.checkout.issuing.controls.responses.create.MidCardControlResponse}, chosen by control_type + */ CompletableFuture updateCardControl(final String controlId, final UpdateCardControlRequest updateCardControlRequest); CompletableFuture removeCardControl(final String controlId); @@ -238,14 +270,46 @@ CompletableFuture simulateReversal( VoidResponse suspendCardSync(String cardId, SuspendCardRequest suspendCardRequest); + /** + * Creates a card control (POST /issuing/controls). + * + * @param cardControlRequest the control to create + * @return the created control, a subtype of {@link CardControlResponse} chosen by control_type + */ CardControlResponse createControlSync(CardControlRequest cardControlRequest); + /** + * Creates a card control (POST /issuing/controls) with an idempotency key. + * + * @param cardControlRequest the control to create + * @param idempotencyKey the idempotency key + * @return the created control, a subtype of {@link CardControlResponse} chosen by control_type + */ CardControlResponse createControlSync(CardControlRequest cardControlRequest, String idempotencyKey); + /** + * Gets the controls applied to a target (GET /issuing/controls). + * + * @param queryFilter the target filter + * @return the list of controls, each a subtype of {@link CardControlResponse} + */ CardControlsQueryResponse getCardControlsSync(CardControlsQuery queryFilter); + /** + * Gets a card control (GET /issuing/controls/{controlId}). + * + * @param controlId the control ID + * @return the control, a subtype of {@link CardControlResponse} chosen by control_type + */ CardControlResponse getCardControlDetailsSync(String controlId); + /** + * Updates a card control (PUT /issuing/controls/{controlId}). + * + * @param controlId the control ID + * @param updateCardControlRequest the fields to update + * @return the updated control: {@link com.checkout.issuing.controls.responses.create.VelocityCardControlResponse}, {@link com.checkout.issuing.controls.responses.create.MccCardControlResponse} or {@link com.checkout.issuing.controls.responses.create.MidCardControlResponse}, chosen by control_type + */ CardControlResponse updateCardControlSync(String controlId, UpdateCardControlRequest updateCardControlRequest); IdResponse removeCardControlSync(String controlId); diff --git a/src/main/java/com/checkout/issuing/controls/requests/MccLimit.java b/src/main/java/com/checkout/issuing/controls/requests/MccLimit.java index f72e1c72c..706148963 100644 --- a/src/main/java/com/checkout/issuing/controls/requests/MccLimit.java +++ b/src/main/java/com/checkout/issuing/controls/requests/MccLimit.java @@ -5,11 +5,23 @@ import java.util.List; +/** + * The merchant category code (MCC) rule, which determines the types of businesses transactions can be processed from. + */ @Data @Builder public final class MccLimit { + /** + * Sets whether to allow or block the list of MCCs supplied. + * [Required] + * Enum: "allow", "block" + */ private String type; + /** + * The list of MCCs to allow or block transactions from, as 4-digit ISO 18245 codes. + * [Required] + */ private List mccList; } diff --git a/src/main/java/com/checkout/issuing/controls/requests/MidLimit.java b/src/main/java/com/checkout/issuing/controls/requests/MidLimit.java index 74ad93718..84d3f6ef0 100644 --- a/src/main/java/com/checkout/issuing/controls/requests/MidLimit.java +++ b/src/main/java/com/checkout/issuing/controls/requests/MidLimit.java @@ -5,11 +5,24 @@ import java.util.List; +/** + * The merchant identification (MID) code rule, which determines the merchants from whom transactions can be processed. + */ @Data @Builder public final class MidLimit { + /** + * Sets whether to allow or block the list of MIDs supplied. + * [Required] + * Enum: "allow", "block" + */ private String type; + /** + * The list of merchant identification (MID) codes to allow or block transactions from. + * [Required] + * Each item: 1 to 15 characters, ^[A-Za-z0-9{}\[\] ,+\-=.();'\/&@*]{1,15}$ + */ private List midList; -} \ No newline at end of file +} diff --git a/src/main/java/com/checkout/issuing/controls/requests/VelocityLimit.java b/src/main/java/com/checkout/issuing/controls/requests/VelocityLimit.java index 760509fe5..9eb5f5464 100644 --- a/src/main/java/com/checkout/issuing/controls/requests/VelocityLimit.java +++ b/src/main/java/com/checkout/issuing/controls/requests/VelocityLimit.java @@ -1,19 +1,63 @@ package com.checkout.issuing.controls.requests; +import lombok.AccessLevel; import lombok.Builder; import lombok.Data; +import lombok.Setter; import java.util.List; +/** + * The velocity limit, which determines how much a target card can spend over a given timeframe. + *

+ * Used in control requests and, as {@code VelocityLimitWithRemainingAmount}, in control responses. + */ @Data -@Builder public final class VelocityLimit { + /** + * The amount that can be spent, in minor units. + * [Required] + * int64, minimum 0 + */ private Integer amountLimit; + /** + * The period of time over which the specified {@code amount_limit} can be spent. + * [Required] + */ private VelocityWindow velocityWindow; + /** + * The list of merchant category codes (MCCs) that the velocity limit applies to, as four-digit ISO 18245 codes. + * [Optional] + */ private List mccList; + /** + * The list of merchant identification (MID) codes to allow or block transactions from. + * You can provide either {@code mcc_list} or {@code mid_list}, but not both. + * [Optional] + */ private List midList; + + /** + * The remaining amount that can be spent, in minor units. + * [Required] in control responses only. It is read from the API response and is never sent on requests: + * it has no builder method and no setter, so it is always null on request bodies. + * int64, minimum 0 + */ + @Setter(AccessLevel.NONE) + private Long amountRemaining; + + @Builder + private VelocityLimit(final Integer amountLimit, + final VelocityWindow velocityWindow, + final List mccList, + final List midList) { + this.amountLimit = amountLimit; + this.velocityWindow = velocityWindow; + this.mccList = mccList; + this.midList = midList; + } } diff --git a/src/main/java/com/checkout/issuing/controls/requests/VelocityWindow.java b/src/main/java/com/checkout/issuing/controls/requests/VelocityWindow.java index b789805fe..a168e7ee8 100644 --- a/src/main/java/com/checkout/issuing/controls/requests/VelocityWindow.java +++ b/src/main/java/com/checkout/issuing/controls/requests/VelocityWindow.java @@ -3,9 +3,17 @@ import lombok.Builder; import lombok.Data; +/** + * The period of time over which the specified {@code amount_limit} can be spent. + */ @Data @Builder public final class VelocityWindow { + /** + * The velocity window's unit of time. + * [Required] + * Enum: "daily", "weekly", "monthly", "all_time" + */ private VelocityWindowType type; } diff --git a/src/main/java/com/checkout/issuing/controls/responses/create/CardControlResponse.java b/src/main/java/com/checkout/issuing/controls/responses/create/CardControlResponse.java index 9c8bc4ee5..0db0d9eb7 100644 --- a/src/main/java/com/checkout/issuing/controls/responses/create/CardControlResponse.java +++ b/src/main/java/com/checkout/issuing/controls/responses/create/CardControlResponse.java @@ -8,21 +8,65 @@ import java.time.Instant; +/** + * Base card control response, discriminated on {@code control_type}. Returned by create, get, list and update + * control operations; see {@link VelocityCardControlResponse}, {@link MccCardControlResponse} and + * {@link MidCardControlResponse}. + */ @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public abstract class CardControlResponse extends HttpMetadata { + /** + * The control's type. A velocity_limit determines how much can be spent over a given period of time. + * An mcc_limit determines the types of businesses from which transactions can be processed. + * A mid_limit specifies the merchants from whom transactions can be processed. + * [Required] + * Enum: "velocity_limit", "mcc_limit", "mid_limit" + */ private final ControlType controlType; + /** + * The control's unique identifier. + * [Required] + * 30 characters, ^ctr_[a-z0-9]{26}$ + */ private String id; + /** + * A description for the control. + * [Optional] + * max 256 characters + */ private String description; + /** + * The ID of the card or control profile. + * [Required] + * 30 characters, ^(crd|cpr)_[a-z0-9]{26}$ + */ private String targetId; + /** + * Indicates whether you can change this control. false: an immutable control applied by Checkout.com. + * true: you applied this control and can change it. + * [Required] + */ + private Boolean isEditable; + + /** + * The date and time the control was created, in UTC. + * [Required] + * format: date-time (UTC) + */ private Instant createdDate; + /** + * The date and time the control was last modified, in UTC. + * [Required] + * format: date-time (UTC) + */ private Instant lastModifiedDate; protected CardControlResponse(final ControlType controlType) { diff --git a/src/main/java/com/checkout/issuing/controls/responses/create/MccCardControlResponse.java b/src/main/java/com/checkout/issuing/controls/responses/create/MccCardControlResponse.java index b527b6701..ba7144fd1 100644 --- a/src/main/java/com/checkout/issuing/controls/responses/create/MccCardControlResponse.java +++ b/src/main/java/com/checkout/issuing/controls/responses/create/MccCardControlResponse.java @@ -8,12 +8,19 @@ import lombok.Setter; import lombok.ToString; +/** + * Card control response for control_type {@code mcc_limit}. + */ @Getter @Setter @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public final class MccCardControlResponse extends CardControlResponse { + /** + * The merchant category code (MCC) rule, which determines the types of businesses transactions can be processed from. + * [Optional] in the update control response, [Required] in the get control response. + */ private MccLimit mccLimit; @Builder diff --git a/src/main/java/com/checkout/issuing/controls/responses/create/MidCardControlResponse.java b/src/main/java/com/checkout/issuing/controls/responses/create/MidCardControlResponse.java index 6a9f73308..d38c6c4c5 100644 --- a/src/main/java/com/checkout/issuing/controls/responses/create/MidCardControlResponse.java +++ b/src/main/java/com/checkout/issuing/controls/responses/create/MidCardControlResponse.java @@ -8,12 +8,19 @@ import lombok.Setter; import lombok.ToString; +/** + * Card control response for control_type {@code mid_limit}. + */ @Getter @Setter @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public final class MidCardControlResponse extends CardControlResponse { + /** + * The merchant identification (MID) code rule, which determines the merchants from whom transactions can be processed. + * [Optional] in the update control response, [Required] in the get control response. + */ private MidLimit midLimit; @Builder diff --git a/src/main/java/com/checkout/issuing/controls/responses/create/VelocityCardControlResponse.java b/src/main/java/com/checkout/issuing/controls/responses/create/VelocityCardControlResponse.java index 87bd7d570..b9c7b9737 100644 --- a/src/main/java/com/checkout/issuing/controls/responses/create/VelocityCardControlResponse.java +++ b/src/main/java/com/checkout/issuing/controls/responses/create/VelocityCardControlResponse.java @@ -8,12 +8,19 @@ import lombok.Setter; import lombok.ToString; +/** + * Card control response for control_type {@code velocity_limit}. + */ @Getter @Setter @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public final class VelocityCardControlResponse extends CardControlResponse { + /** + * The velocity limit, which determines how much a target card can spend over a given timeframe. + * [Optional] in the update control response, [Required] in the get control response. + */ private VelocityLimit velocityLimit; @Builder diff --git a/src/test/java/com/checkout/issuing/controls/responses/create/CardControlResponseSerializationTest.java b/src/test/java/com/checkout/issuing/controls/responses/create/CardControlResponseSerializationTest.java new file mode 100644 index 000000000..d4682e46f --- /dev/null +++ b/src/test/java/com/checkout/issuing/controls/responses/create/CardControlResponseSerializationTest.java @@ -0,0 +1,201 @@ +package com.checkout.issuing.controls.responses.create; + +import com.checkout.GsonSerializer; +import com.checkout.issuing.controls.requests.ControlType; +import com.checkout.issuing.controls.requests.MccLimit; +import com.checkout.issuing.controls.requests.MidLimit; +import com.checkout.issuing.controls.requests.VelocityLimit; +import com.checkout.issuing.controls.requests.VelocityWindow; +import com.checkout.issuing.controls.requests.VelocityWindowType; +import com.checkout.issuing.controls.requests.create.VelocityCardControlRequest; +import com.checkout.issuing.controls.responses.query.CardControlsQueryResponse; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Serialization tests for the card control responses returned by create, get, list and update control + * operations (update-control-response and its velocity, mcc and mid subtypes, discriminated on control_type). + */ +class CardControlResponseSerializationTest { + + private final GsonSerializer serializer = new GsonSerializer(); + + private static final String BASE_FIELDS = + "\"id\": \"ctr_gp7vkmxayztufjz6top5bjcdra\"," + + "\"target_id\": \"crd_fa6psq42dcdd6fdn5gifcq1491\"," + + "\"is_editable\": true," + + "\"created_date\": \"2021-09-09T19:41:39Z\"," + + "\"last_modified_date\": \"2021-09-09T19:41:39Z\","; + + private static final String VELOCITY_JSON = "{" + BASE_FIELDS + + "\"control_type\": \"velocity_limit\"," + + "\"description\": \"Maximum spend of 500€ per week for restaurants\"," + + "\"velocity_limit\": {" + + "\"amount_remaining\": 45000," + + "\"amount_limit\": 50000," + + "\"velocity_window\": {\"type\": \"weekly\"}," + + "\"mcc_list\": [\"4121\", \"4582\"]," + + "\"mid_list\": [\"1234567890\"]" + + "}}"; + + private static final String MCC_JSON = "{" + BASE_FIELDS + + "\"control_type\": \"mcc_limit\"," + + "\"description\": \"Allow the card to be used only in restaurants and supermarkets\"," + + "\"mcc_limit\": {\"type\": \"allow\", \"mcc_list\": [\"5932\", \"5411\"]}" + + "}"; + + private static final String MID_JSON = "{" + BASE_FIELDS + + "\"control_type\": \"mid_limit\"," + + "\"description\": \"Allow the card to be used only in AZ Pizza\"," + + "\"mid_limit\": {\"type\": \"allow\", \"mid_list\": [\"593278\", \"541114\"]}" + + "}"; + + private static void assertBaseFields(final CardControlResponse response) { + assertEquals("ctr_gp7vkmxayztufjz6top5bjcdra", response.getId()); + assertEquals("crd_fa6psq42dcdd6fdn5gifcq1491", response.getTargetId()); + assertEquals(Boolean.TRUE, response.getIsEditable()); + assertEquals(Instant.parse("2021-09-09T19:41:39Z"), response.getCreatedDate()); + assertEquals(Instant.parse("2021-09-09T19:41:39Z"), response.getLastModifiedDate()); + } + + // ------------------------------------------------------------------------ + // VelocityCardControlResponse (update-control-velocity-limit-response, + // velocity_limit is VelocityLimitWithRemainingAmount) + // ------------------------------------------------------------------------ + + @Test + void shouldDeserializeSwaggerExampleForVelocityCardControlResponse() { + final CardControlResponse response = serializer.fromJson(VELOCITY_JSON, CardControlResponse.class); + + final VelocityCardControlResponse velocity = assertInstanceOf(VelocityCardControlResponse.class, response); + assertBaseFields(velocity); + assertEquals(ControlType.VELOCITY_LIMIT, velocity.getControlType()); + assertEquals("Maximum spend of 500€ per week for restaurants", velocity.getDescription()); + assertEquals(Long.valueOf(45000L), velocity.getVelocityLimit().getAmountRemaining()); + assertEquals(Integer.valueOf(50000), velocity.getVelocityLimit().getAmountLimit()); + assertEquals(VelocityWindowType.WEEKLY, velocity.getVelocityLimit().getVelocityWindow().getType()); + assertEquals(Arrays.asList("4121", "4582"), velocity.getVelocityLimit().getMccList()); + assertEquals(Arrays.asList("1234567890"), velocity.getVelocityLimit().getMidList()); + } + + @Test + void shouldRoundTripVelocityCardControlResponseWithAllFields() { + final CardControlResponse original = serializer.fromJson(VELOCITY_JSON, CardControlResponse.class); + + final String json = serializer.toJson(original); + assertTrue(json.contains("\"amount_remaining\":45000")); + assertTrue(json.contains("\"is_editable\":true")); + + final CardControlResponse roundTripped = serializer.fromJson(json, CardControlResponse.class); + assertEquals(original, roundTripped); + } + + // ------------------------------------------------------------------------ + // MccCardControlResponse (update-control-mcc-limit-response) + // ------------------------------------------------------------------------ + + @Test + void shouldDeserializeSwaggerExampleForMccCardControlResponse() { + final CardControlResponse response = serializer.fromJson(MCC_JSON, CardControlResponse.class); + + final MccCardControlResponse mcc = assertInstanceOf(MccCardControlResponse.class, response); + assertBaseFields(mcc); + assertEquals(ControlType.MCC_LIMIT, mcc.getControlType()); + assertEquals("Allow the card to be used only in restaurants and supermarkets", mcc.getDescription()); + assertEquals("allow", mcc.getMccLimit().getType()); + assertEquals(Arrays.asList("5932", "5411"), mcc.getMccLimit().getMccList()); + } + + @Test + void shouldRoundTripMccCardControlResponseWithAllFields() { + final CardControlResponse original = serializer.fromJson(MCC_JSON, CardControlResponse.class); + final CardControlResponse roundTripped = serializer.fromJson(serializer.toJson(original), CardControlResponse.class); + assertEquals(original, roundTripped); + } + + // ------------------------------------------------------------------------ + // MidCardControlResponse (update-control-mid-limit-response). + // Regression: the mid_limit subtype was not registered, so this body could not + // be deserialized into CardControlResponse. + // ------------------------------------------------------------------------ + + @Test + void shouldDeserializeSwaggerExampleForMidCardControlResponse() { + final CardControlResponse response = serializer.fromJson(MID_JSON, CardControlResponse.class); + + final MidCardControlResponse mid = assertInstanceOf(MidCardControlResponse.class, response); + assertBaseFields(mid); + assertEquals(ControlType.MID_LIMIT, mid.getControlType()); + assertEquals("Allow the card to be used only in AZ Pizza", mid.getDescription()); + assertEquals("allow", mid.getMidLimit().getType()); + assertEquals(Arrays.asList("593278", "541114"), mid.getMidLimit().getMidList()); + } + + @Test + void shouldRoundTripMidCardControlResponseWithAllFields() { + final CardControlResponse original = serializer.fromJson(MID_JSON, CardControlResponse.class); + final CardControlResponse roundTripped = serializer.fromJson(serializer.toJson(original), CardControlResponse.class); + assertEquals(original, roundTripped); + } + + // ------------------------------------------------------------------------ + // CardControlsQueryResponse (list controls by target) with every subtype + // ------------------------------------------------------------------------ + + @Test + void shouldDeserializeCardControlsQueryResponseWithAllSubtypes() { + final String json = "{\"controls\": [" + VELOCITY_JSON + "," + MCC_JSON + "," + MID_JSON + "]}"; + + final CardControlsQueryResponse response = serializer.fromJson(json, CardControlsQueryResponse.class); + + assertEquals(3, response.getControls().size()); + assertInstanceOf(VelocityCardControlResponse.class, response.getControls().get(0)); + assertInstanceOf(MccCardControlResponse.class, response.getControls().get(1)); + assertInstanceOf(MidCardControlResponse.class, response.getControls().get(2)); + } + + // ------------------------------------------------------------------------ + // VelocityLimit on requests: amount_remaining is response only and never sent + // ------------------------------------------------------------------------ + + @Test + void shouldNotSendAmountRemainingOnVelocityControlRequest() { + final VelocityCardControlRequest request = VelocityCardControlRequest.builder() + .description("Maximum spend of 500€ per week for restaurants") + .targetId("crd_fa6psq42dcdd6fdn5gifcq1491") + .velocityLimit(VelocityLimit.builder() + .amountLimit(50000) + .velocityWindow(VelocityWindow.builder().type(VelocityWindowType.WEEKLY).build()) + .mccList(Arrays.asList("4121", "4582")) + .build()) + .build(); + + assertNull(request.getVelocityLimit().getAmountRemaining()); + final String json = serializer.toJson(request); + assertFalse(json.contains("amount_remaining")); + assertTrue(json.contains("\"amount_limit\":50000")); + } + + @Test + void shouldBuildMccAndMidLimits() { + final MccCardControlResponse mcc = MccCardControlResponse.builder() + .mccLimit(MccLimit.builder().type("block").mccList(Arrays.asList("5411")).build()) + .build(); + final MidCardControlResponse mid = MidCardControlResponse.builder() + .midLimit(MidLimit.builder().type("block").midList(Arrays.asList("593278")).build()) + .build(); + + assertInstanceOf(MccCardControlResponse.class, + serializer.fromJson(serializer.toJson(mcc), CardControlResponse.class)); + assertInstanceOf(MidCardControlResponse.class, + serializer.fromJson(serializer.toJson(mid), CardControlResponse.class)); + } +}