Compare commits
8 commits
main
...
forge/ACX-
| Author | SHA1 | Date | |
|---|---|---|---|
| c28de6ef7e | |||
| 9f883274d0 | |||
| 8393aac0dd | |||
| 2cec4b0a16 | |||
| ad8fb595d0 | |||
| 6221af0ab6 | |||
| f8b4402cc7 | |||
| 4d18481507 |
8 changed files with 328 additions and 0 deletions
69
proj-acquirex/pom.xml
Normal file
69
proj-acquirex/pom.xml
Normal 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>
|
||||
|
|
@ -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,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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
11
proj-acquirex/src/main/resources/application.properties
Normal file
11
proj-acquirex/src/main/resources/application.properties
Normal 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
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue