대시보드 정산 수수료 계층별 계산 로직 검증 강화
목차
수수료 계산 로직은 건드릴 때마다 긴장된다. 금액이 틀리면 그게 곧 민원이고, 민원은 곧 신뢰 손상이다. 이번 작업도 규모 자체는 크지 않았는데 - 핵심 계산식 패턴 정비 + 계층별 요율 순서 validation 추가 정도 - 막상 파고들면 챙겨야 할 게 꽤 있었음.
계층 구조가 만들어내는 마진 계산
우리 정산 수수료는 유통 계층별 차등 요율 구조다. 최하위 계층이 가장 높은 요율을 부담하고, 상위 계층은 그보다 낮은 요율로 정산을 받는 대신 차액이 수익이 되는 방식. 단순하게 표로 보면 이런 형태임.
| 계층 | 요율 | 하위 대비 차액(마진) |
|---|---|---|
| 최하위 | 1.0% | - (기준) |
| 중간 | 0.8% | 0.2% |
| 최상위 | 0.6% | 0.2% |
문제는 이 요율 순서가 뒤집히면 마진이 음수가 된다는 것. 하위 계층보다 상위 계층 요율이 높아지는 순간, 상위 계층이 하위한테 돈을 역으로 쥐어주는 상황이 생긴다. 데이터 입력 단계에서는 제약이 없었기 때문에 배치가 돌기 전까지 누구도 모르는 상태로 설정값이 저장될 수 있었음.
그래서 이번에 계층 순서 validation을 추가했다. 핵심은 간단한데, 계층 리스트를 순회하면서 rate[i] >= rate[i-1] 이면 예외를 던지는 것. 배치 실행 시점이 아니라 설정 저장 시점에 막아야 의미가 있어서 입력 레이어에 뒀음.
// 계층 요율 순서 검증 - 상위일수록 낮아야 함
for (int i = 1; i < tiers.size(); i++) {
if (tiers.get(i).getRate() >= tiers.get(i - 1).getRate()) {
throw new IllegalArgumentException(
"상위 계층 요율이 하위보다 낮아야 합니다. tier=" + tiers.get(i).getLevel()
);
}
}
// 실제 수수료 계산 - 비율 + 건당 고정비 합산
long feeAmount = (long)(txAmount * feeRate) + perTxFee;
올림/버림의 정치학
(long) 캐스트 하나가 사실 정책 결정이다. Java에서 double을 long으로 캐스팅하면 소수점 아래를 그냥 버린다. 올림이 아니라 내림. 수수료를 내는 쪽 기준에서는 유리한 처리다.
이게 맞는 방향인지 당시에 잠깐 고민했음. 대안을 보면:
Math.round()- 0.5 기준 반올림, 예측 가능하지만 때로 수수료를 더 많이 낼 수 있음Math.ceil()- 무조건 올림, 사업자 수익 보전에는 유리하지만 사용자한테는 불리(long)캐스트 = 내림 - 부담자 유리, 분쟁 소지 최소화
금액이 작을 때는 차이가 미미하지만 거래량이 쌓이면 누적 차이가 생긴다. 어느 방향으로 오차가 쌓이느냐 자체가 이해관계자마다 다르게 읽힌다. 우리가 내림으로 간 건 "수수료는 내는 사람한테 불리하면 안 된다"는 암묵적 원칙 때문이고, 이게 사용자 불만을 줄이는 데 실제로 효과가 있다고 봄.
단 perTxFee 같은 고정 건당 수수료는 이미 정수라 여기서 소수점 처리 이슈는 없음. 비율 수수료 부분(txAmount * feeRate)에서만 발생하는 문제임.
정산 배치와 멱등성
정산 배치는 주기적으로 돌면서 해당 기간 거래를 집계하고 수수료를 확정하는 구조다. 여기서 멱등성이 핵심 설계 요건임. 같은 정산 기간을 배치가 두 번 실행했을 때 결과가 동일해야 한다는 것.
멱등성이 깨지는 대표적인 패턴들:
- 집계 기준이 "현재 시점 조회"에 의존할 때 - 배치 실행 시점마다 데이터가 달라짐
- 중복 실행 방어 없이 INSERT를 반복할 때 - 수수료 레코드가 두 배로 쌓임
- 소수점 처리를 런타임 설정에서 읽어올 때 - 설정이 바뀌면 재실행 결과가 달라짐
이번 작업에서 멱등성 자체를 처음 도입한 건 아니고, 기존 구조를 건드리면서 멱등성을 깨뜨리는 부분이 없는지 확인하는 데 시간을 들였음. 계층 요율을 validation 추가하면서 "이미 저장된 잘못된 설정값으로 과거 배치가 돌았다면?" 같은 케이스도 짚고 넘어갔다. 이건 소급 정산 이슈로 이어질 수 있어서 배치 실행 이력과 설정 변경 이력을 같이 남기는 게 중요함.
솔직히 이런 작업은 PR 크기가 작아서 리뷰에서도 빠르게 넘어가기 쉽다. 코드 줄 수가 적으면 "작은 수정"으로 인식되는 경향이 있는데, 금전 로직에서 작은 수정은 없다고 생각함. validation 하나 빠진 게 월말 정산에서 터지면 그 수습이 훨씬 크다.
계층 요율 순서 체크, 소수점 내림 정책 명시, 배치 멱등성 유지 - 세 가지 모두 "있어야 당연한 것"들인데 이번에 제대로 자리잡은 거라 자잘하지만 의미 있는 변경이었다고 본다.
댓글 0
첫 댓글 달아줘.