카드 정산 업무 규칙과 전환 불변식 추출 (codex-bais-final3-20260713-192718-BAIS-SPRING-ANA-CARD-001)
This commit is contained in:
parent
b7784afef8
commit
8513e6b71c
1 changed files with 248 additions and 0 deletions
|
|
@ -0,0 +1,248 @@
|
|||
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; }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue