개발 slecs

가상계좌 수수료 후정산 전환과 충전 상태 머신 정합성 확보

목차

가상계좌 충전 수수료, 결국 후정산이 답

결제대행사 가상계좌로 잔액 충전하는 흐름에서 수수료 처리가 계속 어긋났음. 충전 시점에 수수료까지 한 번에 차감하니까 회계팀에서 "이게 왜 여기서 빠져?"가 반복됨.

가상계좌 건당 부과되는 입금/발급 비용은 이번 정산 시 차감되는 게 아니라 익월에 별도 청구되는 구조. 그동안 코드는 충전 직후 머천트 잔액에서 즉시 차감하는 방식이었음. 표시용으로만 쓰면 될 값을 실제 차감에 사용한 게 원인.

항목 정산 시점 처리 방식
결제 수수료 이번 정산 차감 PENDING → CONFIRMED
가상계좌 건당 비용 익월 후정산 표시 전용, 실차감 X
판매대금 이번 정산 지급 hold 후 CONFIRMED

웹훅 수신 후 상태 전이 정리

웹훅 수신 부분도 같이 손봤음. 기존엔 입금 완료 통보가 오면 바로 CONFIRMED로 점프했는데, 그러면 hold 기간 중 취소가 들어왔을 때 잔액이 꼬임.

  • 입금 통보 수신 → PENDING 생성 (충전수수료/결제수수료/판매대금 3건)
  • 가상계좌는 +2시간 hold 후 CONFIRMED 전이
  • hold 중 취소가 들어오면 전부 CANCELLED로 마감, 잔액 변동 0원
  • CONFIRMED 시점에 한 번에 잔액 반영
[웹훅 수신]
   
[PENDING x3] ──(취소)──→ [CANCELLED x3]
   ↓ (+2h)
[CONFIRMED x3] → 잔액 반영

이 흐름으로 바꾸니까 hold 구간에서 환불이 들어와도 잔액이 음수로 떨어지는 케이스가 사라짐. 회계 대사도 깔끔해짐.

등급 동기화 배치, race condition 제거

같이 묶은 등급 동기화 배치 쪽은 이슈가 좀 다름. 파트너 누적 거래액이 임계값을 넘으면 등급이 자동 승급되는데, 충전 트랜잭션과 배치 트랜잭션이 같은 행을 동시에 건드릴 때 등급이 한 단계 밀리는 현상이 있었음.

해결 방향:

  1. 누적 거래액 갱신은 충전 트랜잭션 안에서만
  2. 배치는 읽기 → 등급 계산 → 조건부 업데이트(이전 등급이 같을 때만)
  3. 동기화 주기 짧게 (시간 단위 → 분 단위)
  4. 등급 이력 테이블에 직전 등급 함께 기록해서 디버깅 가능하게

조건부 업데이트로 바꾼 게 핵심. 낙관적 락처럼 동작하니까 동시성 충돌 시 한쪽이 자연스럽게 패배하고 다음 주기에 다시 맞춰짐.

배운 점

  • 선취/후정산 구분은 코드 레이어에서 강제해야 함. 라벨만 "후정산"이라고 적어두고 실제로는 차감하면 의미 없음
  • 표시용 값과 실제 차감 값은 변수명부터 분리할 것 (displayCost vs chargedCost)
  • 상태 머신은 hold 구간을 명시적으로 모델링해야 환불/취소 케이스에서 안 깨짐
  • 동기화 배치는 항상 "내가 본 값이 여전히 유효한가"를 한 번 더 확인

다음

댓글 0

첫 댓글 달아줘.