@sayren/storefront-sdk 0.20.7 → 0.20.9
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/auth/index.mjs +1 -1
- package/dist/{checkout-Bt6j2eDw.mjs → checkout-BjJ4Efbn.mjs} +98 -10
- package/dist/{client-Dn_JdIqv.mjs → client-vjq513VD.mjs} +118 -5
- package/dist/{index-atHaDBC3.d.mts → index-BLjQQj49.d.mts} +894 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/payments/index.d.mts +1 -1
- package/dist/payments/index.mjs +1 -1
- package/package.json +1 -1
- package/src/client.ts +75 -0
- package/src/index.ts +2 -0
- package/src/schemas/catalog.ts +10 -0
- package/src/schemas/checkout.ts +12 -0
- package/src/schemas/commerce-flow.ts +115 -0
- package/src/schemas/fulfillment.ts +53 -3
- package/src/schemas/order.ts +21 -0
- package/src/schemas/product-options.ts +72 -2
- package/src/schemas/stay.ts +32 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { stayRequestSchema, stayViewSchema } from "./stay";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 추가 선택·직접 입력 (이슈 #59) — 상품 상세의 옵션 표시, 장바구니 담기·바로 구매 요청, 줄 스냅샷의 공통 모양.
|
|
@@ -30,10 +31,62 @@ export const customInputViewSchema = z.object({
|
|
|
30
31
|
placeholder: z.string().nullable().describe("입력 안내. 없으면 null"),
|
|
31
32
|
maxLength: z.number().int().describe("최대 글자 수"),
|
|
32
33
|
required: z.boolean(),
|
|
34
|
+
key: z.string().nullable().optional().describe("고정 키. 없으면 null"),
|
|
35
|
+
type: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe(
|
|
39
|
+
"입력 타입. 현재 값은 `text`·`number`(정수)·`select`·`date`(`YYYY-MM-DD`, 한국 날짜)·`datetime`(오프셋 있는 ISO 8601)·`image`(업로드 주소 목록, `maxLength`가 최대 장수)이고 값이 추가될 수 있어 문자열로 받는다. 없으면 `text`",
|
|
40
|
+
),
|
|
41
|
+
options: z
|
|
42
|
+
.array(z.string())
|
|
43
|
+
.nullable()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("`select`의 선택지. 그 밖의 타입은 null"),
|
|
33
46
|
});
|
|
34
47
|
|
|
35
48
|
export type CustomInputView = z.infer<typeof customInputViewSchema>;
|
|
36
49
|
|
|
50
|
+
/**
|
|
51
|
+
* 직접 입력값을 타입대로 검사하고 저장할 모양으로 바꾼다(정규화 뒤 값). 서버 판정(400 `CUSTOM_INPUT_INVALID`)과 스토어프론트
|
|
52
|
+
* 입력 검사가 이 함수 하나를 쓴다. 모르는 타입은 `text`로 본다. 맞지 않으면 null
|
|
53
|
+
*/
|
|
54
|
+
export function parseCustomInputValue(
|
|
55
|
+
type: string | null | undefined,
|
|
56
|
+
options: readonly string[] | null | undefined,
|
|
57
|
+
raw: string,
|
|
58
|
+
): string | null {
|
|
59
|
+
const value = normalizeCustomInputValue(raw);
|
|
60
|
+
switch (type) {
|
|
61
|
+
case "number":
|
|
62
|
+
// 플로우 식의 int와 같은 범위 — 정수만 받는다(소수는 셀러가 단위를 바꿔 받는다, 예: g)
|
|
63
|
+
return /^-?\d{1,15}$/.test(value) ? String(Number(value)) : null;
|
|
64
|
+
case "select":
|
|
65
|
+
return options?.includes(value) ? value : null;
|
|
66
|
+
case "date": {
|
|
67
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
|
68
|
+
const date = new Date(`${value}T00:00:00Z`);
|
|
69
|
+
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value
|
|
70
|
+
? value
|
|
71
|
+
: null;
|
|
72
|
+
}
|
|
73
|
+
case "image": {
|
|
74
|
+
// 주문 입력 이미지(#115 E4) — 업로드 주소 목록(공백·줄바꿈 구분). 저장은 줄바꿈으로 잇는다. 주소의 출처(이 상점 업로드)는 서버가 본다
|
|
75
|
+
const urls = value.split(/\s+/).filter(Boolean);
|
|
76
|
+
if (urls.length === 0 || !urls.every((url) => /^https:\/\/\S+$/.test(url))) return null;
|
|
77
|
+
return [...new Set(urls)].join("\n");
|
|
78
|
+
}
|
|
79
|
+
case "datetime": {
|
|
80
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})$/.test(value))
|
|
81
|
+
return null;
|
|
82
|
+
const time = new Date(value);
|
|
83
|
+
return Number.isNaN(time.getTime()) ? null : time.toISOString();
|
|
84
|
+
}
|
|
85
|
+
default:
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
37
90
|
/**
|
|
38
91
|
* 직접 입력값 정규화 — NFC로 모으고, 제어 문자(C0·C1·줄바꿈·줄·문단 구분자)는 공백으로, 제로폭·양방향 제어 문자는 지운 뒤
|
|
39
92
|
* 앞뒤 공백을 지운다. 서버가 저장하는 값이 이것이다. 빈 문자열이면 입력하지 않은 것으로 본다.
|
|
@@ -88,8 +141,9 @@ export const lineSelectionRequestShape = {
|
|
|
88
141
|
.max(20)
|
|
89
142
|
.optional()
|
|
90
143
|
.describe(
|
|
91
|
-
"직접 입력값. 필수 항목을 비우면 400 `CUSTOM_INPUT_REQUIRED`, 최대 글자 수를 넘으면 400 `CUSTOM_INPUT_TOO_LONG`, 없는 항목이면 400 `CUSTOM_INPUT_NOT_FOUND`",
|
|
144
|
+
"직접 입력값. 필수 항목을 비우면 400 `CUSTOM_INPUT_REQUIRED`, 최대 글자 수를 넘으면 400 `CUSTOM_INPUT_TOO_LONG`, 없는 항목이면 400 `CUSTOM_INPUT_NOT_FOUND`, 항목 타입에 맞지 않으면 400 `CUSTOM_INPUT_INVALID`",
|
|
92
145
|
),
|
|
146
|
+
stay: stayRequestSchema.optional(),
|
|
93
147
|
};
|
|
94
148
|
|
|
95
149
|
/** 줄의 옵션 선택 스냅샷 한 칸 — 조합 옵션은 옵션명마다 한 칸이고 추가금이 0이다(조합 추가금은 단가에 들어 있다) */
|
|
@@ -108,7 +162,9 @@ export type OptionSelectionView = z.infer<typeof optionSelectionViewSchema>;
|
|
|
108
162
|
|
|
109
163
|
export const customInputValueViewSchema = z.object({
|
|
110
164
|
label: z.string().describe("항목명"),
|
|
111
|
-
value: z.string().describe("구매자가 적은 값"),
|
|
165
|
+
value: z.string().describe("구매자가 적은 값(타입별 정규화 뒤 값, `datetime`은 UTC ISO 8601)"),
|
|
166
|
+
key: z.string().nullable().optional().describe("항목의 고정 키(주문 시점). 없으면 null"),
|
|
167
|
+
type: z.string().optional().describe("항목의 입력 타입(주문 시점). 없으면 `text`"),
|
|
112
168
|
});
|
|
113
169
|
|
|
114
170
|
export type CustomInputValueView = z.infer<typeof customInputValueViewSchema>;
|
|
@@ -125,4 +181,18 @@ export const lineSelectionResponseShape = {
|
|
|
125
181
|
.optional()
|
|
126
182
|
.default([])
|
|
127
183
|
.describe("직접 입력값(항목명과 값). 없으면 빈 배열. 서버는 항상 싣는다"),
|
|
184
|
+
stay: stayViewSchema.optional().describe("숙박 기간과 박별 가격(날짜별 재고 상품만, 이슈 #118)"),
|
|
128
185
|
};
|
|
186
|
+
|
|
187
|
+
/** 주문 입력 이미지 업로드 자리 요청(#115 E4) — 파일명은 확장자만 쓰인다 */
|
|
188
|
+
export const orderInputUploadRequestSchema = z.object({
|
|
189
|
+
filename: z.string().min(1).max(200),
|
|
190
|
+
size: z
|
|
191
|
+
.number()
|
|
192
|
+
.int()
|
|
193
|
+
.positive()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("바이트. 주면 상한을 넘을 때 미리 거절한다"),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
export type OrderInputUploadRequest = z.infer<typeof orderInputUploadRequestSchema>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 숙박 기간·날짜별 달력(이슈 #118) — 날짜별 재고 상품(상품 상세 `inventoryMode: "DATED"`)은 담기·바로 구매에 기간을 함께 보내고,
|
|
5
|
+
* 가격은 박마다 달력의 가격을 더한 값이다. 날짜는 한국 날짜 `YYYY-MM-DD`다.
|
|
6
|
+
*/
|
|
7
|
+
export const stayRequestSchema = z
|
|
8
|
+
.object({
|
|
9
|
+
checkIn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
10
|
+
checkOut: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
11
|
+
})
|
|
12
|
+
.describe(
|
|
13
|
+
"숙박 기간(날짜별 재고 상품만). 체크인 날부터 체크아웃 전날까지 박마다 재고·가격을 본다. 최대 30박. 날짜별 재고 상품에 빠지면 400 `STAY_REQUIRED`, 다른 상품에 주면 400 `STAY_NOT_APPLICABLE`, 재고가 없는 날이 있으면 409 `DATES_UNAVAILABLE`",
|
|
14
|
+
);
|
|
15
|
+
export type StayRequest = z.infer<typeof stayRequestSchema>;
|
|
16
|
+
|
|
17
|
+
export const stayViewSchema = z.object({
|
|
18
|
+
checkIn: z.string(),
|
|
19
|
+
checkOut: z.string(),
|
|
20
|
+
nights: z
|
|
21
|
+
.array(z.object({ date: z.string(), price: z.number().int() }))
|
|
22
|
+
.describe("박마다 날짜·가격(객실 1개 기준). 합이 줄 단가다"),
|
|
23
|
+
});
|
|
24
|
+
export type StayView = z.infer<typeof stayViewSchema>;
|
|
25
|
+
|
|
26
|
+
/** 구매자 달력 한 칸 — 재고 수는 보이지 않고 예약 가능 여부만 */
|
|
27
|
+
export const calendarDayViewSchema = z.object({
|
|
28
|
+
date: z.string(),
|
|
29
|
+
available: z.boolean(),
|
|
30
|
+
price: z.number().int().describe("그날 가격(원)"),
|
|
31
|
+
});
|
|
32
|
+
export type CalendarDayView = z.infer<typeof calendarDayViewSchema>;
|