@sayren/store-sdk 0.51.0 → 0.53.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 +703 -333
- package/dist/index.mjs +257 -68
- package/package.json +1 -1
- package/src/schemas/coupon-selection.ts +8 -3
- package/src/schemas/coupon.ts +37 -1
- package/src/schemas/plans.ts +6 -5
- package/src/schemas/points.ts +4 -2
- package/src/schemas/promotion.ts +286 -49
- package/src/schemas/subscription.ts +4 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KRW_MONEY_RULES } from "./currency";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
couponRuleDiscountOf,
|
|
4
4
|
couponStackingOf,
|
|
5
5
|
couponsCombinable,
|
|
6
6
|
type PricingCoupon,
|
|
@@ -139,14 +139,19 @@ export function bestCouponSelection(input: CouponSelectionInput): CouponSelectio
|
|
|
139
139
|
const orders = solo.filter((e) => e.candidate.coupon.rule.kind === "ORDER");
|
|
140
140
|
const deliveries = solo.filter((e) => e.candidate.coupon.rule.kind === "DELIVERY");
|
|
141
141
|
const lineIds = input.lines.map((line) => line.lineId);
|
|
142
|
-
const
|
|
142
|
+
const lineById = new Map(input.lines.map((line) => [line.lineId, line]));
|
|
143
143
|
|
|
144
144
|
/** 상품 쿠폰 한 장의 줄별 할인 — 대상 줄만, 줄 금액 기준 */
|
|
145
145
|
const weightsOf = (entry: (typeof products)[number]) => {
|
|
146
146
|
const eligible = new Set(entry.result?.eligibleLineIds ?? []);
|
|
147
147
|
return lineIds.map((id) =>
|
|
148
148
|
eligible.has(id)
|
|
149
|
-
?
|
|
149
|
+
? couponRuleDiscountOf(
|
|
150
|
+
entry.candidate.coupon.rule,
|
|
151
|
+
lineById.get(id)?.amount ?? 0,
|
|
152
|
+
lineById.get(id) ?? null,
|
|
153
|
+
rules,
|
|
154
|
+
)
|
|
150
155
|
: 0,
|
|
151
156
|
);
|
|
152
157
|
};
|
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";
|
|
@@ -103,6 +105,9 @@ const descriptions = {
|
|
|
103
105
|
"발급 대상 트리(이슈 #222) — 셀러 지급·받기·회원가입 지급에서 누가 받을 수 있는가. 구매자 조건만. 코드 입력형에는 두지 않는다",
|
|
104
106
|
stacking:
|
|
105
107
|
"중복 사용 정책(이슈 #222). 생략하면 종류별 한 장·서로 결합·적립금과 함께. 결합은 양쪽 쿠폰이 모두 허용해야 성립한다",
|
|
108
|
+
reward:
|
|
109
|
+
"확장 혜택(구간 할인 `TIERED`·N+1 `BUY_X_GET_Y`·적립금 지급 `POINT`). 주면 `benefit`을 생략하고, 응답의 `benefit`은 가장 가까운 값이다. " +
|
|
110
|
+
"N+1은 상품 쿠폰만, 적립금 지급은 회원 전용 주문 쿠폰만이다. 수정에서 null이면 확장 혜택을 지운다(`benefit`이 필요하다)",
|
|
106
111
|
issueStartsAt: "받기 시작 시각(받기 쿠폰만, 이슈 #222). 생략하면 사용 시작 시각을 따른다",
|
|
107
112
|
issueEndsAt:
|
|
108
113
|
"받기 종료 시각(받기 쿠폰만, 이 시각부터 못 받는다). 생략하면 사용 종료 시각을 따른다",
|
|
@@ -116,6 +121,7 @@ const ruleFields = {
|
|
|
116
121
|
targetRules: couponTargetRulesSchema.optional().describe(descriptions.targetRules),
|
|
117
122
|
audience: couponConditionTreeSchema.optional().describe(descriptions.audience),
|
|
118
123
|
stacking: couponStackingSchema.optional().describe(descriptions.stacking),
|
|
124
|
+
reward: couponRewardSchema.nullable().optional().describe(descriptions.reward),
|
|
119
125
|
};
|
|
120
126
|
|
|
121
127
|
/**
|
|
@@ -141,6 +147,8 @@ export function couponRequestRuleIssues(body: {
|
|
|
141
147
|
targetRules?: CouponRuleTrees["targetRules"];
|
|
142
148
|
audience?: CouponRuleTrees["eligibility"];
|
|
143
149
|
stacking?: { perOrderLimit: number };
|
|
150
|
+
reward?: { type: "TIERED" | "BUY_X_GET_Y" | "POINT" } | null;
|
|
151
|
+
benefit?: unknown;
|
|
144
152
|
perMemberUseLimit?: number | null;
|
|
145
153
|
dailyIssueLimit?: number | null;
|
|
146
154
|
issueStartsAt?: string | null;
|
|
@@ -179,6 +187,27 @@ export function couponRequestRuleIssues(body: {
|
|
|
179
187
|
...(body.audience ? { audience: body.audience } : {}),
|
|
180
188
|
}),
|
|
181
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
|
+
});
|
|
182
211
|
if (body.stacking && body.stacking.perOrderLimit > 1 && body.kind !== "PRODUCT")
|
|
183
212
|
issues.push({
|
|
184
213
|
path: ["stacking", "perOrderLimit"],
|
|
@@ -250,7 +279,7 @@ export const createCouponRequestSchema = strictRequest(
|
|
|
250
279
|
name: z.string().trim().min(1).max(60).describe(descriptions.name),
|
|
251
280
|
description: z.string().max(300).nullable().optional().describe(descriptions.description),
|
|
252
281
|
kind: couponKindSchema,
|
|
253
|
-
benefit: couponBenefitSchema,
|
|
282
|
+
benefit: couponBenefitSchema.optional().describe("혜택. `reward`를 주면 생략한다"),
|
|
254
283
|
conditions: couponConditionsInputSchema
|
|
255
284
|
.optional()
|
|
256
285
|
.describe("사용 조건. 생략한 항목은 기본값(최소 금액 0, 회원 전용, 즉시할인과 중복 가능)"),
|
|
@@ -288,6 +317,8 @@ export const createCouponRequestSchema = strictRequest(
|
|
|
288
317
|
ctx.addIssue({ code: "custom", path: [path], message });
|
|
289
318
|
if (body.issueMethod === "CODE" && !body.code)
|
|
290
319
|
issue("code", "코드 입력형 쿠폰은 코드가 필요합니다");
|
|
320
|
+
if (body.reward && body.benefit)
|
|
321
|
+
issue("benefit", "확장 혜택(reward)을 주면 benefit을 생략합니다");
|
|
291
322
|
if (body.issueMethod === "VOUCHER" && !body.voucher)
|
|
292
323
|
issue("voucher", "쿠폰 번호 쿠폰은 번호 설정이 필요합니다");
|
|
293
324
|
if (body.issueMethod !== "VOUCHER" && body.voucher)
|
|
@@ -423,6 +454,11 @@ export const couponSchema = z.object({
|
|
|
423
454
|
.optional()
|
|
424
455
|
.default(COUPON_STACKING_DEFAULTS)
|
|
425
456
|
.describe("중복 사용 정책(이슈 #222). 서버는 항상 싣는다"),
|
|
457
|
+
reward: couponRewardSchema
|
|
458
|
+
.nullable()
|
|
459
|
+
.optional()
|
|
460
|
+
.default(null)
|
|
461
|
+
.describe("확장 혜택. 없으면 null이고 `benefit`이 혜택이다. 서버는 항상 싣는다"),
|
|
426
462
|
rulesOnly: z
|
|
427
463
|
.boolean()
|
|
428
464
|
.optional()
|
package/src/schemas/plans.ts
CHANGED
|
@@ -10,8 +10,8 @@ const limitOrContract = (description: string) =>
|
|
|
10
10
|
z.number().int().nullable().describe(`${description}. null이면 계약(엔터프라이즈)·한도 없음`);
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* 요금제 기능 키 값 — 요금제 비교표·상점 플랫폼 요금제 화면이 쓴다. 키는 서버 판정의 기능 키와 같다(추가만 한다
|
|
14
|
-
* 기능을 모두 포함한다
|
|
13
|
+
* 요금제 기능 키 값 — 요금제 비교표·상점 플랫폼 요금제 화면이 쓴다. 키는 서버 판정의 기능 키와 같다(추가만 한다 — 예외는 0.53에서 지운
|
|
14
|
+
* `limits.ai_credits_monthly`). 상위 요금제는 하위 요금제 기능을 모두 포함한다
|
|
15
15
|
*/
|
|
16
16
|
export const planEntitlementsSchema = z
|
|
17
17
|
.object({
|
|
@@ -24,6 +24,10 @@ export const planEntitlementsSchema = z
|
|
|
24
24
|
"관리 API(개인 액세스 토큰·외부 앱 토큰 호출). 상점 플랫폼·CLI·MCP는 모든 요금제에서 쓴다",
|
|
25
25
|
),
|
|
26
26
|
"features.analytics": z.boolean().describe("애널리틱스 화면·조회 API"),
|
|
27
|
+
"features.ai_assistant": z
|
|
28
|
+
.boolean()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("AI 상점 도우미. false면 새 실행을 시작할 수 없다(403 `PLAN_FEATURE_REQUIRED`)"),
|
|
27
31
|
"limits.ai_reads_monthly": limitOrContract("MCP 읽기 월 한도"),
|
|
28
32
|
"limits.ai_actions_monthly": limitOrContract(
|
|
29
33
|
"MCP 쓰기(AI 작업) 월 한도 — 주문 처리는 막지 않는다",
|
|
@@ -34,9 +38,6 @@ export const planEntitlementsSchema = z
|
|
|
34
38
|
"limits.private_apps": limitOrContract(
|
|
35
39
|
"비공개 앱(이 상점 전용 앱) 설치 수(0이면 설치할 수 없다)",
|
|
36
40
|
),
|
|
37
|
-
"limits.ai_credits_monthly": limitOrContract(
|
|
38
|
-
"AI 상점 도우미 월 크레딧(1 크레딧 = 모델 원가 $0.01, 이월 없음)",
|
|
39
|
-
),
|
|
40
41
|
"limits.storefront_preview_hours_monthly": limitOrContract("편집 환경 시간(월, 시간)"),
|
|
41
42
|
"limits.storefront_build_minutes_monthly": limitOrContract("빌드 시간(월, 분)"),
|
|
42
43
|
"limits.storefront_requests_monthly": limitOrContract(
|
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이면 소멸 없음"),
|