카드 매입 승인 경로 Spring Boot 3 전환 #10
8 changed files with 323 additions and 0 deletions
29
proj-acquirex/README.md
Normal file
29
proj-acquirex/README.md
Normal file
|
|
@ -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 컨트롤러
|
||||
```
|
||||
66
proj-acquirex/pom.xml
Normal file
66
proj-acquirex/pom.xml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.acquirex</groupId>
|
||||
<artifactId>proj-acquirex</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>proj-acquirex</name>
|
||||
<description>카드 매입 승인 경로 - Spring Boot 3 전환</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<String, CardAcquisitionApprovalResponse> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue