diff --git a/pom.xml b/pom.xml index 5b31f78..99c40cf 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,15 @@ 11 + 1.4.2.Final + 30.1.1-jre + 5.7.2 + 1.18.20 + 1.3.1 + 8.0.24 + 1.5.4 + 2.5.0 + 2.5.0 @@ -32,7 +41,7 @@ com.google.guava guava - 30.1.1-jre + ${guava.version} @@ -53,7 +62,7 @@ org.junit.jupiter junit-jupiter-engine - 5.7.2 + ${junit.jupiter.version} test @@ -61,7 +70,7 @@ org.projectlombok lombok - 1.18.20 + ${lombok.version} provided @@ -69,19 +78,19 @@ com.google.cloud.sql mysql-socket-factory-connector-j-8 - 1.3.1 + ${mysql.socket.factory.version} mysql mysql-connector-java - 8.0.24 + ${mysql.connector.version} ma.glasnost.orika orika-core - 1.5.4 + ${orika.core.version} @@ -107,12 +116,19 @@ io.springfox springfox-swagger2 - 2.5.0 + ${springfox.swagger2.version} io.springfox springfox-swagger-ui - 2.5.0 + ${springfox.swagger.ui.version} + + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} @@ -124,6 +140,18 @@ 11 11 + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + diff --git a/src/main/java/com/bravo/user/controller/PaymentController.java b/src/main/java/com/bravo/user/controller/PaymentController.java new file mode 100644 index 0000000..1138fb9 --- /dev/null +++ b/src/main/java/com/bravo/user/controller/PaymentController.java @@ -0,0 +1,28 @@ +package com.bravo.user.controller; + +import com.bravo.user.annotation.SwaggerController; +import com.bravo.user.model.dto.PaymentDto; +import com.bravo.user.service.PaymentService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@SwaggerController +@RequestMapping("/payments") +@RequiredArgsConstructor +@Slf4j +public class PaymentController { + + private final PaymentService paymentService; + + @GetMapping + @ResponseBody + public List findPaymentByUserId(@RequestParam String userId) { + log.info("Starting to search payment for user with id {}", userId); + return paymentService.findPaymentByUserId(userId); + } +} diff --git a/src/main/java/com/bravo/user/controller/advice/ExceptionHandlerAdvice.java b/src/main/java/com/bravo/user/controller/advice/ExceptionHandlerAdvice.java index 463658c..97d8a88 100644 --- a/src/main/java/com/bravo/user/controller/advice/ExceptionHandlerAdvice.java +++ b/src/main/java/com/bravo/user/controller/advice/ExceptionHandlerAdvice.java @@ -1,8 +1,10 @@ package com.bravo.user.controller.advice; +import com.bravo.user.exception.PaymentNotFoundException; import com.bravo.user.model.dto.ErrorDto; import java.util.Set; import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -11,6 +13,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; @ControllerAdvice +@Slf4j public class ExceptionHandlerAdvice { @ExceptionHandler(value = BindException.class) @@ -31,4 +34,17 @@ public ErrorDto handleBindException(final BindException exception){ response.setStatusCode(400); return response; } + + @ExceptionHandler(value = PaymentNotFoundException.class) + @ResponseBody + @ResponseStatus(value = HttpStatus.NOT_FOUND) + public ErrorDto handlePaymentNotFoundException(final PaymentNotFoundException exception){ + log.info("Payments not found, details: {}", exception.getMessage()); + var errorDto = new ErrorDto(); + errorDto.setMessage(exception.getMessage()); + errorDto.setStatusCode(HttpStatus.NOT_FOUND.value()); + + return errorDto; + } + } diff --git a/src/main/java/com/bravo/user/dao/model/Payment.java b/src/main/java/com/bravo/user/dao/model/Payment.java index 12eb7a5..736ac9b 100644 --- a/src/main/java/com/bravo/user/dao/model/Payment.java +++ b/src/main/java/com/bravo/user/dao/model/Payment.java @@ -1,12 +1,12 @@ package com.bravo.user.dao.model; import java.time.LocalDateTime; -import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import lombok.Data; +import org.hibernate.annotations.GenericGenerator; @Entity @Data @@ -15,6 +15,7 @@ public class Payment { @Id @Column(name = "id") + @GenericGenerator(name = "uuid", strategy = "uuid4") private String id; @Column(name = "user_id", nullable = false) @@ -30,11 +31,6 @@ public class Payment { private Integer expiryYear; @Column(name = "updated", nullable = false) - private LocalDateTime updated; + private LocalDateTime updated = LocalDateTime.now(); - public Payment(){ - super(); - this.id = UUID.randomUUID().toString(); - this.updated = LocalDateTime.now(); - } } diff --git a/src/main/java/com/bravo/user/dao/model/mapper/PaymentMapper.java b/src/main/java/com/bravo/user/dao/model/mapper/PaymentMapper.java new file mode 100644 index 0000000..c3a8db6 --- /dev/null +++ b/src/main/java/com/bravo/user/dao/model/mapper/PaymentMapper.java @@ -0,0 +1,23 @@ +package com.bravo.user.dao.model.mapper; + +import com.bravo.user.dao.model.Payment; +import com.bravo.user.model.dto.PaymentDto; +import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper(componentModel = "spring") +public interface PaymentMapper { + + @Mapping(source = "cardNumber", target = "cardNumber", qualifiedByName = "lastFourCardDigits") + PaymentDto toPaymentDto(Payment payment); + + @Mapping(source = "cardNumber", target = "cardNumber", qualifiedByName = "lastFourCardDigits") + List toPaymentDtoList(List payments); + + @Named("lastFourCardDigits") + static String lastFourCardDigits(String cardNumber) { + return cardNumber.replaceAll("\\w(?=\\w{4})", "*"); + } +} diff --git a/src/main/java/com/bravo/user/dao/model/mapper/ResourceMapper.java b/src/main/java/com/bravo/user/dao/model/mapper/ResourceMapper.java index 4a78188..d8e4553 100644 --- a/src/main/java/com/bravo/user/dao/model/mapper/ResourceMapper.java +++ b/src/main/java/com/bravo/user/dao/model/mapper/ResourceMapper.java @@ -1,11 +1,9 @@ package com.bravo.user.dao.model.mapper; import com.bravo.user.dao.model.Address; -import com.bravo.user.dao.model.Payment; import com.bravo.user.dao.model.Profile; import com.bravo.user.dao.model.User; import com.bravo.user.model.dto.AddressDto; -import com.bravo.user.model.dto.PaymentDto; import com.bravo.user.model.dto.ProfileDto; import com.bravo.user.model.dto.UserReadDto; import java.util.Collection; @@ -41,17 +39,6 @@ public AddressDto convertAddress(final Address address){ return dto; } - public > List convertPayments(final T payments){ - return payments.stream().map(this::convertPayment).collect(Collectors.toList()); - } - - public PaymentDto convertPayment(final Payment payment){ - final String cardNumber = payment.getCardNumber(); - final PaymentDto dto = mapperFacade.map(payment, PaymentDto.class); - dto.setCardNumberLast4(cardNumber.substring(cardNumber.length() - 5)); - return dto; - } - public ProfileDto convertProfile(final Profile profile){ return mapperFacade.map(profile, ProfileDto.class); } diff --git a/src/main/java/com/bravo/user/dao/repository/PaymentRepository.java b/src/main/java/com/bravo/user/dao/repository/PaymentRepository.java new file mode 100644 index 0000000..5f48ac2 --- /dev/null +++ b/src/main/java/com/bravo/user/dao/repository/PaymentRepository.java @@ -0,0 +1,11 @@ +package com.bravo.user.dao.repository; + +import com.bravo.user.dao.model.Payment; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PaymentRepository extends JpaRepository { + List findByUserId(String userId); +} diff --git a/src/main/java/com/bravo/user/exception/PaymentNotFoundException.java b/src/main/java/com/bravo/user/exception/PaymentNotFoundException.java new file mode 100644 index 0000000..bd152e6 --- /dev/null +++ b/src/main/java/com/bravo/user/exception/PaymentNotFoundException.java @@ -0,0 +1,12 @@ +package com.bravo.user.exception; + +import java.text.MessageFormat; + +public class PaymentNotFoundException extends RuntimeException { + + private static final String MESSAGE = "Payment not found for user with id {0}"; + + public PaymentNotFoundException(String userId) { + super(MessageFormat.format(MESSAGE, userId)); + } +} diff --git a/src/main/java/com/bravo/user/model/dto/ErrorDto.java b/src/main/java/com/bravo/user/model/dto/ErrorDto.java index 93998d3..e9fa9c4 100644 --- a/src/main/java/com/bravo/user/model/dto/ErrorDto.java +++ b/src/main/java/com/bravo/user/model/dto/ErrorDto.java @@ -1,15 +1,19 @@ package com.bravo.user.model.dto; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import java.util.Set; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor +@JsonInclude(Include.NON_NULL) public class ErrorDto { private Set errors; private Class exception; private String message; private Integer statusCode; + } diff --git a/src/main/java/com/bravo/user/model/dto/PaymentDto.java b/src/main/java/com/bravo/user/model/dto/PaymentDto.java index db32487..d70b9df 100644 --- a/src/main/java/com/bravo/user/model/dto/PaymentDto.java +++ b/src/main/java/com/bravo/user/model/dto/PaymentDto.java @@ -1,13 +1,18 @@ package com.bravo.user.model.dto; import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@Builder +@AllArgsConstructor +@NoArgsConstructor public class PaymentDto { - private String id; - private String cardNumberLast4; + private String cardNumber; private Integer expiryMonth; private Integer expiryYear; private LocalDateTime updated; diff --git a/src/main/java/com/bravo/user/service/PaymentService.java b/src/main/java/com/bravo/user/service/PaymentService.java new file mode 100644 index 0000000..58eb132 --- /dev/null +++ b/src/main/java/com/bravo/user/service/PaymentService.java @@ -0,0 +1,30 @@ +package com.bravo.user.service; + +import com.bravo.user.dao.model.mapper.PaymentMapper; +import com.bravo.user.dao.repository.PaymentRepository; +import com.bravo.user.exception.PaymentNotFoundException; +import com.bravo.user.model.dto.PaymentDto; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class PaymentService { + + private final PaymentRepository paymentRepository; + private final PaymentMapper paymentMapper; + + public List findPaymentByUserId(String userId) { + var payments = paymentRepository.findByUserId(userId); + + if(payments.isEmpty()) { + throw new PaymentNotFoundException(userId); + } + + return paymentMapper.toPaymentDtoList(payments); + } + +} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index da45720..e79ea1a 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -648,3 +648,10 @@ insert into address (id, user_id, line1, line2, city, state, zip) values ('42f33d30-f3f8-4743-a94e-4db11fdb747d', '008a4215-0b1d-445e-b655-a964039cbb5a', '412 Maple St', null, 'Dowagiac', 'Michigan', '49047'), ('579872ec-46f8-46b5-b809-d0724d965f0e', '00963d9b-f884-485e-9455-fcf30c6ac379', '237 Mountain Ter', 'Apt 10', 'Odenville', 'Alabama', '35120'), ('95a983d0-ba0e-4f30-afb6-667d4724b253', '00963d9b-f884-485e-9455-fcf30c6ac379', '107 Annettes Ct', null, 'Aydlett', 'North Carolina', '27916'); + +insert into payment (id, user_id, card_number, expiry_month, expiry_year) values +('45cb8ef20-5e39-472e-a967-06dee934b0f3', '008a4215-0b1d-445e-b655-a964039cbb5a', '288925317311943', '02', '2026'), +('579872ec-46f8-46b5-b809-d0724d965f0e', '008a4215-0b1d-445e-b655-a964039cbb5a', '377925317620670', '01', '2031'), +('95a983d0-ba0e-4f30-afb6-667d4724b253', '008a4215-0b1d-445e-b655-a964039cbb5a', '377925317621133', '02', '2021'), +('ddabb1fa-0c94-451e-aa76-84c14a24c578', '00963d9b-f884-485e-9455-fcf30c6ac379', '374141394130819', '05', '2023'), +('fa57433d-b7ed-4028-99d9-ce1919362de2', '00963d9b-f884-485e-9455-fcf30c6ac379', '372120757513243', '03', '2025'); \ No newline at end of file diff --git a/src/test/java/com/bravo/user/TestUtils.java b/src/test/java/com/bravo/user/TestUtils.java new file mode 100644 index 0000000..1be45fa --- /dev/null +++ b/src/test/java/com/bravo/user/TestUtils.java @@ -0,0 +1,25 @@ +package com.bravo.user; + +import com.bravo.user.config.AppConfig; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.file.Files; +import java.nio.file.Path; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; + +@UtilityClass +public class TestUtils { + private static final ObjectMapper OBJECT_MAPPER = new AppConfig().objectMapperBuilder().build(); + + @SneakyThrows + public String readFromFile(String file) { + var url = TestUtils.class.getResource(file); + return Files.readString(Path.of(url.getPath())); + } + + @SneakyThrows + public static T deserializeFromJson(String json, TypeReference clazz) { + return OBJECT_MAPPER.readValue(json, clazz); + } +} diff --git a/src/test/java/com/bravo/user/controller/PaymentControllerTest.java b/src/test/java/com/bravo/user/controller/PaymentControllerTest.java new file mode 100644 index 0000000..5c966a7 --- /dev/null +++ b/src/test/java/com/bravo/user/controller/PaymentControllerTest.java @@ -0,0 +1,69 @@ +package com.bravo.user.controller; + +import com.bravo.user.TestUtils; +import com.bravo.user.exception.PaymentNotFoundException; +import com.bravo.user.model.dto.PaymentDto; +import com.bravo.user.service.PaymentService; +import com.fasterxml.jackson.core.type.TypeReference; +import java.util.List; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class PaymentControllerTest { + + private static final String SUCCESS_PAYMENT_RESPONSE_PATH = "/response/paymentsResponse.json"; + private static final String PAYMENTS_NOT_FOUND_RESPONSE_PATH = "/response/paymentsNotFoundResponse.json"; + + private static final String TEST_USER_ID = "testUserId"; + private static final String PAYMENTS_ENDPOINT = "/payments"; + private static final String USER_ID_PARAM_NAME = "userId"; + + @Autowired + private MockMvc mvc; + + @MockBean + private PaymentService paymentService; + + @Test + @SneakyThrows + public void successfullyFoundPaymentByUserIdTest() { + var successResponse = TestUtils.readFromFile(SUCCESS_PAYMENT_RESPONSE_PATH); + List payments = TestUtils.deserializeFromJson(successResponse, new TypeReference<>() {}); + + Mockito.when(paymentService.findPaymentByUserId(TEST_USER_ID)).thenReturn(payments); + + mvc.perform(get(PAYMENTS_ENDPOINT) + .queryParam(USER_ID_PARAM_NAME, TEST_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().json(successResponse)); + } + + @Test + @SneakyThrows + public void paymentsNotFoundShouldReturnNotFoundWithMessageTest() { + var paymentsNotFoundResponse = TestUtils.readFromFile(PAYMENTS_NOT_FOUND_RESPONSE_PATH); + + Mockito.when(paymentService.findPaymentByUserId(TEST_USER_ID)).thenThrow(new PaymentNotFoundException(TEST_USER_ID)); + + mvc.perform(get(PAYMENTS_ENDPOINT) + .queryParam(USER_ID_PARAM_NAME, TEST_USER_ID) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andExpect(content().json(paymentsNotFoundResponse)); + } + +} \ No newline at end of file diff --git a/src/test/java/com/bravo/user/dao/model/mapper/PaymentMapperTest.java b/src/test/java/com/bravo/user/dao/model/mapper/PaymentMapperTest.java new file mode 100644 index 0000000..f886927 --- /dev/null +++ b/src/test/java/com/bravo/user/dao/model/mapper/PaymentMapperTest.java @@ -0,0 +1,64 @@ +package com.bravo.user.dao.model.mapper; + +import com.bravo.user.dao.model.Payment; +import com.bravo.user.model.dto.PaymentDto; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(classes = PaymentMapperImpl.class) +public class PaymentMapperTest { + + private static final String TEST_CARD_NUMBER = "123456789"; + private static final String MASKED_TEST_CARD_NUMBER = "*****6789"; + private static final int TEST_EXPIRY_MONTH = 2; + private static final int TEST_EXPIRY_YEAR = 2022; + private static final LocalDateTime TEST_UPDATED = LocalDateTime.now(); + + @Autowired + private PaymentMapper paymentMapper; + + @Test + public void shouldSuccessfullyMapPaymentToPaymentDtoTest() { + var paymentDto = paymentMapper.toPaymentDto(createTestPayment()); + var expectedPaymentDto = createTestPaymentDto(); + assertEquals(expectedPaymentDto, paymentDto); + } + + @Test + public void shouldSuccessfullyMapPaymentListToPaymentDtoListTest() { + var paymentDtoList = paymentMapper.toPaymentDtoList(List.of(createTestPayment())); + var expectedPaymentDtoList = List.of(createTestPaymentDto()); + assertEquals(expectedPaymentDtoList, paymentDtoList); + } + + @Test + public void shouldSuccessfullyMaskCardNumberTest() { + var actualMaskedCard = PaymentMapper.lastFourCardDigits(TEST_CARD_NUMBER); + + assertEquals(MASKED_TEST_CARD_NUMBER, actualMaskedCard); + } + + private Payment createTestPayment() { + var payment = new Payment(); + payment.setCardNumber(TEST_CARD_NUMBER); + payment.setExpiryMonth(TEST_EXPIRY_MONTH); + payment.setExpiryYear(TEST_EXPIRY_YEAR); + payment.setUpdated(TEST_UPDATED); + + return payment; + } + + private PaymentDto createTestPaymentDto() { + return PaymentDto.builder() + .expiryYear(TEST_EXPIRY_YEAR) + .expiryMonth(TEST_EXPIRY_MONTH) + .cardNumber(MASKED_TEST_CARD_NUMBER) + .updated(TEST_UPDATED) + .build(); + } +} diff --git a/src/test/java/com/bravo/user/service/PaymentServiceTest.java b/src/test/java/com/bravo/user/service/PaymentServiceTest.java new file mode 100644 index 0000000..ebc0787 --- /dev/null +++ b/src/test/java/com/bravo/user/service/PaymentServiceTest.java @@ -0,0 +1,77 @@ +package com.bravo.user.service; + +import com.bravo.user.dao.model.Payment; +import com.bravo.user.dao.model.mapper.PaymentMapper; +import com.bravo.user.dao.repository.PaymentRepository; +import com.bravo.user.exception.PaymentNotFoundException; +import com.bravo.user.model.dto.PaymentDto; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class PaymentServiceTest { + + private static final String TEST_USER_ID = "testUserId"; + + @Mock + private PaymentRepository paymentRepository; + @Mock + private PaymentMapper paymentMapper; + + @InjectMocks + private PaymentService paymentService; + + @Test + public void paymentNotFoundShouldThrowExceptionTest() { + var expectedErrorMessage = "Payment not found for user with id " + TEST_USER_ID; + + when(paymentRepository.findByUserId(TEST_USER_ID)).thenReturn(Collections.emptyList()); + + var exception = assertThrows(PaymentNotFoundException.class, + () -> paymentService.findPaymentByUserId(TEST_USER_ID)); + + assertEquals(expectedErrorMessage, exception.getMessage()); + } + + @Test + public void paymentsIsFoundShouldSuccessfullyReturnPaymentsTest() { + var testPayments = List.of(createTestPayment()); + var expectedPayments = List.of(createTestPaymentDto()); + + when(paymentRepository.findByUserId(TEST_USER_ID)).thenReturn(testPayments); + when(paymentMapper.toPaymentDtoList(testPayments)).thenReturn(expectedPayments); + + var actualPayments = paymentService.findPaymentByUserId(TEST_USER_ID); + + assertEquals(expectedPayments, actualPayments); + } + + private Payment createTestPayment() { + var payment = new Payment(); + payment.setCardNumber("testCardNumber"); + payment.setExpiryMonth(2); + payment.setExpiryYear(2022); + payment.setUpdated(LocalDateTime.now()); + + return payment; + } + + private PaymentDto createTestPaymentDto() { + return PaymentDto.builder() + .cardNumber("testCardNumber") + .expiryMonth(2) + .expiryYear(2022) + .updated(LocalDateTime.now()) + .build(); + } +} diff --git a/src/test/resources/response/paymentsNotFoundResponse.json b/src/test/resources/response/paymentsNotFoundResponse.json new file mode 100644 index 0000000..80b67a0 --- /dev/null +++ b/src/test/resources/response/paymentsNotFoundResponse.json @@ -0,0 +1,4 @@ +{ + "message": "Payment not found for user with id testUserId", + "statusCode": 404 +} \ No newline at end of file diff --git a/src/test/resources/response/paymentsResponse.json b/src/test/resources/response/paymentsResponse.json new file mode 100644 index 0000000..3a4666f --- /dev/null +++ b/src/test/resources/response/paymentsResponse.json @@ -0,0 +1,23 @@ +[ + { + "id": "45cb8ef20-5e39-472e-a967-06dee934b0f3", + "cardNumber": "***********1943", + "expiryMonth": 2, + "expiryYear": 2021, + "updated": "2022-03-17 21:09:29" + }, + { + "id": "579872ec-46f8-46b5-b809-d0724d965f0e", + "cardNumber": "***********0670", + "expiryMonth": 2, + "expiryYear": 2021, + "updated": "2022-03-17 21:09:29" + }, + { + "id": "95a983d0-ba0e-4f30-afb6-667d4724b253", + "cardNumber": "***********1133", + "expiryMonth": 2, + "expiryYear": 2021, + "updated": "2022-03-17 21:09:29" + } +] \ No newline at end of file