@sayren/storefront-sdk 0.6.0 → 0.8.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.
@@ -37,6 +37,11 @@ export const checkoutSessionSchema = z.object({
37
37
  totalAmount: z.number().int(),
38
38
  }),
39
39
  expiresAt: z.string(),
40
+ paymentOptions: z
41
+ .lazy(() => paymentOptionListSchema)
42
+ .describe(
43
+ "이 주문서에서 고를 수 있는 결제 옵션, PG 우선순위순. 셀러가 켠 PG·결제수단·간편결제만 담긴다. 비어 있으면 지금 결제할 수 없다",
44
+ ),
40
45
  testPayment: z
41
46
  .boolean()
42
47
  .describe(
@@ -56,6 +61,70 @@ export const paymentMethodSchema = z.enum([
56
61
 
57
62
  export type PaymentMethod = z.infer<typeof paymentMethodSchema>;
58
63
 
64
+ /** 결제를 처리하는 PG. 셀러가 직접 계약하고 콘솔 설정 › 결제에서 연결한다 */
65
+ export const pgProviderSchema = z.enum(["tosspayments", "portone"]);
66
+ export type PgProvider = z.infer<typeof pgProviderSchema>;
67
+
68
+ /** 간편결제사. 토스페이먼츠·포트원 모두 같은 코드를 쓴다 */
69
+ export const easyPayProviderSchema = z.enum(["NAVERPAY", "KAKAOPAY", "TOSSPAY", "PAYCO"]);
70
+ export type EasyPayProvider = z.infer<typeof easyPayProviderSchema>;
71
+
72
+ /**
73
+ * 응답에 실리는 간편결제사 — 서버가 간편결제사를 추가해도 파싱이 깨지지 않게 문자열로 받는다
74
+ * (`pgProvider`·결제 상태와 같은 방식). 요청과 결제 옵션은 값을 골라 보내는 자리라 enum 그대로다.
75
+ */
76
+ export const easyPayProviderResponseSchema = z.string();
77
+
78
+ const pg = pgProviderSchema.describe("결제를 처리할 PG");
79
+
80
+ /**
81
+ * 결제 옵션 — 어느 PG로 어떤 결제수단을 쓸지. `method`로 좁히면 필요한 필드가 타입으로 정해진다
82
+ * (`EASY_PAY`만 `provider`가 있다). 주문서의 `paymentOptions` 항목을 그대로 넘기거나 직접 써도 된다.
83
+ * 셀러가 켜지 않은 조합이면 서버가 409 `PAYMENT_OPTION_UNAVAILABLE`로 거절한다.
84
+ */
85
+ export const paymentOptionSchema = z.discriminatedUnion("method", [
86
+ z.object({ pg, method: z.literal("CARD") }),
87
+ z.object({
88
+ pg,
89
+ method: z.literal("EASY_PAY"),
90
+ provider: easyPayProviderSchema.describe("간편결제사 — 이 간편결제 결제창을 바로 연다"),
91
+ }),
92
+ z.object({ pg, method: z.literal("BANK_TRANSFER") }),
93
+ z.object({ pg, method: z.literal("VIRTUAL_ACCOUNT") }),
94
+ z.object({ pg, method: z.literal("MOBILE") }),
95
+ ]);
96
+ export type PaymentOption = z.infer<typeof paymentOptionSchema>;
97
+
98
+ /** 주문서가 내려주는 결제 옵션 — 결제 옵션 + 표시 이름 */
99
+ export const availablePaymentOptionSchema = paymentOptionSchema.and(
100
+ z.object({
101
+ label: z.string().describe("결제수단 표시 이름 (예: 신용·체크카드, 네이버페이)"),
102
+ pgName: z.string().describe("PG 표시 이름 (예: 토스페이먼츠)"),
103
+ }),
104
+ );
105
+ export type AvailablePaymentOption = z.infer<typeof availablePaymentOptionSchema>;
106
+
107
+ /**
108
+ * 결제 옵션 목록 — 서버가 나중에 추가한 결제수단·PG를 이 SDK가 모르면 **그 항목만 빼고** 읽는다(목록 파싱은 깨지지 않는다).
109
+ *
110
+ * 모르는 항목을 버리는 일은 `preprocess`가 하고 항목 타입은 그대로 남는다 — OpenAPI 문서에 `items` 없는
111
+ * 배열로 나가지 않게 하기 위해서다(`z.array(z.unknown()).transform(...)`은 출력 타입을 표현하지 못한다).
112
+ */
113
+ export const paymentOptionListSchema = z.preprocess(
114
+ (value) =>
115
+ Array.isArray(value)
116
+ ? value.filter((item) => availablePaymentOptionSchema.safeParse(item).success)
117
+ : value,
118
+ z.array(availablePaymentOptionSchema),
119
+ );
120
+
121
+ /** 결제 옵션을 화면 키로 — 라디오 value·React key. 예: `tosspayments:CARD`, `portone:EASY_PAY:NAVERPAY` */
122
+ export function paymentOptionKey(option: PaymentOption): string {
123
+ return option.method === "EASY_PAY"
124
+ ? `${option.pg}:EASY_PAY:${option.provider}`
125
+ : `${option.pg}:${option.method}`;
126
+ }
127
+
59
128
  export const shippingAddressInputSchema = z.object({
60
129
  addressId: z.string().optional(),
61
130
  receiverName: z.string().min(1, "수령인명을 입력해주세요").max(50),
@@ -78,51 +147,43 @@ export const guestInfoSchema = z.object({
78
147
 
79
148
  export type GuestInfo = z.infer<typeof guestInfoSchema>;
80
149
 
81
- export const requestPaymentRequestSchema = z.object({
150
+ /**
151
+ * 결제 시작 — 배송지를 확정하고 고른 결제 옵션으로 결제 시도를 연다. 응답의 `payUrl`(결제 서비스)을 팝업(`mode=popup`)이나
152
+ * 전체 페이지로 열면 결제 서비스가 PG 결제창을 띄우고 승인까지 한다. 리다이렉트 결제가 끝나면 구매자는 `returnUrl`로 돌아온다.
153
+ * `returnUrl`의 도메인은 스토어 결제 도메인(콘솔 설정 › 결제)에 등록돼 있어야 한다(테스트 결제는 localhost 허용).
154
+ */
155
+ export const startPaymentRequestSchema = z.object({
156
+ option: paymentOptionSchema,
157
+ returnUrl: z
158
+ .url()
159
+ .max(2048)
160
+ .describe(
161
+ "결제를 마치면 돌아올 스토어프론트 주소. 스토어 결제 도메인에 등록된 도메인이어야 한다",
162
+ ),
82
163
  shippingAddress: shippingAddressInputSchema,
83
- paymentMethod: paymentMethodSchema,
84
164
  guest: guestInfoSchema.optional(),
85
165
  });
86
166
 
87
- export type RequestPaymentRequest = z.infer<typeof requestPaymentRequestSchema>;
167
+ export type StartPaymentRequest = z.infer<typeof startPaymentRequestSchema>;
88
168
 
89
- /**
90
- * 결제창 파라미터. 스토어프론트가 쓰는 계약 값은 `popupUrl` 하나다(결제 팝업을 연다).
91
- *
92
- * 나머지는 결제 팝업 내부 값이라 선택 필드로만 타입을 준다(`attemptId`·`provider`·`mock`, 그리고
93
- * 토스 `clientKey`·`orderId`…, 포트원 `storeId`·`channelKey`… 같은 PG별 결제창 값). 공개 값뿐이며
94
- * 시크릿은 어떤 키에도 없다. 서버가 키를 추가·변경해도 파싱이 깨지지 않도록 모르는 키는 그대로 통과시킨다.
95
- */
96
- export const pgParamsSchema = z.looseObject({
97
- popupUrl: z.string().describe("결제 팝업 진입 URL. `window.open`으로 띄운다"),
98
- attemptId: z
99
- .string()
100
- .optional()
101
- .describe(
102
- "첫 결제 시도 id(`att_…`). PG에 보내는 주문번호(토스 `orderId`, 포트원 `paymentId`)다. 팝업 내부 값",
103
- ),
104
- provider: z.string().optional().describe("첫 결제 시도의 PG (`pgProvider`와 같다). 팝업 내부 값"),
105
- mock: z
106
- .boolean()
107
- .optional()
108
- .describe("모의 결제 여부. true면 결제 팝업이 PG SDK 대신 모의 결제 UI를 띄운다. 팝업 내부 값"),
109
- environment: z
110
- .string()
111
- .optional()
112
- .describe("첫 결제 시도의 결제 환경 (`TEST` 샌드박스 · `LIVE` 실결제). 팝업 내부 값"),
169
+ /** 같은 결제에서 다른 결제 옵션으로 다시 시도. `returnUrl`을 생략하면 결제 시작 때의 복귀 주소를 쓴다 */
170
+ export const retryPaymentRequestSchema = z.object({
171
+ option: paymentOptionSchema,
172
+ returnUrl: startPaymentRequestSchema.shape.returnUrl.optional(),
113
173
  });
114
174
 
115
- export type PgParams = z.infer<typeof pgParamsSchema>;
175
+ export type RetryPaymentRequest = z.infer<typeof retryPaymentRequestSchema>;
116
176
 
117
- export const paymentParamsSchema = z.object({
118
- paymentId: z.string(),
119
- pgProvider: z
177
+ export const paymentStartSchema = z.object({
178
+ paymentId: z.string().describe("결제 id. 상태 조회·재시도의 키다"),
179
+ attemptId: z.string().describe("결제 시도 id. PG 주문번호(토스 orderId, 포트원 paymentId)다"),
180
+ option: paymentOptionSchema.describe("이 시도의 결제 옵션"),
181
+ payUrl: z
120
182
  .string()
121
183
  .describe(
122
- "첫 결제 시도의 PG. 현재 값은 `tosspayments`·`portone`이다. PG가 추가될 수 있어 문자열로 받는다",
184
+ "결제 서비스 주소. 팝업으로 열려면 쿼리 `mode=popup`을 붙이고, 그대로 이동하면 리다이렉트 결제다",
123
185
  ),
124
- pgParams: pgParamsSchema,
125
- expiresAt: z.string(),
186
+ expiresAt: z.string().describe("결제 요청 만료 시각"),
126
187
  testPayment: z
127
188
  .boolean()
128
189
  .describe(
@@ -130,27 +191,15 @@ export const paymentParamsSchema = z.object({
130
191
  ),
131
192
  });
132
193
 
133
- export type PaymentParams = z.infer<typeof paymentParamsSchema>;
134
-
135
- export const confirmPaymentRequestSchema = z.object({
136
- pgToken: z.string().min(1).describe("PG 승인 키 (토스 `paymentKey`, 포트원 `txId`)"),
137
- attemptId: z
138
- .string()
139
- .min(1)
140
- .optional()
141
- .describe("승인할 결제 시도 id (결제창이 돌려준 주문번호). 생략하면 현재 시도"),
142
- });
143
-
144
- export type ConfirmPaymentRequest = z.infer<typeof confirmPaymentRequestSchema>;
145
-
146
- export const confirmPaymentResultSchema = z.object({
147
- orderId: z.string(),
148
- status: z.literal("PAID"),
149
- totalAmount: z.number().int(),
150
- paidAt: z.string(),
151
- });
194
+ export type PaymentStart = z.infer<typeof paymentStartSchema>;
152
195
 
153
- export type ConfirmPaymentResult = z.infer<typeof confirmPaymentResultSchema>;
196
+ /** 결제창에서 결제하지 못한 이유 */
197
+ export const paymentFailureCategorySchema = z.enum([
198
+ "PREPARE_FAILURE",
199
+ "DECLINED",
200
+ "USER_CANCELED",
201
+ ]);
202
+ export type PaymentFailureCategory = z.infer<typeof paymentFailureCategorySchema>;
154
203
 
155
204
  /**
156
205
  * 결제 상태 조회의 현재 상태 값 — 응답 스키마는 서버가 상태를 추가해도 파싱이 깨지지 않게 문자열로 받는다
@@ -165,6 +214,17 @@ export const PAYMENT_STATUS_VALUES = [
165
214
  ] as const;
166
215
  export type KnownPaymentStatus = (typeof PAYMENT_STATUS_VALUES)[number];
167
216
 
217
+ /** 결제 결과 구분 — 결제 서비스가 스토어프론트에 돌려주는 값과 같다 */
218
+ export const PAYMENT_RESULT_VALUES = [
219
+ "COMPLETED",
220
+ "PROCESSING",
221
+ "CANCELED",
222
+ "FAILED",
223
+ "EXPIRED",
224
+ "PENDING",
225
+ ] as const;
226
+ export type KnownPaymentResult = (typeof PAYMENT_RESULT_VALUES)[number];
227
+
168
228
  export const paymentStatusSchema = z.object({
169
229
  paymentId: z.string(),
170
230
  status: z
@@ -173,7 +233,28 @@ export const paymentStatusSchema = z.object({
173
233
  "결제 상태. 현재 값은 `pending` 결제 대기 · `processing` 승인 확인 중 · `completed` 결제 완료(주문 생성) · `failed` 실패 · " +
174
234
  "`expired` 만료예요. 상태가 추가될 수 있어 문자열로 받아요 — 모르는 값은 `processing`처럼 다루고 계속 조회하세요",
175
235
  ),
236
+ result: z
237
+ .string()
238
+ .describe(
239
+ "화면 분기용 결과. `COMPLETED` 결제 완료 · `PROCESSING` 결과 확인 중 · `CANCELED` 구매자가 결제창을 닫음 · " +
240
+ "`FAILED` 결제 실패(다른 결제 옵션으로 재시도 가능) · `EXPIRED` 결제 요청 만료 · `PENDING` 결제 전이에요. " +
241
+ "값이 추가될 수 있어 문자열로 받아요 — 모르는 값은 `PROCESSING`처럼 다루세요",
242
+ ),
176
243
  orderId: z.string().nullable().describe("결제가 완료됐으면 주문 번호, 그 외에는 null"),
244
+ lastFailure: z
245
+ .object({
246
+ attemptId: z.string(),
247
+ category: z
248
+ .string()
249
+ .describe("`PREPARE_FAILURE`·`DECLINED`·`USER_CANCELED`(값이 추가될 수 있다)"),
250
+ code: z
251
+ .string()
252
+ .nullable()
253
+ .describe("PG가 준 실패 코드. 내부 사유는 null이에요. 화면 분기는 `category`로 하세요"),
254
+ message: z.string().nullable().describe("구매자에게 보여 줄 안내 문구"),
255
+ })
256
+ .nullable()
257
+ .describe("현재 결제 시도가 실패·취소로 끝났으면 그 내용, 아니면 null"),
177
258
  testPayment: z
178
259
  .boolean()
179
260
  .describe("테스트 결제(샌드박스)인가. true면 실제로 돈이 나가지 않았어요"),
@@ -181,3 +262,16 @@ export const paymentStatusSchema = z.object({
181
262
  });
182
263
 
183
264
  export type PaymentStatus = z.infer<typeof paymentStatusSchema>;
265
+
266
+ /**
267
+ * 결제 서비스가 팝업 opener에 보내는 메시지 — `type`으로 거르고 결과의 원천은 결제 상태 조회다.
268
+ * 안내 문구는 싣지 않는다. 화면 문구는 상태 조회의 `lastFailure`를 쓴다
269
+ */
270
+ export const paymentMessageSchema = z.object({
271
+ type: z.literal("sayren:payment"),
272
+ paymentId: z.string(),
273
+ result: z.string(),
274
+ orderId: z.string().optional(),
275
+ });
276
+
277
+ export type PaymentMessage = z.infer<typeof paymentMessageSchema>;
@@ -23,3 +23,11 @@ export const createInquiryRequestSchema = z.object({
23
23
  });
24
24
 
25
25
  export type CreateInquiryRequest = z.infer<typeof createInquiryRequestSchema>;
26
+
27
+ /** `POST /products/{productId}/inquiries` 응답 */
28
+ export const createInquiryResultSchema = z.object({
29
+ inquiryId: z.string(),
30
+ createdAt: z.string(),
31
+ });
32
+
33
+ export type CreateInquiryResult = z.infer<typeof createInquiryResultSchema>;
@@ -49,3 +49,11 @@ export const memberAddressRequestSchema = z.object({
49
49
  });
50
50
 
51
51
  export type MemberAddressRequest = z.infer<typeof memberAddressRequestSchema>;
52
+
53
+ /** `POST /me/wishlist` 응답 — 이미 찜한 상품이어도 같은 응답이다 */
54
+ export const wishlistAddResultSchema = z.object({
55
+ productId: z.string(),
56
+ wished: z.literal(true),
57
+ });
58
+
59
+ export type WishlistAddResult = z.infer<typeof wishlistAddResultSchema>;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { paymentMethodSchema } from "./checkout";
2
+ import { easyPayProviderResponseSchema, paymentMethodSchema } from "./checkout";
3
3
  import { orderItemStatusSchema } from "./common";
4
4
 
5
5
  export const myOrderItemSchema = z.object({
@@ -46,6 +46,15 @@ export const myOrderSchema = z.object({
46
46
  method: paymentMethodSchema.describe(
47
47
  "실제 결제수단(PG가 보고한 수단, 모르면 주문서에서 고른 수단). 결제창에서 바뀔 수 있다(카드 결제창의 간편결제 탭 등)",
48
48
  ),
49
+ easyPayProvider: easyPayProviderResponseSchema
50
+ .nullable()
51
+ .optional()
52
+ .default(null)
53
+ .describe(
54
+ "간편결제사. `method`가 `EASY_PAY`일 때만 값이 있고, PG가 알려 주지 않았으면 주문서에서 고른 간편결제사다. " +
55
+ "그 밖의 결제수단과 간편결제 이전 주문은 null. 서버는 항상 싣는다. " +
56
+ "현재 값은 `NAVERPAY`·`KAKAOPAY`·`TOSSPAY`·`PAYCO`이고 값이 추가될 수 있어 문자열로 받는다",
57
+ ),
49
58
  totalAmount: z.number().int(),
50
59
  paidAt: z.string().nullable(),
51
60
  receiptUrl: z.url().nullable(),
@@ -74,3 +83,12 @@ export const deliveryTrackingSchema = z.object({
74
83
  });
75
84
 
76
85
  export type DeliveryTracking = z.infer<typeof deliveryTrackingSchema>;
86
+
87
+ /** `POST /me/order-items/{orderItemId}/purchase-decision` 응답 */
88
+ export const purchaseDecisionResultSchema = z.object({
89
+ orderItemId: z.string(),
90
+ status: z.literal("PURCHASE_DECIDED"),
91
+ decidedAt: z.string(),
92
+ });
93
+
94
+ export type PurchaseDecisionResult = z.infer<typeof purchaseDecisionResultSchema>;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { pageSchema } from "./common";
2
3
 
3
4
  export const publicReviewSchema = z.object({
4
5
  reviewId: z.string(),
@@ -63,3 +64,18 @@ export const updateReviewRequestSchema = z
63
64
  });
64
65
 
65
66
  export type UpdateReviewRequest = z.infer<typeof updateReviewRequestSchema>;
67
+
68
+ /** `GET /products/{productId}/reviews` 응답 — 리뷰 페이지와 평점 분포 */
69
+ export const reviewPageSchema = pageSchema(publicReviewSchema).extend({
70
+ summary: reviewSummarySchema.optional(),
71
+ });
72
+
73
+ export type ReviewPage = z.infer<typeof reviewPageSchema>;
74
+
75
+ /** `PUT /me/reviews/{reviewId}` 응답 */
76
+ export const updateReviewResultSchema = z.object({
77
+ reviewId: z.string(),
78
+ updatedAt: z.string(),
79
+ });
80
+
81
+ export type UpdateReviewResult = z.infer<typeof updateReviewResultSchema>;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  /**
4
- * 스토어 공개 설정 — `GET /store`. 스토어프런트가 화면을 그릴 때 읽는 값만 담는다.
4
+ * 스토어 공개 설정 — `GET /store`. 스토어프론트가 화면을 그릴 때 읽는 값만 담는다.
5
5
  */
6
6
  export const storefrontStoreSchema = z.object({
7
7
  storeCode: z.string(),