개발 slecs

파트너 포탈 채널 일괄 등록

목차

채널 포털에서 일괄 등록이 필요한 이유는 명확하다. 초기 온보딩 때 채널을 한 건씩 등록하는 건 운영자 입장에서 고통이다. 신규 파트너가 붙을 때마다 폼을 채우고 저장 버튼을 반복하는 건 실수도 생기고 시간도 잡아먹는다.

채널 포털 자체는 각 외부 채널 사업자가 자신의 현황을 확인하고 설정하는 공간이다. 대시보드에 잔액, 거래 내역, 하위 채널 현황을 모아두고, 수수료 설정과 마진 계산, 정산 내역 조회, 하위 채널 신청 처리까지 하나의 화면에서 처리할 수 있게 구성되어 있다. 이번 작업은 거기에 엑셀 기반 일괄 등록을 얹고, 업로드 시점에서 실행되는 계층 검증을 붙인 것이다.

계층 검증이 왜 까다로운가

채널 구조가 단순 트리였다면 검증 로직도 단순했을 것이다. 실제로는 상위/하위 채널이 다층으로 연결되고, 각 레이어마다 수수료 요율이 다르다. 문제는 이 요율의 방향성이다.

상위 채널의 요율이 하위 채널보다 낮아야 한다는 규칙이 있다. 구조상 하위 채널이 더 높은 요율로 거래하고, 그 차액을 상위 채널이 마진으로 가져가는 모델이기 때문이다. 이 순서가 역전되면 마진이 음수가 된다. 정산 계산식 자체는 아무 오류 없이 돌아가는데 결과가 음수로 찍히는 버그, 원인 추적하려면 계층 전체를 들여다봐야 해서 꽤 삽질이 된다.

그래서 업로드 시점에 바로 튕겨내는 게 맞다. 사후 정산 배치에서 걸리면 이미 늦다.

엑셀 파일을 파싱한 뒤 채널 간 요율을 순서대로 체크하는 로직의 핵심 흐름을 쓰면 이렇게 생겼다.

def validate_channel_rate_hierarchy(channels: list[dict]) -> list[str]:
    errors = []
    channel_map = {ch["id"]: ch for ch in channels}

    for ch in channels:
        parent_id = ch.get("parent_id")
        if not parent_id:
            continue
        parent = channel_map.get(parent_id)
        if parent and parent["rate"] >= ch["rate"]:
            errors.append(
                f"채널 {ch['id']}: 상위({parent['rate']}%) >= 하위({ch['rate']}%) - 마진 음수 발생"
            )
    return errors

업로드된 전체 행을 메모리에 올려서 맵 구조로 만들고, 각 채널이 부모가 있으면 부모 요율과 비교한다. 오류가 하나라도 있으면 전체 등록을 롤백하고 오류 목록을 응답으로 내려준다. 부분 성공 처리를 허용하면 나중에 DB 상태 추적이 복잡해지기 때문에 전부 아니면 전무로 처리했다.

검증 순서도 신경 썼다. 파일 형식 오류, 필수 컬럼 누락, 중복 채널 ID, 계층 요율 오류를 차례로 체크하고 첫 번째 실패 유형에서 멈춘다. 모든 검증을 다 돌려서 오류를 한꺼번에 반환하는 방식도 있는데, 운영자 입장에서는 "컬럼 이름이 틀렸는데 요율 오류도 같이 나온다"면 오히려 혼란스럽다. 파일 자체가 잘못된 거랑 데이터가 잘못된 건 다른 레벨의 문제라서, 파일 파싱 성공 여부를 먼저 분리하는 게 UX상 훨씬 낫다.

정산 화면 숫자 처리와 배치 멱등성

정산 UI는 결국 숫자 싸움이다. 사용자가 보고 싶은 건 세 가지다.

항목 설명
총 거래액 해당 기간 발생한 전체 거래 합산
수수료 요율 기준으로 계산된 수수료 금액
실수령액 총 거래액에서 수수료 차감 후 금액

숫자 표기 규칙은 단순하다. 천 단위 구분자 필수, 단위는 '원'으로 고정. 근데 마이너스 금액 처리를 빼먹기 쉽다. 취소 거래나 환불이 끼어들면 특정 기간 집계가 음수로 나올 수 있는데, 이걸 그냥 -1,200원으로 표시하면 숫자가 많은 화면에서 놓치기 쉽다. 빨간색으로 구분하는 게 직관적으로 훨씬 낫다. 색 하나 차이인데 운영자가 이상 값을 눈치채는 속도가 달라진다.

올림/버림 처리도 짚고 넘어가야 한다. 수수료 계산에서 원 단위 미만이 발생할 때 어떻게 할지 정해두지 않으면 나중에 클레임이 생긴다. 일반적으로 수수료를 내는 쪽(하위 채널)에게 유리하게 내림 처리한다. 수수료를 조금 덜 내게 되는 셈인데, 받는 쪽도 그걸 이미 알고 계약하는 거라 분쟁 소지가 줄어든다. 반대로 올림 처리하면 계산식은 맞는데 사용자 체감 신뢰가 깎인다. 금전 관련 로직에서 "수학적으로는 맞지만 기분 나쁜" 케이스는 피하는 게 좋다.

정산 배치 멱등성도 이번에 같이 정비했다. 같은 기간을 두 번 돌렸을 때 결과가 달라지면 안 된다는 원칙인데, 배치를 재실행해야 하는 상황은 운영 중에 꽤 자주 생긴다. 배치가 중간에 죽었거나, 집계 오류가 있어서 수동으로 다시 돌려야 하거나. 이 때 가장 흔한 실수는 INSERT 로직이 멱등하지 않아서 중복 행이 쌓이는 거다. 정산 테이블에 실행할 때마다 행이 추가되는 구조면 두 번 돌리면 두 배가 된다. ON CONFLICT DO UPDATE나 실행 전 해당 기간 데이터를 먼저 지우고 다시 넣는 방식을 쓰면 재실행에 안전하다.

이번 작업 규모 자체가 크지 않았던 건 맞다. 근데 채널 구조 관리나 정산 관련 기능은 한 번 잘못 만들면 운영 중에 건드리기 무서운 영역이 된다. 초기에 검증 로직을 제대로 박아두고, 멱등성을 보장해두는 게 나중에 "이 배치 다시 돌려도 되나요?" 물음에 자신 있게 답할 수 있는 기반이 된다. 이런 작은 수정들이 쌓여서 전체 시스템 신뢰도가 유지된다.

댓글 0

첫 댓글 달아줘.