@sayren/store-sdk 0.41.0 → 0.43.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.
Files changed (53) hide show
  1. package/dist/index.d.mts +72 -72
  2. package/dist/index.mjs +160 -160
  3. package/dist/server.mjs +1 -1
  4. package/dist/{webhook-headers-D974dE4T.mjs → webhook-headers-CTez3xDY.mjs} +81 -3
  5. package/package.json +1 -1
  6. package/src/client.ts +1 -1
  7. package/src/http.ts +1 -1
  8. package/src/oauth.ts +1 -1
  9. package/src/schemas/ai.ts +40 -33
  10. package/src/schemas/analytics.ts +1 -1
  11. package/src/schemas/api-log.ts +1 -1
  12. package/src/schemas/app-manifest.ts +1 -1
  13. package/src/schemas/apps.ts +19 -16
  14. package/src/schemas/audit.ts +7 -4
  15. package/src/schemas/auth.ts +1 -1
  16. package/src/schemas/category.ts +37 -32
  17. package/src/schemas/claim.ts +15 -12
  18. package/src/schemas/collection.ts +76 -65
  19. package/src/schemas/commerce-flow.ts +107 -86
  20. package/src/schemas/commerce-number.ts +1 -1
  21. package/src/schemas/common.ts +1 -1
  22. package/src/schemas/coupon.ts +88 -80
  23. package/src/schemas/currency-prices.ts +1 -1
  24. package/src/schemas/currency.ts +1 -1
  25. package/src/schemas/customer-delegation.ts +31 -28
  26. package/src/schemas/customer.ts +10 -7
  27. package/src/schemas/fulfillment.ts +1 -1
  28. package/src/schemas/inventory.ts +29 -24
  29. package/src/schemas/locale.ts +1 -1
  30. package/src/schemas/login.ts +18 -11
  31. package/src/schemas/market.ts +1 -1
  32. package/src/schemas/misc.ts +7 -4
  33. package/src/schemas/order.ts +70 -59
  34. package/src/schemas/payment.ts +108 -89
  35. package/src/schemas/plans.ts +1 -1
  36. package/src/schemas/points.ts +33 -28
  37. package/src/schemas/product-info.ts +13 -10
  38. package/src/schemas/product-localization.ts +1 -1
  39. package/src/schemas/product.ts +18 -15
  40. package/src/schemas/promotion.ts +1 -1
  41. package/src/schemas/refund-policy.ts +1 -1
  42. package/src/schemas/saved-option-set.ts +6 -6
  43. package/src/schemas/scope.ts +1 -1
  44. package/src/schemas/shipping-policy.ts +1 -1
  45. package/src/schemas/starter.ts +16 -13
  46. package/src/schemas/stay.ts +16 -13
  47. package/src/schemas/store.ts +130 -114
  48. package/src/schemas/storefront-hosting.ts +272 -220
  49. package/src/schemas/strict.ts +84 -0
  50. package/src/schemas/subscription.ts +1 -1
  51. package/src/schemas/telemetry.ts +1 -1
  52. package/src/schemas/upload.ts +16 -13
  53. package/src/schemas/webhook-event.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
 
3
3
  /**
4
4
  * 언어 코드 (이슈 #134) — BCP 47의 언어[-문자][-지역]까지 받는다(`ko`·`en`·`en-US`·`zh-Hant-TW`).
@@ -1,4 +1,5 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
+ import { strictRequest } from "./strict";
2
3
 
3
4
  /** 소셜 로그인 공급자 */
4
5
  export const loginProviderSchema = z.enum(["kakao", "naver", "google"]);
@@ -32,22 +33,28 @@ export const loginSettingsSchema = z.object({
32
33
  export type LoginSettings = z.infer<typeof loginSettingsSchema>;
33
34
 
34
35
  /** `PATCH /v1/store/login-settings` */
35
- export const updateLoginSettingsRequestSchema = z.object({
36
- passwordLoginEnabled: z.boolean(),
37
- });
36
+ export const updateLoginSettingsRequestSchema = strictRequest(
37
+ z.object({
38
+ passwordLoginEnabled: z.boolean(),
39
+ }),
40
+ );
38
41
  export type UpdateLoginSettingsRequest = z.infer<typeof updateLoginSettingsRequestSchema>;
39
42
 
40
43
  /** `PUT /v1/store/auth-providers/{provider}` — 저장 전에 연결 테스트를 한다 */
41
- export const saveLoginProviderRequestSchema = z.object({
42
- clientId: z.string().trim().min(1).max(200),
43
- clientSecret: z.string().trim().min(1).max(500),
44
- });
44
+ export const saveLoginProviderRequestSchema = strictRequest(
45
+ z.object({
46
+ clientId: z.string().trim().min(1).max(200),
47
+ clientSecret: z.string().trim().min(1).max(500),
48
+ }),
49
+ );
45
50
  export type SaveLoginProviderRequest = z.infer<typeof saveLoginProviderRequestSchema>;
46
51
 
47
52
  /** `PATCH /v1/store/auth-providers/{provider}` */
48
- export const updateLoginProviderRequestSchema = z.object({
49
- enabled: z.boolean(),
50
- });
53
+ export const updateLoginProviderRequestSchema = strictRequest(
54
+ z.object({
55
+ enabled: z.boolean(),
56
+ }),
57
+ );
51
58
 
52
59
  /** 연결 테스트 결과 */
53
60
  export const loginProviderTestSchema = z.object({
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  import { currencyCodeSchema } from "./currency";
3
3
  import { localeCodeSchema } from "./locale";
4
4
 
@@ -1,5 +1,6 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  import { type PageParams, pageQueryShape } from "./common";
3
+ import { strictRequest } from "./strict";
3
4
 
4
5
  export const inquirySchema = z.object({
5
6
  inquiryId: z.string(),
@@ -435,9 +436,11 @@ export const webhookRedeliverFailedResultSchema = z.object({
435
436
  });
436
437
  export type WebhookRedeliverFailedResult = z.infer<typeof webhookRedeliverFailedResultSchema>;
437
438
 
438
- export const webhookTestRequestSchema = z.object({
439
- eventType: eventTypeSchema.describe("샘플 페이로드로 보낼 이벤트 타입"),
440
- });
439
+ export const webhookTestRequestSchema = strictRequest(
440
+ z.object({
441
+ eventType: eventTypeSchema.describe("샘플 페이로드로 보낼 이벤트 타입"),
442
+ }),
443
+ );
441
444
  export type WebhookTestRequest = z.infer<typeof webhookTestRequestSchema>;
442
445
 
443
446
  /** 테스트 이벤트 발송 결과 (동기 반환). 재시도·정지 판정·전송 기록이 없다 */
@@ -1,10 +1,11 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  import { commerceNumberField } from "./commerce-number";
3
3
  import { changeTypeSchema, orderItemStatusSchema, type PageParams, pageSchema } from "./common";
4
4
  import { orderItemFulfillmentSnapshotResponseSchema } from "./fulfillment";
5
5
  import { cashReceiptSchema, easyPayProviderResponseSchema } from "./payment";
6
6
  import { customInputValueSchema, optionSelectionSchema } from "./product";
7
7
  import { staySnapshotSchema } from "./stay";
8
+ import { strictRequest } from "./strict";
8
9
 
9
10
  export const orderSummarySchema = z.object({
10
11
  orderId: z.string(),
@@ -391,27 +392,29 @@ export const deliveryMethodSchema = z.enum(["COURIER", "DIRECT", "QUICK", "NOTHI
391
392
 
392
393
  export type DeliveryMethod = z.infer<typeof deliveryMethodSchema>;
393
394
 
394
- export const dispatchRequestSchema = z
395
- .object({
396
- deliveryMethod: deliveryMethodSchema,
397
- carrierCode: z.string().optional(),
398
- trackingNumber: z.string().optional(),
399
- /**
400
- * 발송 시각. ISO 8601이고 시간대(`Z` 또는 `+09:00`)를 포함해야 한다. 생략하면 현재 시각이다.
401
- *
402
- * 전에는 `z.string()`이라 아무 문자열이나 그대로 저장됐다. 이 값은 배송 정보 jsonb에 들어가
403
- * 자동 배송완료·추적 동기화 잡의 대상 선별 조건이 된다 — 잘못된 한 행이 상점 구분 없이 도는
404
- * 잡 전체를 멈출 수 있어 입력에서 좁혔다. 검증을 통과한 값도 저장 전에 UTC ISO로 정규화한다.
405
- */
406
- dispatchedAt: z.iso.datetime({ offset: true }).optional(),
407
- })
408
- .refine(
409
- (value) =>
410
- value.deliveryMethod === "DIRECT" || value.deliveryMethod === "NOTHING"
411
- ? true
412
- : !!value.carrierCode && !!value.trackingNumber,
413
- { message: "COURIER/QUICK 발송은 carrierCode와 trackingNumber가 필수입니다" },
414
- );
395
+ export const dispatchRequestSchema = strictRequest(
396
+ z
397
+ .object({
398
+ deliveryMethod: deliveryMethodSchema,
399
+ carrierCode: z.string().optional(),
400
+ trackingNumber: z.string().optional(),
401
+ /**
402
+ * 발송 시각. ISO 8601이고 시간대(`Z` 또는 `+09:00`)를 포함해야 한다. 생략하면 현재 시각이다.
403
+ *
404
+ * 전에는 `z.string()`이라 아무 문자열이나 그대로 저장됐다. 이 값은 배송 정보 jsonb에 들어가
405
+ * 자동 배송완료·추적 동기화 잡의 대상 선별 조건이 된다 — 잘못된 한 행이 상점 구분 없이 도는
406
+ * 잡 전체를 멈출 수 있어 입력에서 좁혔다. 검증을 통과한 값도 저장 전에 UTC ISO로 정규화한다.
407
+ */
408
+ dispatchedAt: z.iso.datetime({ offset: true }).optional(),
409
+ })
410
+ .refine(
411
+ (value) =>
412
+ value.deliveryMethod === "DIRECT" || value.deliveryMethod === "NOTHING"
413
+ ? true
414
+ : !!value.carrierCode && !!value.trackingNumber,
415
+ { message: "COURIER/QUICK 발송은 carrierCode와 trackingNumber가 필수입니다" },
416
+ ),
417
+ );
415
418
 
416
419
  export type DispatchRequest = z.infer<typeof dispatchRequestSchema>;
417
420
 
@@ -509,34 +512,38 @@ export const carrierSchema = z.object({
509
512
 
510
513
  export type Carrier = z.infer<typeof carrierSchema>;
511
514
 
512
- export const holdOrderItemRequestSchema = z.object({
513
- reason: z.enum(["OUT_OF_STOCK", "CUSTOMER_REQUEST", "CUSTOMS", "ETC"]),
514
- detail: z.string().max(500).optional(),
515
- expectedDispatchDate: z.string(),
516
- });
515
+ export const holdOrderItemRequestSchema = strictRequest(
516
+ z.object({
517
+ reason: z.enum(["OUT_OF_STOCK", "CUSTOMER_REQUEST", "CUSTOMS", "ETC"]),
518
+ detail: z.string().max(500).optional(),
519
+ expectedDispatchDate: z.string(),
520
+ }),
521
+ );
517
522
 
518
523
  export type HoldOrderItemRequest = z.infer<typeof holdOrderItemRequestSchema>;
519
524
 
520
- export const cancelBySellerRequestSchema = z.object({
521
- reason: z.enum(["OUT_OF_STOCK", "PRICE_ERROR", "DELIVERY_IMPOSSIBLE", "ETC"]),
522
- detail: z.string().max(500).optional(),
523
- quantity: z
524
- .number()
525
- .int()
526
- .min(1)
527
- .optional()
528
- .describe(
529
- "취소할 수량. 생략하면 남은 수량 전체다. 남은 수량보다 크면 400 `INVALID_QUANTITY`. " +
530
- "일부만 취소하면 주문상품은 상태를 유지한 채 `canceledQuantity`가 늘고, 전 수량이 소진되면 `CANCELED`가 된다",
531
- ),
532
- holdRefund: z
533
- .boolean()
534
- .optional()
535
- .describe(
536
- "true면 주문만 취소하고 결제 환불은 대기로 남긴다(#115). 정산·재고·적립금 반환은 그대로 하고, PG 환불은 결제 상세의 " +
537
- "`POST /v1/payments/refunds/{refundId}/process`로 따로 한다. 생략하면 취소와 함께 환불한다",
538
- ),
539
- });
525
+ export const cancelBySellerRequestSchema = strictRequest(
526
+ z.object({
527
+ reason: z.enum(["OUT_OF_STOCK", "PRICE_ERROR", "DELIVERY_IMPOSSIBLE", "ETC"]),
528
+ detail: z.string().max(500).optional(),
529
+ quantity: z
530
+ .number()
531
+ .int()
532
+ .min(1)
533
+ .optional()
534
+ .describe(
535
+ "취소할 수량. 생략하면 남은 수량 전체다. 남은 수량보다 크면 400 `INVALID_QUANTITY`. " +
536
+ "일부만 취소하면 주문상품은 상태를 유지한 채 `canceledQuantity`가 늘고, 전 수량이 소진되면 `CANCELED`가 된다",
537
+ ),
538
+ holdRefund: z
539
+ .boolean()
540
+ .optional()
541
+ .describe(
542
+ "true면 주문만 취소하고 결제 환불은 대기로 남긴다(#115). 정산·재고·적립금 반환은 그대로 하고, PG 환불은 결제 상세의 " +
543
+ "`POST /v1/payments/refunds/{refundId}/process`로 따로 한다. 생략하면 취소와 함께 환불한다",
544
+ ),
545
+ }),
546
+ );
540
547
 
541
548
  export type CancelBySellerRequest = z.infer<typeof cancelBySellerRequestSchema>;
542
549
 
@@ -544,22 +551,26 @@ export type CancelBySellerRequest = z.infer<typeof cancelBySellerRequestSchema>;
544
551
  * 제공 처리 요청 (이슈 #48) — `POST /v1/order-items/{orderItemId}/fulfill`. 배송 없는 주문상품(`MANUAL`)을
545
552
  * 제공 완료(`DELIVERED`)로 바꾼다.
546
553
  */
547
- export const fulfillRequestSchema = z.object({
548
- note: z
549
- .string()
550
- .trim()
551
- .max(1000)
552
- .optional()
553
- .describe("구매자에게 보일 이용 안내(예: 예약 방법, 수령 장소). 주문 상세에 실린다"),
554
- });
554
+ export const fulfillRequestSchema = strictRequest(
555
+ z.object({
556
+ note: z
557
+ .string()
558
+ .trim()
559
+ .max(1000)
560
+ .optional()
561
+ .describe("구매자에게 보일 이용 안내(예: 예약 방법, 수령 장소). 주문 상세에 실린다"),
562
+ }),
563
+ );
555
564
 
556
565
  export type FulfillRequest = z.infer<typeof fulfillRequestSchema>;
557
566
 
558
567
  /** 제공 처리 일괄 요청 — `POST /v1/order-items/fulfill` */
559
- export const fulfillBulkRequestSchema = z.object({
560
- orderItemIds: z.array(z.string()).min(1).max(100),
561
- note: fulfillRequestSchema.shape.note,
562
- });
568
+ export const fulfillBulkRequestSchema = strictRequest(
569
+ z.object({
570
+ orderItemIds: z.array(z.string()).min(1).max(100),
571
+ note: fulfillRequestSchema.shape.note,
572
+ }),
573
+ );
563
574
 
564
575
  export type FulfillBulkRequest = z.infer<typeof fulfillBulkRequestSchema>;
565
576
 
@@ -1,5 +1,6 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  import { type PageParams, pageSchema } from "./common";
3
+ import { strictRequest } from "./strict";
3
4
 
4
5
  /**
5
6
  * 상점 결제(PG) 설정과 매출 리포트 계약.
@@ -253,103 +254,119 @@ const upsertEnvironment = () =>
253
254
  "어느 세트로 결제할지는 이 값이 아니라 샌드박스 스위치(`sandboxMode`)가 정한다",
254
255
  );
255
256
 
256
- export const upsertTossPaymentsProviderRequestSchema = z.object({
257
- environment: upsertEnvironment(),
258
- credentials: tossPaymentsCredentialsInputSchema,
259
- });
257
+ export const upsertTossPaymentsProviderRequestSchema = strictRequest(
258
+ z.object({
259
+ environment: upsertEnvironment(),
260
+ credentials: tossPaymentsCredentialsInputSchema,
261
+ }),
262
+ );
260
263
  export type UpsertTossPaymentsProviderRequest = z.infer<
261
264
  typeof upsertTossPaymentsProviderRequestSchema
262
265
  >;
263
266
 
264
- export const upsertPortOneProviderRequestSchema = z.object({
265
- environment: upsertEnvironment(),
266
- credentials: portOneCredentialsInputSchema,
267
- });
267
+ export const upsertPortOneProviderRequestSchema = strictRequest(
268
+ z.object({
269
+ environment: upsertEnvironment(),
270
+ credentials: portOneCredentialsInputSchema,
271
+ }),
272
+ );
268
273
  export type UpsertPortOneProviderRequest = z.infer<typeof upsertPortOneProviderRequestSchema>;
269
274
 
270
- export const upsertKakaoPayProviderRequestSchema = z.object({
271
- environment: upsertEnvironment(),
272
- credentials: kakaoPayCredentialsInputSchema,
273
- });
275
+ export const upsertKakaoPayProviderRequestSchema = strictRequest(
276
+ z.object({
277
+ environment: upsertEnvironment(),
278
+ credentials: kakaoPayCredentialsInputSchema,
279
+ }),
280
+ );
274
281
  export type UpsertKakaoPayProviderRequest = z.infer<typeof upsertKakaoPayProviderRequestSchema>;
275
282
 
276
- export const upsertNaverPayProviderRequestSchema = z.object({
277
- environment: upsertEnvironment(),
278
- credentials: naverPayCredentialsInputSchema,
279
- });
283
+ export const upsertNaverPayProviderRequestSchema = strictRequest(
284
+ z.object({
285
+ environment: upsertEnvironment(),
286
+ credentials: naverPayCredentialsInputSchema,
287
+ }),
288
+ );
280
289
  export type UpsertNaverPayProviderRequest = z.infer<typeof upsertNaverPayProviderRequestSchema>;
281
290
 
282
- export const upsertPayPalProviderRequestSchema = z.object({
283
- environment: upsertEnvironment(),
284
- credentials: payPalCredentialsInputSchema,
285
- });
291
+ export const upsertPayPalProviderRequestSchema = strictRequest(
292
+ z.object({
293
+ environment: upsertEnvironment(),
294
+ credentials: payPalCredentialsInputSchema,
295
+ }),
296
+ );
286
297
  export type UpsertPayPalProviderRequest = z.infer<typeof upsertPayPalProviderRequestSchema>;
287
298
 
288
- export const updatePaymentProviderRequestSchema = z
289
- .object({
290
- enabled: z
291
- .boolean()
292
- .optional()
293
- .describe(
294
- "사용 여부. 켜려면 활성 환경(샌드박스면 TEST, 아니면 LIVE) 자격 증명이 모두 저장돼 있어야 한다(`PAYMENT_PROVIDER_INCOMPLETE`)",
295
- ),
296
- sandboxMode: z
297
- .boolean()
298
- .optional()
299
- .describe(
300
- "샌드박스 스위치. `true`면 새 결제에 TEST 세트를 쓴다(항상 허용). `false`(라이브 전환)는 라이브 개방 조건을 " +
301
- "모두 충족해야 한다(`LIVE_GATE_NOT_MET`)",
302
- ),
303
- easyPayProviders: z
304
- .array(easyPayProviderSchema)
305
- .max(easyPayProviderSchema.options.length)
306
- .optional()
307
- .describe(
308
- "이 PG로 받는 간편결제사 — 통째로 바꾼다. PG 계약(포트원은 채널)에 추가한 간편결제만 켠다. " +
309
- "켠 간편결제사마다 스토어프론트 결제 옵션이 하나씩 생긴다(포트원은 간편결제를 받는 채널이 있어야 한다)",
310
- ),
311
- })
312
- .refine(
313
- (body) =>
314
- body.enabled !== undefined ||
315
- body.sandboxMode !== undefined ||
316
- body.easyPayProviders !== undefined,
317
- { message: "enabled·sandboxMode·easyPayProviders 중 하나는 보내야 합니다" },
318
- );
299
+ export const updatePaymentProviderRequestSchema = strictRequest(
300
+ z
301
+ .object({
302
+ enabled: z
303
+ .boolean()
304
+ .optional()
305
+ .describe(
306
+ "사용 여부. 켜려면 활성 환경(샌드박스면 TEST, 아니면 LIVE) 자격 증명이 모두 저장돼 있어야 한다(`PAYMENT_PROVIDER_INCOMPLETE`)",
307
+ ),
308
+ sandboxMode: z
309
+ .boolean()
310
+ .optional()
311
+ .describe(
312
+ "샌드박스 스위치. `true`면 새 결제에 TEST 세트를 쓴다(항상 허용). `false`(라이브 전환)는 라이브 개방 조건을 " +
313
+ "모두 충족해야 한다(`LIVE_GATE_NOT_MET`)",
314
+ ),
315
+ easyPayProviders: z
316
+ .array(easyPayProviderSchema)
317
+ .max(easyPayProviderSchema.options.length)
318
+ .optional()
319
+ .describe(
320
+ "이 PG로 받는 간편결제사 — 통째로 바꾼다. PG 계약(포트원은 채널)에 추가한 간편결제만 켠다. " +
321
+ "켠 간편결제사마다 스토어프론트 결제 옵션이 하나씩 생긴다(포트원은 간편결제를 받는 채널이 있어야 한다)",
322
+ ),
323
+ })
324
+ .refine(
325
+ (body) =>
326
+ body.enabled !== undefined ||
327
+ body.sandboxMode !== undefined ||
328
+ body.easyPayProviders !== undefined,
329
+ { message: "enabled·sandboxMode·easyPayProviders 중 하나는 보내야 합니다" },
330
+ ),
331
+ );
319
332
  export type UpdatePaymentProviderRequest = z.infer<typeof updatePaymentProviderRequestSchema>;
320
333
 
321
- export const verifyPaymentProviderRequestSchema = z.object({
322
- environment: pgEnvironmentSchema
323
- .optional()
324
- .describe("연결 테스트할 자격 증명 세트. 생략하면 활성 환경(샌드박스면 TEST, 아니면 LIVE)"),
325
- });
334
+ export const verifyPaymentProviderRequestSchema = strictRequest(
335
+ z.object({
336
+ environment: pgEnvironmentSchema
337
+ .optional()
338
+ .describe("연결 테스트할 자격 증명 세트. 생략하면 활성 환경(샌드박스면 TEST, 아니면 LIVE)"),
339
+ }),
340
+ );
326
341
  export type VerifyPaymentProviderRequest = z.infer<typeof verifyPaymentProviderRequestSchema>;
327
342
 
328
- export const updatePaymentSettingsRequestSchema = z
329
- .object({
330
- priority: z
331
- .array(pgProviderSchema)
332
- .min(1)
333
- // PG 개수와 무관한 안전장치 — 거대한 배열이 파싱을 거치지 않게만 막는다. 실제 개수는 서버가 본다
334
- .max(50)
335
- .optional()
336
- .describe(
337
- "PG 우선순위(앞이 1순위) — 스토어프론트 결제 옵션 목록의 PG 순서다. 연결된 PG 수만큼 **모두 한 번씩** 나열한다" +
338
- "(길이 상한 50은 안전장치다). 빠지거나 중복되거나 연결되지 않은 PG가 있으면 `INVALID_PRIORITY`",
339
- ),
340
- checkoutOrigins: z
341
- .array(z.string().max(200))
342
- .max(20)
343
- .optional()
344
- .describe(
345
- "결제 도메인 — 통째로 바꾼다. 결제를 마친 구매자가 돌아올 스토어프론트 origin(`https://shop.example.com`, https·경로 없음). " +
346
- "결제 시작의 `returnUrl`은 이 목록 안이어야 한다. 비어 있으면 실결제를 시작할 수 없다(테스트 결제는 localhost 허용). " +
347
- "같은 도메인을 PG 가맹 정보에도 등록한다. 형식이 틀리면 `INVALID_CHECKOUT_ORIGIN`",
348
- ),
349
- })
350
- .refine((body) => body.priority !== undefined || body.checkoutOrigins !== undefined, {
351
- message: "priority 또는 checkoutOrigins 중 하나는 보내야 합니다",
352
- });
343
+ export const updatePaymentSettingsRequestSchema = strictRequest(
344
+ z
345
+ .object({
346
+ priority: z
347
+ .array(pgProviderSchema)
348
+ .min(1)
349
+ // PG 개수와 무관한 안전장치 — 거대한 배열이 파싱을 거치지 않게만 막는다. 실제 개수는 서버가 본다
350
+ .max(50)
351
+ .optional()
352
+ .describe(
353
+ "PG 우선순위(앞이 1순위) — 스토어프론트 결제 옵션 목록의 PG 순서다. 연결된 PG 수만큼 **모두 한 번씩** 나열한다" +
354
+ "(길이 상한 50은 안전장치다). 빠지거나 중복되거나 연결되지 않은 PG가 있으면 `INVALID_PRIORITY`",
355
+ ),
356
+ checkoutOrigins: z
357
+ .array(z.string().max(200))
358
+ .max(20)
359
+ .optional()
360
+ .describe(
361
+ "결제 도메인 — 통째로 바꾼다. 결제를 마친 구매자가 돌아올 스토어프론트 origin(`https://shop.example.com`, https·경로 없음). " +
362
+ "결제 시작의 `returnUrl`은 이 목록 안이어야 한다. 비어 있으면 실결제를 시작할 수 없다(테스트 결제는 localhost 허용). " +
363
+ "같은 도메인을 PG 가맹 정보에도 등록한다. 형식이 틀리면 `INVALID_CHECKOUT_ORIGIN`",
364
+ ),
365
+ })
366
+ .refine((body) => body.priority !== undefined || body.checkoutOrigins !== undefined, {
367
+ message: "priority 또는 checkoutOrigins 중 하나는 보내야 합니다",
368
+ }),
369
+ );
353
370
  export type UpdatePaymentSettingsRequest = z.infer<typeof updatePaymentSettingsRequestSchema>;
354
371
 
355
372
  // ─── 조회 응답 ─────────────────────────────────────────────
@@ -958,13 +975,15 @@ export interface PaymentListParams extends PageParams {
958
975
  }
959
976
 
960
977
  /** 테스트모드 키 활성화 요청(2026-10-14) — `PUT /v1/store/payment-providers/{provider}/platform-test-keys` */
961
- export const setPlatformTestKeysRequestSchema = z.object({
962
- enabled: z
963
- .boolean()
964
- .describe(
965
- "`true`면 sayren 지정 테스트 키를 TEST 세트로 저장하고 PG를 켠다. `false`면 고정만 푼다(TEST 세트는 그대로)",
966
- ),
967
- });
978
+ export const setPlatformTestKeysRequestSchema = strictRequest(
979
+ z.object({
980
+ enabled: z
981
+ .boolean()
982
+ .describe(
983
+ "`true`면 sayren 지정 테스트 키를 TEST 세트로 저장하고 PG를 켠다. `false`면 고정만 푼다(TEST 세트는 그대로)",
984
+ ),
985
+ }),
986
+ );
968
987
  export type SetPlatformTestKeysRequest = z.infer<typeof setPlatformTestKeysRequestSchema>;
969
988
 
970
989
  /** 결제 상태 PG 한 줄 — 비밀 값·공개 키 없이 연결·사용·환경·테스트모드 키만 */
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  import { responsePlanCodeSchema } from "./subscription";
3
3
 
4
4
  /**
@@ -1,4 +1,5 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
+ import { strictRequest } from "./strict";
2
3
 
3
4
  /**
4
5
  * 적립금 (이슈 #93) — 상점 정책·원장 유형·순수 계산. 정책 해석은 `resolvePointPolicy`, 리뷰 적립 금액은
@@ -238,12 +239,14 @@ export type StorePointPolicy = z.infer<typeof storePointPolicySchema>;
238
239
  * `PUT /v1/store/point-policy` 본문 — 준 항목만 바꾼다(생략 = 저장된 값 유지). 리뷰 적립·구매 적립 중 하나라도 켜면 유효 기간이 있어야 한다
239
240
  * (본문이나 저장된 값, 없으면 400 `POINT_EXPIRY_REQUIRED`).
240
241
  */
241
- export const updatePointPolicyRequestSchema = z.object({
242
- reviewReward: reviewRewardPolicySchema.optional(),
243
- purchaseEarn: purchaseEarnPolicySchema.optional(),
244
- use: pointUsePolicySchema.optional(),
245
- expiryDays: expiryDays.optional(),
246
- });
242
+ export const updatePointPolicyRequestSchema = strictRequest(
243
+ z.object({
244
+ reviewReward: reviewRewardPolicySchema.optional(),
245
+ purchaseEarn: purchaseEarnPolicySchema.optional(),
246
+ use: pointUsePolicySchema.optional(),
247
+ expiryDays: expiryDays.optional(),
248
+ }),
249
+ );
247
250
  export type UpdatePointPolicyRequest = z.infer<typeof updatePointPolicyRequestSchema>;
248
251
 
249
252
  // ── 잔액·원장 조회와 셀러 조정 ─────────────────────────────
@@ -315,27 +318,29 @@ export const POINT_ADJUSTMENT_MAX = 10_000_000;
315
318
  * 지급의 소멸 시각은 `expiresAt`(생략하면 정책 유효 기간, 정책이 없으면 400 `POINT_EXPIRY_REQUIRED`), 차감은 소멸이 이른 적립금부터 뺀다
316
319
  * (잔액보다 많으면 409 `POINT_BALANCE_INSUFFICIENT`).
317
320
  */
318
- export const pointAdjustmentRequestSchema = z
319
- .object({
320
- amount: z
321
- .number()
322
- .int()
323
- .min(-POINT_ADJUSTMENT_MAX)
324
- .max(POINT_ADJUSTMENT_MAX)
325
- .refine((value) => value !== 0, "0원은 조정할 수 없습니다")
326
- .describe("지급은 양수, 차감은 음수(원)"),
327
- reason: z.string().trim().min(1).max(200).describe("사유(필수). 구매자에게는 보이지 않는다"),
328
- expiresAt: z.iso
329
- .datetime({ offset: true })
330
- .optional()
331
- .describe(
332
- "지급분의 소멸 시각. 생략하면 정책 유효 기간(`expiryDays`)으로 정한다. **정책 유효 기간이 없으면 지급에 필수다**(없으면 400 `POINT_EXPIRY_REQUIRED`). 차감에는 주지 않는다",
333
- ),
334
- })
335
- .refine((value) => !(value.amount < 0 && value.expiresAt), {
336
- message: "차감에는 소멸 시각을 줄 수 없습니다",
337
- path: ["expiresAt"],
338
- });
321
+ export const pointAdjustmentRequestSchema = strictRequest(
322
+ z
323
+ .object({
324
+ amount: z
325
+ .number()
326
+ .int()
327
+ .min(-POINT_ADJUSTMENT_MAX)
328
+ .max(POINT_ADJUSTMENT_MAX)
329
+ .refine((value) => value !== 0, "0원은 조정할 수 없습니다")
330
+ .describe("지급은 양수, 차감은 음수(원)"),
331
+ reason: z.string().trim().min(1).max(200).describe("사유(필수). 구매자에게는 보이지 않는다"),
332
+ expiresAt: z.iso
333
+ .datetime({ offset: true })
334
+ .optional()
335
+ .describe(
336
+ "지급분의 소멸 시각. 생략하면 정책 유효 기간(`expiryDays`)으로 정한다. **정책 유효 기간이 없으면 지급에 필수다**(없으면 400 `POINT_EXPIRY_REQUIRED`). 차감에는 주지 않는다",
337
+ ),
338
+ })
339
+ .refine((value) => !(value.amount < 0 && value.expiresAt), {
340
+ message: "차감에는 소멸 시각을 줄 수 없습니다",
341
+ path: ["expiresAt"],
342
+ }),
343
+ );
339
344
  export type PointAdjustmentRequest = z.infer<typeof pointAdjustmentRequestSchema>;
340
345
 
341
346
  export const pointAdjustmentResultSchema = z.object({
@@ -1,4 +1,5 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
+ import { strictRequest } from "./strict";
2
3
 
3
4
  /**
4
5
  * 상품정보제공 고시·인증 정보·안내 섹션 (이슈 #135).
@@ -81,7 +82,7 @@ export type ProductNotice = z.infer<typeof productNoticeSchema>;
81
82
 
82
83
  export const MAX_NOTICE_VALUE_LENGTH = 1500;
83
84
 
84
- export const productNoticeRequestSchema = z.object({
85
+ export const productNoticeRequestSchema = z.strictObject({
85
86
  schemaId: z.string().trim().min(1).max(50).describe("품목군 코드(`GET /v1/info-schemas`)"),
86
87
  version: z.number().int().positive().optional().describe("품목군 버전. 생략하면 현재 버전"),
87
88
  values: z
@@ -128,10 +129,10 @@ export const productCertificationsSchema = z.object({
128
129
 
129
130
  export type ProductCertifications = z.infer<typeof productCertificationsSchema>;
130
131
 
131
- export const productCertificationsRequestSchema = z.object({
132
+ export const productCertificationsRequestSchema = z.strictObject({
132
133
  subjects: z
133
134
  .array(
134
- z.object({
135
+ z.strictObject({
135
136
  subject: z.string().trim().min(1).max(50),
136
137
  status: z.enum(CERTIFICATION_STATUSES),
137
138
  exemption: z.string().trim().min(1).max(50).nullish(),
@@ -145,7 +146,7 @@ export const productCertificationsRequestSchema = z.object({
145
146
  ),
146
147
  items: z
147
148
  .array(
148
- z.object({
149
+ z.strictObject({
149
150
  kind: z.string().trim().min(1).max(50),
150
151
  agency: z.string().trim().max(100).nullish(),
151
152
  number: z.string().trim().min(1).max(100),
@@ -180,11 +181,13 @@ export const infoSectionSchema = z.object({
180
181
 
181
182
  export type InfoSection = z.infer<typeof infoSectionSchema>;
182
183
 
183
- export const infoSectionRequestSchema = z.object({
184
- title: z.string().trim().min(1).max(MAX_INFO_SECTION_TITLE),
185
- body: z.string().default(""),
186
- bodyJson: z.string().nullable().optional(),
187
- });
184
+ export const infoSectionRequestSchema = strictRequest(
185
+ z.object({
186
+ title: z.string().trim().min(1).max(MAX_INFO_SECTION_TITLE),
187
+ body: z.string().default(""),
188
+ bodyJson: z.string().nullable().optional(),
189
+ }),
190
+ );
188
191
 
189
192
  export type InfoSectionRequest = z.infer<typeof infoSectionRequestSchema>;
190
193
  export type InfoSectionRequestInput = z.input<typeof infoSectionRequestSchema>;
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  import { MAX_PRODUCT_IMAGES, productImageUrlSchema } from "./product";
3
3
  import { MAX_NOTICE_VALUE_LENGTH } from "./product-info";
4
4