계층별 수수료 정산 로직 검증 강화
목차
수수료 계산 로직은 코드 줄 수가 많지 않아도 틀리면 바로 돈 문제가 된다. 그래서 작은 수정이어도 검증 코드를 붙이는 걸 습관처럼 해왔는데, 이번에도 그 연장선이었다.
작업 배경을 간단히 정리하면, 우리 시스템의 수수료 구조는 유통 계층별로 요율이 다르게 설정되는 차등 모델이다. 하위 계층이 상위 계층보다 높은 요율을 부담하고, 그 차액이 상위 계층의 수익이 되는 구조인데, 이걸 마진 스프레드라고 부른다. 표로 보면 이렇다.
| 계층 | 요율 | 마진(상위로 귀속) |
|---|---|---|
| 최하위 | 1.0% | - |
| 중간 | 0.8% | 0.2% |
| 최상위 | 0.6% | 0.2% |
이 구조 자체는 흔한 다단계 수수료 모델이고, 문제는 계층이 늘어나거나 관리자 화면에서 요율을 수동으로 설정할 수 있을 때 생긴다. 누군가 실수로 하위 계층 요율을 상위보다 낮게 입력하면 마진이 음수가 된다. 음수 마진이 배치를 통과하면 정산 결과가 깨진다. 이번에 추가한 validation이 그 케이스를 잡기 위한 거였다.
계산 구조와 검증 포인트
수수료 계산 자체는 단순하다. 비율 수수료와 건당 고정 수수료를 합산하는 패턴이고, 내부적으로는 이렇게 생겼다.
// 비율 수수료 + 건당 고정 수수료 합산
long feeAmount = (long)(txAmount * feeRate) + perTxFee;
여기서 (long) 캐스팅이 사실상 소수점 버림이다. 수수료를 내는 쪽 입장에서 내림 처리는 유리하고, 받는 쪽 입장에서도 분쟁 소지를 줄인다. 올림을 해서 조금이라도 더 받으려다가 "왜 이 금액이냐"는 문의가 쌓이면 CS 비용이 더 크다. 일반적으로 원 단위 반올림은 사용자에게 유리한 방향, 즉 수수료 내는 쪽 기준 내림으로 처리하는 게 업계에서도 흔한 관행이다.
건당 고정 수수료(perTxFee)는 소액 거래에서 비율 수수료보다 무게감이 커진다. 예를 들어 1만 원 거래에서 1%는 100원인데 건당 고정이 200원이면 실질 부담은 3%에 해당한다. 대시보드에서 수수료율을 표시할 때 이 두 요소를 분리해서 보여주지 않으면 사용자 혼란이 생기는 지점이라, 표시 로직도 이번에 같이 손봤다.
계층 요율 순서 검증은 이런 식으로 체크한다.
// 하위 계층 요율이 상위보다 낮으면 예외
for (int i = 0; i < tiers.size() - 1; i++) {
if (tiers.get(i).getRate() < tiers.get(i + 1).getRate()) {
throw new IllegalStateException(
"계층 요율 역전: tier[" + i + "]=" + tiers.get(i).getRate()
+ " < tier[" + (i+1) + "]=" + tiers.get(i + 1).getRate()
);
}
}
예외 메시지에 어느 인덱스에서 역전이 발생했는지 넣는 게 중요하다. 운영 중에 로그만 보고 어느 계층 설정이 잘못됐는지 바로 알 수 있어야 한다.
정산 배치와 멱등성
정산 배치는 주기적으로 실행되면서 해당 기간 거래 내역을 집계하고 수수료를 확정한다. 여기서 멱등성이 핵심이다. 같은 기간을 두 번 실행했을 때 결과가 달라지면 안 된다.
멱등성을 깨는 흔한 패턴들은 대략 이렇다.
- 집계 쿼리에
NOW()같은 현재 시각이 끼어드는 경우 - 중간 상태를 덮어쓰지 않고 누적(
+=)하는 경우 - 정산 기준 시간을 배치 실행 시각 기준으로 잡는 경우
기준 기간을 파라미터로 명시적으로 받고, 해당 기간 정산 레코드가 이미 존재하면 업데이트 또는 스킵하는 upsert 구조로 가야 재실행이 안전하다. 장애 후 재처리 시나리오를 항상 머릿속에 두고 설계하는 편인데, "이 배치가 두 번 돌아도 돈이 두 번 빠지지 않는가"가 기준이다.
이번 작업 규모 자체는 크지 않았다. 하지만 금전 로직에서 검증 코드 하나가 빠져 있으면 나중에 데이터 정합성 문제를 사후에 직접 SQL로 교정하는 상황이 생긴다. 그게 훨씬 비싸다. 이런 방어 코드들이 쌓이면서 정산 시스템 전체의 신뢰도가 올라가고, 운영에서 "왜 이 금액이냐"는 질문 자체가 줄어든다. 눈에 잘 안 띄는 변경이지만 그래서 더 챙겨야 한다고 생각한다.
댓글 0
첫 댓글 달아줘.