diff --git a/.forge/codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-CARD-001-attempt-1-run-4a55ce2ead99.md b/.forge/codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-CARD-001-attempt-1-run-4a55ce2ead99.md deleted file mode 100644 index a205890..0000000 --- a/.forge/codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-CARD-001-attempt-1-run-4a55ce2ead99.md +++ /dev/null @@ -1,3 +0,0 @@ -# codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-CARD-001-attempt-1-run-4a55ce2ead99 - -Forge 이슈 작업 브랜치 `forge/codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-CARD-001-attempt-1-run-4a55ce2ead99`. diff --git a/card-settlement-migration/INVARIANTS.md b/card-settlement-migration/INVARIANTS.md deleted file mode 100644 index 74f87ed..0000000 --- a/card-settlement-migration/INVARIANTS.md +++ /dev/null @@ -1,94 +0,0 @@ -# 카드 정산 불변식 표 (Card Settlement Invariants) - -**범위**: card-settlement-migration - -## 1. 브랜드 정산 (Brand Settlement) 불변식 - -| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 | -|---|---|---|---|---| -| BRAND_SETTLEMENT_NON_NEGATIVE | 브랜드 정산금은 0 이상 | settlementAmount >= 0 | SettlementCalculator | 음수 시 0 보정 | -| BRAND_SETTLEMENT_FORMULA | 정산금 = 승인금액 - 취소금액 - 수수료 | settlementAmount = approved - cancelled - commission | SettlementCalculator | 계산 순서 준수 | -| BRAND_COMMISSION_FORMULA | 수수료 = 승인금액 * 정산요율 | commission = approvedAmount * rate | SettlementCalculator | 2자리 반올림 | -| BRAND_COMMISSION_RATE_DEFAULT | 미지정 시 기본값 0.02(2%) | rate = 0.02 when null | SettlementCalculator | DEFAULT_COMMISSION_RATE | - -## 2. IRD (Interchange Reimbursement Fee) 불변식 - -| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 | -|---|---|---|---|---| -| IRD_NON_NEGATIVE | IRD는 0 이상 | ird >= 0 | SettlementCalculator | 음수 시 0 보정 | -| IRD_FORMULA | IRD = 승인금액 * IRD율 | ird = approvedAmount * rate | SettlementCalculator | 2자리 반올림 | -| IRD_RATE_DEFAULT | 미지정 시 기본값 0.015(1.5%) | rate = 0.015 when null | SettlementCalculator | DEFAULT_IRD_RATE | - -## 3. 발급사 수수료 (Issuer Fee) 불변식 - -| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 | -|---|---|---|---|---| -| ISSUER_FEE_NON_NEGATIVE | 발급사 수수료는 0 이상 | issuerFee >= 0 | SettlementCalculator | 음수 시 0 보정 | -| ISSUER_FEE_FORMULA | 발급사 수수료 = IRD * 발급사 수수료율 | issuerFee = ird * rate | SettlementCalculator | 2자리 반올림 | -| ISSUER_FEE_RATE_DEFAULT | 미지정 시 기본값 0.005(0.5%) | rate = 0.005 when null | SettlementCalculator | DEFAULT_ISSUER_FEE_RATE | -| ISSUER_SETTLEMENT_NON_NEGATIVE | 발급사 정산금은 0 이상 | issuerSettlement >= 0 | SettlementCalculator | 음수 시 0 보정 | -| ISSUER_SETTLEMENT_FORMULA | 발급사 정산금 = IRD - 발급사 수수료 | issuerSettlement = ird - fee | SettlementCalculator | 순서 준수 | - -## 4. 공통 금액 검증 불변식 - -| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 | -|---|---|---|---|---| -| AMOUNT_NOT_NULL | 금액은 null 불가 | amount != null | SettlementCalculator | IllegalArgumentException | -| AMOUNT_NON_NEGATIVE | 금액은 0 이상 | amount >= 0 | SettlementCalculator | IllegalArgumentException | -| AMOUNT_MAX_LIMIT | 최대 한도 이하여야 함 | amount <= 999999999999.99 | SettlementCalculator | IllegalArgumentException | - -## 5. 금액 정밀도 불변식 - -| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 | -|---|---|---|---|---| -| AMOUNT_SCALE | 소수점 2자리 표현 | scale(amount) = 2 | SettlementCalculator | setScale(2, HALF_UP) | -| ROUNDING_MODE | HALF_UP 반올림 | RoundingMode.HALF_UP | SettlementCalculator | 반올림 정책 준수 | - -## 6. 계산 흐름도 - -``` -승인요청 - │ - ▼ -┌─────────────────────────────┐ -│ SettlementCalculator │ -│ ───────────────────────── │ -│ [브랜드 정산 계산] │ -│ 1. 승인금액 검증 (NOT_NULL, │ -│ NON_NEGATIVE, MAX_LIMIT)│ -│ 2. 수수료 = 승인금액 * 요율 │ -│ 3. IRD = 승인금액 * IRD율 │ -│ 4. 정산금 = 승인 - 취소 - 수수료│ -│ 5. 음수 보정 (>= 0) │ -│ ───────────────────────── │ -│ [발급사 수수료 계산] │ -│ 6. 발급사수수료 = IRD * 요율│ -│ 7. 발급사정산 = IRD - 수수료│ -│ 8. 음수 보정 (>= 0) │ -└─────────────────────────────┘ - │ - ▼ -정산 결과 출력 -``` - -## 7. Spring 전환 시 체크리스트 - -- [ ] SettlementCalculator Bean 등록 및 의존성 주입 확인 -- [ ] 정산요율 기본값 (@Value 또는 @ConfigurationProperties) 설정 -- [ ] IRD율 기본값 설정 -- [ ] 발급사 수수료율 기본값 설정 -- [ ] 금액 검증 (@Valid, @DecimalMin, @DecimalMax) 어노테이션 적용 -- [ ] 반올림 정책 (HALF_UP) Bean 또는 Util로 분리 -- [ ] 단위 테스트: 각 불변식별 테스트 케이스 구현 -- [ ] 통합 테스트: 계산 흐름 전체 검증 - -## 8. 불변식 요약 - -| 카테고리 | 불변식 수 | -|---|---| -| 브랜드 정산 | 4 | -| IRD | 3 | -| 발급사 수수료 | 5 | -| 공통 금액 검증 | 3 | -| 금액 정밀도 | 2 | -| **총계** | **17** | diff --git a/card-settlement-migration/README.md b/card-settlement-migration/README.md deleted file mode 100644 index 8c13c0d..0000000 --- a/card-settlement-migration/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Card Settlement Migration Module - -카드 정산 마이그레이션 모듈 - -## 개요 - -card-settlement-migration은 카드 정산 업무 규칙을 Spring 전환 시 보존하기 위한 불변식 정의 및 계산 로직을 포함합니다. - -## 주요 기능 - -- **브랜드 정산 계산**: 가맹점 정산금 = 승인금액 - 취소금액 - 수수료 -- **IRD 계산**: Interchange Reimbursement Fee = 승인금액 * IRD율 -- **발급사 수수료 계산**: 발급사 수수료 = IRD * 발급사 수수료율 - -## 불변식 (Invariants) - -총 17개의 불변식이 정의되어 있습니다. 자세한 내용은 `INVARIANTS.md`를 참조하세요. - -### 카테고리별 불변식 - -| 카테고리 | 불변식 수 | -|---|---| -| 브랜드 정산 | 4 | -| IRD | 3 | -| 발급사 수수료 | 5 | -| 공통 금액 검증 | 3 | -| 금액 정밀도 | 2 | - -## 빌드 및 테스트 - -```bash -mvn clean test -``` - -## Spring 전환 체크리스트 - -- [ ] SettlementCalculator Bean 등록 -- [ ] 정산요율, IRD율, 발급사 수수료율 기본값 설정 -- [ ] 금액 검증 어노테이션 적용 -- [ ] 반올림 정책 Bean 또는 Util 분리 -- [ ] 단위 테스트 및 통합 테스트 실행 diff --git a/card-settlement-migration/pom.xml b/card-settlement-migration/pom.xml deleted file mode 100644 index 693578f..0000000 --- a/card-settlement-migration/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - 4.0.0 - - - com.bais - codex-bais-final3-20260713-192718 - 1.0.0-SNAPSHOT - - - card-settlement-migration - jar - Card Settlement Migration - 카드 정산 마이그레이션 모듈 - 브랜드 정산, IRD, 발급사 수수료 계산 - - - 17 - 17 - 17 - UTF-8 - 5.10.2 - - - - - org.junit.jupiter - junit-jupiter-api - ${junit.version} - test - - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.5 - - - - diff --git a/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementCalculator.java b/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementCalculator.java deleted file mode 100644 index 585cfd7..0000000 --- a/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementCalculator.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.bais.card.settlement; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.Objects; - -/** - * Settlement Calculator - * - * 카드 정산 계산 로직 (Brand Settlement + IRD + Issuer Fee) - * - * INVARIANTS: - * - BRAND_SETTLEMENT_NON_NEGATIVE: 정산금은 0 이상 - * - BRAND_SETTLEMENT_FORMULA: 정산금 = 승인금액 - 취소금액 - 수수료 - * - BRAND_COMMISSION_FORMULA: 수수료 = 승인금액 * 정산요율 - * - BRAND_COMMISSION_RATE_DEFAULT: 미지정 시 0.02 - * - IRD_NON_NEGATIVE: IRD는 0 이상 - * - IRD_FORMULA: IRD = 승인금액 * IRD율 - * - IRD_RATE_DEFAULT: 미지정 시 0.015 - * - ISSUER_FEE_NON_NEGATIVE: 발급사 수수료는 0 이상 - * - ISSUER_FEE_FORMULA: 발급사 수수료 = IRD * 발급사 수수료율 - * - ISSUER_FEE_RATE_DEFAULT: 미지정 시 0.005 - * - ISSUER_SETTLEMENT_NON_NEGATIVE: 발급사 정산금은 0 이상 - * - ISSUER_SETTLEMENT_FORMULA: 발급사 정산금 = IRD - 발급사 수수료 - * - AMOUNT_NOT_NULL: 금액은 null 불가 - * - AMOUNT_NON_NEGATIVE: 금액은 0 이상 - * - AMOUNT_MAX_LIMIT: 최대 한도 이하여야 함 - * - AMOUNT_SCALE: 소수점 2자리 표현 - * - ROUNDING_MODE: HALF_UP 반올림 - */ -public class SettlementCalculator { - - private static final BigDecimal DEFAULT_COMMISSION_RATE = new BigDecimal("0.02"); - private static final BigDecimal DEFAULT_IRD_RATE = new BigDecimal("0.015"); - private static final BigDecimal DEFAULT_ISSUER_FEE_RATE = new BigDecimal("0.005"); - private static final BigDecimal MIN_AMOUNT = BigDecimal.ZERO; - private static final BigDecimal MAX_AMOUNT = new BigDecimal("999999999999.99"); - - /** - * 브랜드 정산금 계산 - * INVARIANT: 정산금 = 승인금액 - 취소금액 - 수수료 - */ - public BrandSettlementResult calculateBrandSettlement(BigDecimal approvedAmount, - BigDecimal cancelledAmount, - BigDecimal commissionRate) { - validateAmount(approvedAmount); - validateAmount(cancelledAmount); - - BigDecimal effectiveCommissionRate = - commissionRate != null ? commissionRate : DEFAULT_COMMISSION_RATE; - - BigDecimal commission = approvedAmount - .multiply(effectiveCommissionRate) - .setScale(2, java.math.RoundingMode.HALF_UP); - - BigDecimal settlementAmount = approvedAmount - .subtract(cancelledAmount) - .subtract(commission); - - if (settlementAmount.compareTo(MIN_AMOUNT) < 0) { - settlementAmount = MIN_AMOUNT; - } - - return new BrandSettlementResult( - settlementAmount, commission, approvedAmount, cancelledAmount, - effectiveCommissionRate, LocalDateTime.now() - ); - } - - /** - * IRD 계산 - * INVARIANT: IRD = 승인금액 * IRD율 - */ - public BigDecimal calculateIRD(BigDecimal approvedAmount, BigDecimal irdRate) { - validateAmount(approvedAmount); - - BigDecimal effectiveIrdRate = irdRate != null ? irdRate : DEFAULT_IRD_RATE; - - BigDecimal ird = approvedAmount - .multiply(effectiveIrdRate) - .setScale(2, java.math.RoundingMode.HALF_UP); - - if (ird.compareTo(MIN_AMOUNT) < 0) { - return MIN_AMOUNT; - } - - return ird; - } - - /** - * 발급사 수수료 계산 - * INVARIANT: 발급사 수수료 = IRD * 발급사 수수료율 - */ - public BigDecimal calculateIssuerFee(BigDecimal ird, BigDecimal issuerFeeRate) { - validateAmount(ird); - - BigDecimal effectiveRate = - issuerFeeRate != null ? issuerFeeRate : DEFAULT_ISSUER_FEE_RATE; - - BigDecimal issuerFee = ird - .multiply(effectiveRate) - .setScale(2, java.math.RoundingMode.HALF_UP); - - if (issuerFee.compareTo(MIN_AMOUNT) < 0) { - return MIN_AMOUNT; - } - - return issuerFee; - } - - /** - * 발급사 정산금 계산 - * INVARIANT: 발급사 정산금 = IRD - 발급사 수수료 - */ - public IssuerSettlementResult calculateIssuerSettlement(BigDecimal ird, - BigDecimal issuerFeeRate) { - validateAmount(ird); - - BigDecimal effectiveRate = - issuerFeeRate != null ? issuerFeeRate : DEFAULT_ISSUER_FEE_RATE; - - BigDecimal issuerFee = ird - .multiply(effectiveRate) - .setScale(2, java.math.RoundingMode.HALF_UP); - - BigDecimal issuerSettlement = ird.subtract(issuerFee); - - if (issuerSettlement.compareTo(MIN_AMOUNT) < 0) { - issuerSettlement = MIN_AMOUNT; - } - - return new IssuerSettlementResult( - issuerSettlement, issuerFee, ird, effectiveRate, LocalDateTime.now() - ); - } - - /** - * 전체 정산 계산 (Brand + Issuer) - */ - public FullSettlementResult calculateFullSettlement(BigDecimal approvedAmount, - BigDecimal cancelledAmount, - BigDecimal commissionRate, - BigDecimal irdRate, - BigDecimal issuerFeeRate) { - BrandSettlementResult brandResult = calculateBrandSettlement( - approvedAmount, cancelledAmount, commissionRate); - - BigDecimal ird = calculateIRD(approvedAmount, irdRate); - IssuerSettlementResult issuerResult = calculateIssuerSettlement(ird, issuerFeeRate); - - return new FullSettlementResult( - brandResult.getSettlementAmount(), - brandResult.getCommission(), - ird, - issuerResult.getIssuerFee(), - issuerResult.getIssuerSettlement(), - LocalDateTime.now() - ); - } - - private void validateAmount(BigDecimal amount) { - Objects.requireNonNull(amount, "Amount must not be null"); - if (amount.compareTo(MIN_AMOUNT) < 0) { - throw new IllegalArgumentException("Amount must be non-negative"); - } - if (amount.compareTo(MAX_AMOUNT) > 0) { - throw new IllegalArgumentException("Amount exceeds maximum limit"); - } - } - - public static class BrandSettlementResult { - private final BigDecimal settlementAmount; - private final BigDecimal commission; - private final BigDecimal approvedAmount; - private final BigDecimal cancelledAmount; - private final BigDecimal commissionRate; - private final LocalDateTime calculatedAt; - - public BrandSettlementResult(BigDecimal settlementAmount, BigDecimal commission, - BigDecimal approvedAmount, BigDecimal cancelledAmount, - BigDecimal commissionRate, LocalDateTime calculatedAt) { - this.settlementAmount = settlementAmount; - this.commission = commission; - this.approvedAmount = approvedAmount; - this.cancelledAmount = cancelledAmount; - this.commissionRate = commissionRate; - this.calculatedAt = calculatedAt; - } - - public BigDecimal getSettlementAmount() { return settlementAmount; } - public BigDecimal getCommission() { return commission; } - public BigDecimal getApprovedAmount() { return approvedAmount; } - public BigDecimal getCancelledAmount() { return cancelledAmount; } - public BigDecimal getCommissionRate() { return commissionRate; } - public LocalDateTime getCalculatedAt() { return calculatedAt; } - } - - public static class IssuerSettlementResult { - private final BigDecimal issuerSettlement; - private final BigDecimal issuerFee; - private final BigDecimal ird; - private final BigDecimal issuerFeeRate; - private final LocalDateTime calculatedAt; - - public IssuerSettlementResult(BigDecimal issuerSettlement, BigDecimal issuerFee, - BigDecimal ird, BigDecimal issuerFeeRate, - LocalDateTime calculatedAt) { - this.issuerSettlement = issuerSettlement; - this.issuerFee = issuerFee; - this.ird = ird; - this.issuerFeeRate = issuerFeeRate; - this.calculatedAt = calculatedAt; - } - - public BigDecimal getIssuerSettlement() { return issuerSettlement; } - public BigDecimal getIssuerFee() { return issuerFee; } - public BigDecimal getIrd() { return ird; } - public BigDecimal getIssuerFeeRate() { return issuerFeeRate; } - public LocalDateTime getCalculatedAt() { return calculatedAt; } - } - - public static class FullSettlementResult { - private final BigDecimal brandSettlement; - private final BigDecimal brandCommission; - private final BigDecimal ird; - private final BigDecimal issuerFee; - private final BigDecimal issuerSettlement; - private final LocalDateTime calculatedAt; - - public FullSettlementResult(BigDecimal brandSettlement, BigDecimal brandCommission, - BigDecimal ird, BigDecimal issuerFee, - BigDecimal issuerSettlement, LocalDateTime calculatedAt) { - this.brandSettlement = brandSettlement; - this.brandCommission = brandCommission; - this.ird = ird; - this.issuerFee = issuerFee; - this.issuerSettlement = issuerSettlement; - this.calculatedAt = calculatedAt; - } - - public BigDecimal getBrandSettlement() { return brandSettlement; } - public BigDecimal getBrandCommission() { return brandCommission; } - public BigDecimal getIrd() { return ird; } - public BigDecimal getIssuerFee() { return issuerFee; } - public BigDecimal getIssuerSettlement() { return issuerSettlement; } - public LocalDateTime getCalculatedAt() { return calculatedAt; } - } -} diff --git a/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementInvariant.java b/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementInvariant.java deleted file mode 100644 index 1f835b6..0000000 --- a/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementInvariant.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.bais.card.settlement; - -/** - * Settlement Invariants Specification - * - * 카드 정산 업무 규칙과 Spring 전환 시 보존해야 할 불변식 표 - * 범위: card-settlement-migration - */ -public class SettlementInvariant { - - /** 1. 브랜드 정산 불변식 */ - public static final Invariant BRAND_SETTLEMENT_NON_NEGATIVE = - new Invariant("BRAND_SETTLEMENT_NON_NEGATIVE", "브랜드 정산금은 0 이상", "settlementAmount >= 0", "SettlementCalculator", "음수 시 0 보정"); - public static final Invariant BRAND_SETTLEMENT_FORMULA = - new Invariant("BRAND_SETTLEMENT_FORMULA", "정산금 = 승인금액 - 취소금액 - 수수료", "settlementAmount = approved - cancelled - commission", "SettlementCalculator", "계산 순서 준수"); - public static final Invariant BRAND_COMMISSION_FORMULA = - new Invariant("BRAND_COMMISSION_FORMULA", "수수료 = 승인금액 * 정산요율", "commission = approvedAmount * rate", "SettlementCalculator", "2자리 반올림"); - public static final Invariant BRAND_COMMISSION_RATE_DEFAULT = - new Invariant("BRAND_COMMISSION_RATE_DEFAULT", "미지정 시 기본값 0.02(2%)", "rate = 0.02 when null", "SettlementCalculator", "DEFAULT_COMMISSION_RATE"); - - /** 2. IRD 불변식 */ - public static final Invariant IRD_NON_NEGATIVE = - new Invariant("IRD_NON_NEGATIVE", "IRD는 0 이상", "ird >= 0", "SettlementCalculator", "음수 시 0 보정"); - public static final Invariant IRD_FORMULA = - new Invariant("IRD_FORMULA", "IRD = 승인금액 * IRD율", "ird = approvedAmount * rate", "SettlementCalculator", "2자리 반올림"); - public static final Invariant IRD_RATE_DEFAULT = - new Invariant("IRD_RATE_DEFAULT", "미지정 시 기본값 0.015(1.5%)", "rate = 0.015 when null", "SettlementCalculator", "DEFAULT_IRD_RATE"); - - /** 3. 발급사 수수료 불변식 */ - public static final Invariant ISSUER_FEE_NON_NEGATIVE = - new Invariant("ISSUER_FEE_NON_NEGATIVE", "발급사 수수료는 0 이상", "issuerFee >= 0", "SettlementCalculator", "음수 시 0 보정"); - public static final Invariant ISSUER_FEE_FORMULA = - new Invariant("ISSUER_FEE_FORMULA", "발급사 수수료 = IRD * 발급사 수수료율", "issuerFee = ird * rate", "SettlementCalculator", "2자리 반올림"); - public static final Invariant ISSUER_FEE_RATE_DEFAULT = - new Invariant("ISSUER_FEE_RATE_DEFAULT", "미지정 시 기본값 0.005(0.5%)", "rate = 0.005 when null", "SettlementCalculator", "DEFAULT_ISSUER_FEE_RATE"); - public static final Invariant ISSUER_SETTLEMENT_NON_NEGATIVE = - new Invariant("ISSUER_SETTLEMENT_NON_NEGATIVE", "발급사 정산금은 0 이상", "issuerSettlement >= 0", "SettlementCalculator", "음수 시 0 보정"); - public static final Invariant ISSUER_SETTLEMENT_FORMULA = - new Invariant("ISSUER_SETTLEMENT_FORMULA", "발급사 정산금 = IRD - 발급사 수수료", "issuerSettlement = ird - fee", "SettlementCalculator", "순서 준수"); - - /** 4. 공통 금액 검증 불변식 */ - public static final Invariant AMOUNT_NOT_NULL = - new Invariant("AMOUNT_NOT_NULL", "금액은 null 불가", "amount != null", "SettlementCalculator", "IllegalArgumentException"); - public static final Invariant AMOUNT_NON_NEGATIVE = - new Invariant("AMOUNT_NON_NEGATIVE", "금액은 0 이상", "amount >= 0", "SettlementCalculator", "IllegalArgumentException"); - public static final Invariant AMOUNT_MAX_LIMIT = - new Invariant("AMOUNT_MAX_LIMIT", "최대 한도 이하여야 함", "amount <= 999999999999.99", "SettlementCalculator", "IllegalArgumentException"); - - /** 5. 금액 정밀도 불변식 */ - public static final Invariant AMOUNT_SCALE = - new Invariant("AMOUNT_SCALE", "소수점 2자리 표현", "scale(amount) = 2", "SettlementCalculator", "setScale(2, HALF_UP)"); - public static final Invariant ROUNDING_MODE = - new Invariant("ROUNDING_MODE", "HALF_UP 반올림", "RoundingMode.HALF_UP", "SettlementCalculator", "반올림 정책 준수"); - - public static Invariant[] getAllInvariants() { - return new Invariant[] { - BRAND_SETTLEMENT_NON_NEGATIVE, BRAND_SETTLEMENT_FORMULA, BRAND_COMMISSION_FORMULA, BRAND_COMMISSION_RATE_DEFAULT, - IRD_NON_NEGATIVE, IRD_FORMULA, IRD_RATE_DEFAULT, - ISSUER_FEE_NON_NEGATIVE, ISSUER_FEE_FORMULA, ISSUER_FEE_RATE_DEFAULT, ISSUER_SETTLEMENT_NON_NEGATIVE, ISSUER_SETTLEMENT_FORMULA, - AMOUNT_NOT_NULL, AMOUNT_NON_NEGATIVE, AMOUNT_MAX_LIMIT, - AMOUNT_SCALE, ROUNDING_MODE - }; - } - - public static class Invariant { - private final String id; - private final String description; - private final String formula; - private final String affectedClass; - private final String preservationNote; - - public Invariant(String id, String description, String formula, String affectedClass, String preservationNote) { - this.id = id; - this.description = description; - this.formula = formula; - this.affectedClass = affectedClass; - this.preservationNote = preservationNote; - } - - public String getId() { return id; } - public String getDescription() { return description; } - public String getFormula() { return formula; } - public String getAffectedClass() { return affectedClass; } - public String getPreservationNote() { return preservationNote; } - } -} diff --git a/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementInvariantTable.java b/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementInvariantTable.java deleted file mode 100644 index 3da7c20..0000000 --- a/card-settlement-migration/src/main/java/com/bais/card/settlement/SettlementInvariantTable.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.bais.card.settlement; - -/** - * Settlement Invariants Table Generator - * Markdown 형식의 불변식 표 생성 - */ -public class SettlementInvariantTable { - - public static String generateMarkdownTable() { - StringBuilder sb = new StringBuilder(); - - sb.append("# 카드 정산 불변식 표 (Card Settlement Invariants)\n\n"); - sb.append("**범위**: card-settlement-migration\n\n"); - - sb.append("## 1. 브랜드 정산 불변식\n\n"); - sb.append("| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 |\n"); - sb.append("|---|---|---|---|---|\n"); - for (SettlementInvariant.Invariant inv : SettlementInvariant.getAllInvariants()) { - if (inv.getId().startsWith("BRAND_SETTLEMENT") || inv.getId().startsWith("BRAND_COMMISSION")) { - sb.append(String.format("| %s | %s | %s | %s | %s |\n", - inv.getId(), inv.getDescription(), inv.getFormula(), - inv.getAffectedClass(), inv.getPreservationNote())); - } - } - - sb.append("\n## 2. IRD 불변식\n\n"); - sb.append("| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 |\n"); - sb.append("|---|---|---|---|---|\n"); - for (SettlementInvariant.Invariant inv : SettlementInvariant.getAllInvariants()) { - if (inv.getId().startsWith("IRD")) { - sb.append(String.format("| %s | %s | %s | %s | %s |\n", - inv.getId(), inv.getDescription(), inv.getFormula(), - inv.getAffectedClass(), inv.getPreservationNote())); - } - } - - sb.append("\n## 3. 발급사 수수료 불변식\n\n"); - sb.append("| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 |\n"); - sb.append("|---|---|---|---|---|\n"); - for (SettlementInvariant.Invariant inv : SettlementInvariant.getAllInvariants()) { - if (inv.getId().startsWith("ISSUER")) { - sb.append(String.format("| %s | %s | %s | %s | %s |\n", - inv.getId(), inv.getDescription(), inv.getFormula(), - inv.getAffectedClass(), inv.getPreservationNote())); - } - } - - sb.append("\n## 4. 공통 금액 검증 불변식\n\n"); - sb.append("| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 |\n"); - sb.append("|---|---|---|---|---|\n"); - for (SettlementInvariant.Invariant inv : SettlementInvariant.getAllInvariants()) { - if (inv.getId().startsWith("AMOUNT")) { - sb.append(String.format("| %s | %s | %s | %s | %s |\n", - inv.getId(), inv.getDescription(), inv.getFormula(), - inv.getAffectedClass(), inv.getPreservationNote())); - } - } - - sb.append("\n## 5. 금액 정밀도 불변식\n\n"); - sb.append("| ID | 설명 | 수식 | 영향 클래스 | 보존 주의사항 |\n"); - sb.append("|---|---|---|---|---|\n"); - for (SettlementInvariant.Invariant inv : SettlementInvariant.getAllInvariants()) { - if (inv.getId().equals("AMOUNT_SCALE") || inv.getId().equals("ROUNDING_MODE")) { - sb.append(String.format("| %s | %s | %s | %s | %s |\n", - inv.getId(), inv.getDescription(), inv.getFormula(), - inv.getAffectedClass(), inv.getPreservationNote())); - } - } - - return sb.toString(); - } - - public static void main(String[] args) { - System.out.println(generateMarkdownTable()); - } -} diff --git a/card-settlement-migration/src/test/java/com/bais/card/settlement/SettlementCalculatorTest.java b/card-settlement-migration/src/test/java/com/bais/card/settlement/SettlementCalculatorTest.java deleted file mode 100644 index 75b58a6..0000000 --- a/card-settlement-migration/src/test/java/com/bais/card/settlement/SettlementCalculatorTest.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.bais.card.settlement; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import java.math.BigDecimal; -import static org.junit.jupiter.api.Assertions.*; - -/** - * SettlementCalculator 단위 테스트 - * 불변식 검증: BRAND_SETTLEMENT_*, IRD_*, ISSUER_FEE_*, AMOUNT_*, ROUNDING_MODE - */ -@DisplayName("SettlementCalculator 불변식 테스트") -class SettlementCalculatorTest { - - private SettlementCalculator calculator; - - @BeforeEach - void setUp() { - calculator = new SettlementCalculator(); - } - - @Nested - @DisplayName("브랜드 정산 불변식 테스트") - class BrandSettlementTests { - - @Test - @DisplayName("BRAND_SETTLEMENT_FORMULA: 정산금 = 승인금액 - 취소금액 - 수수료") - void testSettlementFormula() { - BigDecimal approved = new BigDecimal("100000"); - BigDecimal cancelled = new BigDecimal("10000"); - BigDecimal rate = new BigDecimal("0.02"); - - SettlementCalculator.BrandSettlementResult result = - calculator.calculateBrandSettlement(approved, cancelled, rate); - - assertEquals(new BigDecimal("2000.00"), result.getCommission()); - assertEquals(new BigDecimal("88000.00"), result.getSettlementAmount()); - } - - @Test - @DisplayName("BRAND_SETTLEMENT_NON_NEGATIVE: 정산금은 0 이상") - void testSettlementNonNegative() { - SettlementCalculator.BrandSettlementResult result = - calculator.calculateBrandSettlement( - new BigDecimal("10000"), new BigDecimal("20000"), new BigDecimal("0.02")); - assertEquals(BigDecimal.ZERO.setScale(2), result.getSettlementAmount()); - } - - @Test - @DisplayName("BRAND_COMMISSION_RATE_DEFAULT: 미지정 시 0.02") - void testDefaultCommissionRate() { - SettlementCalculator.BrandSettlementResult result = - calculator.calculateBrandSettlement( - new BigDecimal("100000"), BigDecimal.ZERO, null); - assertEquals(new BigDecimal("0.02"), result.getCommissionRate()); - } - } - - @Nested - @DisplayName("IRD 불변식 테스트") - class IrdTests { - - @Test - @DisplayName("IRD_FORMULA: IRD = 승인금액 * IRD율") - void testIrdFormula() { - BigDecimal ird = calculator.calculateIRD( - new BigDecimal("100000"), new BigDecimal("0.015")); - assertEquals(new BigDecimal("1500.00"), ird); - } - - @Test - @DisplayName("IRD_RATE_DEFAULT: 미지정 시 0.015") - void testDefaultIrdRate() { - BigDecimal ird = calculator.calculateIRD(new BigDecimal("100000"), null); - assertEquals(new BigDecimal("1500.00"), ird); - } - - @Test - @DisplayName("IRD_NON_NEGATIVE: IRD는 0 이상") - void testIrdNonNegative() { - BigDecimal ird = calculator.calculateIRD( - new BigDecimal("100000"), new BigDecimal("-0.015")); - assertTrue(ird.compareTo(BigDecimal.ZERO) >= 0); - } - } - - @Nested - @DisplayName("발급사 수수료 불변식 테스트") - class IssuerFeeTests { - - @Test - @DisplayName("ISSUER_FEE_FORMULA: 발급사 수수료 = IRD * 발급사 수수료율") - void testIssuerFeeFormula() { - BigDecimal fee = calculator.calculateIssuerFee( - new BigDecimal("1500.00"), new BigDecimal("0.005")); - assertEquals(new BigDecimal("7.50"), fee); - } - - @Test - @DisplayName("ISSUER_FEE_RATE_DEFAULT: 미지정 시 0.005") - void testDefaultIssuerFeeRate() { - BigDecimal fee = calculator.calculateIssuerFee(new BigDecimal("1500.00"), null); - assertEquals(new BigDecimal("7.50"), fee); - } - - @Test - @DisplayName("ISSUER_SETTLEMENT_FORMULA: 발급사 정산금 = IRD - 발급사 수수료") - void testIssuerSettlementFormula() { - SettlementCalculator.IssuerSettlementResult result = - calculator.calculateIssuerSettlement( - new BigDecimal("1500.00"), new BigDecimal("0.005")); - assertEquals(new BigDecimal("7.50"), result.getIssuerFee()); - assertEquals(new BigDecimal("1492.50"), result.getIssuerSettlement()); - } - } - - @Nested - @DisplayName("공통 금액 검증 불변식 테스트") - class AmountValidationTests { - - @Test - @DisplayName("AMOUNT_NOT_NULL: null 시 IllegalArgumentException") - void testNullAmount() { - assertThrows(IllegalArgumentException.class, () -> - calculator.calculateBrandSettlement(null, BigDecimal.ZERO, new BigDecimal("0.02"))); - } - - @Test - @DisplayName("AMOUNT_NON_NEGATIVE: 음수 시 IllegalArgumentException") - void testNegativeAmount() { - assertThrows(IllegalArgumentException.class, () -> - calculator.calculateBrandSettlement( - new BigDecimal("-100000"), BigDecimal.ZERO, new BigDecimal("0.02"))); - } - - @Test - @DisplayName("AMOUNT_MAX_LIMIT: 최대 한도 초과 시 IllegalArgumentException") - void testAmountExceedsMaxLimit() { - BigDecimal exceedsMax = new BigDecimal("999999999999.99").add(BigDecimal.ONE); - assertThrows(IllegalArgumentException.class, () -> - calculator.calculateBrandSettlement( - exceedsMax, BigDecimal.ZERO, new BigDecimal("0.02"))); - } - } - - @Nested - @DisplayName("금액 정밀도 불변식 테스트") - class PrecisionTests { - - @Test - @DisplayName("AMOUNT_SCALE: 소수점 2자리") - void testAmountScale() { - SettlementCalculator.BrandSettlementResult result = - calculator.calculateBrandSettlement( - new BigDecimal("100000"), BigDecimal.ZERO, new BigDecimal("0.023456")); - assertEquals(2, result.getCommission().scale()); - assertEquals(new BigDecimal("2345.60"), result.getCommission()); - } - - @Test - @DisplayName("ROUNDING_MODE: HALF_UP 반올림") - void testHalfUpRounding() { - SettlementCalculator.BrandSettlementResult result = - calculator.calculateBrandSettlement( - new BigDecimal("100000"), BigDecimal.ZERO, new BigDecimal("0.023455")); - assertEquals(new BigDecimal("2345.50"), result.getCommission()); - } - } - - @Nested - @DisplayName("전체 정산 흐름 테스트") - class FullSettlementTests { - - @Test - @DisplayName("전체 정산 계산 통합 테스트") - void testFullSettlement() { - SettlementCalculator.FullSettlementResult result = - calculator.calculateFullSettlement( - new BigDecimal("100000"), - new BigDecimal("10000"), - new BigDecimal("0.02"), - new BigDecimal("0.015"), - new BigDecimal("0.005")); - - assertNotNull(result.getBrandSettlement()); - assertNotNull(result.getIrd()); - assertNotNull(result.getIssuerSettlement()); - assertEquals(new BigDecimal("1500.00"), result.getIrd()); - } - } -} diff --git a/card-settlement-migration/src/test/java/com/bais/card/settlement/SettlementInvariantVerificationTest.java b/card-settlement-migration/src/test/java/com/bais/card/settlement/SettlementInvariantVerificationTest.java deleted file mode 100644 index 1aabfd4..0000000 --- a/card-settlement-migration/src/test/java/com/bais/card/settlement/SettlementInvariantVerificationTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.bais.card.settlement; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; - -/** - * 불변식 검증 테스트 - * SettlementInvariant에 정의된 모든 불변식이 Calculator에서 올바르게 구현되었는지 검증 - */ -@DisplayName("불변식 검증 테스트") -class SettlementInvariantVerificationTest { - - @Test - @DisplayName("모든 불변식이 정의되어 있어야 함") - void testAllInvariantsDefined() { - SettlementInvariant.Invariant[] invariants = SettlementInvariant.getAllInvariants(); - assertNotNull(invariants); - assertEquals(17, invariants.length, "총 17개의 불변식이 정의되어야 함"); - } - - @Test - @DisplayName("불변식 ID는 고유해야 함") - void testInvariantIdsAreUnique() { - long uniqueCount = Arrays.stream(SettlementInvariant.getAllInvariants()) - .map(SettlementInvariant.Invariant::getId) - .distinct() - .count(); - assertEquals(17, uniqueCount, "모든 불변식 ID는 고유해야 함"); - } - - @Test - @DisplayName("주요 불변식 존재 확인") - void testKeyInvariantsExist() { - Set ids = Arrays.stream(SettlementInvariant.getAllInvariants()) - .map(SettlementInvariant.Invariant::getId) - .collect(Collectors.toSet()); - - assertTrue(ids.contains("BRAND_SETTLEMENT_NON_NEGATIVE")); - assertTrue(ids.contains("BRAND_SETTLEMENT_FORMULA")); - assertTrue(ids.contains("BRAND_COMMISSION_FORMULA")); - assertTrue(ids.contains("BRAND_COMMISSION_RATE_DEFAULT")); - assertTrue(ids.contains("IRD_NON_NEGATIVE")); - assertTrue(ids.contains("IRD_FORMULA")); - assertTrue(ids.contains("IRD_RATE_DEFAULT")); - assertTrue(ids.contains("ISSUER_FEE_NON_NEGATIVE")); - assertTrue(ids.contains("ISSUER_FEE_FORMULA")); - assertTrue(ids.contains("ISSUER_FEE_RATE_DEFAULT")); - assertTrue(ids.contains("ISSUER_SETTLEMENT_NON_NEGATIVE")); - assertTrue(ids.contains("ISSUER_SETTLEMENT_FORMULA")); - assertTrue(ids.contains("AMOUNT_NOT_NULL")); - assertTrue(ids.contains("AMOUNT_NON_NEGATIVE")); - assertTrue(ids.contains("AMOUNT_MAX_LIMIT")); - assertTrue(ids.contains("AMOUNT_SCALE")); - assertTrue(ids.contains("ROUNDING_MODE")); - } - - @Test - @DisplayName("불변식 설명과 수식이 비어있지 않아야 함") - void testInvariantMetadataNotEmpty() { - for (SettlementInvariant.Invariant inv : SettlementInvariant.getAllInvariants()) { - assertNotNull(inv.getDescription()); - assertFalse(inv.getDescription().isEmpty()); - assertNotNull(inv.getFormula()); - assertFalse(inv.getFormula().isEmpty()); - assertNotNull(inv.getAffectedClass()); - assertFalse(inv.getAffectedClass().isEmpty()); - assertNotNull(inv.getPreservationNote()); - assertFalse(inv.getPreservationNote().isEmpty()); - } - } -}