diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..b2f83c8
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
+
+ com.acquirex
+ card-acquisition
+ 1.0.0-SNAPSHOT
+ card-acquisition
+ 카드 매입 승인 경로 - Spring Boot 3 전환
+
+
+ 17
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/acquirex/cardacquisition/CardAcquisitionApplication.java b/src/main/java/com/acquirex/cardacquisition/CardAcquisitionApplication.java
new file mode 100644
index 0000000..a262683
--- /dev/null
+++ b/src/main/java/com/acquirex/cardacquisition/CardAcquisitionApplication.java
@@ -0,0 +1,12 @@
+package com.acquirex.cardacquisition;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CardAcquisitionApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CardAcquisitionApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/acquirex/cardacquisition/controller/CardAcquisitionController.java b/src/main/java/com/acquirex/cardacquisition/controller/CardAcquisitionController.java
new file mode 100644
index 0000000..6811d89
--- /dev/null
+++ b/src/main/java/com/acquirex/cardacquisition/controller/CardAcquisitionController.java
@@ -0,0 +1,91 @@
+package com.acquirex.cardacquisition.controller;
+
+import com.acquirex.cardacquisition.dto.AcquisitionRequest;
+import com.acquirex.cardacquisition.dto.AcquisitionResponse;
+import com.acquirex.cardacquisition.service.CardAcquisitionService;
+import jakarta.validation.Valid;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/** Spring Boot 3 전환: javax.servlet → jakarta.servlet */
+@RestController
+@RequestMapping("/api/v1/acquisitions")
+public class CardAcquisitionController {
+
+ private final CardAcquisitionService service;
+
+ public CardAcquisitionController(CardAcquisitionService service) {
+ this.service = service;
+ }
+
+ @PostMapping
+ public ResponseEntity requestAcquisition(@Valid @RequestBody AcquisitionRequest request) {
+ return ResponseEntity.status(HttpStatus.CREATED).body(service.requestAcquisition(request));
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity getAcquisition(@PathVariable Long id) {
+ return ResponseEntity.ok(service.getAcquisition(id));
+ }
+
+ @GetMapping("/merchant/{merchantId}")
+ public ResponseEntity> getAcquisitionsByMerchant(@PathVariable String merchantId) {
+ return ResponseEntity.ok(service.getAcquisitionsByMerchant(merchantId));
+ }
+
+ @GetMapping("/pending")
+ public ResponseEntity> getPendingAcquisitions() {
+ return ResponseEntity.ok(service.getPendingAcquisitions());
+ }
+
+ @PostMapping("/{id}/cancel")
+ public ResponseEntity cancelAcquisition(@PathVariable Long id) {
+ return ResponseEntity.ok(service.cancelAcquisition(id));
+ }
+}
+
+/** Spring Boot 3 전환: javax.validation → jakarta.validation */
+@RestControllerAdvice
+class GlobalExceptionHandler {
+
+ @ExceptionHandler(CardAcquisitionService.AcquisitionNotFoundException.class)
+ public ResponseEntity> handleNotFound(CardAcquisitionService.AcquisitionNotFoundException ex) {
+ return buildErrorResponse(HttpStatus.NOT_FOUND, ex.getMessage());
+ }
+
+ @ExceptionHandler(CardAcquisitionService.InvalidAcquisitionException.class)
+ public ResponseEntity> handleInvalid(CardAcquisitionService.InvalidAcquisitionException ex) {
+ return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage());
+ }
+
+ @ExceptionHandler(IllegalStateException.class)
+ public ResponseEntity> handleIllegalState(IllegalStateException ex) {
+ return buildErrorResponse(HttpStatus.CONFLICT, ex.getMessage());
+ }
+
+ @ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class)
+ public ResponseEntity> handleValidationErrors(
+ org.springframework.web.bind.MethodArgumentNotValidException ex) {
+ java.util.Map fieldErrors = new java.util.HashMap<>();
+ ex.getBindingResult().getFieldErrors().forEach(e -> fieldErrors.put(e.getField(), e.getDefaultMessage()));
+
+ java.util.Map body = new java.util.HashMap<>();
+ body.put("timestamp", LocalDateTime.now());
+ body.put("status", HttpStatus.BAD_REQUEST.value());
+ body.put("error", "Validation Failed");
+ body.put("fieldErrors", fieldErrors);
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body);
+ }
+
+ private ResponseEntity> buildErrorResponse(HttpStatus status, String message) {
+ java.util.Map body = new java.util.HashMap<>();
+ body.put("timestamp", LocalDateTime.now());
+ body.put("status", status.value());
+ body.put("error", status.getReasonPhrase());
+ body.put("message", message);
+ return ResponseEntity.status(status).body(body);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/acquirex/cardacquisition/entity/CardAcquisition.java b/src/main/java/com/acquirex/cardacquisition/entity/CardAcquisition.java
new file mode 100644
index 0000000..c50e373
--- /dev/null
+++ b/src/main/java/com/acquirex/cardacquisition/entity/CardAcquisition.java
@@ -0,0 +1,183 @@
+package com.acquirex.cardacquisition.entity;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/** Spring Boot 3 전환: javax.persistence → jakarta.persistence */
+@Entity
+@Table(name = "card_acquisitions")
+public class CardAcquisition {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "merchant_id", nullable = false)
+ private String merchantId;
+
+ @Column(name = "card_number", nullable = false)
+ private String cardNumber;
+
+ @Column(name = "amount", nullable = false, precision = 15, scale = 2)
+ private BigDecimal amount;
+
+ @Column(name = "currency", nullable = false, length = 3)
+ private String currency;
+
+ @Enumerated(EnumType.STRING)
+ @Column(name = "status", nullable = false)
+ private AcquisitionStatus status = AcquisitionStatus.PENDING;
+
+ @Column(name = "approval_code")
+ private String approvalCode;
+
+ @Column(name = "request_time", nullable = false)
+ private LocalDateTime requestTime = LocalDateTime.now();
+
+ @Column(name = "approval_time")
+ private LocalDateTime approvalTime;
+
+ @Column(name = "reject_reason")
+ private String rejectReason;
+
+ public CardAcquisition() {}
+
+ public CardAcquisition(String merchantId, String cardNumber, BigDecimal amount, String currency) {
+ this.merchantId = merchantId;
+ this.cardNumber = cardNumber;
+ this.amount = amount;
+ this.currency = currency;
+ this.status = AcquisitionStatus.PENDING;
+ this.requestTime = LocalDateTime.now();
+ }
+
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+ public String getMerchantId() { return merchantId; }
+ public void setMerchantId(String merchantId) { this.merchantId = merchantId; }
+ public String getCardNumber() { return cardNumber; }
+ public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; }
+ public BigDecimal getAmount() { return amount; }
+ public void setAmount(BigDecimal amount) { this.amount = amount; }
+ public String getCurrency() { return currency; }
+ public void setCurrency(String currency) { this.currency = currency; }
+ public AcquisitionStatus getStatus() { return status; }
+ public void setStatus(AcquisitionStatus status) { this.status = status; }
+ public String getApprovalCode() { return approvalCode; }
+ public void setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; }
+ public LocalDateTime getRequestTime() { return requestTime; }
+ public void setRequestTime(LocalDateTime requestTime) { this.requestTime = requestTime; }
+ public LocalDateTime getApprovalTime() { return approvalTime; }
+ public void setApprovalTime(LocalDateTime approvalTime) { this.approvalTime = approvalTime; }
+ public String getRejectReason() { return rejectReason; }
+ public void setRejectReason(String rejectReason) { this.rejectReason = rejectReason; }
+
+ public void approve(String approvalCode) {
+ this.status = AcquisitionStatus.APPROVED;
+ this.approvalCode = approvalCode;
+ this.approvalTime = LocalDateTime.now();
+ }
+
+ public void reject(String reason) {
+ this.status = AcquisitionStatus.REJECTED;
+ this.rejectReason = reason;
+ this.approvalTime = LocalDateTime.now();
+ }
+
+ /** Spring Boot 3 전환: javax.persistence → jakarta.persistence */
+ public enum AcquisitionStatus {
+ PENDING, APPROVED, REJECTED, CANCELLED
+ }
+}
+
+/** Spring Boot 3 전환: javax.validation → jakarta.validation */
+class AcquisitionRequest {
+ @NotBlank(message = "상점 ID는 필수입니다")
+ private String merchantId;
+
+ @NotBlank(message = "카드번호는 필수입니다")
+ @Pattern(regexp = "\\d{10,19}", message = "유효하지 않은 카드번호 형식입니다")
+ private String cardNumber;
+
+ @NotNull(message = "금액은 필수입니다")
+ @DecimalMin(value = "0.01", message = "금액은 0보다 커야 합니다")
+ private BigDecimal amount;
+
+ @NotBlank(message = "통화는 필수입니다")
+ @Size(min = 3, max = 3, message = "통화 코드는 3자리입니다")
+ private String currency;
+
+ public AcquisitionRequest() {}
+ public AcquisitionRequest(String merchantId, String cardNumber, BigDecimal amount, String currency) {
+ this.merchantId = merchantId;
+ this.cardNumber = cardNumber;
+ this.amount = amount;
+ this.currency = currency;
+ }
+
+ public String getMerchantId() { return merchantId; }
+ public void setMerchantId(String merchantId) { this.merchantId = merchantId; }
+ public String getCardNumber() { return cardNumber; }
+ public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; }
+ public BigDecimal getAmount() { return amount; }
+ public void setAmount(BigDecimal amount) { this.amount = amount; }
+ public String getCurrency() { return currency; }
+ public void setCurrency(String currency) { this.currency = currency; }
+}
+
+class AcquisitionResponse {
+ private Long id;
+ private String merchantId;
+ private String maskedCardNumber;
+ private BigDecimal amount;
+ private String currency;
+ private CardAcquisition.AcquisitionStatus status;
+ private String approvalCode;
+ private LocalDateTime requestTime;
+ private LocalDateTime approvalTime;
+ private String message;
+
+ public AcquisitionResponse() {}
+
+ public static AcquisitionResponse fromEntity(CardAcquisition entity) {
+ AcquisitionResponse response = new AcquisitionResponse();
+ response.setId(entity.getId());
+ response.setMerchantId(entity.getMerchantId());
+ response.setMaskedCardNumber(maskCardNumber(entity.getCardNumber()));
+ response.setAmount(entity.getAmount());
+ response.setCurrency(entity.getCurrency());
+ response.setStatus(entity.getStatus());
+ response.setApprovalCode(entity.getApprovalCode());
+ response.setRequestTime(entity.getRequestTime());
+ response.setApprovalTime(entity.getApprovalTime());
+ return response;
+ }
+
+ private static String maskCardNumber(String cardNumber) {
+ if (cardNumber == null || cardNumber.length() < 8) return "****";
+ return cardNumber.substring(0, 4) + "****" + cardNumber.substring(cardNumber.length() - 4);
+ }
+
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+ public String getMerchantId() { return merchantId; }
+ public void setMerchantId(String merchantId) { this.merchantId = merchantId; }
+ public String getMaskedCardNumber() { return maskedCardNumber; }
+ public void setMaskedCardNumber(String maskedCardNumber) { this.maskedCardNumber = maskedCardNumber; }
+ public BigDecimal getAmount() { return amount; }
+ public void setAmount(BigDecimal amount) { this.amount = amount; }
+ public String getCurrency() { return currency; }
+ public void setCurrency(String currency) { this.currency = currency; }
+ public CardAcquisition.AcquisitionStatus getStatus() { return status; }
+ public void setStatus(CardAcquisition.AcquisitionStatus status) { this.status = status; }
+ public String getApprovalCode() { return approvalCode; }
+ public void setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; }
+ public LocalDateTime getRequestTime() { return requestTime; }
+ public void setRequestTime(LocalDateTime requestTime) { this.requestTime = requestTime; }
+ public LocalDateTime getApprovalTime() { return approvalTime; }
+ public void setApprovalTime(LocalDateTime approvalTime) { this.approvalTime = approvalTime; }
+ public String getMessage() { return message; }
+ public void setMessage(String message) { this.message = message; }
+}
\ No newline at end of file
diff --git a/src/main/java/com/acquirex/cardacquisition/repository/CardAcquisitionRepository.java b/src/main/java/com/acquirex/cardacquisition/repository/CardAcquisitionRepository.java
new file mode 100644
index 0000000..75b34af
--- /dev/null
+++ b/src/main/java/com/acquirex/cardacquisition/repository/CardAcquisitionRepository.java
@@ -0,0 +1,23 @@
+package com.acquirex.cardacquisition.repository;
+
+import com.acquirex.cardacquisition.entity.CardAcquisition;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface CardAcquisitionRepository extends JpaRepository {
+
+ List findByMerchantId(String merchantId);
+
+ List findByStatus(CardAcquisition.AcquisitionStatus status);
+
+ Optional findByApprovalCode(String approvalCode);
+
+ List findByRequestTimeBetween(LocalDateTime start, LocalDateTime end);
+
+ List findByMerchantIdAndStatus(String merchantId, CardAcquisition.AcquisitionStatus status);
+}
\ No newline at end of file
diff --git a/src/main/java/com/acquirex/cardacquisition/service/CardAcquisitionService.java b/src/main/java/com/acquirex/cardacquisition/service/CardAcquisitionService.java
new file mode 100644
index 0000000..f538519
--- /dev/null
+++ b/src/main/java/com/acquirex/cardacquisition/service/CardAcquisitionService.java
@@ -0,0 +1,106 @@
+package com.acquirex.cardacquisition.service;
+
+import com.acquirex.cardacquisition.entity.CardAcquisition;
+import com.acquirex.cardacquisition.repository.CardAcquisitionRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Service
+public class CardAcquisitionService {
+
+ private static final BigDecimal MAX_SINGLE_TRANSACTION = new BigDecimal("10000000");
+ private static final BigDecimal DAILY_LIMIT = new BigDecimal("50000000");
+
+ private final CardAcquisitionRepository repository;
+
+ public CardAcquisitionService(CardAcquisitionRepository repository) {
+ this.repository = repository;
+ }
+
+ @Transactional
+ public AcquisitionResponse requestAcquisition(AcquisitionRequest request) {
+ validateRequest(request);
+
+ CardAcquisition acquisition = new CardAcquisition(
+ request.getMerchantId(), request.getCardNumber(), request.getAmount(), request.getCurrency());
+
+ CardAcquisition saved = repository.save(acquisition);
+ String approvalCode = generateApprovalCode();
+ saved.approve(approvalCode);
+
+ AcquisitionResponse response = AcquisitionResponse.fromEntity(repository.save(saved));
+ response.setMessage("매입 승인이 완료되었습니다");
+ return response;
+ }
+
+ @Transactional(readOnly = true)
+ public AcquisitionResponse getAcquisition(Long id) {
+ CardAcquisition acquisition = repository.findById(id)
+ .orElseThrow(() -> new AcquisitionNotFoundException("매입 정보를 찾을 수 없습니다: " + id));
+ AcquisitionResponse response = AcquisitionResponse.fromEntity(acquisition);
+ response.setMessage("매입 정보를 조회했습니다");
+ return response;
+ }
+
+ @Transactional(readOnly = true)
+ public List getAcquisitionsByMerchant(String merchantId) {
+ return repository.findByMerchantId(merchantId).stream()
+ .map(AcquisitionResponse::fromEntity).collect(Collectors.toList());
+ }
+
+ @Transactional(readOnly = true)
+ public List getPendingAcquisitions() {
+ return repository.findByStatus(CardAcquisition.AcquisitionStatus.PENDING).stream()
+ .map(AcquisitionResponse::fromEntity).collect(Collectors.toList());
+ }
+
+ @Transactional
+ public AcquisitionResponse cancelAcquisition(Long id) {
+ CardAcquisition acquisition = repository.findById(id)
+ .orElseThrow(() -> new AcquisitionNotFoundException("매입 정보를 찾을 수 없습니다: " + id));
+
+ if (acquisition.getStatus() == CardAcquisition.AcquisitionStatus.CANCELLED) {
+ throw new IllegalStateException("이미 취소된 매입입니다");
+ }
+
+ acquisition.setStatus(CardAcquisition.AcquisitionStatus.CANCELLED);
+ acquisition.setApprovalTime(LocalDateTime.now());
+
+ AcquisitionResponse response = AcquisitionResponse.fromEntity(repository.save(acquisition));
+ response.setMessage("매입이 취소되었습니다");
+ return response;
+ }
+
+ private void validateRequest(AcquisitionRequest request) {
+ if (request.getAmount().compareTo(MAX_SINGLE_TRANSACTION) > 0) {
+ throw new InvalidAcquisitionException("단일 거래 한도를 초과했습니다");
+ }
+
+ LocalDateTime startOfDay = LocalDateTime.now().toLocalDate().atStartOfDay();
+ BigDecimal todayTotal = repository.findByRequestTimeBetween(startOfDay, startOfDay.plusDays(1)).stream()
+ .filter(a -> a.getMerchantId().equals(request.getMerchantId()))
+ .map(CardAcquisition::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
+
+ if (todayTotal.add(request.getAmount()).compareTo(DAILY_LIMIT) > 0) {
+ throw new InvalidAcquisitionException("일일 한도를 초과했습니다");
+ }
+ }
+
+ private String generateApprovalCode() {
+ return "APR" + UUID.randomUUID().toString().substring(0, 12).toUpperCase();
+ }
+
+ public static class AcquisitionNotFoundException extends RuntimeException {
+ public AcquisitionNotFoundException(String message) { super(message); }
+ }
+
+ public static class InvalidAcquisitionException extends RuntimeException {
+ public InvalidAcquisitionException(String message) { super(message); }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..920b6a5
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,24 @@
+spring.application.name=card-acquisition
+
+# H2 Database
+spring.datasource.url=jdbc:h2:mem:cardacquisition;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=
+
+# JPA/Hibernate 6 (Spring Boot 3)
+spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.show-sql=true
+spring.jpa.properties.hibernate.format_sql=true
+
+# H2 Console
+spring.h2.console.enabled=true
+spring.h2.console.path=/h2-console
+
+# Server
+server.port=8080
+
+# Jackson
+spring.jackson.serialization.write-dates-as-timestamps=false
+spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss
\ No newline at end of file
diff --git a/src/test/java/com/acquirex/cardacquisition/CardAcquisitionServiceTest.java b/src/test/java/com/acquirex/cardacquisition/CardAcquisitionServiceTest.java
new file mode 100644
index 0000000..57c3151
--- /dev/null
+++ b/src/test/java/com/acquirex/cardacquisition/CardAcquisitionServiceTest.java
@@ -0,0 +1,98 @@
+package com.acquirex.cardacquisition;
+
+import com.acquirex.cardacquisition.entity.CardAcquisition;
+import com.acquirex.cardacquisition.repository.CardAcquisitionRepository;
+import com.acquirex.cardacquisition.service.CardAcquisitionService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class CardAcquisitionServiceTest {
+
+ @Mock
+ private CardAcquisitionRepository repository;
+
+ private CardAcquisitionService service;
+
+ @BeforeEach
+ void setUp() {
+ service = new CardAcquisitionService(repository);
+ }
+
+ @Test
+ void requestAcquisition_ValidRequest_ReturnsApprovedResponse() {
+ when(repository.save(any(CardAcquisition.class))).thenAnswer(i -> {
+ CardAcquisition e = i.getArgument(0);
+ if (e.getId() == null) e.setId(1L);
+ return e;
+ });
+ when(repository.findByRequestTimeBetween(any(), any())).thenReturn(List.of());
+
+ AcquisitionRequest request = new AcquisitionRequest("MERCHANT001", "1234567890123456", new BigDecimal("50000"), "KRW");
+ AcquisitionResponse response = service.requestAcquisition(request);
+
+ assertNotNull(response);
+ assertEquals(1L, response.getId());
+ assertEquals(CardAcquisition.AcquisitionStatus.APPROVED, response.getStatus());
+ assertTrue(response.getApprovalCode().startsWith("APR"));
+ verify(repository, times(2)).save(any(CardAcquisition.class));
+ }
+
+ @Test
+ void requestAcquisition_ExceedsLimit_ThrowsException() {
+ AcquisitionRequest request = new AcquisitionRequest("MERCHANT001", "1234567890123456", new BigDecimal("15000000"), "KRW");
+ assertThrows(CardAcquisitionService.InvalidAcquisitionException.class, () -> service.requestAcquisition(request));
+ }
+
+ @Test
+ void getAcquisition_ExistingId_ReturnsResponse() {
+ CardAcquisition entity = new CardAcquisition("M1", "1111", new BigDecimal("1000"), "KRW");
+ entity.setId(1L);
+ entity.approve("APR123");
+ when(repository.findById(1L)).thenReturn(Optional.of(entity));
+
+ AcquisitionResponse response = service.getAcquisition(1L);
+
+ assertNotNull(response);
+ assertEquals(CardAcquisition.AcquisitionStatus.APPROVED, response.getStatus());
+ }
+
+ @Test
+ void getAcquisition_NotFound_ThrowsException() {
+ when(repository.findById(999L)).thenReturn(Optional.empty());
+ assertThrows(CardAcquisitionService.AcquisitionNotFoundException.class, () -> service.getAcquisition(999L));
+ }
+
+ @Test
+ void cancelAcquisition_ExistingPending_ReturnsCancelled() {
+ CardAcquisition entity = new CardAcquisition("M1", "1111", new BigDecimal("1000"), "KRW");
+ entity.setId(1L);
+ when(repository.findById(1L)).thenReturn(Optional.of(entity));
+ when(repository.save(any())).thenAnswer(i -> i.getArgument(0));
+
+ AcquisitionResponse response = service.cancelAcquisition(1L);
+
+ assertEquals(CardAcquisition.AcquisitionStatus.CANCELLED, response.getStatus());
+ }
+
+ @Test
+ void cancelAcquisition_AlreadyCancelled_ThrowsException() {
+ CardAcquisition entity = new CardAcquisition("M1", "1111", new BigDecimal("1000"), "KRW");
+ entity.setId(1L);
+ entity.setStatus(CardAcquisition.AcquisitionStatus.CANCELLED);
+ when(repository.findById(1L)).thenReturn(Optional.of(entity));
+
+ assertThrows(IllegalStateException.class, () -> service.cancelAcquisition(1L));
+ }
+}
\ No newline at end of file