입점 신청하기내 파트너센터 바로가기
개발가이드

주문 연동하기

주문을 수집하고 발주확인·송장 등록·배송완료까지 처리하는 방법이에요.

주문 상품(라인) 상태 흐름

상태는 주문 상품(라인) 단위로 관리돼요. 한 주문에 여러 상품이 있으면 상품마다 다른 상태를 가질 수 있어요. 응답 필드는 item_status예요.

주문 상품 상태 머신 — pending에서 paid·preparing·shipping·delivered를 거쳐 confirmed로 이어지고, paid에서 preparing을 건너뛰고 바로 shipping으로 가는 경로가 있으며, 각 단계에서 canceled·returned·exchanged로 빠지는 흐름
상태설명오픈 API 노출
pending결제 진행 중노출 안 됨
paid결제완료 — 발주확인 대기노출
preparing상품준비중노출
shipping배송중 — 송장 등록됨노출
delivered배송완료노출
confirmed구매확정 — 종결노출
canceled취소 완료 — 종결노출
returned반품 완료 — 종결노출
exchanged교환 완료 — 종결노출
failed결제 실패 — 종결노출 안 됨
expired결제 만료 — 종결노출 안 됨
  • pending·failed·expired는 결제가 완료되지 않은 라인의 상태예요. 오픈 API는 결제 완료 주문만 다루므로 이 세 값은 응답에 나타나지 않아요.
  • 운영 보정은 뷰리티 운영자가 잘못된 처리를 되돌리는 경로예요. API로는 호출할 수 없지만, 증분 수집 중에 상태가 뒤로 가는 것을 관측할 수 있어요. 상태가 단조 증가한다고 가정하지 마세요.
  • 종결 상태(confirmed·canceled·returned·exchanged)에서 나가는 전이는 없어요.
발주확인을 건너뛰고 바로 발송해도 돼요
발주확인(paidpreparing)을 거치는 흐름을 권장하지만, paid 상태에서 바로 송장을 등록해도 돼요(그 경우 발주확인 단계는 건너뛴 것으로 처리돼요). 창고에서 곧바로 출고하는 운영을 위한 경로예요. 다만 라인은 preparing을 거치지 않고 shipping으로 바로 넘어가므로, preparing으로의 전환 이벤트를 기다리는 로직을 두지 마세요.
처리 판단은 item_status로
주문 헤더의 order_status는 별개 값이에요. 주문에 포함된 모든 셀러의 상품 상태에서 계산되고 paid·preparing·shipping·delivered·confirmed·partially_claimed·canceled 중 하나예요. 다른 셀러 상품 때문에 값이 흔들리므로 처리 판단에는 쓰지 마세요.

증분 수집

주문·클레임·문의 목록은 "이 시각 이후에 변경된 것"만 가져오는 방식으로 수집해요.

항목권장값
수집 주기5분
조회 구간 시작(updated_at_gte)마지막 수집 시각보다 10분 앞선 시각
저장 방식식별자 기준 덮어쓰기(upsert)

구간을 겹쳐 조회하는 건 경계에서의 누락을 막기 위해서예요. 같은 건이 두 번 와도 upsert로 저장하면 문제되지 않아요. 중복 없이 정확히 한 번만 받는 것(exactly-once)은 보장하지 않아요.

대량 일괄 변경으로 여러 건이 같은 updated_at을 갖는 경우에도 커서 방식이라 누락·중복 없이 페이지를 넘겨요.

증분 수집 루프 — 5분마다 마지막 수집 시각에서 10분을 뺀 시각으로 주문을 조회하고 next_cursor가 null이 될 때까지 반복하며 upsert하는 흐름

주의할 점

  • 다음 페이지를 요청할 때 필터 조건을 그대로 유지해야 해요. cursor만 바꿔요.
  • 한 회차가 끝나면 커서를 버리고, 다음 회차는 새 updated_at_gte로 커서 없이 다시 시작해요.
  • 클레임(/open/v1/claims)·문의(/open/v1/qna)도 같은 방식이에요. 세 목록을 각각 수집해요.
  • 페이지마다 행 수 한도(분당 3,000행)가 누적돼요. 초기 전체 적재 때는 429를 만날 수 있으니 Retry-After를 지켜 주세요.

조회 축 두 가지

목적이 달라요. 섞어 쓰면 원하는 결과가 나오지 않아요.

파라미터의미용도
변경 시각updated_at_gte / updated_at_lte그 건의 정보가 마지막으로 바뀐 시각정기 증분 수집. 무엇이 바뀌었는지 빠짐없이 따라가기
상태 전환 시각status_changed_to + status_changed_at_gte / status_changed_at_lte지정한 상태로 전환된 이벤트가 그 구간에 있었는지업무 단위 집계. "어제 발송된 주문", "지난주 취소된 주문"
결제 시각paid_at_gte / paid_at_lte결제 완료 시각주문 유입 기준 조회

상태 전환 시각 조회 규칙

status_changed_to와 시각 필터는 함께 보내야 해요. 한쪽만 보내면 422 STATUS_CHANGED_FILTER_INCOMPLETE예요. 허용 조합은 넷이에요.

조합결과
셋 다 미지정통과(상태 전환 필터 미적용)
to + gte통과
to + lte통과
to + gte + lte통과
그 밖(시각만 지정·to만 지정)422
  • gtelte보다 뒤면 422 STATUS_CHANGED_RANGE_INVALID예요.
  • 지정할 수 있는 상태는 8종이에요: paid·preparing·shipping·delivered·confirmed·canceled·returned·exchanged
  • 의미는 "그 상태로 전환된 이벤트가 구간 안에 존재하는 주문"이에요. 같은 상태에 두 번 진입해도 주문은 한 번만 반환돼요.
  • 취소·환불 신청 시각 기준으로 찾고 싶다면 이 축이 아니라 클레임 API의 requested_at_gte/requested_at_lte를 써요. 주문 축의 canceled는 "취소가 완료되어 주문 상품이 취소 상태가 된" 시각이에요.

발주확인부터 배송완료까지

주문 처리 호출 순서 — 주문 수집 후 발주확인, 송장 등록, 배송완료를 차례로 호출하고 구매확정은 조회로만 관측하는 흐름
단계엔드포인트요청 본문주의점
발주확인POST /open/v1/orders/{order_no}/preparationitem_ids[]비어 있으면 422. paid 라인만 대상
송장 등록POST /open/v1/orders/{order_no}/shipmentitem_ids[], carrier_name, tracking_no, logistics_company(선택)택배사는 허용 5종 표기 그대로. paid·preparing 라인 대상(paid면 발주확인을 건너뛴 것으로 처리)
일괄 송장POST /open/v1/orders/batch/shipmentitems[](주문별 entry, 최대 500)부분 성공. HTTP는 항상 200. 대상 상태는 단건과 같아요
배송완료POST /open/v1/orders/{order_no}/deliveryitem_ids[]shipping 라인만 대상

공통 사항

  • 모든 처리 요청에 Idempotency-Key 헤더가 필수예요.
  • item_ids에는 주문 조회 응답의 items[].item_id(UUID)를 넣어요. item_code가 아니에요.
  • 없는 ID·다른 주문의 ID·다른 셀러의 ID·형식이 틀린 값은 모두 같은 404 ORDER_ITEM_NOT_FOUND예요.
  • 이미 목표 상태인 라인은 오류가 아니라 성공이고 응답의 already_donetrue예요.
  • 취소·반품·교환이 진행 중인 라인은 409 ORDER_ITEM_HAS_ACTIVE_CLAIM으로 거절돼요. 클레임을 먼저 처리해 주세요.
구매확정은 호출할 수 없어요
고객이 직접 확정하거나 배송완료 후 14일이 지나면 자동 확정돼요. 정산 기준 시점이라 조회로 관측만 해 주세요.

택배사 표기

아래 5종의 표기를 그대로 보내요. 목록 밖 값은 422 SHIPMENT_CARRIER_NOT_ALLOWED이고, 이 오류는 아무것도 바꾸지 않아요.

CJ대한통운 · 우체국택배 · 한진택배 · 롯데택배 · 로젠택배

일괄 송장 등록의 응답 판정

{
  "meta": { "code": 200, "message": "OK", "error_code": null, "error_data": null },
  "data": {
    "succeeded": ["0f8b7c21-4d3e-4a55-9b16-2c7d8e9f0a1b"],
    "failed": [
      {
        "order_no": "VR20260826-0000009",
        "order_item_public_ids": ["5a1c2d33-8e47-4b90-b6f2-1d0e9a7c4b58"],
        "error_code": "ORDER_ITEM_NOT_SHIPPABLE",
        "message": "발송 처리할 수 없는 상태의 상품입니다."
      }
    ],
    "summary": { "succeeded": 1, "failed": 1 }
  }
}
  • succeeded는 처리에 성공한 주문 상품 고유 ID(UUID) 목록이고, summary의 두 값도 주문 수가 아니라 주문 상품 수예요.
  • failed[].order_item_public_ids는 주문 자체를 찾지 못한 경우 빈 배열이에요.
  • 전건 성공·부분 성공·전건 실패가 모두 HTTP 200이에요. HTTP 상태가 아니라 data.failed[]로 판정해 주세요.
  • entry마다 별도 트랜잭션이라 한 주문의 실패가 다른 주문에 영향을 주지 않아요.

상태별로 채워지는 데이터

필드paidpreparingshippingdeliveredconfirmed
carrier_namenullnull
tracking_nonullnull
logistics_companynullnull값 또는 null좌동좌동
shipped_atnullnull
delivered_atnullnullnull
confirmed_atnullnullnullnull
has_active_claim클레임 진행 중이면 true좌동좌동좌동false

is_exchange는 교환으로 새로 만들어진 상품이면 true예요. 상태와 무관하게 고정된 값이에요.

주문 헤더의 order_no·paid_at·currency·금액 필드는 조회되는 모든 주문에 항상 담기고, 주문자·수취인 정보(orderer_name·recipient_name·주소·ship_memo 등)도 항상 담기지만 회원 탈퇴 후에는 null이에요. updated_at은 주문·라인·송장·클레임이 바뀔 때마다 갱신돼요.

엣지 케이스

부분 클레임으로 주문 상품이 나뉘는 경우

고객이 3개 중 1개만 취소하면 뷰리티는 그 주문 상품을 둘로 나눠요(취소 1개 / 잔여 2개). 새로 만들어진 쪽은 item_id(UUID)를 갖고, 주문 조회 응답에서는 item_code(다음 순번)도 함께 받아요.

식별자성질용도
item_id (UUID)줄마다 하나씩, 그 줄에 고정연동사 시스템의 저장·갱신(upsert) 키. 처리 API의 item_ids에 넣는 값
item_code ({order_no}-{NN})어느 응답에 담겼느냐에 따라 가리키는 줄이 달라져요 — 주문 응답에서는 분할된 줄이 새 라인 번호를 부여받아 다음 순번의 새 item_code를 갖고, 원본 라인 번호를 그대로 유지하는 건 클레임 응답의 item_code이에요원본 주문 라인 단위로 묶어 대사할 때 쓰는 기준. 다만 다리로 삼을 값은 클레임 응답의 item_code예요

같은 라인 -01(수량 3) 중 1개가 취소로 완결되면 응답이 이렇게 갈려요.

응답item_iditem_codequantity비고
주문 조회처음 받은 값 그대로…-012남은 수량 줄(원래 줄)
새 값…-021분리된 canceled 줄 — 코드도 새로 붙어요
클레임 조회분리된 줄의 값…-011item_code원본 라인을 가리켜요
주문 응답만 보고 같은 item_code끼리 합산하지 마세요
분리된 줄은 주문 응답에서 item_code를 받으므로, 주문 응답 안에서 같은 코드끼리 더해도 원래 수량이 복원되지 않아요. 원본 라인 기준 합산은 클레임 응답의 item_code를 다리로 삼아요 — 위 예에서 -01의 남은 수량 2 + item_code-01인 클레임의 신청 수량 1 = 원래 주문 수량 3이에요.
  • 행 단위 저장·중복 판정은 item_id로 해요. 줄마다 고유하고 그 줄에 고정된 값이라 upsert 키로 안전해요.
  • 원본 라인 묶음·대사는 item_code로 하되, 기준은 클레임 응답의 item_code예요. 클레임이 어느 주문 라인에서 갈라져 나왔는지는 이 값으로만 알 수 있어요(주문 응답의 코드는 분할된 줄에 새로 붙어요).
  • 처리 API 호출 직전에는 주문 상세를 다시 조회해 그 시점의 item_id를 써요. 오래된 item_id를 들고 있으면 404 ORDER_ITEM_NOT_FOUND가 나요.
  • 그래서 클레임 응답에서는 item_codeitem_id서로 다른 줄을 가리킬 수 있어요. 코드는 원본 라인, ID는 지금 그 클레임에 묶여 있는 라인이에요.

이미 발송된 주문에 다시 송장을 등록하는 경우

"같은 송장인가"는 세 값이 모두 같은지로 판정해요 — carrier_name·tracking_no·logistics_company. 앞의 두 값은 서버가 정규화한 뒤 비교하고, logistics_company는 보낸 값 그대로 비교해요.

상황결과
세 값이 모두 같음200, already_done: true(재시도로 간주)
택배사 또는 송장번호가 다름409 SHIPMENT_STATE_CONFLICT
택배사·송장번호는 같은데 logistics_company가 다름
(한쪽이 null인 경우 포함)
409 SHIPMENT_STATE_CONFLICT
logistics_company를 빠뜨리면 409예요
선택 항목이라고 재전송 때 생략하면 "값 있음 ↔ null" 차이로 취급돼 409가 나요. 처음 보낸 값을 그대로 다시 보내 주세요. 반대로 처음에 안 보냈다면 재전송 때도 보내지 않아야 해요.

송장 정정은 API로 되지 않아요. 셀러가 파트너센터에서 처리해요. 409를 받으면 주문 조회로 등록된 송장 세 값을 확인해 연동사 시스템 쪽 값을 맞춰 주세요.

혼합 주문

한 주문에 다른 셀러의 상품이 섞여 있으면 items[]에는 요청한 키의 셀러 상품만 담겨요. 금액 필드(seller_로 시작)도 그 상품들만 합산해요. order_status만 다른 셀러 상품까지 반영해 계산되므로, 자기 상품이 전부 shipping인데 order_statuspreparing으로 보일 수 있어요. 정상이에요.

상태가 뒤로 가는 경우

뷰리티 운영자가 잘못된 처리를 되돌리면 shippingpreparing처럼 상태가 역행해요. 증분 수집에서 이를 관측할 수 있으니 상태 전이를 단조 증가로 가정한 로직(예: "이미 delivered면 무시")을 두지 마세요. 수신한 상태를 그대로 반영하는 upsert가 안전해요.