Compare commits

...
Sign in to create a new pull request.

8 commits

Author SHA1 Message Date
c28de6ef7e 카드 매입 승인 경로 Spring Boot 3 전환 (ACX-E2E-1785394564819)
All checks were successful
Verify / source-contract (pull_request) Successful in 10s
Verify / source-contract (push) Successful in 11s
2026-07-30 09:33:52 +00:00
9f883274d0 카드 매입 승인 경로 Spring Boot 3 전환 (ACX-E2E-1785394564819)
Some checks failed
Verify / source-contract (push) Has been cancelled
2026-07-30 09:33:51 +00:00
8393aac0dd 카드 매입 승인 경로 Spring Boot 3 전환 (ACX-E2E-1785394564819)
Some checks are pending
Verify / source-contract (push) Waiting to run
2026-07-30 09:33:49 +00:00
2cec4b0a16 카드 매입 승인 경로 Spring Boot 3 전환 (ACX-E2E-1785394564819)
Some checks failed
Verify / source-contract (push) Has been cancelled
2026-07-30 09:33:48 +00:00
ad8fb595d0 카드 매입 승인 경로 Spring Boot 3 전환 (ACX-E2E-1785394564819)
Some checks are pending
Verify / source-contract (push) Waiting to run
2026-07-30 09:33:47 +00:00
6221af0ab6 카드 매입 승인 경로 Spring Boot 3 전환 (ACX-E2E-1785394564819)
Some checks are pending
Verify / source-contract (push) Waiting to run
2026-07-30 09:33:45 +00:00
f8b4402cc7 카드 매입 승인 경로 Spring Boot 3 전환 (ACX-E2E-1785394564819)
Some checks are pending
Verify / source-contract (push) Waiting to run
2026-07-30 09:33:44 +00:00
4d18481507 카드 매입 승인 경로 Spring Boot 3 전환 (ACX-E2E-1785394564819)
Some checks failed
Verify / source-contract (push) Has been cancelled
2026-07-30 09:33:43 +00:00
8 changed files with 328 additions and 0 deletions

69
proj-acquirex/pom.xml Normal file
View file

@ -0,0 +1,69 @@
<?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 http://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>
<packaging>jar</packaging>
<name>proj-acquirex</name>
<description>카드 매입 승인 경로 - Spring Boot 3 마이그레이션</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-bom</artifactId>
<version>10.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>

View file

@ -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);
}
}

View file

@ -0,0 +1,51 @@
package com.acquirex.approval.controller;
import com.acquirex.approval.domain.ApprovalDomain.Request;
import com.acquirex.approval.domain.ApprovalDomain.Response;
import com.acquirex.approval.domain.ApprovalDomain.Status;
import com.acquirex.approval.service.ApprovalService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/approvals")
public class ApprovalController {
private static final Logger log = LoggerFactory.getLogger(ApprovalController.class);
private final ApprovalService approvalService;
public ApprovalController(ApprovalService approvalService) {
this.approvalService = approvalService;
}
@PostMapping
public ResponseEntity<Response> requestApproval(@Valid @RequestBody Request request) {
log.info("매입 승인 API 호출: {}", request.getTransactionId());
Response response = approvalService.processApproval(request);
return response.getStatus() == Status.APPROVED
? ResponseEntity.ok(response)
: ResponseEntity.badRequest().body(response);
}
@DeleteMapping("/{approvalId}")
public ResponseEntity<Response> cancelApproval(@PathVariable String approvalId) {
log.info("매입 취소 API 호출: {}", approvalId);
return ResponseEntity.ok(approvalService.cancelApproval(approvalId));
}
@GetMapping("/{approvalId}")
public ResponseEntity<Response> getApprovalStatus(@PathVariable String approvalId) {
log.info("승인 상태 조회: {}", approvalId);
return ResponseEntity.ok(approvalService.cancelApproval(approvalId));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleException(Exception ex, HttpServletRequest request) {
log.error("서버 오류: {}", ex.getMessage(), ex);
return ResponseEntity.internalServerError().body(
java.util.Map.of("error", ex.getMessage(), "path", request.getRequestURI()));
}
}

View file

@ -0,0 +1,76 @@
package com.acquirex.approval.domain;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
/** 카드 매입 승인 도메인 - Jakarta EE 9+ */
public class ApprovalDomain {
public enum Status { PENDING, APPROVED, REJECTED, CANCELLED }
public static class Request {
@NotBlank(message = "거래 ID는 필수입니다")
private String transactionId;
@NotBlank(message = "가맹점 ID는 필수입니다")
private String merchantId;
@NotNull @Positive private BigDecimal amount;
@NotBlank private String cardNumber;
private LocalDateTime requestTime = LocalDateTime.now();
public Request() {}
public Request(String transactionId, String merchantId, BigDecimal amount, String cardNumber) {
this.transactionId = transactionId; this.merchantId = merchantId;
this.amount = amount; this.cardNumber = cardNumber;
}
public String getTransactionId() { return transactionId; }
public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
public String getMerchantId() { return merchantId; }
public void setMerchantId(String merchantId) { this.merchantId = merchantId; }
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getCardNumber() { return cardNumber; }
public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; }
public LocalDateTime getRequestTime() { return requestTime; }
}
public static class Response {
private String approvalId;
private String transactionId;
private Status status;
private String message;
private LocalDateTime approvalTime = LocalDateTime.now();
public Response() {}
public Response(String approvalId, String transactionId, Status status, String message) {
this.approvalId = approvalId; this.transactionId = transactionId;
this.status = status; this.message = message;
}
public static Response success(String approvalId, String transactionId) {
return new Response(approvalId, transactionId, Status.APPROVED, "매입 승인 완료");
}
public static Response rejected(String transactionId, String reason) {
return new Response(null, transactionId, Status.REJECTED, reason);
}
public static Response cancelled(String approvalId) {
return new Response(approvalId, null, Status.CANCELLED, "매입 취소 완료");
}
public String getApprovalId() { return approvalId; }
public void setApprovalId(String approvalId) { this.approvalId = approvalId; }
public String getTransactionId() { return transactionId; }
public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
public Status getStatus() { return status; }
public void setStatus(Status status) { this.status = status; }
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
public LocalDateTime getApprovalTime() { return approvalTime; }
public void setApprovalTime(LocalDateTime approvalTime) { this.approvalTime = approvalTime; }
}
public static String generateApprovalId() {
return "APR-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
}
}

View file

@ -0,0 +1,43 @@
package com.acquirex.approval.service;
import com.acquirex.approval.domain.ApprovalDomain;
import com.acquirex.approval.domain.ApprovalDomain.Request;
import com.acquirex.approval.domain.ApprovalDomain.Response;
import com.acquirex.approval.domain.ApprovalDomain.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class ApprovalService {
private static final Logger log = LoggerFactory.getLogger(ApprovalService.class);
public Response processApproval(Request request) {
log.info("매입 승인 요청: transactionId={}, merchantId={}, amount={}",
request.getTransactionId(), request.getMerchantId(), request.getAmount());
if (!validate(request)) {
return Response.rejected(request.getTransactionId(), "유효성 검증 실패");
}
String approvalId = ApprovalDomain.generateApprovalId();
Response response = Response.success(approvalId, request.getTransactionId());
log.info("매입 승인 완료: approvalId={}, status={}", approvalId, response.getStatus());
return response;
}
public Response cancelApproval(String approvalId) {
log.info("매입 승인 취소: approvalId={}", approvalId);
if (approvalId == null || approvalId.isBlank()) {
return Response.rejected(null, "승인 ID가 유효하지 않습니다");
}
return Response.cancelled(approvalId);
}
private boolean validate(Request request) {
if (request == null) return false;
if (request.getAmount() == null || request.getAmount().signum() <= 0) return false;
if (request.getCardNumber() == null || request.getCardNumber().length() < 13) return false;
return true;
}
}

View file

@ -0,0 +1,11 @@
spring.application.name=proj-acquirex
server.port=8080
spring.datasource.url=jdbc:h2:mem:acquirex;DB_CLOSE_DELAY=-1
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
logging.level.com.acquirex=DEBUG

View file

@ -0,0 +1,63 @@
package com.acquirex.approval;
import com.acquirex.approval.domain.ApprovalDomain;
import com.acquirex.approval.domain.ApprovalDomain.Request;
import com.acquirex.approval.domain.ApprovalDomain.Response;
import com.acquirex.approval.domain.ApprovalDomain.Status;
import com.acquirex.approval.service.ApprovalService;
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.*;
class ApprovalServiceTest {
private ApprovalService service;
@BeforeEach
void setUp() { service = new ApprovalService(); }
@Test
@DisplayName("유효한 요청으로 매입 승인 성공")
void processApproval_ValidRequest_Success() {
Request req = new Request("TXN-001", "MERCHANT-001", new BigDecimal("50000"), "1234567890123456");
Response res = service.processApproval(req);
assertEquals(Status.APPROVED, res.getStatus());
assertNotNull(res.getApprovalId());
assertTrue(res.getApprovalId().startsWith("APR-"));
}
@Test
@DisplayName("유효하지 않은 금액으로 매입 승인 거절")
void processApproval_InvalidAmount_Rejected() {
Request req = new Request("TXN-002", "MERCHANT-001", new BigDecimal("-1000"), "1234567890123456");
assertEquals(Status.REJECTED, service.processApproval(req).getStatus());
}
@Test
@DisplayName("유효하지 않은 카드번호로 매입 승인 거절")
void processApproval_InvalidCardNumber_Rejected() {
Request req = new Request("TXN-003", "MERCHANT-001", new BigDecimal("50000"), "12345");
assertEquals(Status.REJECTED, service.processApproval(req).getStatus());
}
@Test
@DisplayName("null 요청으로 매입 승인 거절")
void processApproval_NullRequest_Rejected() {
assertEquals(Status.REJECTED, service.processApproval(null).getStatus());
}
@Test
@DisplayName("유효한 승인 ID로 취소 성공")
void cancelApproval_ValidApprovalId_Success() {
Response res = service.cancelApproval("APR-12345678");
assertEquals(Status.CANCELLED, res.getStatus());
}
@Test
@DisplayName("유효하지 않은 승인 ID로 취소 거절")
void cancelApproval_InvalidApprovalId_Rejected() {
assertEquals(Status.REJECTED, service.cancelApproval("").getStatus());
assertEquals(Status.REJECTED, service.cancelApproval(null).getStatus());
}
}

View file

@ -0,0 +1,4 @@
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
logging.level.com.acquirex=DEBUG