간편결제 입금 자동 매칭으로 파트너 정산 클레임 해소
목차
정산 파이프라인에서 수동 구간이 남아 있으면, 앞단을 아무리 자동화해도 거기서 병목이 생긴다. 간편결제 입금 매칭이 딱 그 케이스였음. 송금 건수가 늘면서 운영팀이 화면 보며 손으로 맞추는 구간이 한계에 왔고, 매칭 지연이 파트너 정산 클레임으로 쌓이고 있었다.
문제를 알고도 손을 못 대는 이유가 따로 있는 경우가 많은데, 이건 그냥 미뤄진 케이스였음. "사람이 하면 되는데"가 "사람이 하기 힘들어졌다"로 바뀌는 시점이 오면 결국 손을 대게 된다. 그 시점이 왔다.
이중 수신 구조를 잡기까지
결제대행사 알림이 있으면 그냥 받으면 되지 않냐고 할 수 있다. 근데 외부 알림 채널은 생각보다 불안정하다. 네트워크 단절, 대행사 시스템 점검, 웹훅 재전송 실패 같은 상황이 운영 중에 실제로 생긴다. 알림만 믿었다가 조용히 입금이 누락되면 잡는 게 더 어렵다. 반대로 폴링만 쓰면 분 단위 지연이 발생하고 API 호출 비용이 계속 나간다.
그래서 알림 수신을 1차, 폴링을 보조 안전망으로 병행하는 구조를 택함.
| 방식 | 장점 | 단점 |
|---|---|---|
| 알림 수신 | 지연 거의 없음, 호출 비용 없음 | 채널 단절 시 누락 위험 |
| 주기 폴링 | 구현 단순, 누락 복구 가능 | API 호출 비용, 분 단위 지연 |
두 채널이 같은 입금을 잡아오는 상황은 멱등성으로 처리하면 되니, 중복 수신 자체는 문제가 안 된다. 실제로 폴링 보조망이 운영 중에 두 번 살렸음. 한 번은 알림이 아예 안 왔고, 한 번은 늦게 왔다. 보조망 없었으면 조용한 누락이 됐을 거다.
외부 시스템에 의존하는 파이프라인을 짤 때 이 조합은 꽤 쓸 만하다. 알림으로 속도를 챙기고, 폴링으로 정확도를 챙기는 방식. 어느 한쪽이 죽어도 다른 쪽이 받쳐준다.
매칭 키 설계에 가장 많은 시간이 들었다
입금 건과 파트너 충전 요청을 묶을 기준이 없는 상태에서 자동 매칭을 한다는 건 사실상 불가능하다. 금액만으로 매칭하면 같은 금액이 여러 건일 때 구분이 안 되고, 시각 기반 매칭은 오차 범위 설정이 까다롭다.
결국 파트너가 송금할 때 메모에 토큰을 끼워 받는 방식으로 갔음. 충전 요청이 들어오면 서버가 토큰을 발급하고, 파트너가 그 토큰을 송금 메모에 넣어서 보내는 구조다.
token = hash(partnerId, requestedAt, salt)[:8]
expireAt = requestedAt + 30m
토큰 설계에서 결정해야 할 게 세 가지였다. 길이, 만료, 예외 처리.
- 길이는 8자리로 정착했음. 너무 짧으면 충돌률이 올라가고, 너무 길면 사람이 메모에 직접 입력할 때 실수가 잦아진다. 8자리면 충돌 확률이 충분히 낮고 가독성도 버틸 만한 수준이다.
- 만료는 30분으로 잘랐다. 토큰을 무한정 열어두면 오래된 미매칭 건이 쌓였다가 나중에 엉뚱하게 매칭되는 사고가 날 수 있음.
- 예외 처리는 두 갈래로 나눴다. 토큰이 없거나 일치하지 않으면 미매칭 큐로 보내고, 만료된 건은 운영툴에서 하루 단위로 수동 해소하는 방식.
"완전 자동화"보다는 "자동 처리 케이스를 최대한 늘리고, 예외는 사람이 확인하게" 하는 방향이 현실적이라고 판단했음. 운영팀이 손대는 건수가 하루 수십 건에서 한 자릿수로 줄어들면 그걸로 충분하다.
매칭 키 논의가 코드 짜는 시간보다 더 길었다. 이상한 일이 아니었음. 정산 시스템에서 "어떤 기준으로 묶을 것인가"는 구현 문제가 아니라 도메인 설계 문제고, 여기서 구멍 나면 나중에 장부 맞추는 게 훨씬 고통스러워진다. 코드는 그 설계를 표현하는 수단이지, 설계를 대신해주지는 않는다.
멱등성은 설계 시점에 결정해야 한다
테스트 중에 같은 입금 알림이 두 번 들어오는 상황을 잡았음. 웹훅 재전송, 폴링과 알림 동시 수신 같은 경로가 있다. 처음엔 단순 삽입으로 짰다가 중복 처리 흔적이 남았다.
세 가지를 정리했음.
- 거래 고유값 기준 unique 제약으로 DB 레벨에서 1차 차단
- 상태 전이 기반으로 처리해서 재시도해도 결과가 같아지게
- 멱등 키 없는 알림은 아예 받지 않도록 정책화
마지막 항목이 좀 강한 정책이긴 한데, 키 없이 들어오면 중복 여부를 판단할 방법이 없다. 운영하다 보면 "이 건 두 번 처리됐나요?"라는 질문이 정산 클레임보다 더 해결하기 까다롭다. 로그 뒤져서 사후 복기하는 것보다 애초에 받지 않는 게 낫다고 봤음.
멱등성은 나중에 덧붙이려고 하면 구조를 꽤 건드려야 하는 경우가 많다. 파이프라인 설계 초반에 "같은 이벤트가 두 번 들어오면 어떻게 될까"를 미리 물어보고 답을 정해두는 게 맞다. 이번엔 테스트에서 잡혔으니 다행이었음. 운영 중에 잡혔으면 수습이 훨씬 번거로웠을 거다.
돌아보면 "수동으로 잘 돌아감"이 진짜 잘 돌아가는 게 아니었다는 걸 이번에도 확인했다. 임계점 전까지는 굴러가다가, 넘으면 한꺼번에 터진다. 외부 알림은 안 올 가능성을 항상 가정해야 하고, 폴링 보조망은 옵션이 아니라 기본값으로 봐야 한다. 그리고 키 설계가 곧 정합성이라는 건, 이 작업에서 가장 오래 붙잡고 있었던 부분이었기 때문에 가장 선명하게 남았음.
댓글 0
첫 댓글 달아줘.