@sayren/store-sdk 0.64.0 → 0.66.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.
@@ -690,6 +690,16 @@ export const cancelBySellerRequestSchema = strictRequest(
690
690
  .describe(
691
691
  "환불액에서 뺄 추가 차감(#301, 원). 생략하면 0이다. 현금에서 먼저 빼고 모자란 몫만 돌려줄 적립금에서 뺀다. 환불 원장·`ORDER.CANCELED` 웹훅에 따로 남는다",
692
692
  ),
693
+ refundAmount: z
694
+ .number()
695
+ .int()
696
+ .min(0)
697
+ .optional()
698
+ .describe(
699
+ "최종 현금 환불액(#365, 원). 생략하면 서버 계산값이다. 배송비 환불 여부·귀책과 무관하게 판매자가 정한다. " +
700
+ "배송비·쿠폰 정산 기록과 적립금 반환은 계산값대로 하고 현금만 이 값이다. 결제 잔액(`availableRefundAmount`)을 넘으면 409 `REFUND_EXCEEDS_BALANCE`. " +
701
+ "`deductAmount`와 함께 주면 400이다. 응답·웹훅의 `calculatedRefundAmount`가 서버 계산값이다",
702
+ ),
693
703
  restock: z
694
704
  .boolean()
695
705
  .optional()
@@ -705,10 +715,43 @@ export const cancelBySellerRequestSchema = strictRequest(
705
715
  "회원 잔액이 모자라면 있는 만큼만 회수한다(리뷰 적립 회수와 같은 규칙)",
706
716
  ),
707
717
  }),
708
- );
718
+ ).refine((body) => body.refundAmount === undefined || body.deductAmount === undefined, {
719
+ message: "refundAmount와 deductAmount는 함께 줄 수 없습니다",
720
+ path: ["refundAmount"],
721
+ });
709
722
 
710
723
  export type CancelBySellerRequest = z.infer<typeof cancelBySellerRequestSchema>;
711
724
 
725
+ /**
726
+ * 금액만 환불 요청(#365) — `POST /v1/orders/{orderId}/refunds`. 주문상품 상태·수량·재고·적립금·쿠폰·배송비 정산을 바꾸지 않고
727
+ * 결제에서 금액만 돌려준다. 헤더 `Idempotency-Key`가 필수다
728
+ */
729
+ export const sellerRefundRequestSchema = strictRequest(
730
+ z.object({
731
+ amount: z
732
+ .number()
733
+ .int()
734
+ .min(1)
735
+ .describe(
736
+ "돌려줄 현금 (원). 결제의 남은 금액(결제 상세 `availableRefundAmount`)을 넘으면 409 `REFUND_EXCEEDS_BALANCE`",
737
+ ),
738
+ reason: z
739
+ .string()
740
+ .trim()
741
+ .min(1)
742
+ .max(500)
743
+ .describe("환불 사유. 감사 로그와 결제 상세 환불 이력에 남는다"),
744
+ paymentId: z
745
+ .string()
746
+ .optional()
747
+ .describe(
748
+ "환불할 결제(주문 응답 `payments[].paymentId`). 생략하면 주문의 첫 결제다. 추가 결제가 있는 주문은 이 값으로 고른다",
749
+ ),
750
+ }),
751
+ );
752
+
753
+ export type SellerRefundRequest = z.infer<typeof sellerRefundRequestSchema>;
754
+
712
755
  /**
713
756
  * 제공 처리 요청 (이슈 #48) — `POST /v1/order-items/{orderItemId}/fulfill`. 배송 없는 주문상품(`MANUAL`)을
714
757
  * 제공 완료(`DELIVERED`)로 바꾼다.
@@ -744,7 +787,14 @@ export const cancelBySellerResultSchema = z.object({
744
787
  .int()
745
788
  .describe(
746
789
  "실제 환불한 금액 — 수량 비율 상품 실결제 금액(쿠폰 배분을 뺀 금액) + 배송비 환불 − 조건부 무료배송 미달 차감 " +
747
- "− 쿠폰 조건 미달 차감 + 되돌린 쿠폰 차감(`couponChargeRefundAmount`) − 추가 차감(`deductAmount`)",
790
+ "− 쿠폰 조건 미달 차감 + 되돌린 쿠폰 차감(`couponChargeRefundAmount`) − 추가 차감(`deductAmount`). 판매자가 `refundAmount`를 정했으면 그 값이다",
791
+ ),
792
+ calculatedRefundAmount: z
793
+ .number()
794
+ .int()
795
+ .optional()
796
+ .describe(
797
+ "서버가 계산한 현금 환불액(#365). 판매자가 `refundAmount`를 정하지 않았으면 `refundAmount`와 같다. 이 필드가 없는 이전 응답은 `refundAmount`와 같다고 읽는다",
748
798
  ),
749
799
  quantity: z.number().int().describe("취소한 수량"),
750
800
  canceledQuantity: z.number().int().describe("취소 후 누적 취소 수량"),
@@ -910,10 +910,21 @@ export const paymentRefundRecordSchema = z.object({
910
910
  .optional()
911
911
  .default(0)
912
912
  .describe("같은 환불에서 돌려준 적립금 (#93). 적립금을 쓰지 않은 결제는 0"),
913
+ calculatedAmount: z
914
+ .number()
915
+ .int()
916
+ .nullable()
917
+ .optional()
918
+ .default(null)
919
+ .describe(
920
+ "서버가 계산한 현금 환불액 (#365). 판매자가 최종 금액을 정했으면 `amount`와 다르다. 계산이 없는 환불(결제 오류 보상 등)과 이전 환불은 null이다",
921
+ ),
913
922
  reason: z.string().describe("환불 사유 코드"),
914
923
  kind: z
915
924
  .string()
916
- .describe("환불 원인 — SELLER_CANCEL·CLAIM·COMPENSATION·DUPLICATE. 새 값이 추가될 수 있다"),
925
+ .describe(
926
+ "환불 원인 — SELLER_CANCEL·CLAIM·SELLER_REFUND(금액만 환불)·COMPENSATION·DUPLICATE. 새 값이 추가될 수 있다",
927
+ ),
917
928
  status: z
918
929
  .string()
919
930
  .describe(
@@ -927,6 +938,13 @@ export type PaymentRefundRecord = z.infer<typeof paymentRefundRecordSchema>;
927
938
 
928
939
  export const paymentRecordDetailSchema = paymentRecordSchema.extend({
929
940
  refundableAmount: z.number().int().describe("환불 가능 잔액 = 승인 금액 - 환불 완료 금액"),
941
+ availableRefundAmount: z
942
+ .number()
943
+ .int()
944
+ .optional()
945
+ .describe(
946
+ "지금 더 환불할 수 있는 금액 = 승인 금액 - (환불 완료 + 처리 중 + 대기 환불). 이보다 큰 환불은 409 `REFUND_EXCEEDS_BALANCE`다. 승인되지 않은 결제는 0. 이 필드가 없는 이전 api는 `refundableAmount`로 읽는다",
947
+ ),
930
948
  merchantId: z
931
949
  .string()
932
950
  .nullable()
@@ -177,7 +177,14 @@ export const orderCanceledWebhookEventDataSchema = z.object({
177
177
  .describe(
178
178
  "환불액 (원). 수량 비율 상품 실결제 금액(쿠폰 배분을 뺀 금액) + 배송비 환불 − 조건부 무료배송 미달 차감 " +
179
179
  "− 쿠폰 조건 미달 차감 + 되돌린 쿠폰 차감(`couponChargeRefundAmount`) − 추가 차감(`deductAmount`)이다. " +
180
- "결제사 환불 결과는 결제 내역(`GET /v1/payments/{paymentId}`)으로 확인한다",
180
+ "판매자가 `refundAmount`를 정했으면 그 값이다. 결제사 환불 결과는 결제 내역(`GET /v1/payments/{paymentId}`)으로 확인한다",
181
+ ),
182
+ calculatedRefundAmount: z
183
+ .number()
184
+ .int()
185
+ .optional()
186
+ .describe(
187
+ "서버가 계산한 환불액 (원, #365). 판매자가 최종 금액을 정하지 않았으면 `refundAmount`와 같다. 이 필드가 없는 이전 이벤트는 `refundAmount`와 같다고 읽는다",
181
188
  ),
182
189
  deliveryFeeRefundAmount: z.number().int().describe("환불액에 포함된 배송비 (원)"),
183
190
  deliveryFeeChargeAmount: z
@@ -257,9 +264,16 @@ export const claimWebhookEventDataSchema = z.object({
257
264
  .optional()
258
265
  .default(null)
259
266
  .describe(
260
- "환불액 (원). 배송비 환불을 포함하고 배송비 차감·추가 차감을 뺀 실제 금액이다. " +
267
+ "환불액 (원). 배송비 환불을 포함하고 배송비 차감·추가 차감을 뺀 실제 금액이다. 판매자가 승인에 최종 금액을 정했으면 그 값이다. " +
261
268
  "환불이 확정되지 않은 접수 이벤트는 예상 환불액이고, 교환처럼 환불이 없으면 null이다",
262
269
  ),
270
+ calculatedRefundAmount: z
271
+ .number()
272
+ .int()
273
+ .optional()
274
+ .describe(
275
+ "서버가 계산한 환불액 (원, #365) — 완료 이벤트(`CLAIM.COMPLETED`)에만 싣는다. 판매자가 최종 금액을 정하지 않았으면 `refundAmount`와 같다. 이 필드가 없는 이벤트는 `refundAmount`와 같다고 읽는다",
276
+ ),
263
277
  deliveryFeeRefundAmount: z
264
278
  .number()
265
279
  .int()
@@ -542,6 +556,27 @@ export const orderPaymentRequestedWebhookEventSchema = z.object({
542
556
  data: orderPaymentRequestedWebhookEventDataSchema,
543
557
  });
544
558
 
559
+ /**
560
+ * 금액만 환불(#365) — 판매자가 주문상품을 취소하지 않고 결제에서 금액만 돌려주기로 했다. 결제사 환불 결과는 결제 상세
561
+ * (`GET /v1/payments/{paymentId}`의 `refunds[]`, `refundId`)로 확인한다. 개인정보는 싣지 않는다
562
+ */
563
+ export const orderRefundedWebhookEventDataSchema = z.object({
564
+ orderId: z.string().describe("주문 ID"),
565
+ orderNo: z.string().nullable().describe("주문번호. 번호 체계 이전 주문은 null"),
566
+ paymentId: z.string().describe("환불한 결제"),
567
+ refundId: z.string().describe("환불 이력 id(결제 상세 `refunds[].refundId`)"),
568
+ amount: z.number().int().describe("돌려준 현금 (원)"),
569
+ reason: z.string().describe("판매자가 적은 환불 사유"),
570
+ currency: z.string().describe("주문 통화"),
571
+ testPayment: z.boolean(),
572
+ });
573
+
574
+ export const orderRefundedWebhookEventSchema = z.object({
575
+ ...envelopeFields,
576
+ type: z.literal("ORDER.REFUNDED"),
577
+ data: orderRefundedWebhookEventDataSchema,
578
+ });
579
+
545
580
  /** 웹훅 요청 본문. `type`으로 판별하며 `data` 형태가 타입별로 다르다 */
546
581
  export const webhookEventSchema = z
547
582
  .discriminatedUnion("type", [
@@ -557,6 +592,7 @@ export const webhookEventSchema = z
557
592
  inventoryChangedWebhookEventSchema,
558
593
  orderPaymentAddedWebhookEventSchema,
559
594
  orderPaymentRequestedWebhookEventSchema,
595
+ orderRefundedWebhookEventSchema,
560
596
  ])
561
597
  .describe(
562
598
  "웹훅 요청 본문 (얇은 이벤트). 개인정보를 싣지 않으며 `data`의 id로 관리 API를 조회한다",