@sayren/store-sdk 0.19.0 → 0.20.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.
@@ -5,10 +5,8 @@ import { z } from "zod";
5
5
  *
6
6
  * - `product-image`: 상품 목록·상세의 상품 이미지. 목록 썸네일로 쓰이므로 정지 이미지만 받는다.
7
7
  * - `description`: 상세설명 본문에 삽입하는 미디어. 동영상과 svg도 받는다.
8
- * - `digital-asset`: 파일 다운로드 상품의 판매 파일(이슈 #48). **비공개 객체**로 올라가고 공개 주소가 없다 — 구매자는 제공된
9
- * 주문상품에서 수명 300초의 서명 주소로만 받는다.
10
8
  */
11
- export const uploadPurposeSchema = z.enum(["product-image", "description", "digital-asset"]);
9
+ export const uploadPurposeSchema = z.enum(["product-image", "description"]);
12
10
 
13
11
  export type UploadPurpose = z.infer<typeof uploadPurposeSchema>;
14
12
 
@@ -36,37 +34,6 @@ export const UPLOAD_PURPOSE_LIMITS: Record<UploadPurpose, UploadPurposeLimit> =
36
34
  extensions: ["jpg", "jpeg", "png", "gif", "webp", "avif", "svg", "mp4", "webm", "mov"],
37
35
  maxBytes: 50 * MB,
38
36
  },
39
- // 판매 파일 — 실행 파일(exe·dmg 등)은 받지 않는다. 크기 상한은 CDN 업로드 한도 안으로 잡는다
40
- "digital-asset": {
41
- extensions: [
42
- "pdf",
43
- "epub",
44
- "zip",
45
- "mp3",
46
- "wav",
47
- "flac",
48
- "m4a",
49
- "mp4",
50
- "mov",
51
- "jpg",
52
- "jpeg",
53
- "png",
54
- "webp",
55
- "psd",
56
- "ai",
57
- "txt",
58
- "csv",
59
- "xlsx",
60
- "docx",
61
- "pptx",
62
- ],
63
- maxBytes: 200 * MB,
64
- },
65
- };
66
-
67
- /** 비공개 업로드(판매 파일)의 PUT 필수 헤더 — CDN이 이 값으로 공개 조회를 막는다 */
68
- export const PRIVATE_UPLOAD_HEADERS: Readonly<Record<string, string>> = {
69
- "x-amz-meta-visibility": "private",
70
37
  };
71
38
 
72
39
  /** 파일명의 소문자 확장자. 확장자가 없으면 `""` */
@@ -133,23 +100,10 @@ export const uploadRequestSchema = z.object({
133
100
  export type UploadRequest = z.infer<typeof uploadRequestSchema>;
134
101
 
135
102
  export const uploadTicketSchema = z.object({
136
- /** 업로드 완료 후 공개될 주소. 비공개 업로드(`digital-asset`)는 이 주소로 받을 수 없다 */
103
+ /** 업로드 완료 후 공개될 주소 */
137
104
  url: z.url(),
138
105
  /** 파일 바이트를 PUT할 presigned URL. 1시간 유효 */
139
106
  uploadUrl: z.url(),
140
- key: z
141
- .string()
142
- .optional()
143
- .describe(
144
- "객체 키. 비공개 업로드(`digital-asset`)는 이 값으로 상품에 파일을 연결한다(`POST /v1/products/{productId}/digital-assets`). 공개 업로드에는 없다",
145
- ),
146
- headers: z
147
- .record(z.string(), z.string())
148
- .optional()
149
- .default({})
150
- .describe(
151
- "PUT에 반드시 실을 헤더. 비공개 업로드는 `x-amz-meta-visibility: private`이고 빠뜨리면 공개 객체가 된다. 공개 업로드는 빈 객체다",
152
- ),
153
107
  });
154
108
 
155
109
  export type UploadTicket = z.infer<typeof uploadTicketSchema>;
@@ -57,22 +57,21 @@ export const orderWebhookEventDataSchema = z.object({
57
57
  .string()
58
58
  .optional()
59
59
  .describe(
60
- "이벤트를 만든 주문상품 ID. 비실물 상품 제공으로 보내는 `ORDER.DELIVERED`에만 실린다. " +
60
+ "이벤트를 만든 주문상품 ID. 배송 없는 상품 제공으로 보내는 `ORDER.DELIVERED`에만 실린다. " +
61
61
  "택배 배송완료와 그 밖의 주문 이벤트에는 없다",
62
62
  ),
63
63
  fulfillmentSnapshot: orderItemFulfillmentSnapshotResponseSchema
64
64
  .optional()
65
65
  .describe(
66
66
  "이벤트를 만든 주문상품의 주문 시점 이행 스냅샷(주문상품 응답의 `fulfillmentSnapshot`과 같다, 이슈 #57). " +
67
- "비실물 상품의 제공 완료는 `ORDER.DELIVERED`로도 오고 `type`이 `SHIPPING`이 아니다. `orderItemId`가 있는 이벤트에만 실린다",
67
+ "배송 없는 상품의 제공 완료는 `ORDER.DELIVERED`로도 오고 `type`이 `MANUAL`이다. `orderItemId`가 있는 이벤트에만 실린다",
68
68
  ),
69
69
  });
70
70
  export type OrderWebhookEventData = z.infer<typeof orderWebhookEventDataSchema>;
71
71
 
72
72
  /**
73
- * `ORDER.FULFILLED` — 비실물 주문상품(파일·코드·이용권·방문 수령)을 제공했다(이슈 #48). 자동 제공은 결제 확정과 함께,
74
- * 수동 제공은 셀러의 제공 처리(`POST /v1/order-items/{orderItemId}/fulfill`)로 온다. 기존 구독자를 위해 같은 순간
75
- * `ORDER.DELIVERED`도 함께 보낸다. **코드·파일 주소는 싣지 않는다** — 관리 API로 조회한다.
73
+ * `ORDER.FULFILLED` — 배송 없는 주문상품(`MANUAL`)을 제공했다(이슈 #48). 셀러의 제공 처리
74
+ * (`POST /v1/order-items/{orderItemId}/fulfill`)로 온다. 기존 구독자를 위해 같은 순간 `ORDER.DELIVERED`도 함께 보낸다.
76
75
  */
77
76
  export const orderFulfilledWebhookEventDataSchema = z.object({
78
77
  orderId: z.string().describe("주문 ID"),
@@ -82,14 +81,11 @@ export const orderFulfilledWebhookEventDataSchema = z.object({
82
81
  "제공한 주문상품의 주문 시점 이행 스냅샷(주문상품 응답의 `fulfillmentSnapshot`과 같다, 이슈 #57)",
83
82
  ),
84
83
  fulfillment: z
85
- .object({
86
- mode: z
87
- .string()
88
- .describe(
89
- "실제 제공 방식. `AUTO`(결제 확정과 함께)·`MANUAL`(셀러 제공 처리). 주문상품 제공 기록의 `mode`와 같다",
90
- ),
91
- })
92
- .describe("제공 기록"),
84
+ .object({ mode: z.string() })
85
+ .optional()
86
+ .describe(
87
+ '**Deprecated** — 항상 `{ mode: "MANUAL" }`이다. 자동 제공을 없앴고(이슈 #48) store-sdk 0.19 이하 호환으로만 싣는다. 게시된 연동이 store-sdk 0.20 이상으로 넘어간 뒤 다음 릴리스에서 뺀다',
88
+ ),
93
89
  fulfilledAt: z.string().describe("제공 시각 (ISO 8601). 주문상품의 `deliveredAt`과 같다"),
94
90
  testPayment: z
95
91
  .boolean()
@@ -1,65 +0,0 @@
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>;
@@ -1,143 +0,0 @@
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>;