@sayren/store-sdk 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -34,6 +34,11 @@ const errorDetailSchema = z.object({
34
34
  message: z.string(),
35
35
  traceId: z.string().optional(),
36
36
  /**
37
+ * 이 에러 코드를 설명하는 문서 URL(선택). 서버가 코드별 매핑을 가진 코드에만 실린다 —
38
+ * 없으면 필드가 아예 없으므로 소비측은 있을 때만 쓴다
39
+ */
40
+ docs: z.url().optional().catch(void 0),
41
+ /**
37
42
  * 에러 코드별 보조 데이터(선택). 예: 관리 API `LIVE_GATE_NOT_MET`·`LIVE_PAYMENTS_DISABLED`의
38
43
  * `{ conditions: LiveGateCondition[] }`. 형태는 에러 코드마다 다르므로 소비측이 코드별 스키마로 다시 파싱한다
39
44
  */
@@ -69,13 +74,15 @@ var ApiError = class extends Error {
69
74
  traceId;
70
75
  body;
71
76
  details;
72
- constructor(status, code, message, traceId, body, details) {
77
+ docs;
78
+ constructor(status, code, message, traceId, body, details, docs) {
73
79
  super(message);
74
80
  this.status = status;
75
81
  this.code = code;
76
82
  this.traceId = traceId;
77
83
  this.body = body;
78
84
  this.details = details;
85
+ this.docs = docs;
79
86
  this.name = "ApiError";
80
87
  }
81
88
  };
@@ -88,7 +95,7 @@ var ApiError = class extends Error {
88
95
  function toApiError(status, raw) {
89
96
  const envelope = envelopeSchema.safeParse(raw);
90
97
  const detail = envelope.success ? envelope.data.error : errorDetailSchema.safeParse(raw).data ?? null;
91
- if (detail) return new ApiError(status, detail.code, detail.message, detail.traceId, raw, detail.details);
98
+ if (detail) return new ApiError(status, detail.code, detail.message, detail.traceId, raw, detail.details, detail.docs);
92
99
  const oauth = envelope.success ? null : oauthErrorSchema.safeParse(raw).data;
93
100
  if (oauth) return new ApiError(status, oauth.error, oauth.error_description ?? oauth.error, void 0, raw);
94
101
  return new ApiError(status, "UNKNOWN", `HTTP ${status}`, void 0, raw);
@@ -467,7 +474,8 @@ const apiLogListQuerySchema = z.object({
467
474
  principalType: z.union([z.string(), z.array(z.string())]).transform((value) => (Array.isArray(value) ? value : [value]).flatMap((part) => part.split(",")).map((part) => part.trim()).filter((part) => part.length > 0)).pipe(z.array(apiLogPrincipalTypeSchema).min(1)).optional().describe("호출 주체 유형 필터. 여러 개는 쉼표로 구분한다 (예: `PAT,CLIENT`). 파싱 결과는 배열로 정규화된다"),
468
475
  principalId: z.string().optional().describe("호출 주체 ID 필터"),
469
476
  status: apiLogStatusFilterSchema.optional(),
470
- q: z.string().optional().describe("경로 부분 일치 검색어"),
477
+ q: z.string().optional().describe("경로 부분 일치 검색어. 리소스 id(예: `ord_…`)를 넣으면 그 리소스를 건드린 요청만 남으므로 웹훅 전송 기록의 `resource.id`와 짝지어 쓴다"),
478
+ traceId: z.string().optional().describe("추적 ID 필터(정확히 일치). 에러 응답 `error.traceId` 값을 그대로 넣어 그 요청 한 건을 찾는다"),
471
479
  cursor: z.string().optional().describe("이전 응답의 `nextCursor`. 생략하면 가장 최신부터"),
472
480
  limit: z.coerce.number().int().min(1).max(100).optional().describe("최대 건수 (1~100, 기본 50)")
473
481
  });
@@ -722,6 +730,21 @@ const claimReasonSchema = z.enum([
722
730
  "DIFFERENT_FROM_PAGE",
723
731
  "DELAY"
724
732
  ]);
733
+ /**
734
+ * 반품지 (이슈 #30) — 반품·교환 수거가 안내하는 주소다. 스토어 주소록의 기본 반품지이거나,
735
+ * 상품이 반품지를 따로 지정했으면 그 주소다.
736
+ *
737
+ * 취소 클레임은 보낼 물건이 없어 null이고, 반품지를 등록하지 않은 스토어도 null이다.
738
+ * 구매자가 직접 보내는 수거(`BUYER_SEND`)에 이 주소가 필요하다.
739
+ */
740
+ const claimReturnAddressSchema = z.object({
741
+ name: z.string().describe("주소 이름(셀러가 붙인 별칭). 예: 본사 물류센터"),
742
+ contactName: z.string().describe("수취인 이름"),
743
+ phone: z.string().describe("연락처"),
744
+ zipCode: z.string(),
745
+ address1: z.string(),
746
+ address2: z.string().nullable()
747
+ });
725
748
  const claimSchema = z.object({
726
749
  claimId: z.string(),
727
750
  type: claimTypeSchema,
@@ -750,7 +773,8 @@ const claimSchema = z.object({
750
773
  /** 보류 중이면 자동 해제 시각(ISO 8601), 아니면 null */
751
774
  holdExpiresAt: z.string().nullable(),
752
775
  /** 보류를 이미 썼는지 — 보류는 클레임당 1회라 해제·만료 뒤에도 true */
753
- holdUsed: z.boolean()
776
+ holdUsed: z.boolean(),
777
+ returnAddress: claimReturnAddressSchema.nullable().describe("수거 안내에 쓸 반품지(이슈 #30). 상품이 지정한 반품지, 없으면 스토어 기본 반품지다. 취소 클레임과 반품지를 등록하지 않은 스토어는 null")
754
778
  });
755
779
  /** `GET /v1/claims` 쿼리 — OpenAPI 문서용 형태(값은 쿼리 문자열) */
756
780
  const claimListQuerySchema = z.object({
@@ -1164,6 +1188,44 @@ const webhookDeliveryErrorCodeSchema = z.enum([
1164
1188
  "NON_2XX",
1165
1189
  "WEBHOOK_SUSPENDED"
1166
1190
  ]).describe("전송 실패 사유. `TIMEOUT`=10초 안에 응답 없음, `CONNECTION_FAILED`=연결 실패(DNS·TLS 포함), `BLOCKED_ADDRESS`=사설·루프백 등 전송이 허용되지 않는 주소, `NON_2XX`=2xx가 아닌 응답(리다이렉트 포함), `WEBHOOK_SUSPENDED`=구독 정지로 대기 중인 전송이 종료됨");
1191
+ /**
1192
+ * 실패 원인 분류의 **현재** 값. 응답 스키마(`webhookFailureReasonSchema`)는 문자열로 열려 있다 —
1193
+ * 서버가 값을 더해도 이전 SDK가 목록 파싱에 실패하지 않게 한다(택배 추적 `stage`와 같은 이유).
1194
+ * 모르는 값은 `errorCode`로 판단한다.
1195
+ */
1196
+ const WEBHOOK_FAILURE_REASONS = [
1197
+ "DNS",
1198
+ "TLS",
1199
+ "TIMEOUT",
1200
+ "CONNECTION",
1201
+ "HTTP_4XX",
1202
+ "HTTP_5XX",
1203
+ "HTTP_OTHER",
1204
+ "SSRF_BLOCKED",
1205
+ "SUSPENDED"
1206
+ ];
1207
+ const webhookFailureReasonSchema = z.string().describe("실패 원인 분류(진단용). 현재 값은 `DNS`=호스트 이름을 주소로 바꾸지 못함, `TLS`=인증서·핸드셰이크 실패, `TIMEOUT`=10초 안에 응답 없음, `CONNECTION`=연결 거부·끊김, `HTTP_4XX`·`HTTP_5XX`=그 대역의 응답, `HTTP_OTHER`=2xx·4xx·5xx가 아닌 응답(리다이렉트 등), `SSRF_BLOCKED`=사설·루프백 등 전송이 허용되지 않는 주소, `SUSPENDED`=구독 정지로 대기 중인 전송이 종료됨. `errorCode`보다 잘게 나눈 값이며 둘은 함께 쓸 수 있다. 값이 추가될 수 있으므로 모르는 값은 `errorCode`로 판단한다");
1208
+ /**
1209
+ * 대상 리소스 종류의 **현재** 값. 응답 스키마는 문자열로 열려 있다(새 이벤트가 새 리소스를 가리킬 수 있다).
1210
+ */
1211
+ const WEBHOOK_RESOURCE_TYPES = [
1212
+ "ORDER",
1213
+ "CLAIM",
1214
+ "PRODUCT",
1215
+ "REVIEW",
1216
+ "INQUIRY"
1217
+ ];
1218
+ const webhookResourceTypeSchema = z.string().describe("이벤트가 가리키는 리소스 종류. 현재 값은 `ORDER`·`CLAIM`·`PRODUCT`·`REVIEW`·`INQUIRY`이고 값이 추가될 수 있다");
1219
+ /** 이벤트가 가리키는 대상 리소스 — 전송 기록을 주문·상품과 잇는 키 */
1220
+ const webhookDeliveryResourceSchema = z.object({
1221
+ type: webhookResourceTypeSchema,
1222
+ id: z.string().describe("리소스 ID. 전송 기록 조회의 `resourceId`, API 로그 조회의 `q`로 그대로 넣을 수 있다")
1223
+ });
1224
+ /** 발송 요청 헤더 한 줄 (값 마스킹) */
1225
+ const webhookRequestHeaderSchema = z.object({
1226
+ name: z.string().describe("헤더 이름"),
1227
+ value: z.string().nullable().describe("헤더 값. 서명(`Sayren-Signature`)처럼 비밀이 섞이는 헤더는 이름만 주고 값은 null이다")
1228
+ });
1167
1229
  /** 웹훅 전송 기록 한 건 (전송 기록 조회 항목) */
1168
1230
  const webhookDeliverySchema = z.object({
1169
1231
  deliveryId: z.string().describe("전송 기록 ID. 수신 요청의 `Sayren-Delivery-Id` 헤더 값"),
@@ -1173,8 +1235,10 @@ const webhookDeliverySchema = z.object({
1173
1235
  status: webhookDeliveryStatusSchema,
1174
1236
  responseStatusCode: z.number().int().nullable().describe("마지막 시도의 HTTP 응답 코드. 미시도·연결 실패·타임아웃·차단이면 null"),
1175
1237
  responseTimeMs: z.number().int().nullable().describe("마지막 시도의 응답 시간(ms). 미시도·연결 실패면 null"),
1176
- responseBodyExcerpt: z.string().nullable().describe("마지막 시도의 응답 본문 앞부분(최대 1,024자). 응답이 없으면 null"),
1238
+ responseBodyExcerpt: z.string().nullable().describe("마지막 시도의 응답 본문 앞부분(최대 4,096자). 응답이 없으면 null. 가린 뒤에 자른다. 키 이름에 `authorization`·`token`·`secret`·`password`·`key`·`signature`·`credential`·`cookie`가 **들어 있으면**(부분 일치, 대소문자 무시. 예: `apiKey`·`access_token`·`keyword`) 그 값을 `***`로 가린다. 값이 객체·배열이면 통째로 가린다. 평문 `Authorization: …` 같은 헤더 줄, `Bearer <토큰>`, 쿼리 형식 `access_token=…`, 끝에서 잘려 닫히지 않은 값도 가린다"),
1177
1239
  errorCode: webhookDeliveryErrorCodeSchema.nullable().describe("마지막 시도의 실패 사유. 성공했거나 미시도면 null"),
1240
+ failureReason: webhookFailureReasonSchema.nullable().describe("마지막 시도의 실패 원인 분류. 성공했거나 미시도면 null. `errorCode`를 더 잘게 나눈 진단 값이다"),
1241
+ resource: webhookDeliveryResourceSchema.nullable().describe("이벤트가 가리키는 대상 리소스. 리소스를 특정할 수 없는 이벤트면 null. 이 값으로 전송 기록(`resourceId`)과 API 로그(`q`)를 오갈 수 있다"),
1178
1242
  attemptCount: z.number().int().min(0).describe("지금까지의 시도 횟수. 최초 시도 1회 + 재시도 최대 4회"),
1179
1243
  createdAt: z.string().describe("전송 기록이 적재된 일시 (ISO 8601)"),
1180
1244
  attemptedAt: z.string().nullable().describe("마지막 시도 일시 (ISO 8601). 미시도면 null"),
@@ -1182,9 +1246,20 @@ const webhookDeliverySchema = z.object({
1182
1246
  redeliveryOf: z.string().nullable().describe("수동 재전송으로 생긴 기록이면 원본 전송 기록 ID. 아니면 null"),
1183
1247
  redeliveredAs: z.string().nullable().describe("이 기록을 원본으로 수동 재전송해 생긴 기록의 전송 기록 ID. 아직 재전송하지 않았으면 null. 재전송은 원본당 1회다")
1184
1248
  });
1249
+ /** 발송 본문 최대 노출 길이(바이트). 넘으면 잘라 주고 `payloadTruncated`가 true다 */
1250
+ const WEBHOOK_PAYLOAD_EXCERPT_MAX_BYTES = 16384;
1251
+ /** 웹훅 전송 기록 상세 — `GET /webhooks/{id}/deliveries/{deliveryId}`. 목록 항목 + 요청 본문·헤더 */
1252
+ const webhookDeliveryDetailSchema = webhookDeliverySchema.extend({
1253
+ payload: z.string().describe(`수신 서버로 보낸 요청 본문 그대로(JSON 문자열). 서명은 이 문자열을 대상으로 계산한다. 최대 ${WEBHOOK_PAYLOAD_EXCERPT_MAX_BYTES / 1024}KB까지 주고 넘으면 잘라 준다`),
1254
+ payloadTruncated: z.boolean().describe("본문이 길어서 잘렸으면 true. true면 `payload`는 유효한 JSON이 아니다"),
1255
+ payloadMasked: z.boolean().describe("민감 키 마스킹(`responseBodyExcerpt`와 같은 키 이름 규칙)이 본문을 바꿨으면 true. true면 `payload`는 보낸 본문과 달라 서명을 맞춰 볼 수 없다. 이벤트 본문에는 보통 민감 키가 없어 false다"),
1256
+ requestHeaders: z.array(webhookRequestHeaderSchema).describe("발송 요청에 실은 헤더. 값은 전송 시점 그대로이나 서명 헤더는 이름만 주고 값은 null이다. `Content-Length`는 실제로 보낸 본문(마스킹·자르기 전)의 바이트 수다. 매 시도가 같은 헤더 집합을 쓰고 서명·타임스탬프만 달라진다")
1257
+ });
1185
1258
  /** 전송 기록 조회 쿼리 — `GET /webhooks/{id}/deliveries` */
1186
1259
  const webhookDeliveryListQuerySchema = z.object({
1187
1260
  status: webhookDeliveryStatusSchema.optional().describe("전송 상태 필터. 생략하면 전체"),
1261
+ eventType: eventTypeSchema.optional().describe("이벤트 타입 필터. 생략하면 전체"),
1262
+ resourceId: z.string().optional().describe("대상 리소스 ID 필터(정확히 일치). 주문·클레임·상품·리뷰·문의 id를 넣으면 그 리소스의 이벤트만 남는다. 같은 id로 API 로그를 `q`로 검색하면 그 리소스를 건드린 API 요청을 볼 수 있다"),
1188
1263
  limit: z.coerce.number().int().min(1).max(100).optional().describe("최대 건수 (1~100, 기본 50). 최신 적재순으로 반환하며 보관 기간은 30일이다")
1189
1264
  });
1190
1265
  /** 실패 건 일괄 재전송 응답 — `POST /webhooks/{id}/deliveries/redeliver-failed` */
@@ -1194,7 +1269,11 @@ const webhookTestRequestSchema = z.object({ eventType: eventTypeSchema.describe(
1194
1269
  const webhookTestResultSchema = z.object({
1195
1270
  responseStatusCode: z.number().int().nullable().describe("수신 서버의 HTTP 응답 코드. 연결 실패·타임아웃·차단이면 null"),
1196
1271
  responseTimeMs: z.number().int().nullable().describe("응답 시간(ms). 연결 실패면 null"),
1197
- errorCode: webhookDeliveryErrorCodeSchema.nullable().describe("실패 사유. 2xx 응답이면 null")
1272
+ errorCode: webhookDeliveryErrorCodeSchema.nullable().describe("실패 사유. 2xx 응답이면 null"),
1273
+ failureReason: webhookFailureReasonSchema.nullable().describe("실패 원인 분류. 2xx 응답이면 null"),
1274
+ responseBodyExcerpt: z.string().nullable().describe("응답 본문 앞부분(최대 4,096자). 규칙은 전송 기록의 `responseBodyExcerpt`와 같다. 응답이 없으면 null"),
1275
+ requestHeaders: z.array(webhookRequestHeaderSchema).describe("보낸 요청 헤더. 서명 헤더는 이름만 주고 값은 null이다"),
1276
+ payload: z.string().describe("보낸 요청 본문 그대로(JSON 문자열). 샘플 이벤트다")
1198
1277
  });
1199
1278
  const webhookRequestSchema = z.object({
1200
1279
  url: z.url().startsWith("https://", "웹훅 URL은 HTTPS만 허용됩니다"),
@@ -1488,6 +1567,42 @@ const paymentRecordStatusSchema = z.enum([
1488
1567
  "FAILED"
1489
1568
  ]);
1490
1569
  const paymentAttemptStatusSchema = z.string().describe("시도 상태 — ready·approving·approved·unknown·declined·prepare_failed·canceled·failed·abandoned·voided. 새 값이 추가될 수 있으니 모르는 값은 '진행 중이 아닌 시도'로 다룬다");
1570
+ /**
1571
+ * 현금영수증 상태 — 값이 추가될 수 있어 응답은 문자열로 받는다. 화면 분기는 이 목록으로 하고 모르는 값은 처리 중으로 다룬다.
1572
+ * - `REQUESTED`: 구매자가 신청했고 결제 승인 전이다
1573
+ * - `PENDING`: 발급·취소를 기다린다(승인 직후, 또는 일시 오류로 자동 재시도 대기)
1574
+ * - `ISSUED`: 발급됐다. 부분 환불이 있었으면 그만큼 취소(또는 재발급)된 금액이다
1575
+ * - `CANCELED`: 전액 환불로 취소됐다
1576
+ * - `FAILED`: 발급·취소가 실패했고 자동 재시도가 끝났다. `failCode`·`failMessage`를 보고 재발급한다
1577
+ * - `SKIPPED`: 발급 대상이 아니다(승인된 결제수단이 계좌이체·가상계좌가 아님)
1578
+ */
1579
+ const CASH_RECEIPT_STATUS_VALUES = [
1580
+ "REQUESTED",
1581
+ "PENDING",
1582
+ "ISSUED",
1583
+ "CANCELED",
1584
+ "FAILED",
1585
+ "SKIPPED"
1586
+ ];
1587
+ const cashReceiptSchema = z.object({
1588
+ cashReceiptId: z.string(),
1589
+ status: z.string().describe("상태. `REQUESTED` 결제 전 · `PENDING` 발급 대기 · `ISSUED` 발급 · `CANCELED` 취소 · `FAILED` 실패 · `SKIPPED` 발급 대상 아님. 값이 추가될 수 있다"),
1590
+ type: z.string().describe("용도. `INCOME_DEDUCTION` 소득공제 · `EXPENSE_PROOF` 지출증빙"),
1591
+ identityType: z.string().describe("식별 번호 종류. `PHONE` 휴대폰 · `CARD` 현금영수증카드 · `BUSINESS` 사업자등록번호"),
1592
+ identityNumberMasked: z.string().describe("가린 식별 번호 (예: 010****5678). 원문은 조회할 수 없다"),
1593
+ amount: z.number().int().describe("발급해야 할 금액 = 결제 금액 − 환불 완료 금액. 승인 전에는 결제 금액"),
1594
+ issuedAmount: z.number().int().describe("PG에 현재 발급돼 있는 금액. 취소됐거나 발급 전이면 0"),
1595
+ provider: pgProviderSchema.nullable().describe("발급 PG — 결제를 승인한 PG. 승인 전에는 null"),
1596
+ issueNumber: z.string().nullable().describe("현금영수증 승인 번호"),
1597
+ receiptUrl: z.string().nullable().describe("현금영수증 확인 주소"),
1598
+ issuedAt: z.string().nullable().describe("최근 발급 시각 (ISO 8601)"),
1599
+ canceledAt: z.string().nullable().describe("최근 취소 시각 (ISO 8601)"),
1600
+ failCode: z.string().nullable().describe("마지막 실패 코드. 성공하면 비운다"),
1601
+ failMessage: z.string().nullable().describe("마지막 실패 사유"),
1602
+ reissuable: z.boolean().describe("콘솔 재발급으로 다시 시도할 수 있는가 (`FAILED`·`PENDING`)"),
1603
+ requestedAt: z.string().describe("신청 시각 (ISO 8601)"),
1604
+ updatedAt: z.string().describe("마지막 변경 시각 (ISO 8601)")
1605
+ });
1491
1606
  const paymentRecordSchema = z.object({
1492
1607
  paymentId: z.string().describe("결제 건 id. 상세 조회 키"),
1493
1608
  orderId: z.string().nullable().describe("승인돼 만들어진 주문. 승인 전에 끝난 결제는 null"),
@@ -1507,7 +1622,8 @@ const paymentRecordSchema = z.object({
1507
1622
  failCode: z.string().nullable().describe("실패 코드. 실패로 끝난 결제만"),
1508
1623
  failMessage: z.string().nullable().describe("실패 사유"),
1509
1624
  paidAt: z.string().nullable().describe("승인 시각 (ISO 8601)"),
1510
- createdAt: z.string().describe("결제 시작 시각 (ISO 8601)")
1625
+ createdAt: z.string().describe("결제 시작 시각 (ISO 8601)"),
1626
+ cashReceiptStatus: z.string().nullable().optional().default(null).describe("현금영수증 상태(`cashReceiptSchema.status`와 같은 값). 신청이 없으면 null. 서버는 항상 싣는다")
1511
1627
  });
1512
1628
  const paymentRecordPageSchema = pageSchema(paymentRecordSchema);
1513
1629
  const paymentAttemptRecordSchema = z.object({
@@ -1541,7 +1657,8 @@ const paymentRecordDetailSchema = paymentRecordSchema.extend({
1541
1657
  refundableAmount: z.number().int().describe("환불 가능 잔액 = 승인 금액 - 환불 완료 금액"),
1542
1658
  merchantId: z.string().nullable().describe("가맹점 식별자 지문 (토스 clientKey, 포트원 Store ID). 비밀 값은 없다"),
1543
1659
  attempts: z.array(paymentAttemptRecordSchema).describe("시도 이력. 순번 오름차순"),
1544
- refunds: z.array(paymentRefundRecordSchema).describe("환불 이력. 실패한 요청도 남는다")
1660
+ refunds: z.array(paymentRefundRecordSchema).describe("환불 이력. 실패한 요청도 남는다"),
1661
+ cashReceipt: cashReceiptSchema.nullable().optional().default(null).describe("현금영수증. 구매자가 신청하지 않았으면 null. 서버는 항상 싣는다")
1545
1662
  });
1546
1663
  /** `GET /v1/payments` 쿼리 — OpenAPI 문서용 형태(값은 쿼리 문자열) */
1547
1664
  const paymentListQuerySchema = z.object({
@@ -1630,7 +1747,8 @@ const orderSchema = z.object({
1630
1747
  deliveryFee: z.number().int(),
1631
1748
  discountAmount: z.number().int(),
1632
1749
  totalAmount: z.number().int(),
1633
- paidAt: z.string().nullable()
1750
+ paidAt: z.string().nullable(),
1751
+ cashReceipt: cashReceiptSchema.nullable().optional().default(null).describe("현금영수증. 구매자가 계좌이체·가상계좌 결제에서 신청했을 때만 있고 그 밖에는 null. 서버는 항상 싣는다")
1634
1752
  }),
1635
1753
  testPayment: z.boolean().describe("테스트 결제(샌드박스 결제) 주문인가. true면 실제로 돈이 오가지 않았어요. 상품을 발송하지 않도록 주의하세요"),
1636
1754
  items: z.array(orderItemSchema)
@@ -1677,7 +1795,14 @@ const dispatchRequestSchema = z.object({
1677
1795
  deliveryMethod: deliveryMethodSchema,
1678
1796
  carrierCode: z.string().optional(),
1679
1797
  trackingNumber: z.string().optional(),
1680
- dispatchedAt: z.string().optional()
1798
+ /**
1799
+ * 발송 시각. ISO 8601이고 시간대(`Z` 또는 `+09:00`)를 포함해야 한다. 생략하면 현재 시각이다.
1800
+ *
1801
+ * 전에는 `z.string()`이라 아무 문자열이나 그대로 저장됐다. 이 값은 배송 정보 jsonb에 들어가
1802
+ * 자동 배송완료·추적 동기화 잡의 대상 선별 조건이 된다 — 잘못된 한 행이 스토어 구분 없이 도는
1803
+ * 잡 전체를 멈출 수 있어 입력에서 좁혔다. 검증을 통과한 값도 저장 전에 UTC ISO로 정규화한다.
1804
+ */
1805
+ dispatchedAt: z.iso.datetime({ offset: true }).optional()
1681
1806
  }).refine((value) => value.deliveryMethod === "DIRECT" || value.deliveryMethod === "NOTHING" ? true : !!value.carrierCode && !!value.trackingNumber, { message: "COURIER/QUICK 발송은 carrierCode와 trackingNumber가 필수입니다" });
1682
1807
  const deliveryInfoSchema = z.object({
1683
1808
  orderItemId: z.string(),
@@ -1694,12 +1819,26 @@ const deliveryInfoSchema = z.object({
1694
1819
  dispatchedAt: z.string(),
1695
1820
  deliveredAt: z.string().nullable()
1696
1821
  });
1822
+ /**
1823
+ * 택배 추적 단계. 응답에서는 문자열로 받는다(값이 추가될 수 있다) — 현재 값은 `ACCEPTED`(접수)·`IN_TRANSIT`(이동 중)·
1824
+ * `OUT_FOR_DELIVERY`(배송 출발)·`DELIVERED`(배송완료)다.
1825
+ */
1826
+ const trackingStageResponseSchema = z.string();
1697
1827
  /** `GET /v1/order-items/{orderItemId}/delivery` 응답 — 배송 정보 + 배송 추적 이벤트 */
1698
- const deliveryStatusSchema = deliveryInfoSchema.extend({ trackingEvents: z.array(z.object({
1699
- status: z.string(),
1700
- location: z.string(),
1701
- occurredAt: z.string()
1702
- })).optional() });
1828
+ const deliveryStatusSchema = deliveryInfoSchema.extend({
1829
+ trackingEvents: z.array(z.object({
1830
+ status: z.string().describe("택배사가 알려 준 처리 내용(예: 집화처리, 배송출발)"),
1831
+ location: z.string().describe("처리 장소. 모르면 빈 문자열"),
1832
+ occurredAt: z.string(),
1833
+ stage: trackingStageResponseSchema.nullable().optional().default(null).describe("이 이벤트의 추적 단계. 배달 실패처럼 단계로 나눌 수 없는 이벤트는 null. 서버는 항상 싣는다. 현재 값은 `ACCEPTED`·`IN_TRANSIT`·`OUT_FOR_DELIVERY`·`DELIVERED`이고 값이 추가될 수 있어 문자열로 받는다")
1834
+ })).optional().describe("배송 추적 이력. 시각 오름차순(오래된 것부터)"),
1835
+ tracking: z.object({
1836
+ result: z.string().nullable().describe("마지막 추적 조회 결과. 조회에 성공한 적이 없으면 null. 현재 값은 `FOUND`(조회됨)·`NOT_FOUND`(택배사 전산에 송장 없음)·`UNSUPPORTED_CARRIER`(추적을 지원하지 않는 택배사)이고 값이 추가될 수 있어 문자열로 받는다"),
1837
+ stage: trackingStageResponseSchema.nullable().describe("현재 추적 단계. 조회되지 않았으면 null. 현재 값은 `ACCEPTED`·`IN_TRANSIT`·`OUT_FOR_DELIVERY`·`DELIVERED`이고 값이 추가될 수 있어 문자열로 받는다"),
1838
+ checkedAt: z.string().nullable().describe("택배사에서 추적 정보를 마지막으로 받아 온 시각(ISO 8601)"),
1839
+ carrierDeliveredAt: z.string().nullable().describe("택배사가 알려 준 배송완료 시각(ISO 8601). 추적으로 배송완료가 반영되면 주문상품의 `deliveredAt`은 반영 시각이라 이 값과 다를 수 있다")
1840
+ }).nullable().optional().default(null).describe("택배 추적 요약. 택배 발송이 아니거나 추적을 쓰지 않으면 null. 추적이 배송완료면 주문상품이 자동으로 배송완료된다. 서버는 항상 싣는다")
1841
+ });
1703
1842
  /** `PUT /v1/order-items/{orderItemId}/dispatch/tracking` 응답 — 바뀐 송장 */
1704
1843
  const trackingUpdateResultSchema = z.object({
1705
1844
  orderItemId: z.string(),
@@ -1774,7 +1913,7 @@ const addressChangeRequestListSchema = z.array(z.record(z.string(), z.unknown())
1774
1913
  * 한 주문에 정책이 섞이면 **상품별로 계산한다**. 배송비 묶음도 상품 단위다(주문서·장바구니의
1775
1914
  * 배송비 합산과 같은 규칙: 같은 상품의 주문상품이 한 묶음이고 묶음마다 배송비가 붙는다).
1776
1915
  */
1777
- const descriptions = {
1916
+ const descriptions$1 = {
1778
1917
  preShipmentDeliveryFeeRefund: "발송 전 취소로 배송 묶음(같은 상품의 주문상품)이 모두 취소되면 그 묶음의 배송비를 환불한다. false면 배송비를 환불하지 않는다. 기본 true",
1779
1918
  returnDeliveryFee: "단순 변심(구매자 귀책) 반품의 반품 배송비. 원 단위. 환불액에서 뺀다. 기본 0(무료). 상품별 정책에서 이 값을 주면 상품의 `returnFee`도 같은 값으로 맞춘다",
1780
1919
  exchangeDeliveryFee: "단순 변심(구매자 귀책) 교환의 교환 배송비. 원 단위. 구매자에게 청구할 금액을 안내값으로 싣는다. 기본 0(무료). 상품별 정책에서 이 값을 주면 상품의 `exchangeFee`도 같은 값으로 맞춘다",
@@ -1783,11 +1922,11 @@ const descriptions = {
1783
1922
  };
1784
1923
  /** 유효 환불 정책 — 스토어 기본 + 상품별 덮어쓰기를 합친 결과 */
1785
1924
  const refundPolicySchema = z.object({
1786
- preShipmentDeliveryFeeRefund: z.boolean().describe(descriptions.preShipmentDeliveryFeeRefund),
1787
- returnDeliveryFee: z.number().int().min(0).describe(descriptions.returnDeliveryFee),
1788
- exchangeDeliveryFee: z.number().int().min(0).describe(descriptions.exchangeDeliveryFee),
1789
- sellerFaultFreeShipping: z.boolean().describe(descriptions.sellerFaultFreeShipping),
1790
- conditionalFreeShortfallCharge: z.boolean().describe(descriptions.conditionalFreeShortfallCharge)
1925
+ preShipmentDeliveryFeeRefund: z.boolean().describe(descriptions$1.preShipmentDeliveryFeeRefund),
1926
+ returnDeliveryFee: z.number().int().min(0).describe(descriptions$1.returnDeliveryFee),
1927
+ exchangeDeliveryFee: z.number().int().min(0).describe(descriptions$1.exchangeDeliveryFee),
1928
+ sellerFaultFreeShipping: z.boolean().describe(descriptions$1.sellerFaultFreeShipping),
1929
+ conditionalFreeShortfallCharge: z.boolean().describe(descriptions$1.conditionalFreeShortfallCharge)
1791
1930
  });
1792
1931
  /**
1793
1932
  * 스토어가 정하지 않았을 때 서버가 쓰는 값.
@@ -1809,11 +1948,11 @@ const REFUND_POLICY_DEFAULTS = {
1809
1948
  * 믿는데 정책은 기본값으로 남는다.
1810
1949
  */
1811
1950
  const refundPolicyPatchSchema = z.strictObject({
1812
- preShipmentDeliveryFeeRefund: z.boolean().optional().describe(descriptions.preShipmentDeliveryFeeRefund),
1813
- returnDeliveryFee: z.number().int().min(0).optional().describe(descriptions.returnDeliveryFee),
1814
- exchangeDeliveryFee: z.number().int().min(0).optional().describe(descriptions.exchangeDeliveryFee),
1815
- sellerFaultFreeShipping: z.boolean().optional().describe(descriptions.sellerFaultFreeShipping),
1816
- conditionalFreeShortfallCharge: z.boolean().optional().describe(descriptions.conditionalFreeShortfallCharge)
1951
+ preShipmentDeliveryFeeRefund: z.boolean().optional().describe(descriptions$1.preShipmentDeliveryFeeRefund),
1952
+ returnDeliveryFee: z.number().int().min(0).optional().describe(descriptions$1.returnDeliveryFee),
1953
+ exchangeDeliveryFee: z.number().int().min(0).optional().describe(descriptions$1.exchangeDeliveryFee),
1954
+ sellerFaultFreeShipping: z.boolean().optional().describe(descriptions$1.sellerFaultFreeShipping),
1955
+ conditionalFreeShortfallCharge: z.boolean().optional().describe(descriptions$1.conditionalFreeShortfallCharge)
1817
1956
  });
1818
1957
  /**
1819
1958
  * 유효 정책 해석 — 상품별 덮어쓰기 → 스토어 기본 → `REFUND_POLICY_DEFAULTS` 순이다.
@@ -2161,6 +2300,21 @@ const variantStockSchema = z.object({
2161
2300
  variantId: z.string(),
2162
2301
  stockQuantity: z.number().int()
2163
2302
  });
2303
+ /**
2304
+ * 출고지·반품지 지정 문구 — POST·PUT·PATCH가 같은 규칙을 쓰므로 설명도 한 곳이다 (이슈 #30).
2305
+ *
2306
+ * 세 값이 뜻이 다르다. **생략**은 「정하지 않음」이라 서버가 그 시점 스토어 기본 주소를 박아 저장하고,
2307
+ * **null**은 「스토어 기본을 따른다」는 명시적 비우기다. 주소록 항목은 상품이 참조 중이면 지울 수 없어
2308
+ * (`409 ADDRESS_IN_USE`) 참조를 푸는 수단이 필요한데, 생략을 기본값으로 되채우면 기본 주소는 영원히
2309
+ * 풀리지 않는다 — null이 그 경로다. 배송 묶음 판정은 null을 그 시점 스토어 기본 출고지로 정규화하므로
2310
+ * 두 표현이 배송비를 갈라놓지 않는다.
2311
+ */
2312
+ const ADDRESS_DESCRIPTIONS = {
2313
+ shipping: "출고지 주소 id(`GET /v1/seller-addresses?type=SHIPPING`). 생략하면 스토어 기본 출고지를 저장하고, `null`이면 비워 둔다(스토어 기본 출고지를 따른다). 이 스토어의 `SHIPPING` 주소가 아니면 거부한다",
2314
+ return: "반품지 주소 id(`GET /v1/seller-addresses?type=RETURN`). 생략하면 스토어 기본 반품지를 저장하고, `null`이면 비워 둔다(스토어 기본 반품지를 따른다). 이 스토어의 `RETURN` 주소가 아니면 거부한다",
2315
+ patchShipping: "출고지 주소 id. **생략은 바꾸지 않음**이고 `null`은 비우기다(스토어 기본 출고지를 따른다). 이 스토어의 `SHIPPING` 주소가 아니면 거부한다",
2316
+ patchReturn: "반품지 주소 id. **생략은 바꾸지 않음**이고 `null`은 비우기다(스토어 기본 반품지를 따른다). 이 스토어의 `RETURN` 주소가 아니면 거부한다"
2317
+ };
2164
2318
  const productCreateRequestSchema = z.object({
2165
2319
  name: z.string().min(1).max(100),
2166
2320
  categoryId: z.string().min(1, "카테고리를 선택해주세요"),
@@ -2178,19 +2332,21 @@ const productCreateRequestSchema = z.object({
2178
2332
  value: z.string()
2179
2333
  })).optional(),
2180
2334
  deliveryType: deliveryTypeSchema.default("PAID").describe("배송비 유형. FREE 무료, PAID 유료, CONDITIONAL_FREE 조건부 무료"),
2181
- deliveryFee: z.number().int().default(0).describe("배송비. 원 단위"),
2335
+ deliveryFee: z.number().int().min(0).optional().describe("배송비. 원 단위. **생략하면 스토어 기본 배송비**(`GET /v1/store`의 `shippingPolicy.baseDeliveryFee`)를 쓴다. `deliveryType`이 FREE면 0으로 저장한다"),
2182
2336
  conditionalFreeAmount: z.number().int().optional().describe("CONDITIONAL_FREE일 때 무료 배송이 되는 주문 금액. 원 단위"),
2183
2337
  returnFee: z.number().int().default(0).describe("반품 배송비. 원 단위. `refundPolicy.returnDeliveryFee`와 같은 값이다"),
2184
2338
  exchangeFee: z.number().int().default(0).describe("교환 배송비. 원 단위. `refundPolicy.exchangeDeliveryFee`와 같은 값이다"),
2185
2339
  refundPolicy: refundPolicyPatchSchema.nullish().describe("상품별 환불 정책 — 준 항목만 스토어 기본을 덮어쓴다. `null`이나 생략은 스토어 기본을 그대로 쓴다는 뜻이다. `returnDeliveryFee`·`exchangeDeliveryFee`를 주면 `returnFee`·`exchangeFee`도 같은 값으로 맞춘다"),
2186
- shippingAddressId: z.string().optional(),
2187
- returnAddressId: z.string().optional()
2340
+ shippingAddressId: z.string().nullable().optional().describe(ADDRESS_DESCRIPTIONS.shipping),
2341
+ returnAddressId: z.string().nullable().optional().describe(ADDRESS_DESCRIPTIONS.return)
2188
2342
  }).extend(optionModelRequestSchema.shape);
2189
2343
  const productPatchRequestSchema = z.object({
2190
2344
  name: z.string().max(100).optional(),
2191
2345
  salePrice: z.number().int().multipleOf(10).optional(),
2192
2346
  status: productStatusSchema.optional(),
2193
- deliveryFee: z.number().int().optional()
2347
+ deliveryFee: z.number().int().optional(),
2348
+ shippingAddressId: z.string().nullable().optional().describe(ADDRESS_DESCRIPTIONS.patchShipping),
2349
+ returnAddressId: z.string().nullable().optional().describe(ADDRESS_DESCRIPTIONS.patchReturn)
2194
2350
  }).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" });
2195
2351
  const productSortSchema = z.enum([
2196
2352
  "createdAt.desc",
@@ -2223,6 +2379,441 @@ const discountRequestSchema = z.object({
2223
2379
  endAt: z.string().optional()
2224
2380
  });
2225
2381
  //#endregion
2382
+ //#region src/schemas/shipping-policy.ts
2383
+ /**
2384
+ * 배송비 규칙 (이슈 #29) — 배송비를 **몇 번, 얼마나** 부과하는지를 정한다.
2385
+ *
2386
+ * 세 축이다.
2387
+ * 1. **배송 묶음**: 같은 출고지 + 같은 배송 정책인 주문상품은 한 묶음이고 배송비가 한 번만 붙는다
2388
+ * (`deliveryBundleKeyOf`). 이슈 #25의 「묶음 = 같은 상품」 규칙을 일반화한 것이다 — 키를 상품 id에서
2389
+ * 정책 지문으로 바꿨고, 주문상품이 주문 시점 키를 스냅샷해(`order_items.delivery_bundle_key`)
2390
+ * 환불 정산(`refund-settlement.ts`)과 주문 스냅샷(`orders.delivery_fee_settlements`)이 같은 키를 쓴다.
2391
+ * 2. **무료 기준**: 상품별 조건부 무료(`CONDITIONAL_FREE`, 묶음 소계 기준)와 스토어 전체 무료 기준
2392
+ * (`freeThreshold`, 주문 상품 금액 합 기준)이 있다. 둘 중 하나라도 충족하면 그 배송비는 0이다.
2393
+ * 3. **지역 추가 배송비**: 우편번호가 제주·도서산간이면 **묶음마다** 추가된다. 묶음이 곧 한 번의 발송이라
2394
+ * 두 출고지에서 각각 보내면 추가 운임도 두 번 든다.
2395
+ *
2396
+ * 지역 추가 배송비는 **무료배송과 무관하게 부과한다.** 무료 기준은 셀러가 기본 운임을 부담하겠다는
2397
+ * 뜻이지, 도서 지역 추가 운임까지 부담하겠다는 뜻은 아니다(국내 택배사 공통 관행).
2398
+ *
2399
+ * 계산은 `quoteDelivery` 하나다. 장바구니·주문서·결제 시작·환불 정산이 모두 이 함수를 쓴다 —
2400
+ * 네 곳이 각자 계산하면 화면 금액과 결제 금액이 갈라진다.
2401
+ */
2402
+ const descriptions = {
2403
+ baseDeliveryFee: "스토어 기본 배송비. 원 단위. 상품 등록·수정에서 `deliveryFee`를 생략하면 이 값을 쓴다. 기본 3000",
2404
+ freeThreshold: "스토어 전체 무료배송 기준액. 주문 상품 금액 합이 이 값 이상이면 모든 묶음의 배송비가 0이다. null이면 전체 무료배송을 쓰지 않는다(상품별 조건부 무료는 그대로 동작한다). 기본 null",
2405
+ jejuSurcharge: "제주 지역 추가 배송비. 원 단위. **배송 묶음마다** 더한다. 기본 0",
2406
+ isolatedSurcharge: "도서산간 지역 추가 배송비. 원 단위. **배송 묶음마다** 더한다. 기본 0"
2407
+ };
2408
+ /** 유효 배송비 정책 — 스토어가 정하지 않은 항목은 `SHIPPING_POLICY_DEFAULTS`로 채운 값이다 */
2409
+ const shippingPolicySchema = z.object({
2410
+ baseDeliveryFee: z.number().int().min(0).describe(descriptions.baseDeliveryFee),
2411
+ freeThreshold: z.number().int().min(1).nullable().describe(descriptions.freeThreshold),
2412
+ jejuSurcharge: z.number().int().min(0).describe(descriptions.jejuSurcharge),
2413
+ isolatedSurcharge: z.number().int().min(0).describe(descriptions.isolatedSurcharge)
2414
+ });
2415
+ /**
2416
+ * 스토어가 정하지 않았을 때 서버가 쓰는 값.
2417
+ *
2418
+ * `as const`가 아니라 `ShippingPolicy`로 좁힌다 — 리터럴 타입이면 `Partial<typeof …>`로 일부만 바꿀 때
2419
+ * `null` 자리에 숫자를 넣을 수 없다(`REFUND_POLICY_DEFAULTS`와 같은 이유).
2420
+ */
2421
+ const SHIPPING_POLICY_DEFAULTS = {
2422
+ baseDeliveryFee: 3e3,
2423
+ freeThreshold: null,
2424
+ jejuSurcharge: 0,
2425
+ isolatedSurcharge: 0
2426
+ };
2427
+ /**
2428
+ * 부분 정책 — `PATCH /v1/store`의 `shippingPolicy`가 쓴다. 준 항목만 바뀐다.
2429
+ *
2430
+ * `strictObject`라 모르는 키는 400이다. 오타를 조용히 무시하면 셀러는 저장됐다고 믿는데
2431
+ * 정책은 기본값으로 남는다(`refundPolicyPatchSchema`와 같은 이유).
2432
+ */
2433
+ const shippingPolicyPatchSchema = z.strictObject({
2434
+ baseDeliveryFee: z.number().int().min(0).optional().describe(descriptions.baseDeliveryFee),
2435
+ freeThreshold: z.number().int().min(1).nullable().optional().describe(descriptions.freeThreshold),
2436
+ jejuSurcharge: z.number().int().min(0).optional().describe(descriptions.jejuSurcharge),
2437
+ isolatedSurcharge: z.number().int().min(0).optional().describe(descriptions.isolatedSurcharge)
2438
+ });
2439
+ /** 유효 정책 해석 — 스토어가 정한 항목 + `SHIPPING_POLICY_DEFAULTS` */
2440
+ function resolveShippingPolicy(patch) {
2441
+ if (!patch) return { ...SHIPPING_POLICY_DEFAULTS };
2442
+ const given = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== void 0));
2443
+ return {
2444
+ ...SHIPPING_POLICY_DEFAULTS,
2445
+ ...given
2446
+ };
2447
+ }
2448
+ /** 지역 구분 — 우편번호로 판정한다 */
2449
+ const remoteAreaSchema = z.enum([
2450
+ "NONE",
2451
+ "JEJU",
2452
+ "ISOLATED"
2453
+ ]);
2454
+ /**
2455
+ * 제주·도서산간 우편번호 범위 — **코드 상수다.** 금액은 스토어가 정하고 범위는 플랫폼이 고정한다.
2456
+ *
2457
+ * 국내 택배사가 공표하는 제주·도서산간 할증 구간을 5자리 우편번호 범위로 옮긴 표다. 셀러마다
2458
+ * 범위가 다르면 같은 주소에 다른 금액이 나가고 CS가 셀러 수만큼 갈라진다. 스토어별 범위 편집은
2459
+ * 후속 과제이고, 지금은 이 표가 유일한 원천이다.
2460
+ *
2461
+ * ## 근거
2462
+ *
2463
+ * 원천은 **CJ대한통운이 공표한 「제주 및 도서산간 지역 추가 운송비」 표**(구간 시작·끝 우편번호와
2464
+ * 지역명이 붙은 그 표)다. 같은 표가 한진택배 공지와 쇼핑몰 솔루션 안내에도 같은 값으로 실린다.
2465
+ * 서로 다른 게시자의 전재본 네 곳이 인천·전남·경남 구간까지 글자 그대로 일치하는 것을 확인했다.
2466
+ *
2467
+ * - CJ대한통운 표 전재 (구간별 금액 포함): https://ichibanhouse.com/article/공지뉴스/1/16/
2468
+ * - 같은 표의 다른 전재본: https://peppytail.com/article/공지사항/1/4046/
2469
+ * - 시행 시점(2020-02-01)과 구간별 행정구역명을 함께 적은 전재본:
2470
+ * https://tosstoss.co.kr/bbs/content.php?co_id=pilotage
2471
+ * - 솔루션 안내 「제주도 및 도서산간 지역 기준표」:
2472
+ * https://help.campaignus.me/ko/articles/제주도-및-도서산간-지역-기준표-342d2e5e
2473
+ *
2474
+ * 라벨의 행정구역명은 우편번호 배정을 따로 확인해 고쳤다. 택배사 표의 지역명은 축약·오기가 있다.
2475
+ * 확인한 것만 남기고, 확인하지 못한 구간은 이 표에서 뺀다 — 판정하지 못하면 받지 않는 쪽이
2476
+ * `remoteAreaOf`의 규칙과 같다.
2477
+ *
2478
+ * ## 표에서 뺀 구간
2479
+ *
2480
+ * - **54000**: 택배사 표는 「경남 통영 섬지역3」으로 적었지만 이 번호는 **전북 군산시 옥도면**이다.
2481
+ * 게다가 공정거래위원회가 2025-10-07 발표에서 연륙교로 이어진 신시도·야미도·선유도와 비연륙
2482
+ * 도서(방축도·관리도)가 같은 우편번호를 쓰는 대표 사례로 이 지역을 들었다. 라벨도 부과 근거도
2483
+ * 확정하지 못해 뺀다.
2484
+ * - **강화군 본섬(23000~23003, 23011~23099)**: 택배사 표의 강화 할증 구간은 23004~23010
2485
+ * (교동면·삼산면·서도면)뿐이다. 강화대교·초지대교로 이어진 본섬은 도선료가 없어 어느 택배사도
2486
+ * 할증하지 않는다. 「누락」이 아니라 대상이 아니다.
2487
+ *
2488
+ * ## 한계
2489
+ *
2490
+ * 우편번호 단위 판정은 한 번호 안에 연륙도서와 비연륙도서가 섞인 구간에서 과대 부과가 된다.
2491
+ * 공정위가 2025-10-07에 온라인몰 13곳의 이 방식을 시정하게 한 지적이 그것이다
2492
+ * (https://eiec.kdi.re.kr/policy/materialView.do?num=272070). 아래 구간 중 33411(보령 오천면,
2493
+ * 원산도가 해저터널로 연결), 46768~46771(강서구 가덕도, 거가대교로 연결)이 그런 혼재 구간이다.
2494
+ * 도로명주소·건물관리번호 단위 판정은 후속 과제다.
2495
+ *
2496
+ * 범위는 **닫힌 구간**(`from` ≤ zip ≤ `to`)이고 겹치지 않는다. 앞에서부터 처음 맞는 항목을 쓴다.
2497
+ * 순서는 콘솔 「추가 배송비 지역」 표의 행 순서이기도 하다(`remoteAreaGroups`). 제주를 먼저 두고
2498
+ * 도서산간은 우편번호 오름차순으로 둔다.
2499
+ */
2500
+ const REMOTE_AREA_ZIP_RANGES = [
2501
+ {
2502
+ area: "JEJU",
2503
+ from: 63e3,
2504
+ to: 63644,
2505
+ label: "제주특별자치도"
2506
+ },
2507
+ {
2508
+ area: "ISOLATED",
2509
+ from: 22386,
2510
+ to: 22388,
2511
+ label: "인천 영종구 무의동"
2512
+ },
2513
+ {
2514
+ area: "ISOLATED",
2515
+ from: 23004,
2516
+ to: 23010,
2517
+ label: "인천 강화군 도서"
2518
+ },
2519
+ {
2520
+ area: "ISOLATED",
2521
+ from: 23100,
2522
+ to: 23116,
2523
+ label: "인천 옹진군 도서"
2524
+ },
2525
+ {
2526
+ area: "ISOLATED",
2527
+ from: 23124,
2528
+ to: 23136,
2529
+ label: "인천 옹진군 도서"
2530
+ },
2531
+ {
2532
+ area: "ISOLATED",
2533
+ from: 31708,
2534
+ to: 31708,
2535
+ label: "충남 당진시 도서"
2536
+ },
2537
+ {
2538
+ area: "ISOLATED",
2539
+ from: 32133,
2540
+ to: 32133,
2541
+ label: "충남 태안군 도서"
2542
+ },
2543
+ {
2544
+ area: "ISOLATED",
2545
+ from: 33411,
2546
+ to: 33411,
2547
+ label: "충남 보령시 도서"
2548
+ },
2549
+ {
2550
+ area: "ISOLATED",
2551
+ from: 40200,
2552
+ to: 40240,
2553
+ label: "경북 울릉군"
2554
+ },
2555
+ {
2556
+ area: "ISOLATED",
2557
+ from: 46768,
2558
+ to: 46771,
2559
+ label: "부산 강서구 도서"
2560
+ },
2561
+ {
2562
+ area: "ISOLATED",
2563
+ from: 52570,
2564
+ to: 52571,
2565
+ label: "경남 사천시 도서"
2566
+ },
2567
+ {
2568
+ area: "ISOLATED",
2569
+ from: 53031,
2570
+ to: 53033,
2571
+ label: "경남 통영시 도서"
2572
+ },
2573
+ {
2574
+ area: "ISOLATED",
2575
+ from: 53089,
2576
+ to: 53104,
2577
+ label: "경남 통영시 도서"
2578
+ },
2579
+ {
2580
+ area: "ISOLATED",
2581
+ from: 56347,
2582
+ to: 56349,
2583
+ label: "전북 부안군 위도면"
2584
+ },
2585
+ {
2586
+ area: "ISOLATED",
2587
+ from: 57068,
2588
+ to: 57069,
2589
+ label: "전남 영광군 낙월면"
2590
+ },
2591
+ {
2592
+ area: "ISOLATED",
2593
+ from: 58760,
2594
+ to: 58762,
2595
+ label: "전남 목포시 도서"
2596
+ },
2597
+ {
2598
+ area: "ISOLATED",
2599
+ from: 58800,
2600
+ to: 58810,
2601
+ label: "전남 신안군"
2602
+ },
2603
+ {
2604
+ area: "ISOLATED",
2605
+ from: 58816,
2606
+ to: 58818,
2607
+ label: "전남 신안군"
2608
+ },
2609
+ {
2610
+ area: "ISOLATED",
2611
+ from: 58826,
2612
+ to: 58826,
2613
+ label: "전남 신안군"
2614
+ },
2615
+ {
2616
+ area: "ISOLATED",
2617
+ from: 58828,
2618
+ to: 58866,
2619
+ label: "전남 신안군"
2620
+ },
2621
+ {
2622
+ area: "ISOLATED",
2623
+ from: 58953,
2624
+ to: 58958,
2625
+ label: "전남 진도군 조도면"
2626
+ },
2627
+ {
2628
+ area: "ISOLATED",
2629
+ from: 59102,
2630
+ to: 59103,
2631
+ label: "전남 완도군 도서"
2632
+ },
2633
+ {
2634
+ area: "ISOLATED",
2635
+ from: 59106,
2636
+ to: 59106,
2637
+ label: "전남 완도군 도서"
2638
+ },
2639
+ {
2640
+ area: "ISOLATED",
2641
+ from: 59127,
2642
+ to: 59127,
2643
+ label: "전남 완도군 도서"
2644
+ },
2645
+ {
2646
+ area: "ISOLATED",
2647
+ from: 59129,
2648
+ to: 59129,
2649
+ label: "전남 완도군 도서"
2650
+ },
2651
+ {
2652
+ area: "ISOLATED",
2653
+ from: 59137,
2654
+ to: 59166,
2655
+ label: "전남 완도군 도서"
2656
+ },
2657
+ {
2658
+ area: "ISOLATED",
2659
+ from: 59421,
2660
+ to: 59421,
2661
+ label: "전남 보성군 장도"
2662
+ },
2663
+ {
2664
+ area: "ISOLATED",
2665
+ from: 59531,
2666
+ to: 59531,
2667
+ label: "전남 고흥군 도서"
2668
+ },
2669
+ {
2670
+ area: "ISOLATED",
2671
+ from: 59551,
2672
+ to: 59551,
2673
+ label: "전남 고흥군 도서"
2674
+ },
2675
+ {
2676
+ area: "ISOLATED",
2677
+ from: 59563,
2678
+ to: 59563,
2679
+ label: "전남 고흥군 도서"
2680
+ },
2681
+ {
2682
+ area: "ISOLATED",
2683
+ from: 59568,
2684
+ to: 59568,
2685
+ label: "전남 고흥군 도서"
2686
+ },
2687
+ {
2688
+ area: "ISOLATED",
2689
+ from: 59650,
2690
+ to: 59650,
2691
+ label: "전남 여수시 도서"
2692
+ },
2693
+ {
2694
+ area: "ISOLATED",
2695
+ from: 59766,
2696
+ to: 59766,
2697
+ label: "전남 여수시 도서"
2698
+ },
2699
+ {
2700
+ area: "ISOLATED",
2701
+ from: 59781,
2702
+ to: 59790,
2703
+ label: "전남 여수시 도서"
2704
+ }
2705
+ ];
2706
+ /**
2707
+ * 우편번호 → 지역 구분. 5자리 숫자가 아니면 `NONE`이다.
2708
+ *
2709
+ * 형식 검증은 배송지 스키마(`/^[0-9]{5}$/`)가 하고, 여기서는 **판정하지 못하면 추가 배송비를 받지 않는다**.
2710
+ * 반대로 두면 우편번호를 잘못 넣은 주문에 근거 없는 금액이 붙는다.
2711
+ */
2712
+ function remoteAreaOf(zipCode) {
2713
+ if (!zipCode || !/^[0-9]{5}$/.test(zipCode)) return "NONE";
2714
+ const zip = Number.parseInt(zipCode, 10);
2715
+ for (const range of REMOTE_AREA_ZIP_RANGES) if (zip >= range.from && zip <= range.to) return range.area;
2716
+ return "NONE";
2717
+ }
2718
+ /** 지역 구분의 안내 이름 — 판정된 범위의 `label`이다. 판정되지 않으면 null */
2719
+ function remoteAreaLabelOf(zipCode) {
2720
+ if (!zipCode || !/^[0-9]{5}$/.test(zipCode)) return null;
2721
+ const zip = Number.parseInt(zipCode, 10);
2722
+ return REMOTE_AREA_ZIP_RANGES.find((r) => zip >= r.from && zip <= r.to)?.label ?? null;
2723
+ }
2724
+ /** 묶음 하나에 붙는 지역 추가 배송비 */
2725
+ function remoteSurchargeOf(policy, area) {
2726
+ if (area === "JEJU") return policy.jejuSurcharge;
2727
+ if (area === "ISOLATED") return policy.isolatedSurcharge;
2728
+ return 0;
2729
+ }
2730
+ /**
2731
+ * 배송 묶음 키 — **같은 출고지 + 같은 배송 정책이면 같은 키다.**
2732
+ *
2733
+ * 키는 주문 시점에 주문상품 행에 스냅샷된다(`order_items.delivery_bundle_key`). 셀러가 주문 뒤
2734
+ * 출고지나 배송비를 바꿔도 그 주문의 묶음은 움직이지 않는다 — 움직이면 이미 환불한 묶음의 키가
2735
+ * 달라져 `orders.delivery_fee_settlements`의 이중 환불 방지 표시가 무력해진다.
2736
+ *
2737
+ * `-`는 「값 없음」이다. 사람이 읽을 수 있게 남기고, 값 자체에 `:`가 들어갈 수 없는 형식만 쓴다
2738
+ * (주소 id는 `adr_…`, 나머지는 열거형·정수).
2739
+ */
2740
+ function deliveryBundleKeyOf(product) {
2741
+ const origin = product.shippingAddressId ?? "-";
2742
+ const threshold = product.deliveryType === "CONDITIONAL_FREE" ? product.conditionalFreeAmount ?? 0 : "-";
2743
+ const fee = product.deliveryType === "FREE" ? 0 : product.deliveryFee;
2744
+ return `b:${origin}:${product.deliveryType}:${fee}:${threshold}`;
2745
+ }
2746
+ /**
2747
+ * 기본 배송비를 면제한 근거 — 면제하지 않았으면 null이다.
2748
+ *
2749
+ * 환불 정산이 근거를 구분해야 한다. 조건부 무료 기준 미달 차감은 **상품별 조건부 무료(`CONDITIONAL`)로
2750
+ * 면제된 묶음만** 대상이다. 스토어 전체 무료 기준(`STORE_THRESHOLD`)이나 무료배송 상품(`FREE`)은 셀러가
2751
+ * 배송비를 받지 않기로 한 것이므로, 일부 취소로 기준이 깨져도 구매자에게 배송비를 물리지 않는다.
2752
+ *
2753
+ * 세 값이 겹치면 `FREE` → `STORE_THRESHOLD` → `CONDITIONAL` 순으로 센 근거를 남긴다.
2754
+ */
2755
+ const deliveryFeeWaiverSchema = z.enum([
2756
+ "FREE",
2757
+ "STORE_THRESHOLD",
2758
+ "CONDITIONAL"
2759
+ ]);
2760
+ /**
2761
+ * 배송비 계산 — **순수 함수다.** 장바구니 요약, 주문서 금액, 결제 시작의 최종 확정,
2762
+ * 주문 배송비 스냅샷이 모두 이 한 곳을 쓴다.
2763
+ *
2764
+ * 순서가 규칙이다. 묶음별 조건부 무료를 먼저 보고, 그다음 스토어 전체 무료 기준으로 기본 배송비를
2765
+ * 한꺼번에 0으로 만들고, 마지막에 지역 추가 배송비를 묶음마다 더한다. 추가 배송비는 무료 기준에
2766
+ * 걸리지 않는다.
2767
+ */
2768
+ function quoteDelivery(input) {
2769
+ const { policy } = input;
2770
+ const area = remoteAreaOf(input.zipCode);
2771
+ const surcharge = remoteSurchargeOf(policy, area);
2772
+ const groups = /* @__PURE__ */ new Map();
2773
+ let productAmount = 0;
2774
+ for (const line of input.lines) {
2775
+ const product = input.productOf(line.productId);
2776
+ if (!product) continue;
2777
+ productAmount += line.amount;
2778
+ const key = deliveryBundleKeyOf(product);
2779
+ const group = groups.get(key) ?? {
2780
+ product,
2781
+ ids: /* @__PURE__ */ new Set(),
2782
+ sum: 0
2783
+ };
2784
+ group.ids.add(line.productId);
2785
+ group.sum += line.amount;
2786
+ groups.set(key, group);
2787
+ }
2788
+ const freeByThreshold = policy.freeThreshold !== null && productAmount >= policy.freeThreshold && groups.size > 0;
2789
+ const bundles = [];
2790
+ for (const [bundleKey, group] of groups) {
2791
+ const { product } = group;
2792
+ const freeThreshold = product.deliveryType === "CONDITIONAL_FREE" && product.conditionalFreeAmount !== null ? product.conditionalFreeAmount ?? 0 : null;
2793
+ const listFee = product.deliveryType === "FREE" ? 0 : product.deliveryFee;
2794
+ const waivedBy = product.deliveryType === "FREE" ? "FREE" : freeByThreshold ? "STORE_THRESHOLD" : freeThreshold !== null && group.sum >= freeThreshold ? "CONDITIONAL" : null;
2795
+ bundles.push({
2796
+ bundleKey,
2797
+ productIds: [...group.ids],
2798
+ fee: waivedBy === null ? listFee : 0,
2799
+ waivedBy,
2800
+ listFee,
2801
+ freeThreshold,
2802
+ remoteSurcharge: surcharge
2803
+ });
2804
+ }
2805
+ const baseFee = bundles.reduce((sum, b) => sum + b.fee, 0);
2806
+ const remoteSurcharge = bundles.reduce((sum, b) => sum + b.remoteSurcharge, 0);
2807
+ return {
2808
+ bundles,
2809
+ baseFee,
2810
+ remoteSurcharge,
2811
+ deliveryFee: baseFee + remoteSurcharge,
2812
+ remoteArea: area,
2813
+ freeByThreshold
2814
+ };
2815
+ }
2816
+ //#endregion
2226
2817
  //#region src/schemas/store.ts
2227
2818
  /**
2228
2819
  * 스토어 코드 규칙 — 스토어프론트 API가 테넌트를 가르는 값이다(`X-Store-Code` 헤더).
@@ -2322,6 +2913,7 @@ const updateStoreRequestSchema = z.object({
2322
2913
  autoDeliverDays: z.number().int().min(0).max(30).optional().describe(autoDeliverDaysDescription),
2323
2914
  autoConfirmDays: z.number().int().min(0).max(30).optional().describe(autoConfirmDaysDescription),
2324
2915
  refundPolicy: refundPolicyPatchSchema.optional().describe("스토어 기본 환불 정책 — 준 항목만 바꾼다. 상품별 정책(`products.refundPolicy`)이 없는 상품에 적용된다"),
2916
+ shippingPolicy: shippingPolicyPatchSchema.optional().describe("스토어 배송비 규칙 — 준 항목만 바꾼다. 기본 배송비, 전체 무료배송 기준액, 제주·도서산간 추가 배송비다. 바꾼 값은 **새 주문서부터** 적용된다(이미 만든 주문서·주문의 배송비는 스냅샷이 원천이다)"),
2325
2917
  termsVersion: policyVersionSchema.optional().describe("이용약관 버전. 올리면 이 약관에 동의한 구매자에게 재동의를 요구한다(`GET /storefront/v1/me`의 `reconsentRequired`)"),
2326
2918
  privacyVersion: policyVersionSchema.optional().describe("개인정보 수집이용 동의 버전. 올리면 구매자에게 재동의를 요구한다"),
2327
2919
  termsUrl: policyDocumentUrlSchema.optional().describe("이용약관 문서 주소(https). 동의 시점의 값이 동의 이력에 복사된다. `null`을 주면 지운다"),
@@ -2346,6 +2938,7 @@ const storeSchema = z.object({
2346
2938
  autoDeliverDays: z.number().int().describe(autoDeliverDaysDescription),
2347
2939
  autoConfirmDays: z.number().int().describe(autoConfirmDaysDescription),
2348
2940
  refundPolicy: refundPolicySchema.describe("스토어 기본 환불 정책. 스토어가 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다"),
2941
+ shippingPolicy: shippingPolicySchema.describe("스토어 배송비 규칙. 스토어가 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다"),
2349
2942
  termsVersion: z.string().describe("현재 이용약관 버전. 가입·재동의가 이 값으로 이력에 남는다"),
2350
2943
  privacyVersion: z.string().describe("현재 개인정보 수집이용 동의 버전"),
2351
2944
  termsVersionUpdatedAt: z.string().nullable().describe("이용약관 버전을 마지막으로 바꾼 시각. 바꾼 적이 없으면 null"),
@@ -2614,7 +3207,12 @@ function createStoreClient(options) {
2614
3207
  * 그 밖의 값은 400 `INVALID_PARAMETER`. 행마다 `testPayment`가 붙는다.
2615
3208
  */
2616
3209
  list: (params) => storeHttp.request("GET", "/orders", pageSchema(orderSummarySchema), { query: params }),
2617
- get: (orderId) => storeHttp.request("GET", path`/orders/${orderId}`, orderSchema)
3210
+ get: (orderId) => storeHttp.request("GET", path`/orders/${orderId}`, orderSchema),
3211
+ /**
3212
+ * `POST /v1/orders/{orderId}/cash-receipt/reissue` — 실패했거나 재시도 대기 중인 현금영수증을 지금 다시 발급·취소한다.
3213
+ * 콘솔 OWNER·ADMIN 전용(PAT·M2M 403 `INSUFFICIENT_ROLE`). 404 `CASH_RECEIPT_NOT_FOUND`, 409 `CASH_RECEIPT_NOT_REISSUABLE`
3214
+ */
3215
+ reissueCashReceipt: (orderId) => storeHttp.request("POST", path`/orders/${orderId}/cash-receipt/reissue`, cashReceiptSchema)
2618
3216
  },
2619
3217
  orderItems: {
2620
3218
  /** `GET /v1/order-items`. `testPayment` 필터는 `orders.list`와 같다 */
@@ -2857,6 +3455,12 @@ get: (params) => storeHttp.request("GET", "/sales-report", salesReportSchema, {
2857
3455
  /** 전송 기록 (최신 적재순, 기본 50건·최대 100건, 보관 30일) */
2858
3456
  listDeliveries: (webhookId, params) => storeHttp.request("GET", path`/webhooks/${webhookId}/deliveries`, z.array(webhookDeliverySchema), { query: params }),
2859
3457
  /**
3458
+ * 전송 기록 한 건의 상세. 목록 항목에 보낸 요청 본문(최대 16KB)과 요청 헤더가 더해진다.
3459
+ * 실패 원인 분류(`failureReason`)·응답 본문(`responseBodyExcerpt`)으로 원인을 찾고,
3460
+ * `resource.id`를 API 로그 조회의 `q`로 넘겨 같은 리소스의 API 요청과 맞춰 본다.
3461
+ */
3462
+ getDelivery: (webhookId, deliveryId) => storeHttp.request("GET", path`/webhooks/${webhookId}/deliveries/${deliveryId}`, webhookDeliveryDetailSchema),
3463
+ /**
2860
3464
  * FAILED 전송 기록 한 건을 같은 eventId로 다시 보낸다(202). 새로 적재된 기록을 돌려주며
2861
3465
  * `redeliveryOf`가 원본 deliveryId다. 원본이 FAILED가 아니면 409 `DELIVERY_NOT_FAILED`,
2862
3466
  * 원본이 이미 재전송됐으면 409 `DELIVERY_ALREADY_REDELIVERED`(원본당 1회, 원본의 `redeliveredAs` 참고),
@@ -3296,4 +3900,4 @@ function createStoreSession(options) {
3296
3900
  };
3297
3901
  }
3298
3902
  //#endregion
3299
- export { ApiError, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_AUTO_TRANSITION_DAYS, MAX_OPTION_GROUPS, MAX_PRODUCT_IMAGES, MAX_VALUES_PER_GROUP, MAX_VARIANTS, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, REFUND_POLICY_DEFAULTS, RESERVED_STORE_CODES, STORE_CODE_PATTERN, STORE_ROLE_SCOPES, STORE_SCOPE_RESOURCES, STORE_SESSION_KEYS, TOSS_WIDGET_CLIENT_KEY_PATTERN, TOSS_WIDGET_KEY_MESSAGE, TOSS_WIDGET_SECRET_KEY_PATTERN, UPLOAD_PURPOSE_LIMITS, VARIANT_STOCK_EXCLUSIVE_MESSAGE, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, addressChangeRequestListSchema, addressTypeSchema, analyticsChannelSchema, analyticsCheckoutOutcomeSchema, analyticsCheckoutSchema, analyticsDeviceRowSchema, analyticsDevicesSchema, analyticsFunnelStepSchema, analyticsOriginSchema, analyticsOverviewSchema, analyticsPagesQuerySchema, analyticsPagesSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsRealtimeSchema, analyticsSearchQuerySchema, analyticsSearchSchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiClientListQuerySchema, apiClientSchema, apiClientSecretRotatedSchema, apiClientStatusSchema, apiClientTypeSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, apiTokenCreateRequestSchema, apiTokenCreatedSchema, apiTokenSchema, approveClaimResultSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, consentActionSchema, consentKindSchema, consentSourceSchema, createPkcePair, createStoreClient, createStoreSession, customerAddressSchema, customerConsentListQuerySchema, customerConsentSchema, customerConsentStateSchema, customerConsentsSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, easyPayProviderResponseSchema, easyPayProviderSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationListQuerySchema, invitationSchema, isReservedStoreCode, isStoreScope, isWebOrigin, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, myAccountSchema, myStoreSchema, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionValueRequestSchema, optionValueSchema, orderItemListQuerySchema, orderItemMemoResultSchema, orderItemPageSchema, orderItemSchema, orderItemStatusSchema, orderListQuerySchema, orderSchema, orderSummaryPageSchema, orderSummarySchema, orderWebhookEventDataSchema, orderWebhookEventSchema, pageInfoSchema, pageQueryShape, pageSchema, paymentAttemptRecordSchema, paymentAttemptStatusSchema, paymentCredentialSetSchema, paymentListQuerySchema, paymentProviderSchema, paymentProviderVerificationSchema, paymentRecordDetailSchema, paymentRecordPageSchema, paymentRecordSchema, paymentRecordStatusSchema, paymentRefundRecordSchema, paymentSettingsSchema, pgEnvironmentSchema, pgPaymentMethodSchema, pgProviderSchema, pgVerificationResultSchema, policyDocumentUrlSchema, policyVersionSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productImageRejection, productImageUrlSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productRefundPolicyOverride, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, proratedRefundBase, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, refreshUserToken, refundPolicyPatchSchema, refundPolicySchema, rejectClaimRequestSchema, resolveRefundPolicy, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, scopeAccessSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, tossPaymentsCredentialsInputSchema, trackingUpdateResultSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadExtensionOf, uploadPurposeSchema, uploadRejection, uploadRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookRedeliverFailedResultSchema, webhookRequestSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema };
3903
+ export { ApiError, CASH_RECEIPT_STATUS_VALUES, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_AUTO_TRANSITION_DAYS, MAX_OPTION_GROUPS, MAX_PRODUCT_IMAGES, MAX_VALUES_PER_GROUP, MAX_VARIANTS, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, REFUND_POLICY_DEFAULTS, REMOTE_AREA_ZIP_RANGES, RESERVED_STORE_CODES, SHIPPING_POLICY_DEFAULTS, STORE_CODE_PATTERN, STORE_ROLE_SCOPES, STORE_SCOPE_RESOURCES, STORE_SESSION_KEYS, TOSS_WIDGET_CLIENT_KEY_PATTERN, TOSS_WIDGET_KEY_MESSAGE, TOSS_WIDGET_SECRET_KEY_PATTERN, UPLOAD_PURPOSE_LIMITS, VARIANT_STOCK_EXCLUSIVE_MESSAGE, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_FAILURE_REASONS, WEBHOOK_HEADERS, WEBHOOK_PAYLOAD_EXCERPT_MAX_BYTES, WEBHOOK_RESOURCE_TYPES, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, addressChangeRequestListSchema, addressTypeSchema, analyticsChannelSchema, analyticsCheckoutOutcomeSchema, analyticsCheckoutSchema, analyticsDeviceRowSchema, analyticsDevicesSchema, analyticsFunnelStepSchema, analyticsOriginSchema, analyticsOverviewSchema, analyticsPagesQuerySchema, analyticsPagesSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsRealtimeSchema, analyticsSearchQuerySchema, analyticsSearchSchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiClientListQuerySchema, apiClientSchema, apiClientSecretRotatedSchema, apiClientStatusSchema, apiClientTypeSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, apiTokenCreateRequestSchema, apiTokenCreatedSchema, apiTokenSchema, approveClaimResultSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, cashReceiptSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimReturnAddressSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, consentActionSchema, consentKindSchema, consentSourceSchema, createPkcePair, createStoreClient, createStoreSession, customerAddressSchema, customerConsentListQuerySchema, customerConsentSchema, customerConsentStateSchema, customerConsentsSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deliveryBundleKeyOf, deliveryFeeWaiverSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, easyPayProviderResponseSchema, easyPayProviderSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationListQuerySchema, invitationSchema, isReservedStoreCode, isStoreScope, isWebOrigin, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, myAccountSchema, myStoreSchema, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionValueRequestSchema, optionValueSchema, orderItemListQuerySchema, orderItemMemoResultSchema, orderItemPageSchema, orderItemSchema, orderItemStatusSchema, orderListQuerySchema, orderSchema, orderSummaryPageSchema, orderSummarySchema, orderWebhookEventDataSchema, orderWebhookEventSchema, pageInfoSchema, pageQueryShape, pageSchema, paymentAttemptRecordSchema, paymentAttemptStatusSchema, paymentCredentialSetSchema, paymentListQuerySchema, paymentProviderSchema, paymentProviderVerificationSchema, paymentRecordDetailSchema, paymentRecordPageSchema, paymentRecordSchema, paymentRecordStatusSchema, paymentRefundRecordSchema, paymentSettingsSchema, pgEnvironmentSchema, pgPaymentMethodSchema, pgProviderSchema, pgVerificationResultSchema, policyDocumentUrlSchema, policyVersionSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productImageRejection, productImageUrlSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productRefundPolicyOverride, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, proratedRefundBase, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, quoteDelivery, refreshUserToken, refundPolicyPatchSchema, refundPolicySchema, rejectClaimRequestSchema, remoteAreaLabelOf, remoteAreaOf, remoteAreaSchema, remoteSurchargeOf, resolveRefundPolicy, resolveShippingPolicy, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, scopeAccessSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, shippingPolicyPatchSchema, shippingPolicySchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, tossPaymentsCredentialsInputSchema, trackingStageResponseSchema, trackingUpdateResultSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadExtensionOf, uploadPurposeSchema, uploadRejection, uploadRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryDetailSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliveryResourceSchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookFailureReasonSchema, webhookRedeliverFailedResultSchema, webhookRequestHeaderSchema, webhookRequestSchema, webhookResourceTypeSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema };