개발 slecs

정산 출금 KPI 패널에 PG 비용 차감 반영

목차

merchant-withdraw 도메인에 PG 비용 차감 로직을 붙였다. 변경 파일은 뷰/스타일 1개뿐이지만, 실제 작업은 쿼리 설계부터 시작해서 꽤 넓게 퍼졌다.


정산 KPI 패널은 대시보드에서 가맹점이 "오늘 얼마 출금받을 수 있나"를 한눈에 확인하는 화면이다. 매출 합산, 취소 차감, 수수료 등을 집계해 보여주는데, 여기서 결제대행사(PG) 비용이 빠져 있었다. 가맹점 입장에서는 화면에 뜬 숫자와 실제 입금액이 달라서 혼란스러운 상황이었고, 이걸 맞춰줘야 했다.

단순히 뷰에서 어떤 값을 빼는 게 아니라, PG 비용 데이터 자체를 정산 흐름 안에 제대로 태워야 한다는 게 포인트였다.

구현하면서 손댄 것들

뷰 파일 1개라고 했지만, 그 뷰에 들어가는 데이터를 맞추려면 아래 레이어까지 건드려야 했다.

  • 정산 흐름에 PG 비용 단계를 신규 상태로 추가 (PENDING → CONFIRMED 전환 포함)
  • 취소/환불 발생 시 PG 비용도 함께 동기화
  • 감사(audit) 이력 테이블에 PG 차감 항목 연동
  • 중복 처리 방어를 위한 멱등성 키 적용

멱등성 키는 특히 중요하다. PG 비용 콜백이 네트워크 문제로 재전송될 수 있고, 그때마다 차감이 이중으로 쌓이면 숫자가 틀어진다. 키를 기준으로 "이미 처리된 이벤트인가"를 먼저 확인하고, 맞으면 그냥 성공 응답만 돌려주는 패턴이다.

-- PG 비용 차감 후 KPI 집계 예시 (단순화)
SELECT
  SUM(settlement_amount)           AS gross_amount,
  SUM(pg_fee)                      AS pg_fee_total,
  SUM(settlement_amount - pg_fee)  AS net_payout
FROM settlement_items
WHERE merchant_id = :merchant_id
  AND status = 'CONFIRMED'
  AND settled_at BETWEEN :from AND :to;

집계 쿼리에 pg_fee 컬럼이 들어오면서 실행 계획도 다시 확인했다. 인덱스가 merchant_id + settled_at 복합이라 range scan은 괜찮았는데, status 필터가 섞이면서 선택도가 어떻게 되는지 EXPLAIN 결과를 한 번 더 봤다. 큰 문제는 없었지만, 데이터가 쌓이면 pg_fee IS NOT NULL 필터가 발목 잡을 수 있어서 인덱스 확장 여지는 메모해뒀다.

설계할 때 가장 많이 고민한 것

금융 도메인에서 숫자 정합성은 선택이 아니라 기본값이다. 새 필드 하나 추가할 때도 "이 값이 다른 화면에서 보이는 값과 일치하는가"를 먼저 따진다.

이번에 고민한 항목들을 표로 정리하면:

항목 선택 이유
실시간 갱신 방식 폴링 (단기) PG 콜백 주기가 일정해서 이벤트 드리븐 전환 전까지 폴링으로 충분
중복 방어 멱등성 키 콜백 재전송 케이스 실측으로 확인됨
NULL 처리 COALESCE(pg_fee, 0) 레거시 데이터에 pg_fee 없는 row 존재
권한 체크 기존 미들웨어 재사용 merchant_id 바인딩 로직 이미 있어서 별도 레이어 추가 불필요

NULL 처리는 생각보다 자주 발목을 잡는다. 신규 컬럼을 추가하면 기존 데이터는 NULL이고, 집계 함수는 NULL을 0으로 안 본다. SUM(NULL) 이 NULL을 반환하는 건 SQL 스펙이지만, 화면에 나타나면 그냥 "빈 칸" 또는 "0"처럼 보여서 버그인지 정상인지 파악하기 어렵다. COALESCE를 빠뜨리면 특정 가맹점에서만 숫자가 이상하게 나오는 사례가 생기고, 재현도 안 되면 찾기 더 힘들다.

검증과 커밋 습관

구현 후에 직접 화면을 열어서 기존 케이스와 숫자를 비교했다. PG 비용이 있는 정산 건과 없는 건 두 케이스를 모두 확인하고, 관련 화면이 두 곳 이상이라 cross-check도 했다.

커밋은 논리 단위로 쪼갰다. 대략 이런 흐름이었다.

feat(settlement): add pg_fee column to settlement_items
feat(settlement): apply pg_fee deduction in CONFIRMED transition
feat(settlement): sync pg_fee on cancel/refund
feat(settlement): add pg_fee entry to audit history
feat(withdraw-kpi): reflect net_payout after pg_fee in KPI panel

뷰 변경이 마지막에 오는 이유는, 앞 단계에서 데이터가 제대로 쌓이고 있는지를 먼저 확인하고 나서 화면에 붙여야 하기 때문이다. 반대 순서로 커밋하면 뷰는 붙었는데 숫자가 안 나와서 "화면 버그인가 데이터 버그인가"를 한꺼번에 디버깅하는 상황이 생긴다.

커밋 메시지는 "무엇을"보다 "왜"를 담으려 한다. add pg_fee column보다는 add pg_fee column to reflect PG deduction in settlement KPI가 나중에 git blame 뒤져볼 때 컨텍스트가 살아 있다.


사내 서비스를 만들다 보면 기능 하나가 뷰 파일 하나로 끝나지 않는다는 걸 계속 체감한다. SQL 집계, 상태 머신, 예외 처리, 렌더링, 권한 체크가 모두 엮여 있어서 하나만 빠뜨려도 특정 가맹점에게만 이상한 숫자가 뜬다. 그게 결제 금액이면 신뢰 문제가 된다. "대충 맞는 것 같다"로 넘어간 것들은 반드시 다시 돌아온다는 걸 여러 번 겪었고, 그래서 지금은 꼼꼼함을 아예 기본 속도로 깔고 간다.

댓글 0

첫 댓글 달아줘.