diff --git a/proj-acquirex/README.md b/proj-acquirex/README.md new file mode 100644 index 0000000..9b1fc6d --- /dev/null +++ b/proj-acquirex/README.md @@ -0,0 +1,29 @@ +# proj-acquirex + +## 카드 매입 승인 경로 Spring Boot 3 전환 + +### 전환 범위 +- Spring Boot 3.2.5 (Jakarta EE 9+) +- Java 17+ +- Spring Framework 6.x +- Hibernate 6.x + +### 검증 절차 +1. `./mvnw clean compile` - 컴파일 검증 +2. `./mvnw test` - 단위 테스트 실행 +3. `./mvnw verify` - 통합 검증 + +### API 엔드포인트 +- `POST /api/v1/approvals/card-acquisition` - 카드 매입 승인 요청 +- `GET /api/v1/approvals/card-acquisition/{approvalId}` - 승인 상태 조회 + +### 프로젝트 구조 +``` +src/main/java/com/acquirex/ +├── AcquirexApplication.java # 메인 애플리케이션 +├── CardAcquisitionApprovalStatus.java # 승인 상태 enum +├── CardAcquisitionApprovalRequest.java # 요청 DTO +├── CardAcquisitionApprovalResponse.java # 응답 DTO +├── CardAcquisitionApprovalService.java # 비즈니스 로직 +└── CardAcquisitionApprovalController.java # REST 컨트롤러 +``` diff --git a/proj-acquirex/pom.xml b/proj-acquirex/pom.xml new file mode 100644 index 0000000..7f8bbd4 --- /dev/null +++ b/proj-acquirex/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + + com.acquirex + proj-acquirex + 1.0.0-SNAPSHOT + proj-acquirex + 카드 매입 승인 경로 - Spring Boot 3 전환 + + + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + true + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/proj-acquirex/src/main/java/com/acquirex/AcquirexApplication.java b/proj-acquirex/src/main/java/com/acquirex/AcquirexApplication.java new file mode 100644 index 0000000..cbf43e9 --- /dev/null +++ b/proj-acquirex/src/main/java/com/acquirex/AcquirexApplication.java @@ -0,0 +1,11 @@ +package com.acquirex; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AcquirexApplication { + public static void main(String[] args) { + SpringApplication.run(AcquirexApplication.class, args); + } +} diff --git a/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalRequest.java b/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalRequest.java new file mode 100644 index 0000000..5ae51d9 --- /dev/null +++ b/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalRequest.java @@ -0,0 +1,34 @@ +package com.acquirex.approval; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CardAcquisitionApprovalRequest { + @NotBlank(message = "가맹점 ID는 필수입니다") + private String merchantId; + + @NotBlank(message = "카드사 코드는 필수입니다") + private String cardCompanyCode; + + @NotNull(message = "거래 금액은 필수입니다") + @Positive(message = "거래 금액은 양수여야 합니다") + private BigDecimal transactionAmount; + + @NotBlank(message = "거래 유형은 필수입니다") + private String transactionType; + + private String merchantName; + private LocalDateTime requestDateTime; +} diff --git a/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalResponse.java b/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalResponse.java new file mode 100644 index 0000000..255c289 --- /dev/null +++ b/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalResponse.java @@ -0,0 +1,25 @@ +package com.acquirex.approval; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CardAcquisitionApprovalResponse { + private String approvalId; + private String merchantId; + private String cardCompanyCode; + private BigDecimal transactionAmount; + private CardAcquisitionApprovalStatus status; + private String statusMessage; + private LocalDateTime approvalDateTime; + private String responseCode; + private String responseMessage; +} diff --git a/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalService.java b/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalService.java new file mode 100644 index 0000000..9bb2985 --- /dev/null +++ b/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalService.java @@ -0,0 +1,59 @@ +package com.acquirex.approval; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Service +public class CardAcquisitionApprovalService { + private final Map approvalStore = new ConcurrentHashMap<>(); + + public CardAcquisitionApprovalResponse processApproval(CardAcquisitionApprovalRequest request) { + log.info("카드 매입 승인 요청 처리: merchantId={}, amount={}", + request.getMerchantId(), request.getTransactionAmount()); + + String approvalId = generateApprovalId(); + CardAcquisitionApprovalStatus status = determineApprovalStatus(request); + + CardAcquisitionApprovalResponse response = CardAcquisitionApprovalResponse.builder() + .approvalId(approvalId) + .merchantId(request.getMerchantId()) + .cardCompanyCode(request.getCardCompanyCode()) + .transactionAmount(request.getTransactionAmount()) + .status(status) + .statusMessage(status.getDescription()) + .approvalDateTime(LocalDateTime.now()) + .responseCode(status == CardAcquisitionApprovalStatus.APPROVED ? "0000" : "9999") + .responseMessage(status == CardAcquisitionApprovalStatus.APPROVED ? "승인 완료" : "승인 실패") + .build(); + + approvalStore.put(approvalId, response); + log.info("카드 매입 승인 완료: approvalId={}, status={}", approvalId, status); + return response; + } + + public CardAcquisitionApprovalResponse getApprovalStatus(String approvalId) { + log.info("승인 상태 조회: approvalId={}", approvalId); + return approvalStore.get(approvalId); + } + + private CardAcquisitionApprovalStatus determineApprovalStatus(CardAcquisitionApprovalRequest request) { + if (request.getTransactionAmount() == null) { + return CardAcquisitionApprovalStatus.REJECTED; + } + if (request.getTransactionAmount().compareTo(new BigDecimal("100000")) >= 0) { + return CardAcquisitionApprovalStatus.PROCESSING; + } + return CardAcquisitionApprovalStatus.APPROVED; + } + + private String generateApprovalId() { + return "APR-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); + } +} diff --git a/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalStatus.java b/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalStatus.java new file mode 100644 index 0000000..56a9d35 --- /dev/null +++ b/proj-acquirex/src/main/java/com/acquirex/approval/CardAcquisitionApprovalStatus.java @@ -0,0 +1,19 @@ +package com.acquirex.approval; + +public enum CardAcquisitionApprovalStatus { + PENDING("승인 대기"), + APPROVED("승인 완료"), + REJECTED("승인 거절"), + CANCELLED("승인 취소"), + PROCESSING("처리 중"); + + private final String description; + + CardAcquisitionApprovalStatus(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} diff --git a/proj-acquirex/src/test/java/com/acquirex/approval/CardAcquisitionApprovalServiceTest.java b/proj-acquirex/src/test/java/com/acquirex/approval/CardAcquisitionApprovalServiceTest.java new file mode 100644 index 0000000..db245b2 --- /dev/null +++ b/proj-acquirex/src/test/java/com/acquirex/approval/CardAcquisitionApprovalServiceTest.java @@ -0,0 +1,80 @@ +package com.acquirex.approval; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("카드 매입 승인 서비스 테스트") +class CardAcquisitionApprovalServiceTest { + + private CardAcquisitionApprovalService approvalService; + + @BeforeEach + void setUp() { + approvalService = new CardAcquisitionApprovalService(); + } + + @Test + @DisplayName("소액 거래 승인 처리 성공") + void testSmallAmountApproval() { + CardAcquisitionApprovalRequest request = CardAcquisitionApprovalRequest.builder() + .merchantId("MERCHANT001") + .cardCompanyCode("CARD001") + .transactionAmount(new BigDecimal("50000")) + .transactionType("PURCHASE") + .build(); + + CardAcquisitionApprovalResponse response = approvalService.processApproval(request); + + assertNotNull(response); + assertNotNull(response.getApprovalId()); + assertTrue(response.getApprovalId().startsWith("APR-")); + assertEquals("MERCHANT001", response.getMerchantId()); + assertEquals(CardAcquisitionApprovalStatus.APPROVED, response.getStatus()); + assertEquals("0000", response.getResponseCode()); + } + + @Test + @DisplayName("대액 거래 처리 중 상태") + void testLargeAmountProcessing() { + CardAcquisitionApprovalRequest request = CardAcquisitionApprovalRequest.builder() + .merchantId("MERCHANT002") + .cardCompanyCode("CARD002") + .transactionAmount(new BigDecimal("150000")) + .transactionType("PURCHASE") + .build(); + + CardAcquisitionApprovalResponse response = approvalService.processApproval(request); + + assertNotNull(response); + assertEquals(CardAcquisitionApprovalStatus.PROCESSING, response.getStatus()); + } + + @Test + @DisplayName("승인 상태 조회 성공") + void testGetApprovalStatus() { + CardAcquisitionApprovalRequest request = CardAcquisitionApprovalRequest.builder() + .merchantId("MERCHANT003") + .cardCompanyCode("CARD003") + .transactionAmount(new BigDecimal("30000")) + .transactionType("PURCHASE") + .build(); + + CardAcquisitionApprovalResponse created = approvalService.processApproval(request); + CardAcquisitionApprovalResponse found = approvalService.getApprovalStatus(created.getApprovalId()); + + assertNotNull(found); + assertEquals(created.getApprovalId(), found.getApprovalId()); + } + + @Test + @DisplayName("존재하지 않는 승인 ID 조회 시 null 반환") + void testGetNonExistentApproval() { + CardAcquisitionApprovalResponse response = approvalService.getApprovalStatus("APR-NONEXIST"); + assertNull(response); + } +}