@sayren/store-sdk 0.16.0 → 0.18.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.
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * 파일 다운로드 상품의 판매 파일 (이슈 #48 단계 3).
5
+ *
6
+ * 파일은 CDN **비공개 객체**다. `POST /v1/uploads`(`purpose: "digital-asset"`)로 자리를 받아 응답의 `headers`와 함께 PUT하고,
7
+ * 응답의 `key`로 상품에 연결한다. 객체 키와 파일 주소는 어떤 응답에도 싣지 않는다 — 구매자는 제공된 주문상품에서
8
+ * 수명 300초의 서명 주소로만 받는다. 주문은 주문 시점 파일을 스냅샷하므로 파일을 바꿔도 이미 산 구매자는 옛 파일을 받는다.
9
+ */
10
+
11
+ export const digitalAssetAttachRequestSchema = z.object({
12
+ key: z
13
+ .string()
14
+ .min(1)
15
+ .max(512)
16
+ .describe("업로드 자리 발급 응답의 `key`. 이 스토어의 비공개 업로드 키여야 한다"),
17
+ fileName: z.string().trim().min(1).max(200).describe("구매자에게 보일 파일 이름(확장자 포함)"),
18
+ sizeBytes: z.number().int().positive().optional().describe("파일 크기(바이트). 안내용이다"),
19
+ contentType: z.string().max(200).optional().describe("파일 형식(MIME). 안내용이다"),
20
+ variantId: z
21
+ .string()
22
+ .optional()
23
+ .describe("이 파일을 줄 옵션(variant). 생략하면 모든 옵션의 구매자가 받는다"),
24
+ });
25
+ export type DigitalAssetAttachRequest = z.infer<typeof digitalAssetAttachRequestSchema>;
26
+
27
+ /** 판매 파일 — 객체 키·주소는 싣지 않는다 */
28
+ export const digitalAssetSchema = z.object({
29
+ assetId: z.string(),
30
+ productId: z.string(),
31
+ variantId: z.string().nullable().describe("옵션 전용 파일이면 그 옵션, 모든 옵션 공통이면 null"),
32
+ fileName: z.string(),
33
+ contentType: z.string().nullable(),
34
+ sizeBytes: z.number().int().nullable(),
35
+ status: z
36
+ .string()
37
+ .describe(
38
+ "`ACTIVE`(새 주문에 제공)·`REMOVED`(해제 — 이미 산 구매자는 계속 받는다). 값이 추가될 수 있어 문자열로 받는다",
39
+ ),
40
+ createdAt: z.string(),
41
+ });
42
+ export type DigitalAsset = z.infer<typeof digitalAssetSchema>;
43
+
44
+ /** 셀러 확인용 서명 주소 — `POST /v1/products/{productId}/digital-assets/{assetId}/preview-url` */
45
+ export const digitalAssetPreviewSchema = z.object({
46
+ url: z.url().describe("수명 300초의 서명 주소"),
47
+ expiresAt: z.string(),
48
+ });
49
+ export type DigitalAssetPreview = z.infer<typeof digitalAssetPreviewSchema>;
50
+
51
+ /** 주문상품의 다운로드 제공 정보 — 주문 응답의 `fulfillment.download` */
52
+ export const orderItemDownloadSchema = z.object({
53
+ count: z.number().int().describe("지금까지 받은 횟수"),
54
+ limit: z.number().int().describe("받을 수 있는 횟수"),
55
+ expiresAt: z.string().nullable().describe("받을 수 있는 기한 (ISO 8601)"),
56
+ firstDownloadedAt: z.string().nullable().describe("처음 받은 시각 — 제공 개시 증거다"),
57
+ files: z.array(
58
+ z.object({
59
+ assetId: z.string(),
60
+ fileName: z.string(),
61
+ sizeBytes: z.number().int().nullable(),
62
+ }),
63
+ ),
64
+ });
65
+ export type OrderItemDownload = z.infer<typeof orderItemDownloadSchema>;
@@ -0,0 +1,143 @@
1
+ import { z } from "zod";
2
+ import { pageQueryShape, pageSchema } from "./common";
3
+
4
+ /**
5
+ * 코드 발급 상품의 코드 풀 (이슈 #48 단계 2) — 라이선스 키·기프트 코드처럼 결제 확정과 함께 한 개씩 배정하는 코드다.
6
+ *
7
+ * **평문 코드는 두 경로로만 나간다** — 구매자 열람(`POST /storefront/v1/me/order-items/{id}/fulfillment/reveal`)과 셀러 콘솔
8
+ * 열람(`POST /v1/fulfillment-codes/{codeId}/reveal`, 콘솔 전용). 목록·주문·웹훅·API 로그에는 가린 값(`hint`)만 싣는다.
9
+ * 코드는 결제 자격 증명과 같은 키링·AES-256-GCM으로 암호화해 저장한다.
10
+ */
11
+
12
+ /**
13
+ * 코드 상태.
14
+ * - `AVAILABLE` 판매 가능 — 재고로 센다
15
+ * - `ASSIGNED` 주문상품에 배정됨
16
+ * - `REVOKED` 환불로 회수됨(구매자 화면에서 숨긴다. 코드 자체의 무효화는 셀러 시스템 몫이다)
17
+ * - `EXPIRED` 유효기간이 지나 배정하지 않음
18
+ * - `VOID` 셀러가 폐기함
19
+ */
20
+ export const fulfillmentCodeStatusSchema = z.enum([
21
+ "AVAILABLE",
22
+ "ASSIGNED",
23
+ "REVOKED",
24
+ "EXPIRED",
25
+ "VOID",
26
+ ]);
27
+ export type FulfillmentCodeStatus = z.infer<typeof fulfillmentCodeStatusSchema>;
28
+
29
+ /** 한 번에 올릴 수 있는 코드 수 */
30
+ export const MAX_CODES_PER_UPLOAD = 10_000;
31
+ /** 코드 한 개의 최대 길이 */
32
+ export const MAX_CODE_LENGTH = 200;
33
+
34
+ /**
35
+ * 코드 일괄 업로드 요청 — `POST /v1/products/{productId}/codes`. `codes`(배열)와 `csv`(본문) 중 하나를 보낸다.
36
+ * 앞뒤 공백은 지우고 빈 줄은 건너뛴다. 같은 상품에 이미 있는 코드와 요청 안의 중복은 받지 않는다(`duplicates`).
37
+ */
38
+ export const fulfillmentCodeUploadRequestSchema = z
39
+ .object({
40
+ variantId: z
41
+ .string()
42
+ .optional()
43
+ .describe("코드를 채울 옵션(variant) ID. 옵션 없는 상품은 생략한다(기본 옵션)"),
44
+ codes: z
45
+ .array(z.string())
46
+ .max(MAX_CODES_PER_UPLOAD)
47
+ .optional()
48
+ .describe(`코드 목록. 한 번에 ${MAX_CODES_PER_UPLOAD}개까지. \`csv\`와 둘 중 하나만 보낸다`),
49
+ csv: z
50
+ .string()
51
+ .max(4_000_000)
52
+ .optional()
53
+ .describe(
54
+ "CSV 본문 — 줄마다 첫 칸이 코드다. 첫 줄의 첫 칸이 `code`면 머리글로 보고 건너뛴다. " +
55
+ `한 번에 ${MAX_CODES_PER_UPLOAD}줄까지`,
56
+ ),
57
+ expiresAt: z.iso
58
+ .datetime({ offset: true })
59
+ .optional()
60
+ .describe("이 업로드 코드의 유효기간 (ISO 8601). 지나면 배정하지 않고 만료로 바꾼다"),
61
+ })
62
+ .refine((value) => (value.codes === undefined) !== (value.csv === undefined), {
63
+ message: "codes와 csv 중 하나만 보내야 합니다",
64
+ });
65
+ export type FulfillmentCodeUploadRequest = z.infer<typeof fulfillmentCodeUploadRequestSchema>;
66
+
67
+ /** 받지 않은 줄 */
68
+ export const fulfillmentCodeRejectionSchema = z.object({
69
+ line: z.number().int().describe("요청 안의 위치 (1부터). CSV는 머리글을 포함한 줄 번호다"),
70
+ reason: z
71
+ .string()
72
+ .describe(
73
+ "사유. `DUPLICATE`(이미 있는 코드이거나 요청 안에서 겹침)·`TOO_LONG`(200자 초과)·`INVALID_CHARACTER`(제어 문자). 값이 추가될 수 있어 문자열로 받는다",
74
+ ),
75
+ });
76
+
77
+ /** 업로드 결과 — 배치 요약 */
78
+ export const fulfillmentCodeBatchSchema = z.object({
79
+ batchId: z.string(),
80
+ productId: z.string(),
81
+ variantId: z.string(),
82
+ total: z.number().int().describe("요청에서 읽은 코드 수(빈 줄·머리글 제외)"),
83
+ accepted: z.number().int().describe("받은 코드 수 — 재고가 이만큼 는다"),
84
+ duplicates: z.number().int().describe("중복이라 받지 않은 코드 수"),
85
+ rejected: z
86
+ .array(fulfillmentCodeRejectionSchema)
87
+ .describe("받지 않은 줄(중복 포함). 최대 1,000개까지 싣는다"),
88
+ expiresAt: z.string().nullable(),
89
+ createdAt: z.string(),
90
+ });
91
+ export type FulfillmentCodeBatch = z.infer<typeof fulfillmentCodeBatchSchema>;
92
+
93
+ /** 코드 목록의 한 줄 — **가린 값만** 싣는다 */
94
+ export const fulfillmentCodeSchema = z.object({
95
+ codeId: z.string(),
96
+ productId: z.string(),
97
+ variantId: z.string(),
98
+ batchId: z.string().nullable().describe("업로드 배치. 수동 제공으로 셀러가 넘긴 코드는 null"),
99
+ hint: z.string().describe("가린 코드 — 끝 4자만 보인다(예: `****AB12`)"),
100
+ status: z
101
+ .string()
102
+ .describe(
103
+ "코드 상태. `AVAILABLE`·`ASSIGNED`·`REVOKED`·`EXPIRED`·`VOID`. 값이 추가될 수 있어 문자열로 받는다",
104
+ ),
105
+ orderItemId: z.string().nullable().describe("배정된 주문상품. 배정 전이면 null"),
106
+ assignedAt: z.string().nullable(),
107
+ revealedAt: z.string().nullable().describe("구매자가 처음 열람한 시각 — 제공 개시 증거다"),
108
+ expiresAt: z.string().nullable(),
109
+ createdAt: z.string(),
110
+ });
111
+ export type FulfillmentCode = z.infer<typeof fulfillmentCodeSchema>;
112
+
113
+ export const fulfillmentCodePageSchema = pageSchema(fulfillmentCodeSchema).extend({
114
+ available: z.number().int().describe("판매 가능한 코드 수(필터와 무관한 상품 전체)"),
115
+ });
116
+ export type FulfillmentCodePage = z.infer<typeof fulfillmentCodePageSchema>;
117
+
118
+ /** `GET /v1/products/{productId}/codes` 쿼리 */
119
+ export const fulfillmentCodeListQuerySchema = z.object({
120
+ status: fulfillmentCodeStatusSchema.optional().describe("상태 필터"),
121
+ variantId: z.string().optional().describe("옵션 필터"),
122
+ batchId: z.string().optional().describe("업로드 배치 필터"),
123
+ orderItemId: z.string().optional().describe("주문상품 필터"),
124
+ ...pageQueryShape(),
125
+ });
126
+
127
+ /** 셀러 콘솔 코드 열람 응답 — `POST /v1/fulfillment-codes/{codeId}/reveal` */
128
+ export const fulfillmentCodeRevealSchema = z.object({
129
+ codeId: z.string(),
130
+ code: z.string().describe("코드 평문. 응답은 캐시하지 않는다(`Cache-Control: no-store`)"),
131
+ });
132
+ export type FulfillmentCodeReveal = z.infer<typeof fulfillmentCodeRevealSchema>;
133
+
134
+ /** 주문상품에 배정된 코드의 가린 값 — 주문 응답의 `fulfillment.codes` */
135
+ export const orderItemCodeSchema = z.object({
136
+ codeId: z.string(),
137
+ hint: z.string().describe("가린 코드 — 끝 4자만 보인다"),
138
+ status: z
139
+ .string()
140
+ .describe("`ASSIGNED`(유효)·`REVOKED`(환불로 회수). 값이 추가될 수 있어 문자열로 받는다"),
141
+ revealedAt: z.string().nullable().describe("구매자가 처음 열람한 시각. 열람 전이면 null"),
142
+ });
143
+ export type OrderItemCode = z.infer<typeof orderItemCodeSchema>;