배송비와 출고지·반품지
배송 묶음, 무료배송 기준, 제주·도서산간 추가 배송비, 출고지·반품지 주소록
배송비 규칙을 정하고 출고지·반품지를 등록해 상품마다 지정합니다. 배송비는 배송 묶음마다 한 번 붙고, 제주·도서산간 추가 배송비는 배송지를 받은 뒤 확정됩니다.
준비
- 배송비 규칙: 소유자·관리자 또는
store:rw - 출고지·반품지 주소록 조회: 소유자·관리자 또는
store:r(액세스 토큰·연동 앱·AI 에이전트 포함) - 출고지·반품지 주소록 등록·수정·삭제·기본 지정: 상점 플랫폼으로 로그인한 소유자·관리자만(액세스 토큰·연동 앱·AI 에이전트 불가)
- 상품에 배송 정책·주소 지정: 소유자·관리자·스태프 또는
product:rw
1. 상점 배송비 규칙
상점 플랫폼에서 설정 › 배송으로 이동합니다(소유자·관리자).
- 배송비 규칙 카드에 아래 네 값을 입력합니다. 전체 무료배송은 스위치를 켜고 기준액을 넣습니다.
- **[저장]**을 누릅니다. 바꾼 값은 새 주문서부터 적용됩니다.
| 항목 (API 필드) | 뜻 | 기본값 |
|---|---|---|
기본 배송비 (baseDeliveryFee) | 상품 등록에서 배송비를 비우면 쓰는 값 | 3000 |
전체 무료배송 기준액 (freeThreshold) | 주문 상품 금액 합이 이 값 이상이면 모든 묶음의 기본 배송비가 0. null이면 끔 | null |
제주 추가 배송비 (jejuSurcharge) | 묶음마다 더함 | 0 |
도서산간 추가 배송비 (isolatedSurcharge) | 묶음마다 더함 | 0 |
API: PATCH /v1/store의 shippingPolicy(준 항목만 바뀜, 모르는 항목은 400). 현재 값은 GET /v1/store로 전부 옵니다.
{ "shippingPolicy": { "baseDeliveryFee": 3500, "freeThreshold": 50000, "jejuSurcharge": 3000, "isolatedSurcharge": 5000 } }2. 배송 묶음
같은 출고지 + 같은 배송 정책(배송비 유형·배송비·무료배송 기준금액)인 상품은 한 묶음이고 배송비가 한 번 붙습니다.
- 출고지가 다르거나 배송비가 다르면 다른 묶음입니다.
- 출고지를 비운 상품은 상점 기본 출고지로 봅니다. 기본 출고지를 지정한 상품과 한 묶음입니다.
- 무료배송 상품끼리는 한 묶음이고 배송비는 0입니다.
- 주문서의
delivery.bundleCount가 배송비를 부과한 횟수입니다. 묶음은 주문 시점에 고정됩니다. - 배송 없는 상품(
MANUAL)은 묶음에 들지 않고 배송비가 없습니다(비실물 상품 판매하기).
3. 무료배송 기준 두 가지
| 기준 | 정하는 곳 | 비교 금액 |
|---|---|---|
| 상품별 조건부 무료 | 상품의 CONDITIONAL_FREE + conditionalFreeAmount | 그 묶음의 상품 금액 합 |
| 상점 전체 무료 | shippingPolicy.freeThreshold | 배송 상품 금액 합(배송이 없는 상품 제외) |
계산 순서입니다.
- 묶음마다 소계를 조건부 무료 기준과 비교합니다.
- 배송 상품 금액 합이 전체 기준을 넘으면 모든 묶음의 기본 배송비를 0으로 만듭니다(
delivery.freeByThreshold: true). - 제주·도서산간 추가 배송비를 묶음마다 더합니다.
4. 제주·도서산간 추가 배송비
배송지 우편번호가 아래 범위면 추가 배송비가 묶음마다 붙습니다. 무료배송이어도 부과합니다. 범위는 플랫폼이 고정하고 금액만 상점이 정합니다. 설정 › 배송의 추가 배송비 지역 카드에서도 봅니다.
| 구분 | 지역 | 우편번호 |
|---|---|---|
JEJU | 제주특별자치도 | 63000–63644 |
ISOLATED | 인천 영종구 무의동 | 22386–22388 |
ISOLATED | 인천 강화군 도서 | 23004–23010 |
ISOLATED | 인천 옹진군 도서 | 23100–23116, 23124–23136 |
ISOLATED | 충남 당진시 · 태안군 · 보령시 도서 | 31708 · 32133 · 33411 |
ISOLATED | 경북 울릉군 | 40200–40240 |
ISOLATED | 부산 강서구 도서 | 46768–46771 |
ISOLATED | 경남 사천시 도서 | 52570–52571 |
ISOLATED | 경남 통영시 도서 | 53031–53033, 53089–53104 |
ISOLATED | 전북 부안군 위도면 | 56347–56349 |
ISOLATED | 전남 영광군 낙월면 | 57068–57069 |
ISOLATED | 전남 목포시 도서 | 58760–58762 |
ISOLATED | 전남 신안군 | 58800–58810, 58816–58818, 58826, 58828–58866 |
ISOLATED | 전남 진도군 조도면 | 58953–58958 |
ISOLATED | 전남 완도군 도서 | 59102–59103, 59106, 59127, 59129, 59137–59166 |
ISOLATED | 전남 보성군 장도 | 59421 |
ISOLATED | 전남 고흥군 도서 | 59531, 59551, 59563, 59568 |
ISOLATED | 전남 여수시 도서 | 59650, 59766, 59781–59790 |
우편번호가 숫자 5자리가 아니거나 범위 밖이면 추가 배송비를 받지 않습니다. 범위는 택배사 할증 구간을 따르고, 연륙교로 이어진 섬(강화 본섬·진도 본섬·영흥면 등)은 대상이 아닙니다.
5. 배송지를 받아 금액을 확정하기
주문서를 만들 때는 배송지를 몰라 추가 배송비가 빠져 있습니다(delivery.zipCode: null, remoteSurcharge: 0). 장바구니 요약도 같습니다.
- 주문서 생성 —
sdk.checkout.create(). 묶음별 기본 배송비까지 반영됩니다. - 미리보기 — 배송지를 입력하거나 고칠 때
sdk.checkout.quoteDelivery(). 주문서를 바꾸지 않습니다. - 결제 시작 —
POST /storefront/v1/checkout/{checkoutId}/payment가 배송지로 다시 계산해 그 금액으로 결제합니다. 응답amounts가 확정 금액입니다.
const quote = await sdk.checkout.quoteDelivery(checkout.checkoutId, { zipCode: "63000" });
// quote.amounts: { productAmount: 40000, deliveryFee: 6000, totalAmount: 46000 }
const start = await sdk.checkout.startPayment(checkout.checkoutId, { option, returnUrl, shippingAddress });
// start.amounts.totalAmount === 46000미리보기를 건너뛰면 결제창 금액이 갑자기 늘 수 있습니다. payments.start()로 결제창까지 한 번에 열면 확정 금액이 반환되지 않으니 미리보기로 금액을 보여 주십시오(결제 연동).
delivery 필드 | 뜻 |
|---|---|
baseFee | 묶음별 기본 배송비의 합 |
remoteSurcharge | 지역 추가 배송비 합. 배송지를 모르면 0 |
remoteArea · remoteAreaLabel | NONE·JEJU·ISOLATED와 안내 이름(해당 없으면 null) |
bundleCount | 묶음 수(배송비 부과 횟수) |
freeByThreshold | 전체 무료배송 기준으로 면제했는지 |
zipCode | 반영한 우편번호. null이면 배송지 미반영 |
| 미리보기 오류 | 의미 |
|---|---|
400 VALIDATION_FAILED | 우편번호가 숫자 5자리가 아님 |
404 CHECKOUT_NOT_FOUND | 주문서 없음 |
409 CHECKOUT_EXPIRED | 주문서 만료(30분). 다시 만듭니다 |
409 ALREADY_PAID | 이미 결제된 주문서 |
6. 상품의 배송 정책
배송 상품 등록·수정의 배송 정책 섹션입니다(상품 등록). API에서는 상품의 fulfillment.shipping입니다
(fulfillment.type이 SHIPPING).
| 필드 | 뜻 |
|---|---|
deliveryType | FREE · PAID · CONDITIONAL_FREE. 등록에서 생략하면 PAID |
deliveryFee | 등록에서 생략하면 그 시점의 상점 기본 배송비를 숫자로 저장합니다. 나중에 기본값을 바꿔도 움직이지 않습니다. FREE면 0이거나 생략합니다 |
conditionalFreeAmount | 조건부 무료의 묶음 기준금액. CONDITIONAL_FREE면 필수이고 그 밖에는 null입니다 |
shippingAddressId · returnAddressId | 등록에서 생략하면 기본 출고지·반품지를 저장하고, null이면 비워 두고 상점 기본을 따릅니다 |
{ "fulfillment": { "type": "SHIPPING", "shipping": { "deliveryType": "CONDITIONAL_FREE", "deliveryFee": 3000, "conditionalFreeAmount": 50000 } } }- 수정(
PUT·PATCH)에서fulfillment를 보내면shipping의 다섯 필드를 모두 보냅니다. 빠뜨리면400입니다. 바꾸지 않으려면fulfillment를 생략합니다. FREE인데 배송비가 0이 아니거나,CONDITIONAL_FREE가 아닌데 기준금액을 주면400입니다. 값을 조용히 바꿔 저장하지 않습니다.- 반품·교환 배송비는 이 필드가 아니라 상품의
refundPolicy입니다(환불 정책 덮어쓰기). - 반품 안내에 쓰는 반품지는 주문 시점의 값입니다. 주문 뒤 상품의 반품지를 바꿔도 이미 들어온 주문의 안내는 바뀌지 않습니다.
이행 방식
상품의 fulfillment.methods로 구매자가 고를 수 있는 이행 방식을 둡니다. 첫 값이 기본입니다.
| 방식 | 뜻 | 받는 유형 | 배송지·배송비 |
|---|---|---|---|
CARRIER | 택배 | SHIPPING | 있음 |
DIRECT | 직접 배달 | SHIPPING | 있음 |
PICKUP | 방문 수령 | SHIPPING · MANUAL | 없음 |
PROVIDED | 제공 처리 | MANUAL | 없음 |
{ "fulfillment": { "type": "SHIPPING", "shipping": { "deliveryType": "PAID", "deliveryFee": 3000, "conditionalFreeAmount": null, "shippingAddressId": null, "returnAddressId": null }, "methods": ["CARRIER", "PICKUP"] } }- 생략하면 등록은 유형 기본(
SHIPPING은CARRIER,MANUAL은PROVIDED)이고, 수정은 지금 값을 유지합니다. 유형을 바꾸면 유형 기본으로 돌아갑니다. - 구매자는 주문서를 만들 때
fulfillmentMethod로 고릅니다. 모든 줄에 같은 방식을 적용하고, 허용하지 않는 상품이 있으면400 FULFILLMENT_METHOD_NOT_ALLOWED입니다(error.details.productIds). 다른 방식으로 바꾸려면 주문서를 새로 만듭니다. - 방문 수령은 배송 묶음에 들어가지 않아 배송비가 없고, 결제 시작에 배송지를 보내지 않아도 됩니다.
- 주문상품의
fulfillmentSnapshot.method에 고른 방식이 남습니다.
픽업 장소
방문 수령을 받는 상점은 /v1/pickup-locations에 수령 장소를 등록합니다(상점당 20개, 이름·주소·연락처·수령 가능 시간). 조회는 store:r, 등록·수정·삭제는 상점 플랫폼의 OWNER·ADMIN입니다.
- 구매자는
GET /storefront/v1/pickup-locations로 쓰는 장소를 보고, 주문서를 만들 때pickupLocationId로 고릅니다. - 쓰는 장소가 있는데 방문 수령에서 고르지 않으면
400 PICKUP_LOCATION_REQUIRED, 없거나 쓰지 않는 장소면400 PICKUP_LOCATION_NOT_FOUND, 다른 방식에 주면400 PICKUP_LOCATION_NOT_APPLICABLE입니다. 등록한 장소가 없으면 장소 없이 받습니다. - 주문상품의
fulfillmentSnapshot.pickupLocation에 고른 장소의 그때 값이 남습니다. 장소를 고치거나 지워도 지난 주문의 안내는 바뀌지 않습니다.
7. 출고지·반품지 주소록
상점 플랫폼에서 설정 › 주소록으로 이동합니다(소유자·관리자).
- 출고지 또는 반품지 영역에서 **[주소 추가]**를 누릅니다.
- 주소 이름·받는 사람·휴대폰·우편번호·주소·상세주소를 입력하고 **[저장]**을 누릅니다.
- 기본으로 쓸 주소에 **[기본으로 지정]**을 누릅니다.
- 유형별 최대 10개, 기본 주소 1개입니다. 첫 주소는 자동으로 기본입니다.
- 주소를 고치면 그 주소를 쓰는 상품에 바로 반영됩니다. 받은 주문의 묶음은 바뀌지 않습니다.
- 반품지는 반품·교환 수거 안내에 쓰이고 묶음 판정에는 쓰이지 않습니다.
| 하는 일 | API | Store SDK |
|---|---|---|
목록(?type=SHIPPING) | GET /v1/seller-addresses | sellerAddresses.list(type) |
| 등록 | POST /v1/seller-addresses | sellerAddresses.create(body) |
| 상세 | GET /v1/seller-addresses/{addressId} | |
| 수정(전체 교체) | PUT /v1/seller-addresses/{addressId} | sellerAddresses.update(id, body) |
| 삭제 | DELETE /v1/seller-addresses/{addressId} | sellerAddresses.remove(id) |
| 기본 지정 | POST /v1/seller-addresses/{addressId}/default | sellerAddresses.setDefault(id) |
SDK의 create·update·setDefault는 본문을 돌려주지 않습니다. 저장값은 list()로 다시 읽습니다.
목록·상세 조회는 store:r 스코프만 있으면 됩니다. AI 에이전트가 반품 안내 문구에 반품지를 쓰거나 상품에 출고지를 지정할 때 이 조회로 주소 id를 찾습니다. 등록·수정·삭제·기본 지정은 액세스 토큰·연동 앱으로 부르면 403 INSUFFICIENT_ROLE입니다.
| 오류 | 의미 |
|---|---|
400 INVALID_ADDRESS_TYPE | 출고지 자리에 반품지(또는 반대)를 지정함 |
403 INSUFFICIENT_ROLE | 소유자·관리자가 아니거나 액세스 토큰·연동 앱으로 호출함 |
404 ADDRESS_NOT_FOUND | 이 상점의 주소가 아님 |
409 ADDRESS_LIMIT_EXCEEDED | 유형별 10개 초과 |
409 DEFAULT_ADDRESS_UNDELETABLE | 기본 주소. 다른 주소를 기본으로 지정한 뒤 지웁니다 |
409 ADDRESS_IN_USE | 상품이 쓰는 주소. 상품의 주소를 다른 값이나 null로 바꾼 뒤 지웁니다 |
8. 취소·반품과 배송비
묶음의 남은 수량이 0이 될 때만 그 묶음 배송비(추가 배송비 포함)를 한 번 환불하고, 낸 배송비를 넘기지 않습니다. 누가 부담하는지는 환불 정책과 배송비에 있습니다.
자주 막히는 문제
| 증상 | 해결 |
|---|---|
| 배송비가 두 번 붙음 | 출고지나 배송 정책이 다른 상품입니다(delivery.bundleCount) |
| 무료배송인데 배송비가 있음 | 제주·도서산간 추가 배송비입니다 |
| 결제 금액이 주문서보다 큼 | 추가 배송비 지역입니다. 배송지 입력 뒤 미리보기를 부릅니다 |
| 상품 배송비가 예상과 다름 | 등록 당시 기본 배송비가 저장됐습니다. 상품에서 값을 지정합니다 |