개발 slecs

채널 일괄 등록 시 계층별 요율 검증으로 마진 음수 방지

목차

채널 포털은 외부 채널 사업자가 자신의 잔액, 거래 내역, 하위 채널 현황을 한눈에 파악하는 공간이다. 대시보드에서 수수료 설정과 마진 계산 결과까지 바로 확인할 수 있도록 설계돼 있는데, 이 포털에 일괄 등록 기능을 추가하면서 계층별 요율 검증 로직을 함께 붙였다.

발단은 단순했다. 채널 수가 늘어날수록 한 건씩 등록하는 방식이 운영 부담으로 돌아왔고, 엑셀로 한 번에 올릴 수 있어야 한다는 요구가 생겼다. 기능 자체는 크지 않지만, 여기서 놓치면 안 되는 부분이 있었다. 바로 요율 순서 검증이다.

계층 요율이 뒤집히면 생기는 일

채널 구조는 상위-하위 계층으로 나뉘어 있고, 요율은 계층마다 다르게 적용된다. 핵심 규칙은 하나다.

상위 채널의 요율 < 하위 채널의 요율

상위가 하위보다 낮아야 그 차이가 상위 채널의 마진이 된다. 이 순서가 뒤집히면 상위 채널이 하위에게 오히려 더 많이 줘야 하는 상황이 발생하고, 결과적으로 마진이 음수로 떨어진다. 정산 배치가 돌기 전까지는 눈에 안 띄다가, 배치 결과가 나왔을 때 "왜 이 채널 정산이 마이너스야?"로 터진다. 원인 추적하다 보면 등록 시점의 요율 입력 실수로 귀결되는 케이스가 많다.

일괄 등록이 추가되면서 이 리스크가 더 커졌다. 개별 등록은 화면에서 바로 시각적으로 확인이 가능한데, 엑셀 업로드는 수십 건이 한 번에 밀려 들어오기 때문에 잘못된 행 하나를 놓치기 쉽다. 그래서 업로드 처리 시점에 계층 관계를 참조해서 요율 순서를 검사하고, 위반하는 행이 있으면 등록 전체를 막고 오류를 반환하도록 처리했다.

검증 로직의 구조는 대략 이렇다.

def validate_channel_rate_hierarchy(channels: list[dict]) -> list[str]:
    errors = []
    for ch in channels:
        parent_rate = get_parent_rate(ch["parent_id"])
        if parent_rate is None:
            continue  # 최상위 채널은 검증 제외
        if parent_rate >= ch["rate"]:
            errors.append(
                f"채널 '{ch['name']}': 상위 요율({parent_rate}%) >= "
                f"등록 요율({ch['rate']}%) — 마진 역전 발생 가능"
            )
    return errors

실제 구현은 이보다 복잡하지만 핵심 흐름은 같다. 부모 요율을 조회해서 자식 요율과 대소 비교하고, 위반이 있으면 해당 채널명과 요율 값을 명시한 오류 메시지를 돌려준다. 업로드 단에서 이 검증을 통과해야만 INSERT가 이뤄지도록 했다.

정산 계산에서 반올림이 분쟁을 만드는 방식

요율 순서 문제를 막은 다음에도 계층별 수수료 계산에서 놓치기 쉬운 부분이 있다. 원 단위 나머지 처리다.

수수료 계산은 곱셈이라 소수점이 생기는 경우가 빈번하고, 이걸 어느 방향으로 자르느냐가 사용자 경험에 직접 영향을 준다. 일반적으로 수수료를 내는 쪽에게는 내림을 적용해서 실제로 내야 하는 것보다 조금이라도 덜 내게, 받는 쪽에게는 올림 없이 정확하게 계산하는 방향이 분쟁을 줄인다.

처리 방향 적용 대상 효과
내림(floor) 수수료 납부자 실납부액이 계산값 이하 유지
절사(truncate) 수수료 수취자 지급액이 실거래 기반으로만 산출
반올림(round) 양쪽 혼용 시 누적 오차로 합산 불일치 발생 가능

반올림을 무심코 쓰면 건수가 쌓일수록 합산 금액이 미세하게 어긋나고, 정산 리포트의 총합이 맞지 않는다는 민원으로 이어진다. 금전 로직은 가장 안전한 방향으로 처리하는 게 맞다.

정산 UI 쪽에서는 결국 숫자가 전부다. 총 거래액, 수수료, 실수령액을 명확하게 구분해서 보여주는 것, 천 단위 구분자와 '원' 단위를 일관되게 표기하는 것, 마이너스 금액은 빨간색으로 즉시 눈에 들어오게 하는 것. 이 작은 표현 요소들이 사용자가 숫자를 신뢰하게 만드는 기반이 된다.

정산 배치는 멱등성이 필수다. 같은 기간을 두 번 실행했을 때 결과가 달라지면, 수동 재처리 상황에서 이중 정산이 발생한다. 실행 이력을 기록하고 이미 완료된 기간에 대한 재실행은 무시하거나 명시적 덮어쓰기 플래그를 요구하게 하는 구조가 안전하다.


이번 작업 자체의 규모는 크지 않았다. 업로드 검증 하나 추가한 거라 코드 양도 많지 않음. 그런데 이런 게 쌓이지 않으면 운영 중에 "마진이 왜 마이너스예요?"라는 질문이 들어오고, 원인 파악하는 데 더 많은 시간이 든다. 등록 시점에 막는 비용이 정산 이후에 수습하는 비용보다 압도적으로 작다. 결국 방어 코드를 제때 넣는 것이 가장 저렴한 QA다.

댓글 0

첫 댓글 달아줘.