LabHub
배우기 러닝패스 코스

주문은 100건인데 정산은 98건이다 · 주문의 일생 · 이론

주문의 일생 — 승인과 매입은 다른 사건이다

LabHub 에서 이어서 보기

한 줄 요약

커머스의 주문은 한 번에 끝나는 사건이 아니라 주문·승인·매입·출고·배송·구매확정이라는 여섯 개의 서로 다른 사건이고, 그 사건들이 각각 다른 시스템에서 다른 시각에 일어나기 때문에 "주문 건수" 와 "정산 건수" 는 원래 같을 수 없다.

왜 이게 필요했나

장애 보고서의 첫 줄은 대체로 이렇게 시작합니다. "어제 주문은 100건인데 정산 대상은 98건입니다." 이 문장을 받은 엔지니어가 가장 먼저 해야 할 일은 코드를 여는 것이 아니라 되묻는 것입니다 — 100은 무엇을 센 것이고 98은 무엇을 센 것입니까. 앞의 것은 주문서가 만들어진 횟수이고 뒤의 것은 카드사에 실제로 청구된 횟수입니다. 서로 다른 것을 세었으니 다른 것이 당연하고, 문제는 차이가 있다는 사실이 아니라 그 차이를 설명할 수 없다는 것 입니다.

커머스에 처음 온 엔지니어가 가장 늦게 배우는 것이 승인과 매입의 차이입니다. 승인(authorization)은 카드사에 "이 카드로 이 금액을 쓸 수 있습니까" 를 물어 한도를 잡아 두는 일입니다. 이 단계에서 고객의 카드 명세에는 금액이 보이지만 돈은 아직 움직이지 않았습니다. 매입(capture)은 "그 금액을 실제로 청구하겠습니다" 라고 확정하는 일이고, 정산은 매입된 것만 셉니다. 그래서 승인만 나고 매입까지 가지 못한 주문은 고객에게는 결제된 것처럼 보이고 우리 매출에는 없습니다. 두 시스템의 숫자가 어긋나는 가장 흔한 자리가 여기입니다.

어떻게 동작하나

주문 하나를 따라가면 사건이 이렇게 쌓입니다.

PLACED      주문서가 만들어졌다. 아직 돈 이야기는 없다AUTH_OK     카드사가 한도를 잡아 주었다 (실패하면 AUTH_FAIL)CAPTURED    실제로 청구했다. 여기부터 우리 매출이다RELEASED    창고에서 물건이 나갔다. 되돌리려면 물건을 되찾아야 한다DELIVERED   고객이 받았다CONFIRMED   구매가 확정됐다. 판매자에게 돈이 나갈 자격이 생긴다

중요한 것은 이 사건들이 순서대로, 빠짐없이, 한 번씩 들어온다는 보장이 없다는 점입니다. 결제창이 느리면 고객이 결제 단추를 한 번 더 눌러 승인이 두 번 올라갑니다. 배송사 연동이 밀리면 배송완료가 출고보다 먼저 적히기도 합니다. 그래서 현재 상태를 "마지막 사건" 으로 판정하면 틀립니다. 안전한 방법은 그 주문에 무엇이 일어났는가의 집합 을 보고 우선순위표로 상태를 유도하는 것입니다. 상태 컬럼 하나를 덮어쓰며 관리하는 설계가 위험한 이유도 같습니다 — 덮어쓰는 순간 과거를 잃고, 어긋났을 때 되짚을 근거가 사라집니다.

시각도 함정입니다. 자정 직전에 들어온 주문은 주문은 어제, 매입은 오늘이 됩니다. 우리는 주문 시각으로 집계하고 결제대행사는 승인 시각으로 정산하므로, 같은 주문이 두 파일의 다른 날짜에 나타납니다. 그래서 시각은 시간대(offset)까지 포함해 적어 두어야 하고, 그 표기 규칙은 [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) 가 정해 둔 것을 쓰는 편이 안전합니다. 그리고 되돌릴 수 없는 사건을 만드는 요청은 몇 번을 보내도 결과가 같아야 합니다 — 같은 요청을 다시 보내도 부작용이 늘지 않는 성질을 [RFC 9110 이 멱등](https://www.rfc-editor.org/rfc/rfc9110#name-idempotent-methods) 이라고 부르고, 결제 요청에 주문 단위 키를 붙이는 관행이 여기서 나옵니다.

현장에서 만나는 모습

"고객이 취소해 달라고 합니다" 라는 한 문장이 시스템에서는 다섯 갈래입니다. 매입 전이면 잡아 둔 한도만 풀면 되고, 매입 후 출고 전이면 청구를 되돌려 환불해야 하고, 출고 뒤에는 배송을 회수해야 하고, 배송이 끝났으면 반품 절차로 가고, 구매확정 뒤에는 판매자 동의가 필요합니다. 어느 갈래인지는 그 주문이 지금 어디까지 왔는지가 정합니다. 청약철회처럼 소비자에게 보장된 권리의 범위는 법이 정하고 있으므로, 화면 문구를 정할 때는 [전자상거래 등에서의 소비자보호에 관한 법률](https://www.law.go.kr/%EB%B2%95%EB%A0%B9/%EC%A0%84%EC%9E%90%EC%83%81%EA%B1%B0%EB%9E%98%EB%93%B1%EC%97%90%EC%84%9C%EC%9D%98%EC%86%8C%EB%B9%84%EC%9E%90%EB%B3%B4%ED%98%B8%EC%97%90%EA%B4%80%ED%95%9C%EB%B2%95%EB%A5%A0) 의 원문과 사내 법무 검토를 근거로 삼아야 합니다. 개발자가 기억에 의존해 기간을 적어 넣는 것이 가장 흔한 사고입니다.

또 하나 자주 보는 장면은 대시보드끼리 숫자가 다른 일입니다. 마케팅 화면은 주문 건수를, 재무 화면은 매입 건수를, CS 화면은 배송 건수를 세는데 아무도 기준을 적어 두지 않았습니다. 세 숫자가 다른 것은 정상인데, 기준이 없으면 회의에서 누가 틀렸는지를 두고 한 시간을 씁니다. 숫자마다 "무엇을 세는가" 를 한 줄로 적어 두는 것이 이 도메인에서 가장 값싼 방어입니다.

다음 실습에서 할 것

주문 100건의 사건 기록을 직접 만들고, 사건 집합에서 현재 상태를 유도해 봅니다. 단계마다 몇 건이 남는지 세어 100과 98 사이의 두 건이 각각 어떤 사연으로 빠졌는지 확인하고, 자정을 걸친 주문과 승인이 두 번 올라간 주문을 목록으로 뽑아냅니다.