차등수수료 모델 전환과 정산 검증 로직 강화
목차
차등수수료 모델을 실제로 코드에 녹여 넣는 건 개념은 단순한데 구현 디테일에서 생각보다 신경 쓸 게 많다. 수수료율 하나 틀리면 정산 금액이 통째로 틀어지는 구조라, 작업 규모는 작아도 검증 설계에 가장 많은 시간을 쏟았다.
차등 구조가 생기는 이유
유통 계층이 여럿인 시스템에서는 최하위 계층이 수수료를 가장 많이 내고, 상위 계층은 그 차액을 마진으로 가져간다. 이걸 "내려갈수록 요율이 높아진다"고 표현하면 반직관적으로 들리지만, 수익 구조를 따라가면 자연스럽다. 상위가 하위 계층에 서비스를 제공하는 대가로 스프레드를 먹는 것이니까.
이번 작업에서 적용한 구조는 이렇다.
| 계층 | 요율 | 비고 |
|---|---|---|
| 최하위 | 1.0% | 최대 부담 |
| 중간 | 0.8% | 차액 0.2% 수익 |
| 최상위 | 0.6% | 차액 0.2% 수익 |
테이블 자체는 단순한데, 이걸 코드로 옮길 때 "어느 계층이 상위고 어느 계층이 하위인가"를 DB 구조와 일치시키는 게 관건이었다. 계층 순서 정의가 코드 한쪽과 DB 다른 쪽에 이원화되면 나중에 계층이 추가될 때 한 군데를 빠뜨리는 사고가 난다.
수수료 계산과 validation
수수료 계산 자체는 비율 수수료와 건당 고정 수수료를 합산하는 방식이다.
// 비율 수수료 + 건당 고정 수수료 합산
long feeAmount = (long)(txAmount * feeRate) + perTxFee;
double을 long으로 캐스팅할 때 소수점 버림이 발생한다. 금액 계산에서 이 버림이 건당 1원 미만이라도, 정산 배치가 수만 건을 처리하면 합산 오차가 무시 못 할 수준이 된다. 이번엔 기존 방식과 동일하게 유지했지만, 정밀도가 중요한 시스템이라면 BigDecimal로 중간 계산을 하고 마지막에 반올림 정책을 명시적으로 적용하는 편이 안전하다.
계층 요율 순서 검증은 이번 작업에서 별도로 추가한 부분이다. 하위 계층 요율이 상위 계층보다 낮게 설정되면 마진이 음수가 된다. 이게 운영 중에 슬그머니 들어오면 정산 배치가 조용히 마이너스 마진을 계산하고 넘어갈 수 있다. 에러가 나는 게 아니라 틀린 숫자를 확정해버리는 쪽이 훨씬 위험하다.
// 계층 순서 검증 - 하위 요율 > 상위 요율 보장
for (int i = 0; i < tiers.size() - 1; i++) {
if (tiers.get(i).getFeeRate() <= tiers.get(i + 1).getFeeRate()) {
throw new IllegalStateException("계층 요율 순서 오류: 하위 계층 요율이 상위 계층보다 낮음");
}
}
이런 validation은 설정 로드 시점에 한 번, 그리고 관리자가 요율을 변경할 때 한 번, 두 군데에 걸어두는 게 낫다. 런타임 계산 직전에 매번 체크하는 건 과하다.
정산 배치는 주기적으로 실행되면서 해당 기간 거래 내역을 집계하고 수수료를 확정한다. 이 흐름에서 한 가지 트레이드오프가 있는데, 배치 실행 중간에 요율 변경이 들어오면 어떻게 할지다. 일반적으로는 배치 시작 시점의 요율 스냅샷을 찍어서 그걸 기준으로 전체 집계를 끝내는 방식을 쓴다. 배치 도중 요율이 바뀌었다고 같은 정산 주기 안에서 앞뒤 거래의 요율이 달라지면 설명하기가 곤란해진다.
정산 UI에서 숫자를 보여주는 방식
정산 화면은 결국 숫자를 얼마나 명확하게 보여주느냐의 문제다. 총 거래액, 수수료, 실수령액 세 가지를 한눈에 읽을 수 있어야 한다.
금액 표기 원칙은 단순하게 잡는 게 좋다.
- 천 단위 구분자 일관 적용
- '원' 단위 명시
- 마이너스 금액은 색으로 구분 (보통 빨간색)
- 소수점 이하는 원 단위에서 표시 안 함
여기서 자주 실수하는 건 백엔드에서 long으로 내려오는 금액을 프론트에서 JavaScript number로 받을 때다. JS의 number는 53비트 정수까지 안전하게 표현하는데, 원화 금액이 수십억 단위를 넘기면 정밀도 손실이 생긴다. 이 범위가 실제로 발생할 수 있는 시스템이라면 금액 필드를 문자열로 받아서 처리하거나, BigInt를 쓰는 방향을 검토해야 한다.
마이너스 금액 빨간색 표시는 단순해 보이지만 CSS 클래스 분기가 프론트 컴포넌트 여러 곳에 흩어지면 나중에 정책이 바뀔 때 다 찾아서 고쳐야 한다. 금액 포맷팅 유틸 하나에 몰아두고 isNegative 플래그를 반환하게 하면 컴포넌트는 그 값만 보고 클래스를 붙이면 된다.
작업 자체는 크지 않았다. 하지만 정산 로직은 한 번 잘못 들어가면 운영팀에서 수동으로 역추적해야 하는 상황이 생긴다. 그 비용이 개발 시간보다 훨씬 크다는 걸 알기 때문에, 이런 류의 변경은 작을수록 더 꼼꼼하게 검증 코드를 붙여두는 습관을 들이고 있다.
댓글 0
첫 댓글 달아줘.