stack-analysis
CHAPTER 75 / 90
읽기 약 2분
FUNCTION
주문 상태 머신: 주문→결제→배송→완료
핵심 개념
state machine·전환 규칙·이벤트·동시성 — 견고한 주문 흐름.
본문
주문 상태 머신
pending (주문 생성)
↓ 결제 성공
paid (결제 완료)
↓ 사장 발송
shipped (배송 중)
↓ 도착 + 자동 7일 또는 사용자 확정
delivered (배송 완료)
↓
completed (구매 확정)
[취소 경로]
pending → cancelled (사용자 취소)
paid → refund_requested → refunded
shipped → return_requested → returning → returned → refundedXState로 모델링
import { createMachine, interpret, assign } from 'xstate';
const orderMachine = createMachine({
id: 'order',
initial: 'pending',
context: {
orderId: '',
paidAt: null,
shippedAt: null,
deliveredAt: null,
},
states: {
pending: {
on: {
PAYMENT_SUCCESS: { target: 'paid', actions: 'recordPaidAt' },
CANCEL: 'cancelled',
EXPIRE: 'cancelled', // 30분 후 자동
},
after: {
1800000: 'cancelled', // 30분
},
},
paid: {
on: {
SHIP: { target: 'shipped', actions: 'recordShippedAt' },
REFUND_REQUEST: 'refund_requested',
},
},
shipped: {
on: {
DELIVER: { target: 'delivered', actions: 'recordDeliveredAt' },
RETURN_REQUEST: 'return_requested',
},
},
delivered: {
on: {
CONFIRM: 'completed',
RETURN_REQUEST: 'return_requested',
},
after: {
604800000: 'completed', // 7일 후 자동 확정
},
},
completed: { type: 'final' },
cancelled: { type: 'final' },
refund_requested: {
on: {
APPROVE: 'refunded',
REJECT: 'paid',
},
},
return_requested: {
on: {
APPROVE: 'returning',
REJECT: 'shipped',
},
},
returning: {
on: {
RECEIVED: 'returned',
},
},
returned: {
on: {
REFUND: 'refunded',
},
},
refunded: { type: 'final' },
},
}, {
actions: {
recordPaidAt: assign({ paidAt: () => new Date() }),
recordShippedAt: assign({ shippedAt: () => new Date() }),
recordDeliveredAt: assign({ deliveredAt: () => new Date() }),
},
});DB로 상태 관리 — 단순화
const ALLOWED_TRANSITIONS: Record<string, string[]> = {
pending: ['paid', 'cancelled'],
paid: ['shipped', 'refund_requested'],
shipped: ['delivered', 'return_requested'],
delivered: ['completed', 'return_requested'],
refund_requested: ['refunded', 'paid'],
return_requested: ['returning', 'shipped'],
returning: ['returned'],
returned: ['refunded'],
// final: completed, cancelled, refunded
};
async function transitionOrder(orderId: string, newStatus: string, actor: { userId?: string; system?: boolean }) {
return db.$transaction(async (tx) => {
const order = await tx.order.findUnique({ where: { id: orderId } });
if (!order) throw new NotFoundError('Order');
const allowed = ALLOWED_TRANSITIONS[order.status] ?? [];
if (!allowed.includes(newStatus)) {
throw new ConflictError(`Invalid transition: ${order.status} → ${newStatus}`);
}
const timestampField = {
paid: 'paidAt',
shipped: 'shippedAt',
delivered: 'deliveredAt',
completed: 'completedAt',
cancelled: 'cancelledAt',
}[newStatus];
const updated = await tx.order.update({
where: { id: orderId },
data: {
status: newStatus,
...(timestampField && { [timestampField]: new Date() }),
},
});
// 이력 기록
await tx.orderStatusHistory.create({
data: {
orderId,
from: order.status,
to: newStatus,
actorId: actor.userId,
system: actor.system ?? false,
},
});
return updated;
});
}자동 전환 (스케줄러)
// 30분 후 미결제 주문 자동 취소
const cancelExpiredScheduler = new Worker('cancel-expired', async () => {
const expired = await db.order.findMany({
where: {
status: 'pending',
createdAt: { lt: new Date(Date.now() - 30 * 60 * 1000) },
},
});
for (const order of expired) {
await transitionOrder(order.id, 'cancelled', { system: true });
// 예약 해제
await db.inventoryReservation.deleteMany({ where: { orderId: order.id } });
}
});
// 배송 완료 7일 후 자동 확정
const autoConfirmScheduler = new Worker('auto-confirm', async () => {
const ready = await db.order.findMany({
where: {
status: 'delivered',
deliveredAt: { lt: new Date(Date.now() - 7 * 86400 * 1000) },
},
});
for (const order of ready) {
await transitionOrder(order.id, 'completed', { system: true });
}
});
// Cron 스케줄
scheduler.upsertJobScheduler('cancel-expired', { pattern: '*/5 * * * *' }, { name: 'check' });
scheduler.upsertJobScheduler('auto-confirm', { pattern: '0 * * * *' }, { name: 'check' });이벤트 기반 알림
// 상태 변경 시 자동 이메일·SMS·푸시
async function transitionOrder(orderId: string, newStatus: string, actor: any) {
// ... 위 로직
// 이벤트 발행
await eventBus.publish(`order.${newStatus}`, { orderId, order: updated });
}
// 구독자
eventBus.subscribe('order.paid', async ({ orderId }) => {
const order = await db.order.findUnique({ where: { id: orderId }, include: { user: true } });
await emailQueue.add('order_paid', { orderId, email: order!.user.email });
});
eventBus.subscribe('order.shipped', async ({ orderId, order }) => {
await smsQueue.add('order_shipped', { phone: order.shippingAddress.phone, trackingNumber: order.trackingNumber });
});다음 챕터
CH.76 "재고 관리 시스템".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 프로젝트의 주문 상태 머신 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
주문 상태 머신 관련 실제 서비스 5개를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 코드베이스에서 주문 상태 머신 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 풀스택 시장의 주문 상태 머신 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
주문 상태 머신: 주문→결제→배송→완료는 이 3가지만 확실히 잡으세요
1.주문 상태 머신 — 명시적 전환 규칙 + 이력 기록
2.XState 또는 단순 ALLOWED_TRANSITIONS 매핑
3.자동 전환 = cron + 이벤트 발행으로 알림 자동
공유하기
진행도 75 / 90