계층별 수수료 정산 로직에 요율 검증 추가
목차
유통 계층 구조에 수수료 요율 검증을 추가한 작업이었다. 작업 자체는 크지 않았는데, 하고 나서 생각보다 핵심을 건드린 거라는 걸 느꼈다.
수수료 구조가 단순히 "몇 퍼센트"로 끝나는 게 아니라 계층 간 차액으로 수익이 발생하는 모델이었기 때문이다. 하위 계층이 높은 요율을 부담하고, 상위 계층은 그 차액을 마진으로 가져가는 구조. 언뜻 보면 단순한데, 이 구조가 성립하려면 반드시 하위 계층 요율 ≥ 상위 계층 요율이어야 한다. 역전되는 순간 마진이 음수가 된다.
최하위(1.0%) → 중간(0.8%) → 최상위(0.6%)
↑ 0.2% 마진 ↑ 0.2% 마진
이 관계가 코드 어딘가에서 깨지더라도 계산 자체는 오류 없이 진행된다는 게 문제였다. 음수 마진이 DB에 조용히 적히고, 정산 배치가 그걸 그대로 합산하고, 나중에 리포트 뽑을 때야 이상한 숫자가 보이는 식이다. 금전 로직에서 이런 류의 무성한 오류는 추적하기도 어렵다.
그래서 요율 설정 시점에 계층 순서를 체크하는 validation을 넣은 거다.
계산 구조와 검증 포인트
수수료 계산 자체는 비율 수수료와 건당 고정 수수료를 합산하는 형태였다.
// 계층별 수수료 계산 (비율 + 건당 고정)
long feeAmount = (long)(txAmount * feeRate) + perTxFee;
여기서 (long) 캐스팅은 버림 처리를 의미한다. 수수료를 내는 쪽 기준으로 내림을 적용해서 사용자에게 유리하게 맞추는 거고, 이건 업계에서 통용되는 관행이기도 하다. 반올림 기준이 명시되지 않은 채로 Math.round를 쓰다가 나중에 클레임이 들어오는 경우를 생각하면, 처음부터 정책을 코드로 고정해두는 게 낫다.
요율 검증 쪽은 아래와 같은 방식으로 접근했다.
// 계층 요율 오름차순 검증 (하위 계층 인덱스가 클수록 요율이 높아야 함)
for (int i = 1; i < tierRates.size(); i++) {
if (tierRates.get(i) >= tierRates.get(i - 1)) {
throw new IllegalArgumentException(
"계층 요율 역전 오류: tier[" + i + "]=" + tierRates.get(i)
+ " >= tier[" + (i-1) + "]=" + tierRates.get(i-1)
);
}
}
실제 적용한 코드와 완전히 같지는 않지만 방향은 이랬다. 중요한 건 검증이 요율 저장 직전에 터지느냐, 정산 배치 실행 중에 터지느냐인데 - 가능하면 저장 시점에 터지게 하는 게 맞다. 배치 돌다가 중간에 예외 터지면 멱등성 처리가 복잡해진다.
정산 배치와 멱등성
정산 배치는 특정 기간의 거래를 집계해서 수수료를 확정하는 방식으로 실행된다. 여기서 멱등성은 선택이 아니라 필수다.
같은 정산 기간을 두 번 실행했을 때 결과가 달라지는 배치는, 장애 복구 시나리오에서 반드시 문제가 된다. 배치가 중간에 실패해서 재실행하거나, 운영 중 "혹시나" 싶어서 다시 돌리는 상황은 반드시 생긴다. 그때마다 수동으로 중복 여부를 확인해야 한다면 운영 부담이 너무 크다.
멱등성 구현 방식은 여러 가지인데, 가장 단순한 건 정산 레코드가 이미 존재하면 스킵하거나 덮어쓰지 않는 거다.
| 접근 방식 | 장점 | 단점 |
|---|---|---|
| 중복 체크 후 스킵 | 단순, 예측 가능 | 데이터 수정 후 재실행 불가 |
| UPSERT(같은 key) | 재실행 허용 | 의도치 않은 덮어쓰기 위험 |
| 상태 플래그로 잠금 | 안전성 높음 | 상태 관리 복잡도 증가 |
이번 작업에서는 이미 확정된 정산 레코드가 있으면 처리를 건너뛰는 방식이었다. 단순하고 검증된 패턴이다.
작은 수정이 커버하는 범위
요율 역전 버그는 "그럴 일 없겠지"가 쌓이면서 생긴다. 개발 중에 테스트 데이터로 계층 설정을 건드리거나, 어드민에서 요율 값을 수동으로 수정하거나, 마이그레이션 스크립트가 잘못된 순서로 값을 밀어 넣거나. 이런 경로는 코드 리뷰로 잡기 어렵다.
validation이 있으면 그 경로 중 어느 하나에서 문제가 생겨도 정산 배치가 돌기 전에 차단된다. 사후 복구보다 사전 차단이 비용이 훨씬 적다는 건 두말할 필요가 없고.
로직 자체가 복잡하지 않아서 코드 분량은 얼마 안 됐다. 그래도 금전 관련 로직에서 "이 조건이 깨지면 어떻게 돼?"를 하나씩 짚어가면서 검증 레이어를 쌓는 습관은, 나중에 트러블슈팅 시간을 수십 배로 줄여준다. JSP 쪽 개선도 같이 묶여 있었는데, 계산 결과를 화면에 보여주는 부분도 손을 봤다. 백엔드 로직이 바뀌면 화면 검증도 같이 확인해야 하는 게 원칙이다.
댓글 0
첫 댓글 달아줘.