@sayren/store-sdk 0.50.0 → 0.52.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 +1303 -666
- package/dist/index.mjs +584 -62
- package/dist/server.d.mts +3 -1
- package/dist/server.mjs +2 -1
- package/dist/userinfo-n1JYxWAz.d.mts +90 -0
- package/package.json +1 -1
- package/src/client.ts +29 -0
- package/src/index.ts +1 -0
- package/src/member-delegation.ts +4 -0
- package/src/schemas/condition-tree.ts +103 -3
- package/src/schemas/coupon-rules.ts +178 -0
- package/src/schemas/coupon-selection.ts +8 -3
- package/src/schemas/coupon.ts +112 -6
- package/src/schemas/customer-delegation.ts +6 -0
- package/src/schemas/customer.ts +4 -0
- package/src/schemas/points.ts +4 -2
- package/src/schemas/promotion.ts +286 -49
- package/src/schemas/userinfo.ts +118 -0
- package/dist/webhook-headers-D6QmPdEJ.d.mts +0 -22
package/src/schemas/coupon.ts
CHANGED
|
@@ -12,10 +12,12 @@ import {
|
|
|
12
12
|
import { currencyCodeSchema } from "./currency";
|
|
13
13
|
import {
|
|
14
14
|
COUPON_CONDITIONS_DEFAULTS,
|
|
15
|
+
COUPON_REWARD_KINDS,
|
|
15
16
|
COUPON_TARGET_LIST_MAX,
|
|
16
17
|
couponBenefitSchema,
|
|
17
18
|
couponConditionsSchema,
|
|
18
19
|
couponKindSchema,
|
|
20
|
+
couponRewardSchema,
|
|
19
21
|
couponTargetSchema,
|
|
20
22
|
} from "./promotion";
|
|
21
23
|
import { strictRequest } from "./strict";
|
|
@@ -35,17 +37,34 @@ export const couponStatusSchema = z
|
|
|
35
37
|
export type CouponStatus = z.infer<typeof couponStatusSchema>;
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
* 발급 방식 — 코드 입력·셀러 지급·다운로드(구매자가 받기)·회원가입 자동 지급(이슈 #110).
|
|
40
|
+
* 발급 방식 — 코드 입력·셀러 지급·다운로드(구매자가 받기)·회원가입 자동 지급(이슈 #110)·흐름 지급(이슈 #264).
|
|
39
41
|
* 응답(`couponSchema.issueMethod`)은 값이 늘 수 있어 문자열로 받는다. 이 enum은 요청 검증용이다.
|
|
40
42
|
*/
|
|
41
43
|
export const couponIssueMethodSchema = z
|
|
42
|
-
.enum(["CODE", "MANUAL", "DOWNLOAD", "SIGNUP"])
|
|
44
|
+
.enum(["CODE", "MANUAL", "DOWNLOAD", "SIGNUP", "FLOW", "VOUCHER"])
|
|
43
45
|
.describe(
|
|
44
46
|
"발급 방식. `CODE`=구매자가 주문서에 코드를 입력한다, `MANUAL`=셀러가 회원을 골라 지급한다, " +
|
|
45
|
-
"`DOWNLOAD`=구매자가 스토어프론트에서 받는다, `SIGNUP`=회원가입 때 자동으로
|
|
47
|
+
"`DOWNLOAD`=구매자가 스토어프론트에서 받는다, `SIGNUP`=회원가입 때 자동으로 지급한다, " +
|
|
48
|
+
"`FLOW`=주문 흐름의 쿠폰 지급 단계가 그 주문의 구매자에게 지급한다, " +
|
|
49
|
+
"`VOUCHER`=셀러가 1회용 쿠폰 번호를 만들어 나눠 주고 구매자가 번호를 등록하면 지급한다",
|
|
46
50
|
);
|
|
47
51
|
export type CouponIssueMethod = z.infer<typeof couponIssueMethodSchema>;
|
|
48
52
|
|
|
53
|
+
/** 쿠폰 번호 접두어 — 영문·숫자 2~10자, 대문자로 저장한다. 상점 안에서 유일하다 */
|
|
54
|
+
export const VOUCHER_PREFIX_PATTERN = /^[A-Za-z0-9]{2,10}$/;
|
|
55
|
+
/** 쿠폰 번호 한 쿠폰의 최대 개수 */
|
|
56
|
+
export const VOUCHER_COUNT_MAX = 100_000;
|
|
57
|
+
|
|
58
|
+
/** 쿠폰 번호 설정(`VOUCHER`, 이슈 #264) — 만든 뒤에는 바꿀 수 없다 */
|
|
59
|
+
export const couponVoucherInputSchema = z
|
|
60
|
+
.strictObject({
|
|
61
|
+
prefix: z.string().regex(VOUCHER_PREFIX_PATTERN).describe("번호 앞부분(영문·숫자 2~10자)"),
|
|
62
|
+
count: z.number().int().min(1).max(VOUCHER_COUNT_MAX).describe("만들 번호 수(1~100,000)"),
|
|
63
|
+
})
|
|
64
|
+
.describe(
|
|
65
|
+
"쿠폰 번호 설정 — 1회용 번호 `count`개를 만든다. 번호는 `GET /v1/coupons/{couponId}/voucher-codes`로 받는다",
|
|
66
|
+
);
|
|
67
|
+
|
|
49
68
|
/** 코드 형식 — 영문·숫자·`-`·`_` 6~30자. 대소문자를 구분하지 않고 서버가 대문자로 저장한다 */
|
|
50
69
|
export const COUPON_CODE_PATTERN = /^[A-Za-z0-9_-]{6,30}$/;
|
|
51
70
|
|
|
@@ -86,6 +105,9 @@ const descriptions = {
|
|
|
86
105
|
"발급 대상 트리(이슈 #222) — 셀러 지급·받기·회원가입 지급에서 누가 받을 수 있는가. 구매자 조건만. 코드 입력형에는 두지 않는다",
|
|
87
106
|
stacking:
|
|
88
107
|
"중복 사용 정책(이슈 #222). 생략하면 종류별 한 장·서로 결합·적립금과 함께. 결합은 양쪽 쿠폰이 모두 허용해야 성립한다",
|
|
108
|
+
reward:
|
|
109
|
+
"확장 혜택(구간 할인 `TIERED`·N+1 `BUY_X_GET_Y`·적립금 지급 `POINT`). 주면 `benefit`을 생략하고, 응답의 `benefit`은 가장 가까운 값이다. " +
|
|
110
|
+
"N+1은 상품 쿠폰만, 적립금 지급은 회원 전용 주문 쿠폰만이다. 수정에서 null이면 확장 혜택을 지운다(`benefit`이 필요하다)",
|
|
89
111
|
issueStartsAt: "받기 시작 시각(받기 쿠폰만, 이슈 #222). 생략하면 사용 시작 시각을 따른다",
|
|
90
112
|
issueEndsAt:
|
|
91
113
|
"받기 종료 시각(받기 쿠폰만, 이 시각부터 못 받는다). 생략하면 사용 종료 시각을 따른다",
|
|
@@ -99,6 +121,7 @@ const ruleFields = {
|
|
|
99
121
|
targetRules: couponTargetRulesSchema.optional().describe(descriptions.targetRules),
|
|
100
122
|
audience: couponConditionTreeSchema.optional().describe(descriptions.audience),
|
|
101
123
|
stacking: couponStackingSchema.optional().describe(descriptions.stacking),
|
|
124
|
+
reward: couponRewardSchema.nullable().optional().describe(descriptions.reward),
|
|
102
125
|
};
|
|
103
126
|
|
|
104
127
|
/**
|
|
@@ -124,6 +147,8 @@ export function couponRequestRuleIssues(body: {
|
|
|
124
147
|
targetRules?: CouponRuleTrees["targetRules"];
|
|
125
148
|
audience?: CouponRuleTrees["eligibility"];
|
|
126
149
|
stacking?: { perOrderLimit: number };
|
|
150
|
+
reward?: { type: "TIERED" | "BUY_X_GET_Y" | "POINT" } | null;
|
|
151
|
+
benefit?: unknown;
|
|
127
152
|
perMemberUseLimit?: number | null;
|
|
128
153
|
dailyIssueLimit?: number | null;
|
|
129
154
|
issueStartsAt?: string | null;
|
|
@@ -162,6 +187,27 @@ export function couponRequestRuleIssues(body: {
|
|
|
162
187
|
...(body.audience ? { audience: body.audience } : {}),
|
|
163
188
|
}),
|
|
164
189
|
);
|
|
190
|
+
if (!body.reward && body.benefit === undefined)
|
|
191
|
+
issues.push({ path: ["benefit"], message: "혜택(benefit 또는 reward)이 필요합니다" });
|
|
192
|
+
if (
|
|
193
|
+
body.reward &&
|
|
194
|
+
body.kind !== undefined &&
|
|
195
|
+
!(COUPON_REWARD_KINDS[body.reward.type] as readonly string[]).includes(body.kind)
|
|
196
|
+
)
|
|
197
|
+
issues.push({
|
|
198
|
+
path: ["reward", "type"],
|
|
199
|
+
message:
|
|
200
|
+
body.reward.type === "POINT"
|
|
201
|
+
? "적립금 지급은 주문 쿠폰만 쓸 수 있습니다"
|
|
202
|
+
: body.reward.type === "BUY_X_GET_Y"
|
|
203
|
+
? "N+1은 상품 쿠폰만 쓸 수 있습니다"
|
|
204
|
+
: "구간 할인은 배송비 쿠폰에 쓸 수 없습니다",
|
|
205
|
+
});
|
|
206
|
+
if (body.reward?.type === "POINT" && !conditions.memberOnly)
|
|
207
|
+
issues.push({
|
|
208
|
+
path: ["reward", "type"],
|
|
209
|
+
message: "적립금 지급은 회원 전용 쿠폰만 쓸 수 있습니다",
|
|
210
|
+
});
|
|
165
211
|
if (body.stacking && body.stacking.perOrderLimit > 1 && body.kind !== "PRODUCT")
|
|
166
212
|
issues.push({
|
|
167
213
|
path: ["stacking", "perOrderLimit"],
|
|
@@ -233,13 +279,14 @@ export const createCouponRequestSchema = strictRequest(
|
|
|
233
279
|
name: z.string().trim().min(1).max(60).describe(descriptions.name),
|
|
234
280
|
description: z.string().max(300).nullable().optional().describe(descriptions.description),
|
|
235
281
|
kind: couponKindSchema,
|
|
236
|
-
benefit: couponBenefitSchema,
|
|
282
|
+
benefit: couponBenefitSchema.optional().describe("혜택. `reward`를 주면 생략한다"),
|
|
237
283
|
conditions: couponConditionsInputSchema
|
|
238
284
|
.optional()
|
|
239
285
|
.describe("사용 조건. 생략한 항목은 기본값(최소 금액 0, 회원 전용, 즉시할인과 중복 가능)"),
|
|
240
286
|
target: couponTargetInputSchema.optional().describe("대상 상품·카테고리. 생략하면 전 상품"),
|
|
241
287
|
issueMethod: couponIssueMethodSchema,
|
|
242
288
|
code: z.string().regex(COUPON_CODE_PATTERN).optional().describe(descriptions.code),
|
|
289
|
+
voucher: couponVoucherInputSchema.optional(),
|
|
243
290
|
startsAt: couponTimeField(descriptions.startsAt),
|
|
244
291
|
endsAt: couponTimeField(descriptions.endsAt),
|
|
245
292
|
validDaysAfterIssue: z
|
|
@@ -270,6 +317,12 @@ export const createCouponRequestSchema = strictRequest(
|
|
|
270
317
|
ctx.addIssue({ code: "custom", path: [path], message });
|
|
271
318
|
if (body.issueMethod === "CODE" && !body.code)
|
|
272
319
|
issue("code", "코드 입력형 쿠폰은 코드가 필요합니다");
|
|
320
|
+
if (body.reward && body.benefit)
|
|
321
|
+
issue("benefit", "확장 혜택(reward)을 주면 benefit을 생략합니다");
|
|
322
|
+
if (body.issueMethod === "VOUCHER" && !body.voucher)
|
|
323
|
+
issue("voucher", "쿠폰 번호 쿠폰은 번호 설정이 필요합니다");
|
|
324
|
+
if (body.issueMethod !== "VOUCHER" && body.voucher)
|
|
325
|
+
issue("voucher", "번호 설정은 쿠폰 번호 쿠폰에만 둡니다");
|
|
273
326
|
if (body.issueMethod !== "CODE" && body.code)
|
|
274
327
|
issue("code", "코드는 코드 입력형 쿠폰에만 둡니다");
|
|
275
328
|
if (body.issueMethod === "CODE" && body.validDaysAfterIssue != null) {
|
|
@@ -348,7 +401,7 @@ export const couponSchema = z.object({
|
|
|
348
401
|
issueMethod: z
|
|
349
402
|
.string()
|
|
350
403
|
.describe(
|
|
351
|
-
"발급 방식. 현재 값은 `CODE`·`MANUAL`·`DOWNLOAD`(구매자가 받기)·`SIGNUP`(회원가입 자동 지급)이고 값이 추가될 수 있어 문자열로 받는다",
|
|
404
|
+
"발급 방식. 현재 값은 `CODE`·`MANUAL`·`DOWNLOAD`(구매자가 받기)·`SIGNUP`(회원가입 자동 지급)·`FLOW`(주문 흐름 지급)·`VOUCHER`(쿠폰 번호)이고 값이 추가될 수 있어 문자열로 받는다",
|
|
352
405
|
),
|
|
353
406
|
code: z.string().nullable().describe("쿠폰 코드(대문자). 코드 입력형이 아니면 null"),
|
|
354
407
|
startsAt: z.string().nullable(),
|
|
@@ -365,6 +418,12 @@ export const couponSchema = z.object({
|
|
|
365
418
|
totalUseLimit: z.number().int().nullable(),
|
|
366
419
|
perMemberIssueLimit: z.number().int().nullable(),
|
|
367
420
|
perMemberUseLimit: z.number().int().nullable(),
|
|
421
|
+
voucher: z
|
|
422
|
+
.object({ prefix: z.string(), count: z.number().int() })
|
|
423
|
+
.nullable()
|
|
424
|
+
.optional()
|
|
425
|
+
.default(null)
|
|
426
|
+
.describe("쿠폰 번호 설정(`VOUCHER`만, 이슈 #264). 다른 발급 방식은 null. 서버는 항상 싣는다"),
|
|
368
427
|
issuedCount: z
|
|
369
428
|
.number()
|
|
370
429
|
.int()
|
|
@@ -395,6 +454,11 @@ export const couponSchema = z.object({
|
|
|
395
454
|
.optional()
|
|
396
455
|
.default(COUPON_STACKING_DEFAULTS)
|
|
397
456
|
.describe("중복 사용 정책(이슈 #222). 서버는 항상 싣는다"),
|
|
457
|
+
reward: couponRewardSchema
|
|
458
|
+
.nullable()
|
|
459
|
+
.optional()
|
|
460
|
+
.default(null)
|
|
461
|
+
.describe("확장 혜택. 없으면 null이고 `benefit`이 혜택이다. 서버는 항상 싣는다"),
|
|
398
462
|
rulesOnly: z
|
|
399
463
|
.boolean()
|
|
400
464
|
.optional()
|
|
@@ -444,7 +508,7 @@ export const couponIssueSchema = z.object({
|
|
|
444
508
|
.string()
|
|
445
509
|
.optional()
|
|
446
510
|
.describe(
|
|
447
|
-
"발급 경로(이슈 #110). 현재 값은 `MANUAL`(셀러 지급)·`DOWNLOAD`(구매자가 받음)·`SIGNUP`(회원가입 자동 지급)이고 값이 추가될 수 있다. 서버는 항상 싣는다",
|
|
511
|
+
"발급 경로(이슈 #110). 현재 값은 `MANUAL`(셀러 지급)·`DOWNLOAD`(구매자가 받음)·`SIGNUP`(회원가입 자동 지급)·`FLOW`(주문 흐름 지급)·`VOUCHER`(쿠폰 번호 등록)이고 값이 추가될 수 있다. 서버는 항상 싣는다",
|
|
448
512
|
),
|
|
449
513
|
status: couponIssueStatusSchema,
|
|
450
514
|
validFrom: z.string(),
|
|
@@ -460,6 +524,48 @@ export const couponIssueListQuerySchema = z.object({
|
|
|
460
524
|
...pageQueryShape(),
|
|
461
525
|
});
|
|
462
526
|
|
|
527
|
+
/** `GET /v1/coupons/{couponId}/voucher-codes` — 쿠폰 번호 전체(순번 순). 상점 플랫폼이 CSV로 내려받는다 */
|
|
528
|
+
export const couponVoucherCodesSchema = z
|
|
529
|
+
.object({
|
|
530
|
+
prefix: z.string(),
|
|
531
|
+
count: z.number().int(),
|
|
532
|
+
codes: z.array(z.string()).describe("번호 — 순번 1부터. 등록에 쓴 번호도 싣는다"),
|
|
533
|
+
})
|
|
534
|
+
.describe("쿠폰 번호 목록");
|
|
535
|
+
export type CouponVoucherCodes = z.infer<typeof couponVoucherCodesSchema>;
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* 발급 대상 전원 지급(이슈 #264) — `POST /v1/coupons/{couponId}/distributions`(202)가 만들고 서버가 뒤에서 처리한다. 셀러 지급(`MANUAL`) 쿠폰을
|
|
539
|
+
* 발급 대상 조건에 맞는 회원 모두에게 준다(셀러 지급과 같은 한도 판정). 쿠폰마다 진행 중 작업은 하나다
|
|
540
|
+
*/
|
|
541
|
+
export const couponDistributionSchema = z
|
|
542
|
+
.object({
|
|
543
|
+
distributionId: z.string(),
|
|
544
|
+
couponId: z.string(),
|
|
545
|
+
status: z
|
|
546
|
+
.string()
|
|
547
|
+
.describe(
|
|
548
|
+
"작업 상태. 현재 값은 `QUEUED`(대기)·`RUNNING`(지급 중)·`COMPLETED`(끝남)·`FAILED`(실패)이고 값이 추가될 수 있다",
|
|
549
|
+
),
|
|
550
|
+
processedCount: z.number().int().describe("지금까지 살펴본 회원 수"),
|
|
551
|
+
issuedCount: z.number().int().describe("이 작업이 지급한 장수"),
|
|
552
|
+
skippedCount: z
|
|
553
|
+
.number()
|
|
554
|
+
.int()
|
|
555
|
+
.describe("건너뛴 회원 수 — 발급 대상이 아니거나 1인 발급 한도에 닿은 회원"),
|
|
556
|
+
stopCode: z
|
|
557
|
+
.string()
|
|
558
|
+
.nullable()
|
|
559
|
+
.describe(
|
|
560
|
+
"일찍 끝난 사유. `COUPON_EXHAUSTED`(총 발급 한도)·`COUPON_DAILY_EXHAUSTED`(하루 발급 한도)·`COUPON_NOT_ACTIVE`(중지·종료)·`COUPON_EXPIRED`(사용 기간 끝) 등. 끝까지 돌았으면 null",
|
|
561
|
+
),
|
|
562
|
+
createdAt: z.string(),
|
|
563
|
+
startedAt: z.string().nullable(),
|
|
564
|
+
finishedAt: z.string().nullable(),
|
|
565
|
+
})
|
|
566
|
+
.describe("발급 대상 전원 지급 작업");
|
|
567
|
+
export type CouponDistribution = z.infer<typeof couponDistributionSchema>;
|
|
568
|
+
|
|
463
569
|
/** `POST /v1/coupons/{couponId}/issues` — 셀러 지급. 한 번에 1,000명까지 */
|
|
464
570
|
export const issueCouponRequestSchema = z.strictObject({
|
|
465
571
|
customerIds: z
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { strictRequest } from "./strict";
|
|
3
|
+
import { customerUserinfoInputSchema } from "./userinfo";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 구매자 위임 REST(`POST /v1/customers/delegations`, #71) — 계정 토큰(또는 관리 API 토큰) + `X-Store-Id`로 부른다(`customer:rw`).
|
|
@@ -31,6 +32,11 @@ export const customerDelegationRequestSchema = strictRequest(
|
|
|
31
32
|
.describe(
|
|
32
33
|
"사용자가 셀러 서비스에서 실제로 로그인한 시각(uid 위임만). 주면 구매자 토큰의 인증 시각이 된다",
|
|
33
34
|
),
|
|
35
|
+
userinfo: customerUserinfoInputSchema
|
|
36
|
+
.optional()
|
|
37
|
+
.describe(
|
|
38
|
+
"OIDC 클레임(이슈 #264) — 위임할 때마다 준 클레임만 바뀌고 null은 지운다. `email_verified`를 생략하면 위임으로 만든 구매자의 이메일은 확인된 것으로 본다",
|
|
39
|
+
),
|
|
34
40
|
})
|
|
35
41
|
.refine((value) => Boolean(value.uid || value.email), {
|
|
36
42
|
message: "uid 또는 email이 필요합니다",
|
package/src/schemas/customer.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { orderItemStatusSchema, pageQueryShape } from "./common";
|
|
3
3
|
import { strictRequest } from "./strict";
|
|
4
|
+
import { customerUserinfoSchema } from "./userinfo";
|
|
4
5
|
|
|
5
6
|
/** 구매자 목록 정렬 */
|
|
6
7
|
export const customerSortSchema = z
|
|
@@ -169,6 +170,9 @@ export const customerDetailSchema = z.object({
|
|
|
169
170
|
"약관·개인정보·마케팅 동의의 현재 상태. 이력은 `GET /v1/customers/{customerId}/consents`",
|
|
170
171
|
),
|
|
171
172
|
addresses: z.array(customerAddressSchema),
|
|
173
|
+
userinfo: customerUserinfoSchema
|
|
174
|
+
.optional()
|
|
175
|
+
.describe("OIDC userinfo 표준 클레임(이슈 #264, 주소는 기본 배송지). 서버는 항상 싣는다"),
|
|
172
176
|
stats: customerStatsSchema.extend({
|
|
173
177
|
canceledOrReturnedCount: z.number().int().describe("취소된 주문 상품 + 완료된 반품 수"),
|
|
174
178
|
reviewCount: z.number().int(),
|
package/src/schemas/points.ts
CHANGED
|
@@ -180,6 +180,7 @@ export const POINT_LOT_SOURCES = [
|
|
|
180
180
|
"ADJUST",
|
|
181
181
|
"REFUND_RESTORE",
|
|
182
182
|
"OPENING",
|
|
183
|
+
"COUPON",
|
|
183
184
|
] as const;
|
|
184
185
|
export type PointLotSource = (typeof POINT_LOT_SOURCES)[number];
|
|
185
186
|
|
|
@@ -194,6 +195,7 @@ export const POINT_ENTRY_TYPES = [
|
|
|
194
195
|
"EXPIRE",
|
|
195
196
|
"ADJUST",
|
|
196
197
|
"OPENING",
|
|
198
|
+
"EARN_COUPON",
|
|
197
199
|
] as const;
|
|
198
200
|
export type PointEntryType = (typeof POINT_ENTRY_TYPES)[number];
|
|
199
201
|
|
|
@@ -257,7 +259,7 @@ export const responsePointEntryTypeSchema = z
|
|
|
257
259
|
.describe(
|
|
258
260
|
"유형. `EARN_REVIEW` 리뷰 적립 · `REVOKE_REVIEW` 리뷰 삭제 회수 · `EARN_PURCHASE` 구매 적립 · `EXPIRE` 소멸 · `ADJUST` 셀러 지급·차감 · " +
|
|
259
261
|
"`OPENING` 기존 잔액 이전 · `USE_RESERVE` 결제 사용(결제 시작 때 빠짐) · `USE_RELEASE` 결제가 실패·만료되어 되돌림 · " +
|
|
260
|
-
"`REFUND_RESTORE` 취소·반품 환불로
|
|
262
|
+
"`REFUND_RESTORE` 취소·반품 환불로 반환 · `EARN_COUPON` 적립금 지급 쿠폰. 값이 추가될 수 있어 문자열로 받는다",
|
|
261
263
|
);
|
|
262
264
|
|
|
263
265
|
/** 원장 항목 — 셀러 조회용(사유·행위자 포함) */
|
|
@@ -283,7 +285,7 @@ export const pointLotSchema = z.object({
|
|
|
283
285
|
lotId: z.string(),
|
|
284
286
|
source: z
|
|
285
287
|
.union([z.enum(POINT_LOT_SOURCES), z.string()])
|
|
286
|
-
.describe("출처. `REVIEW`·`PURCHASE`·`ADJUST`·`REFUND_RESTORE`·`OPENING` 등"),
|
|
288
|
+
.describe("출처. `REVIEW`·`PURCHASE`·`ADJUST`·`REFUND_RESTORE`·`OPENING`·`COUPON` 등"),
|
|
287
289
|
amount: z.number().int().describe("적립한 금액"),
|
|
288
290
|
remaining: z.number().int().describe("남은 금액"),
|
|
289
291
|
expiresAt: z.string().nullable().describe("소멸 시각. null이면 소멸 없음"),
|