계층별 수수료 정산 계산 로직 안정성 개선
목차
계층형 수수료 구조를 다루다 보면 언제나 마진 역전 버그가 최대 리스크다. 하위 파트너 요율이 상위보다 낮아지면 중간 계층이 음수 마진을 가져가는 상황이 생기는데, 배치가 실행되고 나서야 정산 금액이 이상하다는 게 드러나면 그때부터는 단순 버그가 아니라 신뢰 문제가 된다. 이번 작업은 그 지점을 막는 validation 추가와 기존 계산 로직 전반 점검이었다.
구조 먼저
우리가 쓰는 수수료 모델은 유통 계층별로 요율이 차등 적용되는 형태다. 하위 계층이 더 높은 요율을 부담하고, 상위 계층은 그 차액을 마진으로 가져간다. 구조만 보면 심플한데, 계층이 3단 이상 쌓이고 파트너 계정이 많아질수록 요율 순서가 꼬일 여지가 생긴다.
| 계층 | 요율 | 비고 |
|---|---|---|
| 최하위 | 1.0% | 최대 부담 |
| 중간 | 0.8% | 차액 0.2% 수익 |
| 최상위 | 0.6% | 차액 0.2% 수익 |
이 구조에서 요율 역전이 생기면 중간 계층이 받아야 할 마진이 음수가 된다. 조용하게 잘못된 금액이 쌓이다가 정산 시점에 터지는 유형의 버그다. 실시간 알림이 없으면 몇 주 치가 틀린 채로 확정될 수 있다.
비율 수수료 외에 건당 고정 수수료도 있어서 실제 계산은 두 값을 합산해야 한다.
// 비율 수수료 + 건당 고정 수수료 합산
long feeAmount = (long)(txAmount * feeRate) + perTxFee;
여기서 (long) 캐스팅은 소수점 버림이다. 수수료를 내는 쪽 기준으로 내림 처리하는 게 일반적인 관행인데, 올림으로 처리하면 1원씩 초과 부과되는 거라 클레임이 들어올 수 있다. 반대로 수취 쪽이 올림을 기대하고 있다면 계층마다 반올림 방향을 다르게 적용하는 경우도 있는데, 그게 복잡해지기 때문에 단일 정책을 코드 주석이나 상수로 명시해두는 게 낫다. 나중에 다른 사람이 건드릴 때 "왜 내림이야?"라고 의아해하는 걸 방지할 수 있다.
Validation 추가한 이유
계층별 요율 순서 체크는 사실 이전에도 있어야 했던 로직이다. 요율을 등록하거나 수정할 때 상위 계층보다 하위 계층 요율이 높은지를 검증하지 않으면, 데이터 입력 실수 하나가 조용히 계산 오류로 이어진다. 운영 초반에는 설정 건수가 적어서 수동으로 확인이 됐겠지만, 파트너가 늘어나면 그렇게 할 수가 없다.
이번에 추가한 validation은 간단하다. 계층 목록을 순서대로 순회하면서 인접 계층 간 요율 대소 관계를 체크하고, 역전이 감지되면 예외를 던진다.
for (int i = 0; i < tiers.size() - 1; i++) {
if (tiers.get(i).getRate() >= tiers.get(i + 1).getRate()) {
throw new InvalidFeeStructureException(
"계층 " + i + " 요율이 상위 계층보다 낮거나 같습니다."
);
}
}
복잡한 로직은 아니지만 없으면 안 되는 가드다. 실제로는 파트너 등록 시점과 정산 배치 시작 전 두 곳에서 체크하는 게 좋다. 등록 시에만 검증하면 이후 요율 변경 이벤트를 놓칠 수 있고, 배치에서만 검증하면 오류가 너무 늦게 잡힌다. 양쪽에 걸어두면 최소한 실행 직전에 한 번은 막힌다.
정산 배치 멱등성과 UI
이번 작업에서 같이 확인한 게 배치 멱등성이었다. 같은 정산 기간을 두 번 실행해도 결과가 동일해야 한다. 네트워크 장애나 서버 재시작으로 중간에 배치가 끊겼다가 재실행되는 상황이 실제로 생기기 때문이다.
멱등성을 보장하는 가장 단순한 방법은 정산 상태를 명시적으로 관리하는 것이다.
- 배치 시작 시 해당 기간의 정산 레코드가 이미
CONFIRMED상태면 스킵 - 중간에 실패한 경우
PENDING상태에서 재시작 가능하도록 구현 - 확정된 정산 레코드는 재계산 불가로 잠금 처리
이렇게 해두면 배치가 몇 번 재실행되든 숫자가 달라지지 않는다. 반대로 상태 관리 없이 매번 덮어쓰는 구조면, 정산 확정 후에도 데이터가 바뀔 수 있어서 감사 추적이 안 된다. 분쟁이 생겼을 때 이력이 없으면 곤란하다.
정산 화면에서도 결국 중요한 건 숫자 가독성이다. 총 거래액, 수수료, 실수령액을 명확하게 보여줘야 하는데, 천 단위 구분자와 통화 단위를 일관되게 쓰고 마이너스 금액은 시각적으로 구분해주는 것만으로도 문의가 줄어든다. 계산이 맞아도 보기 불편하면 틀렸다고 생각하는 사람이 생긴다. 이건 UX 얘기이기도 하지만 운영 리소스 얘기이기도 하다.
작업 규모 자체는 크지 않았다. 하지만 금전 계산 로직은 오류가 발생하면 복구 비용이 단순 버그랑 다르다. 계층별 요율 역전 방지, 반올림 정책 명시, 배치 멱등성-이 세 가지가 정산 시스템의 최소 안전망이라는 걸 이번에 다시 확인했다. 작은 수정이지만 이게 없으면 언젠가 한 번은 터진다.
댓글 0
첫 댓글 달아줘.