개발 slecs

충전 수수료를 충전 트랜잭션 단위로 매칭해 회계 역추적 해결

목차

충전 수수료 차감, 어느 시점 요율을 따라야 하나

결제 플랫폼에서 파트너가 잔액을 충전할 때 충전 수수료를 떼는 구조인데, 환불·취소 흐름에서 차감 기준이 애매했음. 충전 시점 요율을 박아두는 방식이었는데, 파트너 등급이 중간에 바뀌면 과거 충전건과 현재 차감액이 어긋남. 회계팀에서 "이 충전건이 그 차감인지 매칭이 안 된다"는 컴플레인이 들어와서 손을 댔음.

무엇이 문제였나

  • 충전 시 수수료를 잔액에서 즉시 차감하던 코드가 일부 남아 있었음. PENDING 후 확정 시 차감하는 표준 흐름과 어긋남
  • 환불 시 "어느 충전건의 수수료인지" 역추적이 안 돼서 단순 합계로만 차감
  • 파트너 등급이 바뀐 케이스에서 차감액이 실제 부과액과 미세하게 안 맞음

요약하면 수수료 부과 단위가 충전 트랜잭션이 아니라 잔액 단위였다는 점이 화근.

어떻게 바꿨나

파트너의 최근 충전건을 역순으로 훑으면서 매칭 키를 넘기도록 했음. 컨트롤러 두 곳에서 들어오는 흐름을 한 서비스로 모았고, 쿼리는 충전 이력에서 가장 최근 PENDING/CONFIRMED 건을 픽하도록 LIMIT 처리.

-- 의사 쿼리
select charge_seq, fee_rate, fee_amount
  from charge_history
 where partner_id = :partnerId
   and status in ('PENDING','CONFIRMED')
 order by created_at desc
 limit 1;
변경 전 변경 후
잔액에서 단순 차감 매칭된 충전건의 fee_amount 차감
충전 시점 요율 모호 충전건 자체에 요율 스냅샷
역추적 불가 charge_seq로 1:1 매칭

API 두 개의 진입점이 같은 서비스를 타게 되니, 분기 로직이 컨트롤러에서 사라진 게 가장 컸음. 컨트롤러는 입력 검증과 응답 매핑만, 매칭과 차감은 서비스 한 곳으로.

배운 점

  • 수수료는 트랜잭션 단위로 묶어야 함. 잔액 차감으로만 처리하면 회계 추적이 안 됨
  • 요율 스냅샷을 충전건 row에 박아두면 등급 변경 이슈가 사라짐. 파생 계산보다 저장이 답일 때가 있음
  • 컨트롤러 두 개에 분산된 로직은 결국 서비스로 합치게 되어 있음. 처음부터 합쳐 짤 걸 그랬다는 후회

남은 숙제

  • 환불 시 매칭된 충전건이 이미 CANCELLED인 경우 fallback 정책이 아직 임시
  • 같은 시각에 들어온 다중 충전건의 동시성 처리 — 일단 row lock으로 막아뒀지만 부하 시점 재검토 필요
  • 회계 대시보드에서 "충전건 ↔ 차감건" 매칭 뷰를 따로 뽑을지 검토 중

PENDING/CONFIRMED 흐름 정비하면서 곁다리로 잔재 코드들을 같이 정리하게 됐음. 결제 도메인은 한 번 꼬이면 추적 비용이 폭증하는 영역이라, 매칭 키를 데이터에 박아두는 습관을 들여야겠다고 다시 새김.

다음

댓글 0

첫 댓글 달아줘.