@sayren/store-sdk 0.19.0 → 0.21.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 +2010 -1314
- package/dist/index.mjs +1283 -1028
- package/package.json +1 -1
- package/src/client.ts +165 -93
- package/src/index.ts +1 -2
- package/src/schemas/claim.ts +1 -1
- package/src/schemas/fulfillment.ts +26 -275
- package/src/schemas/misc.ts +1 -1
- package/src/schemas/order.ts +25 -61
- package/src/schemas/product.ts +234 -7
- package/src/schemas/promotion.ts +3 -1
- package/src/schemas/saved-option-set.ts +168 -0
- package/src/schemas/store.ts +2 -30
- package/src/schemas/storefront-hosting.ts +576 -3
- package/src/schemas/upload.ts +2 -48
- package/src/schemas/webhook-event.ts +28 -14
- package/src/schemas/digital-asset.ts +0 -65
- package/src/schemas/fulfillment-code.ts +0 -143
|
@@ -1,33 +1,27 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* 상품
|
|
4
|
+
* 상품 유형 (이슈 #48·#57) — 배송이 필요한 상품과 필요 없는 상품을 가르는 축이다.
|
|
5
5
|
*
|
|
6
|
-
* **상품 유형은
|
|
6
|
+
* **상품 유형은 이행 방식 하나로 표현한다.** `fulfillment.type`이 배송 필요 여부, 이행 흐름, 클레임 규칙을 정한다.
|
|
7
7
|
* 코드 곳곳에서 유형 이름으로 분기하지 않고 이 파일의 판정 함수(`requiresShipping`·`fulfillmentRuleOf`)를 지난다.
|
|
8
|
-
* 유형이 늘어나도 판정표 한 곳만 고치면 배송비 계산·결제 시작·클레임·콘솔 버튼이 같이 따라온다.
|
|
9
8
|
*
|
|
10
9
|
* 주문상품 상태 enum은 바꾸지 않는다. 비실물의 **제공 완료는 `DELIVERED`를 재사용한다** — 새 상태를 더하면 기존 소비자의
|
|
11
|
-
* 상태 분기(배송완료 뒤 구매확정 대기, 반품 가능 판정)가 새 값을 모른다. 표시는
|
|
10
|
+
* 상태 분기(배송완료 뒤 구매확정 대기, 반품 가능 판정)가 새 값을 모른다. 표시는 유형으로 가른다.
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* 상품 유형.
|
|
16
|
-
* - `SHIPPING` 배송
|
|
17
|
-
* - `
|
|
18
|
-
*
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
15
|
+
* - `SHIPPING` 배송 상품. 택배로 발송하고 송장·배송 추적으로 배송완료가 된다
|
|
16
|
+
* - `MANUAL` 배송 없는 상품(비실물). 셀러가 제공 처리(`POST /v1/order-items/{orderItemId}/fulfill`)로 제공 완료를 기록한다
|
|
17
|
+
*
|
|
18
|
+
* 비실물의 세부 구분(파일 다운로드·코드 발급·이용권·방문 수령)은 2026-09-28에 제품에서 뺐다(이슈 #48 범위 축소).
|
|
19
|
+
* 이름은 이행 방식이다 — `SHIPPING`이 택배 발송이듯 `MANUAL`은 셀러가 직접 제공한다는 뜻이다.
|
|
21
20
|
*/
|
|
22
|
-
export const fulfillmentTypeSchema = z.enum(["SHIPPING", "
|
|
21
|
+
export const fulfillmentTypeSchema = z.enum(["SHIPPING", "MANUAL"]);
|
|
23
22
|
|
|
24
23
|
export type FulfillmentType = z.infer<typeof fulfillmentTypeSchema>;
|
|
25
24
|
|
|
26
|
-
/** 제공 방식 — `AUTO`는 결제 확정과 함께 제공하고, `MANUAL`은 셀러가 제공 처리한다 */
|
|
27
|
-
export const fulfillmentModeSchema = z.enum(["AUTO", "MANUAL"]);
|
|
28
|
-
|
|
29
|
-
export type FulfillmentMode = z.infer<typeof fulfillmentModeSchema>;
|
|
30
|
-
|
|
31
25
|
/**
|
|
32
26
|
* 응답 스키마용 유형 — 서버가 나중에 유형을 더해도 옛 SDK가 응답 파싱에서 깨지지 않게 문자열로 받는다.
|
|
33
27
|
* 요청은 `fulfillmentTypeSchema`(닫힌 enum)로 검증한다.
|
|
@@ -35,71 +29,35 @@ export type FulfillmentMode = z.infer<typeof fulfillmentModeSchema>;
|
|
|
35
29
|
export const fulfillmentTypeResponseSchema = z
|
|
36
30
|
.string()
|
|
37
31
|
.describe(
|
|
38
|
-
"상품 유형. 현재 값은 `SHIPPING`(배송 상품)·`
|
|
39
|
-
"`PICKUP`(방문 수령)이고 값이 추가될 수 있어 문자열로 받는다",
|
|
32
|
+
"상품 유형. 현재 값은 `SHIPPING`(배송 상품)·`MANUAL`(배송 없는 상품, 셀러가 제공 처리)이고 값이 추가될 수 있어 문자열로 받는다",
|
|
40
33
|
);
|
|
41
34
|
|
|
42
|
-
/** 유형 하나의 규칙 —
|
|
35
|
+
/** 유형 하나의 규칙 — 배송·클레임이 이 표를 읽는다 */
|
|
43
36
|
export interface FulfillmentRule {
|
|
44
37
|
/** 택배 배송이 필요한가 — 배송비·배송지·발송 흐름의 대상인가 */
|
|
45
38
|
requiresShipping: boolean;
|
|
46
|
-
/**
|
|
47
|
-
modes: readonly FulfillmentMode[];
|
|
48
|
-
/** 제공 방식을 정하지 않았을 때의 기본값. 배송 상품은 null */
|
|
49
|
-
defaultMode: FulfillmentMode | null;
|
|
50
|
-
/** 반품에 수거가 필요한가 — 실물만 수거한다(방문 수령 포함) */
|
|
39
|
+
/** 반품에 수거가 필요한가 — 실물만 수거한다 */
|
|
51
40
|
requiresCollection: boolean;
|
|
52
41
|
/** 교환을 받는가 — 실물만 받는다 */
|
|
53
42
|
exchangeSupported: boolean;
|
|
54
43
|
}
|
|
55
44
|
|
|
56
|
-
/**
|
|
57
|
-
* 유형별 규칙표 — **유일한 원천이다.**
|
|
58
|
-
*
|
|
59
|
-
* `PICKUP`은 실물이라 반품 수거와 교환을 받지만 택배 배송은 없다. `DOWNLOAD`는 결제 즉시 권한을 발급하므로 `AUTO`만,
|
|
60
|
-
* `PICKUP`은 셀러가 수령 완료를 누르므로 `MANUAL`만 둔다.
|
|
61
|
-
*/
|
|
45
|
+
/** 유형별 규칙표 — **유일한 원천이다.** */
|
|
62
46
|
export const FULFILLMENT_RULES: Readonly<Record<FulfillmentType, FulfillmentRule>> = {
|
|
63
47
|
SHIPPING: {
|
|
64
48
|
requiresShipping: true,
|
|
65
|
-
modes: [],
|
|
66
|
-
defaultMode: null,
|
|
67
49
|
requiresCollection: true,
|
|
68
50
|
exchangeSupported: true,
|
|
69
51
|
},
|
|
70
|
-
|
|
71
|
-
requiresShipping: false,
|
|
72
|
-
modes: ["AUTO"],
|
|
73
|
-
defaultMode: "AUTO",
|
|
74
|
-
requiresCollection: false,
|
|
75
|
-
exchangeSupported: false,
|
|
76
|
-
},
|
|
77
|
-
CODE: {
|
|
52
|
+
MANUAL: {
|
|
78
53
|
requiresShipping: false,
|
|
79
|
-
modes: ["AUTO", "MANUAL"],
|
|
80
|
-
defaultMode: "AUTO",
|
|
81
54
|
requiresCollection: false,
|
|
82
55
|
exchangeSupported: false,
|
|
83
56
|
},
|
|
84
|
-
SERVICE: {
|
|
85
|
-
requiresShipping: false,
|
|
86
|
-
modes: ["MANUAL", "AUTO"],
|
|
87
|
-
defaultMode: "MANUAL",
|
|
88
|
-
requiresCollection: false,
|
|
89
|
-
exchangeSupported: false,
|
|
90
|
-
},
|
|
91
|
-
PICKUP: {
|
|
92
|
-
requiresShipping: false,
|
|
93
|
-
modes: ["MANUAL"],
|
|
94
|
-
defaultMode: "MANUAL",
|
|
95
|
-
requiresCollection: true,
|
|
96
|
-
exchangeSupported: true,
|
|
97
|
-
},
|
|
98
57
|
};
|
|
99
58
|
|
|
100
59
|
/**
|
|
101
|
-
* 저장된 값 → 유형. null·모르는 값은 `SHIPPING`이다 — 이 필드 이전의
|
|
102
|
-
* 상품은 모두 배송 상품이었다.
|
|
60
|
+
* 저장된 값 → 유형. null·모르는 값은 `SHIPPING`이다 — 이 필드 이전의 상품·주문상품은 모두 배송 상품이었다.
|
|
103
61
|
*/
|
|
104
62
|
export function fulfillmentTypeOf(value: string | null | undefined): FulfillmentType {
|
|
105
63
|
const parsed = fulfillmentTypeSchema.safeParse(value);
|
|
@@ -121,20 +79,6 @@ export function requiresCollection(type: string | null | undefined): boolean {
|
|
|
121
79
|
return fulfillmentRuleOf(type).requiresCollection;
|
|
122
80
|
}
|
|
123
81
|
|
|
124
|
-
/**
|
|
125
|
-
* 유효 제공 방식 — 요청 값이 유형에 허용되면 그 값, 생략이면 유형 기본값이다. 배송 상품은 null이다.
|
|
126
|
-
* 허용되지 않는 값은 `null`을 돌려주지 않고 호출부가 거부하도록 `undefined`를 돌려준다.
|
|
127
|
-
*/
|
|
128
|
-
export function resolveFulfillmentMode(
|
|
129
|
-
type: FulfillmentType,
|
|
130
|
-
mode: FulfillmentMode | null | undefined,
|
|
131
|
-
): FulfillmentMode | null | undefined {
|
|
132
|
-
const rule = FULFILLMENT_RULES[type];
|
|
133
|
-
if (rule.modes.length === 0) return null;
|
|
134
|
-
if (mode === null || mode === undefined) return rule.defaultMode;
|
|
135
|
-
return rule.modes.includes(mode) ? mode : undefined;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
82
|
/**
|
|
139
83
|
* 배송이 없는 주문상품의 배송 묶음 키 (`order_items.delivery_bundle_key`).
|
|
140
84
|
*
|
|
@@ -146,11 +90,12 @@ export const NO_SHIPPING_BUNDLE_KEY = "none";
|
|
|
146
90
|
|
|
147
91
|
// ─── 상품 이행 (이슈 #57) ─────────────────────────────────────
|
|
148
92
|
//
|
|
149
|
-
// 상품은 공통 필드 + `fulfillment` 판별 유니온이다. 유형 키 안에 그 유형의 속성만 있다(서버는 `
|
|
93
|
+
// 상품은 공통 필드 + `fulfillment` 판별 유니온이다. 유형 키 안에 그 유형의 속성만 있다(서버는 배송 상품만 `product_shipping` 1:1 테이블).
|
|
94
|
+
// 배송 없는 상품(`MANUAL`)은 유형 속성이 없어 `{ "type": "MANUAL" }`뿐이다.
|
|
150
95
|
// - 요청은 닫힌 유니온이다. 모르는 `type`·다른 유형의 키·오타는 400이다(`strictObject`).
|
|
151
96
|
// - 등록은 유형 안 필드에 기본값이 있다. 수정(PUT·PATCH)은 `fulfillment`를 통째로 받고 **유형 안 필드가 모두 필수다** —
|
|
152
|
-
// 통째 교체에 기본값을 두면 부분 수정이
|
|
153
|
-
// - 응답은 열린 유니온이다. 서버가 유형을 더해도
|
|
97
|
+
// 통째 교체에 기본값을 두면 부분 수정이 출고지를 조용히 되돌린다.
|
|
98
|
+
// - 응답은 열린 유니온이다. 서버가 유형을 더해도 SDK가 깨지지 않게 모르는 유형은
|
|
154
99
|
// `{ type: "OTHER", rawType }`로 읽는다. 알려진 유형의 모양이 틀리면 파싱 오류다.
|
|
155
100
|
|
|
156
101
|
/** 배송비 유형 — 무료·유료·조건부 무료 */
|
|
@@ -158,9 +103,6 @@ export const deliveryTypeSchema = z.enum(["FREE", "PAID", "CONDITIONAL_FREE"]);
|
|
|
158
103
|
|
|
159
104
|
export type DeliveryType = z.infer<typeof deliveryTypeSchema>;
|
|
160
105
|
|
|
161
|
-
/** 파일 다운로드 기본값 — 횟수 5회·기간 30일(#48 결정 8) */
|
|
162
|
-
export const DOWNLOAD_DEFAULTS = { limit: 5, days: 30 } as const;
|
|
163
|
-
|
|
164
106
|
const KNOWN_FULFILLMENT_TYPES: readonly string[] = fulfillmentTypeSchema.options;
|
|
165
107
|
|
|
166
108
|
const ADDRESS_ID_DESCRIPTIONS = {
|
|
@@ -170,9 +112,6 @@ const ADDRESS_ID_DESCRIPTIONS = {
|
|
|
170
112
|
return:
|
|
171
113
|
"반품지 주소 id(`GET /v1/seller-addresses?type=RETURN`). 등록에서 생략하면 그 시점 스토어 기본 반품지를 저장한다. " +
|
|
172
114
|
"`null`이면 비워 둔다(스토어 기본 반품지를 따른다). 이 스토어의 `RETURN` 주소가 아니면 거부한다",
|
|
173
|
-
pickup:
|
|
174
|
-
"방문 수령지 주소 id(`GET /v1/seller-addresses?type=PICKUP`). 등록에서 생략하면 그 시점 스토어 기본 방문 수령지를 저장하고, " +
|
|
175
|
-
"기본 방문 수령지도 없으면 400 `PICKUP_ADDRESS_REQUIRED`다. 이 스토어의 `PICKUP` 주소가 아니면 거부한다",
|
|
176
115
|
} as const;
|
|
177
116
|
|
|
178
117
|
/**
|
|
@@ -236,78 +175,12 @@ const shippingCreateSchema = z
|
|
|
236
175
|
})
|
|
237
176
|
.superRefine(refineShippingConsistency);
|
|
238
177
|
|
|
239
|
-
const downloadCreateSchema = z.strictObject({
|
|
240
|
-
limit: z
|
|
241
|
-
.number()
|
|
242
|
-
.int()
|
|
243
|
-
.min(1)
|
|
244
|
-
.max(100)
|
|
245
|
-
.default(DOWNLOAD_DEFAULTS.limit)
|
|
246
|
-
.describe("다운로드 횟수 한도(수량 1개당). 생략하면 5회"),
|
|
247
|
-
days: z
|
|
248
|
-
.number()
|
|
249
|
-
.int()
|
|
250
|
-
.min(1)
|
|
251
|
-
.max(3650)
|
|
252
|
-
.default(DOWNLOAD_DEFAULTS.days)
|
|
253
|
-
.describe("다운로드 기간(제공일부터 일수). 생략하면 30일"),
|
|
254
|
-
stockTracking: z
|
|
255
|
-
.boolean()
|
|
256
|
-
.default(false)
|
|
257
|
-
.describe("재고를 세는가. 생략하면 false(재고와 무관하게 판다)"),
|
|
258
|
-
withdrawalRestricted: z
|
|
259
|
-
.boolean()
|
|
260
|
-
.default(false)
|
|
261
|
-
.describe(
|
|
262
|
-
"청약철회 제한 대상인가. true면 주문서가 제한 고지 동의를 받고, 첫 다운로드 뒤 구매자 단순 변심 취소·반품을 받지 않는다",
|
|
263
|
-
),
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const codeCreateSchema = z.strictObject({
|
|
267
|
-
mode: fulfillmentModeSchema
|
|
268
|
-
.default("AUTO")
|
|
269
|
-
.describe(
|
|
270
|
-
"제공 방식. `AUTO`는 코드 풀에서 결제 확정과 함께 배정하고, `MANUAL`은 셀러가 제공 처리 때 코드를 넘긴다",
|
|
271
|
-
),
|
|
272
|
-
withdrawalRestricted: z
|
|
273
|
-
.boolean()
|
|
274
|
-
.default(false)
|
|
275
|
-
.describe("청약철회 제한 대상인가. true면 첫 열람 뒤 구매자 단순 변심 취소·반품을 받지 않는다"),
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const serviceCreateSchema = z.strictObject({
|
|
279
|
-
mode: fulfillmentModeSchema
|
|
280
|
-
.default("MANUAL")
|
|
281
|
-
.describe("제공 방식. `AUTO`는 결제 확정과 함께 제공 완료, `MANUAL`은 셀러가 제공 처리한다"),
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
const pickupCreateSchema = z.strictObject({
|
|
285
|
-
addressId: z.string().optional().describe(ADDRESS_ID_DESCRIPTIONS.pickup),
|
|
286
|
-
});
|
|
287
|
-
|
|
288
178
|
/**
|
|
289
|
-
* 상품 이행 — 등록(POST).
|
|
179
|
+
* 상품 이행 — 등록(POST). 배송 상품의 `shipping`을 생략하면 기본값(유료 배송·스토어 기본 배송비·기본 출고지·반품지)이다.
|
|
290
180
|
*/
|
|
291
181
|
export const productFulfillmentCreateSchema = z.discriminatedUnion("type", [
|
|
292
182
|
z.strictObject({ type: z.literal("SHIPPING"), shipping: shippingCreateSchema.optional() }),
|
|
293
|
-
z.strictObject({
|
|
294
|
-
type: z.literal("DOWNLOAD"),
|
|
295
|
-
download: downloadCreateSchema.default({
|
|
296
|
-
limit: DOWNLOAD_DEFAULTS.limit,
|
|
297
|
-
days: DOWNLOAD_DEFAULTS.days,
|
|
298
|
-
stockTracking: false,
|
|
299
|
-
withdrawalRestricted: false,
|
|
300
|
-
}),
|
|
301
|
-
}),
|
|
302
|
-
z.strictObject({
|
|
303
|
-
type: z.literal("CODE"),
|
|
304
|
-
code: codeCreateSchema.default({ mode: "AUTO", withdrawalRestricted: false }),
|
|
305
|
-
}),
|
|
306
|
-
z.strictObject({
|
|
307
|
-
type: z.literal("SERVICE"),
|
|
308
|
-
service: serviceCreateSchema.default({ mode: "MANUAL" }),
|
|
309
|
-
}),
|
|
310
|
-
z.strictObject({ type: z.literal("PICKUP"), pickup: pickupCreateSchema.default({}) }),
|
|
183
|
+
z.strictObject({ type: z.literal("MANUAL") }),
|
|
311
184
|
]);
|
|
312
185
|
|
|
313
186
|
export type ProductFulfillmentCreate = z.infer<typeof productFulfillmentCreateSchema>;
|
|
@@ -337,27 +210,7 @@ const shippingUpdateSchema = z
|
|
|
337
210
|
*/
|
|
338
211
|
export const productFulfillmentUpdateSchema = z.discriminatedUnion("type", [
|
|
339
212
|
z.strictObject({ type: z.literal("SHIPPING"), shipping: shippingUpdateSchema }),
|
|
340
|
-
z.strictObject({
|
|
341
|
-
type: z.literal("DOWNLOAD"),
|
|
342
|
-
download: z.strictObject({
|
|
343
|
-
limit: z.number().int().min(1).max(100),
|
|
344
|
-
days: z.number().int().min(1).max(3650),
|
|
345
|
-
stockTracking: z.boolean(),
|
|
346
|
-
withdrawalRestricted: z.boolean(),
|
|
347
|
-
}),
|
|
348
|
-
}),
|
|
349
|
-
z.strictObject({
|
|
350
|
-
type: z.literal("CODE"),
|
|
351
|
-
code: z.strictObject({ mode: fulfillmentModeSchema, withdrawalRestricted: z.boolean() }),
|
|
352
|
-
}),
|
|
353
|
-
z.strictObject({
|
|
354
|
-
type: z.literal("SERVICE"),
|
|
355
|
-
service: z.strictObject({ mode: fulfillmentModeSchema }),
|
|
356
|
-
}),
|
|
357
|
-
z.strictObject({
|
|
358
|
-
type: z.literal("PICKUP"),
|
|
359
|
-
pickup: z.strictObject({ addressId: z.string().describe(ADDRESS_ID_DESCRIPTIONS.pickup) }),
|
|
360
|
-
}),
|
|
213
|
+
z.strictObject({ type: z.literal("MANUAL") }),
|
|
361
214
|
]);
|
|
362
215
|
|
|
363
216
|
export type ProductFulfillmentUpdate = z.infer<typeof productFulfillmentUpdateSchema>;
|
|
@@ -389,37 +242,7 @@ export const productFulfillmentSchema = z.discriminatedUnion("type", [
|
|
|
389
242
|
.describe("반품지 주소 id. null이면 스토어 기본 반품지를 따른다"),
|
|
390
243
|
}),
|
|
391
244
|
}),
|
|
392
|
-
z.object({
|
|
393
|
-
type: z.literal("DOWNLOAD"),
|
|
394
|
-
download: z.object({
|
|
395
|
-
limit: z.number().int().describe("다운로드 횟수 한도(수량 1개당)"),
|
|
396
|
-
days: z.number().int().describe("다운로드 기간(제공일부터 일수)"),
|
|
397
|
-
stockTracking: z.boolean().describe("재고를 세는가"),
|
|
398
|
-
withdrawalRestricted: z.boolean().describe("청약철회 제한 대상인가"),
|
|
399
|
-
}),
|
|
400
|
-
}),
|
|
401
|
-
z.object({
|
|
402
|
-
type: z.literal("CODE"),
|
|
403
|
-
code: z.object({
|
|
404
|
-
mode: fulfillmentModeSchema.describe("제공 방식"),
|
|
405
|
-
withdrawalRestricted: z.boolean().describe("청약철회 제한 대상인가"),
|
|
406
|
-
}),
|
|
407
|
-
}),
|
|
408
|
-
z.object({
|
|
409
|
-
type: z.literal("SERVICE"),
|
|
410
|
-
service: z.object({ mode: fulfillmentModeSchema.describe("제공 방식") }),
|
|
411
|
-
}),
|
|
412
|
-
z.object({
|
|
413
|
-
type: z.literal("PICKUP"),
|
|
414
|
-
pickup: z.object({
|
|
415
|
-
addressId: z
|
|
416
|
-
.string()
|
|
417
|
-
.nullable()
|
|
418
|
-
.describe(
|
|
419
|
-
"방문 수령지 주소 id. null은 수령지가 지워진 상품이다(수령지를 다시 지정할 때까지 팔 수 없다)",
|
|
420
|
-
),
|
|
421
|
-
}),
|
|
422
|
-
}),
|
|
245
|
+
z.object({ type: z.literal("MANUAL") }),
|
|
423
246
|
]);
|
|
424
247
|
|
|
425
248
|
export type ProductFulfillment = z.infer<typeof productFulfillmentSchema>;
|
|
@@ -464,52 +287,11 @@ export const productFulfillmentResponseSchema = openFulfillmentUnion(
|
|
|
464
287
|
|
|
465
288
|
export type ProductFulfillmentView = z.infer<typeof productFulfillmentResponseSchema>;
|
|
466
289
|
|
|
467
|
-
// ── 판정 헬퍼 ──
|
|
468
|
-
|
|
469
|
-
/** 제공 방식 — CODE·SERVICE는 저장된 값, DOWNLOAD는 AUTO, PICKUP은 MANUAL, 배송 상품은 null */
|
|
470
|
-
export function fulfillmentModeOf(fulfillment: ProductFulfillment): FulfillmentMode | null {
|
|
471
|
-
switch (fulfillment.type) {
|
|
472
|
-
case "CODE":
|
|
473
|
-
return fulfillment.code.mode;
|
|
474
|
-
case "SERVICE":
|
|
475
|
-
return fulfillment.service.mode;
|
|
476
|
-
default:
|
|
477
|
-
return FULFILLMENT_RULES[fulfillment.type].defaultMode;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/** 재고를 세는가 — 파일 다운로드만 끌 수 있다 */
|
|
482
|
-
export function stockTrackingOf(fulfillment: ProductFulfillment): boolean {
|
|
483
|
-
return fulfillment.type === "DOWNLOAD" ? fulfillment.download.stockTracking : true;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/** 청약철회 제한 대상인가 — 파일 다운로드·코드 발급만 켤 수 있다(전자상거래법 제17조 제2항 제5호, 디지털 콘텐츠) */
|
|
487
|
-
export function withdrawalRestrictedOf(fulfillment: ProductFulfillment): boolean {
|
|
488
|
-
if (fulfillment.type === "DOWNLOAD") return fulfillment.download.withdrawalRestricted;
|
|
489
|
-
if (fulfillment.type === "CODE") return fulfillment.code.withdrawalRestricted;
|
|
490
|
-
return false;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/** 코드 풀을 쓰는가 — 코드 발급 + 자동 제공. 재고가 판매 가능 코드 수다 */
|
|
494
|
-
export function usesCodePool(fulfillment: ProductFulfillment): boolean {
|
|
495
|
-
return fulfillment.type === "CODE" && fulfillment.code.mode === "AUTO";
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/** 결제 확정과 함께 제공까지 끝내는가 — 비실물 + 자동 제공 */
|
|
499
|
-
export function autoFulfillableOf(fulfillment: ProductFulfillment): boolean {
|
|
500
|
-
return (
|
|
501
|
-
!FULFILLMENT_RULES[fulfillment.type].requiresShipping &&
|
|
502
|
-
fulfillmentModeOf(fulfillment) === "AUTO"
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
290
|
// ─── 주문상품 이행 스냅샷 (이슈 #57) ─────────────────────────────
|
|
507
291
|
|
|
508
292
|
/**
|
|
509
293
|
* 주문상품의 주문 시점 이행 — 결제 확정이 주문서 줄에서 굳힌다. 상품의 현재 값이 아니다. 서버는 주문상품 컬럼
|
|
510
|
-
* (`fulfillment_type`·`delivery_bundle_key
|
|
511
|
-
*
|
|
512
|
-
* `mode`는 **주문 시점 상품의 방식**이다. 실제 제공 방식은 제공 기록(`fulfillment.mode`)에 있고 다를 수 있다(자동 제공 상품의 수동 복구).
|
|
294
|
+
* (`fulfillment_type`·`delivery_bundle_key`)과 jsonb(`fulfillment_snapshot`)를 합쳐 이 모양을 만든다.
|
|
513
295
|
*/
|
|
514
296
|
export const orderItemFulfillmentSnapshotSchema = z.discriminatedUnion("type", [
|
|
515
297
|
z.object({
|
|
@@ -529,38 +311,7 @@ export const orderItemFulfillmentSnapshotSchema = z.discriminatedUnion("type", [
|
|
|
529
311
|
),
|
|
530
312
|
}),
|
|
531
313
|
}),
|
|
532
|
-
z.object({
|
|
533
|
-
type: z.literal("DOWNLOAD"),
|
|
534
|
-
download: z.object({
|
|
535
|
-
limit: z.number().int().describe("주문 시점 다운로드 횟수 한도(수량 1개당)"),
|
|
536
|
-
days: z.number().int().describe("주문 시점 다운로드 기간(제공일부터 일수)"),
|
|
537
|
-
stockTracked: z.boolean().describe("주문 시점에 재고를 뺐는가"),
|
|
538
|
-
withdrawalRestricted: z
|
|
539
|
-
.boolean()
|
|
540
|
-
.describe("구매자가 제한 고지에 동의한 청약철회 제한 주문상품인가"),
|
|
541
|
-
}),
|
|
542
|
-
}),
|
|
543
|
-
z.object({
|
|
544
|
-
type: z.literal("CODE"),
|
|
545
|
-
code: z.object({
|
|
546
|
-
mode: z.string().describe("주문 시점 상품의 제공 방식. 현재 값은 `AUTO`·`MANUAL`"),
|
|
547
|
-
withdrawalRestricted: z
|
|
548
|
-
.boolean()
|
|
549
|
-
.describe("구매자가 제한 고지에 동의한 청약철회 제한 주문상품인가"),
|
|
550
|
-
}),
|
|
551
|
-
}),
|
|
552
|
-
z.object({
|
|
553
|
-
type: z.literal("SERVICE"),
|
|
554
|
-
service: z.object({
|
|
555
|
-
mode: z.string().describe("주문 시점 상품의 제공 방식. 현재 값은 `AUTO`·`MANUAL`"),
|
|
556
|
-
}),
|
|
557
|
-
}),
|
|
558
|
-
z.object({
|
|
559
|
-
type: z.literal("PICKUP"),
|
|
560
|
-
pickup: z.object({
|
|
561
|
-
addressId: z.string().nullable().describe("주문 시점 방문 수령지 주소 id"),
|
|
562
|
-
}),
|
|
563
|
-
}),
|
|
314
|
+
z.object({ type: z.literal("MANUAL") }),
|
|
564
315
|
]);
|
|
565
316
|
|
|
566
317
|
export type OrderItemFulfillmentSnapshot = z.infer<typeof orderItemFulfillmentSnapshotSchema>;
|
|
@@ -568,7 +319,7 @@ export type OrderItemFulfillmentSnapshot = z.infer<typeof orderItemFulfillmentSn
|
|
|
568
319
|
export const orderItemFulfillmentSnapshotResponseSchema = openFulfillmentUnion(
|
|
569
320
|
orderItemFulfillmentSnapshotSchema,
|
|
570
321
|
).describe(
|
|
571
|
-
"주문 시점 이행 스냅샷. 상품을 나중에 고쳐도 바뀌지 않는다.
|
|
322
|
+
"주문 시점 이행 스냅샷. 상품을 나중에 고쳐도 바뀌지 않는다. 제공 기록은 `fulfillment`에 있다. 값이 추가될 수 있어 SDK는 모르는 유형을 `OTHER`로 읽는다",
|
|
572
323
|
);
|
|
573
324
|
|
|
574
325
|
export type OrderItemFulfillmentSnapshotView = z.infer<
|
package/src/schemas/misc.ts
CHANGED
|
@@ -149,7 +149,7 @@ export const eventTypeSchema = z
|
|
|
149
149
|
"ORDER.ADDRESS_CHANGE_REQUESTED",
|
|
150
150
|
// 셀러 직권취소 (이슈 #50). 뒤에 붙여 기존 값의 순서를 바꾸지 않는다
|
|
151
151
|
"ORDER.CANCELED",
|
|
152
|
-
//
|
|
152
|
+
// 배송 없는 상품 제공 (이슈 #48). 구독한 수신자에게만 간다
|
|
153
153
|
"ORDER.FULFILLED",
|
|
154
154
|
"CLAIM.CANCEL_REQUESTED",
|
|
155
155
|
"CLAIM.RETURN_REQUESTED",
|
package/src/schemas/order.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { changeTypeSchema, orderItemStatusSchema, type PageParams, pageSchema } from "./common";
|
|
3
|
-
import { orderItemDownloadSchema } from "./digital-asset";
|
|
4
3
|
import { orderItemFulfillmentSnapshotResponseSchema } from "./fulfillment";
|
|
5
|
-
import { MAX_CODE_LENGTH, orderItemCodeSchema } from "./fulfillment-code";
|
|
6
4
|
import { cashReceiptSchema, easyPayProviderResponseSchema } from "./payment";
|
|
5
|
+
import { customInputValueSchema, optionSelectionSchema } from "./product";
|
|
7
6
|
|
|
8
7
|
export const orderSummarySchema = z.object({
|
|
9
8
|
orderId: z.string(),
|
|
@@ -33,55 +32,23 @@ export const shippingAddressSchema = z.object({
|
|
|
33
32
|
|
|
34
33
|
export type ShippingAddress = z.infer<typeof shippingAddressSchema>;
|
|
35
34
|
|
|
36
|
-
/**
|
|
35
|
+
/** 배송 없는 주문상품(`MANUAL`)의 제공 기록 (이슈 #48) — 셀러의 제공 처리가 만든다 */
|
|
37
36
|
export const orderItemFulfillmentSchema = z.object({
|
|
38
37
|
status: z
|
|
39
38
|
.string()
|
|
40
39
|
.describe(
|
|
41
|
-
"제공 상태. `FULFILLED`(제공함)·`REVOKED`(
|
|
40
|
+
"제공 상태. `FULFILLED`(제공함)·`REVOKED`(전량 취소·환불로 회수함). 값이 추가될 수 있어 문자열로 받는다",
|
|
42
41
|
),
|
|
43
|
-
mode: z.string().nullable().describe("제공 방식. `AUTO`·`MANUAL`"),
|
|
44
42
|
fulfilledAt: z.string().describe("제공 시각 (ISO 8601)"),
|
|
45
|
-
note: z.string().nullable().describe("셀러가 남긴 이용 안내. 없으면 null"),
|
|
46
|
-
|
|
47
|
-
.
|
|
48
|
-
.optional()
|
|
49
|
-
.default([])
|
|
50
|
-
.describe(
|
|
51
|
-
"코드 발급 상품에 배정한 코드의 가린 값. 평문은 싣지 않는다. 코드 상품이 아니면 빈 배열. 서버는 항상 싣는다",
|
|
52
|
-
),
|
|
53
|
-
download: orderItemDownloadSchema
|
|
54
|
-
.nullable()
|
|
43
|
+
note: z.string().nullable().describe("셀러가 제공 처리 때 남긴 이용 안내. 없으면 null"),
|
|
44
|
+
mode: z
|
|
45
|
+
.string()
|
|
55
46
|
.optional()
|
|
56
|
-
.default(null)
|
|
57
47
|
.describe(
|
|
58
|
-
"
|
|
48
|
+
"**Deprecated** — 항상 `MANUAL`이다. 자동 제공을 없앴고(이슈 #48) store-sdk 0.19 이하 호환으로만 싣는다. 게시된 연동이 store-sdk 0.20 이상으로 넘어간 뒤 다음 릴리스에서 뺀다",
|
|
59
49
|
),
|
|
60
|
-
mail: z
|
|
61
|
-
.object({
|
|
62
|
-
status: z
|
|
63
|
-
.string()
|
|
64
|
-
.describe(
|
|
65
|
-
"제공 안내 메일 상태. 현재 값은 `PENDING`(보낼 예정)·`SENDING`·`SENT`·`FAILED`·`SKIPPED`(보내지 않음: 스토어 설정 꺼짐·받을 주소 없음)이고 값이 추가될 수 있다",
|
|
66
|
-
),
|
|
67
|
-
sentAt: z.string().nullable(),
|
|
68
|
-
errorCode: z.string().nullable().describe("실패·건너뜀 사유 코드"),
|
|
69
|
-
})
|
|
70
|
-
.nullable()
|
|
71
|
-
.optional()
|
|
72
|
-
.default(null)
|
|
73
|
-
.describe("구매자에게 보낸 제공 안내 메일(이슈 #48). 기록이 없으면 null. 서버는 항상 싣는다"),
|
|
74
50
|
});
|
|
75
51
|
|
|
76
|
-
/** 제공 안내 메일 재발송 결과 */
|
|
77
|
-
export const fulfillmentMailResendSchema = z.object({
|
|
78
|
-
mailStatus: z
|
|
79
|
-
.string()
|
|
80
|
-
.describe("재발송 뒤 메일 상태 — `SENT`·`PENDING`(곧 다시 보낸다)·`FAILED`·`SKIPPED`"),
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
export type FulfillmentMailResend = z.infer<typeof fulfillmentMailResendSchema>;
|
|
84
|
-
|
|
85
52
|
export type OrderItemFulfillment = z.infer<typeof orderItemFulfillmentSchema>;
|
|
86
53
|
|
|
87
54
|
export const orderItemSchema = z.object({
|
|
@@ -93,10 +60,24 @@ export const orderItemSchema = z.object({
|
|
|
93
60
|
productName: z.string(),
|
|
94
61
|
optionId: z.string().nullable(),
|
|
95
62
|
optionName: z.string().nullable(),
|
|
63
|
+
optionSelections: z
|
|
64
|
+
.array(optionSelectionSchema)
|
|
65
|
+
.optional()
|
|
66
|
+
.default([])
|
|
67
|
+
.describe(
|
|
68
|
+
"주문 시점 옵션 선택(이슈 #59) — 조합 옵션(옵션명마다 한 칸, 추가금 0)과 추가 선택(값의 추가금). 옵션이 없거나 이 필드 이전 주문은 빈 배열. 서버는 항상 싣는다",
|
|
69
|
+
),
|
|
70
|
+
customInputs: z
|
|
71
|
+
.array(customInputValueSchema)
|
|
72
|
+
.optional()
|
|
73
|
+
.default([])
|
|
74
|
+
.describe(
|
|
75
|
+
"구매자가 적은 직접 입력값(주문 시점 항목명과 값, 이슈 #59). 없으면 빈 배열. 서버는 항상 싣는다",
|
|
76
|
+
),
|
|
96
77
|
sellerCode: z.string().nullable(),
|
|
97
78
|
fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema.describe(
|
|
98
79
|
"주문 시점 이행 스냅샷(이슈 #57). `type`이 주문 시점 상품 유형이다. 배송 상품이 아니면 발송·배송완료 대신 제공 처리를 하고, " +
|
|
99
|
-
"`DELIVERED`는 제공
|
|
80
|
+
"`DELIVERED`는 제공 완료다. 상품을 나중에 고쳐도 바뀌지 않는다. 제공 기록은 `fulfillment`에 있다",
|
|
100
81
|
),
|
|
101
82
|
quantity: z.number().int().describe("주문 수량"),
|
|
102
83
|
canceledQuantity: z
|
|
@@ -128,7 +109,7 @@ export const orderItemSchema = z.object({
|
|
|
128
109
|
.optional()
|
|
129
110
|
.default(null)
|
|
130
111
|
.describe(
|
|
131
|
-
"
|
|
112
|
+
"배송 없는 상품의 제공 기록(이슈 #48). 제공 전이거나 배송 상품이면 null이다. 서버는 항상 싣는다",
|
|
132
113
|
),
|
|
133
114
|
autoDecisionDate: z.string().nullable(),
|
|
134
115
|
sellerMemo: z.string().nullable(),
|
|
@@ -242,14 +223,6 @@ export const orderSchema = z.object({
|
|
|
242
223
|
"현금영수증. 구매자가 계좌이체·가상계좌 결제에서 신청했을 때만 있고 그 밖에는 null. 서버는 항상 싣는다",
|
|
243
224
|
),
|
|
244
225
|
}),
|
|
245
|
-
withdrawalAgreedAt: z
|
|
246
|
-
.string()
|
|
247
|
-
.nullable()
|
|
248
|
-
.optional()
|
|
249
|
-
.default(null)
|
|
250
|
-
.describe(
|
|
251
|
-
"청약철회 제한 안내에 동의한 시각(이슈 #48). 제한 상품이 없던 주문은 null. 서버는 항상 싣는다",
|
|
252
|
-
),
|
|
253
226
|
testPayment: z
|
|
254
227
|
.boolean()
|
|
255
228
|
.describe(
|
|
@@ -298,7 +271,7 @@ export const orderListQuerySchema = z.object({
|
|
|
298
271
|
.string()
|
|
299
272
|
.optional()
|
|
300
273
|
.describe(
|
|
301
|
-
"상품 유형 필터(이슈 #48) — 이 유형(`SHIPPING`·`
|
|
274
|
+
"상품 유형 필터(이슈 #48) — 이 유형(`SHIPPING`·`MANUAL`)의 주문상품이 하나라도 있는 주문",
|
|
302
275
|
),
|
|
303
276
|
testPayment: testPaymentQuery,
|
|
304
277
|
...pageQuery,
|
|
@@ -489,7 +462,7 @@ export const cancelBySellerRequestSchema = z.object({
|
|
|
489
462
|
export type CancelBySellerRequest = z.infer<typeof cancelBySellerRequestSchema>;
|
|
490
463
|
|
|
491
464
|
/**
|
|
492
|
-
* 제공 처리 요청 (이슈 #48) — `POST /v1/order-items/{orderItemId}/fulfill`.
|
|
465
|
+
* 제공 처리 요청 (이슈 #48) — `POST /v1/order-items/{orderItemId}/fulfill`. 배송 없는 주문상품(`MANUAL`)을
|
|
493
466
|
* 제공 완료(`DELIVERED`)로 바꾼다.
|
|
494
467
|
*/
|
|
495
468
|
export const fulfillRequestSchema = z.object({
|
|
@@ -499,15 +472,6 @@ export const fulfillRequestSchema = z.object({
|
|
|
499
472
|
.max(1000)
|
|
500
473
|
.optional()
|
|
501
474
|
.describe("구매자에게 보일 이용 안내(예: 예약 방법, 수령 장소). 주문 상세에 실린다"),
|
|
502
|
-
codes: z
|
|
503
|
-
.array(z.string().trim().min(1).max(MAX_CODE_LENGTH))
|
|
504
|
-
.min(1)
|
|
505
|
-
.max(100)
|
|
506
|
-
.optional()
|
|
507
|
-
.describe(
|
|
508
|
-
"수동 제공 코드 발급 상품의 코드 — 셀러 시스템이 발급한 코드를 넘긴다. 남은 수량만큼 보낸다(`CODE_COUNT_MISMATCH`). " +
|
|
509
|
-
"암호화해 저장하고 구매자가 열람할 때만 평문으로 나간다. 다른 유형은 보내지 않는다",
|
|
510
|
-
),
|
|
511
475
|
});
|
|
512
476
|
|
|
513
477
|
export type FulfillRequest = z.infer<typeof fulfillRequestSchema>;
|