클레임·문의 처리하기
취소·반품·교환의 모든 단계와 예외 상황, 그리고 상품 문의 답변까지 다뤄요.
클레임은 취소(cancel)·반품(return)·교환(exchange) 세 종류이고 상태 그래프가 각각 달라요. 이 문서는 연동 중에 마주칠 수 있는 케이스를 단계별로 모아 정리했어요.
available_actions 배열이 지금 호출할 수 있는 처리를 알려줘요. 서버가 신청 종류와 현재 상태를 보고 계산한 값이니, 상태 문자열로 직접 추론하지 말고 이 배열을 그대로 쓰세요.이 배열은 "지금 새로 실행할 수 있는 처리"를 뜻해요. 빈 배열은 새로 실행할 처리가 없다는 뜻이지 모든 호출이 거절된다는 뜻은 아니에요 — 이미 수행된 처리를 같은 값으로 재전송하는 것은 배열이 비어 있어도 200이에요(재시도 판정 참고).
케이스 빠른 찾기
- 클레임 수집하기 — 목록 필터와 증분 수집
- 취소 (cancel) · 반품 (return) · 교환 (exchange) — 상태 그래프와 호출 가능한 처리
- 처리 호출 순서와 요청 본문
- 관측 전용 상태 — 연동사가 호출할 게 없는 상태들
- 검수 분기 —
pass·fail의 후속, 검수 없이 환불로 직행하는 경우 - 환불 진행 — 취소는 승인 즉시·반품은 검수 통과 시 자동 실행,
refund_processing이 길어질 때 - 금액 필드 읽는 법 — 환불액·클레임 배송비·교환 차액
- 동시 처리 충돌 — 파트너센터와 API가 같은 클레임을 건드릴 때
- 재시도와 409
IDEMPOTENCY_UNRESOLVED - 교환 재발송 상품
- 부분 클레임과 라인 분할
- 클레임 응답에서 채워지는 데이터
- 상품 문의(QnA) 처리
클레임 수집하기
GET /open/v1/claims로 수집해요(claims:read 스코프). 페이지 넘김은 주문과 같은 커서 방식이에요.
| 파라미터 | 용도 |
|---|---|
type | cancel·return·exchange 중 하나로 종류를 걸러요 |
status | 진행 상태가 정확히 그 값인 건만 가져와요. 처리 대기 건만 뽑으려면 requested, 검수 대기 건만 뽑으려면 collected |
requested_at_gte / requested_at_lte | 신청 시각 기준 조회. "어제 접수된 반품" 같은 업무 단위 집계에 써요 |
updated_at_gte / updated_at_lte | 변경 시각 기준 조회. 정기 증분 수집의 기준이에요 |
증분 수집 주기·겹침 구간은 주문과 동일해요 — 주문 연동하기의 증분 수집 절을 참고해 주세요.
취소 (cancel)
| 상태 | 설명 | available_actions |
|---|---|---|
requested | 취소 신청 접수 | approve(승인), reject(거부) |
| 그 외 전부 | 없음 |
취소는 수거·검수 단계가 없어요. 승인(approve)하면 그 자리에서 환불이 걸리고 refund_processing → completed로 이어져요. 그 구간은 뷰리티가 진행해요. 환불이 곧바로 끝나면 approve 응답의 status가 이미 completed일 수도 있어요.
세 종류 중 승인 시점에 환불이 걸리는 건 취소뿐이에요. 반품·교환은 환불 진행의 타입별 표를 봐 주세요.
취소를 신청할 수 있는 주문 상품 상태는 paid·preparing이에요. 이미 발송된 건은 취소가 아니라 반품으로 들어와요.
반품 (return)
| 상태 | 설명 | available_actions | 엔드포인트 |
|---|---|---|---|
requested | 반품 신청 접수 | approve(승인), reject(거부) | /approve · /reject |
approved | 승인됨 — 수거 준비 | pickup(수거 송장 등록) | /pickup |
collecting | 수거 중 | collect-done(수거 완료 처리) | /collect-done |
collected | 수거 완료 — 검수 대기 | inspect(검수 결과 등록) | /inspect |
inspecting | 검수 중 | reject(검수 불합격 거부) | /reject |
| 그 외 | 없음 |
반품·교환은 배송완료(delivered)된 주문 상품에만 신청할 수 있어요.
교환 (exchange)
| 상태 | 설명 | available_actions | 엔드포인트 |
|---|---|---|---|
requested | 교환 신청 접수 | approve(승인), reject(거부) | /approve · /reject |
approved | 승인됨 — 수거 준비 | pickup(수거 송장 등록) | /pickup |
collecting | 수거 중 | collect-done(수거 완료 처리) | /collect-done |
collected | 수거 완료 — 검수 대기 | inspect(검수 결과 등록) | /inspect |
inspecting | 검수 중 | reject(검수 불합격 거부) | /reject |
reshipping | 재출고 대기 — 검수 통과 | reship(재발송 송장 등록) | /reship |
| 그 외 | 없음 |
교환은 requested부터 reshipping까지 어느 단계에서든 converted_to_return(반품 전환)으로 빠질 수 있어요. 아래 관측 전용 상태에서 자세히 다뤄요.
처리 호출 순서와 요청 본문
모든 처리 요청에 Idempotency-Key 헤더가 필수예요. 경로는 POST /open/v1/claims/{claim_no}/{액션} 형태이고, {액션}은 available_actions의 값과 그대로 같아요.
| 액션 | 본문 | 결과 |
|---|---|---|
approve | 없음 | 취소는 refund_processing(환불 시작)으로, 반품·교환은 approved로 — 반품·교환은 승인만으로 환불도 수거도 시작되지 않아요. 수거를 시작하려면 이어서 pickup을 호출해야 해요 |
reject | memo(거부 사유, 필수 · 1자 이상) | rejected로 종결 |
pickup | carrier_name, tracking_no(수거 송장) | collecting으로 |
collect-done | 없음 | collected로 |
inspect | result(pass 또는 fail), memo | 아래 검수 분기 참고 |
reship | carrier_name, tracking_no(재발송 송장) | 교환이 completed로 |
- 택배사명은 주문 발송과 같은 허용 5종 표기를 그대로 보내요: CJ대한통운 · 우체국택배 · 한진택배 · 롯데택배 · 로젠택배. 목록 밖 값은 422
SHIPMENT_CARRIER_NOT_ALLOWED예요. reject를 이미 거부된 신청에 다시 호출하면 사유 문구가 달라도 성공으로 응답해요. 저장되는 사유는 처음 성공한 요청의 값이고 재시도로 덮어써지지 않아요.inspect의memo는result가fail일 때 필수예요. 비워 보내면 400으로 거절돼요.
관측 전용 상태
아래 상태에서는 연동사가 호출할 처리가 없어요(available_actions가 빈 배열이에요). 수집해서 status를 그대로 반영하기만 하면 돼요.
| 상태 | 설명 | 다음에 올 수 있는 상태 | 연동사가 할 일 |
|---|---|---|---|
payment_pending | 고객이 부담할 금액(반품·교환 배송비, 교환 차액)이 환불액보다 커서 부족분을 먼저 결제해야 하는 상태예요. 반품·교환에만 있어요 | requested(결제 완료) · expired · withdrawn | 없음 — requested가 될 때까지 대기 |
withdrawn | 고객이 신청을 철회했어요. approved까지의 단계에서 발생할 수 있어요 | 없음(종결) | 없음 — 진행 중이던 수거가 있으면 중단 |
expired | 부족분 결제 기한이 지나 신청이 자동으로 만료됐어요 | 없음(종결) | 없음 |
converted_to_return | 교환이 반품으로 전환됐어요. 고객·운영자 요청으로만 발생하고 API로는 만들 수 없어요 | 없음(종결) | 없음 — 별도의 반품 클레임이 새로 조회되므로 그쪽을 처리 |
refund_processing | 환불이 진행 중이에요. 가상계좌 건은 고객의 환불계좌 입력을 기다리는 구간도 여기 포함돼요 | completed | 없음 — 환불 진행 참고 |
completed | 환불·교환이 끝났어요 | 없음(종결) | 없음 — 정산·재고 대사 반영 |
rejected | 거부로 종결됐어요. 명시적 거부(reject)와 검수 불합격 둘 다 이 상태예요 | 없음(종결) | 없음 — 어느 쪽인지는 inspect_result로 구분 |
completed·rejected·withdrawn·converted_to_return·expired 다섯은 종결이에요. 새로 실행할 수 있는 처리가 없으니 available_actions도 빈 배열이에요.다만 종결 상태라고 해서 모든 호출이 409는 아니에요 — 과거에 실제로 수행된 처리를 같은 값으로 다시 보내면 종결 상태에서도 200이에요(재시도로 간주해 아무것도 바꾸지 않고 현재 상태를 돌려줘요). 판정 규칙은 재시도와 409
IDEMPOTENCY_UNRESOLVED가 정본이에요.검수 분기
inspect의 result에 따라 이후 흐름이 완전히 갈려요.
| 종류 | result | 전이 | 이후 |
|---|---|---|---|
| 반품 | pass | collected → inspecting → refund_processing 또는 completed | 이 시점에 환불이 걸려요. 환불이 그 자리에서 끝나면 inspect 응답의 status가 이미 completed이고, 아니면 refund_processing으로 왔다가 completed가 돼요 |
fail | collected → inspecting → rejected | 신청이 거부로 종결돼요. 회수한 상품을 고객에게 돌려보내는 절차는 파트너센터 안내를 따라 주세요 | |
| 교환 | pass | collected → inspecting → reshipping | 재발송 송장을 /reship으로 등록하면 completed가 돼요. 돌려줄 차액이 있으면 이 시점에 차액 환불도 함께 걸려요(refund_processing은 거치지 않아요) |
fail | collected → inspecting → rejected | 반품과 같아요. 고객이 선결제한 부족분이 있으면 함께 정리돼요 |
fail일 때memo(불합격 사유)는 필수예요.inspect는collected에서만 호출할 수 있어요. 이미inspecting인 클레임에 다시 호출하면 409예요.inspecting에서 남는 선택지는reject뿐이에요.- 검수를 생략하고
collected에서 곧바로refund_processing으로 가는 경우도 있어요 — 수거완료 후 일정 기간 검수가 없으면 검수 통과로 간주해 뷰리티가 환불을 걸어요. API로 만들 수는 없지만 수집 중에 관측될 수 있으니 그 전이를 오류로 다루지 마세요.
환불 진행
별도의 환불 실행 호출은 없어요. 다만 환불이 시작되는 시점은 클레임 종류마다 달라요 — "승인하면 자동"은 취소에만 해당해요.
| 타입 | 환불이 시작되는 시점 | 비고 |
|---|---|---|
| 취소 ( cancel) | approve 승인 시 | 수거·검수가 없어요. 승인 즉시 refund_processing으로 넘어가요 |
| 반품 ( return) | inspect 검수 통과 시(result=pass) | approve는 requested → approved까지만 옮겨요 — 환불도, 수거도 시작되지 않아요. 수거는 pickup(수거 송장 등록)을 호출해야 비로소 시작돼요(approved → collecting). 승인 뒤 pickup을 빠뜨리면 클레임이 approved에 멈춰 있어요. 수거완료 뒤 일정 기간 검수가 없으면 뷰리티가 검수 통과로 간주해 환불을 걸기도 해요(collected → refund_processing 직행) |
| 교환 ( exchange) | 검수 통과 시 재출고(reshipping)로 가고, 환불은 고객에게 돌려줄 차액이 있을 때만 그 시점에 걸려요 | 교환은 refund_processing을 거치지 않아요. 차액은 price_difference가 음수일 때 생겨요(양수는 고객이 추가 결제한 금액이라 환불이 없어요) |
진행 상황은 클레임 상세의 status로 확인하면 돼요. 취소·반품은 refund_processing → completed로 이어지고, 처리 응답을 받는 순간 이미 completed일 수도 있어요.
refund_processing이 예상보다 길어지는 경우가 있는데 대부분 아래 두 가지예요. 어느 쪽이든 연동사가 따로 할 일은 없어요.
- 가상계좌·무통장 결제 건 — 고객이 환불받을 계좌를 입력해야 이체가 진행돼요. 입력은 고객이 앱에서 직접 해요.
- PG 환불이 실패한 건 — 뷰리티가 확인해 수동으로 처리해요.
금액 필드 읽는 법
| 필드 | 설명 | 언제 채워지나 |
|---|---|---|
refund_amount | 고객에게 환불되는 금액(원). 배송비 부담분까지 반영한 최종 금액이에요 | 금액이 확정된 뒤. 확정 전이거나 환불이 없는 신청이면 null |
claim_shipping_fee | 이 신청에 부과된 반품·교환 배송비(원) | 산정 후. fault가 buyer면 환불액에서 차감되고, seller면 0이에요 |
price_difference | 교환 시 발생한 상품 가격 차액(원). 양수면 고객이 추가 결제한 금액, 음수면 고객에게 돌려주는 금액이에요 | 교환에서 산정 후. 취소·반품은 0 |
fault | 귀책 주체. buyer(고객 사유 — 배송비 고객 부담) 또는 seller(판매자 사유 — 배송비 판매자 부담) | 신청 사유에서 자동으로 결정돼요 |
reason_code | 신청 사유. change_of_mind(단순변심) · defective(하자·파손) · wrong_delivery(오배송) · out_of_stock(품절) · delayed_by_seller(발송지연) · etc | 신청 시점부터 |
payment_pending으로 생성돼요. 고객이 부족분을 결제해 requested가 되기 전까지는 available_actions가 비어 있어요 — 승인·거부도 그때부터 가능해요.동시 처리 충돌
같은 클레임을 파트너센터(사람)와 API(연동 시스템)가 동시에 처리할 수 있어요. 셀러가 파트너센터에서 먼저 승인해 버린 뒤 연동 시스템이 approve를 보내는 상황이 대표적이에요.
| 상황 | 응답 |
|---|---|
| 같은 처리를 같은 값으로 보냄(이미 수행된 처리) | 200 — 아무것도 바꾸지 않고 현재 상태를 돌려줘요. 클레임이 이미 다음 단계로 갔거나 종결됐어도 마찬가지예요 |
| 같은 처리를 다른 값으로 보냄(다른 송장·다른 검수 결과) | 409 IDEMPOTENCY_UNRESOLVED |
| 수행된 적 없는 처리를 보냄(건너뛴 단계·해당 없는 액션) | 409 IDEMPOTENCY_UNRESOLVED |
누가 처리했는지는 판정에 쓰지 않아요. 파트너센터에서 사람이 대신 승인했어도 연동사가 원한 처리가 수행됐다면 200이에요.
available_actions를 확인하고, 그 배열에 있는 액션만 보내세요. 수집 시점의 상태를 그대로 믿고 호출하면 그 사이 파트너센터에서 처리된 건이 409로 돌아와요.재시도와 409 IDEMPOTENCY_UNRESOLVED
클레임 처리는 주문 전이와 달리 "한 방향으로 계속 전진"해요. 그래서 서버는 세 갈래로 판정해요.
- 지금 상태가 그 액션의 시작 상태그대로 실행해요.
- 이미 그 액션이 수행된 증거가 있음아무것도 바꾸지 않고 현재 상태로 200을 돌려줘요. 상태가 이미 종결이어도 마찬가지예요.
- 둘 다 아님409
IDEMPOTENCY_UNRESOLVED예요.
3번은 "지금 상태에서는 그 처리를 확정할 수 없다"는 뜻이에요. 서버가 추측으로 재실행하면 이중 환불이 될 수 있어 거절해요. 클레임 상세를 조회해 현재 상태와 available_actions를 확인한 뒤 판단해 주세요.
값이 있는 액션(pickup·inspect·reship)은 값까지 같아야 2번으로 접혀요. 같은 액션을 다른 송장번호·다른 검수 결과로 다시 보내면 409예요.
판정은 현재 상태가 아니라 "그 처리가 실제로 수행됐는가"를 봐요. 그래서 같은 종결 상태라도 어느 액션을 보냈는지에 따라 답이 갈려요.
| 현재 상태 | 보낸 처리 | 응답 |
|---|---|---|
completed(반품 환불 완료) | approve | 200 — 그 클레임은 실제로 승인을 거쳤어요(2번) |
pickup을 다른 송장으로 | 409 — 저장된 값과 달라요(3번) | |
rejected | reject(사유가 달라도) | 200 — 명시적 거부를 거친 건이면 재시도로 접혀요. 저장되는 사유는 처음 성공한 값이에요 |
검수 불합격으로 rejected가 된 건에 reject | 409 — 그 건은 reject 처리를 거친 적이 없어요 | |
withdrawn·expired(고객 철회·기한 만료) | 승인 전에 끝난 건에 approve | 409 — 그 건은 승인을 거친 적이 없어요 |
누가 처리했는지는 판정에 쓰지 않아요 — 파트너센터에서 사람이 대신 승인했어도 원하는 처리가 수행됐다면 200이에요.
교환 재발송 상품
교환이 승인되어 재출고 단계에 오면 새 주문 상품이 생성돼요(is_exchange: true). 이 상품은 일반 주문 상품과 처리 조건이 달라요.
| 시도 | 결과 |
|---|---|
교환 신청이 reshipping이 되기 전에 발주확인 | 409 EXCHANGE_ITEM_NOT_PREPARABLE |
교환 신청이 reshipping이 아닐 때 송장 등록 | 409 EXCHANGE_ITEM_NOT_RESHIPPING |
교환 신청이 reshipping일 때 POST /open/v1/claims/{claim_no}/reship | 정상. 클레임이 completed가 되고 새 상품이 shipping으로 전환 |
/reship)로 등록해요. 주문 쪽 송장 등록으로는 교환 클레임이 종결되지 않아요.reship 호출 후 409 EXCHANGE_ITEM_SHIP_FAILED를 받으면 재출고는 등록됐지만 새 상품의 출고 처리가 확정되지 않은 상태예요. 클레임 상세로 현재 상태를 확인한 뒤 다시 시도해 주세요.
부분 클레임과 라인 분할
한 클레임에 여러 상품이 담길 수 있고, 한 상품의 수량 일부만 신청될 수도 있어요. 클레임 응답의 items[]에는 신청 대상이 된 상품과 quantity(신청 수량)가 담겨요.
부분 클레임이 완결되면 원래 주문 상품 1줄이 「신청 수량」 줄과 「남은 수량」 줄로 나뉘어요. 신청 수량 쪽은 새 item_id를 가진 종료 상태 줄로 분리되고, 남은 수량은 원래 줄에 그대로 남아요. 주문 조회 응답에서도 두 줄이 각각 별도 항목으로 나타나요.
| 식별자 | 성질 | 용도 |
|---|---|---|
item_id (UUID) | 줄마다 하나씩, 그 줄에 고정 | 연동사 시스템의 저장·갱신(upsert) 키. 처리 API의 item_ids에 넣는 값 |
item_code ({order_no}-{NN}) | 어느 응답에 담겼느냐에 따라 가리키는 줄이 달라져요 — 주문 응답에서는 분할된 줄이 새 라인 번호를 부여받아 다음 순번의 새 item_code를 갖고, 원본 라인 번호를 그대로 유지하는 건 클레임 응답의 item_code뿐이에요 | 원본 주문 라인 단위로 묶어 대사할 때 쓰는 기준. 다만 다리로 삼을 값은 클레임 응답의 item_code예요 |
item_code(다음 순번)를 받아요. 반면 클레임 조회 응답의 item_code는 언제나 원본 라인 번호를 가리켜요(item_id는 분리된 줄을 가리키므로 두 값이 서로 다른 줄을 말할 수 있어요). 저장 키로는 item_id를, 원본 라인 묶음용으로는 item_code를 쓰세요.권장 처리
- 행 단위 저장·중복 판정은
item_id로 해요. 줄마다 고유하고 그 줄에 고정된 값이라 upsert 키로 안전해요. - 원본 라인 묶음·대사는
item_code로 해요. 라인-01(수량 3) 중 1개가 취소되면 주문 응답의-01남은 수량 2 +item_code가-01인 클레임의 신청 수량 1 = 원래 수량 3이에요. 주문 응답 안에서만 같은 코드끼리 더하면 분리된 줄이 새 코드를 받아 빠지니 주의해 주세요. - 처리 API 호출 직전에는 주문 상세를 다시 조회해 그 시점의
item_id를 써요. 오래된item_id를 들고 있으면 404ORDER_ITEM_NOT_FOUND가 나요. - 클레임이 진행 중인 라인은 주문 처리 API가 409
ORDER_ITEM_HAS_ACTIVE_CLAIM으로 거절해요. 주문 조회의has_active_claim으로 미리 걸러내면 불필요한 호출을 줄일 수 있어요.
클레임 응답에서 채워지는 데이터
| 필드 | 채워지는 시점 |
|---|---|
claim_no·order_no·type·status·requested_at·items[] | 항상 |
available_actions | 항상(호출 가능한 처리가 없으면 빈 배열) |
fault·reason_code | 신청 시점부터 |
refund_amount·claim_shipping_fee·price_difference | 산정된 뒤. 산정 전이면 null |
collect_carrier_name·collect_tracking_no | pickup 등록 후 |
inspect_result | inspect 등록 후 |
reship_carrier_name·reship_tracking_no | reship 등록 후 |
completed_at | 종결(completed) 시점 |
items[]의 각 항목에는 item_code·item_id·quantity·product_name·option_name이 담기고, 교환이면 고객이 원하는 옵션이 exchange_option_name에 담겨요. 클레임 응답에는 수취인·주소 같은 개인정보가 들어가지 않아요.
상품 문의(QnA) 처리
수집
GET /open/v1/qna로 자기 상품에 달린 문의를 수집해요(qna:read 스코프). 필터는 status와 updated_at_gte/updated_at_lte이고, 페이지 넘김은 주문·클레임과 같은 커서 방식이에요. 증분 수집 주기도 동일하게 잡으면 돼요.
비밀글로 등록된 문의도 자기 상품에 달린 것이라면 원문을 볼 수 있어요(파트너센터와 같은 기준). 다만 반출 기록이 문의 단위로 남으니 필요 이상으로 반복 수집하지 말아 주세요.
답변 등록
POST /open/v1/qna/{qna_id}/answer로 답변을 등록해요(qna:write 스코프, Idempotency-Key 필수). qna_id는 문의 조회 응답의 고유 ID(UUID)예요.
QNA_ALREADY_ANSWERED예요. 같은 내용을 다시 보내는 건 재시도로 보고 성공 처리되지만, 내용 수정은 파트너센터에서만 할 수 있어요.