@sayren/store-sdk 0.28.0 → 0.28.2
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 +393 -324
- package/dist/index.mjs +184 -134
- package/dist/server.d.mts +1 -1
- package/dist/server.mjs +1 -1
- package/package.json +1 -1
- package/src/client.ts +21 -21
- package/src/http.ts +1 -1
- package/src/member-delegation.ts +1 -1
- package/src/schemas/api-log.ts +7 -5
- package/src/schemas/audit.ts +5 -5
- package/src/schemas/claim.ts +4 -4
- package/src/schemas/coupon.ts +2 -2
- 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 +32 -12
- 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 -3
- package/src/schemas/shipping-policy.ts +18 -18
- package/src/schemas/store.ts +106 -11
- package/src/schemas/storefront-hosting.ts +35 -39
- package/src/schemas/subscription.ts +8 -8
- package/src/schemas/telemetry.ts +2 -2
- package/src/schemas/upload.ts +1 -1
- package/src/schemas/webhook-event.ts +1 -1
- package/src/session.ts +1 -1
package/dist/server.d.mts
CHANGED
|
@@ -19,7 +19,7 @@ export declare class MemberDelegationError extends Error {
|
|
|
19
19
|
constructor(error: string, description: string, status: number);
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
|
-
* 계정 토큰으로 구매자 위임(`POST /v1/customers/delegations`) — 계정 토큰(개인 액세스 토큰 등)과
|
|
22
|
+
* 계정 토큰으로 구매자 위임(`POST /v1/customers/delegations`) — 계정 토큰(개인 액세스 토큰 등)과 상점 id를 쓴다.
|
|
23
23
|
* `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
|
|
24
24
|
*/
|
|
25
25
|
export declare function delegateMember(options: {
|
package/dist/server.mjs
CHANGED
|
@@ -14,7 +14,7 @@ var MemberDelegationError = class extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
|
-
* 계정 토큰으로 구매자 위임(`POST /v1/customers/delegations`) — 계정 토큰(개인 액세스 토큰 등)과
|
|
17
|
+
* 계정 토큰으로 구매자 위임(`POST /v1/customers/delegations`) — 계정 토큰(개인 액세스 토큰 등)과 상점 id를 쓴다.
|
|
18
18
|
* `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
|
|
19
19
|
*/
|
|
20
20
|
async function delegateMember(options) {
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -282,11 +282,11 @@ export interface StoreClientOptions {
|
|
|
282
282
|
/** 예: https://api.example.com/v1 */
|
|
283
283
|
baseUrl: string;
|
|
284
284
|
/**
|
|
285
|
-
* 계정 토큰(accounts가 발급한 JWT —
|
|
286
|
-
* 계정 API(`/me/*`)와
|
|
285
|
+
* 계정 토큰(accounts가 발급한 JWT — 상점 플랫폼 로그인, 개인 액세스 토큰, 연결된 앱의 토큰).
|
|
286
|
+
* 계정 API(`/me/*`)와 상점 API 모두에 Bearer로 싣는다. `session`을 주면 생략해도 세션 토큰을 쓴다.
|
|
287
287
|
*/
|
|
288
288
|
accessToken?: TokenSource;
|
|
289
|
-
/** 대상
|
|
289
|
+
/** 대상 상점 id — 상점 API 요청에 `X-Store-Id`로 싣는다. 계정 API(`/me/*`)에는 싣지 않는다 */
|
|
290
290
|
storeId?: TokenSource;
|
|
291
291
|
/**
|
|
292
292
|
* SDK 세션 매니저 — 전달하면 Bearer 토큰·401 refresh·복구 불가 시 clear를
|
|
@@ -315,7 +315,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
315
315
|
return token ? { authorization: `Bearer ${token}` } : {};
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
/** 계정 API(`/me/*`) —
|
|
318
|
+
/** 계정 API(`/me/*`) — 상점을 고르지 않는다 */
|
|
319
319
|
const userHttp = createHttp({
|
|
320
320
|
baseUrl: options.baseUrl,
|
|
321
321
|
fetch: fetchFn,
|
|
@@ -324,7 +324,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
324
324
|
onUnauthorized,
|
|
325
325
|
});
|
|
326
326
|
|
|
327
|
-
/**
|
|
327
|
+
/** 상점 API — 같은 계정 토큰 + `X-Store-Id` */
|
|
328
328
|
const storeHttp = createHttp({
|
|
329
329
|
baseUrl: options.baseUrl,
|
|
330
330
|
fetch: fetchFn,
|
|
@@ -370,7 +370,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
370
370
|
userinfo: () => requireSession().userinfo(),
|
|
371
371
|
|
|
372
372
|
auth: {
|
|
373
|
-
/** 소속
|
|
373
|
+
/** 소속 상점 목록 */
|
|
374
374
|
listMyStores: () => userHttp.request("GET", "/me/stores", z.array(myStoreSchema)),
|
|
375
375
|
me: () => userHttp.request("GET", "/me", myAccountSchema),
|
|
376
376
|
},
|
|
@@ -491,7 +491,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
491
491
|
}),
|
|
492
492
|
get: (optionSetId: string) =>
|
|
493
493
|
storeHttp.request("GET", path`/saved-option-sets/${optionSetId}`, savedOptionSetSchema),
|
|
494
|
-
/** 이름이 겹치면 409 `OPTION_SET_NAME_TAKEN`,
|
|
494
|
+
/** 이름이 겹치면 409 `OPTION_SET_NAME_TAKEN`, 상점당 200개를 넘으면 409 `OPTION_SET_LIMIT_EXCEEDED` */
|
|
495
495
|
create: (body: SavedOptionSetRequestInput) =>
|
|
496
496
|
storeHttp.request("POST", "/saved-option-sets", savedOptionSetSchema, {
|
|
497
497
|
body: savedOptionSetRequestSchema.parse(body),
|
|
@@ -561,7 +561,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
561
561
|
get: (orderId: string) => storeHttp.request("GET", path`/orders/${orderId}`, orderSchema),
|
|
562
562
|
/**
|
|
563
563
|
* `POST /v1/orders/{orderId}/cash-receipt/reissue` — 실패했거나 재시도 대기 중인 현금영수증을 지금 다시 발급·취소한다.
|
|
564
|
-
*
|
|
564
|
+
* 상점 플랫폼 OWNER·ADMIN 전용(PAT·M2M 403 `INSUFFICIENT_ROLE`). 404 `CASH_RECEIPT_NOT_FOUND`, 409 `CASH_RECEIPT_NOT_REISSUABLE`
|
|
565
565
|
*/
|
|
566
566
|
reissueCashReceipt: (orderId: string) =>
|
|
567
567
|
storeHttp.request("POST", path`/orders/${orderId}/cash-receipt/reissue`, cashReceiptSchema),
|
|
@@ -624,7 +624,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
624
624
|
}),
|
|
625
625
|
/**
|
|
626
626
|
* 배송완료 처리 — `DISPATCHED`·`DELIVERING`만 넘어간다. 응답의 `autoDecisionDate`가 자동 구매확정 예정일이다
|
|
627
|
-
* (
|
|
627
|
+
* (상점 설정 `autoConfirmDays`가 0이면 null).
|
|
628
628
|
*/
|
|
629
629
|
markDelivered: (orderItemId: string, idempotencyKey?: string) =>
|
|
630
630
|
storeHttp.request("POST", path`/order-items/${orderItemId}/delivered`, orderItemSchema, {
|
|
@@ -713,7 +713,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
713
713
|
},
|
|
714
714
|
|
|
715
715
|
/**
|
|
716
|
-
* 구매자 로그인 설정 — 이메일·비밀번호 허용, 소셜 공급자 연결. 비밀 값을 다루므로
|
|
716
|
+
* 구매자 로그인 설정 — 이메일·비밀번호 허용, 소셜 공급자 연결. 비밀 값을 다루므로 상점 플랫폼에 로그인한 OWNER만 호출할 수 있다
|
|
717
717
|
*/
|
|
718
718
|
loginSettings: {
|
|
719
719
|
get: () => storeHttp.request("GET", "/store/login-settings", loginSettingsSchema),
|
|
@@ -741,7 +741,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
741
741
|
storeHttp.request("DELETE", path`/store/auth-providers/${provider}`, loginSettingsSchema),
|
|
742
742
|
},
|
|
743
743
|
|
|
744
|
-
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(
|
|
744
|
+
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(상점 플랫폼 OWNER·ADMIN) */
|
|
745
745
|
customers: {
|
|
746
746
|
list: (
|
|
747
747
|
params?: PageParams & Omit<z.input<typeof customerListQuerySchema>, "page" | "size">,
|
|
@@ -955,8 +955,8 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
955
955
|
},
|
|
956
956
|
|
|
957
957
|
/**
|
|
958
|
-
* 결제(PG) 설정 —
|
|
959
|
-
* 403 `INSUFFICIENT_ROLE`이다(조회 포함). OWNER 여부는 요청 시점의
|
|
958
|
+
* 결제(PG) 설정 — **상점 플랫폼 OWNER 전용**. ADMIN·STAFF·PAT·M2M·3rd-party 앱 토큰은 스코프가 있어도
|
|
959
|
+
* 403 `INSUFFICIENT_ROLE`이다(조회 포함). OWNER 여부는 요청 시점의 상점 멤버십으로 다시 확인하므로
|
|
960
960
|
* 양도·강등 직후에도 바로 403이다. 비밀 값(시크릿 키·API Secret·웹훅 시크릿)은 요청으로만 보내고
|
|
961
961
|
* 응답에는 `secrets[].masked`(앞 4자리 마스킹)만 나온다.
|
|
962
962
|
*/
|
|
@@ -1127,7 +1127,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1127
1127
|
}),
|
|
1128
1128
|
},
|
|
1129
1129
|
|
|
1130
|
-
/** API 로그 (보관 7일,
|
|
1130
|
+
/** API 로그 (보관 7일, 상점 플랫폼 OWNER/ADMIN) */
|
|
1131
1131
|
apiLogs: {
|
|
1132
1132
|
/** 최신순 커서 페이지네이션. 다음 페이지는 응답의 `nextCursor`를 `cursor`로 넘긴다 */
|
|
1133
1133
|
list: (params?: ApiLogListOptions) =>
|
|
@@ -1143,7 +1143,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1143
1143
|
summary: () => storeHttp.request("GET", "/api-logs/summary", apiLogSummarySchema),
|
|
1144
1144
|
},
|
|
1145
1145
|
|
|
1146
|
-
/** 감사 로그 (관리 API 쓰기의 행위자·대상·결과,
|
|
1146
|
+
/** 감사 로그 (관리 API 쓰기의 행위자·대상·결과, 상점 플랫폼 OWNER/ADMIN, `audit:r`) */
|
|
1147
1147
|
auditLogs: {
|
|
1148
1148
|
/** 최신순 커서 페이지네이션. 다음 페이지는 응답의 `nextCursor`를 `cursor`로 넘긴다 */
|
|
1149
1149
|
list: (params?: AuditLogListOptions) =>
|
|
@@ -1158,7 +1158,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1158
1158
|
},
|
|
1159
1159
|
|
|
1160
1160
|
/**
|
|
1161
|
-
* 승인 요청 — 에이전트의 고위험 쓰기가 202로 보류된 건. 목록·승인·거절은
|
|
1161
|
+
* 승인 요청 — 에이전트의 고위험 쓰기가 202로 보류된 건. 목록·승인·거절은 상점 플랫폼 OWNER/ADMIN,
|
|
1162
1162
|
* 단건 조회는 요청한 토큰도 할 수 있다(상태 확인용).
|
|
1163
1163
|
*/
|
|
1164
1164
|
approvalRequests: {
|
|
@@ -1247,7 +1247,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1247
1247
|
),
|
|
1248
1248
|
/**
|
|
1249
1249
|
* `POST /v1/storefront/site` (201) — 템플릿으로 사이트를 만들고 v1을 발행한다. 공개 상태는 오픈 준비 중이다.
|
|
1250
|
-
* 사이트를 만드는 유일한 경로다(
|
|
1250
|
+
* 사이트를 만드는 유일한 경로다(상점을 만들어도 사이트는 생기지 않는다). 템플릿은 여기서만 고른다
|
|
1251
1251
|
*/
|
|
1252
1252
|
create: (body: CreateStorefrontSiteRequest) =>
|
|
1253
1253
|
storeHttp.request("POST", "/storefront/site", storefrontSiteSchema, {
|
|
@@ -1298,7 +1298,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1298
1298
|
}),
|
|
1299
1299
|
/**
|
|
1300
1300
|
* `POST /v1/storefront/site/preview-link` — 오픈 준비 중인 사이트를 구성원이 볼 수 있는 5분짜리 링크.
|
|
1301
|
-
*
|
|
1301
|
+
* 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다
|
|
1302
1302
|
*/
|
|
1303
1303
|
createPreviewLink: (body: CreateStorefrontPreviewLinkRequest = {}) =>
|
|
1304
1304
|
storeHttp.request("POST", "/storefront/site/preview-link", storefrontPreviewLinkSchema, {
|
|
@@ -1356,7 +1356,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1356
1356
|
storeHttp.request("DELETE", "/storefront/site/editing", storefrontEditingSchema),
|
|
1357
1357
|
/**
|
|
1358
1358
|
* `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 5분짜리 링크.
|
|
1359
|
-
*
|
|
1359
|
+
* 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다(에이전트 불가)
|
|
1360
1360
|
*/
|
|
1361
1361
|
createEditPreviewLink: (body: CreateStorefrontEditPreviewLinkRequest = {}) =>
|
|
1362
1362
|
storeHttp.request(
|
|
@@ -1472,7 +1472,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1472
1472
|
subscription: {
|
|
1473
1473
|
/**
|
|
1474
1474
|
* `GET /v1/store/subscription` — 스코프 `store:r`. 현재 요금제·상태·사용량 기간·한도·이번 기간 사용량.
|
|
1475
|
-
* 결제 수단·청구 금액은 싣지 않는다. 요금제 변경은
|
|
1475
|
+
* 결제 수단·청구 금액은 싣지 않는다. 요금제 변경은 상점 플랫폼에서만 한다
|
|
1476
1476
|
*/
|
|
1477
1477
|
get: () => storeHttp.request("GET", "/store/subscription", storeSubscriptionSchema),
|
|
1478
1478
|
},
|
|
@@ -1510,7 +1510,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1510
1510
|
}),
|
|
1511
1511
|
/**
|
|
1512
1512
|
* `POST /v1/store/invitations` (201) — 스코프 `member:rw`. 초대 메일을 보낸다. 발송 결과는 응답의
|
|
1513
|
-
* `deliveryStatus`다(발송이 실패해도 초대는 만들어진다).
|
|
1513
|
+
* `deliveryStatus`다(발송이 실패해도 초대는 만들어진다). 상점·초대한 사람·받는 주소별 발송 한도가 있다(429, `details.limit`).
|
|
1514
1514
|
*/
|
|
1515
1515
|
invite: (body: InviteMemberRequest) =>
|
|
1516
1516
|
storeHttp.request("POST", "/store/invitations", invitationSchema, {
|
package/src/http.ts
CHANGED
|
@@ -74,7 +74,7 @@ export const APPROVAL_ALREADY_DECIDED_CODE = "APPROVAL_ALREADY_DECIDED";
|
|
|
74
74
|
* 성공 값으로 돌려주지 않는다 — 돌려주면 호출부가 승인 요청을 상품·주문으로 읽는다.
|
|
75
75
|
*
|
|
76
76
|
* - `code = APPROVAL_REQUIRED`(202): 에이전트의 고위험 쓰기가 보류됐다. 아직 아무것도 바뀌지 않았다.
|
|
77
|
-
* 셀러가
|
|
77
|
+
* 셀러가 상점 플랫폼(`approval.approvalUrl`)에서 승인하면 서버가 원 요청을 실행한다. 같은 요청을 다시 보내지 말고
|
|
78
78
|
* `approvalRequests.get(approvalId)`로 결과를 확인한다.
|
|
79
79
|
* - `code = APPROVAL_ALREADY_DECIDED`(200): 같은 멱등키로 보낸 요청의 승인이 이미 끝났다. `status`가
|
|
80
80
|
* `SUCCEEDED`·`FAILED`·`UNKNOWN`이면 실행 결과(`approval.result`)를 보고, `REJECTED`·`EXPIRED`면 새 멱등키로 다시 요청한다.
|
package/src/member-delegation.ts
CHANGED
|
@@ -24,7 +24,7 @@ export class MemberDelegationError extends Error {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* 계정 토큰으로 구매자 위임(`POST /v1/customers/delegations`) — 계정 토큰(개인 액세스 토큰 등)과
|
|
27
|
+
* 계정 토큰으로 구매자 위임(`POST /v1/customers/delegations`) — 계정 토큰(개인 액세스 토큰 등)과 상점 id를 쓴다.
|
|
28
28
|
* `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
|
|
29
29
|
*/
|
|
30
30
|
export async function delegateMember(options: {
|
package/src/schemas/api-log.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* API 로그 — `/v1/api-logs`.
|
|
5
|
-
* 인증에 실패해
|
|
4
|
+
* API 로그 — `/v1/api-logs`. 상점이 해석된 관리 API(`/v1/**`) 요청을 7일간 보관한다.
|
|
5
|
+
* 인증에 실패해 상점을 알 수 없는 요청(401 등)은 기록되지 않는다.
|
|
6
6
|
* Authorization·쿠키 헤더, 요청 본문, 개인정보는 저장하지 않는다.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export const apiLogPrincipalTypeSchema = z
|
|
10
10
|
.enum(["USER", "PAT", "CLIENT"])
|
|
11
|
-
.describe(
|
|
11
|
+
.describe(
|
|
12
|
+
"호출 주체 유형. `USER`=상점 플랫폼 사용자, `PAT`=API 토큰, `CLIENT`=앱(OAuth 클라이언트)",
|
|
13
|
+
);
|
|
12
14
|
export type ApiLogPrincipalType = z.infer<typeof apiLogPrincipalTypeSchema>;
|
|
13
15
|
|
|
14
16
|
export const apiLogPrincipalSchema = z.object({
|
|
@@ -100,7 +102,7 @@ export type ApiLogPage = z.infer<typeof apiLogPageSchema>;
|
|
|
100
102
|
|
|
101
103
|
/**
|
|
102
104
|
* API 로그 요약 — `GET /api-logs/summary`. 연동 트래픽(호출 주체 `PAT`·`CLIENT`)만 집계하며
|
|
103
|
-
*
|
|
105
|
+
* 상점 플랫폼 사용자(`USER`) 요청은 제외한다
|
|
104
106
|
*/
|
|
105
107
|
export const apiLogSummarySchema = z.object({
|
|
106
108
|
windowHours: z.number().int().describe("집계 구간(시간). 최근 24시간"),
|
|
@@ -108,7 +110,7 @@ export const apiLogSummarySchema = z.object({
|
|
|
108
110
|
.number()
|
|
109
111
|
.int()
|
|
110
112
|
.min(0)
|
|
111
|
-
.describe("구간 내 연동 요청 수 (호출 주체 `PAT`·`CLIENT`,
|
|
113
|
+
.describe("구간 내 연동 요청 수 (호출 주체 `PAT`·`CLIENT`, 상점 플랫폼 사용자 제외)"),
|
|
112
114
|
errors: z.number().int().min(0).describe("구간 내 연동 요청 중 4xx·5xx 응답 수"),
|
|
113
115
|
serverErrors: z.number().int().min(0).describe("구간 내 연동 요청 중 5xx 응답 수"),
|
|
114
116
|
});
|
package/src/schemas/audit.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
export const auditActorTypeSchema = z
|
|
11
11
|
.enum(["USER", "AGENT", "APP", "TOKEN", "SYSTEM"])
|
|
12
12
|
.describe(
|
|
13
|
-
"행위자 유형. `USER
|
|
13
|
+
"행위자 유형. `USER`=상점 플랫폼 사용자, `AGENT`=AI 에이전트(MCP·에이전트용 API 토큰), `APP`=앱(OAuth 클라이언트·M2M), " +
|
|
14
14
|
"`TOKEN`=API 토큰, `SYSTEM`=자동화(주기 잡)",
|
|
15
15
|
);
|
|
16
16
|
export type AuditActorType = z.infer<typeof auditActorTypeSchema>;
|
|
@@ -55,7 +55,7 @@ export const auditLogSchema = z.object({
|
|
|
55
55
|
actorRole: z
|
|
56
56
|
.string()
|
|
57
57
|
.nullable()
|
|
58
|
-
.describe("요청 시점의
|
|
58
|
+
.describe("요청 시점의 상점 역할. 역할이 없는 토큰·자동화는 null"),
|
|
59
59
|
target: z.object({
|
|
60
60
|
type: z.string().describe("대상 리소스 유형 (예: `products`, `order-items`, `claim`)"),
|
|
61
61
|
id: z
|
|
@@ -144,7 +144,7 @@ export type AuditLogPage = z.infer<typeof auditLogPageSchema>;
|
|
|
144
144
|
export const agentPolicyDecisionSchema = z
|
|
145
145
|
.enum(["ALLOW", "APPROVAL_REQUIRED", "DENIED"])
|
|
146
146
|
.describe(
|
|
147
|
-
"에이전트 쓰기 판정. `ALLOW`=바로 실행, `APPROVAL_REQUIRED
|
|
147
|
+
"에이전트 쓰기 판정. `ALLOW`=바로 실행, `APPROVAL_REQUIRED`=상점 플랫폼 승인 뒤 실행, `DENIED`=에이전트는 실행할 수 없음",
|
|
148
148
|
);
|
|
149
149
|
export type AgentPolicyDecision = z.infer<typeof agentPolicyDecisionSchema>;
|
|
150
150
|
|
|
@@ -208,7 +208,7 @@ export const approvalRequestSchema = z.object({
|
|
|
208
208
|
decidedBy: z
|
|
209
209
|
.object({ id: z.string(), name: z.string().nullable() })
|
|
210
210
|
.nullable()
|
|
211
|
-
.describe("승인·거절한
|
|
211
|
+
.describe("승인·거절한 상점 플랫폼 사용자"),
|
|
212
212
|
decisionNote: z.string().nullable().describe("승인·거절 메모"),
|
|
213
213
|
executedAt: z.string().nullable().describe("원 요청 실행을 마친 일시"),
|
|
214
214
|
result: z
|
|
@@ -227,7 +227,7 @@ export const approvalRequestSchema = z.object({
|
|
|
227
227
|
approvalUrl: z
|
|
228
228
|
.string()
|
|
229
229
|
.nullable()
|
|
230
|
-
.describe("
|
|
230
|
+
.describe("상점 플랫폼의 승인 화면 주소. 이 배포의 상점 플랫폼 주소를 알 수 없으면 null"),
|
|
231
231
|
});
|
|
232
232
|
export type ApprovalRequest = z.infer<typeof approvalRequestSchema>;
|
|
233
233
|
|
package/src/schemas/claim.ts
CHANGED
|
@@ -31,10 +31,10 @@ export const claimReasonSchema = z.enum([
|
|
|
31
31
|
export type ClaimReason = z.infer<typeof claimReasonSchema>;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* 반품지 (이슈 #30) — 반품·교환 수거가 안내하는 주소다.
|
|
34
|
+
* 반품지 (이슈 #30) — 반품·교환 수거가 안내하는 주소다. 상점 주소록의 기본 반품지이거나,
|
|
35
35
|
* 상품이 반품지를 따로 지정했으면 그 주소다.
|
|
36
36
|
*
|
|
37
|
-
* 취소 클레임은 보낼 물건이 없어 null이고, 반품지를 등록하지 않은
|
|
37
|
+
* 취소 클레임은 보낼 물건이 없어 null이고, 반품지를 등록하지 않은 상점도 null이다.
|
|
38
38
|
* 구매자가 직접 보내는 수거(`BUYER_SEND`)에 이 주소가 필요하다.
|
|
39
39
|
*/
|
|
40
40
|
export const claimReturnAddressSchema = z.object({
|
|
@@ -102,8 +102,8 @@ export const claimSchema = z.object({
|
|
|
102
102
|
returnAddress: claimReturnAddressSchema
|
|
103
103
|
.nullable()
|
|
104
104
|
.describe(
|
|
105
|
-
"수거 안내에 쓸 반품지(이슈 #30). 상품이 지정한 반품지, 없으면
|
|
106
|
-
"취소 클레임과 반품지를 등록하지 않은
|
|
105
|
+
"수거 안내에 쓸 반품지(이슈 #30). 상품이 지정한 반품지, 없으면 상점 기본 반품지다. " +
|
|
106
|
+
"취소 클레임과 반품지를 등록하지 않은 상점은 null",
|
|
107
107
|
),
|
|
108
108
|
});
|
|
109
109
|
|
package/src/schemas/coupon.ts
CHANGED
|
@@ -45,7 +45,7 @@ const limitField = (description: string) =>
|
|
|
45
45
|
const descriptions = {
|
|
46
46
|
name: "구매자에게 보이는 쿠폰 이름. 1~60자",
|
|
47
47
|
description: "구매자에게 보이는 설명. 300자까지",
|
|
48
|
-
code: "쿠폰 코드 — 발급 방식이 `CODE`일 때 필수. 영문·숫자·`-`·`_` 6~30자, 대소문자 구분 없음(대문자로 저장).
|
|
48
|
+
code: "쿠폰 코드 — 발급 방식이 `CODE`일 때 필수. 영문·숫자·`-`·`_` 6~30자, 대소문자 구분 없음(대문자로 저장). 상점 안에서 유일하다",
|
|
49
49
|
startsAt: "사용 시작 시각(ISO 8601, 시간대 포함). 생략하면 바로 쓸 수 있다",
|
|
50
50
|
endsAt:
|
|
51
51
|
"사용 종료 시각(ISO 8601, 시간대 포함). 이 시각부터 못 쓴다. 생략하면 제한이 없다. " +
|
|
@@ -247,7 +247,7 @@ export const issueCouponResultSchema = z.object({
|
|
|
247
247
|
code: z
|
|
248
248
|
.string()
|
|
249
249
|
.describe(
|
|
250
|
-
"실패 사유. `CUSTOMER_NOT_FOUND`(이
|
|
250
|
+
"실패 사유. `CUSTOMER_NOT_FOUND`(이 상점 구매자가 아님)·`COUPON_LIMIT_REACHED`(1인 발급 한도)·" +
|
|
251
251
|
"`COUPON_EXHAUSTED`(총 발급 한도)",
|
|
252
252
|
),
|
|
253
253
|
message: z.string(),
|
package/src/schemas/customer.ts
CHANGED
|
@@ -88,7 +88,7 @@ export const customerConsentSchema = z.object({
|
|
|
88
88
|
kind: consentKindSchema,
|
|
89
89
|
/** 동의한 약관 버전. 마케팅은 버전이 없어 null */
|
|
90
90
|
version: z.string().nullable(),
|
|
91
|
-
/** 동의 시점의 약관 문서 주소.
|
|
91
|
+
/** 동의 시점의 약관 문서 주소. 상점이 주소를 정하지 않았거나 마케팅이면 null */
|
|
92
92
|
documentUrl: z.string().nullable(),
|
|
93
93
|
action: consentActionSchema,
|
|
94
94
|
source: consentSourceSchema,
|
|
@@ -102,9 +102,9 @@ export const customerConsentStateSchema = z.object({
|
|
|
102
102
|
agreed: z.boolean(),
|
|
103
103
|
/** 동의한 약관 버전. 마케팅이거나 이력이 없으면 null */
|
|
104
104
|
agreedVersion: z.string().nullable(),
|
|
105
|
-
/**
|
|
105
|
+
/** 상점의 현재 약관 버전. 마케팅은 null */
|
|
106
106
|
currentVersion: z.string().nullable(),
|
|
107
|
-
/**
|
|
107
|
+
/** 상점이 정한 현재 약관 문서 주소. 정하지 않았거나 마케팅이면 null */
|
|
108
108
|
currentDocumentUrl: z.string().nullable(),
|
|
109
109
|
updatedAt: z.string().nullable(),
|
|
110
110
|
/** 약관이 개정돼 다시 받아야 하는가. 마케팅은 항상 false */
|
|
@@ -107,11 +107,11 @@ const KNOWN_FULFILLMENT_TYPES: readonly string[] = fulfillmentTypeSchema.options
|
|
|
107
107
|
|
|
108
108
|
const ADDRESS_ID_DESCRIPTIONS = {
|
|
109
109
|
shipping:
|
|
110
|
-
"출고지 주소 id(`GET /v1/seller-addresses?type=SHIPPING`). 등록에서 생략하면 그 시점
|
|
111
|
-
"`null`이면 비워 둔다(
|
|
110
|
+
"출고지 주소 id(`GET /v1/seller-addresses?type=SHIPPING`). 등록에서 생략하면 그 시점 상점 기본 출고지를 저장한다. " +
|
|
111
|
+
"`null`이면 비워 둔다(상점 기본 출고지를 따른다). 이 상점의 `SHIPPING` 주소가 아니면 거부한다",
|
|
112
112
|
return:
|
|
113
|
-
"반품지 주소 id(`GET /v1/seller-addresses?type=RETURN`). 등록에서 생략하면 그 시점
|
|
114
|
-
"`null`이면 비워 둔다(
|
|
113
|
+
"반품지 주소 id(`GET /v1/seller-addresses?type=RETURN`). 등록에서 생략하면 그 시점 상점 기본 반품지를 저장한다. " +
|
|
114
|
+
"`null`이면 비워 둔다(상점 기본 반품지를 따른다). 이 상점의 `RETURN` 주소가 아니면 거부한다",
|
|
115
115
|
} as const;
|
|
116
116
|
|
|
117
117
|
/**
|
|
@@ -161,7 +161,7 @@ const shippingCreateSchema = z
|
|
|
161
161
|
.min(0)
|
|
162
162
|
.optional()
|
|
163
163
|
.describe(
|
|
164
|
-
"배송비. 원 단위. 생략하면
|
|
164
|
+
"배송비. 원 단위. 생략하면 상점 기본 배송비(`GET /v1/store`의 `shippingPolicy.baseDeliveryFee`)를 저장한다. FREE면 0이거나 생략한다",
|
|
165
165
|
),
|
|
166
166
|
conditionalFreeAmount: z
|
|
167
167
|
.number()
|
|
@@ -176,7 +176,7 @@ const shippingCreateSchema = z
|
|
|
176
176
|
.superRefine(refineShippingConsistency);
|
|
177
177
|
|
|
178
178
|
/**
|
|
179
|
-
* 상품 이행 — 등록(POST). 배송 상품의 `shipping`을 생략하면 기본값(유료
|
|
179
|
+
* 상품 이행 — 등록(POST). 배송 상품의 `shipping`을 생략하면 기본값(유료 배송·상점 기본 배송비·기본 출고지·반품지)이다.
|
|
180
180
|
*/
|
|
181
181
|
export const productFulfillmentCreateSchema = z.discriminatedUnion("type", [
|
|
182
182
|
z.strictObject({ type: z.literal("SHIPPING"), shipping: shippingCreateSchema.optional() }),
|
|
@@ -235,11 +235,11 @@ export const productFulfillmentSchema = z.discriminatedUnion("type", [
|
|
|
235
235
|
shippingAddressId: z
|
|
236
236
|
.string()
|
|
237
237
|
.nullable()
|
|
238
|
-
.describe("출고지 주소 id. null이면
|
|
238
|
+
.describe("출고지 주소 id. null이면 상점 기본 출고지를 따른다"),
|
|
239
239
|
returnAddressId: z
|
|
240
240
|
.string()
|
|
241
241
|
.nullable()
|
|
242
|
-
.describe("반품지 주소 id. null이면
|
|
242
|
+
.describe("반품지 주소 id. null이면 상점 기본 반품지를 따른다"),
|
|
243
243
|
}),
|
|
244
244
|
}),
|
|
245
245
|
z.object({ type: z.literal("MANUAL") }),
|
|
@@ -306,9 +306,7 @@ export const orderItemFulfillmentSnapshotSchema = z.discriminatedUnion("type", [
|
|
|
306
306
|
returnAddressId: z
|
|
307
307
|
.string()
|
|
308
308
|
.nullable()
|
|
309
|
-
.describe(
|
|
310
|
-
"주문 시점 반품지 주소 id. null이거나 지워진 주소면 스토어 기본 반품지로 안내한다",
|
|
311
|
-
),
|
|
309
|
+
.describe("주문 시점 반품지 주소 id. null이거나 지워진 주소면 상점 기본 반품지로 안내한다"),
|
|
312
310
|
}),
|
|
313
311
|
}),
|
|
314
312
|
z.object({ type: z.literal("MANUAL") }),
|
package/src/schemas/order.ts
CHANGED
|
@@ -328,7 +328,7 @@ export const dispatchRequestSchema = z
|
|
|
328
328
|
* 발송 시각. ISO 8601이고 시간대(`Z` 또는 `+09:00`)를 포함해야 한다. 생략하면 현재 시각이다.
|
|
329
329
|
*
|
|
330
330
|
* 전에는 `z.string()`이라 아무 문자열이나 그대로 저장됐다. 이 값은 배송 정보 jsonb에 들어가
|
|
331
|
-
* 자동 배송완료·추적 동기화 잡의 대상 선별 조건이 된다 — 잘못된 한 행이
|
|
331
|
+
* 자동 배송완료·추적 동기화 잡의 대상 선별 조건이 된다 — 잘못된 한 행이 상점 구분 없이 도는
|
|
332
332
|
* 잡 전체를 멈출 수 있어 입력에서 좁혔다. 검증을 통과한 값도 저장 전에 UTC ISO로 정규화한다.
|
|
333
333
|
*/
|
|
334
334
|
dispatchedAt: z.iso.datetime({ offset: true }).optional(),
|
package/src/schemas/payment.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { z } from "zod";
|
|
|
2
2
|
import { type PageParams, pageSchema } from "./common";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* 상점 결제(PG) 설정과 매출 리포트 계약.
|
|
6
6
|
*
|
|
7
|
-
* PG는 셀러가 직접 계약한다. 자격 증명은
|
|
7
|
+
* PG는 셀러가 직접 계약한다. 자격 증명은 상점별로 저장되고, 비밀 값(시크릿 키·API Secret·
|
|
8
8
|
* 웹훅 시크릿)은 저장 후 다시 조회할 수 없다. 응답에는 앞 4자리 마스킹만 나온다. 공개 값
|
|
9
9
|
* (클라이언트 키·Store ID·채널 키)은 원래 브라우저 결제창에 노출되는 값이라 그대로 돌려준다.
|
|
10
10
|
*/
|
|
@@ -53,7 +53,7 @@ const secretInput = () => z.string().trim().min(8).max(512);
|
|
|
53
53
|
/**
|
|
54
54
|
* 토스페이먼츠 결제위젯 키 접두사. 결제창은 API 개별 연동(`*_ck_`·`*_sk_`)만 쓴다. 위젯 키는 결제창 SDK가
|
|
55
55
|
* `NOT_SUPPORTED_WIDGET_KEY`로, 서버 API가 404 `NOT_FOUND_MERCHANT`로 거부하므로 저장 전에 막는다.
|
|
56
|
-
* 요청 스키마·api
|
|
56
|
+
* 요청 스키마·api 어댑터·상점 플랫폼 폼이 같은 상수를 쓴다(원천). `g` 플래그가 없어 `.test()`가 상태를 갖지 않는다.
|
|
57
57
|
*/
|
|
58
58
|
export const TOSS_WIDGET_CLIENT_KEY_PATTERN = /^(test|live)_gck_/;
|
|
59
59
|
/** 토스페이먼츠 결제위젯 시크릿 키 접두사. {@link TOSS_WIDGET_CLIENT_KEY_PATTERN} 참고 */
|
|
@@ -360,7 +360,7 @@ export const paymentProviderSchema = z.object({
|
|
|
360
360
|
.optional()
|
|
361
361
|
.default(null)
|
|
362
362
|
.describe(
|
|
363
|
-
"지금 새 결제의 후보가 아닌 사유. 후보면 null. 판정 원천은 서버 라우팅이다(
|
|
363
|
+
"지금 새 결제의 후보가 아닌 사유. 후보면 null. 판정 원천은 서버 라우팅이다(상점 플랫폼이 다시 계산하지 않는다). " +
|
|
364
364
|
"후보가 아닌 PG에는 항상 사유 코드를 붙인다(연결 안 된 PG 포함) — 모든 PG는 `candidates`에 있거나 이 값이 null이 아니다. 현재 값: " +
|
|
365
365
|
"`NOT_CONNECTED`(연결 안 됨) · `DISABLED`(사용 중지) · `CREDENTIAL_MISSING`(활성 환경 자격 증명 없음) · " +
|
|
366
366
|
"`SANDBOX_IN_LIVE_STORE`(다른 PG가 라이브라 이 샌드박스 PG는 실구매자 결제에 쓰지 않음) · " +
|
|
@@ -559,7 +559,9 @@ export const cashReceiptSchema = z.object({
|
|
|
559
559
|
canceledAt: z.string().nullable().describe("최근 취소 시각 (ISO 8601)"),
|
|
560
560
|
failCode: z.string().nullable().describe("마지막 실패 코드. 성공하면 비운다"),
|
|
561
561
|
failMessage: z.string().nullable().describe("마지막 실패 사유"),
|
|
562
|
-
reissuable: z
|
|
562
|
+
reissuable: z
|
|
563
|
+
.boolean()
|
|
564
|
+
.describe("상점 플랫폼 재발급으로 다시 시도할 수 있는가 (`FAILED`·`PENDING`)"),
|
|
563
565
|
requestedAt: z.string().describe("신청 시각 (ISO 8601)"),
|
|
564
566
|
updatedAt: z.string().describe("마지막 변경 시각 (ISO 8601)"),
|
|
565
567
|
});
|
package/src/schemas/product.ts
CHANGED
|
@@ -40,7 +40,7 @@ export const productSummarySchema = z.object({
|
|
|
40
40
|
.optional()
|
|
41
41
|
.default(false)
|
|
42
42
|
.describe(
|
|
43
|
-
"
|
|
43
|
+
"상점을 만들 때 넣은 샘플 상품인가(이슈 #70). 상점 플랫폼이 「샘플」 배지를 단다. 스토어프론트에는 싣지 않는다. 서버는 항상 싣는다",
|
|
44
44
|
),
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -112,7 +112,7 @@ export const MAX_ADDON_GROUPS = 5;
|
|
|
112
112
|
export const MAX_CUSTOM_INPUTS = 5;
|
|
113
113
|
/** 직접 입력 항목 최대 글자 수의 상한 */
|
|
114
114
|
export const MAX_CUSTOM_INPUT_LENGTH = 200;
|
|
115
|
-
/** 조합 수가 이 값 이상이면
|
|
115
|
+
/** 조합 수가 이 값 이상이면 상점 플랫폼이 경고한다(상한은 `MAX_VARIANTS`) */
|
|
116
116
|
export const VARIANT_COUNT_WARNING = 100;
|
|
117
117
|
|
|
118
118
|
/**
|
|
@@ -159,7 +159,7 @@ export const customInputSchema = z.object({
|
|
|
159
159
|
export type CustomInput = z.infer<typeof customInputSchema>;
|
|
160
160
|
|
|
161
161
|
/**
|
|
162
|
-
* 직접 입력 항목명에 쓸 수 없는 개인정보성 단어 —
|
|
162
|
+
* 직접 입력 항목명에 쓸 수 없는 개인정보성 단어 — 상점 플랫폼 검사와 서버 400(`CUSTOM_INPUT_LABEL_FORBIDDEN`)이 이 목록 하나를 쓴다.
|
|
163
163
|
* 구매자 개인정보는 주문 정보로 받는다. 직접 입력 항목으로 받으면 보관·파기 규칙 밖에 남는다.
|
|
164
164
|
*/
|
|
165
165
|
export const CUSTOM_INPUT_FORBIDDEN_WORDS = [
|
|
@@ -283,7 +283,15 @@ export const productSchema = productSummarySchema.extend({
|
|
|
283
283
|
* 표시는 `description`(HTML)이 맡는다 — 소비자는 이 필드를 몰라도 된다.
|
|
284
284
|
*/
|
|
285
285
|
descriptionJson: z.string().nullable().optional(),
|
|
286
|
-
originalPrice: z
|
|
286
|
+
originalPrice: z
|
|
287
|
+
.number()
|
|
288
|
+
.int()
|
|
289
|
+
.nullable()
|
|
290
|
+
.optional()
|
|
291
|
+
.describe(
|
|
292
|
+
"정가(할인 전 표시 가격). 원 단위. 비교용 표시 값이고 결제 금액 계산에 쓰지 않는다 — 구매자가 내는 가격은 " +
|
|
293
|
+
"즉시할인이 있으면 `discountedPrice`, 없으면 `salePrice`다. 스토어프론트 상품 응답에도 같은 값이 실린다. 정하지 않았으면 null",
|
|
294
|
+
),
|
|
287
295
|
images: z.array(z.url()).optional(),
|
|
288
296
|
attributes: z.array(z.object({ attributeId: z.string(), value: z.string() })).optional(),
|
|
289
297
|
optionGroups: z.array(optionGroupSchema),
|
|
@@ -314,7 +322,7 @@ export const productSchema = productSummarySchema.extend({
|
|
|
314
322
|
refundPolicy: productRefundPolicyPatchSchema
|
|
315
323
|
.nullable()
|
|
316
324
|
.describe(
|
|
317
|
-
"상품별 환불 정책 덮어쓰기(상품이 정한 항목만). null이면
|
|
325
|
+
"상품별 환불 정책 덮어쓰기(상품이 정한 항목만). null이면 상점 기본 환불 정책을 쓴다. " +
|
|
318
326
|
"반품·교환 배송비(`returnDeliveryFee`·`exchangeDeliveryFee`)는 배송 상품에만 있다",
|
|
319
327
|
),
|
|
320
328
|
/**
|
|
@@ -513,6 +521,9 @@ export const variantStockSchema = z.object({
|
|
|
513
521
|
|
|
514
522
|
export type VariantStock = z.infer<typeof variantStockSchema>;
|
|
515
523
|
|
|
524
|
+
/** 상품 쓰기 요청의 이미지 목록 — 등록·전체 수정·부분 수정이 같은 항목 형식을 쓴다. 첫 장이 대표(썸네일)다 */
|
|
525
|
+
const productImagesRequestSchema = z.array(productImageUrlSchema).min(1).max(MAX_PRODUCT_IMAGES);
|
|
526
|
+
|
|
516
527
|
/**
|
|
517
528
|
* 상품 쓰기 요청의 공통 필드 — 등록(POST)과 전체 수정(PUT)이 같다. 이행(`fulfillment`)만 두 요청의 스키마가 다르다.
|
|
518
529
|
*
|
|
@@ -528,24 +539,28 @@ const productWriteShape = {
|
|
|
528
539
|
.min(10)
|
|
529
540
|
.multipleOf(10, "판매가는 10원 단위여야 합니다 (INVALID_PRICE_UNIT)")
|
|
530
541
|
.describe("판매가. 원(KRW) 단위 정수, 최소 10원, 10원 단위"),
|
|
531
|
-
originalPrice: z
|
|
542
|
+
originalPrice: z
|
|
543
|
+
.number()
|
|
544
|
+
.int()
|
|
545
|
+
.optional()
|
|
546
|
+
.describe(
|
|
547
|
+
"정가(할인 전 표시 가격). 원 단위. 비교용 표시 값이고 결제 금액은 판매가(즉시할인이 있으면 할인 적용가)다. " +
|
|
548
|
+
"스토어프론트가 판매가보다 클 때 취소선·할인율로 보인다. 즉시할인은 `PUT /v1/products/{productId}/discount`로 따로 정한다",
|
|
549
|
+
),
|
|
532
550
|
description: z.string().optional(),
|
|
533
551
|
/**
|
|
534
552
|
* 상세설명 에디터(@avarlabs/editor)의 직렬화 상태. 재편집 때 이 값으로 문서를 복원한다.
|
|
535
553
|
* 표시는 `description`(HTML)이 맡는다 — 소비자는 이 필드를 몰라도 된다.
|
|
536
554
|
*/
|
|
537
555
|
descriptionJson: z.string().nullable().optional(),
|
|
538
|
-
images:
|
|
539
|
-
.array(productImageUrlSchema)
|
|
540
|
-
.min(1)
|
|
541
|
-
.max(MAX_PRODUCT_IMAGES)
|
|
556
|
+
images: productImagesRequestSchema
|
|
542
557
|
.optional()
|
|
543
558
|
.describe("상품 이미지 주소 목록. 첫 장이 대표 이미지(썸네일)다"),
|
|
544
559
|
attributes: z.array(z.object({ attributeId: z.string(), value: z.string() })).optional(),
|
|
545
560
|
refundPolicy: productRefundPolicyPatchSchema
|
|
546
561
|
.nullish()
|
|
547
562
|
.describe(
|
|
548
|
-
"상품별 환불 정책 — 준 항목만
|
|
563
|
+
"상품별 환불 정책 — 준 항목만 상점 기본을 덮어쓴다. `null`이나 생략은 상점 기본을 그대로 쓴다는 뜻이다. " +
|
|
549
564
|
"반품·교환 배송비(`returnDeliveryFee`·`exchangeDeliveryFee`)는 배송 상품에만 줄 수 있다(다른 유형은 400)",
|
|
550
565
|
),
|
|
551
566
|
...optionModelRequestSchema.shape,
|
|
@@ -589,12 +604,17 @@ export const productPatchRequestSchema = z
|
|
|
589
604
|
name: z.string().max(100).optional(),
|
|
590
605
|
salePrice: z.number().int().multipleOf(10).optional(),
|
|
591
606
|
status: productStatusSchema.optional(),
|
|
607
|
+
images: productImagesRequestSchema
|
|
608
|
+
.optional()
|
|
609
|
+
.describe(
|
|
610
|
+
"상품 이미지 주소 목록. 주면 통째로 바꾼다(현재 목록과 병합하지 않는다). 첫 장이 대표 이미지(썸네일)다. 생략하면 바꾸지 않는다",
|
|
611
|
+
),
|
|
592
612
|
fulfillment: productFulfillmentUpdateSchema.optional().describe(fulfillmentUpdateDescription),
|
|
593
613
|
refundPolicy: productRefundPolicyPatchSchema
|
|
594
614
|
.nullable()
|
|
595
615
|
.optional()
|
|
596
616
|
.describe(
|
|
597
|
-
"상품별 환불 정책. 주면 통째로 바꾼다(준 항목만
|
|
617
|
+
"상품별 환불 정책. 주면 통째로 바꾼다(준 항목만 상점 기본을 덮어쓴다). `null`은 덮어쓰기를 지워 상점 기본을 따른다. 생략하면 바꾸지 않는다",
|
|
598
618
|
),
|
|
599
619
|
})
|
|
600
620
|
.refine((value) => Object.values(value).some((v) => v !== undefined), {
|
package/src/schemas/promotion.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { proratedRefundBase } from "./refund-policy";
|
|
|
16
16
|
* 4. 배송비 쿠폰(`DELIVERY`) — 주문에 한 장. 기본 배송비(지역 추가 배송비 제외)를 기준으로 할인하고
|
|
17
17
|
* 배송 묶음에 기본 배송비 비율로 나눈다.
|
|
18
18
|
*
|
|
19
|
-
* 무료배송 기준(상품별 조건부
|
|
19
|
+
* 무료배송 기준(상품별 조건부 무료·상점 전체 기준)은 **쿠폰 차감 전 금액**으로 판정한다. 그래서 배송비
|
|
20
20
|
* 견적(`quoteDelivery`)은 쿠폰과 무관하게 먼저 계산되고, 이 함수는 그 결과만 입력으로 받는다.
|
|
21
21
|
*
|
|
22
22
|
* 최소 주문 금액은 대상 상품의 **즉시할인 반영 합계**(쿠폰 차감 전, 배송비 제외)로 판정한다.
|
|
@@ -34,7 +34,7 @@ const descriptions = {
|
|
|
34
34
|
"정률 할인의 최대 할인액(원). null이면 상한이 없다. 정액 할인에는 쓰지 않는다(null)",
|
|
35
35
|
minOrderAmount:
|
|
36
36
|
"최소 주문 금액(원). 대상 상품의 즉시할인 반영 합계(배송비 제외)가 이 값 이상이어야 쓸 수 있다. 0이면 조건이 없다",
|
|
37
|
-
firstOrderOnly: "이
|
|
37
|
+
firstOrderOnly: "이 상점에서 결제 완료 주문이 없는 회원만 쓸 수 있다",
|
|
38
38
|
memberOnly:
|
|
39
39
|
"회원만 쓸 수 있다. false면 비회원도 쓸 수 있다 — 코드 입력형이면서 1인 한도가 없는 쿠폰만 false로 둘 수 있다",
|
|
40
40
|
stackWithInstantDiscount:
|
|
@@ -224,7 +224,7 @@ export interface PricingInput {
|
|
|
224
224
|
coupons: readonly PricingCoupon[];
|
|
225
225
|
/** 배송비 견적. 없으면 배송비 0으로 계산하고 배송비 쿠폰은 할인할 금액이 없다 */
|
|
226
226
|
delivery?: PricingDelivery | null;
|
|
227
|
-
/** 이
|
|
227
|
+
/** 이 상점에서 결제 완료 주문이 없는 회원인가 */
|
|
228
228
|
firstOrder: boolean;
|
|
229
229
|
/** 회원 주문인가 */
|
|
230
230
|
isMember: boolean;
|
|
@@ -635,7 +635,7 @@ export interface CouponSettlementInput {
|
|
|
635
635
|
coupons: readonly AppliedCouponSnapshot[];
|
|
636
636
|
lines: readonly CouponSettlementLine[];
|
|
637
637
|
settlements: Readonly<Record<string, Partial<CouponSettlementState> | undefined>>;
|
|
638
|
-
/**
|
|
638
|
+
/** 상점 환불 정책 `couponShortfallCharge` */
|
|
639
639
|
shortfallCharge: boolean;
|
|
640
640
|
/** 판매자 귀책(직권취소, 불량·오배송 반품) — 미달분을 청구하지 않는다 */
|
|
641
641
|
sellerFault: boolean;
|