@sayren/store-sdk 0.28.0 → 0.28.1

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.
@@ -3,9 +3,9 @@ import { z } from "zod";
3
3
  /**
4
4
  * 환불 정책 (이슈 #25) — 취소·반품·교환에서 **배송비를 누가 부담하는지**를 정한다.
5
5
  *
6
- * 두 층이다. 스토어 설정(`PATCH /v1/store`의 `refundPolicy`)이 기본값이고, 상품별 정책
6
+ * 두 층이다. 상점 설정(`PATCH /v1/store`의 `refundPolicy`)이 기본값이고, 상품별 정책
7
7
  * (`POST/PUT /v1/products`의 `refundPolicy`)이 그 상품에만 덮어쓴다. 상품이 정하지 않은 항목은
8
- * 스토어 기본을 따르고, 스토어도 정하지 않았으면 `REFUND_POLICY_DEFAULTS`다.
8
+ * 상점 기본을 따르고, 상점도 정하지 않았으면 `REFUND_POLICY_DEFAULTS`다.
9
9
  *
10
10
  * 한 주문에 정책이 섞이면 **상품별로 계산한다**. 배송비 묶음도 상품 단위다(주문서·장바구니의
11
11
  * 배송비 합산과 같은 규칙: 같은 상품의 주문상품이 한 묶음이고 묶음마다 배송비가 붙는다).
@@ -30,7 +30,7 @@ const descriptions = {
30
30
  "판매자 귀책(셀러 직권취소, 불량·오배송 반품)은 빼지 않는다. false면 차감 없이 실결제 금액만 나눠 환불한다. 기본 true",
31
31
  } as const;
32
32
 
33
- /** 유효 환불 정책 — 스토어 기본 + 상품별 덮어쓰기를 합친 결과 */
33
+ /** 유효 환불 정책 — 상점 기본 + 상품별 덮어쓰기를 합친 결과 */
34
34
  export const refundPolicySchema = z.object({
35
35
  preShipmentDeliveryFeeRefund: z.boolean().describe(descriptions.preShipmentDeliveryFeeRefund),
36
36
  returnDeliveryFee: z.number().int().min(0).describe(descriptions.returnDeliveryFee),
@@ -49,7 +49,7 @@ export const refundPolicySchema = z.object({
49
49
  export type RefundPolicy = z.infer<typeof refundPolicySchema>;
50
50
 
51
51
  /**
52
- * 스토어가 정하지 않았을 때 서버가 쓰는 값.
52
+ * 상점이 정하지 않았을 때 서버가 쓰는 값.
53
53
  *
54
54
  * `as const`가 아니라 `RefundPolicy`로 타입을 좁힌다 — 리터럴 타입이면 `Partial<typeof …>`로 일부만 바꿀 때
55
55
  * `true` 자리에 `false`를 넣을 수 없다.
@@ -64,7 +64,7 @@ export const REFUND_POLICY_DEFAULTS: RefundPolicy = {
64
64
  };
65
65
 
66
66
  /**
67
- * 부분 정책 — 스토어 설정 요청과 상품별 덮어쓰기가 쓴다. 준 항목만 바뀐다.
67
+ * 부분 정책 — 상점 설정 요청과 상품별 덮어쓰기가 쓴다. 준 항목만 바뀐다.
68
68
  *
69
69
  * `strictObject`라 모르는 키는 400이다. 오타(`returnFee` 등)를 조용히 무시하면 셀러는 저장됐다고
70
70
  * 믿는데 정책은 기본값으로 남는다.
@@ -93,7 +93,7 @@ export type RefundPolicyPatch = z.infer<typeof refundPolicyPatchSchema>;
93
93
 
94
94
  /**
95
95
  * 상품별 덮어쓰기 — 쿠폰 조건 미달 차감(`couponShortfallCharge`)은 주문 단위 규칙이라 상품이 정하지 않는다.
96
- * 스토어 정책만 읽는다(이슈 #47).
96
+ * 상점 정책만 읽는다(이슈 #47).
97
97
  */
98
98
  export const productRefundPolicyPatchSchema = refundPolicyPatchSchema.omit({
99
99
  couponShortfallCharge: true,
@@ -101,12 +101,12 @@ export const productRefundPolicyPatchSchema = refundPolicyPatchSchema.omit({
101
101
 
102
102
  /** 정책 해석에 쓰는 상품 값 — 상품별 덮어쓰기 */
103
103
  export interface RefundPolicyProductInput {
104
- /** 상품별 덮어쓰기. 없으면 스토어 기본을 그대로 쓴다 */
104
+ /** 상품별 덮어쓰기. 없으면 상점 기본을 그대로 쓴다 */
105
105
  refundPolicy?: RefundPolicyPatch | null;
106
106
  }
107
107
 
108
108
  /**
109
- * 유효 정책 해석 — 상품별 덮어쓰기 → 스토어 기본 → `REFUND_POLICY_DEFAULTS` 순이다.
109
+ * 유효 정책 해석 — 상품별 덮어쓰기 → 상점 기본 → `REFUND_POLICY_DEFAULTS` 순이다.
110
110
  *
111
111
  * 반품·교환 배송비의 원천은 정책 하나다(이슈 #57 결정 3). 상품의 레거시 반품비·교환비 필드와의 연결은 없앴고,
112
112
  * 레거시 값은 마이그레이션이 상품별 정책으로 옮겼다.
@@ -122,15 +122,15 @@ export function resolveRefundPolicy(
122
122
  /**
123
123
  * 상품의 **덮어쓰기 자체** — 덮어쓰기가 없으면 null.
124
124
  *
125
- * `resolveRefundPolicy`가 합친 결과로는 "상품이 정한 값"과 "스토어 기본을 물려받은 값"을 가를 수 없다.
126
- * 관리 API 응답의 `refundPolicy`와 콘솔 폼의 덮어쓰기 스위치가 이 함수를 원천으로 쓴다 — 가르지 못하면
127
- * 스토어 기본을 쓰는 상품을 저장할 때 그 순간의 기본값이 상품에 박힌다.
125
+ * `resolveRefundPolicy`가 합친 결과로는 "상품이 정한 값"과 "상점 기본을 물려받은 값"을 가를 수 없다.
126
+ * 관리 API 응답의 `refundPolicy`와 상점 플랫폼 폼의 덮어쓰기 스위치가 이 함수를 원천으로 쓴다 — 가르지 못하면
127
+ * 상점 기본을 쓰는 상품을 저장할 때 그 순간의 기본값이 상품에 박힌다.
128
128
  */
129
129
  export function productRefundPolicyOverride(
130
130
  product?: RefundPolicyProductInput | null,
131
131
  ): RefundPolicyPatch | null {
132
132
  if (product?.refundPolicy == null) return null;
133
- // 쿠폰 조건 미달 차감은 주문 단위라 스토어만 정한다 — 상품 행에 키가 있어도 덮어쓰기로 읽지 않는다(이슈 #47)
133
+ // 쿠폰 조건 미달 차감은 주문 단위라 상점만 정한다 — 상품 행에 키가 있어도 덮어쓰기로 읽지 않는다(이슈 #47)
134
134
  const { couponShortfallCharge: _storeOnly, ...override } = dropUndefined(product.refundPolicy);
135
135
  return override;
136
136
  }
@@ -16,7 +16,7 @@ import {
16
16
  * 달라 저장하지 않는다. 추가금은 조합별이 아니라 **값별**로 둔다 — 조합 옵션으로 불러오면 조합 추가금은 고른 값 추가금의 합이다.
17
17
  */
18
18
 
19
- /** 스토어당 저장 옵션 최대 개수 */
19
+ /** 상점당 저장 옵션 최대 개수 */
20
20
  export const MAX_SAVED_OPTION_SETS = 200;
21
21
  /** 저장 옵션 이름 최대 글자 수 */
22
22
  export const MAX_SAVED_OPTION_SET_NAME = 50;
@@ -2,10 +2,10 @@ import { z } from "zod";
2
2
  import type { StoreRole } from "./auth";
3
3
 
4
4
  /**
5
- * 스토어 스코프 카탈로그 (auth-spec §7) — 형식 `{리소스}:{r|rw}`, rw는 r을 함의한다.
5
+ * 상점 스코프 카탈로그 (auth-spec §7) — 형식 `{리소스}:{r|rw}`, rw는 r을 함의한다.
6
6
  *
7
7
  * 관리 API가 요구하는 스코프와 역할별 자동 부여 스코프의 **단일 원천**이다.
8
- * 서버(apps/api)가 이 상수를 그대로 가져다 역할→스코프를 산출하므로, 콘솔 권한 매트릭스와
8
+ * 서버(apps/api)가 이 상수를 그대로 가져다 역할→스코프를 산출하므로, 상점 플랫폼 권한 매트릭스와
9
9
  * 서버 인가가 갈라지지 않는다.
10
10
  */
11
11
 
@@ -49,7 +49,7 @@ export function isStoreScope(scope: string): boolean {
49
49
 
50
50
  /**
51
51
  * 역할별 자동 부여 스코프 (auth-spec §7).
52
- * 계정 토큰으로 스토어를 호출할 때의 스코프는 그 스토어에서의 역할로 이 표에서 결정된다.
52
+ * 계정 토큰으로 상점을 호출할 때의 스코프는 그 상점에서의 역할로 이 표에서 결정된다.
53
53
  */
54
54
  export const STORE_ROLE_SCOPES = {
55
55
  OWNER: [
@@ -9,7 +9,7 @@ import { type DeliveryType, type FulfillmentType, requiresShipping } from "./ful
9
9
  * (`deliveryBundleKeyOf`). 이슈 #25의 「묶음 = 같은 상품」 규칙을 일반화한 것이다 — 키를 상품 id에서
10
10
  * 정책 지문으로 바꿨고, 주문상품이 주문 시점 키를 스냅샷해(`order_items.delivery_bundle_key`)
11
11
  * 환불 정산(`refund-settlement.ts`)과 주문 스냅샷(`orders.delivery_fee_settlements`)이 같은 키를 쓴다.
12
- * 2. **무료 기준**: 상품별 조건부 무료(`CONDITIONAL_FREE`, 묶음 소계 기준)와 스토어 전체 무료 기준
12
+ * 2. **무료 기준**: 상품별 조건부 무료(`CONDITIONAL_FREE`, 묶음 소계 기준)와 상점 전체 무료 기준
13
13
  * (`freeThreshold`, 주문 상품 금액 합 기준)이 있다. 둘 중 하나라도 충족하면 그 배송비는 0이다.
14
14
  * 3. **지역 추가 배송비**: 우편번호가 제주·도서산간이면 **묶음마다** 추가된다. 묶음이 곧 한 번의 발송이라
15
15
  * 두 출고지에서 각각 보내면 추가 운임도 두 번 든다.
@@ -20,21 +20,21 @@ import { type DeliveryType, type FulfillmentType, requiresShipping } from "./ful
20
20
  * 계산은 `quoteDelivery` 하나다. 장바구니·주문서·결제 시작·환불 정산이 모두 이 함수를 쓴다 —
21
21
  * 네 곳이 각자 계산하면 화면 금액과 결제 금액이 갈라진다.
22
22
  *
23
- * **비실물 줄은 배송 계산에서 빠진다**(이슈 #48, `requiresShipping`). 묶음을 만들지 않고, 스토어 전체 무료 기준의
23
+ * **비실물 줄은 배송 계산에서 빠진다**(이슈 #48, `requiresShipping`). 묶음을 만들지 않고, 상점 전체 무료 기준의
24
24
  * 판정 금액(`shippableAmount`)에도 넣지 않는다 — 디지털 상품 금액이 실물 배송비를 면제시키지 않게 한다.
25
25
  */
26
26
 
27
27
  const descriptions = {
28
28
  baseDeliveryFee:
29
- "스토어 기본 배송비. 원 단위. 상품 등록·수정에서 `deliveryFee`를 생략하면 이 값을 쓴다. 기본 3000",
29
+ "상점 기본 배송비. 원 단위. 상품 등록·수정에서 `deliveryFee`를 생략하면 이 값을 쓴다. 기본 3000",
30
30
  freeThreshold:
31
- "스토어 전체 무료배송 기준액. 주문 상품 금액 합이 이 값 이상이면 모든 묶음의 배송비가 0이다. " +
31
+ "상점 전체 무료배송 기준액. 주문 상품 금액 합이 이 값 이상이면 모든 묶음의 배송비가 0이다. " +
32
32
  "null이면 전체 무료배송을 쓰지 않는다(상품별 조건부 무료는 그대로 동작한다). 기본 null",
33
33
  jejuSurcharge: "제주 지역 추가 배송비. 원 단위. **배송 묶음마다** 더한다. 기본 0",
34
34
  isolatedSurcharge: "도서산간 지역 추가 배송비. 원 단위. **배송 묶음마다** 더한다. 기본 0",
35
35
  } as const;
36
36
 
37
- /** 유효 배송비 정책 — 스토어가 정하지 않은 항목은 `SHIPPING_POLICY_DEFAULTS`로 채운 값이다 */
37
+ /** 유효 배송비 정책 — 상점이 정하지 않은 항목은 `SHIPPING_POLICY_DEFAULTS`로 채운 값이다 */
38
38
  export const shippingPolicySchema = z.object({
39
39
  baseDeliveryFee: z.number().int().min(0).describe(descriptions.baseDeliveryFee),
40
40
  freeThreshold: z.number().int().min(1).nullable().describe(descriptions.freeThreshold),
@@ -45,7 +45,7 @@ export const shippingPolicySchema = z.object({
45
45
  export type ShippingPolicy = z.infer<typeof shippingPolicySchema>;
46
46
 
47
47
  /**
48
- * 스토어가 정하지 않았을 때 서버가 쓰는 값.
48
+ * 상점이 정하지 않았을 때 서버가 쓰는 값.
49
49
  *
50
50
  * `as const`가 아니라 `ShippingPolicy`로 좁힌다 — 리터럴 타입이면 `Partial<typeof …>`로 일부만 바꿀 때
51
51
  * `null` 자리에 숫자를 넣을 수 없다(`REFUND_POLICY_DEFAULTS`와 같은 이유).
@@ -72,7 +72,7 @@ export const shippingPolicyPatchSchema = z.strictObject({
72
72
 
73
73
  export type ShippingPolicyPatch = z.infer<typeof shippingPolicyPatchSchema>;
74
74
 
75
- /** 유효 정책 해석 — 스토어가 정한 항목 + `SHIPPING_POLICY_DEFAULTS` */
75
+ /** 유효 정책 해석 — 상점이 정한 항목 + `SHIPPING_POLICY_DEFAULTS` */
76
76
  export function resolveShippingPolicy(patch?: ShippingPolicyPatch | null): ShippingPolicy {
77
77
  if (!patch) return { ...SHIPPING_POLICY_DEFAULTS };
78
78
  const given = Object.fromEntries(
@@ -89,10 +89,10 @@ export const remoteAreaSchema = z.enum(["NONE", "JEJU", "ISOLATED"]);
89
89
  export type RemoteArea = z.infer<typeof remoteAreaSchema>;
90
90
 
91
91
  /**
92
- * 제주·도서산간 우편번호 범위 — **코드 상수다.** 금액은 스토어가 정하고 범위는 플랫폼이 고정한다.
92
+ * 제주·도서산간 우편번호 범위 — **코드 상수다.** 금액은 상점이 정하고 범위는 플랫폼이 고정한다.
93
93
  *
94
94
  * 국내 택배사가 공표하는 제주·도서산간 할증 구간을 5자리 우편번호 범위로 옮긴 표다. 셀러마다
95
- * 범위가 다르면 같은 주소에 다른 금액이 나가고 CS가 셀러 수만큼 갈라진다. 스토어별 범위 편집은
95
+ * 범위가 다르면 같은 주소에 다른 금액이 나가고 CS가 셀러 수만큼 갈라진다. 상점별 범위 편집은
96
96
  * 후속 과제이고, 지금은 이 표가 유일한 원천이다.
97
97
  *
98
98
  * ## 근거
@@ -131,7 +131,7 @@ export type RemoteArea = z.infer<typeof remoteAreaSchema>;
131
131
  * 도로명주소·건물관리번호 단위 판정은 후속 과제다.
132
132
  *
133
133
  * 범위는 **닫힌 구간**(`from` ≤ zip ≤ `to`)이고 겹치지 않는다. 앞에서부터 처음 맞는 항목을 쓴다.
134
- * 순서는 콘솔 「추가 배송비 지역」 표의 행 순서이기도 하다(`remoteAreaGroups`). 제주를 먼저 두고
134
+ * 순서는 상점 플랫폼 「추가 배송비 지역」 표의 행 순서이기도 하다(`remoteAreaGroups`). 제주를 먼저 두고
135
135
  * 도서산간은 우편번호 오름차순으로 둔다.
136
136
  */
137
137
  export const REMOTE_AREA_ZIP_RANGES: ReadonlyArray<{
@@ -141,7 +141,7 @@ export const REMOTE_AREA_ZIP_RANGES: ReadonlyArray<{
141
141
  readonly label: string;
142
142
  }> = [
143
143
  // 제주 전지역. 추자면·우도는 택배사 표에서 더 높은 운임이지만 공개 계약의 지역 구분은
144
- // JEJU 하나이고 금액은 스토어가 정한다.
144
+ // JEJU 하나이고 금액은 상점이 정한다.
145
145
  { area: "JEJU", from: 63_000, to: 63_644, label: "제주특별자치도" },
146
146
  // 무의도·실미도·팔미도. 택배사 표는 「인천 중구 섬지역」이지만 2026-07-01 행정체제 개편으로
147
147
  // 중구가 나뉘면서 무의동은 영종구 소속이다.
@@ -220,7 +220,7 @@ export interface DeliveryBundleProduct {
220
220
  deliveryType: DeliveryType;
221
221
  deliveryFee: number;
222
222
  conditionalFreeAmount: number | null;
223
- /** 출고지 주소 id(`seller_addresses`). null이면 스토어 기본 출고지를 쓰는 상품끼리 한 묶음이다 */
223
+ /** 출고지 주소 id(`seller_addresses`). null이면 상점 기본 출고지를 쓰는 상품끼리 한 묶음이다 */
224
224
  shippingAddressId?: string | null;
225
225
  /**
226
226
  * 상품 유형 (이슈 #48). 생략·null이면 배송 상품이다. 배송이 필요 없는 유형은 묶음을 만들지 않는다.
@@ -265,7 +265,7 @@ export interface DeliveryFeeLine {
265
265
  * 기본 배송비를 면제한 근거 — 면제하지 않았으면 null이다.
266
266
  *
267
267
  * 환불 정산이 근거를 구분해야 한다. 조건부 무료 기준 미달 차감은 **상품별 조건부 무료(`CONDITIONAL`)로
268
- * 면제된 묶음만** 대상이다. 스토어 전체 무료 기준(`STORE_THRESHOLD`)이나 무료배송 상품(`FREE`)은 셀러가
268
+ * 면제된 묶음만** 대상이다. 상점 전체 무료 기준(`STORE_THRESHOLD`)이나 무료배송 상품(`FREE`)은 셀러가
269
269
  * 배송비를 받지 않기로 한 것이므로, 일부 취소로 기준이 깨져도 구매자에게 배송비를 물리지 않는다.
270
270
  *
271
271
  * 세 값이 겹치면 `FREE` → `STORE_THRESHOLD` → `CONDITIONAL` 순으로 센 근거를 남긴다.
@@ -277,7 +277,7 @@ export type DeliveryFeeWaiver = z.infer<typeof deliveryFeeWaiverSchema>;
277
277
  /** 묶음 하나의 견적 */
278
278
  export interface DeliveryBundleQuote {
279
279
  bundleKey: string;
280
- /** 이 묶음에 든 상품 id — 스냅샷 검증과 콘솔 안내에 쓴다 */
280
+ /** 이 묶음에 든 상품 id — 스냅샷 검증과 상점 플랫폼 안내에 쓴다 */
281
281
  productIds: string[];
282
282
  /** 실제 청구한 기본 배송비 — 무료배송·무료 기준 충족이면 0 */
283
283
  fee: number;
@@ -301,10 +301,10 @@ export interface DeliveryQuote {
301
301
  /** 청구할 배송비 총액 = `baseFee` + `remoteSurcharge` */
302
302
  deliveryFee: number;
303
303
  remoteArea: RemoteArea;
304
- /** 스토어 전체 무료 기준(`freeThreshold`) 충족으로 기본 배송비를 면제했는가 */
304
+ /** 상점 전체 무료 기준(`freeThreshold`) 충족으로 기본 배송비를 면제했는가 */
305
305
  freeByThreshold: boolean;
306
306
  /**
307
- * 배송이 필요한 줄의 상품 금액 합 — 스토어 전체 무료 기준의 판정 금액이다(이슈 #48). 비실물 줄은 빠진다.
307
+ * 배송이 필요한 줄의 상품 금액 합 — 상점 전체 무료 기준의 판정 금액이다(이슈 #48). 비실물 줄은 빠진다.
308
308
  * 주문 상품 금액 합(`productAmount`)과 다를 수 있다.
309
309
  */
310
310
  shippableAmount: number;
@@ -325,7 +325,7 @@ export interface DeliveryQuoteInput {
325
325
  * 배송비 계산 — **순수 함수다.** 장바구니 요약, 주문서 금액, 결제 시작의 최종 확정,
326
326
  * 주문 배송비 스냅샷이 모두 이 한 곳을 쓴다.
327
327
  *
328
- * 순서가 규칙이다. 묶음별 조건부 무료를 먼저 보고, 그다음 스토어 전체 무료 기준으로 기본 배송비를
328
+ * 순서가 규칙이다. 묶음별 조건부 무료를 먼저 보고, 그다음 상점 전체 무료 기준으로 기본 배송비를
329
329
  * 한꺼번에 0으로 만들고, 마지막에 지역 추가 배송비를 묶음마다 더한다. 추가 배송비는 무료 기준에
330
330
  * 걸리지 않는다.
331
331
  */
@@ -363,7 +363,7 @@ export function quoteDelivery(input: DeliveryQuoteInput): DeliveryQuote {
363
363
  product.deliveryType === "CONDITIONAL_FREE" && product.conditionalFreeAmount !== null;
364
364
  const freeThreshold = conditionalFree ? (product.conditionalFreeAmount ?? 0) : null;
365
365
  const listFee = product.deliveryType === "FREE" ? 0 : product.deliveryFee;
366
- // 면제 근거는 센 것부터 본다 — 무료배송 상품 > 스토어 전체 무료 기준 > 상품별 조건부 무료.
366
+ // 면제 근거는 센 것부터 본다 — 무료배송 상품 > 상점 전체 무료 기준 > 상품별 조건부 무료.
367
367
  // 환불 정산의 미달 차감은 CONDITIONAL만 대상이므로 근거를 뭉개면 안 된다.
368
368
  const waivedBy: DeliveryFeeWaiver | null =
369
369
  product.deliveryType === "FREE"
@@ -4,7 +4,7 @@ import { refundPolicyPatchSchema, refundPolicySchema } from "./refund-policy";
4
4
  import { shippingPolicyPatchSchema, shippingPolicySchema } from "./shipping-policy";
5
5
 
6
6
  /**
7
- * 스토어 코드 규칙 — 스토어프론트 API가 테넌트를 가르는 값이다(`X-Store-Code` 헤더).
7
+ * 상점 코드 규칙 — 스토어프론트 API가 테넌트를 가르는 값이다(`X-Store-Code` 헤더).
8
8
  * **개설할 때 서버가 랜덤으로 발급하고** 이후 바꿀 수 없다. 이미 나간 링크와 승인된 결제가
9
9
  * 이 값에 묶여 있어서 바꾸면 함께 깨진다.
10
10
  *
@@ -12,7 +12,7 @@ import { shippingPolicyPatchSchema, shippingPolicySchema } from "./shipping-poli
12
12
  */
13
13
  export const STORE_CODE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])$/;
14
14
 
15
- /** 플랫폼이 쓰거나 쓸 호스트 이름 — 스토어 코드로 내주지 않는다 */
15
+ /** 플랫폼이 쓰거나 쓸 호스트 이름 — 상점 코드로 내주지 않는다 */
16
16
  export const RESERVED_STORE_CODES: readonly string[] = [
17
17
  "www",
18
18
  "api",
@@ -72,7 +72,7 @@ export const storeCodeSchema = z
72
72
  )
73
73
  .refine(
74
74
  (code) => !isReservedStoreCode(code),
75
- "플랫폼이 쓰는 이름이라 스토어 코드로 쓸 수 없습니다",
75
+ "플랫폼이 쓰는 이름이라 상점 코드로 쓸 수 없습니다",
76
76
  );
77
77
 
78
78
  /** `https://shop.example.com`처럼 경로·쿼리 없는 http(s) 오리진인가 */
@@ -108,7 +108,7 @@ export const MAX_ANALYTICS_ALLOWED_ORIGINS = 20;
108
108
  export const MAX_AUTO_TRANSITION_DAYS = 30;
109
109
 
110
110
  /**
111
- * 약관 버전 — 스토어가 정하는 문자열이다(`1`·`2`·`2026-09-26`·`v1.2` 등). 형식은 강제하지 않고 값이 다르면 다른 버전이다.
111
+ * 약관 버전 — 상점이 정하는 문자열이다(`1`·`2`·`2026-09-26`·`v1.2` 등). 형식은 강제하지 않고 값이 다르면 다른 버전이다.
112
112
  * 올리면 그 약관에 동의한 구매자의 `reconsentRequired`가 켜지고, 재동의가 이력에 남는다. 되돌리면 재동의 요구도 풀린다.
113
113
  */
114
114
  export const policyVersionSchema = z
@@ -183,12 +183,12 @@ export const updateStoreRequestSchema = z
183
183
  refundPolicy: refundPolicyPatchSchema
184
184
  .optional()
185
185
  .describe(
186
- "스토어 기본 환불 정책 — 준 항목만 바꾼다. 상품별 정책(`products.refundPolicy`)이 없는 상품에 적용된다",
186
+ "상점 기본 환불 정책 — 준 항목만 바꾼다. 상품별 정책(`products.refundPolicy`)이 없는 상품에 적용된다",
187
187
  ),
188
188
  shippingPolicy: shippingPolicyPatchSchema
189
189
  .optional()
190
190
  .describe(
191
- "스토어 배송비 규칙 — 준 항목만 바꾼다. 기본 배송비, 전체 무료배송 기준액, 제주·도서산간 추가 배송비다. " +
191
+ "상점 배송비 규칙 — 준 항목만 바꾼다. 기본 배송비, 전체 무료배송 기준액, 제주·도서산간 추가 배송비다. " +
192
192
  "바꾼 값은 **새 주문서부터** 적용된다(이미 만든 주문서·주문의 배송비는 스냅샷이 원천이다)",
193
193
  ),
194
194
  termsVersion: policyVersionSchema
@@ -234,10 +234,10 @@ export const storeSchema = z.object({
234
234
  autoDeliverDays: z.number().int().describe(autoDeliverDaysDescription),
235
235
  autoConfirmDays: z.number().int().describe(autoConfirmDaysDescription),
236
236
  refundPolicy: refundPolicySchema.describe(
237
- "스토어 기본 환불 정책. 스토어가 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다",
237
+ "상점 기본 환불 정책. 상점이 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다",
238
238
  ),
239
239
  shippingPolicy: shippingPolicySchema.describe(
240
- "스토어 배송비 규칙. 스토어가 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다",
240
+ "상점 배송비 규칙. 상점이 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다",
241
241
  ),
242
242
  termsVersion: z.string().describe("현재 이용약관 버전. 가입·재동의가 이 값으로 이력에 남는다"),
243
243
  privacyVersion: z.string().describe("현재 개인정보 수집이용 동의 버전"),
@@ -270,7 +270,7 @@ export type StoreMember = z.infer<typeof storeMemberSchema>;
270
270
 
271
271
  /**
272
272
  * 초대 메일 발송 결과 — `SENDING`은 발송 요청 중(결과를 아직 적지 못함), `NOT_CONFIGURED`는 메일 발송이 설정되지 않아
273
- * 보내지 않은 것이다. 스토어의 월 메일 한도(요금제)를 다 써서 보내지 않았으면 `FAILED` + `deliveryErrorCode: MAIL_QUOTA_EXCEEDED`다. 발송 실패는 초대를 무효로 만들지 않는다 — 재발송으로 다시 보낸다
273
+ * 보내지 않은 것이다. 상점의 월 메일 한도(요금제)를 다 써서 보내지 않았으면 `FAILED` + `deliveryErrorCode: MAIL_QUOTA_EXCEEDED`다. 발송 실패는 초대를 무효로 만들지 않는다 — 재발송으로 다시 보낸다
274
274
  */
275
275
  export const invitationDeliveryStatusSchema = z.enum([
276
276
  "SENDING",
@@ -4,7 +4,7 @@ import { RESERVED_STORE_CODES } from "./store";
4
4
  /**
5
5
  * 스토어프론트 호스팅 (이슈 #53, M1: 템플릿 발행) — 관리 API `/v1/storefront/**`의 계약이다.
6
6
  *
7
- * 셀러는 템플릿을 골라 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다. 스토어를 만들어도 사이트는 생기지 않고
7
+ * 셀러는 템플릿을 골라 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다. 상점을 만들어도 사이트는 생기지 않고
8
8
  * `POST /v1/storefront/site`가 유일한 생성 경로다. 템플릿은 만들 때만 고르고 바꾸지 않는다. 발행은 버전 단위이고,
9
9
  * 되돌리기는 이전 버전을 다시 발행하는 것이다(재빌드 없음). 상태의 원천은 api DB이고 Cloudflare 쪽 반영
10
10
  * (라우팅 표·사이트 Worker)은 커밋 뒤 비동기다 — 반영 상태는 `provisioning`으로 본다.
@@ -15,13 +15,13 @@ import { RESERVED_STORE_CODES } from "./store";
15
15
  * 오류 코드:
16
16
  * - 403 `STOREFRONT_HOSTING_DISABLED` — 플랫폼 스위치가 꺼져 새 사이트를 만들 수 없다
17
17
  * - 404 `STOREFRONT_SITE_NOT_FOUND` · `STOREFRONT_TEMPLATE_NOT_FOUND` · `STOREFRONT_VERSION_NOT_FOUND`
18
- * - 409 `STOREFRONT_SITE_EXISTS` — 이미 사이트가 있다(스토어당 1개)
18
+ * - 409 `STOREFRONT_SITE_EXISTS` — 이미 사이트가 있다(상점당 1개)
19
19
  * - 409 `STOREFRONT_SLUG_TAKEN` — 다른 사이트가 쓰거나 보류 중인 슬러그다
20
- * - 409 `STOREFRONT_SLUG_CHANGE_LIMIT` — 슬러그는 스토어당 30일에 3번까지 바꾼다
21
- * - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는 스토어당 30일에 3번까지 만든다
20
+ * - 409 `STOREFRONT_SLUG_CHANGE_LIMIT` — 슬러그는 상점당 30일에 3번까지 바꾼다
21
+ * - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는 상점당 30일에 3번까지 만든다
22
22
  * - 409 `STOREFRONT_SITE_CHANGED` — 읽은 상태(`expected*`)와 현재 상태가 다르다. 다시 읽고 시도한다
23
23
  * - 409 `STOREFRONT_SITE_NOT_ACTIVE` — 정지(`SUSPENDED`)된 사이트는 바꾸거나 끌 수 없다
24
- * - 409 `STORE_SUSPENDED` — 정지된 스토어에는 사이트를 만들 수 없다
24
+ * - 409 `STORE_SUSPENDED` — 정지된 상점에는 사이트를 만들 수 없다
25
25
  * - 409 `STOREFRONT_VERSION_NOT_READY` — 빌드가 끝나지 않았거나 실패한 버전은 발행할 수 없다
26
26
  * - 409 `STOREFRONT_NO_ROLLBACK_TARGET` — 되돌릴 이전 발행 버전이 없다
27
27
  * - 409 `STOREFRONT_NOT_READY` — 공개 전환: 판매 중인 상품(샘플 제외)이 없다(이슈 #70). 상품을 등록한 뒤 공개한다
@@ -47,7 +47,7 @@ import { RESERVED_STORE_CODES } from "./store";
47
47
  * - 502 `STOREFRONT_SANDBOX_UNAVAILABLE` — 편집 Sandbox 호출이 실패했다. 잠시 뒤 다시 한다
48
48
  *
49
49
  * 소스 내려받기·올리기(#60, `/v1/storefront/site/source/**`, `sayren` CLI):
50
- * - 409 `SOURCE_CHANGED` — 작업 트리가 내려받은 뒤(`expectedBaseRef`) 바뀌었다(콘솔·AI 편집). `error.details.currentRef`
50
+ * - 409 `SOURCE_CHANGED` — 작업 트리가 내려받은 뒤(`expectedBaseRef`) 바뀌었다(상점 플랫폼·AI 편집). `error.details.currentRef`
51
51
  * - 409 `EDIT_SESSION_OPEN` — 편집 환경이 열려 있다. `endEditSession: true`로 끄고 진행할 수 있다
52
52
  * - 409 `SOURCE_BLOBS_MISSING` — 올리지 않은 파일 내용이 있다(`error.details.hashes`). `POST source/blobs`로 올리고 다시 한다
53
53
  * - 409 `DEPENDENCY_REJECTED` — 쓸 수 없는 npm 의존성이다(`error.details.rejected: [{ name, version, reason, detail }]`)
@@ -60,12 +60,12 @@ import { RESERVED_STORE_CODES } from "./store";
60
60
  // ─── 슬러그 ───────────────────────────────────────────────
61
61
 
62
62
  /**
63
- * 슬러그 형식 — 스토어 코드와 같은 DNS 라벨 규칙(소문자·숫자·하이픈 3~30자, 하이픈으로 시작·끝 불가)에
63
+ * 슬러그 형식 — 상점 코드와 같은 DNS 라벨 규칙(소문자·숫자·하이픈 3~30자, 하이픈으로 시작·끝 불가)에
64
64
  * 연속 하이픈 금지를 더한다. `--`는 버전 미리보기 주소(`v3--{slug}`)와 punycode(`xn--`)가 쓰는 구분자다.
65
65
  */
66
66
  export const STOREFRONT_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])$/;
67
67
 
68
- /** 호스팅 도메인에서 플랫폼이 쓰거나 쓸 이름 — 스토어 코드 예약어에 호스팅 전용 이름을 더한다 */
68
+ /** 호스팅 도메인에서 플랫폼이 쓰거나 쓸 이름 — 상점 코드 예약어에 호스팅 전용 이름을 더한다 */
69
69
  export const RESERVED_STOREFRONT_SLUGS: readonly string[] = [
70
70
  ...RESERVED_STORE_CODES,
71
71
  "mcp",
@@ -94,7 +94,7 @@ export const STOREFRONT_EDIT_PREVIEW_SUFFIX = "-preview";
94
94
 
95
95
  /**
96
96
  * 공식 쇼케이스 슬러그 접두사(이슈 #72) — `demo-{템플릿 id}`는 템플릿 미리보기용 쇼케이스 사이트 주소다. 쇼케이스로 지정된
97
- * 스토어만 쓸 수 있다(서버가 스토어 용도로 판정한다). 공식 미리보기를 흉내 내는 주소를 막는다
97
+ * 상점만 쓸 수 있다(서버가 상점 용도로 판정한다). 공식 미리보기를 흉내 내는 주소를 막는다
98
98
  */
99
99
  export const STOREFRONT_SHOWCASE_SLUG_PREFIX = "demo-";
100
100
 
@@ -132,9 +132,9 @@ export const storefrontSlugSchema = z
132
132
  export const STOREFRONT_SLUG_HOLD_DAYS = 90;
133
133
  /** 슬러그 변경 한도 — 이 기간(일) 안에 */
134
134
  export const STOREFRONT_SLUG_CHANGE_WINDOW_DAYS = 30;
135
- /** 슬러그 변경 한도 — 횟수(스토어 단위. 끄고 다시 만들어도 이어서 센다) */
135
+ /** 슬러그 변경 한도 — 횟수(상점 단위. 끄고 다시 만들어도 이어서 센다) */
136
136
  export const STOREFRONT_SLUG_CHANGE_LIMIT = 3;
137
- /** 사이트 만들기 한도 — 같은 창(30일) 안의 횟수(스토어 단위). 끄고 만들기를 되풀이해 이름을 묶어 두지 못하게 한다 */
137
+ /** 사이트 만들기 한도 — 같은 창(30일) 안의 횟수(상점 단위). 끄고 만들기를 되풀이해 이름을 묶어 두지 못하게 한다 */
138
138
  export const STOREFRONT_SITE_CREATE_LIMIT = 3;
139
139
 
140
140
  // ─── 템플릿 ───────────────────────────────────────────────
@@ -146,7 +146,7 @@ export const storefrontTemplateSchema = z.object({
146
146
  description: z.string().describe("템플릿 설명"),
147
147
  thumbnailUrl: z.string().nullable().describe("썸네일 이미지 주소. 없으면 null"),
148
148
  version: z.string().describe("현재 템플릿 버전. 새 사이트는 이 버전으로 만든다"),
149
- isDefault: z.boolean().describe("기본 템플릿인가 (스토어 생성 시 자동 생성에 쓴다)"),
149
+ isDefault: z.boolean().describe("기본 템플릿인가 (상점 생성 시 자동 생성에 쓴다)"),
150
150
  tags: z
151
151
  .array(z.string())
152
152
  .default([])
@@ -207,7 +207,7 @@ export const storefrontVersionSchema = z.object({
207
207
  .nullable()
208
208
  .optional()
209
209
  .describe(
210
- "만든 경로 (`CONSOLE` 콘솔 · `AI` AI 에이전트(MCP) · `CLI` `sayren` CLI · `API` 관리 API · `SYSTEM` 자동). 늘어날 수 있다",
210
+ "만든 경로 (`CONSOLE` 상점 플랫폼 · `AI` AI 에이전트(MCP) · `CLI` `sayren` CLI · `API` 관리 API · `SYSTEM` 자동). 늘어날 수 있다",
211
211
  ),
212
212
  createdAt: z.string().describe("만든 시각 (ISO 8601)"),
213
213
  });
@@ -217,7 +217,7 @@ export type StorefrontVersion = z.infer<typeof storefrontVersionSchema>;
217
217
 
218
218
  export const storefrontSiteStatusSchema = z
219
219
  .enum(["ACTIVE", "SUSPENDED", "CLOSED"])
220
- .describe("`ACTIVE` 운영 중 · `SUSPENDED` 운영자 정지 · `CLOSED` 호스팅 끔·스토어 폐쇄");
220
+ .describe("`ACTIVE` 운영 중 · `SUSPENDED` 운영자 정지 · `CLOSED` 호스팅 끔·상점 폐쇄");
221
221
  export type StorefrontSiteStatus = z.infer<typeof storefrontSiteStatusSchema>;
222
222
 
223
223
  export const storefrontVisibilitySchema = z
@@ -259,7 +259,7 @@ export type StorefrontProvisioning = z.infer<typeof storefrontProvisioningSchema
259
259
 
260
260
  /**
261
261
  * 편집 미리보기 주소(M2b) — 사이트마다 `{사이트 id 정규화}-preview.{호스팅 도메인}` 호스트 하나다. 호스팅 도메인의 와일드카드
262
- * 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(콘솔 전용)로 받은 쿠키가 있어야 열린다.
262
+ * 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(상점 플랫폼 전용)로 받은 쿠키가 있어야 열린다.
263
263
  */
264
264
  export const storefrontEditPreviewSchema = z.object({
265
265
  hostname: z
@@ -332,7 +332,7 @@ export const storefrontSlugAvailabilitySchema = z.object({
332
332
  export type StorefrontSlugAvailability = z.infer<typeof storefrontSlugAvailabilitySchema>;
333
333
 
334
334
  /**
335
- * 배포 진행 — 사이트의 지금 발행 상태가 공개 주소에 반영되기까지의 단계와 기록이다(콘솔의 스토어프론트 만들기 3단계가 폴링한다).
335
+ * 배포 진행 — 사이트의 지금 발행 상태가 공개 주소에 반영되기까지의 단계와 기록이다(상점 플랫폼의 스토어프론트 만들기 3단계가 폴링한다).
336
336
  * 만들기(`POST /v1/storefront/site`)는 곧바로 201로 답하고 반영은 그 뒤에 비동기로 하므로, 끝났는지는 이 조회로 본다.
337
337
  * 템플릿 사이트는 미리 빌드한 템플릿 산출물을 올리므로 빌드 단계가 없다. 기록은 사이트 상태에서 만든 요약이다.
338
338
  */
@@ -379,7 +379,7 @@ export type StorefrontDeployment = z.infer<typeof storefrontDeploymentSchema>;
379
379
 
380
380
  export const createStorefrontSiteRequestSchema = z.object({
381
381
  templateId: z.string().min(1).max(64).describe("템플릿 id"),
382
- slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 스토어 코드"),
382
+ slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 상점 코드"),
383
383
  });
384
384
  export type CreateStorefrontSiteRequest = z.infer<typeof createStorefrontSiteRequestSchema>;
385
385
 
@@ -439,7 +439,7 @@ export const storefrontSlugAvailabilityQuerySchema = z.object({
439
439
  });
440
440
 
441
441
  /**
442
- * 오픈 준비 중 미리보기 링크 — 콘솔에서 로그인한 OWNER·ADMIN만 받는다(PAT·에이전트 불가). 링크는 5분 안에 한 번 열면
442
+ * 오픈 준비 중 미리보기 링크 — 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다(PAT·에이전트 불가). 링크는 5분 안에 한 번 열면
443
443
  * 그 브라우저에 12시간짜리 미리보기 쿠키가 생기고 오픈 준비 중인 사이트의 실제 화면을 본다. 링크를 남에게 넘기면 그 사람도
444
444
  * 볼 수 있으므로 공유하지 않는다.
445
445
  */
@@ -459,14 +459,14 @@ export type StorefrontPreviewLink = z.infer<typeof storefrontPreviewLinkSchema>;
459
459
  // ─── 내 도메인 (M3) ───────────────────────────────────────
460
460
  //
461
461
  // 셀러가 가진 도메인(예: `shop.brand.com`)을 사이트 주소로 연결한다. 흐름은 추가(`PENDING_DNS`) → 셀러가 DNS에 TXT(소유 확인)·
462
- // CNAME(연결)을 넣음 → 서버가 TXT를 확인하면 도메인을 이 스토어에 잡고 인증서 발급을 시작한다(`PENDING_VALIDATION`) → 인증서가
462
+ // CNAME(연결)을 넣음 → 서버가 TXT를 확인하면 도메인을 이 상점에 잡고 인증서 발급을 시작한다(`PENDING_VALIDATION`) → 인증서가
463
463
  // 발급되면 `ACTIVE`다. 확인은 서버가 주기적으로 하고 `verify`는 곧바로 한 번 더 본다. 연결된 도메인은 대표 주소로 지정할 수 있고,
464
464
  // 대표가 아닌 주소(기본 주소 포함)는 대표 주소로 301한다.
465
465
  //
466
- // - TXT 소유 확인은 도메인마다 새 값이다. CNAME만으로는 연결하지 않는다 — 연결을 끊은 뒤 CNAME이 남은 도메인을 다른 스토어가
466
+ // - TXT 소유 확인은 도메인마다 새 값이다. CNAME만으로는 연결하지 않는다 — 연결을 끊은 뒤 CNAME이 남은 도메인을 다른 상점이
467
467
  // 가져가지 못하게 한다.
468
- // - 소유 확인 전(`PENDING_DNS`)에는 도메인을 잡지 않는다. 같은 도메인을 여러 스토어가 추가해 둘 수 있고, 먼저 TXT 확인을 통과한
469
- // 스토어가 잡는다. 이미 다른 스토어가 잡은 도메인은 409 `STOREFRONT_DOMAIN_TAKEN`이다.
468
+ // - 소유 확인 전(`PENDING_DNS`)에는 도메인을 잡지 않는다. 같은 도메인을 여러 상점이 추가해 둘 수 있고, 먼저 TXT 확인을 통과한
469
+ // 상점이 잡는다. 이미 다른 상점이 잡은 도메인은 409 `STOREFRONT_DOMAIN_TAKEN`이다.
470
470
  // - 해제하면 곧바로 연결을 지운다. 연결된 뒤 DNS가 7일 넘게 이 서비스를 가리키지 않으면 서버가 연결을 해제한다.
471
471
  // - 결제 복귀·방문 분석·소셜 로그인 허용 목록은 연결된(`ACTIVE`) 도메인을 자동으로 더한다.
472
472
  //
@@ -474,11 +474,11 @@ export type StorefrontPreviewLink = z.infer<typeof storefrontPreviewLinkSchema>;
474
474
  // - 403 `STOREFRONT_CUSTOM_DOMAINS_DISABLED` — 플랫폼 스위치가 꺼져 도메인을 추가할 수 없다
475
475
  // - 400 `STOREFRONT_DOMAIN_INVALID` — 형식이 틀렸거나 연결할 수 없는 도메인이다(`error.details.reason`)
476
476
  // - 404 `STOREFRONT_DOMAIN_NOT_FOUND`
477
- // - 409 `STOREFRONT_DOMAIN_EXISTS` — 이 스토어에 이미 추가한 도메인이다
478
- // - 409 `STOREFRONT_DOMAIN_TAKEN` — 다른 스토어가 연결했거나 연결 중인 도메인이다
479
- // - 409 `STOREFRONT_DOMAIN_LIMIT` — 스토어당 도메인 수 상한(`error.details.limit`)
477
+ // - 409 `STOREFRONT_DOMAIN_EXISTS` — 이 상점에 이미 추가한 도메인이다
478
+ // - 409 `STOREFRONT_DOMAIN_TAKEN` — 다른 상점이 연결했거나 연결 중인 도메인이다
479
+ // - 409 `STOREFRONT_DOMAIN_LIMIT` — 상점당 도메인 수 상한(`error.details.limit`)
480
480
  // - 409 `STOREFRONT_DOMAIN_NOT_ACTIVE` — 연결되지 않았거나 경고(`failureCode`)가 있는 도메인은 대표 주소로 지정할 수 없다
481
- // - 429 `TOO_MANY_REQUESTS` — 재확인은 스토어당 분당 20번까지다(`error.details.retryAfterSeconds`)
481
+ // - 429 `TOO_MANY_REQUESTS` — 재확인은 상점당 분당 20번까지다(`error.details.retryAfterSeconds`)
482
482
  // - 400 `STOREFRONT_DOMAIN_NOT_REMOVABLE` — 기본 주소는 해제할 수 없다
483
483
 
484
484
  /**
@@ -575,7 +575,7 @@ export const STOREFRONT_PUBLIC_SUFFIXES: readonly string[] = [
575
575
  "imweb.me",
576
576
  ];
577
577
 
578
- /** 스토어당 연결할 수 있는 도메인 수 기본값 — 요금제(#44) 전의 상한이다. 실제 값은 `GET …/domains`의 `limit` */
578
+ /** 상점당 연결할 수 있는 도메인 수 기본값 — 요금제(#44) 전의 상한이다. 실제 값은 `GET …/domains`의 `limit` */
579
579
  export const STOREFRONT_CUSTOM_DOMAIN_LIMIT_DEFAULT = 2;
580
580
  /** DNS를 넣을 때까지 기다리는 기간(일) — 지나면 `FAILED`(`DNS_NOT_CONFIGURED`) */
581
581
  export const STOREFRONT_DOMAIN_DNS_WAIT_DAYS = 7;
@@ -683,7 +683,7 @@ export const storefrontSiteDomainSchema = z.object({
683
683
  .string()
684
684
  .nullable()
685
685
  .describe(
686
- "실패·경고 코드. `DNS_NOT_CONFIGURED` 기한 안에 TXT를 찾지 못함 · `VALIDATION_TIMEOUT` 인증서 발급 기한 지남 · `CERTIFICATE_FAILED` 인증서 발급 실패 · `DNS_NOT_POINTING` 연결된 뒤 DNS가 이 서비스를 가리키지 않음(기한이 지나면 해제) · `DOMAIN_TAKEN` 다른 스토어가 먼저 소유를 확인함 · `HOSTNAME_BLOCKED` 연결이 막힘 · `HOSTNAME_CONFLICT` 연결 준비 중 충돌(운영 확인). 늘어날 수 있다",
686
+ "실패·경고 코드. `DNS_NOT_CONFIGURED` 기한 안에 TXT를 찾지 못함 · `VALIDATION_TIMEOUT` 인증서 발급 기한 지남 · `CERTIFICATE_FAILED` 인증서 발급 실패 · `DNS_NOT_POINTING` 연결된 뒤 DNS가 이 서비스를 가리키지 않음(기한이 지나면 해제) · `DOMAIN_TAKEN` 다른 상점이 먼저 소유를 확인함 · `HOSTNAME_BLOCKED` 연결이 막힘 · `HOSTNAME_CONFLICT` 연결 준비 중 충돌(운영 확인). 늘어날 수 있다",
687
687
  ),
688
688
  verifiedAt: z.string().nullable().describe("소유를 확인한 시각 (ISO 8601)"),
689
689
  activatedAt: z.string().nullable().describe("연결된 시각 (ISO 8601)"),
@@ -695,11 +695,7 @@ export type StorefrontSiteDomain = z.infer<typeof storefrontSiteDomainSchema>;
695
695
  /** `GET /v1/storefront/site/domains` — 사이트의 주소 목록(기본 주소 + 내 도메인)과 연결 설정 */
696
696
  export const storefrontSiteDomainsSchema = z.object({
697
697
  enabled: z.boolean().describe("내 도메인을 추가할 수 있는가 (플랫폼 스위치)"),
698
- limit: z
699
- .number()
700
- .int()
701
- .min(0)
702
- .describe("스토어당 내 도메인 수 상한 (해제한 도메인은 세지 않는다)"),
698
+ limit: z.number().int().min(0).describe("상점당 내 도메인 수 상한 (해제한 도메인은 세지 않는다)"),
703
699
  target: z.string().describe("CNAME 대상 (예: `sites.sayren.co`)"),
704
700
  domains: z.array(storefrontSiteDomainSchema).describe("대표 주소 먼저, 그다음 추가한 순서"),
705
701
  });
@@ -716,11 +712,11 @@ export type AddStorefrontDomainRequest = z.infer<typeof addStorefrontDomainReque
716
712
 
717
713
  // ─── 편집 (M2b) ───────────────────────────────────────────
718
714
  //
719
- // 셀러(콘솔)와 에이전트(MCP)가 사이트의 작업 트리를 고치고 버전을 만든다. 작업 트리는 사이트당 하나이고 편집 Sandbox
715
+ // 셀러(상점 플랫폼)와 에이전트(MCP)가 사이트의 작업 트리를 고치고 버전을 만든다. 작업 트리는 사이트당 하나이고 편집 Sandbox
720
716
  // (격리된 컨테이너)가 dev 서버로 띄운다. 편집 환경은 플랫폼 전체 동시 상한이 있고 15분 동안 활동이 없으면 잠든다(작업
721
717
  // 트리는 보존된다). 버전 만들기는 그 시점의 작업 트리를 고정해 일회용 빌드 환경에서 규칙 검증 → 타입 검사 → 빌드를
722
718
  // 하고, 통과하면 발행할 수 있는 `READY` 버전이 된다. 에이전트의 파일 쓰기·검사·버전 만들기는 승인 없이 되고, 발행·
723
- // 되돌리기·공개 전환은 기존대로 승인이다. 편집 미리보기 링크는 콘솔 OWNER·ADMIN만 받는다(에이전트에게 주지 않는다).
719
+ // 되돌리기·공개 전환은 기존대로 승인이다. 편집 미리보기 링크는 상점 플랫폼 OWNER·ADMIN만 받는다(에이전트에게 주지 않는다).
724
720
 
725
721
  /** 플랫폼 전체 동시 편집 환경 상한 */
726
722
  export const STOREFRONT_EDIT_CAPACITY = 20;
@@ -813,7 +809,7 @@ export const storefrontEditSessionStatusSchema = z
813
809
  .enum(["STARTING", "RUNNING"])
814
810
  .describe("`STARTING` 띄우는 중 · `RUNNING` 사용 중");
815
811
 
816
- /** 편집 환경을 띄우는 단계 — 콘솔 진행 표시용이다. 값은 늘어날 수 있다 */
812
+ /** 편집 환경을 띄우는 단계 — 상점 플랫폼 진행 표시용이다. 값은 늘어날 수 있다 */
817
813
  export const STOREFRONT_EDIT_START_STAGES = ["CONTAINER", "RESTORE", "DEV_SERVER"] as const;
818
814
 
819
815
  export const storefrontEditSessionSchema = z.object({
@@ -1252,7 +1248,7 @@ export const storefrontVersionCreatedSchema = z.object({
1252
1248
  export type StorefrontVersionCreated = z.infer<typeof storefrontVersionCreatedSchema>;
1253
1249
 
1254
1250
  /**
1255
- * `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 링크. 콘솔에서 로그인한 OWNER·ADMIN만
1251
+ * `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 링크. 상점 플랫폼에서 로그인한 OWNER·ADMIN만
1256
1252
  * 받는다(PAT·에이전트 불가). 5분 안에 열면 그 브라우저에 미리보기 호스트 전용 쿠키(12시간)가 생긴다. 편집 환경이
1257
1253
  * 없으면 409 `EDIT_SESSION_REQUIRED`다(편집 환경이 `STARTING`이면 링크는 주고, 미리보기 화면이 기동 대기를 보여 준다).
1258
1254
  */
@@ -1262,7 +1258,7 @@ export type CreateStorefrontEditPreviewLinkRequest = CreateStorefrontPreviewLink
1262
1258
 
1263
1259
  // ─── 소스 내려받기·올리기 (#60, `sayren` CLI) ─────────────────────────────
1264
1260
  //
1265
- // 셀러가 사이트 작업 트리를 로컬로 받아(`npx sayren pull`) 고치고 되돌려 놓는다(`npx sayren deploy`). 콘솔·AI 편집과 같은
1261
+ // 셀러가 사이트 작업 트리를 로컬로 받아(`npx sayren pull`) 고치고 되돌려 놓는다(`npx sayren deploy`). 상점 플랫폼·AI 편집과 같은
1266
1262
  // 작업 트리다. 올리는 것은 소스뿐이고 빌드는 서버가 한다(버전 만들기). 흐름:
1267
1263
  // 1. `GET source` — 지금 작업 트리를 고정하고(`sourceRef`) 압축 파일 크기를 알린다
1268
1264
  // 2. `GET source/archive?sourceRef=&part=` — 압축 파일(tar.gz)을 4MiB씩 받는다
@@ -1286,7 +1282,7 @@ export const storefrontSourceRefSchema = z
1286
1282
  export const storefrontSourceSchema = z.object({
1287
1283
  siteId: z.string(),
1288
1284
  storeId: z.string(),
1289
- storeCode: z.string().describe("스토어 코드 — 로컬 개발 서버의 `SAYREN_STORE_CODE`"),
1285
+ storeCode: z.string().describe("상점 코드 — 로컬 개발 서버의 `SAYREN_STORE_CODE`"),
1290
1286
  sourceRef: storefrontSourceRefSchema,
1291
1287
  treeSha256: z.string().describe("트리 지문 (경로 오름차순 `{path}\\t{sha256}\\n`의 SHA-256)"),
1292
1288
  files: z.number().int().min(0).describe("파일 수"),