@sayren/store-sdk 0.23.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.
- package/dist/index.d.mts +636 -482
- package/dist/index.mjs +370 -347
- package/dist/server.d.mts +19 -28
- package/dist/server.mjs +29 -42
- package/package.json +1 -1
- package/src/client.ts +55 -153
- package/src/http.ts +1 -1
- package/src/index.ts +3 -2
- package/src/member-delegation.ts +51 -77
- package/src/oauth.ts +28 -33
- package/src/schemas/api-log.ts +7 -5
- package/src/schemas/audit.ts +5 -5
- package/src/schemas/auth.ts +0 -64
- package/src/schemas/claim.ts +4 -4
- package/src/schemas/coupon.ts +2 -2
- package/src/schemas/customer-delegation.ts +48 -0
- package/src/schemas/customer.ts +3 -3
- package/src/schemas/fulfillment.ts +9 -11
- package/src/schemas/order.ts +1 -1
- package/src/schemas/payment.ts +7 -5
- package/src/schemas/product.ts +12 -5
- package/src/schemas/promotion.ts +4 -4
- package/src/schemas/refund-policy.ts +12 -12
- package/src/schemas/saved-option-set.ts +1 -1
- package/src/schemas/scope.ts +3 -4
- package/src/schemas/shipping-policy.ts +18 -18
- package/src/schemas/store.ts +9 -9
- package/src/schemas/storefront-hosting.ts +62 -38
- package/src/schemas/subscription.ts +222 -0
- package/src/schemas/telemetry.ts +2 -2
- package/src/schemas/upload.ts +1 -1
- package/src/schemas/webhook-event.ts +1 -1
- package/src/server.ts +1 -2
- package/src/session.ts +46 -29
- package/src/schemas/api-client.ts +0 -121
package/dist/index.mjs
CHANGED
|
@@ -1,32 +1,5 @@
|
|
|
1
1
|
import { a as WEBHOOK_HEADERS, i as WEBHOOK_EVENT_TYPE_HEADER, n as WEBHOOK_DELIVERY_ID_HEADER, o as WEBHOOK_SIGNATURE_HEADER, r as WEBHOOK_EVENT_ID_HEADER, s as WEBHOOK_USER_AGENT, t as WEBHOOK_DEFAULT_TOLERANCE_SEC } from "./webhook-headers-ap_exsb9.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
//#region src/base64.ts
|
|
4
|
-
/**
|
|
5
|
-
* base64 인코딩 헬퍼 — 웹 표준(`btoa` + `TextEncoder`)만 사용한다.
|
|
6
|
-
*
|
|
7
|
-
* 이 패키지는 브라우저/서버 공용 entrypoint 하나로 배포되므로 Node 전용 `Buffer`에
|
|
8
|
-
* 의존하지 않는다. `btoa`는 Latin-1 코드유닛만 받기 때문에 문자열은 반드시
|
|
9
|
-
* UTF-8 바이트로 먼저 인코딩한 뒤 넘긴다 — 그래야 non-ASCII 입력에서도
|
|
10
|
-
* `Buffer.from(text, "utf8").toString("base64")`와 동일한 결과가 나온다.
|
|
11
|
-
*/
|
|
12
|
-
/** 바이트 배열 → 표준 base64 (패딩 포함) */
|
|
13
|
-
function base64FromBytes(bytes) {
|
|
14
|
-
let binary = "";
|
|
15
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
16
|
-
return btoa(binary);
|
|
17
|
-
}
|
|
18
|
-
/** 바이트 배열 → base64url (RFC 4648 §5, 패딩 없음) */
|
|
19
|
-
function base64UrlFromBytes(bytes) {
|
|
20
|
-
return base64FromBytes(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* 문자열 → UTF-8 base64. `btoa(text)`를 직접 쓰면 Latin-1로 해석돼
|
|
24
|
-
* non-ASCII에서 잘못된 바이트가 나오거나(예: "café") 예외가 난다(예: "🔑").
|
|
25
|
-
*/
|
|
26
|
-
function base64Utf8(text) {
|
|
27
|
-
return base64FromBytes(new TextEncoder().encode(text));
|
|
28
|
-
}
|
|
29
|
-
//#endregion
|
|
30
3
|
//#region src/schemas/audit.ts
|
|
31
4
|
/**
|
|
32
5
|
* 감사 로그와 승인 요청 — `/v1/audit-logs`, `/v1/approval-requests` (이슈 #36).
|
|
@@ -40,7 +13,7 @@ const auditActorTypeSchema = z.enum([
|
|
|
40
13
|
"APP",
|
|
41
14
|
"TOKEN",
|
|
42
15
|
"SYSTEM"
|
|
43
|
-
]).describe("행위자 유형. `USER
|
|
16
|
+
]).describe("행위자 유형. `USER`=상점 플랫폼 사용자, `AGENT`=AI 에이전트(MCP·에이전트용 API 토큰), `APP`=앱(OAuth 클라이언트·M2M), `TOKEN`=API 토큰, `SYSTEM`=자동화(주기 잡)");
|
|
44
17
|
const auditActorSchema = z.object({
|
|
45
18
|
type: auditActorTypeSchema,
|
|
46
19
|
id: z.string().describe("행위자 ID. `USER`=userId, `TOKEN`·`AGENT`(API 토큰)=`pat:{tokenId}`, `APP`=`client:{clientId}`, `SYSTEM`=`system`. 사람이 앱에 위임한 호출은 `APP`이고 위임한 사람은 `metadata.onBehalfOf`에 있다"),
|
|
@@ -60,7 +33,7 @@ const auditLogSchema = z.object({
|
|
|
60
33
|
source: auditLogSourceSchema,
|
|
61
34
|
action: z.string().describe("동작. `REQUEST`는 오퍼레이션 ID(OpenAPI `operationId`, 예: `ProductsController_patch`), `DOMAIN`은 도메인 코드(예: `ORDER_ITEM_CANCELED_BY_SELLER`)"),
|
|
62
35
|
actor: auditActorSchema,
|
|
63
|
-
actorRole: z.string().nullable().describe("요청 시점의
|
|
36
|
+
actorRole: z.string().nullable().describe("요청 시점의 상점 역할. 역할이 없는 토큰·자동화는 null"),
|
|
64
37
|
target: z.object({
|
|
65
38
|
type: z.string().describe("대상 리소스 유형 (예: `products`, `order-items`, `claim`)"),
|
|
66
39
|
id: z.string().nullable().describe("대상 리소스 ID. 목록 대상 요청처럼 하나로 정할 수 없으면 null")
|
|
@@ -104,7 +77,7 @@ const agentPolicyDecisionSchema = z.enum([
|
|
|
104
77
|
"ALLOW",
|
|
105
78
|
"APPROVAL_REQUIRED",
|
|
106
79
|
"DENIED"
|
|
107
|
-
]).describe("에이전트 쓰기 판정. `ALLOW`=바로 실행, `APPROVAL_REQUIRED
|
|
80
|
+
]).describe("에이전트 쓰기 판정. `ALLOW`=바로 실행, `APPROVAL_REQUIRED`=상점 플랫폼 승인 뒤 실행, `DENIED`=에이전트는 실행할 수 없음");
|
|
108
81
|
const previewValueSchema = z.union([
|
|
109
82
|
z.string(),
|
|
110
83
|
z.number(),
|
|
@@ -159,7 +132,7 @@ const approvalRequestSchema = z.object({
|
|
|
159
132
|
decidedBy: z.object({
|
|
160
133
|
id: z.string(),
|
|
161
134
|
name: z.string().nullable()
|
|
162
|
-
}).nullable().describe("승인·거절한
|
|
135
|
+
}).nullable().describe("승인·거절한 상점 플랫폼 사용자"),
|
|
163
136
|
decisionNote: z.string().nullable().describe("승인·거절 메모"),
|
|
164
137
|
executedAt: z.string().nullable().describe("원 요청 실행을 마친 일시"),
|
|
165
138
|
result: z.object({
|
|
@@ -170,7 +143,7 @@ const approvalRequestSchema = z.object({
|
|
|
170
143
|
message: z.string()
|
|
171
144
|
}).nullable().describe("실패 응답의 `error` 요약")
|
|
172
145
|
}).nullable().describe("실행 결과. `SUCCEEDED`·`FAILED`·`UNKNOWN`에서 값이 있다. 감사 로그로 확정한 결과는 `data`가 null이다"),
|
|
173
|
-
approvalUrl: z.string().nullable().describe("
|
|
146
|
+
approvalUrl: z.string().nullable().describe("상점 플랫폼의 승인 화면 주소. 이 배포의 상점 플랫폼 주소를 알 수 없으면 null")
|
|
174
147
|
});
|
|
175
148
|
const approvalListQuerySchema = z.object({
|
|
176
149
|
status: commaList(approvalStatusSchema).optional().describe("상태 필터. 여러 개는 쉼표로 구분한다 (예: `PENDING,EXECUTING`)"),
|
|
@@ -251,7 +224,7 @@ const APPROVAL_ALREADY_DECIDED_CODE = "APPROVAL_ALREADY_DECIDED";
|
|
|
251
224
|
* 성공 값으로 돌려주지 않는다 — 돌려주면 호출부가 승인 요청을 상품·주문으로 읽는다.
|
|
252
225
|
*
|
|
253
226
|
* - `code = APPROVAL_REQUIRED`(202): 에이전트의 고위험 쓰기가 보류됐다. 아직 아무것도 바뀌지 않았다.
|
|
254
|
-
* 셀러가
|
|
227
|
+
* 셀러가 상점 플랫폼(`approval.approvalUrl`)에서 승인하면 서버가 원 요청을 실행한다. 같은 요청을 다시 보내지 말고
|
|
255
228
|
* `approvalRequests.get(approvalId)`로 결과를 확인한다.
|
|
256
229
|
* - `code = APPROVAL_ALREADY_DECIDED`(200): 같은 멱등키로 보낸 요청의 승인이 이미 끝났다. `status`가
|
|
257
230
|
* `SUCCEEDED`·`FAILED`·`UNKNOWN`이면 실행 결과(`approval.result`)를 보고, `REJECTED`·`EXPIRED`면 새 멱등키로 다시 요청한다.
|
|
@@ -577,73 +550,17 @@ const analyticsCheckoutSchema = z.object({
|
|
|
577
550
|
})).describe("결과별 결제 세션 수. 결과마다 한 행")
|
|
578
551
|
});
|
|
579
552
|
//#endregion
|
|
580
|
-
//#region src/schemas/api-client.ts
|
|
581
|
-
/**
|
|
582
|
-
* 앱(OAuth 클라이언트) 관리 — `/v1/store/api-clients` (auth-spec 부록 B-13).
|
|
583
|
-
* 등록은 DCR(`POST /v1/oauth2/register`, RFC 7591)이 담당하고, 이 스키마는 등록 이후의
|
|
584
|
-
* 목록·상세·시크릿 재발급·비활성화 응답 계약이다. 시크릿 해시는 어떤 응답에도 포함되지 않는다.
|
|
585
|
-
*/
|
|
586
|
-
const apiClientTypeSchema = z.enum(["public", "confidential"]).describe("클라이언트 유형. `public`=시크릿 없음(PKCE 로그인용), `confidential`=시크릿 보유(서버 간 연동용)");
|
|
587
|
-
const apiClientStatusSchema = z.enum(["ACTIVE", "REVOKED"]).describe("상태. `REVOKED`는 비활성화된 클라이언트이며 다시 활성화할 수 없다");
|
|
588
|
-
const apiClientSchema = z.object({
|
|
589
|
-
clientId: z.string().describe("클라이언트 ID (`client_id`)"),
|
|
590
|
-
name: z.string().describe("클라이언트 이름 (`client_name`)"),
|
|
591
|
-
type: apiClientTypeSchema,
|
|
592
|
-
status: apiClientStatusSchema,
|
|
593
|
-
redirectUris: z.array(z.string()).describe("authorization_code 리다이렉트 URI 허용 목록 (정확 일치)"),
|
|
594
|
-
scopes: z.array(z.string()).describe("클라이언트가 요청할 수 있는 최대 스코프"),
|
|
595
|
-
grantTypes: z.array(z.string()).describe("허용된 grant_type 목록"),
|
|
596
|
-
secretPrefix: z.string().nullable().describe("시크릿 앞부분(식별용, 인증에 사용할 수 없음). public 클라이언트와 시크릿 앞부분이 기록되기 전에 등록된 클라이언트는 null"),
|
|
597
|
-
createdBy: z.string().nullable().describe("등록자 userId. 등록자 기록이 도입되기 전에 등록된 클라이언트는 null"),
|
|
598
|
-
createdByName: z.string().nullable().describe("등록자 이름 (표시용). 계정이 없으면 null"),
|
|
599
|
-
createdByEmail: z.string().nullable().describe("등록자 이메일 (표시용). 계정이 없으면 null"),
|
|
600
|
-
createdAt: z.string().describe("등록 일시 (ISO 8601)"),
|
|
601
|
-
revokedAt: z.string().nullable().describe("비활성화 일시 (ISO 8601). 활성 상태면 null")
|
|
602
|
-
});
|
|
603
|
-
/** 시크릿 재발급 응답 — 평문 시크릿은 이 응답에서 단 한 번만 노출된다 */
|
|
604
|
-
const apiClientSecretRotatedSchema = apiClientSchema.extend({ clientSecret: z.string().describe("새 클라이언트 시크릿 평문. 이 응답에서만 노출되며 다시 조회할 수 없다") });
|
|
605
|
-
const apiClientListQuerySchema = z.object({ status: apiClientStatusSchema.optional().describe("상태 필터. 생략하면 전체") });
|
|
606
|
-
/**
|
|
607
|
-
* Dynamic Client Registration 요청 (RFC 7591 §2, auth-spec §12) — `POST /v1/oauth2/register`.
|
|
608
|
-
* OAuth 프로토콜 표면이라 필드명이 snake_case이고 응답·등록 오류는 평면 JSON이다.
|
|
609
|
-
* 생략한 필드는 서버 기본값을 따른다(SDK가 기본값을 채워 보내지 않는다).
|
|
610
|
-
*/
|
|
611
|
-
const oauthClientRegistrationRequestSchema = z.object({
|
|
612
|
-
client_name: z.string().min(1).max(80).describe("클라이언트 이름 (1~80자)"),
|
|
613
|
-
token_endpoint_auth_method: z.enum(["none", "client_secret_basic"]).optional().describe("토큰 엔드포인트 인증 방식. `none`=public(시크릿 없음), `client_secret_basic`=confidential. 생략하면 `client_secret_basic`"),
|
|
614
|
-
redirect_uris: z.array(z.string()).max(10).optional().describe("리다이렉트 URI 목록(최대 10개). authorization_code에는 필수이며 https(개발용 루프백 http 제외)만 허용"),
|
|
615
|
-
grant_types: z.array(z.enum([
|
|
616
|
-
"authorization_code",
|
|
617
|
-
"refresh_token",
|
|
618
|
-
"client_credentials",
|
|
619
|
-
"urn:ietf:params:oauth:grant-type:token-exchange"
|
|
620
|
-
])).min(1).optional().describe("허용할 grant_type 목록. 생략하면 `client_credentials`"),
|
|
621
|
-
scope: z.string().optional().describe("공백 구분 스코프. 등록자 보유 스코프 이내여야 한다. 생략하면 confidential은 등록자 스코프, public은 `stores:read`")
|
|
622
|
-
});
|
|
623
|
-
/** Dynamic Client Registration 응답 (RFC 7591 §3.2.1) — client_secret은 이 응답에서만 노출된다 */
|
|
624
|
-
const oauthClientRegistrationResponseSchema = z.object({
|
|
625
|
-
client_id: z.string().describe("발급된 클라이언트 ID"),
|
|
626
|
-
client_secret: z.string().optional().describe("클라이언트 시크릿 평문. confidential만 있으며 이 응답에서만 노출된다"),
|
|
627
|
-
client_secret_expires_at: z.number().int().optional().describe("시크릿 만료 시각(epoch 초). 0은 만료 없음. confidential만 있다"),
|
|
628
|
-
client_id_issued_at: z.number().int().describe("발급 시각 (epoch 초)"),
|
|
629
|
-
client_name: z.string(),
|
|
630
|
-
token_endpoint_auth_method: z.enum(["none", "client_secret_basic"]),
|
|
631
|
-
redirect_uris: z.array(z.string()),
|
|
632
|
-
grant_types: z.array(z.string()),
|
|
633
|
-
scope: z.string().describe("부여된 스코프 (공백 구분)")
|
|
634
|
-
});
|
|
635
|
-
//#endregion
|
|
636
553
|
//#region src/schemas/api-log.ts
|
|
637
554
|
/**
|
|
638
|
-
* API 로그 — `/v1/api-logs`.
|
|
639
|
-
* 인증에 실패해
|
|
555
|
+
* API 로그 — `/v1/api-logs`. 상점이 해석된 관리 API(`/v1/**`) 요청을 7일간 보관한다.
|
|
556
|
+
* 인증에 실패해 상점을 알 수 없는 요청(401 등)은 기록되지 않는다.
|
|
640
557
|
* Authorization·쿠키 헤더, 요청 본문, 개인정보는 저장하지 않는다.
|
|
641
558
|
*/
|
|
642
559
|
const apiLogPrincipalTypeSchema = z.enum([
|
|
643
560
|
"USER",
|
|
644
561
|
"PAT",
|
|
645
562
|
"CLIENT"
|
|
646
|
-
]).describe("호출 주체 유형. `USER
|
|
563
|
+
]).describe("호출 주체 유형. `USER`=상점 플랫폼 사용자, `PAT`=API 토큰, `CLIENT`=앱(OAuth 클라이언트)");
|
|
647
564
|
const apiLogPrincipalSchema = z.object({
|
|
648
565
|
type: apiLogPrincipalTypeSchema,
|
|
649
566
|
id: z.string().describe("호출 주체 ID. `USER`=userId, `PAT`=API 토큰 ID, `CLIENT`=clientId"),
|
|
@@ -690,11 +607,11 @@ const apiLogPageSchema = z.object({
|
|
|
690
607
|
});
|
|
691
608
|
/**
|
|
692
609
|
* API 로그 요약 — `GET /api-logs/summary`. 연동 트래픽(호출 주체 `PAT`·`CLIENT`)만 집계하며
|
|
693
|
-
*
|
|
610
|
+
* 상점 플랫폼 사용자(`USER`) 요청은 제외한다
|
|
694
611
|
*/
|
|
695
612
|
const apiLogSummarySchema = z.object({
|
|
696
613
|
windowHours: z.number().int().describe("집계 구간(시간). 최근 24시간"),
|
|
697
|
-
total: z.number().int().min(0).describe("구간 내 연동 요청 수 (호출 주체 `PAT`·`CLIENT`,
|
|
614
|
+
total: z.number().int().min(0).describe("구간 내 연동 요청 수 (호출 주체 `PAT`·`CLIENT`, 상점 플랫폼 사용자 제외)"),
|
|
698
615
|
errors: z.number().int().min(0).describe("구간 내 연동 요청 중 4xx·5xx 응답 수"),
|
|
699
616
|
serverErrors: z.number().int().min(0).describe("구간 내 연동 요청 중 5xx 응답 수")
|
|
700
617
|
});
|
|
@@ -705,27 +622,6 @@ const storeRoleSchema = z.enum([
|
|
|
705
622
|
"ADMIN",
|
|
706
623
|
"STAFF"
|
|
707
624
|
]);
|
|
708
|
-
const userTokenResponseSchema = z.object({
|
|
709
|
-
access_token: z.string(),
|
|
710
|
-
token_type: z.literal("Bearer"),
|
|
711
|
-
expires_in: z.number().int(),
|
|
712
|
-
refresh_token: z.string().optional()
|
|
713
|
-
});
|
|
714
|
-
/** client_credentials 응답 (RFC 6749 §4.4.3) — storeToken 직발급, refresh 없음 */
|
|
715
|
-
const clientCredentialsTokenResponseSchema = z.object({
|
|
716
|
-
access_token: z.string(),
|
|
717
|
-
token_type: z.literal("Bearer"),
|
|
718
|
-
expires_in: z.number().int(),
|
|
719
|
-
scope: z.string()
|
|
720
|
-
});
|
|
721
|
-
const storeTokenResponseSchema = z.object({
|
|
722
|
-
access_token: z.string(),
|
|
723
|
-
token_type: z.literal("Bearer"),
|
|
724
|
-
expires_in: z.number().int(),
|
|
725
|
-
storeId: z.string(),
|
|
726
|
-
role: storeRoleSchema,
|
|
727
|
-
scopes: z.array(z.string())
|
|
728
|
-
});
|
|
729
625
|
const myStoreSchema = z.object({
|
|
730
626
|
storeId: z.string(),
|
|
731
627
|
storeName: z.string(),
|
|
@@ -744,30 +640,6 @@ const myAccountSchema = z.object({
|
|
|
744
640
|
storeCount: z.number().int(),
|
|
745
641
|
createdAt: z.string()
|
|
746
642
|
});
|
|
747
|
-
/** API 토큰(PAT) — 목록/조회 뷰 (평문·해시 미포함) */
|
|
748
|
-
const apiTokenSchema = z.object({
|
|
749
|
-
tokenId: z.string(),
|
|
750
|
-
name: z.string(),
|
|
751
|
-
prefix: z.string(),
|
|
752
|
-
scopes: z.array(z.string()),
|
|
753
|
-
createdBy: z.string().describe("발급자 userId"),
|
|
754
|
-
createdByName: z.string().nullable().describe("발급자 이름 (표시용). 계정이 없으면 null"),
|
|
755
|
-
createdByEmail: z.string().nullable().describe("발급자 이메일 (표시용). 계정이 없으면 null"),
|
|
756
|
-
createdAt: z.string(),
|
|
757
|
-
expiresAt: z.string().nullable(),
|
|
758
|
-
lastUsedAt: z.string().nullable(),
|
|
759
|
-
agent: z.boolean().default(false).describe("AI 에이전트(MCP)용 토큰인가. true면 이 토큰의 쓰기는 행위자 `AGENT`로 기록되고 에이전트 쓰기 정책(승인 필요·거부)을 따른다")
|
|
760
|
-
});
|
|
761
|
-
/** 생성 응답 — 평문 토큰은 이 응답에서 단 한 번만 노출된다 */
|
|
762
|
-
const apiTokenCreatedSchema = apiTokenSchema.extend({ token: z.string() });
|
|
763
|
-
const apiTokenCreateRequestSchema = z.object({
|
|
764
|
-
name: z.string().min(1).max(50),
|
|
765
|
-
scopes: z.array(z.string()).min(1),
|
|
766
|
-
/** 일 단위 만료. null = 무기한, 생략 = 90일 */
|
|
767
|
-
expiresInDays: z.number().int().nullable().optional(),
|
|
768
|
-
/** AI 에이전트(MCP)용 토큰. 생략 = false. 발급 뒤에는 바꿀 수 없다 */
|
|
769
|
-
agent: z.boolean().optional()
|
|
770
|
-
});
|
|
771
643
|
//#endregion
|
|
772
644
|
//#region src/schemas/category.ts
|
|
773
645
|
const categoryProductSortSchema = z.enum([
|
|
@@ -938,10 +810,10 @@ const claimReasonSchema = z.enum([
|
|
|
938
810
|
"DELAY"
|
|
939
811
|
]);
|
|
940
812
|
/**
|
|
941
|
-
* 반품지 (이슈 #30) — 반품·교환 수거가 안내하는 주소다.
|
|
813
|
+
* 반품지 (이슈 #30) — 반품·교환 수거가 안내하는 주소다. 상점 주소록의 기본 반품지이거나,
|
|
942
814
|
* 상품이 반품지를 따로 지정했으면 그 주소다.
|
|
943
815
|
*
|
|
944
|
-
* 취소 클레임은 보낼 물건이 없어 null이고, 반품지를 등록하지 않은
|
|
816
|
+
* 취소 클레임은 보낼 물건이 없어 null이고, 반품지를 등록하지 않은 상점도 null이다.
|
|
945
817
|
* 구매자가 직접 보내는 수거(`BUYER_SEND`)에 이 주소가 필요하다.
|
|
946
818
|
*/
|
|
947
819
|
const claimReturnAddressSchema = z.object({
|
|
@@ -982,7 +854,7 @@ const claimSchema = z.object({
|
|
|
982
854
|
holdExpiresAt: z.string().nullable(),
|
|
983
855
|
/** 보류를 이미 썼는지 — 보류는 클레임당 1회라 해제·만료 뒤에도 true */
|
|
984
856
|
holdUsed: z.boolean(),
|
|
985
|
-
returnAddress: claimReturnAddressSchema.nullable().describe("수거 안내에 쓸 반품지(이슈 #30). 상품이 지정한 반품지, 없으면
|
|
857
|
+
returnAddress: claimReturnAddressSchema.nullable().describe("수거 안내에 쓸 반품지(이슈 #30). 상품이 지정한 반품지, 없으면 상점 기본 반품지다. 취소 클레임과 반품지를 등록하지 않은 상점은 null")
|
|
986
858
|
});
|
|
987
859
|
/** `GET /v1/claims` 쿼리 — OpenAPI 문서용 형태(값은 쿼리 문자열) */
|
|
988
860
|
const claimListQuerySchema = z.object({
|
|
@@ -1035,9 +907,9 @@ const approveClaimResultSchema = z.object({
|
|
|
1035
907
|
/**
|
|
1036
908
|
* 환불 정책 (이슈 #25) — 취소·반품·교환에서 **배송비를 누가 부담하는지**를 정한다.
|
|
1037
909
|
*
|
|
1038
|
-
* 두 층이다.
|
|
910
|
+
* 두 층이다. 상점 설정(`PATCH /v1/store`의 `refundPolicy`)이 기본값이고, 상품별 정책
|
|
1039
911
|
* (`POST/PUT /v1/products`의 `refundPolicy`)이 그 상품에만 덮어쓴다. 상품이 정하지 않은 항목은
|
|
1040
|
-
*
|
|
912
|
+
* 상점 기본을 따르고, 상점도 정하지 않았으면 `REFUND_POLICY_DEFAULTS`다.
|
|
1041
913
|
*
|
|
1042
914
|
* 한 주문에 정책이 섞이면 **상품별로 계산한다**. 배송비 묶음도 상품 단위다(주문서·장바구니의
|
|
1043
915
|
* 배송비 합산과 같은 규칙: 같은 상품의 주문상품이 한 묶음이고 묶음마다 배송비가 붙는다).
|
|
@@ -1050,7 +922,7 @@ const descriptions$3 = {
|
|
|
1050
922
|
conditionalFreeShortfallCharge: "조건부 무료배송(`CONDITIONAL_FREE`) 상품에서 일부 취소로 남은 금액이 무료 기준액에 미달하면 그 배송비를 환불액에서 뺀다. false면 추가 청구 없이 셀러가 부담한다. 기본 false",
|
|
1051
923
|
couponShortfallCharge: "쿠폰 조건 미달 차감. 일부 취소·반품으로 남은 주문상품이 쿠폰 조건(최소 주문 금액 등)을 잃으면 남은 주문상품이 받던 쿠폰 할인을 환불액에서 뺀다. 판매자 귀책(셀러 직권취소, 불량·오배송 반품)은 빼지 않는다. false면 차감 없이 실결제 금액만 나눠 환불한다. 기본 true"
|
|
1052
924
|
};
|
|
1053
|
-
/** 유효 환불 정책 —
|
|
925
|
+
/** 유효 환불 정책 — 상점 기본 + 상품별 덮어쓰기를 합친 결과 */
|
|
1054
926
|
const refundPolicySchema = z.object({
|
|
1055
927
|
preShipmentDeliveryFeeRefund: z.boolean().describe(descriptions$3.preShipmentDeliveryFeeRefund),
|
|
1056
928
|
returnDeliveryFee: z.number().int().min(0).describe(descriptions$3.returnDeliveryFee),
|
|
@@ -1060,7 +932,7 @@ const refundPolicySchema = z.object({
|
|
|
1060
932
|
couponShortfallCharge: z.boolean().optional().default(true).describe(`${descriptions$3.couponShortfallCharge}. 이 필드가 없는 이전 서버 응답은 true로 읽는다`)
|
|
1061
933
|
});
|
|
1062
934
|
/**
|
|
1063
|
-
*
|
|
935
|
+
* 상점이 정하지 않았을 때 서버가 쓰는 값.
|
|
1064
936
|
*
|
|
1065
937
|
* `as const`가 아니라 `RefundPolicy`로 타입을 좁힌다 — 리터럴 타입이면 `Partial<typeof …>`로 일부만 바꿀 때
|
|
1066
938
|
* `true` 자리에 `false`를 넣을 수 없다.
|
|
@@ -1074,7 +946,7 @@ const REFUND_POLICY_DEFAULTS = {
|
|
|
1074
946
|
couponShortfallCharge: true
|
|
1075
947
|
};
|
|
1076
948
|
/**
|
|
1077
|
-
* 부분 정책 —
|
|
949
|
+
* 부분 정책 — 상점 설정 요청과 상품별 덮어쓰기가 쓴다. 준 항목만 바뀐다.
|
|
1078
950
|
*
|
|
1079
951
|
* `strictObject`라 모르는 키는 400이다. 오타(`returnFee` 등)를 조용히 무시하면 셀러는 저장됐다고
|
|
1080
952
|
* 믿는데 정책은 기본값으로 남는다.
|
|
@@ -1089,11 +961,11 @@ const refundPolicyPatchSchema = z.strictObject({
|
|
|
1089
961
|
});
|
|
1090
962
|
/**
|
|
1091
963
|
* 상품별 덮어쓰기 — 쿠폰 조건 미달 차감(`couponShortfallCharge`)은 주문 단위 규칙이라 상품이 정하지 않는다.
|
|
1092
|
-
*
|
|
964
|
+
* 상점 정책만 읽는다(이슈 #47).
|
|
1093
965
|
*/
|
|
1094
966
|
const productRefundPolicyPatchSchema = refundPolicyPatchSchema.omit({ couponShortfallCharge: true });
|
|
1095
967
|
/**
|
|
1096
|
-
* 유효 정책 해석 — 상품별 덮어쓰기 →
|
|
968
|
+
* 유효 정책 해석 — 상품별 덮어쓰기 → 상점 기본 → `REFUND_POLICY_DEFAULTS` 순이다.
|
|
1097
969
|
*
|
|
1098
970
|
* 반품·교환 배송비의 원천은 정책 하나다(이슈 #57 결정 3). 상품의 레거시 반품비·교환비 필드와의 연결은 없앴고,
|
|
1099
971
|
* 레거시 값은 마이그레이션이 상품별 정책으로 옮겼다.
|
|
@@ -1108,9 +980,9 @@ function resolveRefundPolicy(storePolicy, product) {
|
|
|
1108
980
|
/**
|
|
1109
981
|
* 상품의 **덮어쓰기 자체** — 덮어쓰기가 없으면 null.
|
|
1110
982
|
*
|
|
1111
|
-
* `resolveRefundPolicy`가 합친 결과로는 "상품이 정한 값"과 "
|
|
1112
|
-
* 관리 API 응답의 `refundPolicy`와
|
|
1113
|
-
*
|
|
983
|
+
* `resolveRefundPolicy`가 합친 결과로는 "상품이 정한 값"과 "상점 기본을 물려받은 값"을 가를 수 없다.
|
|
984
|
+
* 관리 API 응답의 `refundPolicy`와 상점 플랫폼 폼의 덮어쓰기 스위치가 이 함수를 원천으로 쓴다 — 가르지 못하면
|
|
985
|
+
* 상점 기본을 쓰는 상품을 저장할 때 그 순간의 기본값이 상품에 박힌다.
|
|
1114
986
|
*/
|
|
1115
987
|
function productRefundPolicyOverride(product) {
|
|
1116
988
|
if (product?.refundPolicy == null) return null;
|
|
@@ -1160,7 +1032,7 @@ function proratedRefundBase(totalPrice, quantity, settledQuantity, cancelQuantit
|
|
|
1160
1032
|
* 4. 배송비 쿠폰(`DELIVERY`) — 주문에 한 장. 기본 배송비(지역 추가 배송비 제외)를 기준으로 할인하고
|
|
1161
1033
|
* 배송 묶음에 기본 배송비 비율로 나눈다.
|
|
1162
1034
|
*
|
|
1163
|
-
* 무료배송 기준(상품별 조건부
|
|
1035
|
+
* 무료배송 기준(상품별 조건부 무료·상점 전체 기준)은 **쿠폰 차감 전 금액**으로 판정한다. 그래서 배송비
|
|
1164
1036
|
* 견적(`quoteDelivery`)은 쿠폰과 무관하게 먼저 계산되고, 이 함수는 그 결과만 입력으로 받는다.
|
|
1165
1037
|
*
|
|
1166
1038
|
* 최소 주문 금액은 대상 상품의 **즉시할인 반영 합계**(쿠폰 차감 전, 배송비 제외)로 판정한다.
|
|
@@ -1173,7 +1045,7 @@ const descriptions$2 = {
|
|
|
1173
1045
|
benefitValue: "할인 값. `RATE`면 1~100(%), `AMOUNT`면 원 단위 금액",
|
|
1174
1046
|
maxDiscountAmount: "정률 할인의 최대 할인액(원). null이면 상한이 없다. 정액 할인에는 쓰지 않는다(null)",
|
|
1175
1047
|
minOrderAmount: "최소 주문 금액(원). 대상 상품의 즉시할인 반영 합계(배송비 제외)가 이 값 이상이어야 쓸 수 있다. 0이면 조건이 없다",
|
|
1176
|
-
firstOrderOnly: "이
|
|
1048
|
+
firstOrderOnly: "이 상점에서 결제 완료 주문이 없는 회원만 쓸 수 있다",
|
|
1177
1049
|
memberOnly: "회원만 쓸 수 있다. false면 비회원도 쓸 수 있다 — 코드 입력형이면서 1인 한도가 없는 쿠폰만 false로 둘 수 있다",
|
|
1178
1050
|
stackWithInstantDiscount: "상품 즉시할인과 함께 쓸 수 있다. false면 즉시할인이 걸린 주문상품은 쿠폰 대상에서 빠진다",
|
|
1179
1051
|
includeProductIds: "대상 상품 id. 포함 목록(상품·카테고리)이 모두 비면 전 상품이 대상이다",
|
|
@@ -1680,7 +1552,7 @@ const limitField = (description) => z.number().int().min(1).max(1e7).nullable().
|
|
|
1680
1552
|
const descriptions$1 = {
|
|
1681
1553
|
name: "구매자에게 보이는 쿠폰 이름. 1~60자",
|
|
1682
1554
|
description: "구매자에게 보이는 설명. 300자까지",
|
|
1683
|
-
code: "쿠폰 코드 — 발급 방식이 `CODE`일 때 필수. 영문·숫자·`-`·`_` 6~30자, 대소문자 구분 없음(대문자로 저장).
|
|
1555
|
+
code: "쿠폰 코드 — 발급 방식이 `CODE`일 때 필수. 영문·숫자·`-`·`_` 6~30자, 대소문자 구분 없음(대문자로 저장). 상점 안에서 유일하다",
|
|
1684
1556
|
startsAt: "사용 시작 시각(ISO 8601, 시간대 포함). 생략하면 바로 쓸 수 있다",
|
|
1685
1557
|
endsAt: "사용 종료 시각(ISO 8601, 시간대 포함). 이 시각부터 못 쓴다. 생략하면 제한이 없다. 이미 지급한 쿠폰의 유효 기간은 지급 때 정해지므로 뒤에 종료일을 늦춰도 늘어나지 않는다",
|
|
1686
1558
|
validDaysAfterIssue: "발급 후 유효 일수 — 셀러 지급(`MANUAL`) 쿠폰만. 쿠폰 종료일과 둘 중 먼저 끝나는 쪽이 적용된다",
|
|
@@ -1821,7 +1693,7 @@ const issueCouponResultSchema = z.object({
|
|
|
1821
1693
|
})),
|
|
1822
1694
|
failed: z.array(z.object({
|
|
1823
1695
|
customerId: z.string(),
|
|
1824
|
-
code: z.string().describe("실패 사유. `CUSTOMER_NOT_FOUND`(이
|
|
1696
|
+
code: z.string().describe("실패 사유. `CUSTOMER_NOT_FOUND`(이 상점 구매자가 아님)·`COUPON_LIMIT_REACHED`(1인 발급 한도)·`COUPON_EXHAUSTED`(총 발급 한도)"),
|
|
1825
1697
|
message: z.string()
|
|
1826
1698
|
}))
|
|
1827
1699
|
});
|
|
@@ -1937,7 +1809,7 @@ const customerConsentSchema = z.object({
|
|
|
1937
1809
|
kind: consentKindSchema,
|
|
1938
1810
|
/** 동의한 약관 버전. 마케팅은 버전이 없어 null */
|
|
1939
1811
|
version: z.string().nullable(),
|
|
1940
|
-
/** 동의 시점의 약관 문서 주소.
|
|
1812
|
+
/** 동의 시점의 약관 문서 주소. 상점이 주소를 정하지 않았거나 마케팅이면 null */
|
|
1941
1813
|
documentUrl: z.string().nullable(),
|
|
1942
1814
|
action: consentActionSchema,
|
|
1943
1815
|
source: consentSourceSchema,
|
|
@@ -1949,9 +1821,9 @@ const customerConsentStateSchema = z.object({
|
|
|
1949
1821
|
agreed: z.boolean(),
|
|
1950
1822
|
/** 동의한 약관 버전. 마케팅이거나 이력이 없으면 null */
|
|
1951
1823
|
agreedVersion: z.string().nullable(),
|
|
1952
|
-
/**
|
|
1824
|
+
/** 상점의 현재 약관 버전. 마케팅은 null */
|
|
1953
1825
|
currentVersion: z.string().nullable(),
|
|
1954
|
-
/**
|
|
1826
|
+
/** 상점이 정한 현재 약관 문서 주소. 정하지 않았거나 마케팅이면 null */
|
|
1955
1827
|
currentDocumentUrl: z.string().nullable(),
|
|
1956
1828
|
updatedAt: z.string().nullable(),
|
|
1957
1829
|
/** 약관이 개정돼 다시 받아야 하는가. 마케팅은 항상 false */
|
|
@@ -2444,8 +2316,8 @@ const deliveryTypeSchema = z.enum([
|
|
|
2444
2316
|
]);
|
|
2445
2317
|
const KNOWN_FULFILLMENT_TYPES = fulfillmentTypeSchema.options;
|
|
2446
2318
|
const ADDRESS_ID_DESCRIPTIONS = {
|
|
2447
|
-
shipping: "출고지 주소 id(`GET /v1/seller-addresses?type=SHIPPING`). 등록에서 생략하면 그 시점
|
|
2448
|
-
return: "반품지 주소 id(`GET /v1/seller-addresses?type=RETURN`). 등록에서 생략하면 그 시점
|
|
2319
|
+
shipping: "출고지 주소 id(`GET /v1/seller-addresses?type=SHIPPING`). 등록에서 생략하면 그 시점 상점 기본 출고지를 저장한다. `null`이면 비워 둔다(상점 기본 출고지를 따른다). 이 상점의 `SHIPPING` 주소가 아니면 거부한다",
|
|
2320
|
+
return: "반품지 주소 id(`GET /v1/seller-addresses?type=RETURN`). 등록에서 생략하면 그 시점 상점 기본 반품지를 저장한다. `null`이면 비워 둔다(상점 기본 반품지를 따른다). 이 상점의 `RETURN` 주소가 아니면 거부한다"
|
|
2449
2321
|
};
|
|
2450
2322
|
/**
|
|
2451
2323
|
* 배송비 필드 사이의 일관성 — FREE의 배송비, 조건부 무료가 아닌 상품의 무료 기준을 조용히 0·null로 바꿔 저장하지 않고 400으로 돌려준다
|
|
@@ -2473,13 +2345,13 @@ function refineShippingConsistency(value, ctx) {
|
|
|
2473
2345
|
const deliveryTypeDescription = "배송비 유형. `FREE` 무료, `PAID` 유료, `CONDITIONAL_FREE` 조건부 무료";
|
|
2474
2346
|
const shippingCreateSchema = z.strictObject({
|
|
2475
2347
|
deliveryType: deliveryTypeSchema.default("PAID").describe(deliveryTypeDescription),
|
|
2476
|
-
deliveryFee: z.number().int().min(0).optional().describe("배송비. 원 단위. 생략하면
|
|
2348
|
+
deliveryFee: z.number().int().min(0).optional().describe("배송비. 원 단위. 생략하면 상점 기본 배송비(`GET /v1/store`의 `shippingPolicy.baseDeliveryFee`)를 저장한다. FREE면 0이거나 생략한다"),
|
|
2477
2349
|
conditionalFreeAmount: z.number().int().min(0).nullable().optional().describe("무료 배송이 되는 묶음 소계. 원 단위. CONDITIONAL_FREE에서만 받고 필수다"),
|
|
2478
2350
|
shippingAddressId: z.string().nullable().optional().describe(ADDRESS_ID_DESCRIPTIONS.shipping),
|
|
2479
2351
|
returnAddressId: z.string().nullable().optional().describe(ADDRESS_ID_DESCRIPTIONS.return)
|
|
2480
2352
|
}).superRefine(refineShippingConsistency);
|
|
2481
2353
|
/**
|
|
2482
|
-
* 상품 이행 — 등록(POST). 배송 상품의 `shipping`을 생략하면 기본값(유료
|
|
2354
|
+
* 상품 이행 — 등록(POST). 배송 상품의 `shipping`을 생략하면 기본값(유료 배송·상점 기본 배송비·기본 출고지·반품지)이다.
|
|
2483
2355
|
*/
|
|
2484
2356
|
const productFulfillmentCreateSchema = z.discriminatedUnion("type", [z.strictObject({
|
|
2485
2357
|
type: z.literal("SHIPPING"),
|
|
@@ -2510,8 +2382,8 @@ const productFulfillmentSchema = z.discriminatedUnion("type", [z.object({
|
|
|
2510
2382
|
deliveryType: deliveryTypeSchema,
|
|
2511
2383
|
deliveryFee: z.number().int().describe("배송비. 원 단위. FREE면 0"),
|
|
2512
2384
|
conditionalFreeAmount: z.number().int().nullable().describe("조건부 무료 기준 묶음 소계. CONDITIONAL_FREE가 아니면 null"),
|
|
2513
|
-
shippingAddressId: z.string().nullable().describe("출고지 주소 id. null이면
|
|
2514
|
-
returnAddressId: z.string().nullable().describe("반품지 주소 id. null이면
|
|
2385
|
+
shippingAddressId: z.string().nullable().describe("출고지 주소 id. null이면 상점 기본 출고지를 따른다"),
|
|
2386
|
+
returnAddressId: z.string().nullable().describe("반품지 주소 id. null이면 상점 기본 반품지를 따른다")
|
|
2515
2387
|
})
|
|
2516
2388
|
}), z.object({ type: z.literal("MANUAL") })]);
|
|
2517
2389
|
/**
|
|
@@ -2535,16 +2407,16 @@ const orderItemFulfillmentSnapshotSchema = z.discriminatedUnion("type", [z.objec
|
|
|
2535
2407
|
type: z.literal("SHIPPING"),
|
|
2536
2408
|
shipping: z.object({
|
|
2537
2409
|
bundleKey: z.string().nullable().describe("배송 묶음 키. 같은 키의 주문상품이 배송비를 한 번 낸 한 묶음이다. #29 이전 주문은 null"),
|
|
2538
|
-
returnAddressId: z.string().nullable().describe("주문 시점 반품지 주소 id. null이거나 지워진 주소면
|
|
2410
|
+
returnAddressId: z.string().nullable().describe("주문 시점 반품지 주소 id. null이거나 지워진 주소면 상점 기본 반품지로 안내한다")
|
|
2539
2411
|
})
|
|
2540
2412
|
}), z.object({ type: z.literal("MANUAL") })]);
|
|
2541
2413
|
const orderItemFulfillmentSnapshotResponseSchema = openFulfillmentUnion(orderItemFulfillmentSnapshotSchema).describe("주문 시점 이행 스냅샷. 상품을 나중에 고쳐도 바뀌지 않는다. 제공 기록은 `fulfillment`에 있다. 값이 추가될 수 있어 SDK는 모르는 유형을 `OTHER`로 읽는다");
|
|
2542
2414
|
//#endregion
|
|
2543
2415
|
//#region src/schemas/payment.ts
|
|
2544
2416
|
/**
|
|
2545
|
-
*
|
|
2417
|
+
* 상점 결제(PG) 설정과 매출 리포트 계약.
|
|
2546
2418
|
*
|
|
2547
|
-
* PG는 셀러가 직접 계약한다. 자격 증명은
|
|
2419
|
+
* PG는 셀러가 직접 계약한다. 자격 증명은 상점별로 저장되고, 비밀 값(시크릿 키·API Secret·
|
|
2548
2420
|
* 웹훅 시크릿)은 저장 후 다시 조회할 수 없다. 응답에는 앞 4자리 마스킹만 나온다. 공개 값
|
|
2549
2421
|
* (클라이언트 키·Store ID·채널 키)은 원래 브라우저 결제창에 노출되는 값이라 그대로 돌려준다.
|
|
2550
2422
|
*/
|
|
@@ -2579,7 +2451,7 @@ const secretInput = () => z.string().trim().min(8).max(512);
|
|
|
2579
2451
|
/**
|
|
2580
2452
|
* 토스페이먼츠 결제위젯 키 접두사. 결제창은 API 개별 연동(`*_ck_`·`*_sk_`)만 쓴다. 위젯 키는 결제창 SDK가
|
|
2581
2453
|
* `NOT_SUPPORTED_WIDGET_KEY`로, 서버 API가 404 `NOT_FOUND_MERCHANT`로 거부하므로 저장 전에 막는다.
|
|
2582
|
-
* 요청 스키마·api
|
|
2454
|
+
* 요청 스키마·api 어댑터·상점 플랫폼 폼이 같은 상수를 쓴다(원천). `g` 플래그가 없어 `.test()`가 상태를 갖지 않는다.
|
|
2583
2455
|
*/
|
|
2584
2456
|
const TOSS_WIDGET_CLIENT_KEY_PATTERN = /^(test|live)_gck_/;
|
|
2585
2457
|
/** 토스페이먼츠 결제위젯 시크릿 키 접두사. {@link TOSS_WIDGET_CLIENT_KEY_PATTERN} 참고 */
|
|
@@ -2700,7 +2572,7 @@ const paymentProviderSchema = z.object({
|
|
|
2700
2572
|
liveGate: liveGateSchema.describe("라이브 개방 조건. 조건을 모두 충족해야 샌드박스를 끌 수 있다"),
|
|
2701
2573
|
easyPayProviders: z.array(easyPayProviderResponseSchema).describe("이 PG로 받는 간편결제사. 연결되지 않았으면 빈 목록. 현재 값은 `NAVERPAY`·`KAKAOPAY`·`TOSSPAY`·`PAYCO`이고 값이 추가될 수 있어 문자열로 받는다"),
|
|
2702
2574
|
retiredCredentialCount: z.number().int().describe("보관된(가맹점 교체·연결 해제로 새 결제에 더 쓰이지 않는) 자격 증명 수. 그 가맹점으로 받은 결제의 환불·대사에 계속 쓰인다"),
|
|
2703
|
-
routingExclusion: z.string().nullable().optional().default(null).describe("지금 새 결제의 후보가 아닌 사유. 후보면 null. 판정 원천은 서버 라우팅이다(
|
|
2575
|
+
routingExclusion: z.string().nullable().optional().default(null).describe("지금 새 결제의 후보가 아닌 사유. 후보면 null. 판정 원천은 서버 라우팅이다(상점 플랫폼이 다시 계산하지 않는다). 후보가 아닌 PG에는 항상 사유 코드를 붙인다(연결 안 된 PG 포함) — 모든 PG는 `candidates`에 있거나 이 값이 null이 아니다. 현재 값: `NOT_CONNECTED`(연결 안 됨) · `DISABLED`(사용 중지) · `CREDENTIAL_MISSING`(활성 환경 자격 증명 없음) · `SANDBOX_IN_LIVE_STORE`(다른 PG가 라이브라 이 샌드박스 PG는 실구매자 결제에 쓰지 않음) · `PLATFORM_BLOCKED`(플랫폼이 실결제를 막음) · `MODE_MISMATCH`(라이브를 연 서버 모드와 현재 모드가 다름). 값이 추가될 수 있어 문자열로 받는다 — 모르는 값은 후보 아님으로 다룬다. 서버는 항상 싣는다. 이 필드가 없는 이전 서버 응답은 기본값(null)으로 읽는다")
|
|
2704
2576
|
});
|
|
2705
2577
|
/**
|
|
2706
2578
|
* `PaymentProvider.routingExclusion`의 현재 값 — 응답 스키마는 서버가 사유를 추가해도 파싱이 깨지지 않게 문자열로 받는다.
|
|
@@ -2814,7 +2686,7 @@ const cashReceiptSchema = z.object({
|
|
|
2814
2686
|
canceledAt: z.string().nullable().describe("최근 취소 시각 (ISO 8601)"),
|
|
2815
2687
|
failCode: z.string().nullable().describe("마지막 실패 코드. 성공하면 비운다"),
|
|
2816
2688
|
failMessage: z.string().nullable().describe("마지막 실패 사유"),
|
|
2817
|
-
reissuable: z.boolean().describe("
|
|
2689
|
+
reissuable: z.boolean().describe("상점 플랫폼 재발급으로 다시 시도할 수 있는가 (`FAILED`·`PENDING`)"),
|
|
2818
2690
|
requestedAt: z.string().describe("신청 시각 (ISO 8601)"),
|
|
2819
2691
|
updatedAt: z.string().describe("마지막 변경 시각 (ISO 8601)")
|
|
2820
2692
|
});
|
|
@@ -2939,7 +2811,7 @@ function uploadExtensionOf(filename) {
|
|
|
2939
2811
|
}
|
|
2940
2812
|
/**
|
|
2941
2813
|
* 용도 제한 위반 여부. 통과하면 `null`.
|
|
2942
|
-
* 서버가 던지는 오류와
|
|
2814
|
+
* 서버가 던지는 오류와 상점 플랫폼이 먼저 띄우는 안내가 갈리지 않도록 한 함수로 판정한다.
|
|
2943
2815
|
*/
|
|
2944
2816
|
function uploadRejection(purpose, filename, size) {
|
|
2945
2817
|
const limit = UPLOAD_PURPOSE_LIMITS[purpose];
|
|
@@ -2994,7 +2866,8 @@ const productSummarySchema = z.object({
|
|
|
2994
2866
|
/** optionGroups.length > 0 */
|
|
2995
2867
|
hasOptions: z.boolean(),
|
|
2996
2868
|
fulfillment: z.object({ type: fulfillmentTypeResponseSchema }).describe("상품 이행의 유형. 유형별 속성은 단건 조회(`GET /v1/products/{productId}`)의 `fulfillment`에 있다"),
|
|
2997
|
-
updatedAt: z.string()
|
|
2869
|
+
updatedAt: z.string(),
|
|
2870
|
+
sample: z.boolean().optional().default(false).describe("상점을 만들 때 넣은 샘플 상품인가(이슈 #70). 상점 플랫폼이 「샘플」 배지를 단다. 스토어프론트에는 싣지 않는다. 서버는 항상 싣는다")
|
|
2998
2871
|
});
|
|
2999
2872
|
/** 옵션 축(그룹)이 가지는 값 하나 — 예: 축 "색상"의 값 "블랙" */
|
|
3000
2873
|
const optionValueSchema = z.object({
|
|
@@ -3041,7 +2914,7 @@ const MAX_ADDON_GROUPS = 5;
|
|
|
3041
2914
|
const MAX_CUSTOM_INPUTS = 5;
|
|
3042
2915
|
/** 직접 입력 항목 최대 글자 수의 상한 */
|
|
3043
2916
|
const MAX_CUSTOM_INPUT_LENGTH = 200;
|
|
3044
|
-
/** 조합 수가 이 값 이상이면
|
|
2917
|
+
/** 조합 수가 이 값 이상이면 상점 플랫폼이 경고한다(상한은 `MAX_VARIANTS`) */
|
|
3045
2918
|
const VARIANT_COUNT_WARNING = 100;
|
|
3046
2919
|
/**
|
|
3047
2920
|
* 옵션 값 정렬 순서 — 등록순·가나다순·낮은 추가금순·높은 추가금순. 서버가 저장할 때 이 순서로 값의 `sortOrder`를 매긴다.
|
|
@@ -3080,7 +2953,7 @@ const customInputSchema = z.object({
|
|
|
3080
2953
|
sortOrder: z.number().int()
|
|
3081
2954
|
});
|
|
3082
2955
|
/**
|
|
3083
|
-
* 직접 입력 항목명에 쓸 수 없는 개인정보성 단어 —
|
|
2956
|
+
* 직접 입력 항목명에 쓸 수 없는 개인정보성 단어 — 상점 플랫폼 검사와 서버 400(`CUSTOM_INPUT_LABEL_FORBIDDEN`)이 이 목록 하나를 쓴다.
|
|
3084
2957
|
* 구매자 개인정보는 주문 정보로 받는다. 직접 입력 항목으로 받으면 보관·파기 규칙 밖에 남는다.
|
|
3085
2958
|
*/
|
|
3086
2959
|
const CUSTOM_INPUT_FORBIDDEN_WORDS = [
|
|
@@ -3186,7 +3059,7 @@ const productSchema = productSummarySchema.extend({
|
|
|
3186
3059
|
optionSort: optionSortSchema.optional().default("REGISTERED").describe("옵션 값 정렬 순서(이슈 #59). 서버는 항상 싣는다"),
|
|
3187
3060
|
fulfillment: productFulfillmentResponseSchema,
|
|
3188
3061
|
fulfillmentTypeLocked: z.boolean().describe("판매 이력(주문상품)이 있어 상품 유형(`fulfillment.type`)을 바꿀 수 없는가. true면 다른 유형으로 수정하는 요청이 409 `FULFILLMENT_TYPE_LOCKED`다. 같은 유형 안의 속성은 바꿀 수 있다"),
|
|
3189
|
-
refundPolicy: productRefundPolicyPatchSchema.nullable().describe("상품별 환불 정책 덮어쓰기(상품이 정한 항목만). null이면
|
|
3062
|
+
refundPolicy: productRefundPolicyPatchSchema.nullable().describe("상품별 환불 정책 덮어쓰기(상품이 정한 항목만). null이면 상점 기본 환불 정책을 쓴다. 반품·교환 배송비(`returnDeliveryFee`·`exchangeDeliveryFee`)는 배송 상품에만 있다"),
|
|
3190
3063
|
/**
|
|
3191
3064
|
* 설정된 즉시할인. `GET /products/{productId}/discount`의 응답과 같은 형태다(이슈 #51).
|
|
3192
3065
|
* 할인이 없으면 null이다. 이 필드를 싣기 전 서버의 응답에는 없으므로 optional이다.
|
|
@@ -3334,7 +3207,7 @@ const productWriteShape = {
|
|
|
3334
3207
|
attributeId: z.string(),
|
|
3335
3208
|
value: z.string()
|
|
3336
3209
|
})).optional(),
|
|
3337
|
-
refundPolicy: productRefundPolicyPatchSchema.nullish().describe("상품별 환불 정책 — 준 항목만
|
|
3210
|
+
refundPolicy: productRefundPolicyPatchSchema.nullish().describe("상품별 환불 정책 — 준 항목만 상점 기본을 덮어쓴다. `null`이나 생략은 상점 기본을 그대로 쓴다는 뜻이다. 반품·교환 배송비(`returnDeliveryFee`·`exchangeDeliveryFee`)는 배송 상품에만 줄 수 있다(다른 유형은 400)"),
|
|
3338
3211
|
...optionModelRequestSchema.shape,
|
|
3339
3212
|
sourceOptionSetIds: z.array(z.string()).max(10).optional().describe("쓰기 전용(이슈 #59) — 이 저장으로 옵션을 불러온 저장 옵션 id. 상품 저장과 같은 트랜잭션에서 불러오기 기록을 남겨 저장 옵션의 `importedProductCount`를 센다. 없는 id는 무시한다")
|
|
3340
3213
|
};
|
|
@@ -3353,7 +3226,7 @@ const productPatchRequestSchema = z.strictObject({
|
|
|
3353
3226
|
salePrice: z.number().int().multipleOf(10).optional(),
|
|
3354
3227
|
status: productStatusSchema.optional(),
|
|
3355
3228
|
fulfillment: productFulfillmentUpdateSchema.optional().describe(fulfillmentUpdateDescription),
|
|
3356
|
-
refundPolicy: productRefundPolicyPatchSchema.nullable().optional().describe("상품별 환불 정책. 주면 통째로 바꾼다(준 항목만
|
|
3229
|
+
refundPolicy: productRefundPolicyPatchSchema.nullable().optional().describe("상품별 환불 정책. 주면 통째로 바꾼다(준 항목만 상점 기본을 덮어쓴다). `null`은 덮어쓰기를 지워 상점 기본을 따른다. 생략하면 바꾸지 않는다")
|
|
3357
3230
|
}).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" });
|
|
3358
3231
|
const productSortSchema = z.enum([
|
|
3359
3232
|
"createdAt.desc",
|
|
@@ -3529,7 +3402,7 @@ const dispatchRequestSchema = z.object({
|
|
|
3529
3402
|
* 발송 시각. ISO 8601이고 시간대(`Z` 또는 `+09:00`)를 포함해야 한다. 생략하면 현재 시각이다.
|
|
3530
3403
|
*
|
|
3531
3404
|
* 전에는 `z.string()`이라 아무 문자열이나 그대로 저장됐다. 이 값은 배송 정보 jsonb에 들어가
|
|
3532
|
-
* 자동 배송완료·추적 동기화 잡의 대상 선별 조건이 된다 — 잘못된 한 행이
|
|
3405
|
+
* 자동 배송완료·추적 동기화 잡의 대상 선별 조건이 된다 — 잘못된 한 행이 상점 구분 없이 도는
|
|
3533
3406
|
* 잡 전체를 멈출 수 있어 입력에서 좁혔다. 검증을 통과한 값도 저장 전에 UTC ISO로 정규화한다.
|
|
3534
3407
|
*/
|
|
3535
3408
|
dispatchedAt: z.iso.datetime({ offset: true }).optional()
|
|
@@ -3651,7 +3524,7 @@ const addressChangeRequestListSchema = z.array(z.record(z.string(), z.unknown())
|
|
|
3651
3524
|
* **불러오기는 복사다.** 저장 옵션을 고치거나 지워도 이미 불러온 상품의 옵션은 그대로다. 재고·SKU·사용 여부는 상품마다
|
|
3652
3525
|
* 달라 저장하지 않는다. 추가금은 조합별이 아니라 **값별**로 둔다 — 조합 옵션으로 불러오면 조합 추가금은 고른 값 추가금의 합이다.
|
|
3653
3526
|
*/
|
|
3654
|
-
/**
|
|
3527
|
+
/** 상점당 저장 옵션 최대 개수 */
|
|
3655
3528
|
const MAX_SAVED_OPTION_SETS = 200;
|
|
3656
3529
|
/** 저장 옵션 이름 최대 글자 수 */
|
|
3657
3530
|
const MAX_SAVED_OPTION_SET_NAME = 50;
|
|
@@ -3758,7 +3631,7 @@ const savedOptionSetListQuerySchema = z.object({
|
|
|
3758
3631
|
* (`deliveryBundleKeyOf`). 이슈 #25의 「묶음 = 같은 상품」 규칙을 일반화한 것이다 — 키를 상품 id에서
|
|
3759
3632
|
* 정책 지문으로 바꿨고, 주문상품이 주문 시점 키를 스냅샷해(`order_items.delivery_bundle_key`)
|
|
3760
3633
|
* 환불 정산(`refund-settlement.ts`)과 주문 스냅샷(`orders.delivery_fee_settlements`)이 같은 키를 쓴다.
|
|
3761
|
-
* 2. **무료 기준**: 상품별 조건부 무료(`CONDITIONAL_FREE`, 묶음 소계 기준)와
|
|
3634
|
+
* 2. **무료 기준**: 상품별 조건부 무료(`CONDITIONAL_FREE`, 묶음 소계 기준)와 상점 전체 무료 기준
|
|
3762
3635
|
* (`freeThreshold`, 주문 상품 금액 합 기준)이 있다. 둘 중 하나라도 충족하면 그 배송비는 0이다.
|
|
3763
3636
|
* 3. **지역 추가 배송비**: 우편번호가 제주·도서산간이면 **묶음마다** 추가된다. 묶음이 곧 한 번의 발송이라
|
|
3764
3637
|
* 두 출고지에서 각각 보내면 추가 운임도 두 번 든다.
|
|
@@ -3769,16 +3642,16 @@ const savedOptionSetListQuerySchema = z.object({
|
|
|
3769
3642
|
* 계산은 `quoteDelivery` 하나다. 장바구니·주문서·결제 시작·환불 정산이 모두 이 함수를 쓴다 —
|
|
3770
3643
|
* 네 곳이 각자 계산하면 화면 금액과 결제 금액이 갈라진다.
|
|
3771
3644
|
*
|
|
3772
|
-
* **비실물 줄은 배송 계산에서 빠진다**(이슈 #48, `requiresShipping`). 묶음을 만들지 않고,
|
|
3645
|
+
* **비실물 줄은 배송 계산에서 빠진다**(이슈 #48, `requiresShipping`). 묶음을 만들지 않고, 상점 전체 무료 기준의
|
|
3773
3646
|
* 판정 금액(`shippableAmount`)에도 넣지 않는다 — 디지털 상품 금액이 실물 배송비를 면제시키지 않게 한다.
|
|
3774
3647
|
*/
|
|
3775
3648
|
const descriptions = {
|
|
3776
|
-
baseDeliveryFee: "
|
|
3777
|
-
freeThreshold: "
|
|
3649
|
+
baseDeliveryFee: "상점 기본 배송비. 원 단위. 상품 등록·수정에서 `deliveryFee`를 생략하면 이 값을 쓴다. 기본 3000",
|
|
3650
|
+
freeThreshold: "상점 전체 무료배송 기준액. 주문 상품 금액 합이 이 값 이상이면 모든 묶음의 배송비가 0이다. null이면 전체 무료배송을 쓰지 않는다(상품별 조건부 무료는 그대로 동작한다). 기본 null",
|
|
3778
3651
|
jejuSurcharge: "제주 지역 추가 배송비. 원 단위. **배송 묶음마다** 더한다. 기본 0",
|
|
3779
3652
|
isolatedSurcharge: "도서산간 지역 추가 배송비. 원 단위. **배송 묶음마다** 더한다. 기본 0"
|
|
3780
3653
|
};
|
|
3781
|
-
/** 유효 배송비 정책 —
|
|
3654
|
+
/** 유효 배송비 정책 — 상점이 정하지 않은 항목은 `SHIPPING_POLICY_DEFAULTS`로 채운 값이다 */
|
|
3782
3655
|
const shippingPolicySchema = z.object({
|
|
3783
3656
|
baseDeliveryFee: z.number().int().min(0).describe(descriptions.baseDeliveryFee),
|
|
3784
3657
|
freeThreshold: z.number().int().min(1).nullable().describe(descriptions.freeThreshold),
|
|
@@ -3786,7 +3659,7 @@ const shippingPolicySchema = z.object({
|
|
|
3786
3659
|
isolatedSurcharge: z.number().int().min(0).describe(descriptions.isolatedSurcharge)
|
|
3787
3660
|
});
|
|
3788
3661
|
/**
|
|
3789
|
-
*
|
|
3662
|
+
* 상점이 정하지 않았을 때 서버가 쓰는 값.
|
|
3790
3663
|
*
|
|
3791
3664
|
* `as const`가 아니라 `ShippingPolicy`로 좁힌다 — 리터럴 타입이면 `Partial<typeof …>`로 일부만 바꿀 때
|
|
3792
3665
|
* `null` 자리에 숫자를 넣을 수 없다(`REFUND_POLICY_DEFAULTS`와 같은 이유).
|
|
@@ -3809,7 +3682,7 @@ const shippingPolicyPatchSchema = z.strictObject({
|
|
|
3809
3682
|
jejuSurcharge: z.number().int().min(0).optional().describe(descriptions.jejuSurcharge),
|
|
3810
3683
|
isolatedSurcharge: z.number().int().min(0).optional().describe(descriptions.isolatedSurcharge)
|
|
3811
3684
|
});
|
|
3812
|
-
/** 유효 정책 해석 —
|
|
3685
|
+
/** 유효 정책 해석 — 상점이 정한 항목 + `SHIPPING_POLICY_DEFAULTS` */
|
|
3813
3686
|
function resolveShippingPolicy(patch) {
|
|
3814
3687
|
if (!patch) return { ...SHIPPING_POLICY_DEFAULTS };
|
|
3815
3688
|
const given = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== void 0));
|
|
@@ -3825,10 +3698,10 @@ const remoteAreaSchema = z.enum([
|
|
|
3825
3698
|
"ISOLATED"
|
|
3826
3699
|
]);
|
|
3827
3700
|
/**
|
|
3828
|
-
* 제주·도서산간 우편번호 범위 — **코드 상수다.** 금액은
|
|
3701
|
+
* 제주·도서산간 우편번호 범위 — **코드 상수다.** 금액은 상점이 정하고 범위는 플랫폼이 고정한다.
|
|
3829
3702
|
*
|
|
3830
3703
|
* 국내 택배사가 공표하는 제주·도서산간 할증 구간을 5자리 우편번호 범위로 옮긴 표다. 셀러마다
|
|
3831
|
-
* 범위가 다르면 같은 주소에 다른 금액이 나가고 CS가 셀러 수만큼 갈라진다.
|
|
3704
|
+
* 범위가 다르면 같은 주소에 다른 금액이 나가고 CS가 셀러 수만큼 갈라진다. 상점별 범위 편집은
|
|
3832
3705
|
* 후속 과제이고, 지금은 이 표가 유일한 원천이다.
|
|
3833
3706
|
*
|
|
3834
3707
|
* ## 근거
|
|
@@ -3867,7 +3740,7 @@ const remoteAreaSchema = z.enum([
|
|
|
3867
3740
|
* 도로명주소·건물관리번호 단위 판정은 후속 과제다.
|
|
3868
3741
|
*
|
|
3869
3742
|
* 범위는 **닫힌 구간**(`from` ≤ zip ≤ `to`)이고 겹치지 않는다. 앞에서부터 처음 맞는 항목을 쓴다.
|
|
3870
|
-
* 순서는
|
|
3743
|
+
* 순서는 상점 플랫폼 「추가 배송비 지역」 표의 행 순서이기도 하다(`remoteAreaGroups`). 제주를 먼저 두고
|
|
3871
3744
|
* 도서산간은 우편번호 오름차순으로 둔다.
|
|
3872
3745
|
*/
|
|
3873
3746
|
const REMOTE_AREA_ZIP_RANGES = [
|
|
@@ -4120,7 +3993,7 @@ function deliveryBundleKeyOf(product) {
|
|
|
4120
3993
|
* 기본 배송비를 면제한 근거 — 면제하지 않았으면 null이다.
|
|
4121
3994
|
*
|
|
4122
3995
|
* 환불 정산이 근거를 구분해야 한다. 조건부 무료 기준 미달 차감은 **상품별 조건부 무료(`CONDITIONAL`)로
|
|
4123
|
-
* 면제된 묶음만** 대상이다.
|
|
3996
|
+
* 면제된 묶음만** 대상이다. 상점 전체 무료 기준(`STORE_THRESHOLD`)이나 무료배송 상품(`FREE`)은 셀러가
|
|
4124
3997
|
* 배송비를 받지 않기로 한 것이므로, 일부 취소로 기준이 깨져도 구매자에게 배송비를 물리지 않는다.
|
|
4125
3998
|
*
|
|
4126
3999
|
* 세 값이 겹치면 `FREE` → `STORE_THRESHOLD` → `CONDITIONAL` 순으로 센 근거를 남긴다.
|
|
@@ -4134,7 +4007,7 @@ const deliveryFeeWaiverSchema = z.enum([
|
|
|
4134
4007
|
* 배송비 계산 — **순수 함수다.** 장바구니 요약, 주문서 금액, 결제 시작의 최종 확정,
|
|
4135
4008
|
* 주문 배송비 스냅샷이 모두 이 한 곳을 쓴다.
|
|
4136
4009
|
*
|
|
4137
|
-
* 순서가 규칙이다. 묶음별 조건부 무료를 먼저 보고, 그다음
|
|
4010
|
+
* 순서가 규칙이다. 묶음별 조건부 무료를 먼저 보고, 그다음 상점 전체 무료 기준으로 기본 배송비를
|
|
4138
4011
|
* 한꺼번에 0으로 만들고, 마지막에 지역 추가 배송비를 묶음마다 더한다. 추가 배송비는 무료 기준에
|
|
4139
4012
|
* 걸리지 않는다.
|
|
4140
4013
|
*/
|
|
@@ -4192,14 +4065,14 @@ function quoteDelivery(input) {
|
|
|
4192
4065
|
//#endregion
|
|
4193
4066
|
//#region src/schemas/store.ts
|
|
4194
4067
|
/**
|
|
4195
|
-
*
|
|
4068
|
+
* 상점 코드 규칙 — 스토어프론트 API가 테넌트를 가르는 값이다(`X-Store-Code` 헤더).
|
|
4196
4069
|
* **개설할 때 서버가 랜덤으로 발급하고** 이후 바꿀 수 없다. 이미 나간 링크와 승인된 결제가
|
|
4197
4070
|
* 이 값에 묶여 있어서 바꾸면 함께 깨진다.
|
|
4198
4071
|
*
|
|
4199
4072
|
* 호스트 라벨로도 쓸 수 있게 DNS 라벨 규칙까지 좁힌다. 하이픈으로 시작하거나 끝나는 라벨은 호스트가 될 수 없다.
|
|
4200
4073
|
*/
|
|
4201
4074
|
const STORE_CODE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])$/;
|
|
4202
|
-
/** 플랫폼이 쓰거나 쓸 호스트 이름 —
|
|
4075
|
+
/** 플랫폼이 쓰거나 쓸 호스트 이름 — 상점 코드로 내주지 않는다 */
|
|
4203
4076
|
const RESERVED_STORE_CODES = [
|
|
4204
4077
|
"www",
|
|
4205
4078
|
"api",
|
|
@@ -4249,7 +4122,7 @@ const RESERVED_STORE_CODES = [
|
|
|
4249
4122
|
function isReservedStoreCode(code) {
|
|
4250
4123
|
return RESERVED_STORE_CODES.includes(code.toLowerCase());
|
|
4251
4124
|
}
|
|
4252
|
-
const storeCodeSchema = z.string().regex(STORE_CODE_PATTERN, "소문자·숫자·하이픈 3~30자여야 하고 하이픈으로 시작하거나 끝날 수 없습니다").refine((code) => !isReservedStoreCode(code), "플랫폼이 쓰는 이름이라
|
|
4125
|
+
const storeCodeSchema = z.string().regex(STORE_CODE_PATTERN, "소문자·숫자·하이픈 3~30자여야 하고 하이픈으로 시작하거나 끝날 수 없습니다").refine((code) => !isReservedStoreCode(code), "플랫폼이 쓰는 이름이라 상점 코드로 쓸 수 없습니다");
|
|
4253
4126
|
/** `https://shop.example.com`처럼 경로·쿼리 없는 http(s) 오리진인가 */
|
|
4254
4127
|
function isWebOrigin(value) {
|
|
4255
4128
|
try {
|
|
@@ -4266,7 +4139,7 @@ const MAX_ANALYTICS_ALLOWED_ORIGINS = 20;
|
|
|
4266
4139
|
/** 자동 배송완료·자동 구매확정 대기일 상한 */
|
|
4267
4140
|
const MAX_AUTO_TRANSITION_DAYS = 30;
|
|
4268
4141
|
/**
|
|
4269
|
-
* 약관 버전 —
|
|
4142
|
+
* 약관 버전 — 상점이 정하는 문자열이다(`1`·`2`·`2026-09-26`·`v1.2` 등). 형식은 강제하지 않고 값이 다르면 다른 버전이다.
|
|
4270
4143
|
* 올리면 그 약관에 동의한 구매자의 `reconsentRequired`가 켜지고, 재동의가 이력에 남는다. 되돌리면 재동의 요구도 풀린다.
|
|
4271
4144
|
*/
|
|
4272
4145
|
const policyVersionSchema = z.string().trim().min(1).max(20).describe("약관 버전 문자열(1~20자). 예: `1`, `2026-09-26`");
|
|
@@ -4288,8 +4161,8 @@ const updateStoreRequestSchema = z.object({
|
|
|
4288
4161
|
guestCheckoutEnabled: z.boolean().optional().describe("비회원 주문 허용. false(회원만)면 로그인하지 않은 구매자의 주문서 생성·결제 요청이 403 `GUEST_CHECKOUT_DISABLED`다. 장바구니 담기와 이미 받은 비회원 주문의 조회·취소는 막지 않는다"),
|
|
4289
4162
|
autoDeliverDays: z.number().int().min(0).max(30).optional().describe(autoDeliverDaysDescription),
|
|
4290
4163
|
autoConfirmDays: z.number().int().min(0).max(30).optional().describe(autoConfirmDaysDescription),
|
|
4291
|
-
refundPolicy: refundPolicyPatchSchema.optional().describe("
|
|
4292
|
-
shippingPolicy: shippingPolicyPatchSchema.optional().describe("
|
|
4164
|
+
refundPolicy: refundPolicyPatchSchema.optional().describe("상점 기본 환불 정책 — 준 항목만 바꾼다. 상품별 정책(`products.refundPolicy`)이 없는 상품에 적용된다"),
|
|
4165
|
+
shippingPolicy: shippingPolicyPatchSchema.optional().describe("상점 배송비 규칙 — 준 항목만 바꾼다. 기본 배송비, 전체 무료배송 기준액, 제주·도서산간 추가 배송비다. 바꾼 값은 **새 주문서부터** 적용된다(이미 만든 주문서·주문의 배송비는 스냅샷이 원천이다)"),
|
|
4293
4166
|
termsVersion: policyVersionSchema.optional().describe("이용약관 버전. 올리면 이 약관에 동의한 구매자에게 재동의를 요구한다(`GET /storefront/v1/me`의 `reconsentRequired`)"),
|
|
4294
4167
|
privacyVersion: policyVersionSchema.optional().describe("개인정보 수집이용 동의 버전. 올리면 구매자에게 재동의를 요구한다"),
|
|
4295
4168
|
termsUrl: policyDocumentUrlSchema.optional().describe("이용약관 문서 주소(https). 동의 시점의 값이 동의 이력에 복사된다. `null`을 주면 지운다"),
|
|
@@ -4313,8 +4186,8 @@ const storeSchema = z.object({
|
|
|
4313
4186
|
guestCheckoutEnabled: z.boolean().describe("비회원 주문 허용. false면 회원만 주문할 수 있다"),
|
|
4314
4187
|
autoDeliverDays: z.number().int().describe(autoDeliverDaysDescription),
|
|
4315
4188
|
autoConfirmDays: z.number().int().describe(autoConfirmDaysDescription),
|
|
4316
|
-
refundPolicy: refundPolicySchema.describe("
|
|
4317
|
-
shippingPolicy: shippingPolicySchema.describe("
|
|
4189
|
+
refundPolicy: refundPolicySchema.describe("상점 기본 환불 정책. 상점이 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다"),
|
|
4190
|
+
shippingPolicy: shippingPolicySchema.describe("상점 배송비 규칙. 상점이 정하지 않은 항목은 플랫폼 기본값으로 채워 항상 전부 싣는다"),
|
|
4318
4191
|
termsVersion: z.string().describe("현재 이용약관 버전. 가입·재동의가 이 값으로 이력에 남는다"),
|
|
4319
4192
|
privacyVersion: z.string().describe("현재 개인정보 수집이용 동의 버전"),
|
|
4320
4193
|
termsVersionUpdatedAt: z.string().nullable().describe("이용약관 버전을 마지막으로 바꾼 시각. 바꾼 적이 없으면 null"),
|
|
@@ -4334,7 +4207,7 @@ const storeMemberSchema = z.object({
|
|
|
4334
4207
|
});
|
|
4335
4208
|
/**
|
|
4336
4209
|
* 초대 메일 발송 결과 — `SENDING`은 발송 요청 중(결과를 아직 적지 못함), `NOT_CONFIGURED`는 메일 발송이 설정되지 않아
|
|
4337
|
-
* 보내지 않은 것이다. 발송 실패는 초대를 무효로 만들지 않는다 — 재발송으로 다시 보낸다
|
|
4210
|
+
* 보내지 않은 것이다. 상점의 월 메일 한도(요금제)를 다 써서 보내지 않았으면 `FAILED` + `deliveryErrorCode: MAIL_QUOTA_EXCEEDED`다. 발송 실패는 초대를 무효로 만들지 않는다 — 재발송으로 다시 보낸다
|
|
4338
4211
|
*/
|
|
4339
4212
|
const invitationDeliveryStatusSchema = z.enum([
|
|
4340
4213
|
"SENDING",
|
|
@@ -4413,7 +4286,7 @@ const sellerAddressRequestSchema = z.object({
|
|
|
4413
4286
|
/**
|
|
4414
4287
|
* 스토어프론트 호스팅 (이슈 #53, M1: 템플릿 발행) — 관리 API `/v1/storefront/**`의 계약이다.
|
|
4415
4288
|
*
|
|
4416
|
-
* 셀러는 템플릿을 골라 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다.
|
|
4289
|
+
* 셀러는 템플릿을 골라 사이트를 만들고 `{slug}.{호스팅 도메인}` 주소를 받는다. 상점을 만들어도 사이트는 생기지 않고
|
|
4417
4290
|
* `POST /v1/storefront/site`가 유일한 생성 경로다. 템플릿은 만들 때만 고르고 바꾸지 않는다. 발행은 버전 단위이고,
|
|
4418
4291
|
* 되돌리기는 이전 버전을 다시 발행하는 것이다(재빌드 없음). 상태의 원천은 api DB이고 Cloudflare 쪽 반영
|
|
4419
4292
|
* (라우팅 표·사이트 Worker)은 커밋 뒤 비동기다 — 반영 상태는 `provisioning`으로 본다.
|
|
@@ -4424,15 +4297,16 @@ const sellerAddressRequestSchema = z.object({
|
|
|
4424
4297
|
* 오류 코드:
|
|
4425
4298
|
* - 403 `STOREFRONT_HOSTING_DISABLED` — 플랫폼 스위치가 꺼져 새 사이트를 만들 수 없다
|
|
4426
4299
|
* - 404 `STOREFRONT_SITE_NOT_FOUND` · `STOREFRONT_TEMPLATE_NOT_FOUND` · `STOREFRONT_VERSION_NOT_FOUND`
|
|
4427
|
-
* - 409 `STOREFRONT_SITE_EXISTS` — 이미 사이트가 있다(
|
|
4300
|
+
* - 409 `STOREFRONT_SITE_EXISTS` — 이미 사이트가 있다(상점당 1개)
|
|
4428
4301
|
* - 409 `STOREFRONT_SLUG_TAKEN` — 다른 사이트가 쓰거나 보류 중인 슬러그다
|
|
4429
|
-
* - 409 `STOREFRONT_SLUG_CHANGE_LIMIT` — 슬러그는
|
|
4430
|
-
* - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는
|
|
4302
|
+
* - 409 `STOREFRONT_SLUG_CHANGE_LIMIT` — 슬러그는 상점당 30일에 3번까지 바꾼다
|
|
4303
|
+
* - 409 `STOREFRONT_SITE_CREATE_LIMIT` — 사이트는 상점당 30일에 3번까지 만든다
|
|
4431
4304
|
* - 409 `STOREFRONT_SITE_CHANGED` — 읽은 상태(`expected*`)와 현재 상태가 다르다. 다시 읽고 시도한다
|
|
4432
4305
|
* - 409 `STOREFRONT_SITE_NOT_ACTIVE` — 정지(`SUSPENDED`)된 사이트는 바꾸거나 끌 수 없다
|
|
4433
|
-
* - 409 `STORE_SUSPENDED` — 정지된
|
|
4306
|
+
* - 409 `STORE_SUSPENDED` — 정지된 상점에는 사이트를 만들 수 없다
|
|
4434
4307
|
* - 409 `STOREFRONT_VERSION_NOT_READY` — 빌드가 끝나지 않았거나 실패한 버전은 발행할 수 없다
|
|
4435
4308
|
* - 409 `STOREFRONT_NO_ROLLBACK_TARGET` — 되돌릴 이전 발행 버전이 없다
|
|
4309
|
+
* - 409 `STOREFRONT_NOT_READY` — 공개 전환: 판매 중인 상품(샘플 제외)이 없다(이슈 #70). 상품을 등록한 뒤 공개한다
|
|
4436
4310
|
* - 409 `TEMPLATE_CHANGE_NOT_SUPPORTED` — 템플릿 변경(`POST /v1/storefront/site/template`, 폐기)은 항상 이 오류다
|
|
4437
4311
|
* - 400 `STOREFRONT_SLUG_CONFIRM_MISMATCH` — 호스팅 끄기 확인 입력이 현재 슬러그와 다르다
|
|
4438
4312
|
* - 503 `STOREFRONT_PREVIEW_NOT_CONFIGURED` — 미리보기 서명 키가 설정되지 않았다
|
|
@@ -4455,7 +4329,7 @@ const sellerAddressRequestSchema = z.object({
|
|
|
4455
4329
|
* - 502 `STOREFRONT_SANDBOX_UNAVAILABLE` — 편집 Sandbox 호출이 실패했다. 잠시 뒤 다시 한다
|
|
4456
4330
|
*
|
|
4457
4331
|
* 소스 내려받기·올리기(#60, `/v1/storefront/site/source/**`, `sayren` CLI):
|
|
4458
|
-
* - 409 `SOURCE_CHANGED` — 작업 트리가 내려받은 뒤(`expectedBaseRef`) 바뀌었다(
|
|
4332
|
+
* - 409 `SOURCE_CHANGED` — 작업 트리가 내려받은 뒤(`expectedBaseRef`) 바뀌었다(상점 플랫폼·AI 편집). `error.details.currentRef`
|
|
4459
4333
|
* - 409 `EDIT_SESSION_OPEN` — 편집 환경이 열려 있다. `endEditSession: true`로 끄고 진행할 수 있다
|
|
4460
4334
|
* - 409 `SOURCE_BLOBS_MISSING` — 올리지 않은 파일 내용이 있다(`error.details.hashes`). `POST source/blobs`로 올리고 다시 한다
|
|
4461
4335
|
* - 409 `DEPENDENCY_REJECTED` — 쓸 수 없는 npm 의존성이다(`error.details.rejected: [{ name, version, reason, detail }]`)
|
|
@@ -4465,11 +4339,11 @@ const sellerAddressRequestSchema = z.object({
|
|
|
4465
4339
|
* - 404 `STOREFRONT_SOURCE_NOT_FOUND` — 그 스냅샷이 없다
|
|
4466
4340
|
*/
|
|
4467
4341
|
/**
|
|
4468
|
-
* 슬러그 형식 —
|
|
4342
|
+
* 슬러그 형식 — 상점 코드와 같은 DNS 라벨 규칙(소문자·숫자·하이픈 3~30자, 하이픈으로 시작·끝 불가)에
|
|
4469
4343
|
* 연속 하이픈 금지를 더한다. `--`는 버전 미리보기 주소(`v3--{slug}`)와 punycode(`xn--`)가 쓰는 구분자다.
|
|
4470
4344
|
*/
|
|
4471
4345
|
const STOREFRONT_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,28}[a-z0-9])$/;
|
|
4472
|
-
/** 호스팅 도메인에서 플랫폼이 쓰거나 쓸 이름 —
|
|
4346
|
+
/** 호스팅 도메인에서 플랫폼이 쓰거나 쓸 이름 — 상점 코드 예약어에 호스팅 전용 이름을 더한다 */
|
|
4473
4347
|
const RESERVED_STOREFRONT_SLUGS = [
|
|
4474
4348
|
...RESERVED_STORE_CODES,
|
|
4475
4349
|
"mcp",
|
|
@@ -4493,6 +4367,14 @@ const RESERVED_STOREFRONT_SLUGS = [
|
|
|
4493
4367
|
* 1단계 이름이라 슬러그가 이 모양이면 남의 편집 미리보기 주소와 겹칠 수 있어 슬러그로 쓰지 못한다.
|
|
4494
4368
|
*/
|
|
4495
4369
|
const STOREFRONT_EDIT_PREVIEW_SUFFIX = "-preview";
|
|
4370
|
+
/**
|
|
4371
|
+
* 공식 쇼케이스 슬러그 접두사(이슈 #72) — `demo-{템플릿 id}`는 템플릿 미리보기용 쇼케이스 사이트 주소다. 쇼케이스로 지정된
|
|
4372
|
+
* 상점만 쓸 수 있다(서버가 상점 용도로 판정한다). 공식 미리보기를 흉내 내는 주소를 막는다
|
|
4373
|
+
*/
|
|
4374
|
+
const STOREFRONT_SHOWCASE_SLUG_PREFIX = "demo-";
|
|
4375
|
+
function isShowcaseStorefrontSlug(slug) {
|
|
4376
|
+
return slug.toLowerCase().startsWith(STOREFRONT_SHOWCASE_SLUG_PREFIX);
|
|
4377
|
+
}
|
|
4496
4378
|
function isReservedStorefrontSlug(slug) {
|
|
4497
4379
|
const lower = slug.toLowerCase();
|
|
4498
4380
|
return RESERVED_STOREFRONT_SLUGS.includes(lower) || lower.endsWith("-preview");
|
|
@@ -4510,9 +4392,9 @@ const storefrontSlugSchema = z.string().regex(STOREFRONT_SLUG_PATTERN, "소문
|
|
|
4510
4392
|
const STOREFRONT_SLUG_HOLD_DAYS = 90;
|
|
4511
4393
|
/** 슬러그 변경 한도 — 이 기간(일) 안에 */
|
|
4512
4394
|
const STOREFRONT_SLUG_CHANGE_WINDOW_DAYS = 30;
|
|
4513
|
-
/** 슬러그 변경 한도 — 횟수(
|
|
4395
|
+
/** 슬러그 변경 한도 — 횟수(상점 단위. 끄고 다시 만들어도 이어서 센다) */
|
|
4514
4396
|
const STOREFRONT_SLUG_CHANGE_LIMIT = 3;
|
|
4515
|
-
/** 사이트 만들기 한도 — 같은 창(30일) 안의 횟수(
|
|
4397
|
+
/** 사이트 만들기 한도 — 같은 창(30일) 안의 횟수(상점 단위). 끄고 만들기를 되풀이해 이름을 묶어 두지 못하게 한다 */
|
|
4516
4398
|
const STOREFRONT_SITE_CREATE_LIMIT = 3;
|
|
4517
4399
|
const storefrontTemplateSchema = z.object({
|
|
4518
4400
|
templateId: z.string().describe("템플릿 id (예: `basic`)"),
|
|
@@ -4521,7 +4403,10 @@ const storefrontTemplateSchema = z.object({
|
|
|
4521
4403
|
description: z.string().describe("템플릿 설명"),
|
|
4522
4404
|
thumbnailUrl: z.string().nullable().describe("썸네일 이미지 주소. 없으면 null"),
|
|
4523
4405
|
version: z.string().describe("현재 템플릿 버전. 새 사이트는 이 버전으로 만든다"),
|
|
4524
|
-
isDefault: z.boolean().describe("기본 템플릿인가 (
|
|
4406
|
+
isDefault: z.boolean().describe("기본 템플릿인가 (상점 생성 시 자동 생성에 쓴다)"),
|
|
4407
|
+
tags: z.array(z.string()).default([]).describe("템플릿 특징 태그 (예: `후기 강조`). 목록 카드에 보인다. 옛 서버 응답은 빈 배열로 읽는다"),
|
|
4408
|
+
supportsCheckout: z.boolean().default(true).describe("주문을 받는 템플릿인가. false는 가격·문의 중심의 카탈로그형이다. 옛 서버 응답은 true로 읽는다"),
|
|
4409
|
+
previewUrl: z.string().nullable().default(null).describe("공식 쇼케이스 사이트 주소. 연결된 쇼케이스가 공개 중일 때만 싣고 그 밖에는 null")
|
|
4525
4410
|
});
|
|
4526
4411
|
/**
|
|
4527
4412
|
* 버전 상태 — 템플릿 버전(`source: TEMPLATE`)은 템플릿 산출물을 그대로 쓰므로 만들 때 곧바로 `READY`다. 편집 버전
|
|
@@ -4546,14 +4431,14 @@ const storefrontVersionSchema = z.object({
|
|
|
4546
4431
|
source: z.enum(["TEMPLATE", "EDIT"]).describe("`TEMPLATE` 템플릿 산출물 그대로 · `EDIT` 편집 작업 트리를 빌드함"),
|
|
4547
4432
|
buildId: z.string().nullable().describe("편집 버전의 빌드 (`GET /v1/storefront/site/builds/{buildId}`). 템플릿 버전은 null"),
|
|
4548
4433
|
createdByType: z.string().describe("만든 행위자 유형 (`USER`·`AGENT`·`APP`·`TOKEN`·`SYSTEM`). 늘어날 수 있다"),
|
|
4549
|
-
createdVia: z.string().nullable().optional().describe("만든 경로 (`CONSOLE`
|
|
4434
|
+
createdVia: z.string().nullable().optional().describe("만든 경로 (`CONSOLE` 상점 플랫폼 · `AI` AI 에이전트(MCP) · `CLI` `sayren` CLI · `API` 관리 API · `SYSTEM` 자동). 늘어날 수 있다"),
|
|
4550
4435
|
createdAt: z.string().describe("만든 시각 (ISO 8601)")
|
|
4551
4436
|
});
|
|
4552
4437
|
const storefrontSiteStatusSchema = z.enum([
|
|
4553
4438
|
"ACTIVE",
|
|
4554
4439
|
"SUSPENDED",
|
|
4555
4440
|
"CLOSED"
|
|
4556
|
-
]).describe("`ACTIVE` 운영 중 · `SUSPENDED` 운영자 정지 · `CLOSED` 호스팅
|
|
4441
|
+
]).describe("`ACTIVE` 운영 중 · `SUSPENDED` 운영자 정지 · `CLOSED` 호스팅 끔·상점 폐쇄");
|
|
4557
4442
|
const storefrontVisibilitySchema = z.enum(["COMING_SOON", "PUBLIC"]).describe("`COMING_SOON` 오픈 준비 중(방문자에게 준비 중 페이지) · `PUBLIC` 공개");
|
|
4558
4443
|
const storefrontSourceModeSchema = z.enum(["MANAGED", "CUSTOM"]).describe("`MANAGED` 템플릿 그대로 · `CUSTOM` 한 번이라도 편집함");
|
|
4559
4444
|
const storefrontDomainSchema = z.object({
|
|
@@ -4578,7 +4463,7 @@ const storefrontProvisioningSchema = z.object({
|
|
|
4578
4463
|
});
|
|
4579
4464
|
/**
|
|
4580
4465
|
* 편집 미리보기 주소(M2b) — 사이트마다 `{사이트 id 정규화}-preview.{호스팅 도메인}` 호스트 하나다. 호스팅 도메인의 와일드카드
|
|
4581
|
-
* 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(
|
|
4466
|
+
* 인증서가 덮으므로 따로 준비할 것이 없다. 이 주소는 편집 미리보기 링크(상점 플랫폼 전용)로 받은 쿠키가 있어야 열린다.
|
|
4582
4467
|
*/
|
|
4583
4468
|
const storefrontEditPreviewSchema = z.object({ hostname: z.string().nullable().describe("편집 미리보기 호스트 (예: `sfs-ab12cd34-preview.sayren.co`). 편집을 쓸 수 없으면 null") });
|
|
4584
4469
|
/**
|
|
@@ -4625,7 +4510,7 @@ const storefrontSlugAvailabilitySchema = z.object({
|
|
|
4625
4510
|
]).nullable().describe("쓸 수 없는 이유. `INVALID` 형식 · `RESERVED` 예약어 · `TAKEN` 사용·보류 중 · `CURRENT` 지금 내 슬러그")
|
|
4626
4511
|
});
|
|
4627
4512
|
/**
|
|
4628
|
-
* 배포 진행 — 사이트의 지금 발행 상태가 공개 주소에 반영되기까지의 단계와 기록이다(
|
|
4513
|
+
* 배포 진행 — 사이트의 지금 발행 상태가 공개 주소에 반영되기까지의 단계와 기록이다(상점 플랫폼의 스토어프론트 만들기 3단계가 폴링한다).
|
|
4629
4514
|
* 만들기(`POST /v1/storefront/site`)는 곧바로 201로 답하고 반영은 그 뒤에 비동기로 하므로, 끝났는지는 이 조회로 본다.
|
|
4630
4515
|
* 템플릿 사이트는 미리 빌드한 템플릿 산출물을 올리므로 빌드 단계가 없다. 기록은 사이트 상태에서 만든 요약이다.
|
|
4631
4516
|
*/
|
|
@@ -4671,7 +4556,7 @@ const storefrontDeploymentSchema = z.object({
|
|
|
4671
4556
|
});
|
|
4672
4557
|
const createStorefrontSiteRequestSchema = z.object({
|
|
4673
4558
|
templateId: z.string().min(1).max(64).describe("템플릿 id"),
|
|
4674
|
-
slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면
|
|
4559
|
+
slug: storefrontSlugSchema.optional().describe("주소 슬러그. 생략하면 상점 코드")
|
|
4675
4560
|
});
|
|
4676
4561
|
/** @deprecated 템플릿 변경은 지원하지 않는다(항상 409 `TEMPLATE_CHANGE_NOT_SUPPORTED`). 호환을 위해 스키마만 남겼다 */
|
|
4677
4562
|
const changeStorefrontTemplateRequestSchema = z.object({
|
|
@@ -4698,7 +4583,7 @@ const setStorefrontVisibilityRequestSchema = z.object({
|
|
|
4698
4583
|
const disableStorefrontHostingRequestSchema = z.object({ confirmSlug: z.string().min(1).describe("확인 입력 — 현재 슬러그") });
|
|
4699
4584
|
const storefrontSlugAvailabilityQuerySchema = z.object({ slug: z.string().describe("확인할 슬러그") });
|
|
4700
4585
|
/**
|
|
4701
|
-
* 오픈 준비 중 미리보기 링크 —
|
|
4586
|
+
* 오픈 준비 중 미리보기 링크 — 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다(PAT·에이전트 불가). 링크는 5분 안에 한 번 열면
|
|
4702
4587
|
* 그 브라우저에 12시간짜리 미리보기 쿠키가 생기고 오픈 준비 중인 사이트의 실제 화면을 본다. 링크를 남에게 넘기면 그 사람도
|
|
4703
4588
|
* 볼 수 있으므로 공유하지 않는다.
|
|
4704
4589
|
*/
|
|
@@ -4798,7 +4683,7 @@ const STOREFRONT_PUBLIC_SUFFIXES = [
|
|
|
4798
4683
|
"cafe24.com",
|
|
4799
4684
|
"imweb.me"
|
|
4800
4685
|
];
|
|
4801
|
-
/**
|
|
4686
|
+
/** 상점당 연결할 수 있는 도메인 수 기본값 — 요금제(#44) 전의 상한이다. 실제 값은 `GET …/domains`의 `limit` */
|
|
4802
4687
|
const STOREFRONT_CUSTOM_DOMAIN_LIMIT_DEFAULT = 2;
|
|
4803
4688
|
/** DNS를 넣을 때까지 기다리는 기간(일) — 지나면 `FAILED`(`DNS_NOT_CONFIGURED`) */
|
|
4804
4689
|
const STOREFRONT_DOMAIN_DNS_WAIT_DAYS = 7;
|
|
@@ -4873,7 +4758,7 @@ const storefrontSiteDomainSchema = z.object({
|
|
|
4873
4758
|
"ACTIVE",
|
|
4874
4759
|
"FAILED"
|
|
4875
4760
|
]).nullable().describe("인증서 상태. 소유 확인 전·기본 주소는 null"),
|
|
4876
|
-
failureCode: z.string().nullable().describe("실패·경고 코드. `DNS_NOT_CONFIGURED` 기한 안에 TXT를 찾지 못함 · `VALIDATION_TIMEOUT` 인증서 발급 기한 지남 · `CERTIFICATE_FAILED` 인증서 발급 실패 · `DNS_NOT_POINTING` 연결된 뒤 DNS가 이 서비스를 가리키지 않음(기한이 지나면 해제) · `DOMAIN_TAKEN` 다른
|
|
4761
|
+
failureCode: z.string().nullable().describe("실패·경고 코드. `DNS_NOT_CONFIGURED` 기한 안에 TXT를 찾지 못함 · `VALIDATION_TIMEOUT` 인증서 발급 기한 지남 · `CERTIFICATE_FAILED` 인증서 발급 실패 · `DNS_NOT_POINTING` 연결된 뒤 DNS가 이 서비스를 가리키지 않음(기한이 지나면 해제) · `DOMAIN_TAKEN` 다른 상점이 먼저 소유를 확인함 · `HOSTNAME_BLOCKED` 연결이 막힘 · `HOSTNAME_CONFLICT` 연결 준비 중 충돌(운영 확인). 늘어날 수 있다"),
|
|
4877
4762
|
verifiedAt: z.string().nullable().describe("소유를 확인한 시각 (ISO 8601)"),
|
|
4878
4763
|
activatedAt: z.string().nullable().describe("연결된 시각 (ISO 8601)"),
|
|
4879
4764
|
checkedAt: z.string().nullable().describe("마지막으로 확인한 시각 (ISO 8601)"),
|
|
@@ -4882,7 +4767,7 @@ const storefrontSiteDomainSchema = z.object({
|
|
|
4882
4767
|
/** `GET /v1/storefront/site/domains` — 사이트의 주소 목록(기본 주소 + 내 도메인)과 연결 설정 */
|
|
4883
4768
|
const storefrontSiteDomainsSchema = z.object({
|
|
4884
4769
|
enabled: z.boolean().describe("내 도메인을 추가할 수 있는가 (플랫폼 스위치)"),
|
|
4885
|
-
limit: z.number().int().min(0).describe("
|
|
4770
|
+
limit: z.number().int().min(0).describe("상점당 내 도메인 수 상한 (해제한 도메인은 세지 않는다)"),
|
|
4886
4771
|
target: z.string().describe("CNAME 대상 (예: `sites.sayren.co`)"),
|
|
4887
4772
|
domains: z.array(storefrontSiteDomainSchema).describe("대표 주소 먼저, 그다음 추가한 순서")
|
|
4888
4773
|
});
|
|
@@ -4956,7 +4841,7 @@ const storefrontFileEncodingSchema = z.enum(["utf8", "base64"]).describe("`utf8`
|
|
|
4956
4841
|
* 90초). `RUNNING`이면 미리보기와 파일 쓰기가 바로 반영된다(HMR).
|
|
4957
4842
|
*/
|
|
4958
4843
|
const storefrontEditSessionStatusSchema = z.enum(["STARTING", "RUNNING"]).describe("`STARTING` 띄우는 중 · `RUNNING` 사용 중");
|
|
4959
|
-
/** 편집 환경을 띄우는 단계 —
|
|
4844
|
+
/** 편집 환경을 띄우는 단계 — 상점 플랫폼 진행 표시용이다. 값은 늘어날 수 있다 */
|
|
4960
4845
|
const STOREFRONT_EDIT_START_STAGES = [
|
|
4961
4846
|
"CONTAINER",
|
|
4962
4847
|
"RESTORE",
|
|
@@ -5236,7 +5121,7 @@ const storefrontVersionCreatedSchema = z.object({
|
|
|
5236
5121
|
build: storefrontBuildSchema
|
|
5237
5122
|
});
|
|
5238
5123
|
/**
|
|
5239
|
-
* `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 링크.
|
|
5124
|
+
* `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 링크. 상점 플랫폼에서 로그인한 OWNER·ADMIN만
|
|
5240
5125
|
* 받는다(PAT·에이전트 불가). 5분 안에 열면 그 브라우저에 미리보기 호스트 전용 쿠키(12시간)가 생긴다. 편집 환경이
|
|
5241
5126
|
* 없으면 409 `EDIT_SESSION_REQUIRED`다(편집 환경이 `STARTING`이면 링크는 주고, 미리보기 화면이 기동 대기를 보여 준다).
|
|
5242
5127
|
*/
|
|
@@ -5253,7 +5138,7 @@ const storefrontSourceRefSchema = z.string().regex(/^sources\/sfs_[a-z0-9]{8,40}
|
|
|
5253
5138
|
const storefrontSourceSchema = z.object({
|
|
5254
5139
|
siteId: z.string(),
|
|
5255
5140
|
storeId: z.string(),
|
|
5256
|
-
storeCode: z.string().describe("
|
|
5141
|
+
storeCode: z.string().describe("상점 코드 — 로컬 개발 서버의 `SAYREN_STORE_CODE`"),
|
|
5257
5142
|
sourceRef: storefrontSourceRefSchema,
|
|
5258
5143
|
treeSha256: z.string().describe("트리 지문 (경로 오름차순 `{path}\\t{sha256}\\n`의 SHA-256)"),
|
|
5259
5144
|
files: z.number().int().min(0).describe("파일 수"),
|
|
@@ -5316,35 +5201,188 @@ const storefrontSourceReplacedSchema = z.object({
|
|
|
5316
5201
|
dependencyWarnings: z.array(storefrontDependencyFindingSchema).describe("배포를 막지 않는 의존성 경고 (보안 권고 등)")
|
|
5317
5202
|
});
|
|
5318
5203
|
//#endregion
|
|
5204
|
+
//#region src/schemas/subscription.ts
|
|
5205
|
+
/**
|
|
5206
|
+
* 요금제·구독 — `GET /v1/store/subscription`(스코프 `store:r`). 구독 단위는 상점이다.
|
|
5207
|
+
* 결제 수단·청구 금액은 싣지 않는다. 요금제 변경은 상점 플랫폼에서만 한다.
|
|
5208
|
+
*/
|
|
5209
|
+
/** 요금제 코드 — 이름(`plan.name`)이 바뀌어도 코드는 바뀌지 않는다. 값은 추가만 한다 */
|
|
5210
|
+
const planCodeSchema = z.enum([
|
|
5211
|
+
"BETA",
|
|
5212
|
+
"FREE",
|
|
5213
|
+
"BASIC",
|
|
5214
|
+
"PRO",
|
|
5215
|
+
"BUSINESS",
|
|
5216
|
+
"ENTERPRISE",
|
|
5217
|
+
"PREMIUM"
|
|
5218
|
+
]).describe("요금제 코드. `FREE`=Free, `PRO`=Pro, `PREMIUM`=프리미엄, `ENTERPRISE`=엔터프라이즈, `BETA`=베타(운영 오픈 기간). `BASIC`·`BUSINESS`는 2026-09-30 이전 요금제(더 쓰지 않는다)");
|
|
5219
|
+
/**
|
|
5220
|
+
* 응답의 요금제 코드 — 아는 코드(`PlanCode`)거나 이 SDK가 모르는 새 코드(문자열)다. 요금제 코드는 추가될 수 있어 응답 파싱을 깨지 않게
|
|
5221
|
+
* 넓게 받는다(옛 SDK는 새 코드를 모르면 파싱에 실패했다, #44 리뷰 M3). 요청에는 `planCodeSchema`를 쓴다
|
|
5222
|
+
*/
|
|
5223
|
+
const responsePlanCodeSchema = z.union([planCodeSchema, z.string()]).describe(planCodeSchema.description ?? "요금제 코드");
|
|
5224
|
+
const subscriptionStatusSchema = z.enum([
|
|
5225
|
+
"ACTIVE",
|
|
5226
|
+
"PAST_DUE",
|
|
5227
|
+
"CANCELED",
|
|
5228
|
+
"INCOMPLETE"
|
|
5229
|
+
]).describe("구독 상태. `ACTIVE`=이용 중, `PAST_DUE`=결제 실패 후 유예 중, `CANCELED`=해지됨, `INCOMPLETE`=첫 결제 대기");
|
|
5230
|
+
const limit = (description) => z.number().int().nullable().describe(`${description}. null이면 한도 없음`);
|
|
5231
|
+
const subscriptionLimitsSchema = z.object({
|
|
5232
|
+
liveOrdersMonthly: limit("더 쓰지 않는다 — 라이브 주문 수로 요금제를 나누지 않아 항상 null"),
|
|
5233
|
+
seatsIncluded: limit("구성원 석 — 구성원은 모든 요금제에서 무료·무제한이라 null"),
|
|
5234
|
+
aiActionsMonthly: limit("MCP(에이전트) 쓰기(AI 작업) 월 한도"),
|
|
5235
|
+
aiReadsMonthly: limit("MCP(에이전트) 읽기 월 한도"),
|
|
5236
|
+
customDomains: limit("내 도메인 수"),
|
|
5237
|
+
webhooks: limit("웹훅 구독 수(0이면 쓸 수 없다)"),
|
|
5238
|
+
socialProviders: limit("더 쓰지 않는다 — 소셜 로그인 공급자 수로 요금제를 나누지 않아 항상 null"),
|
|
5239
|
+
emailsMonthly: limit("더 쓰지 않는다 — 메일 한도가 없어 항상 null"),
|
|
5240
|
+
storefrontRequestsMonthly: limit("스토어프론트 월 트래픽(요청 수, 넘어도 막지 않고 안내만)"),
|
|
5241
|
+
apiLogRetentionDays: limit("API 로그 보관 일수(0이면 API 로그 없음)"),
|
|
5242
|
+
auditLogRetentionDays: limit("감사 로그 보관 일수"),
|
|
5243
|
+
storefrontPreviewHoursMonthly: limit("스토어프론트 편집 미리보기 월 시간"),
|
|
5244
|
+
storefrontBuildMinutesMonthly: limit("스토어프론트 빌드 월 분"),
|
|
5245
|
+
adminApiRequestsMonthly: limit("관리 API 요청 월 포함량(넘으면 속도만 낮춘다, 주문 처리 제외)")
|
|
5246
|
+
});
|
|
5247
|
+
const subscriptionUsageSchema = z.object({
|
|
5248
|
+
liveOrders: z.number().int().describe("이번 기간 라이브 주문 수(테스트 결제 주문 제외)"),
|
|
5249
|
+
aiActions: z.number().int().describe("이번 기간 MCP(에이전트) 쓰기(AI 작업) 수"),
|
|
5250
|
+
aiReads: z.number().int().describe("이번 기간 MCP(에이전트) 읽기 수"),
|
|
5251
|
+
storefrontRequests: z.number().int().describe("이번 기간 스토어프론트 요청 수"),
|
|
5252
|
+
adminApiRequests: z.number().int().describe("이번 기간 관리 API 요청 수(API 토큰·앱·에이전트 호출, 상점 플랫폼 제외)"),
|
|
5253
|
+
emails: z.number().int().describe("이번 기간 초대·양도 알림 메일 수(한도 없음, 표시만)"),
|
|
5254
|
+
storefrontPreviewMinutes: z.number().int().describe("이번 기간 편집 미리보기 분(끝난 세션)"),
|
|
5255
|
+
storefrontBuildMinutes: z.number().int().describe("이번 기간 빌드 분(끝난 빌드)"),
|
|
5256
|
+
seats: z.number().int().describe("현재 석 수 — 구성원(OWNER 포함) + 대기 중 초대"),
|
|
5257
|
+
customDomains: z.number().int().describe("현재 내 도메인 수(해제한 도메인 제외)"),
|
|
5258
|
+
webhooks: z.number().int().describe("현재 웹훅 구독 수")
|
|
5259
|
+
});
|
|
5260
|
+
const storeSubscriptionSchema = z.object({
|
|
5261
|
+
plan: z.object({
|
|
5262
|
+
code: responsePlanCodeSchema,
|
|
5263
|
+
name: z.string().describe("요금제 표시 이름(예: 프로)")
|
|
5264
|
+
}),
|
|
5265
|
+
status: subscriptionStatusSchema,
|
|
5266
|
+
interval: z.enum(["MONTH"]).nullable().describe("결제 주기. `MONTH`=월간. 요금을 내지 않는 요금제(베타·무료)는 null"),
|
|
5267
|
+
cancelAtPeriodEnd: z.boolean().describe("기간 끝에 해지 예정인가"),
|
|
5268
|
+
period: z.object({
|
|
5269
|
+
start: z.string().describe("사용량 기간 시작(ISO 8601)"),
|
|
5270
|
+
end: z.string().describe("사용량 기간 끝(ISO 8601, 이 시각에 사용량이 초기화된다)")
|
|
5271
|
+
}).describe("사용량을 세는 기간. 요금을 내지 않는 요금제는 한국 시간 달력 월이다"),
|
|
5272
|
+
limits: subscriptionLimitsSchema,
|
|
5273
|
+
features: z.object({
|
|
5274
|
+
adminApi: z.boolean().describe("관리 API(개인 액세스 토큰·외부 앱 토큰 호출)를 쓸 수 있다"),
|
|
5275
|
+
analytics: z.boolean().describe("애널리틱스 조회를 쓸 수 있다"),
|
|
5276
|
+
storeClone: z.boolean().describe("더 쓰지 않는다 — 상점 복제 기능을 없애 항상 false"),
|
|
5277
|
+
removeBadge: z.boolean().describe("더 쓰지 않는다 — 배지 키를 없애 항상 false")
|
|
5278
|
+
}),
|
|
5279
|
+
usage: subscriptionUsageSchema,
|
|
5280
|
+
aiActions: z.object({
|
|
5281
|
+
limit: z.number().int().nullable(),
|
|
5282
|
+
used: z.number().int(),
|
|
5283
|
+
resetsAt: z.string().describe("한도가 초기화되는 시각(ISO 8601)")
|
|
5284
|
+
}).describe("AI 작업(MCP 쓰기) 한도 요약 — 에이전트가 남은 작업 수를 판단할 때 쓴다"),
|
|
5285
|
+
aiReads: z.object({
|
|
5286
|
+
limit: z.number().int().nullable(),
|
|
5287
|
+
used: z.number().int(),
|
|
5288
|
+
resetsAt: z.string().describe("한도가 초기화되는 시각(ISO 8601)")
|
|
5289
|
+
}).describe("MCP 읽기 한도 요약"),
|
|
5290
|
+
salesFeePercent: z.literal(0).describe("판매 수수료(%). 모든 요금제 0이다")
|
|
5291
|
+
});
|
|
5292
|
+
/**
|
|
5293
|
+
* 요금제 한도 오류의 `error.details` — 기존 오류 코드(`STORE_LIMIT_EXCEEDED`·`STOREFRONT_DOMAIN_LIMIT`·`WEBHOOK_LIMIT_EXCEEDED` 등)와
|
|
5294
|
+
* 요금제 오류(`PLAN_LIMIT_EXCEEDED`·`PLAN_AI_ACTIONS_EXCEEDED`·`PLAN_AI_READS_EXCEEDED`·`PLAN_FEATURE_REQUIRED`·`PLAN_API_QUOTA_EXCEEDED`)가 같은 형식을 싣는다.
|
|
5295
|
+
* 모든 필드가 선택이다(기존 코드는 요금제 판정 전 필드만 실을 수 있다).
|
|
5296
|
+
*/
|
|
5297
|
+
const planErrorDetailsSchema = z.object({
|
|
5298
|
+
feature: z.string().optional().describe("한도에 닿은 기능 키(예: `limits.ai_actions_monthly`)"),
|
|
5299
|
+
limit: z.number().nullable().optional(),
|
|
5300
|
+
used: z.number().optional(),
|
|
5301
|
+
plan: responsePlanCodeSchema.optional(),
|
|
5302
|
+
resetsAt: z.string().optional().describe("월 한도가 초기화되는 시각(ISO 8601)"),
|
|
5303
|
+
upgradeUrl: z.string().optional().describe("요금제 화면 주소(상점 플랫폼)"),
|
|
5304
|
+
reason: z.string().optional().describe("상점 수 한도의 이유 — `OWNED_STORES`(소유 상점 수). `FREE_STORES`는 더 쓰지 않는다")
|
|
5305
|
+
}).loose();
|
|
5306
|
+
const limitOrContract = (description) => z.number().int().nullable().describe(`${description}. null이면 계약(엔터프라이즈)·한도 없음`);
|
|
5307
|
+
/**
|
|
5308
|
+
* 요금제 기능 키 값 — 요금제 비교표·상점 플랫폼 요금제 화면이 쓴다. 키는 서버 판정의 기능 키와 같다(추가만 한다). 상위 요금제는 하위 요금제
|
|
5309
|
+
* 기능을 모두 포함한다
|
|
5310
|
+
*/
|
|
5311
|
+
const planEntitlementsSchema = z.object({
|
|
5312
|
+
"features.admin_api": z.boolean().describe("관리 API(개인 액세스 토큰·외부 앱 토큰 호출). 상점 플랫폼·CLI·MCP는 모든 요금제에서 쓴다"),
|
|
5313
|
+
"features.analytics": z.boolean().describe("애널리틱스 화면·조회 API"),
|
|
5314
|
+
"limits.ai_reads_monthly": limitOrContract("MCP 읽기 월 한도"),
|
|
5315
|
+
"limits.ai_actions_monthly": limitOrContract("MCP 쓰기(AI 작업) 월 한도 — 주문 처리는 막지 않는다"),
|
|
5316
|
+
"limits.custom_domains": limitOrContract("내 도메인 수"),
|
|
5317
|
+
"limits.webhooks": limitOrContract("웹훅 구독 수(0이면 쓸 수 없다)"),
|
|
5318
|
+
"limits.storefront_preview_hours_monthly": limitOrContract("편집 환경 시간(월, 시간)"),
|
|
5319
|
+
"limits.storefront_build_minutes_monthly": limitOrContract("빌드 시간(월, 분)"),
|
|
5320
|
+
"limits.storefront_requests_monthly": limitOrContract("스토어프론트 월 트래픽(요청 수) — 넘어도 막지 않고 안내만 한다"),
|
|
5321
|
+
"limits.admin_api_requests_monthly": limitOrContract("관리 API 월 요청 — 넘으면 속도만 낮춘다(주문 처리 제외)"),
|
|
5322
|
+
"rate_limit.admin_api": z.number().nullable().describe("관리 API 분당 속도 배수(기본 분당 600 × 배수)"),
|
|
5323
|
+
"storefront.cpu_ms": z.number().int().describe("스토어프론트 사이트 요청당 CPU 상한(ms)"),
|
|
5324
|
+
"storefront.subrequests": z.number().int().describe("스토어프론트 사이트 요청당 하위 요청 상한"),
|
|
5325
|
+
"storefront.rate_limit_per_minute": z.number().int().describe("스토어프론트 남용 방지 요청 속도 상한(사이트당 분당)"),
|
|
5326
|
+
"seats.included": limitOrContract("포함 구성원 석 — 구성원은 모든 요금제에서 무료·무제한이라 null"),
|
|
5327
|
+
"retention.analytics_days": z.number().int().describe("애널리틱스 보관 일수"),
|
|
5328
|
+
"retention.api_logs_days": limitOrContract("API 로그 보관 일수(0이면 API 로그 없음)"),
|
|
5329
|
+
"retention.audit_days": limitOrContract("감사 로그 보관 일수"),
|
|
5330
|
+
"support.tier": z.union([z.enum([
|
|
5331
|
+
"COMMUNITY",
|
|
5332
|
+
"EMAIL",
|
|
5333
|
+
"EMAIL_PRIORITY",
|
|
5334
|
+
"DEDICATED",
|
|
5335
|
+
"SLA"
|
|
5336
|
+
]), z.string()]).describe("지원 등급(`COMMUNITY`·`EMAIL`·`EMAIL_PRIORITY`·`DEDICATED`·`SLA`, 새 등급이 더해질 수 있다)")
|
|
5337
|
+
}).describe("요금제 기능 키 값");
|
|
5338
|
+
/** 공개 요금제 한 줄 — `GET /v1/public/plans` */
|
|
5339
|
+
const publicPlanSchema = z.object({
|
|
5340
|
+
code: responsePlanCodeSchema,
|
|
5341
|
+
name: z.string().describe("표시 이름(Free·Pro·프리미엄·엔터프라이즈)"),
|
|
5342
|
+
version: z.number().int().describe("요금제 버전 — 값이 바뀌면 새 버전이 게시된다"),
|
|
5343
|
+
sort: z.number().int(),
|
|
5344
|
+
purchasable: z.boolean().describe("셀프 결제로 고를 수 있는가(Free·엔터프라이즈는 아니다 — 엔터프라이즈는 계약)"),
|
|
5345
|
+
prices: z.object({
|
|
5346
|
+
currency: z.literal("KRW"),
|
|
5347
|
+
monthly: z.number().int().nullable().describe("월 가격(공급가, 부가세 별도)"),
|
|
5348
|
+
startingAt: z.boolean().describe("「부터」 가격(엔터프라이즈 계약)"),
|
|
5349
|
+
vatIncluded: z.object({ monthly: z.number().int().nullable() }).describe("표시 가격(부가세 포함) — 요금제 비교표는 이 값을 보인다")
|
|
5350
|
+
}),
|
|
5351
|
+
entitlements: planEntitlementsSchema
|
|
5352
|
+
});
|
|
5353
|
+
/** 요금제 비교표 — `GET /v1/public/plans`(인증 없음, 캐시 가능). 값은 서버가 관리하고 언제든 바뀔 수 있다 */
|
|
5354
|
+
const publicPlansSchema = z.object({
|
|
5355
|
+
vatPercent: z.number().int().describe("부가세율(%)"),
|
|
5356
|
+
plans: z.array(publicPlanSchema).describe("공개 요금제의 지금 버전 — 정렬 순")
|
|
5357
|
+
});
|
|
5358
|
+
//#endregion
|
|
5319
5359
|
//#region src/client.ts
|
|
5320
|
-
function formBody(fields) {
|
|
5321
|
-
return new URLSearchParams(fields).toString();
|
|
5322
|
-
}
|
|
5323
5360
|
function createStoreClient(options) {
|
|
5324
5361
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
5325
5362
|
const { session } = options;
|
|
5326
|
-
const
|
|
5363
|
+
const tokenSource = options.accessToken ?? (session ? () => session.getToken() : void 0);
|
|
5327
5364
|
const refresh = options.refresh ?? (session ? () => session.refresh() : void 0);
|
|
5328
5365
|
const onUnauthorized = options.onUnauthorized ?? (session ? () => session.clear() : void 0);
|
|
5329
|
-
|
|
5366
|
+
function bearer() {
|
|
5367
|
+
const token = resolveToken(tokenSource);
|
|
5368
|
+
return token ? { authorization: `Bearer ${token}` } : {};
|
|
5369
|
+
}
|
|
5370
|
+
/** 계정 API(`/me/*`) — 상점을 고르지 않는다 */
|
|
5330
5371
|
const userHttp = createHttp({
|
|
5331
5372
|
baseUrl: options.baseUrl,
|
|
5332
5373
|
fetch: fetchFn,
|
|
5333
|
-
headers:
|
|
5334
|
-
const token = resolveToken(userTokenSource);
|
|
5335
|
-
return token ? { authorization: `Bearer ${token}` } : {};
|
|
5336
|
-
},
|
|
5374
|
+
headers: bearer,
|
|
5337
5375
|
refresh,
|
|
5338
5376
|
onUnauthorized
|
|
5339
5377
|
});
|
|
5378
|
+
/** 상점 API — 같은 계정 토큰 + `X-Store-Id` */
|
|
5340
5379
|
const storeHttp = createHttp({
|
|
5341
5380
|
baseUrl: options.baseUrl,
|
|
5342
5381
|
fetch: fetchFn,
|
|
5343
5382
|
headers: () => {
|
|
5344
|
-
const token = resolveToken(storeTokenSource);
|
|
5345
5383
|
const storeId = resolveToken(options.storeId);
|
|
5346
5384
|
return {
|
|
5347
|
-
...
|
|
5385
|
+
...bearer(),
|
|
5348
5386
|
...storeId ? { "x-store-id": storeId } : {}
|
|
5349
5387
|
};
|
|
5350
5388
|
},
|
|
@@ -5363,7 +5401,7 @@ function createStoreClient(options) {
|
|
|
5363
5401
|
return session;
|
|
5364
5402
|
}
|
|
5365
5403
|
return {
|
|
5366
|
-
/** session의 현재
|
|
5404
|
+
/** session의 현재 액세스 토큰 (session 미배선 시 null — 던지지 않음) */
|
|
5367
5405
|
get accessToken() {
|
|
5368
5406
|
return session ? session.accessToken : null;
|
|
5369
5407
|
},
|
|
@@ -5376,69 +5414,10 @@ function createStoreClient(options) {
|
|
|
5376
5414
|
/** session.userinfo 패스스루 — session 미배선 시 명확한 에러를 던진다 */
|
|
5377
5415
|
userinfo: () => requireSession().userinfo(),
|
|
5378
5416
|
auth: {
|
|
5379
|
-
/**
|
|
5380
|
-
* 서버 간 연동(M2M) — client_credentials grant (client_secret_basic).
|
|
5381
|
-
* 클라이언트가 스토어에 바인딩되어 있어 **storeToken이 곧바로 발급**된다.
|
|
5382
|
-
* ⚠ client_secret은 서버 환경에서만 사용할 것 (브라우저 반입 금지)
|
|
5383
|
-
*/
|
|
5384
|
-
async issueStoreTokenByClientCredentials(clientId, clientSecret, scope) {
|
|
5385
|
-
const credentials = base64Utf8(`${clientId}:${clientSecret}`);
|
|
5386
|
-
const response = await fetchFn(`${options.baseUrl}/oauth2/token`, {
|
|
5387
|
-
method: "POST",
|
|
5388
|
-
headers: {
|
|
5389
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
5390
|
-
authorization: `Basic ${credentials}`
|
|
5391
|
-
},
|
|
5392
|
-
body: formBody({
|
|
5393
|
-
grant_type: "client_credentials",
|
|
5394
|
-
...scope ? { scope } : {}
|
|
5395
|
-
})
|
|
5396
|
-
});
|
|
5397
|
-
if (!response.ok) {
|
|
5398
|
-
const body = await response.json().catch(() => null);
|
|
5399
|
-
throw new Error(body?.error_description ?? `토큰 발급 실패 (HTTP ${response.status})`);
|
|
5400
|
-
}
|
|
5401
|
-
return clientCredentialsTokenResponseSchema.parse(unwrapData(await response.json()));
|
|
5402
|
-
},
|
|
5403
|
-
/** 2단계 — 소속 스토어 목록 */
|
|
5417
|
+
/** 소속 상점 목록 */
|
|
5404
5418
|
listMyStores: () => userHttp.request("GET", "/me/stores", z.array(myStoreSchema)),
|
|
5405
|
-
/** RFC 7009 — token을 폐기한다 (public client는 client_id만 동반) */
|
|
5406
|
-
async revoke(token, clientId) {
|
|
5407
|
-
await fetchFn(`${options.baseUrl}/oauth2/revoke`, {
|
|
5408
|
-
method: "POST",
|
|
5409
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
5410
|
-
body: formBody({
|
|
5411
|
-
token,
|
|
5412
|
-
client_id: clientId
|
|
5413
|
-
})
|
|
5414
|
-
});
|
|
5415
|
-
},
|
|
5416
5419
|
me: () => userHttp.request("GET", "/me", myAccountSchema)
|
|
5417
5420
|
},
|
|
5418
|
-
/** API 토큰(PAT) 관리 — OWNER/ADMIN 전용. 발급된 토큰은 storeToken처럼 Bearer로 사용 */
|
|
5419
|
-
apiTokens: {
|
|
5420
|
-
create: (body) => storeHttp.request("POST", "/store/api-tokens", apiTokenCreatedSchema, { body: apiTokenCreateRequestSchema.parse(body) }),
|
|
5421
|
-
list: () => storeHttp.request("GET", "/store/api-tokens", z.array(apiTokenSchema)),
|
|
5422
|
-
revoke: (tokenId) => storeHttp.requestVoid("DELETE", path`/store/api-tokens/${tokenId}`)
|
|
5423
|
-
},
|
|
5424
|
-
/**
|
|
5425
|
-
* 앱(OAuth 클라이언트) 관리 — OWNER/ADMIN storeToken 전용(PAT·M2M 거부). auth-spec §12, 부록 B-13.
|
|
5426
|
-
* 시크릿 평문은 register·rotateSecret 응답에서만 한 번 노출된다(apiTokens.create와 같은 성격).
|
|
5427
|
-
*/
|
|
5428
|
-
apiClients: {
|
|
5429
|
-
/**
|
|
5430
|
-
* DCR 등록 (RFC 7591) — `POST /v1/oauth2/register`. OAuth 프로토콜 표면이라 응답이 평면 JSON이다.
|
|
5431
|
-
* 등록 오류(`invalid_client_metadata`·`invalid_redirect_uri`)는 ApiError(code=error,
|
|
5432
|
-
* message=error_description)로, Bearer 인증 오류는 엔벨로프 그대로 ApiError로 던진다.
|
|
5433
|
-
*/
|
|
5434
|
-
register: (body) => storeHttp.request("POST", "/oauth2/register", oauthClientRegistrationResponseSchema, { body: oauthClientRegistrationRequestSchema.parse(body) }),
|
|
5435
|
-
list: (params) => storeHttp.request("GET", "/store/api-clients", z.array(apiClientSchema), { query: params }),
|
|
5436
|
-
get: (clientId) => storeHttp.request("GET", path`/store/api-clients/${clientId}`, apiClientSchema),
|
|
5437
|
-
/** confidential 전용. 기존 시크릿은 즉시 무효(유예 없음) */
|
|
5438
|
-
rotateSecret: (clientId) => storeHttp.request("POST", path`/store/api-clients/${clientId}/rotate-secret`, apiClientSecretRotatedSchema),
|
|
5439
|
-
/** REVOKED로 전환(되돌릴 수 없음). 이미 비활성이면 현재 상태를 그대로 돌려준다 */
|
|
5440
|
-
deactivate: (clientId) => storeHttp.request("POST", path`/store/api-clients/${clientId}/deactivate`, apiClientSchema)
|
|
5441
|
-
},
|
|
5442
5421
|
/**
|
|
5443
5422
|
* 미디어 업로드 — 자리를 받아(`POST /uploads`) 파일 바이트는 호출자가
|
|
5444
5423
|
* `uploadUrl`로 직접 PUT한다. 업로드가 끝나면 `url`이 공개 주소가 된다.
|
|
@@ -5509,7 +5488,7 @@ function createStoreClient(options) {
|
|
|
5509
5488
|
savedOptionSets: {
|
|
5510
5489
|
list: (params) => storeHttp.request("GET", "/saved-option-sets", pageSchema(savedOptionSetSchema), { query: params }),
|
|
5511
5490
|
get: (optionSetId) => storeHttp.request("GET", path`/saved-option-sets/${optionSetId}`, savedOptionSetSchema),
|
|
5512
|
-
/** 이름이 겹치면 409 `OPTION_SET_NAME_TAKEN`,
|
|
5491
|
+
/** 이름이 겹치면 409 `OPTION_SET_NAME_TAKEN`, 상점당 200개를 넘으면 409 `OPTION_SET_LIMIT_EXCEEDED` */
|
|
5513
5492
|
create: (body) => storeHttp.request("POST", "/saved-option-sets", savedOptionSetSchema, { body: savedOptionSetRequestSchema.parse(body) }),
|
|
5514
5493
|
/** 통째로 바꾼다. 불러온 상품은 바뀌지 않는다 */
|
|
5515
5494
|
update: (optionSetId, body) => storeHttp.request("PUT", path`/saved-option-sets/${optionSetId}`, savedOptionSetSchema, { body: savedOptionSetRequestSchema.parse(body) }),
|
|
@@ -5539,7 +5518,7 @@ function createStoreClient(options) {
|
|
|
5539
5518
|
get: (orderId) => storeHttp.request("GET", path`/orders/${orderId}`, orderSchema),
|
|
5540
5519
|
/**
|
|
5541
5520
|
* `POST /v1/orders/{orderId}/cash-receipt/reissue` — 실패했거나 재시도 대기 중인 현금영수증을 지금 다시 발급·취소한다.
|
|
5542
|
-
*
|
|
5521
|
+
* 상점 플랫폼 OWNER·ADMIN 전용(PAT·M2M 403 `INSUFFICIENT_ROLE`). 404 `CASH_RECEIPT_NOT_FOUND`, 409 `CASH_RECEIPT_NOT_REISSUABLE`
|
|
5543
5522
|
*/
|
|
5544
5523
|
reissueCashReceipt: (orderId) => storeHttp.request("POST", path`/orders/${orderId}/cash-receipt/reissue`, cashReceiptSchema)
|
|
5545
5524
|
},
|
|
@@ -5574,7 +5553,7 @@ function createStoreClient(options) {
|
|
|
5574
5553
|
} }),
|
|
5575
5554
|
/**
|
|
5576
5555
|
* 배송완료 처리 — `DISPATCHED`·`DELIVERING`만 넘어간다. 응답의 `autoDecisionDate`가 자동 구매확정 예정일이다
|
|
5577
|
-
* (
|
|
5556
|
+
* (상점 설정 `autoConfirmDays`가 0이면 null).
|
|
5578
5557
|
*/
|
|
5579
5558
|
markDelivered: (orderItemId, idempotencyKey) => storeHttp.request("POST", path`/order-items/${orderItemId}/delivered`, orderItemSchema, { headers: idempotency(idempotencyKey) }),
|
|
5580
5559
|
/** 배송완료 일괄 처리 — 항목별 부분 성공이다 */
|
|
@@ -5630,7 +5609,7 @@ function createStoreClient(options) {
|
|
|
5630
5609
|
updateAnswer: (inquiryId, content) => storeHttp.requestVoid("PUT", path`/inquiries/${inquiryId}/answer`, { body: { content } })
|
|
5631
5610
|
},
|
|
5632
5611
|
/**
|
|
5633
|
-
* 구매자 로그인 설정 — 이메일·비밀번호 허용, 소셜 공급자 연결. 비밀 값을 다루므로
|
|
5612
|
+
* 구매자 로그인 설정 — 이메일·비밀번호 허용, 소셜 공급자 연결. 비밀 값을 다루므로 상점 플랫폼에 로그인한 OWNER만 호출할 수 있다
|
|
5634
5613
|
*/
|
|
5635
5614
|
loginSettings: {
|
|
5636
5615
|
get: () => storeHttp.request("GET", "/store/login-settings", loginSettingsSchema),
|
|
@@ -5642,7 +5621,7 @@ function createStoreClient(options) {
|
|
|
5642
5621
|
/** 연결 해제 — 키를 지운다. 연결된 구매자 계정은 남는다 */
|
|
5643
5622
|
removeProvider: (provider) => storeHttp.request("DELETE", path`/store/auth-providers/${provider}`, loginSettingsSchema)
|
|
5644
5623
|
},
|
|
5645
|
-
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(
|
|
5624
|
+
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(상점 플랫폼 OWNER·ADMIN) */
|
|
5646
5625
|
customers: {
|
|
5647
5626
|
list: (params) => storeHttp.request("GET", "/customers", pageSchema(customerSummarySchema), { query: params }),
|
|
5648
5627
|
get: (customerId) => storeHttp.request("GET", path`/customers/${customerId}`, customerDetailSchema),
|
|
@@ -5740,9 +5719,9 @@ get: (params) => storeHttp.request("GET", "/sales-report", salesReportSchema, {
|
|
|
5740
5719
|
get: (paymentId) => storeHttp.request("GET", path`/payments/${paymentId}`, paymentRecordDetailSchema)
|
|
5741
5720
|
},
|
|
5742
5721
|
/**
|
|
5743
|
-
* 결제(PG) 설정 —
|
|
5744
|
-
* 403 `INSUFFICIENT_ROLE`이다(조회 포함). OWNER 여부는 요청 시점의
|
|
5745
|
-
* 양도·강등
|
|
5722
|
+
* 결제(PG) 설정 — **상점 플랫폼 OWNER 전용**. ADMIN·STAFF·PAT·M2M·3rd-party 앱 토큰은 스코프가 있어도
|
|
5723
|
+
* 403 `INSUFFICIENT_ROLE`이다(조회 포함). OWNER 여부는 요청 시점의 상점 멤버십으로 다시 확인하므로
|
|
5724
|
+
* 양도·강등 직후에도 바로 403이다. 비밀 값(시크릿 키·API Secret·웹훅 시크릿)은 요청으로만 보내고
|
|
5746
5725
|
* 응답에는 `secrets[].masked`(앞 4자리 마스킹)만 나온다.
|
|
5747
5726
|
*/
|
|
5748
5727
|
paymentSettings: {
|
|
@@ -5848,7 +5827,7 @@ get: (params) => storeHttp.request("GET", "/sales-report", salesReportSchema, {
|
|
|
5848
5827
|
*/
|
|
5849
5828
|
sendTest: (webhookId, body) => storeHttp.request("POST", path`/webhooks/${webhookId}/test`, webhookTestResultSchema, { body: webhookTestRequestSchema.parse(body) })
|
|
5850
5829
|
},
|
|
5851
|
-
/** API 로그 (보관 7일,
|
|
5830
|
+
/** API 로그 (보관 7일, 상점 플랫폼 OWNER/ADMIN) */
|
|
5852
5831
|
apiLogs: {
|
|
5853
5832
|
/** 최신순 커서 페이지네이션. 다음 페이지는 응답의 `nextCursor`를 `cursor`로 넘긴다 */
|
|
5854
5833
|
list: (params) => storeHttp.request("GET", "/api-logs", apiLogPageSchema, { query: params && {
|
|
@@ -5858,7 +5837,7 @@ get: (params) => storeHttp.request("GET", "/sales-report", salesReportSchema, {
|
|
|
5858
5837
|
/** 최근 24시간 요청 수·에러 수 */
|
|
5859
5838
|
summary: () => storeHttp.request("GET", "/api-logs/summary", apiLogSummarySchema)
|
|
5860
5839
|
},
|
|
5861
|
-
/** 감사 로그 (관리 API 쓰기의 행위자·대상·결과,
|
|
5840
|
+
/** 감사 로그 (관리 API 쓰기의 행위자·대상·결과, 상점 플랫폼 OWNER/ADMIN, `audit:r`) */
|
|
5862
5841
|
auditLogs: {
|
|
5863
5842
|
/** 최신순 커서 페이지네이션. 다음 페이지는 응답의 `nextCursor`를 `cursor`로 넘긴다 */
|
|
5864
5843
|
list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, { query: params && {
|
|
@@ -5866,7 +5845,7 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
|
|
|
5866
5845
|
actorType: Array.isArray(params.actorType) ? params.actorType.join(",") || void 0 : params.actorType
|
|
5867
5846
|
} }) },
|
|
5868
5847
|
/**
|
|
5869
|
-
* 승인 요청 — 에이전트의 고위험 쓰기가 202로 보류된 건. 목록·승인·거절은
|
|
5848
|
+
* 승인 요청 — 에이전트의 고위험 쓰기가 202로 보류된 건. 목록·승인·거절은 상점 플랫폼 OWNER/ADMIN,
|
|
5870
5849
|
* 단건 조회는 요청한 토큰도 할 수 있다(상태 확인용).
|
|
5871
5850
|
*/
|
|
5872
5851
|
approvalRequests: {
|
|
@@ -5910,7 +5889,7 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
|
|
|
5910
5889
|
checkSlug: (slug) => storeHttp.request("GET", "/storefront/slug-availability", storefrontSlugAvailabilitySchema, { query: { slug } }),
|
|
5911
5890
|
/**
|
|
5912
5891
|
* `POST /v1/storefront/site` (201) — 템플릿으로 사이트를 만들고 v1을 발행한다. 공개 상태는 오픈 준비 중이다.
|
|
5913
|
-
* 사이트를 만드는 유일한 경로다(
|
|
5892
|
+
* 사이트를 만드는 유일한 경로다(상점을 만들어도 사이트는 생기지 않는다). 템플릿은 여기서만 고른다
|
|
5914
5893
|
*/
|
|
5915
5894
|
create: (body) => storeHttp.request("POST", "/storefront/site", storefrontSiteSchema, { body: createStorefrontSiteRequestSchema.parse(body) }),
|
|
5916
5895
|
/** `GET /v1/storefront/site/deployment` — 배포 진행(단계·기록). 만든 뒤 반영이 끝날 때까지 폴링한다 */
|
|
@@ -5935,7 +5914,7 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
|
|
|
5935
5914
|
setVisibility: (body) => storeHttp.request("PUT", "/storefront/site/visibility", storefrontSiteSchema, { body: setStorefrontVisibilityRequestSchema.parse(body) }),
|
|
5936
5915
|
/**
|
|
5937
5916
|
* `POST /v1/storefront/site/preview-link` — 오픈 준비 중인 사이트를 구성원이 볼 수 있는 5분짜리 링크.
|
|
5938
|
-
*
|
|
5917
|
+
* 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다
|
|
5939
5918
|
*/
|
|
5940
5919
|
createPreviewLink: (body = {}) => storeHttp.request("POST", "/storefront/site/preview-link", storefrontPreviewLinkSchema, { body: createStorefrontPreviewLinkRequestSchema.parse(body) }),
|
|
5941
5920
|
/** `GET /v1/storefront/site/domains` — 주소 목록(기본 주소 + 내 도메인)과 DNS 안내 */
|
|
@@ -5964,7 +5943,7 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
|
|
|
5964
5943
|
stopEditing: () => storeHttp.request("DELETE", "/storefront/site/editing", storefrontEditingSchema),
|
|
5965
5944
|
/**
|
|
5966
5945
|
* `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 5분짜리 링크.
|
|
5967
|
-
*
|
|
5946
|
+
* 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다(에이전트 불가)
|
|
5968
5947
|
*/
|
|
5969
5948
|
createEditPreviewLink: (body = {}) => storeHttp.request("POST", "/storefront/site/editing/preview-link", storefrontPreviewLinkSchema, { body: createStorefrontEditPreviewLinkRequestSchema.parse(body) }),
|
|
5970
5949
|
/** `GET /v1/storefront/site/files?prefix=` — 작업 트리 파일 목록 */
|
|
@@ -6012,6 +5991,18 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
|
|
|
6012
5991
|
*/
|
|
6013
5992
|
replaceSource: (body) => storeHttp.request("PUT", "/storefront/site/source", storefrontSourceReplacedSchema, { body: replaceStorefrontSourceRequestSchema.parse(body) })
|
|
6014
5993
|
},
|
|
5994
|
+
subscription: {
|
|
5995
|
+
/**
|
|
5996
|
+
* `GET /v1/store/subscription` — 스코프 `store:r`. 현재 요금제·상태·사용량 기간·한도·이번 기간 사용량.
|
|
5997
|
+
* 결제 수단·청구 금액은 싣지 않는다. 요금제 변경은 상점 플랫폼에서만 한다
|
|
5998
|
+
*/
|
|
5999
|
+
get: () => storeHttp.request("GET", "/store/subscription", storeSubscriptionSchema) },
|
|
6000
|
+
plans: {
|
|
6001
|
+
/**
|
|
6002
|
+
* `GET /v1/public/plans` — 인증 없음. 공개 요금제의 지금 버전(가격·기능 키 값). 값은 서버가 관리하고 언제든 바뀔 수 있어
|
|
6003
|
+
* 코드에 요금제 수치를 두지 말고 이 값을 읽는다
|
|
6004
|
+
*/
|
|
6005
|
+
listPublic: () => storeHttp.request("GET", "/public/plans", publicPlansSchema) },
|
|
6015
6006
|
store: {
|
|
6016
6007
|
get: () => storeHttp.request("GET", "/store", storeSchema),
|
|
6017
6008
|
/**
|
|
@@ -6027,7 +6018,7 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
|
|
|
6027
6018
|
listInvitations: (params) => storeHttp.request("GET", "/store/invitations", z.array(invitationSchema), { query: params }),
|
|
6028
6019
|
/**
|
|
6029
6020
|
* `POST /v1/store/invitations` (201) — 스코프 `member:rw`. 초대 메일을 보낸다. 발송 결과는 응답의
|
|
6030
|
-
* `deliveryStatus`다(발송이 실패해도 초대는 만들어진다).
|
|
6021
|
+
* `deliveryStatus`다(발송이 실패해도 초대는 만들어진다). 상점·초대한 사람·받는 주소별 발송 한도가 있다(429, `details.limit`).
|
|
6031
6022
|
*/
|
|
6032
6023
|
invite: (body) => storeHttp.request("POST", "/store/invitations", invitationSchema, { body: inviteMemberRequestSchema.parse(body) }),
|
|
6033
6024
|
/** `POST /v1/store/invitations/{invitationId}/resend` — 새 수락 링크로 다시 보낸다. 이전 링크는 무효가 되고 만료가 다시 7일이 된다 */
|
|
@@ -6038,12 +6029,33 @@ list: (params) => storeHttp.request("GET", "/audit-logs", auditLogPageSchema, {
|
|
|
6038
6029
|
};
|
|
6039
6030
|
}
|
|
6040
6031
|
//#endregion
|
|
6032
|
+
//#region src/base64.ts
|
|
6033
|
+
/**
|
|
6034
|
+
* base64 인코딩 헬퍼 — 웹 표준(`btoa` + `TextEncoder`)만 사용한다.
|
|
6035
|
+
*
|
|
6036
|
+
* 이 패키지는 브라우저/서버 공용 entrypoint 하나로 배포되므로 Node 전용 `Buffer`에
|
|
6037
|
+
* 의존하지 않는다. `btoa`는 Latin-1 코드유닛만 받기 때문에 문자열은 반드시
|
|
6038
|
+
* UTF-8 바이트로 먼저 인코딩한 뒤 넘긴다 — 그래야 non-ASCII 입력에서도
|
|
6039
|
+
* `Buffer.from(text, "utf8").toString("base64")`와 동일한 결과가 나온다.
|
|
6040
|
+
*/
|
|
6041
|
+
/** 바이트 배열 → 표준 base64 (패딩 포함) */
|
|
6042
|
+
function base64FromBytes(bytes) {
|
|
6043
|
+
let binary = "";
|
|
6044
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
6045
|
+
return btoa(binary);
|
|
6046
|
+
}
|
|
6047
|
+
/** 바이트 배열 → base64url (RFC 4648 §5, 패딩 없음) */
|
|
6048
|
+
function base64UrlFromBytes(bytes) {
|
|
6049
|
+
return base64FromBytes(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
6050
|
+
}
|
|
6051
|
+
//#endregion
|
|
6041
6052
|
//#region src/oauth.ts
|
|
6042
6053
|
/**
|
|
6043
6054
|
* OAuth 2.1 브라우저 플로우 헬퍼 (authorization_code + PKCE).
|
|
6055
|
+
* 발급자는 Sayren 계정(accounts)이다 — 엔드포인트 주소는 호출부가 준다.
|
|
6044
6056
|
* Web Crypto만 사용 — 브라우저/Node 모두에서 동작하며 **시크릿을 다루지 않는다**.
|
|
6045
6057
|
*/
|
|
6046
|
-
/** RFC 6749 §5.1 토큰 응답
|
|
6058
|
+
/** RFC 6749 §5.1 토큰 응답 */
|
|
6047
6059
|
const oauthTokenResponseSchema = z.object({
|
|
6048
6060
|
access_token: z.string(),
|
|
6049
6061
|
token_type: z.literal("Bearer"),
|
|
@@ -6064,7 +6076,7 @@ async function createPkcePair() {
|
|
|
6064
6076
|
}
|
|
6065
6077
|
/** authorize 엔드포인트 URL 구성 — 브라우저는 이 URL로 이동(top-level navigation) */
|
|
6066
6078
|
function buildAuthorizeUrl(options) {
|
|
6067
|
-
const url = new URL(
|
|
6079
|
+
const url = new URL(options.authorizeUrl);
|
|
6068
6080
|
url.searchParams.set("response_type", "code");
|
|
6069
6081
|
url.searchParams.set("client_id", options.clientId);
|
|
6070
6082
|
url.searchParams.set("redirect_uri", options.redirectUri);
|
|
@@ -6072,10 +6084,12 @@ function buildAuthorizeUrl(options) {
|
|
|
6072
6084
|
url.searchParams.set("code_challenge", options.codeChallenge);
|
|
6073
6085
|
url.searchParams.set("code_challenge_method", "S256");
|
|
6074
6086
|
if (options.scope) url.searchParams.set("scope", options.scope);
|
|
6087
|
+
if (options.resource) url.searchParams.set("resource", options.resource);
|
|
6088
|
+
if (options.prompt) url.searchParams.set("prompt", options.prompt);
|
|
6075
6089
|
return url.toString();
|
|
6076
6090
|
}
|
|
6077
|
-
async function tokenRequest(
|
|
6078
|
-
const response = await fetchFn(
|
|
6091
|
+
async function tokenRequest(endpoint, form, fetchFn = globalThis.fetch) {
|
|
6092
|
+
const response = await fetchFn(endpoint, {
|
|
6079
6093
|
method: "POST",
|
|
6080
6094
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
6081
6095
|
body: new URLSearchParams(form).toString()
|
|
@@ -6087,41 +6101,48 @@ async function tokenRequest(baseUrl, form, fetchFn = globalThis.fetch) {
|
|
|
6087
6101
|
}
|
|
6088
6102
|
return oauthTokenResponseSchema.parse(unwrapData(json));
|
|
6089
6103
|
}
|
|
6090
|
-
/** 콜백에서 받은 code를
|
|
6104
|
+
/** 콜백에서 받은 code를 액세스 토큰으로 교환 (PKCE verifier 동반) */
|
|
6091
6105
|
async function exchangeAuthorizationCode(options) {
|
|
6092
|
-
return tokenRequest(options.
|
|
6106
|
+
return tokenRequest(options.tokenUrl, {
|
|
6093
6107
|
grant_type: "authorization_code",
|
|
6094
6108
|
code: options.code,
|
|
6095
6109
|
redirect_uri: options.redirectUri,
|
|
6096
6110
|
client_id: options.clientId,
|
|
6097
|
-
code_verifier: options.codeVerifier
|
|
6111
|
+
code_verifier: options.codeVerifier,
|
|
6112
|
+
...options.resource ? { resource: options.resource } : {}
|
|
6098
6113
|
}, options.fetch);
|
|
6099
6114
|
}
|
|
6100
6115
|
/** refresh grant (rotation — 응답의 새 refresh_token으로 반드시 교체 저장할 것) */
|
|
6101
6116
|
async function refreshUserToken(options) {
|
|
6102
|
-
return tokenRequest(options.
|
|
6117
|
+
return tokenRequest(options.tokenUrl, {
|
|
6103
6118
|
grant_type: "refresh_token",
|
|
6104
6119
|
refresh_token: options.refreshToken,
|
|
6105
|
-
client_id: options.clientId
|
|
6106
|
-
|
|
6107
|
-
}
|
|
6108
|
-
/** userToken → storeToken 교환 (RFC 8693 token-exchange grant) */
|
|
6109
|
-
async function exchangeStoreTokenViaOAuth(options) {
|
|
6110
|
-
return tokenRequest(options.baseUrl, {
|
|
6111
|
-
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
6112
|
-
subject_token: options.userToken,
|
|
6113
|
-
subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
|
|
6114
|
-
audience: options.storeId,
|
|
6115
|
-
client_id: options.clientId
|
|
6120
|
+
client_id: options.clientId,
|
|
6121
|
+
...options.resource ? { resource: options.resource } : {}
|
|
6116
6122
|
}, options.fetch);
|
|
6117
6123
|
}
|
|
6124
|
+
const customerDelegationRequestSchema = z.object({
|
|
6125
|
+
uid: z.string().regex(/^[A-Za-z0-9._:@|-]{1,128}$/).optional().describe("셀러 자체 인증의 사용자 id(1~128자, 영문·숫자·`._:@|-`). 처음 보는 id면 구매자를 만든다"),
|
|
6126
|
+
email: z.email().max(254).optional().describe("uid가 없으면 이메일로 지목한다. uid와 함께 주면 처음 만들 때의 이메일이다"),
|
|
6127
|
+
name: z.string().max(50).optional().describe("처음 만들 때의 이름"),
|
|
6128
|
+
authenticatedAt: z.iso.datetime({ offset: true }).optional().describe("사용자가 셀러 서비스에서 실제로 로그인한 시각(uid 위임만). 주면 구매자 토큰의 인증 시각이 된다")
|
|
6129
|
+
}).refine((value) => Boolean(value.uid || value.email), {
|
|
6130
|
+
message: "uid 또는 email이 필요합니다",
|
|
6131
|
+
path: ["uid"]
|
|
6132
|
+
}).describe("구매자 위임 요청");
|
|
6133
|
+
const customerDelegationSchema = z.object({
|
|
6134
|
+
accessToken: z.string().describe("구매자 액세스 토큰(스토어프론트 API Bearer)"),
|
|
6135
|
+
tokenType: z.literal("Bearer"),
|
|
6136
|
+
expiresIn: z.number().int().describe("수명(초)"),
|
|
6137
|
+
memberId: z.string()
|
|
6138
|
+
}).describe("구매자 위임 결과");
|
|
6118
6139
|
//#endregion
|
|
6119
6140
|
//#region src/schemas/scope.ts
|
|
6120
6141
|
/**
|
|
6121
|
-
*
|
|
6142
|
+
* 상점 스코프 카탈로그 (auth-spec §7) — 형식 `{리소스}:{r|rw}`, rw는 r을 함의한다.
|
|
6122
6143
|
*
|
|
6123
6144
|
* 관리 API가 요구하는 스코프와 역할별 자동 부여 스코프의 **단일 원천**이다.
|
|
6124
|
-
* 서버(apps/api)가 이 상수를 그대로 가져다 역할→스코프를 산출하므로,
|
|
6145
|
+
* 서버(apps/api)가 이 상수를 그대로 가져다 역할→스코프를 산출하므로, 상점 플랫폼 권한 매트릭스와
|
|
6125
6146
|
* 서버 인가가 갈라지지 않는다.
|
|
6126
6147
|
*/
|
|
6127
6148
|
const scopeAccessSchema = z.enum(["r", "rw"]).describe("접근 수준. `r`=읽기, `rw`=읽기+쓰기(r을 함의)");
|
|
@@ -6152,8 +6173,7 @@ function isStoreScope(scope) {
|
|
|
6152
6173
|
}
|
|
6153
6174
|
/**
|
|
6154
6175
|
* 역할별 자동 부여 스코프 (auth-spec §7).
|
|
6155
|
-
*
|
|
6156
|
-
* 발급 시 지정한 스코프만 갖는다(발급자 보유 스코프 이내).
|
|
6176
|
+
* 계정 토큰으로 상점을 호출할 때의 스코프는 그 상점에서의 역할로 이 표에서 결정된다.
|
|
6157
6177
|
*/
|
|
6158
6178
|
const STORE_ROLE_SCOPES = {
|
|
6159
6179
|
OWNER: [
|
|
@@ -6210,7 +6230,7 @@ const STORE_ROLE_SCOPES = {
|
|
|
6210
6230
|
* 세는 것이다. 그 숫자가 규칙과 안내를 고치는 근거가 된다.
|
|
6211
6231
|
*
|
|
6212
6232
|
* **보내지 않는 것**: 파일 경로·소스 코드·위반 상세(`detail`)·토큰·사람 식별자. 규칙 id와 개수뿐이다.
|
|
6213
|
-
* 테넌트는 요청 토큰이 정한다(본문에
|
|
6233
|
+
* 테넌트는 요청 토큰이 정한다(본문에 상점 id가 없다). 옵트아웃은 클라이언트의 `SAYREN_TELEMETRY=0`이다.
|
|
6214
6234
|
*
|
|
6215
6235
|
* 알 수 없는 키는 저장하지 않고 버린다(`.strict()`를 쓰지 않는다) — 새 버전 클라이언트가 필드를
|
|
6216
6236
|
* 하나 더 실었다고 계측 전체가 400으로 날아가면 그 버전의 숫자가 통째로 빈다.
|
|
@@ -6243,7 +6263,7 @@ const mcpToolEventSchema = z.object({
|
|
|
6243
6263
|
const envelopeFields = {
|
|
6244
6264
|
id: z.string().describe("이벤트 ID (`evt_…`, 테스트 전송은 `evt_test_…`). 중복 제거 키"),
|
|
6245
6265
|
createdAt: z.string().describe("이벤트 발생 일시 (ISO 8601)"),
|
|
6246
|
-
storeId: z.string().describe("이벤트가 발생한
|
|
6266
|
+
storeId: z.string().describe("이벤트가 발생한 상점 ID"),
|
|
6247
6267
|
sequence: z.number().int().positive().optional().describe("리소스별 순번. 같은 주문·클레임·문의·리뷰·상품의 이벤트마다 1씩 커진다. 도착 순서는 보장하지 않으므로 같은 리소스의 이벤트는 이 값으로 정렬한다. 이 필드가 생기기 전의 이벤트와 테스트 전송에는 없다")
|
|
6248
6268
|
};
|
|
6249
6269
|
const orderWebhookEventDataSchema = z.object({
|
|
@@ -6394,7 +6414,8 @@ const REVOKE_TIMEOUT_MS = 5e3;
|
|
|
6394
6414
|
/** Web Locks 이름 — 탭 간 refresh 회전 직렬화용 */
|
|
6395
6415
|
const REFRESH_LOCK = STORE_SESSION_KEYS.refresh;
|
|
6396
6416
|
function createStoreSession(options) {
|
|
6397
|
-
const { baseUrl, clientId, storage } = options;
|
|
6417
|
+
const { baseUrl, clientId, storage, authServer } = options;
|
|
6418
|
+
const { revokeUrl, tokenUrl, resource } = authServer;
|
|
6398
6419
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
6399
6420
|
const listeners = /* @__PURE__ */ new Set();
|
|
6400
6421
|
function notify() {
|
|
@@ -6415,7 +6436,7 @@ function createStoreSession(options) {
|
|
|
6415
6436
|
}
|
|
6416
6437
|
/** RFC 7009 — 무효 토큰이어도 200이다. 실패는 호출부가 무시한다. 응답이 늦어도 로그아웃이 멈추지 않게 5초로 끊는다 */
|
|
6417
6438
|
async function revokeToken(token) {
|
|
6418
|
-
await fetchFn(
|
|
6439
|
+
await fetchFn(revokeUrl, {
|
|
6419
6440
|
method: "POST",
|
|
6420
6441
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
6421
6442
|
body: new URLSearchParams({
|
|
@@ -6455,9 +6476,10 @@ function createStoreSession(options) {
|
|
|
6455
6476
|
if (!refreshToken) return false;
|
|
6456
6477
|
try {
|
|
6457
6478
|
const tokens = await refreshUserToken({
|
|
6458
|
-
baseUrl,
|
|
6459
6479
|
clientId,
|
|
6460
6480
|
refreshToken,
|
|
6481
|
+
tokenUrl,
|
|
6482
|
+
resource,
|
|
6461
6483
|
fetch: fetchFn
|
|
6462
6484
|
});
|
|
6463
6485
|
setTokens({
|
|
@@ -6492,16 +6514,16 @@ function createStoreSession(options) {
|
|
|
6492
6514
|
redirectUri: authorizeOptions.redirectUri
|
|
6493
6515
|
};
|
|
6494
6516
|
storage.set(STORE_SESSION_KEYS.pkce, JSON.stringify(blob));
|
|
6495
|
-
const
|
|
6496
|
-
|
|
6517
|
+
const finalUrl = buildAuthorizeUrl({
|
|
6518
|
+
authorizeUrl: authServer.authorizeUrl,
|
|
6497
6519
|
clientId,
|
|
6498
6520
|
redirectUri: authorizeOptions.redirectUri,
|
|
6499
6521
|
state,
|
|
6500
6522
|
codeChallenge: challenge,
|
|
6501
|
-
scope: authorizeOptions.scope
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6523
|
+
scope: authorizeOptions.scope,
|
|
6524
|
+
resource,
|
|
6525
|
+
prompt: authorizeOptions.prompt
|
|
6526
|
+
});
|
|
6505
6527
|
if (typeof window !== "undefined") {
|
|
6506
6528
|
window.location.assign(finalUrl);
|
|
6507
6529
|
return;
|
|
@@ -6515,11 +6537,12 @@ function createStoreSession(options) {
|
|
|
6515
6537
|
const saved = JSON.parse(raw);
|
|
6516
6538
|
if (saved.state !== state) throw new Error("state가 일치하지 않습니다. 다시 시도해주십시오");
|
|
6517
6539
|
const tokens = await exchangeAuthorizationCode({
|
|
6518
|
-
baseUrl,
|
|
6519
6540
|
clientId,
|
|
6520
6541
|
redirectUri: saved.redirectUri,
|
|
6521
6542
|
code,
|
|
6522
6543
|
codeVerifier: saved.verifier,
|
|
6544
|
+
tokenUrl,
|
|
6545
|
+
resource,
|
|
6523
6546
|
fetch: fetchFn
|
|
6524
6547
|
});
|
|
6525
6548
|
setTokens({
|
|
@@ -6554,4 +6577,4 @@ function createStoreSession(options) {
|
|
|
6554
6577
|
};
|
|
6555
6578
|
}
|
|
6556
6579
|
//#endregion
|
|
6557
|
-
export { APPROVAL_ALREADY_DECIDED_CODE, APPROVAL_REQUIRED_CODE, ApiError, ApprovalPendingError, CASH_RECEIPT_STATUS_VALUES, COUPON_CODE_PATTERN, COUPON_CONDITIONS_DEFAULTS, COUPON_TARGET_ALL, COUPON_TARGET_LIST_MAX, CUSTOM_INPUT_FORBIDDEN_WORDS, FULFILLMENT_RULES, MAX_ADDON_GROUPS, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_AUTO_TRANSITION_DAYS, MAX_CUSTOM_INPUTS, MAX_CUSTOM_INPUT_LENGTH, MAX_OPTION_GROUPS, MAX_PRODUCT_IMAGES, MAX_SAVED_OPTION_SETS, MAX_SAVED_OPTION_SET_NAME, MAX_VALUES_PER_GROUP, MAX_VARIANTS, MCP_TELEMETRY_MAX_RESULTS, MIN_PAYMENT_AMOUNT, NO_COUPON_SETTLEMENT, NO_SHIPPING_BUNDLE_KEY, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, REFUND_POLICY_DEFAULTS, REFUND_POLICY_DELIVERY_FEE_KEYS, REMOTE_AREA_ZIP_RANGES, RESERVED_STOREFRONT_SLUGS, RESERVED_STORE_CODES, SHIPPING_POLICY_DEFAULTS, STOREFRONT_BLOCKED_PATH_SEGMENTS, STOREFRONT_BUILD_STAGES, STOREFRONT_BUILD_TERMINAL_STATUSES, STOREFRONT_CHECK_TERMINAL_STATUSES, STOREFRONT_CUSTOM_DOMAIN_LIMIT_DEFAULT, STOREFRONT_DOMAIN_DNS_GRACE_DAYS, STOREFRONT_DOMAIN_DNS_WAIT_DAYS, STOREFRONT_DOMAIN_TXT_LABEL, STOREFRONT_EDIT_ACTIVITY_TIMEOUT_MINUTES, STOREFRONT_EDIT_CAPACITY, STOREFRONT_EDIT_IDLE_MINUTES, STOREFRONT_EDIT_PREVIEW_SUFFIX, STOREFRONT_EDIT_START_STAGES, STOREFRONT_FILE_BATCH_MAX, STOREFRONT_FILE_MAX_BYTES, STOREFRONT_PATH_SEGMENT_PATTERN, STOREFRONT_PLATFORM_DOMAINS, STOREFRONT_PUBLIC_SUFFIXES, STOREFRONT_SEARCH_REGEX_MAX_LENGTH, STOREFRONT_SITE_CREATE_LIMIT, STOREFRONT_SLUG_CHANGE_LIMIT, STOREFRONT_SLUG_CHANGE_WINDOW_DAYS, STOREFRONT_SLUG_HOLD_DAYS, STOREFRONT_SLUG_PATTERN, STOREFRONT_SOURCE_BLOB_BATCH_MAX, STOREFRONT_SOURCE_PART_BYTES, STOREFRONT_WORKSPACE_MAX_BYTES, STOREFRONT_WORKSPACE_MAX_FILES, 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_COUNT_WARNING, 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, addStorefrontDomainRequestSchema, addonGroupRequestSchema, addonGroupSchema, addonValueRequestSchema, addonValueSchema, addressChangeRequestListSchema, addressTypeSchema, agentPolicyDecisionSchema, allocateByWeight, 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, approvalDecisionRequestSchema, approvalListQuerySchema, approvalPageSchema, approvalRequestSchema, approvalStatusSchema, approveClaimResultSchema, auditActorSchema, auditActorTypeSchema, auditLogListQuerySchema, auditLogOutcomeSchema, auditLogPageSchema, auditLogSchema, auditLogSourceSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, cashReceiptSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeStorefrontSlugRequestSchema, changeStorefrontTemplateRequestSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimReturnAddressSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, consentActionSchema, consentKindSchema, consentSourceSchema, couponBenefitSchema, couponConditionsInputSchema, couponConditionsSchema, couponDiscountOf, couponIssueListQuerySchema, couponIssueMethodSchema, couponIssueSchema, couponIssueStatusSchema, couponKindSchema, couponListQuerySchema, couponRedemptionListQuerySchema, couponRedemptionSchema, couponRejectReasonSchema, couponRuleSchema, couponSchema, couponStatsSchema, couponStatusSchema, couponTargetInputSchema, couponTargetSchema, createCouponRequestSchema, createPkcePair, createStoreClient, createStoreSession, createStorefrontEditPreviewLinkRequestSchema, createStorefrontPreviewLinkRequestSchema, createStorefrontSiteRequestSchema, createStorefrontVersionRequestSchema, customInputLabelForbiddenWord, customInputRequestSchema, customInputSchema, customInputValueSchema, customerAddressSchema, customerConsentListQuerySchema, customerConsentSchema, customerConsentStateSchema, customerConsentsSchema, customerCouponSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deleteStorefrontFileRequestSchema, deliveryBundleKeyOf, deliveryFeeWaiverSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, disableStorefrontHostingRequestSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, easyPayProviderResponseSchema, easyPayProviderSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, fulfillBulkRequestSchema, fulfillRequestSchema, fulfillmentRuleOf, fulfillmentTypeOf, fulfillmentTypeResponseSchema, fulfillmentTypeSchema, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationDeliveryStatusSchema, invitationListQuerySchema, invitationSchema, inviteMemberRequestSchema, isCouponTargetLine, isEditableStorefrontPath, isReservedStoreCode, isReservedStorefrontSlug, isStoreScope, isWebOrigin, issueCouponRequestSchema, issueCouponResultSchema, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, mcpRuleResultSchema, mcpToolEventSchema, moveStorefrontFileRequestSchema, myAccountSchema, myStoreSchema, normalizeCouponCode, normalizeStorefrontHostname, normalizeStorefrontPath, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, openFulfillmentUnion, optionGroupKindSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionSelectionSchema, optionSortSchema, optionValueRequestSchema, optionValueSchema, orderCanceledWebhookEventDataSchema, orderCanceledWebhookEventSchema, orderDiscountSchema, orderFulfilledWebhookEventDataSchema, orderFulfilledWebhookEventSchema, orderItemFulfillmentSchema, orderItemFulfillmentSnapshotResponseSchema, orderItemFulfillmentSnapshotSchema, 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, productFulfillmentCreateSchema, productFulfillmentResponseSchema, productFulfillmentSchema, productFulfillmentUpdateSchema, productImageRejection, productImageUrlSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productRefundPolicyOverride, productRefundPolicyPatchSchema, productReplaceRequestSchema, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, proratedPaidRefundBase, proratedRefundBase, publishStorefrontVersionRequestSchema, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, quoteDelivery, quotePricing, refreshUserToken, refundPolicyPatchSchema, refundPolicySchema, rejectClaimRequestSchema, remoteAreaLabelOf, remoteAreaOf, remoteAreaSchema, remoteSurchargeOf, replaceStorefrontSourceRequestSchema, requiresCollection, requiresShipping, resolveRefundPolicy, resolveShippingPolicy, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, rollbackStorefrontRequestSchema, runStorefrontCheckRequestSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, savedCustomInputRequestSchema, savedCustomInputSchema, savedOptionGroupRequestSchema, savedOptionGroupSchema, savedOptionSetCompositionSchema, savedOptionSetFromProductRequestSchema, savedOptionSetFromProductResultSchema, savedOptionSetListQuerySchema, savedOptionSetRequestSchema, savedOptionSetSchema, savedOptionSetSortSchema, savedOptionValueRequestSchema, savedOptionValueSchema, scopeAccessSchema, searchStorefrontFilesRequestSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, setStorefrontVisibilityRequestSchema, settleCouponShortfall, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, shippingPolicyPatchSchema, shippingPolicySchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, storefrontBuildDependenciesSchema, storefrontBuildListQuerySchema, storefrontBuildSchema, storefrontBuildStatusSchema, storefrontCheckSchema, storefrontCheckStatusSchema, storefrontCustomDomainStatusSchema, storefrontCustomHostnameProblem, storefrontDependencyFindingSchema, storefrontDeploymentLogSchema, storefrontDeploymentSchema, storefrontDeploymentStepSchema, storefrontDnsRecordSchema, storefrontDomainSchema, storefrontDomainTxtNameOf, storefrontEditPreviewLabelOf, storefrontEditPreviewSchema, storefrontEditSessionSchema, storefrontEditSessionStatusSchema, storefrontEditingSchema, storefrontFileConflictDetailsSchema, storefrontFileContentQuerySchema, storefrontFileContentSchema, storefrontFileDeleteResultSchema, storefrontFileEncodingSchema, storefrontFileEntrySchema, storefrontFileHashSchema, storefrontFileListQuerySchema, storefrontFileListSchema, storefrontFileMoveResultSchema, storefrontFilePathSchema, storefrontFileSearchResultSchema, storefrontFileWriteResultSchema, storefrontHostingSchema, storefrontPreviewLinkSchema, storefrontProvisioningSchema, storefrontRuleResultSchema, storefrontRuleSeveritySchema, storefrontSiteDomainSchema, storefrontSiteDomainsSchema, storefrontSiteSchema, storefrontSiteStatusSchema, storefrontSlugAvailabilityQuerySchema, storefrontSlugAvailabilitySchema, storefrontSlugSchema, storefrontSourceArchivePartSchema, storefrontSourceArchiveQuerySchema, storefrontSourceBlobsResultSchema, storefrontSourceModeSchema, storefrontSourceRefSchema, storefrontSourceReplacedSchema, storefrontSourceSchema, storefrontSuspensionSchema, storefrontTemplateSchema, storefrontVerifyReportSchema, storefrontVersionCreatedSchema, storefrontVersionSchema, storefrontVersionStatusSchema, storefrontVisibilitySchema, tossPaymentsCredentialsInputSchema, trackingStageResponseSchema, trackingUpdateResultSchema, updateCouponRequestSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updateMemberRoleRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadExtensionOf, uploadPurposeSchema, uploadRejection, uploadRequestSchema, uploadStorefrontSourceBlobsRequestSchema, 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, writeDryRunResultSchema, writePreviewSchema, writeStorefrontFilesRequestSchema };
|
|
6580
|
+
export { APPROVAL_ALREADY_DECIDED_CODE, APPROVAL_REQUIRED_CODE, ApiError, ApprovalPendingError, CASH_RECEIPT_STATUS_VALUES, COUPON_CODE_PATTERN, COUPON_CONDITIONS_DEFAULTS, COUPON_TARGET_ALL, COUPON_TARGET_LIST_MAX, CUSTOM_INPUT_FORBIDDEN_WORDS, FULFILLMENT_RULES, MAX_ADDON_GROUPS, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_AUTO_TRANSITION_DAYS, MAX_CUSTOM_INPUTS, MAX_CUSTOM_INPUT_LENGTH, MAX_OPTION_GROUPS, MAX_PRODUCT_IMAGES, MAX_SAVED_OPTION_SETS, MAX_SAVED_OPTION_SET_NAME, MAX_VALUES_PER_GROUP, MAX_VARIANTS, MCP_TELEMETRY_MAX_RESULTS, MIN_PAYMENT_AMOUNT, NO_COUPON_SETTLEMENT, NO_SHIPPING_BUNDLE_KEY, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, REFUND_POLICY_DEFAULTS, REFUND_POLICY_DELIVERY_FEE_KEYS, REMOTE_AREA_ZIP_RANGES, RESERVED_STOREFRONT_SLUGS, RESERVED_STORE_CODES, SHIPPING_POLICY_DEFAULTS, STOREFRONT_BLOCKED_PATH_SEGMENTS, STOREFRONT_BUILD_STAGES, STOREFRONT_BUILD_TERMINAL_STATUSES, STOREFRONT_CHECK_TERMINAL_STATUSES, STOREFRONT_CUSTOM_DOMAIN_LIMIT_DEFAULT, STOREFRONT_DOMAIN_DNS_GRACE_DAYS, STOREFRONT_DOMAIN_DNS_WAIT_DAYS, STOREFRONT_DOMAIN_TXT_LABEL, STOREFRONT_EDIT_ACTIVITY_TIMEOUT_MINUTES, STOREFRONT_EDIT_CAPACITY, STOREFRONT_EDIT_IDLE_MINUTES, STOREFRONT_EDIT_PREVIEW_SUFFIX, STOREFRONT_EDIT_START_STAGES, STOREFRONT_FILE_BATCH_MAX, STOREFRONT_FILE_MAX_BYTES, STOREFRONT_PATH_SEGMENT_PATTERN, STOREFRONT_PLATFORM_DOMAINS, STOREFRONT_PUBLIC_SUFFIXES, STOREFRONT_SEARCH_REGEX_MAX_LENGTH, STOREFRONT_SHOWCASE_SLUG_PREFIX, STOREFRONT_SITE_CREATE_LIMIT, STOREFRONT_SLUG_CHANGE_LIMIT, STOREFRONT_SLUG_CHANGE_WINDOW_DAYS, STOREFRONT_SLUG_HOLD_DAYS, STOREFRONT_SLUG_PATTERN, STOREFRONT_SOURCE_BLOB_BATCH_MAX, STOREFRONT_SOURCE_PART_BYTES, STOREFRONT_WORKSPACE_MAX_BYTES, STOREFRONT_WORKSPACE_MAX_FILES, 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_COUNT_WARNING, 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, addStorefrontDomainRequestSchema, addonGroupRequestSchema, addonGroupSchema, addonValueRequestSchema, addonValueSchema, addressChangeRequestListSchema, addressTypeSchema, agentPolicyDecisionSchema, allocateByWeight, analyticsChannelSchema, analyticsCheckoutOutcomeSchema, analyticsCheckoutSchema, analyticsDeviceRowSchema, analyticsDevicesSchema, analyticsFunnelStepSchema, analyticsOriginSchema, analyticsOverviewSchema, analyticsPagesQuerySchema, analyticsPagesSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsRealtimeSchema, analyticsSearchQuerySchema, analyticsSearchSchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, approvalDecisionRequestSchema, approvalListQuerySchema, approvalPageSchema, approvalRequestSchema, approvalStatusSchema, approveClaimResultSchema, auditActorSchema, auditActorTypeSchema, auditLogListQuerySchema, auditLogOutcomeSchema, auditLogPageSchema, auditLogSchema, auditLogSourceSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, cashReceiptSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeStorefrontSlugRequestSchema, changeStorefrontTemplateRequestSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimReturnAddressSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, consentActionSchema, consentKindSchema, consentSourceSchema, couponBenefitSchema, couponConditionsInputSchema, couponConditionsSchema, couponDiscountOf, couponIssueListQuerySchema, couponIssueMethodSchema, couponIssueSchema, couponIssueStatusSchema, couponKindSchema, couponListQuerySchema, couponRedemptionListQuerySchema, couponRedemptionSchema, couponRejectReasonSchema, couponRuleSchema, couponSchema, couponStatsSchema, couponStatusSchema, couponTargetInputSchema, couponTargetSchema, createCouponRequestSchema, createPkcePair, createStoreClient, createStoreSession, createStorefrontEditPreviewLinkRequestSchema, createStorefrontPreviewLinkRequestSchema, createStorefrontSiteRequestSchema, createStorefrontVersionRequestSchema, customInputLabelForbiddenWord, customInputRequestSchema, customInputSchema, customInputValueSchema, customerAddressSchema, customerConsentListQuerySchema, customerConsentSchema, customerConsentStateSchema, customerConsentsSchema, customerCouponSchema, customerDelegationRequestSchema, customerDelegationSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deleteStorefrontFileRequestSchema, deliveryBundleKeyOf, deliveryFeeWaiverSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, disableStorefrontHostingRequestSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, easyPayProviderResponseSchema, easyPayProviderSchema, eventTypeSchema, exchangeAuthorizationCode, fulfillBulkRequestSchema, fulfillRequestSchema, fulfillmentRuleOf, fulfillmentTypeOf, fulfillmentTypeResponseSchema, fulfillmentTypeSchema, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationDeliveryStatusSchema, invitationListQuerySchema, invitationSchema, inviteMemberRequestSchema, isCouponTargetLine, isEditableStorefrontPath, isReservedStoreCode, isReservedStorefrontSlug, isShowcaseStorefrontSlug, isStoreScope, isWebOrigin, issueCouponRequestSchema, issueCouponResultSchema, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, mcpRuleResultSchema, mcpToolEventSchema, moveStorefrontFileRequestSchema, myAccountSchema, myStoreSchema, normalizeCouponCode, normalizeStorefrontHostname, normalizeStorefrontPath, oauthTokenResponseSchema, openFulfillmentUnion, optionGroupKindSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionSelectionSchema, optionSortSchema, optionValueRequestSchema, optionValueSchema, orderCanceledWebhookEventDataSchema, orderCanceledWebhookEventSchema, orderDiscountSchema, orderFulfilledWebhookEventDataSchema, orderFulfilledWebhookEventSchema, orderItemFulfillmentSchema, orderItemFulfillmentSnapshotResponseSchema, orderItemFulfillmentSnapshotSchema, 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, planCodeSchema, planEntitlementsSchema, planErrorDetailsSchema, policyDocumentUrlSchema, policyVersionSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productFulfillmentCreateSchema, productFulfillmentResponseSchema, productFulfillmentSchema, productFulfillmentUpdateSchema, productImageRejection, productImageUrlSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productRefundPolicyOverride, productRefundPolicyPatchSchema, productReplaceRequestSchema, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, proratedPaidRefundBase, proratedRefundBase, publicPlanSchema, publicPlansSchema, publishStorefrontVersionRequestSchema, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, quoteDelivery, quotePricing, refreshUserToken, refundPolicyPatchSchema, refundPolicySchema, rejectClaimRequestSchema, remoteAreaLabelOf, remoteAreaOf, remoteAreaSchema, remoteSurchargeOf, replaceStorefrontSourceRequestSchema, requiresCollection, requiresShipping, resolveRefundPolicy, resolveShippingPolicy, responsePlanCodeSchema, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, rollbackStorefrontRequestSchema, runStorefrontCheckRequestSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, savedCustomInputRequestSchema, savedCustomInputSchema, savedOptionGroupRequestSchema, savedOptionGroupSchema, savedOptionSetCompositionSchema, savedOptionSetFromProductRequestSchema, savedOptionSetFromProductResultSchema, savedOptionSetListQuerySchema, savedOptionSetRequestSchema, savedOptionSetSchema, savedOptionSetSortSchema, savedOptionValueRequestSchema, savedOptionValueSchema, scopeAccessSchema, searchStorefrontFilesRequestSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, setStorefrontVisibilityRequestSchema, settleCouponShortfall, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, shippingPolicyPatchSchema, shippingPolicySchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeSubscriptionSchema, storefrontBuildDependenciesSchema, storefrontBuildListQuerySchema, storefrontBuildSchema, storefrontBuildStatusSchema, storefrontCheckSchema, storefrontCheckStatusSchema, storefrontCustomDomainStatusSchema, storefrontCustomHostnameProblem, storefrontDependencyFindingSchema, storefrontDeploymentLogSchema, storefrontDeploymentSchema, storefrontDeploymentStepSchema, storefrontDnsRecordSchema, storefrontDomainSchema, storefrontDomainTxtNameOf, storefrontEditPreviewLabelOf, storefrontEditPreviewSchema, storefrontEditSessionSchema, storefrontEditSessionStatusSchema, storefrontEditingSchema, storefrontFileConflictDetailsSchema, storefrontFileContentQuerySchema, storefrontFileContentSchema, storefrontFileDeleteResultSchema, storefrontFileEncodingSchema, storefrontFileEntrySchema, storefrontFileHashSchema, storefrontFileListQuerySchema, storefrontFileListSchema, storefrontFileMoveResultSchema, storefrontFilePathSchema, storefrontFileSearchResultSchema, storefrontFileWriteResultSchema, storefrontHostingSchema, storefrontPreviewLinkSchema, storefrontProvisioningSchema, storefrontRuleResultSchema, storefrontRuleSeveritySchema, storefrontSiteDomainSchema, storefrontSiteDomainsSchema, storefrontSiteSchema, storefrontSiteStatusSchema, storefrontSlugAvailabilityQuerySchema, storefrontSlugAvailabilitySchema, storefrontSlugSchema, storefrontSourceArchivePartSchema, storefrontSourceArchiveQuerySchema, storefrontSourceBlobsResultSchema, storefrontSourceModeSchema, storefrontSourceRefSchema, storefrontSourceReplacedSchema, storefrontSourceSchema, storefrontSuspensionSchema, storefrontTemplateSchema, storefrontVerifyReportSchema, storefrontVersionCreatedSchema, storefrontVersionSchema, storefrontVersionStatusSchema, storefrontVisibilitySchema, subscriptionLimitsSchema, subscriptionStatusSchema, subscriptionUsageSchema, tossPaymentsCredentialsInputSchema, trackingStageResponseSchema, trackingUpdateResultSchema, updateCouponRequestSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updateMemberRoleRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadExtensionOf, uploadPurposeSchema, uploadRejection, uploadRequestSchema, uploadStorefrontSourceBlobsRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryDetailSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliveryResourceSchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookFailureReasonSchema, webhookRedeliverFailedResultSchema, webhookRequestHeaderSchema, webhookRequestSchema, webhookResourceTypeSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema, writeDryRunResultSchema, writePreviewSchema, writeStorefrontFilesRequestSchema };
|