개발 slecs

정산 출금 가능 금액 산식 개선과 명세 투명화

목차

결제 플랫폼의 정산 시스템에서 가맹점이 실제로 출금할 수 있는 금액을 계산하는 로직을 손봤다. "확정 출금 가능" 필드의 산식을 개선하고, UI에 세부 라인을 추가해서 투명성을 높이는 작업이었다. 기능 추가처럼 드라마틱하진 않지만, 손댈 때마다 긴장감이 따라오는 영역이다.

정산 금액 산식, 왜 다시 봤나

이커머스 정산 시스템에서 가맹점이 언제 돈을 빼갈 수 있는지는 매우 예민한 부분이다. 단순히 "매출액 - 수수료"가 아니라, 여러 상태의 주문(완료, 취소, 환불 대기, 분쟁 중)을 고려해야 하고, 정산 주기(일별/주별/월별)에 따라 달라진다. 게다가 플랫폼 정책에 따라 계산 방식이 유동적이다. 환불 분쟁 기간 동안 해당 금액을 묶어두는 방식이 대표적인 예고, 선입금과 후결제 모델이 공존하면 더 복잡해진다.

"확정 출금 가능"이라는 필드가 따로 있다는 건, 단순 잔액과 출금 가능액을 명확히 구분하겠다는 설계 의도가 담긴 거다. 예를 들어 총 매출이 100만 원이어도, 환불 대기 중인 20만 원을 제외하고 80만 원만 출금 가능한 상황이 발생한다. 이런 세분화된 상태 관리가 없으면 가맹점 입장에서 자신의 실제 가용 자금을 파악하기 어렵고, 운영팀도 이상 거래나 부정 행위 여부를 추적하기가 훨씬 복잡해진다.

정산 상태를 정의할 때 일반적으로 다음 네 가지 버킷으로 나누는 게 깔끔하다.

상태 설명 출금 가능 여부
완료(settled) 구매 확정 후 정산 주기 경과 가능
보류(held) 환불 분쟁 기간 내, 이상 거래 의심 불가
환불 진행(refunding) 환불 요청 처리 중 불가
취소(cancelled) 결제 취소 완료 해당 없음

이 버킷 정의가 흔들리면 산식 자체가 의미 없어진다. 코드 어딘가에 enum이나 상수로 정의돼 있어도, 비즈니스 로직과 실제 DB 상태 값이 다르게 흘러가는 경우가 생각보다 많다. 이번에 산식을 다시 들여다보면서 가장 먼저 한 게 이 상태 정의를 코드와 명세 양쪽에서 일치시키는 것이었다. 작은 것 같지만 이게 안 돼 있으면 산식을 아무리 잘 고쳐도 결과가 어긋난다.

sub-line 추가로 명세를 투명하게

UI에 sub-line을 추가했다는 건, 숫자 하나를 던져주던 방식에서 벗어나 구성 요소를 드릴다운하는 형태로 바꿨다는 뜻이다.

확정 출금 가능: 800,000원
  ├─ 완료 건 누적: 950,000원
  ├─ 환불 진행 중: -150,000원
  └─ 정산 보류: 0원

이전에 최종 숫자만 표시했을 때는 "왜 내 잔액이 이 금액이냐"는 문의가 반복됐을 거다. 가맹점 입장에서 기대한 금액과 실제 출금 가능 금액이 다를 때, 그 이유를 알 수 없으면 불신으로 이어진다. sub-line이 생기면 "환불 요청이 진행 중이라 이 부분이 빠진 것"을 가맹점 스스로 파악할 수 있다. CS 인입이 줄어드는 건 부수 효과고, 핵심은 플랫폼-가맹점 간 신뢰 자체다.

백엔드 API 단에서도 이 명세를 응답 스펙에 반영하는 게 맞다. 숫자 하나만 내려주던 걸 breakdown 구조로 확장하면 이런 형태가 된다.

{
  "withdrawable_amount": 800000,
  "breakdown": {
    "settled": 950000,
    "refunding": -150000,
    "held": 0
  }
}

프론트에서 이 구조를 받아 접었다 펼 수 있는 형태로 표시하면 된다. 처음부터 이렇게 설계했으면 좋았겠지만, 초기 스펙에서는 "일단 금액 하나만"으로 시작하는 경우가 많다. 시스템이 성숙하면서 이런 투명화 작업이 자연스럽게 뒤따라온다.

여기서 한 가지 신경 써야 할 부분이 있다. breakdown 항목을 추가할 때 합산이 항상 withdrawable_amount와 일치하는지를 API 레벨에서 보장해야 한다는 점이다. UI에 표시되는 항목들의 합이 최종 금액과 달라지는 순간 투명성은 오히려 역효과가 난다. 가맹점이 직접 계산기를 두드려서 숫자가 안 맞으면, 그건 그냥 숫자 하나만 보여줬을 때보다 훨씬 큰 불신을 만든다.

산식 변경의 리스크와 검증 체크리스트

정산 관련 로직을 수정할 때는 항상 신중해진다. 한 글자 실수가 수천 건의 거래를 잘못 정산할 수 있기 때문이다. 특히 이번처럼 산식 자체를 바꾸면, 기존에 계산된 값들과의 일관성을 어떻게 처리할지가 먼저 결정돼야 한다.

  • 소급 적용 vs. 신규 정산부터만: 대부분은 신규 정산부터 적용하는 게 안전하다. 기존 데이터를 소급 재계산하면 이미 처리된 출금 내역과 충돌이 생길 수 있다. 소급이 꼭 필요하다면 별도 마이그레이션 스크립트를 만들고, dry-run 결과를 운영팀과 함께 검토한 뒤 실행하는 게 맞다.
  • 각 상태별 테스트 케이스: 완료, 취소, 환불, 분쟁 각각에 대해 독립된 케이스를 갖고 있어야 한다. 엣지 케이스는 "완료 후 부분 환불", "취소 후 재결제" 같은 복합 시나리오에서 주로 터진다.
  • 스테이징 시뮬레이션: 실제 운영 반영 전, 스테이징에서 가맹점 계정으로 시나리오를 돌려보는 게 중요하다. 자동화 테스트가 커버 못 하는 타이밍 이슈나 DB 상태 의존성이 여기서 잡힌다.
  • 숫자 불일치 모니터링: 배포 이후에도 breakdown 합산이 withdrawable_amount와 다른 케이스가 생길 수 있다. 이를 조용히 넘기지 않고 알림으로 즉시 탐지할 수 있는 장치를 달아두면, 문제가 조용히 쌓이는 걸 막을 수 있다.

이런 체크리스트를 빠뜨리면 배포 후 일부 가맹점이 잘못된 정산액을 받게 되고, 이를 바로잡는 데 인력과 시간이 많이 소요된다. 재처리 비용이 개발 비용보다 훨씬 크게 불어나는 게 정산 시스템의 특성이다.

정산 시스템 개선은 눈에 띄지 않는다. 배포했다고 누군가 박수 치는 종류의 작업이 아니다. 그래도 돈이 오가는 영역에서 투명한 산식과 명확한 UI가 분쟁을 사전에 막고 운영팀의 CS 부담을 덜어준다는 건 분명하다. 금융 관련 로직일수록 "지루한 정확성"이 오래 가는 시스템을 만든다는 걸, 이런 작업을 거칠 때마다 다시 확인한다.

댓글 0

첫 댓글 달아줘.