@sayren/store-sdk 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +72 -72
- package/dist/index.mjs +160 -160
- package/dist/server.mjs +1 -1
- package/dist/{webhook-headers-D974dE4T.mjs → webhook-headers-CTez3xDY.mjs} +81 -3
- package/package.json +1 -1
- package/src/client.ts +1 -1
- package/src/http.ts +1 -1
- package/src/oauth.ts +1 -1
- package/src/schemas/ai.ts +40 -33
- package/src/schemas/analytics.ts +1 -1
- package/src/schemas/api-log.ts +1 -1
- package/src/schemas/app-manifest.ts +1 -1
- package/src/schemas/apps.ts +19 -16
- package/src/schemas/audit.ts +7 -4
- package/src/schemas/auth.ts +1 -1
- package/src/schemas/category.ts +37 -32
- package/src/schemas/claim.ts +15 -12
- package/src/schemas/collection.ts +76 -65
- package/src/schemas/commerce-flow.ts +107 -86
- package/src/schemas/commerce-number.ts +1 -1
- package/src/schemas/common.ts +1 -1
- package/src/schemas/coupon.ts +88 -80
- package/src/schemas/currency-prices.ts +1 -1
- package/src/schemas/currency.ts +1 -1
- package/src/schemas/customer-delegation.ts +31 -28
- package/src/schemas/customer.ts +10 -7
- package/src/schemas/fulfillment.ts +1 -1
- package/src/schemas/inventory.ts +29 -24
- package/src/schemas/locale.ts +1 -1
- package/src/schemas/login.ts +18 -11
- package/src/schemas/market.ts +1 -1
- package/src/schemas/misc.ts +7 -4
- package/src/schemas/order.ts +70 -59
- package/src/schemas/payment.ts +108 -89
- package/src/schemas/plans.ts +1 -1
- package/src/schemas/points.ts +33 -28
- package/src/schemas/product-info.ts +13 -10
- package/src/schemas/product-localization.ts +1 -1
- package/src/schemas/product.ts +18 -15
- package/src/schemas/promotion.ts +1 -1
- package/src/schemas/refund-policy.ts +1 -1
- package/src/schemas/saved-option-set.ts +6 -6
- package/src/schemas/scope.ts +1 -1
- package/src/schemas/shipping-policy.ts +1 -1
- package/src/schemas/starter.ts +16 -13
- package/src/schemas/stay.ts +16 -13
- package/src/schemas/store.ts +130 -114
- package/src/schemas/storefront-hosting.ts +272 -220
- package/src/schemas/strict.ts +84 -0
- package/src/schemas/subscription.ts +1 -1
- package/src/schemas/telemetry.ts +1 -1
- package/src/schemas/upload.ts +16 -13
- package/src/schemas/webhook-event.ts +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import { claimStatusSchema } from "./claim";
|
|
3
3
|
import { orderItemStatusSchema } from "./common";
|
|
4
|
+
import { strictRequest } from "./strict";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 커머스 플로우(이슈 #115) — 주문상품이 따르는 플로우의 지금 상태와 할 수 있는 행동. 공개 계약은 필드 추가로만 자란다.
|
|
@@ -146,10 +147,12 @@ export const orderItemFlowSchema = z.object({
|
|
|
146
147
|
export type OrderItemFlow = z.infer<typeof orderItemFlowSchema>;
|
|
147
148
|
|
|
148
149
|
/** 상태 직접 이동(#115) — 연결선을 지나지 않고 다른 상태로 옮긴다. 블록(재고·환불·송장)은 실행하지 않는다 */
|
|
149
|
-
export const moveFlowStateRequestSchema =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
export const moveFlowStateRequestSchema = strictRequest(
|
|
151
|
+
z.object({
|
|
152
|
+
to: z.string().min(1).describe("옮길 상태 키(`moveTargets[].key`)"),
|
|
153
|
+
reason: z.string().trim().max(200).optional().describe("옮긴 사유(실행 기록에 남는다)"),
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
153
156
|
export type MoveFlowStateRequest = z.infer<typeof moveFlowStateRequestSchema>;
|
|
154
157
|
|
|
155
158
|
export const flowRunStepSchema = z.object({
|
|
@@ -295,9 +298,11 @@ export type CommerceFlow = z.infer<typeof commerceFlowSchema>;
|
|
|
295
298
|
export type CommerceFlowVersionView = z.infer<typeof commerceFlowVersionViewSchema>;
|
|
296
299
|
|
|
297
300
|
/** 진행 중 주문을 활성 버전으로 옮기기(#115) — 한 번에 `limit`개까지. 남으면 다시 부른다 */
|
|
298
|
-
export const migrateFlowVersionRequestSchema =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
+
export const migrateFlowVersionRequestSchema = strictRequest(
|
|
302
|
+
z.object({
|
|
303
|
+
limit: z.number().int().min(1).max(500).optional().describe("한 번에 옮길 흐름 수(기본 100)"),
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
301
306
|
export type MigrateFlowVersionRequest = z.infer<typeof migrateFlowVersionRequestSchema>;
|
|
302
307
|
|
|
303
308
|
export const migrateFlowVersionResultSchema = z.object({
|
|
@@ -321,40 +326,44 @@ export const migrateFlowVersionResultSchema = z.object({
|
|
|
321
326
|
});
|
|
322
327
|
export type MigrateFlowVersionResult = z.infer<typeof migrateFlowVersionResultSchema>;
|
|
323
328
|
|
|
324
|
-
export const createCommerceFlowRequestSchema =
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
329
|
+
export const createCommerceFlowRequestSchema = strictRequest(
|
|
330
|
+
z.object({
|
|
331
|
+
key: z
|
|
332
|
+
.string()
|
|
333
|
+
.regex(/^[a-z][a-z0-9-]{1,48}$/)
|
|
334
|
+
.describe("상점 안 유일한 키(소문자·숫자·하이픈)"),
|
|
335
|
+
name: z.string().min(1).max(60).optional(),
|
|
336
|
+
presetKey: z.string().optional().describe("출발한 프리셋(출처로 남는다). 없으면 빈 플로우"),
|
|
337
|
+
graph: flowDocumentSchema
|
|
338
|
+
.optional()
|
|
339
|
+
.describe(
|
|
340
|
+
"편집기에서 고친 그래프(#115). 없으면 프리셋 그래프 그대로, 프리셋도 없으면 빈 흐름(결제 완료 노드 하나)이다",
|
|
341
|
+
),
|
|
342
|
+
definition: flowDocumentSchema
|
|
343
|
+
.optional()
|
|
344
|
+
.describe(
|
|
345
|
+
"흐름 설정 `requires`(progression·groupClaim·fulfillmentMethods). 없으면 프리셋의 설정",
|
|
346
|
+
),
|
|
347
|
+
variables: z
|
|
348
|
+
.record(z.string(), z.unknown())
|
|
349
|
+
.optional()
|
|
350
|
+
.describe("쓰지 않는다(무시). 값은 그래프의 노드 설정 안에 있다(#115)"),
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
346
353
|
export type CreateCommerceFlowRequest = z.infer<typeof createCommerceFlowRequestSchema>;
|
|
347
354
|
|
|
348
|
-
export const saveFlowDraftRequestSchema =
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
export const saveFlowDraftRequestSchema = strictRequest(
|
|
356
|
+
z.object({
|
|
357
|
+
graph: flowDocumentSchema,
|
|
358
|
+
definition: flowDocumentSchema
|
|
359
|
+
.optional()
|
|
360
|
+
.describe("흐름 설정 `requires`(progression·groupClaim·fulfillmentMethods)"),
|
|
361
|
+
variables: z
|
|
362
|
+
.record(z.string(), z.unknown())
|
|
363
|
+
.optional()
|
|
364
|
+
.describe("쓰지 않는다(무시). 값은 그래프의 노드 설정 안에 있다(#115)"),
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
358
367
|
export type SaveFlowDraftRequest = z.infer<typeof saveFlowDraftRequestSchema>;
|
|
359
368
|
|
|
360
369
|
export const flowIssueSchema = z.object({
|
|
@@ -475,14 +484,16 @@ export const flowDraftOperationSchema = z.discriminatedUnion("op", [
|
|
|
475
484
|
]);
|
|
476
485
|
export type FlowDraftOperation = z.infer<typeof flowDraftOperationSchema>;
|
|
477
486
|
|
|
478
|
-
export const applyFlowOperationsRequestSchema =
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
487
|
+
export const applyFlowOperationsRequestSchema = strictRequest(
|
|
488
|
+
z.object({
|
|
489
|
+
operations: z
|
|
490
|
+
.array(flowDraftOperationSchema)
|
|
491
|
+
.min(1)
|
|
492
|
+
.describe(
|
|
493
|
+
"차례대로 적용한다. 묶음은 원자적이다 — 하나라도 형식이 틀리면 아무것도 바꾸지 않는다(400)",
|
|
494
|
+
),
|
|
495
|
+
}),
|
|
496
|
+
);
|
|
486
497
|
export type ApplyFlowOperationsRequest = z.infer<typeof applyFlowOperationsRequestSchema>;
|
|
487
498
|
|
|
488
499
|
export const flowOperationsResultSchema = z.object({
|
|
@@ -533,17 +544,19 @@ export const flowSecretSchema = z.object({
|
|
|
533
544
|
});
|
|
534
545
|
export type FlowSecret = z.infer<typeof flowSecretSchema>;
|
|
535
546
|
|
|
536
|
-
export const saveFlowSecretRequestSchema =
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
+
export const saveFlowSecretRequestSchema = strictRequest(
|
|
548
|
+
z.object({
|
|
549
|
+
url: z
|
|
550
|
+
.url()
|
|
551
|
+
.refine((value) => value.startsWith("https://"), { message: "https 주소만 받습니다" })
|
|
552
|
+
.describe("요청 주소. https만, 사설·루프백 주소는 400 `FLOW_SECRET_URL_INVALID`"),
|
|
553
|
+
headers: z
|
|
554
|
+
.record(z.string().regex(/^[A-Za-z0-9-]{1,64}$/), z.string().max(2000))
|
|
555
|
+
.refine((value) => Object.keys(value).length <= 10, { message: "헤더는 10개까지입니다" })
|
|
556
|
+
.default({})
|
|
557
|
+
.describe("함께 보낼 헤더(인증 등). 값은 암호화해 두고 다시 보여 주지 않는다"),
|
|
558
|
+
}),
|
|
559
|
+
);
|
|
547
560
|
export type SaveFlowSecretRequest = z.input<typeof saveFlowSecretRequestSchema>;
|
|
548
561
|
|
|
549
562
|
/**
|
|
@@ -584,22 +597,24 @@ const simulationEventSchema = z.object({
|
|
|
584
597
|
key: z.string().optional().describe("행동·사건·타이머 키(start는 없음)"),
|
|
585
598
|
});
|
|
586
599
|
|
|
587
|
-
export const simulateFlowRequestSchema =
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
z.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
600
|
+
export const simulateFlowRequestSchema = strictRequest(
|
|
601
|
+
z.object({
|
|
602
|
+
startAt: z.iso.datetime({ offset: true }).optional().describe("가상 시계 시작(기본: 지금)"),
|
|
603
|
+
context: z
|
|
604
|
+
.record(z.string(), z.unknown())
|
|
605
|
+
.optional()
|
|
606
|
+
.describe("식이 읽을 값(item·order·buyer·product). 비우면 견본 주문 값"),
|
|
607
|
+
steps: z
|
|
608
|
+
.array(
|
|
609
|
+
z.union([
|
|
610
|
+
z.object({ event: simulationEventSchema }),
|
|
611
|
+
z.object({ advance: z.string().regex(/^P/).describe("시간 경과(ISO 8601 기간)") }),
|
|
612
|
+
]),
|
|
613
|
+
)
|
|
614
|
+
.min(1)
|
|
615
|
+
.max(100),
|
|
616
|
+
}),
|
|
617
|
+
);
|
|
603
618
|
export type SimulateFlowRequest = z.infer<typeof simulateFlowRequestSchema>;
|
|
604
619
|
|
|
605
620
|
export const simulationStepSchema = z.object({
|
|
@@ -628,9 +643,13 @@ export const simulateFlowResultSchema = z.object({
|
|
|
628
643
|
});
|
|
629
644
|
export type SimulateFlowResult = z.infer<typeof simulateFlowResultSchema>;
|
|
630
645
|
|
|
631
|
-
export const replayFlowRequestSchema =
|
|
632
|
-
|
|
633
|
-
|
|
646
|
+
export const replayFlowRequestSchema = strictRequest(
|
|
647
|
+
z.object({
|
|
648
|
+
orderItemId: z
|
|
649
|
+
.string()
|
|
650
|
+
.describe("재생할 주문상품 — 그 흐름의 실행 기록을 초안으로 다시 돌린다"),
|
|
651
|
+
}),
|
|
652
|
+
);
|
|
634
653
|
export type ReplayFlowRequest = z.infer<typeof replayFlowRequestSchema>;
|
|
635
654
|
|
|
636
655
|
export const replayFlowResultSchema = z.object({
|
|
@@ -861,12 +880,14 @@ export const flowEditSessionViewSchema = z.object({
|
|
|
861
880
|
});
|
|
862
881
|
export type FlowEditSessionView = z.infer<typeof flowEditSessionViewSchema>;
|
|
863
882
|
|
|
864
|
-
export const claimFlowEditSessionRequestSchema =
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
883
|
+
export const claimFlowEditSessionRequestSchema = strictRequest(
|
|
884
|
+
z.object({
|
|
885
|
+
takeover: z
|
|
886
|
+
.boolean()
|
|
887
|
+
.optional()
|
|
888
|
+
.describe(
|
|
889
|
+
"다른 주체가 잡고 있어도 넘겨받는다. 앞 주체의 저장하지 않은 수정은 사라지고, AI 도우미면 그 실행이 멈춘다",
|
|
890
|
+
),
|
|
891
|
+
}),
|
|
892
|
+
);
|
|
872
893
|
export type ClaimFlowEditSessionRequest = z.infer<typeof claimFlowEditSessionRequestSchema>;
|
package/src/schemas/common.ts
CHANGED
package/src/schemas/coupon.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import { pageQueryShape } from "./common";
|
|
3
3
|
import { currencyCodeSchema } from "./currency";
|
|
4
4
|
import {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
couponKindSchema,
|
|
10
10
|
couponTargetSchema,
|
|
11
11
|
} from "./promotion";
|
|
12
|
+
import { strictRequest } from "./strict";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* 쿠폰 관리 API 계약 (이슈 #47) — `/v1/coupons/**`, 스코프 `promotion:r|rw`.
|
|
@@ -86,93 +87,100 @@ const couponTimeField = (description: string) =>
|
|
|
86
87
|
z.iso.datetime({ offset: true }).nullable().optional().describe(description);
|
|
87
88
|
|
|
88
89
|
/** `POST /v1/coupons` */
|
|
89
|
-
export const createCouponRequestSchema =
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
export const createCouponRequestSchema = strictRequest(
|
|
91
|
+
z
|
|
92
|
+
.strictObject({
|
|
93
|
+
name: z.string().trim().min(1).max(60).describe(descriptions.name),
|
|
94
|
+
description: z.string().max(300).nullable().optional().describe(descriptions.description),
|
|
95
|
+
kind: couponKindSchema,
|
|
96
|
+
benefit: couponBenefitSchema,
|
|
97
|
+
conditions: couponConditionsInputSchema
|
|
98
|
+
.optional()
|
|
99
|
+
.describe("사용 조건. 생략한 항목은 기본값(최소 금액 0, 회원 전용, 즉시할인과 중복 가능)"),
|
|
100
|
+
target: couponTargetInputSchema.optional().describe("대상 상품·카테고리. 생략하면 전 상품"),
|
|
101
|
+
issueMethod: couponIssueMethodSchema,
|
|
102
|
+
code: z.string().regex(COUPON_CODE_PATTERN).optional().describe(descriptions.code),
|
|
103
|
+
startsAt: couponTimeField(descriptions.startsAt),
|
|
104
|
+
endsAt: couponTimeField(descriptions.endsAt),
|
|
105
|
+
validDaysAfterIssue: z
|
|
106
|
+
.number()
|
|
107
|
+
.int()
|
|
108
|
+
.min(1)
|
|
109
|
+
.max(3650)
|
|
110
|
+
.nullable()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe(descriptions.validDaysAfterIssue),
|
|
113
|
+
totalIssueLimit: limitField(descriptions.totalIssueLimit),
|
|
114
|
+
totalUseLimit: limitField(descriptions.totalUseLimit),
|
|
115
|
+
perMemberIssueLimit: limitField(descriptions.perMemberIssueLimit),
|
|
116
|
+
perMemberUseLimit: limitField(descriptions.perMemberUseLimit),
|
|
117
|
+
currency: currencyCodeSchema
|
|
118
|
+
.optional()
|
|
119
|
+
.describe(
|
|
120
|
+
"쿠폰 통화(이슈 #130). 정액·최대 할인액·최소 주문 금액이 이 통화 값이고 같은 통화 주문서에만 적용된다. 생략하면 기본 통화(KRW). 판매 통화여야 하고 만든 뒤에는 바꿀 수 없다",
|
|
121
|
+
),
|
|
122
|
+
})
|
|
123
|
+
.superRefine((body, ctx) => {
|
|
124
|
+
const issue = (path: string, message: string) =>
|
|
125
|
+
ctx.addIssue({ code: "custom", path: [path], message });
|
|
126
|
+
if (body.issueMethod === "CODE" && !body.code)
|
|
127
|
+
issue("code", "코드 입력형 쿠폰은 코드가 필요합니다");
|
|
128
|
+
if (body.issueMethod !== "CODE" && body.code)
|
|
129
|
+
issue("code", "코드는 코드 입력형 쿠폰에만 둡니다");
|
|
130
|
+
if (body.issueMethod === "CODE" && body.validDaysAfterIssue != null) {
|
|
131
|
+
issue("validDaysAfterIssue", "발급 후 유효 일수는 셀러 지급 쿠폰에만 둡니다");
|
|
132
|
+
}
|
|
133
|
+
if (body.issueMethod === "CODE" && body.perMemberIssueLimit != null) {
|
|
134
|
+
issue("perMemberIssueLimit", "1인 발급 수 한도는 발급형 쿠폰에만 둡니다");
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
body.issueMethod === "SIGNUP" &&
|
|
138
|
+
body.perMemberIssueLimit != null &&
|
|
139
|
+
body.perMemberIssueLimit !== 1
|
|
140
|
+
) {
|
|
141
|
+
issue("perMemberIssueLimit", "회원가입 쿠폰은 1인 1장입니다");
|
|
142
|
+
}
|
|
143
|
+
if (body.issueMethod !== "CODE" && body.perMemberUseLimit != null) {
|
|
144
|
+
issue(
|
|
145
|
+
"perMemberUseLimit",
|
|
146
|
+
"발급형 쿠폰은 발급 한 장이 한 번 쓰입니다. 1인 발급 수 한도를 사용하십시오",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const memberOnly = body.conditions?.memberOnly ?? COUPON_CONDITIONS_DEFAULTS.memberOnly;
|
|
150
|
+
if (!memberOnly && (body.issueMethod !== "CODE" || body.perMemberUseLimit != null)) {
|
|
151
|
+
issue(
|
|
152
|
+
"conditions",
|
|
153
|
+
"비회원에게 여는 쿠폰은 코드 입력형이면서 1인 사용 한도가 없어야 합니다",
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (body.startsAt && body.endsAt && Date.parse(body.endsAt) <= Date.parse(body.startsAt)) {
|
|
157
|
+
issue("endsAt", "종료 시각은 시작 시각보다 뒤여야 합니다");
|
|
158
|
+
}
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
export type CreateCouponRequest = z.infer<typeof createCouponRequestSchema>;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* `PATCH /v1/coupons/{couponId}` — 준 항목만 바뀐다. 발급·사용이 시작된 쿠폰(`locked`)은 이름·설명·종료일 연장·
|
|
165
|
+
* 한도 상향만 받고, 그 밖의 항목이 현재 값과 다르면 409 `COUPON_LOCKED`다. 종류·발급 방식은 바꿀 수 없다.
|
|
166
|
+
*/
|
|
167
|
+
export const updateCouponRequestSchema = strictRequest(
|
|
168
|
+
z.strictObject({
|
|
169
|
+
name: z.string().trim().min(1).max(60).optional().describe(descriptions.name),
|
|
92
170
|
description: z.string().max(300).nullable().optional().describe(descriptions.description),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
.optional()
|
|
97
|
-
.describe("사용 조건. 생략한 항목은 기본값(최소 금액 0, 회원 전용, 즉시할인과 중복 가능)"),
|
|
98
|
-
target: couponTargetInputSchema.optional().describe("대상 상품·카테고리. 생략하면 전 상품"),
|
|
99
|
-
issueMethod: couponIssueMethodSchema,
|
|
171
|
+
benefit: couponBenefitSchema.optional(),
|
|
172
|
+
conditions: couponConditionsInputSchema.optional(),
|
|
173
|
+
target: couponTargetInputSchema.optional(),
|
|
100
174
|
code: z.string().regex(COUPON_CODE_PATTERN).optional().describe(descriptions.code),
|
|
101
175
|
startsAt: couponTimeField(descriptions.startsAt),
|
|
102
176
|
endsAt: couponTimeField(descriptions.endsAt),
|
|
103
|
-
validDaysAfterIssue: z
|
|
104
|
-
.number()
|
|
105
|
-
.int()
|
|
106
|
-
.min(1)
|
|
107
|
-
.max(3650)
|
|
108
|
-
.nullable()
|
|
109
|
-
.optional()
|
|
110
|
-
.describe(descriptions.validDaysAfterIssue),
|
|
177
|
+
validDaysAfterIssue: z.number().int().min(1).max(3650).nullable().optional(),
|
|
111
178
|
totalIssueLimit: limitField(descriptions.totalIssueLimit),
|
|
112
179
|
totalUseLimit: limitField(descriptions.totalUseLimit),
|
|
113
180
|
perMemberIssueLimit: limitField(descriptions.perMemberIssueLimit),
|
|
114
181
|
perMemberUseLimit: limitField(descriptions.perMemberUseLimit),
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.describe(
|
|
118
|
-
"쿠폰 통화(이슈 #130). 정액·최대 할인액·최소 주문 금액이 이 통화 값이고 같은 통화 주문서에만 적용된다. 생략하면 기본 통화(KRW). 판매 통화여야 하고 만든 뒤에는 바꿀 수 없다",
|
|
119
|
-
),
|
|
120
|
-
})
|
|
121
|
-
.superRefine((body, ctx) => {
|
|
122
|
-
const issue = (path: string, message: string) =>
|
|
123
|
-
ctx.addIssue({ code: "custom", path: [path], message });
|
|
124
|
-
if (body.issueMethod === "CODE" && !body.code)
|
|
125
|
-
issue("code", "코드 입력형 쿠폰은 코드가 필요합니다");
|
|
126
|
-
if (body.issueMethod !== "CODE" && body.code)
|
|
127
|
-
issue("code", "코드는 코드 입력형 쿠폰에만 둡니다");
|
|
128
|
-
if (body.issueMethod === "CODE" && body.validDaysAfterIssue != null) {
|
|
129
|
-
issue("validDaysAfterIssue", "발급 후 유효 일수는 셀러 지급 쿠폰에만 둡니다");
|
|
130
|
-
}
|
|
131
|
-
if (body.issueMethod === "CODE" && body.perMemberIssueLimit != null) {
|
|
132
|
-
issue("perMemberIssueLimit", "1인 발급 수 한도는 발급형 쿠폰에만 둡니다");
|
|
133
|
-
}
|
|
134
|
-
if (
|
|
135
|
-
body.issueMethod === "SIGNUP" &&
|
|
136
|
-
body.perMemberIssueLimit != null &&
|
|
137
|
-
body.perMemberIssueLimit !== 1
|
|
138
|
-
) {
|
|
139
|
-
issue("perMemberIssueLimit", "회원가입 쿠폰은 1인 1장입니다");
|
|
140
|
-
}
|
|
141
|
-
if (body.issueMethod !== "CODE" && body.perMemberUseLimit != null) {
|
|
142
|
-
issue(
|
|
143
|
-
"perMemberUseLimit",
|
|
144
|
-
"발급형 쿠폰은 발급 한 장이 한 번 쓰입니다. 1인 발급 수 한도를 사용하십시오",
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
const memberOnly = body.conditions?.memberOnly ?? COUPON_CONDITIONS_DEFAULTS.memberOnly;
|
|
148
|
-
if (!memberOnly && (body.issueMethod !== "CODE" || body.perMemberUseLimit != null)) {
|
|
149
|
-
issue("conditions", "비회원에게 여는 쿠폰은 코드 입력형이면서 1인 사용 한도가 없어야 합니다");
|
|
150
|
-
}
|
|
151
|
-
if (body.startsAt && body.endsAt && Date.parse(body.endsAt) <= Date.parse(body.startsAt)) {
|
|
152
|
-
issue("endsAt", "종료 시각은 시작 시각보다 뒤여야 합니다");
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
export type CreateCouponRequest = z.infer<typeof createCouponRequestSchema>;
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* `PATCH /v1/coupons/{couponId}` — 준 항목만 바뀐다. 발급·사용이 시작된 쿠폰(`locked`)은 이름·설명·종료일 연장·
|
|
159
|
-
* 한도 상향만 받고, 그 밖의 항목이 현재 값과 다르면 409 `COUPON_LOCKED`다. 종류·발급 방식은 바꿀 수 없다.
|
|
160
|
-
*/
|
|
161
|
-
export const updateCouponRequestSchema = z.strictObject({
|
|
162
|
-
name: z.string().trim().min(1).max(60).optional().describe(descriptions.name),
|
|
163
|
-
description: z.string().max(300).nullable().optional().describe(descriptions.description),
|
|
164
|
-
benefit: couponBenefitSchema.optional(),
|
|
165
|
-
conditions: couponConditionsInputSchema.optional(),
|
|
166
|
-
target: couponTargetInputSchema.optional(),
|
|
167
|
-
code: z.string().regex(COUPON_CODE_PATTERN).optional().describe(descriptions.code),
|
|
168
|
-
startsAt: couponTimeField(descriptions.startsAt),
|
|
169
|
-
endsAt: couponTimeField(descriptions.endsAt),
|
|
170
|
-
validDaysAfterIssue: z.number().int().min(1).max(3650).nullable().optional(),
|
|
171
|
-
totalIssueLimit: limitField(descriptions.totalIssueLimit),
|
|
172
|
-
totalUseLimit: limitField(descriptions.totalUseLimit),
|
|
173
|
-
perMemberIssueLimit: limitField(descriptions.perMemberIssueLimit),
|
|
174
|
-
perMemberUseLimit: limitField(descriptions.perMemberUseLimit),
|
|
175
|
-
});
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
176
184
|
export type UpdateCouponRequest = z.infer<typeof updateCouponRequestSchema>;
|
|
177
185
|
|
|
178
186
|
/** 쿠폰 응답 */
|
package/src/schemas/currency.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { strictRequest } from "./strict";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 구매자 위임 REST(`POST /v1/customers/delegations`, #71) — 계정 토큰(또는 관리 API 토큰) + `X-Store-Id`로 부른다(`customer:rw`).
|
|
@@ -8,33 +9,35 @@ import { z } from "zod";
|
|
|
8
9
|
|
|
9
10
|
const uidPattern = /^[A-Za-z0-9._:@|-]{1,128}$/;
|
|
10
11
|
|
|
11
|
-
export const customerDelegationRequestSchema =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
12
|
+
export const customerDelegationRequestSchema = strictRequest(
|
|
13
|
+
z
|
|
14
|
+
.object({
|
|
15
|
+
uid: z
|
|
16
|
+
.string()
|
|
17
|
+
.regex(uidPattern)
|
|
18
|
+
.optional()
|
|
19
|
+
.describe(
|
|
20
|
+
"셀러 자체 인증의 사용자 id(1~128자, 영문·숫자·`._:@|-`). 처음 보는 id면 구매자를 만든다",
|
|
21
|
+
),
|
|
22
|
+
email: z
|
|
23
|
+
.email()
|
|
24
|
+
.max(254)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("uid가 없으면 이메일로 지목한다. uid와 함께 주면 처음 만들 때의 이메일이다"),
|
|
27
|
+
name: z.string().max(50).optional().describe("처음 만들 때의 이름"),
|
|
28
|
+
authenticatedAt: z.iso
|
|
29
|
+
.datetime({ offset: true })
|
|
30
|
+
.optional()
|
|
31
|
+
.describe(
|
|
32
|
+
"사용자가 셀러 서비스에서 실제로 로그인한 시각(uid 위임만). 주면 구매자 토큰의 인증 시각이 된다",
|
|
33
|
+
),
|
|
34
|
+
})
|
|
35
|
+
.refine((value) => Boolean(value.uid || value.email), {
|
|
36
|
+
message: "uid 또는 email이 필요합니다",
|
|
37
|
+
path: ["uid"],
|
|
38
|
+
})
|
|
39
|
+
.describe("구매자 위임 요청"),
|
|
40
|
+
);
|
|
38
41
|
export type CustomerDelegationRequest = z.infer<typeof customerDelegationRequestSchema>;
|
|
39
42
|
|
|
40
43
|
export const customerDelegationSchema = z
|
package/src/schemas/customer.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import { orderItemStatusSchema, pageQueryShape } from "./common";
|
|
3
|
+
import { strictRequest } from "./strict";
|
|
3
4
|
|
|
4
5
|
/** 구매자 목록 정렬 */
|
|
5
6
|
export const customerSortSchema = z
|
|
@@ -198,12 +199,14 @@ export const customerOrderSchema = z.object({
|
|
|
198
199
|
export type CustomerOrder = z.infer<typeof customerOrderSchema>;
|
|
199
200
|
|
|
200
201
|
/** `POST /v1/customers/{id}/withdraw` 본문 */
|
|
201
|
-
export const withdrawCustomerRequestSchema =
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
202
|
+
export const withdrawCustomerRequestSchema = strictRequest(
|
|
203
|
+
z.object({
|
|
204
|
+
reason: z
|
|
205
|
+
.enum(["BUYER_REQUEST", "DORMANT", "ABUSE", "OTHER"])
|
|
206
|
+
.describe("처리 사유 — 구매자 요청·장기 미이용·부정 이용·기타. 감사 로그에 남는다"),
|
|
207
|
+
memo: z.string().max(200).optional().describe("사유 보충(선택)"),
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
207
210
|
export type WithdrawCustomerRequest = z.infer<typeof withdrawCustomerRequestSchema>;
|
|
208
211
|
|
|
209
212
|
/** `WITHDRAWAL_BLOCKED`의 `error.details` — 탈퇴를 막는 항목 수(스토어프론트 탈퇴와 같다) */
|
package/src/schemas/inventory.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { strictRequest } from "./strict";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 위치별 재고 (이슈 #132) — 재고 위치(창고·상설 매장·팝업 매장)마다 조합의 가용 재고를 따로 센다. 출고지·반품지·픽업 장소가 위치를
|
|
@@ -202,29 +203,33 @@ export const inventoryOperationResultSchema = z.object({
|
|
|
202
203
|
export type InventoryOperationResult = z.infer<typeof inventoryOperationResultSchema>;
|
|
203
204
|
|
|
204
205
|
/** 수거 완료의 재입고 지정(Q3) — 생략하면 조합은 할당 순서(교환 → 판매), 위치는 반품지에 연결된 위치 → 할당 위치다 */
|
|
205
|
-
export const claimCollectCompleteRequestSchema =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
206
|
+
export const claimCollectCompleteRequestSchema = strictRequest(
|
|
207
|
+
z.object({
|
|
208
|
+
restock: z
|
|
209
|
+
.object({
|
|
210
|
+
optionId: z
|
|
211
|
+
.string()
|
|
212
|
+
.min(1)
|
|
213
|
+
.optional()
|
|
214
|
+
.describe(
|
|
215
|
+
"돌아온 조합 — 그 주문상품의 할당 조합이어야 한다(아니면 400 `INVALID_RESTOCK_OPTION`)",
|
|
216
|
+
),
|
|
217
|
+
inventoryLocationId: z.string().min(1).optional().describe("재입고할 재고 위치"),
|
|
218
|
+
})
|
|
219
|
+
.optional(),
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
219
222
|
export type ClaimCollectCompleteRequest = z.infer<typeof claimCollectCompleteRequestSchema>;
|
|
220
223
|
|
|
221
|
-
export const claimRedeliveryRequestSchema =
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
224
|
+
export const claimRedeliveryRequestSchema = strictRequest(
|
|
225
|
+
z.object({
|
|
226
|
+
carrierCode: z.string().min(1),
|
|
227
|
+
trackingNumber: z.string().min(1),
|
|
228
|
+
inventoryLocationId: z
|
|
229
|
+
.string()
|
|
230
|
+
.min(1)
|
|
231
|
+
.optional()
|
|
232
|
+
.describe("교환품을 보낼 재고 위치. 생략하면 그 상품의 기준 위치"),
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
230
235
|
export type ClaimRedeliveryRequest = z.infer<typeof claimRedeliveryRequestSchema>;
|