본문으로 건너뛰기

주문 상태

주문 상태는 고객의 주문이 현재 어떤 단계에 있는지 표현합니다. 하지만 유통 시스템에서는 주문 상태만으로 모든 업무를 표현할 수 없습니다. 결제, 출고, 배송, 정산은 각각 다른 시스템에서 다른 속도로 진행되기 때문입니다.

주의사항

주문 테이블의 status 하나에 결제, 출고, 배송, 정산 상태를 모두 넣으면 운영이 조금만 복잡해져도 상태 조합이 꼬입니다. 상태는 업무 책임 단위로 분리하는 것이 안전합니다.

주문 상태의 목적

주문 상태는 화면 표시용 텍스트가 아니라 다음을 결정하는 기준입니다.

  • 고객 취소 가능 여부
  • 재고 예약 가능 여부
  • WMS 출고 요청 가능 여부
  • 배송지 변경 가능 여부
  • 반품 또는 환불 흐름 진입 여부
  • 정산 후보 생성 가능 여부

주문 상태와 다른 상태의 차이

구분대표 상태소유 시스템의미
주문 상태PAID, RESERVED, CANCELEDOMS고객 주문의 업무 단계
결제 상태AUTHORIZED, CAPTURED, REFUNDEDPG/OMS돈의 승인/취소 단계
출고 상태PICKING, PACKED, CONFIRMEDWMS창고 작업 단계
배송 상태IN_TRANSIT, DELIVEREDTMS/택배사운송 단계
정산 상태PENDING, CONFIRMED, CLOSEDERP금액 확정 단계

하나의 상태 컬럼에 몰아넣으면 생기는 문제

예를 들어 order.status = COMPLETE라고 저장하면 무엇이 완료됐는지 알기 어렵습니다. 출고가 완료된 것인지, 배송이 완료된 것인지, 매출 정산이 끝난 것인지 구분이 되지 않습니다. 운영자는 "완료인데 왜 환불이 안 되나요?" 같은 질문을 하게 되고, 개발자는 다시 WMS, TMS, ERP 로그를 뒤져야 합니다.

상태 전이 가능/불가능 규칙

상태 전이는 서버에서 검증해야 합니다.

현재 상태허용 액션다음 상태
RECEIVED결제 대기 전환PAYMENT_WAITING
PAYMENT_WAITING결제 완료PAID
PAID재고 예약RESERVED
RESERVED출고 요청OUTBOUND_REQUESTED
OUTBOUND_REQUESTEDWMS 작업 시작OUTBOUND_IN_PROGRESS
OUTBOUND_IN_PROGRESS출고 완료OUTBOUND_COMPLETED
OUTBOUND_COMPLETED배송 시작SHIPPING
SHIPPING배송 완료DELIVERED

주문 상태 전이도

상태 되돌리기가 어려운 케이스

다음 상태 이후에는 단순 되돌리기보다 별도 보정 거래가 필요합니다.

  • 출고확정 이후: 재고 수불이 생성되었기 때문
  • 송장 생성 이후: 택배사 데이터가 만들어졌기 때문
  • 배송완료 이후: 고객 수령 상태가 외부에서 확정되었기 때문
  • 정산마감 이후: 금액이 회계/정산 기준으로 잠겼기 때문

상태 이력 테이블이 필요한 이유

현재 상태만 있으면 "왜 이 상태가 되었는지"를 알 수 없습니다. 상태 이력은 장애 분석, 고객 응대, 운영 책임 추적에 필요합니다.

컬럼설명
entity_typeORDER, OUTBOUND, DELIVERY
entity_id대상 ID
from_status이전 상태
to_status변경 상태
action상태 변경 액션
reason_code품절, 고객취소, 시스템처리 등
memo운영자 메모
changed_by처리자
changed_at처리 시각

상태 전이 검증 로직 예시

const allowedTransitions = {
PAID: ['RESERVED', 'CANCEL_REQUESTED'],
RESERVED: ['OUTBOUND_REQUESTED', 'CANCEL_REQUESTED'],
OUTBOUND_REQUESTED: ['OUTBOUND_IN_PROGRESS', 'CANCEL_REQUESTED'],
OUTBOUND_IN_PROGRESS: ['OUTBOUND_COMPLETED', 'PARTIALLY_CANCELED'],
DELIVERED: ['RETURN_REQUESTED'],
} as const;

function assertTransition(current: string, next: string) {
const allowed = allowedTransitions[current] ?? [];
if (!allowed.includes(next as never)) {
throw new Error(`Invalid transition: ${current} -> ${next}`);
}
}

개발자가 주의해야 할 점

  • 상태 변경 API는 PATCH status보다 업무 액션 API로 설계합니다.
  • 화면에서 버튼을 숨겨도 서버에서 상태 전이를 다시 검증합니다.
  • 취소, 반품, 환불은 주문 상태만 바꾸지 말고 재고와 정산 영향을 함께 봅니다.
  • 상태 이력은 운영자에게 보이는 언어로 사유를 남깁니다.
  • 외부 연동 실패는 주문 상태와 별도의 연동 상태로 관리합니다.

관련 문서