@sayren/store-sdk 0.63.0 → 0.65.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.
- package/dist/index.d.mts +713 -474
- package/dist/index.mjs +171 -21
- package/package.json +1 -1
- package/src/client.ts +36 -5
- package/src/schemas/ai.ts +23 -0
- package/src/schemas/app-manifest.ts +3 -1
- package/src/schemas/claim.ts +44 -1
- package/src/schemas/customer.ts +81 -2
- package/src/schemas/misc.ts +2 -0
- package/src/schemas/order.ts +52 -2
- package/src/schemas/payment.ts +19 -1
- package/src/schemas/scope-descriptions.ts +4 -0
- package/src/schemas/scope.ts +4 -0
- package/src/schemas/webhook-event.ts +38 -2
package/src/schemas/ai.ts
CHANGED
|
@@ -399,6 +399,25 @@ export const storeSetupBriefSchema = z.object({
|
|
|
399
399
|
});
|
|
400
400
|
export type StoreSetupBrief = z.infer<typeof storeSetupBriefSchema>;
|
|
401
401
|
|
|
402
|
+
/**
|
|
403
|
+
* 보내는 사람의 시간대(IANA 이름, 예 `Asia/Seoul`) — 브라우저 `Intl.DateTimeFormat().resolvedOptions().timeZone`. AI는 시각을 이 시간대로 쓴다.
|
|
404
|
+
* 생략하면 `Asia/Seoul`이다
|
|
405
|
+
*/
|
|
406
|
+
export const aiTimeZoneSchema = z
|
|
407
|
+
.string()
|
|
408
|
+
.max(64)
|
|
409
|
+
.refine(
|
|
410
|
+
(value) => {
|
|
411
|
+
try {
|
|
412
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value });
|
|
413
|
+
return true;
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
{ message: "IANA 시간대 이름이어야 합니다" },
|
|
419
|
+
);
|
|
420
|
+
|
|
402
421
|
export const createAiConversationRequestSchema = strictRequest(
|
|
403
422
|
z.object({
|
|
404
423
|
/** 웹사이트에서 남긴 초안(#124 M2) — 연결한 사용자만 쓴다. 주면 첫 메시지로 실행을 시작한다 */
|
|
@@ -407,6 +426,8 @@ export const createAiConversationRequestSchema = strictRequest(
|
|
|
407
426
|
brief: storeSetupBriefSchema.optional(),
|
|
408
427
|
/** AI 종류 — 주지 않으면 초안으로 시작하면 `DESIGNER`, 아니면 `STORE`다 */
|
|
409
428
|
agent: aiAgentSchema.optional(),
|
|
429
|
+
/** 첫 실행의 시간대(`draftId`로 시작할 때) */
|
|
430
|
+
timeZone: aiTimeZoneSchema.optional(),
|
|
410
431
|
}),
|
|
411
432
|
);
|
|
412
433
|
export type CreateAiConversationRequest = z.infer<typeof createAiConversationRequestSchema>;
|
|
@@ -429,6 +450,8 @@ export const sendAiMessageRequestSchema = strictRequest(
|
|
|
429
450
|
selection: aiPreviewSelectionSchema.optional(),
|
|
430
451
|
/** 디자인 탭에서 고른 아트보드(2026-10-14) — 모델은 그 아트보드를 기준으로 고친다 */
|
|
431
452
|
designSelection: aiDesignSelectionSchema.optional(),
|
|
453
|
+
/** 보내는 사람의 시간대 — 이 실행의 AI가 시각을 이 시간대로 쓴다 */
|
|
454
|
+
timeZone: aiTimeZoneSchema.optional(),
|
|
432
455
|
})
|
|
433
456
|
.refine((body) => body.text.length > 0 || body.attachments.length > 0, {
|
|
434
457
|
message: "내용이나 첨부가 있어야 합니다",
|
|
@@ -45,7 +45,7 @@ export const appHandleSchema = z
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* 설치 앱에 열지 않는 스코프 — 구성원(초대로 권한 상승), 상점 설정 전반(`storefrontOrigins`로 소셜 로그인 code 가로채기), 스토어 웹훅
|
|
48
|
-
* (앱 구독으로 대체, 제거 뒤에도 남는 문제), 스토어프론트 편집·발행, 감사
|
|
48
|
+
* (앱 구독으로 대체, 제거 뒤에도 남는 문제), 스토어프론트 편집·발행, 감사 로그, 구매자 개인정보 원문(#217). 커스텀 연동은 계정 액세스 토큰(#71)이 맡는다
|
|
49
49
|
*/
|
|
50
50
|
export const NON_INSTALLABLE_SCOPES: ReadonlySet<string> = new Set([
|
|
51
51
|
"member:r",
|
|
@@ -56,6 +56,7 @@ export const NON_INSTALLABLE_SCOPES: ReadonlySet<string> = new Set([
|
|
|
56
56
|
"storefront:r",
|
|
57
57
|
"storefront:rw",
|
|
58
58
|
"audit:r",
|
|
59
|
+
"privacy:r",
|
|
59
60
|
]);
|
|
60
61
|
|
|
61
62
|
/** 앱이 요청할 수 있는 스코프 — 카탈로그 전체에서 위를 뺀 것 */
|
|
@@ -134,6 +135,7 @@ export const APP_EVENT_SCOPES: Record<AppEventType, string | null> = {
|
|
|
134
135
|
"ORDER.FLOW_NOTIFICATION": "order:r",
|
|
135
136
|
"ORDER.FLOW_STATE_CHANGED": "order:r",
|
|
136
137
|
"ORDER.PAYMENT_ADDED": "order:r",
|
|
138
|
+
"ORDER.REFUNDED": "order:r",
|
|
137
139
|
"ORDER.PAYMENT_REQUESTED": "order:r",
|
|
138
140
|
"CLAIM.CANCEL_REQUESTED": "claim:r",
|
|
139
141
|
"CLAIM.RETURN_REQUESTED": "claim:r",
|
package/src/schemas/claim.ts
CHANGED
|
@@ -181,6 +181,41 @@ export const rejectClaimRequestSchema = strictRequest(
|
|
|
181
181
|
|
|
182
182
|
export type RejectClaimRequest = z.infer<typeof rejectClaimRequestSchema>;
|
|
183
183
|
|
|
184
|
+
/** `POST /v1/claims/{claimId}/approve` 요청 — 본문은 생략할 수 있다 */
|
|
185
|
+
export const approveClaimRequestSchema = strictRequest(
|
|
186
|
+
z.object({
|
|
187
|
+
deductAmount: z
|
|
188
|
+
.number()
|
|
189
|
+
.int()
|
|
190
|
+
.min(0)
|
|
191
|
+
.optional()
|
|
192
|
+
.describe(
|
|
193
|
+
"환불액에서 뺄 추가 차감(원). 생략하면 주문 흐름이 접수 때 계산한 차감액, 그것도 없으면 0이다",
|
|
194
|
+
),
|
|
195
|
+
refundAmount: z
|
|
196
|
+
.number()
|
|
197
|
+
.int()
|
|
198
|
+
.min(0)
|
|
199
|
+
.optional()
|
|
200
|
+
.describe(
|
|
201
|
+
"최종 현금 환불액(#365, 원). 생략하면 서버 계산값이다. 배송비 환불 여부·귀책과 무관하게 판매자가 정한다. " +
|
|
202
|
+
"배송비·쿠폰 정산 기록과 적립금 반환은 계산값대로 하고 현금만 이 값이다. 결제 잔액(`availableRefundAmount`)을 넘으면 409 `REFUND_EXCEEDS_BALANCE`. " +
|
|
203
|
+
"`deductAmount`와 함께 주면 400이다. 응답·웹훅의 `calculatedRefundAmount`가 서버 계산값이다",
|
|
204
|
+
),
|
|
205
|
+
holdRefund: z
|
|
206
|
+
.boolean()
|
|
207
|
+
.optional()
|
|
208
|
+
.describe(
|
|
209
|
+
"true면 승인만 하고 결제 환불은 대기로 남긴다(#115). 결제 상세의 `POST /v1/payments/refunds/{refundId}/process`로 따로 환불한다",
|
|
210
|
+
),
|
|
211
|
+
}),
|
|
212
|
+
).refine((body) => body.refundAmount === undefined || body.deductAmount === undefined, {
|
|
213
|
+
message: "refundAmount와 deductAmount는 함께 줄 수 없습니다",
|
|
214
|
+
path: ["refundAmount"],
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export type ApproveClaimRequest = z.infer<typeof approveClaimRequestSchema>;
|
|
218
|
+
|
|
184
219
|
export const approveClaimResultSchema = z.object({
|
|
185
220
|
claimId: z.string(),
|
|
186
221
|
refundAmount: z
|
|
@@ -188,8 +223,16 @@ export const approveClaimResultSchema = z.object({
|
|
|
188
223
|
.int()
|
|
189
224
|
.describe(
|
|
190
225
|
"실제 환불한 금액 — 수량 비율 상품 실결제 금액(쿠폰 배분을 뺀 금액) + 배송비 환불 − 반품 배송비 − 조건부 무료배송 미달 차감 − " +
|
|
191
|
-
"쿠폰 조건 미달 차감(`couponChargeAmount`) + 되돌린 쿠폰 차감(`couponChargeRefundAmount`) − 추가 차감(`deductAmount`)",
|
|
226
|
+
"쿠폰 조건 미달 차감(`couponChargeAmount`) + 되돌린 쿠폰 차감(`couponChargeRefundAmount`) − 추가 차감(`deductAmount`). 판매자가 `refundAmount`를 정했으면 그 값이다",
|
|
192
227
|
),
|
|
228
|
+
calculatedRefundAmount: z
|
|
229
|
+
.number()
|
|
230
|
+
.int()
|
|
231
|
+
.optional()
|
|
232
|
+
.describe(
|
|
233
|
+
"서버가 계산한 현금 환불액(#365). 판매자가 `refundAmount`를 정하지 않았으면 `refundAmount`와 같다. 이 필드가 없는 이전 응답은 `refundAmount`와 같다고 읽는다",
|
|
234
|
+
),
|
|
235
|
+
|
|
193
236
|
deductAmount: z.number().int().describe("승인 때 셀러가 추가로 차감한 금액"),
|
|
194
237
|
quantity: z.number().int().describe("환불한 수량"),
|
|
195
238
|
deliveryFeeRefundAmount: z
|
package/src/schemas/customer.ts
CHANGED
|
@@ -54,6 +54,79 @@ export const customerSummarySchema = z
|
|
|
54
54
|
.extend(customerStatsSchema.shape);
|
|
55
55
|
export type CustomerSummary = z.infer<typeof customerSummarySchema>;
|
|
56
56
|
|
|
57
|
+
// ── 내보내기 (이슈 #217) ────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/** 내보내기에서 고를 수 있는 필드 */
|
|
60
|
+
export const customerExportFieldSchema = z
|
|
61
|
+
.enum([
|
|
62
|
+
"name",
|
|
63
|
+
"email",
|
|
64
|
+
"phone",
|
|
65
|
+
"joinedAt",
|
|
66
|
+
"orderCount",
|
|
67
|
+
"totalPaid",
|
|
68
|
+
"lastOrderedAt",
|
|
69
|
+
"point",
|
|
70
|
+
"marketingAgreed",
|
|
71
|
+
"marketingUpdatedAt",
|
|
72
|
+
"birthdate",
|
|
73
|
+
"gender",
|
|
74
|
+
"defaultAddress",
|
|
75
|
+
])
|
|
76
|
+
.describe(
|
|
77
|
+
"내보낼 필드. `marketingUpdatedAt`은 마케팅 수신 동의·철회가 마지막으로 바뀐 시각, `defaultAddress`는 기본 배송지다",
|
|
78
|
+
);
|
|
79
|
+
export type CustomerExportField = z.infer<typeof customerExportFieldSchema>;
|
|
80
|
+
|
|
81
|
+
export const CUSTOMER_EXPORT_MAX_SIZE = 500;
|
|
82
|
+
|
|
83
|
+
/** `GET /v1/customers/export` 쿼리 — 목록과 같은 조건 + 내보낼 필드 */
|
|
84
|
+
export const customerExportQuerySchema = customerListQuerySchema.extend({
|
|
85
|
+
fields: z
|
|
86
|
+
.string()
|
|
87
|
+
.describe(
|
|
88
|
+
"내보낼 필드(쉼표로 구분, 하나 이상). `customerExportFieldSchema`의 값이다. 모르는 값은 400이다",
|
|
89
|
+
),
|
|
90
|
+
...pageQueryShape({ defaultSize: 100, maxSize: CUSTOMER_EXPORT_MAX_SIZE }),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/** 내보내기 기본 배송지 — 휴대폰은 `phone`과 같은 규칙으로 가린다 */
|
|
94
|
+
export const customerExportAddressSchema = z.object({
|
|
95
|
+
receiverName: z.string(),
|
|
96
|
+
phone: z.string().nullable(),
|
|
97
|
+
zipCode: z.string(),
|
|
98
|
+
address1: z.string(),
|
|
99
|
+
address2: z.string().nullable(),
|
|
100
|
+
countryCode: z.string(),
|
|
101
|
+
city: z.string().nullable(),
|
|
102
|
+
region: z.string().nullable(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/** 내보내기 한 줄 — 고른 필드만 싣는다 */
|
|
106
|
+
export const customerExportRowSchema = z.object({
|
|
107
|
+
customerId: z.string(),
|
|
108
|
+
name: z.string().optional(),
|
|
109
|
+
email: z.string().nullable().optional(),
|
|
110
|
+
phone: z
|
|
111
|
+
.string()
|
|
112
|
+
.nullable()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe(
|
|
115
|
+
"휴대폰. 스코프 `privacy:r`이 있으면 전체 번호, 없으면 가운데를 가린 번호(`010-****-1234`)",
|
|
116
|
+
),
|
|
117
|
+
joinedAt: z.string().optional(),
|
|
118
|
+
orderCount: z.number().int().optional(),
|
|
119
|
+
totalPaid: z.number().int().optional().describe("구매 금액(원) — 목록과 같은 기준"),
|
|
120
|
+
lastOrderedAt: z.string().nullable().optional(),
|
|
121
|
+
point: z.number().int().optional(),
|
|
122
|
+
marketingAgreed: z.boolean().optional(),
|
|
123
|
+
marketingUpdatedAt: z.string().nullable().optional(),
|
|
124
|
+
birthdate: z.string().nullable().optional(),
|
|
125
|
+
gender: z.string().nullable().optional(),
|
|
126
|
+
defaultAddress: customerExportAddressSchema.nullable().optional(),
|
|
127
|
+
});
|
|
128
|
+
export type CustomerExportRow = z.infer<typeof customerExportRowSchema>;
|
|
129
|
+
|
|
57
130
|
export const customerAddressSchema = z.object({
|
|
58
131
|
addressId: z.string(),
|
|
59
132
|
alias: z.string(),
|
|
@@ -148,12 +221,18 @@ export const customerConsentListQuerySchema = z.object({
|
|
|
148
221
|
...pageQueryShape(),
|
|
149
222
|
});
|
|
150
223
|
|
|
151
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* 구매자 상세 — 휴대폰(구매자·배송지)은 스코프 `privacy:r`이 있으면 전체 번호, 없으면 가린 번호(`010-****-1234`)다(#217).
|
|
226
|
+
* 가린 경우 `userinfo.phone_number`는 싣지 않는다
|
|
227
|
+
*/
|
|
152
228
|
export const customerDetailSchema = z.object({
|
|
153
229
|
customerId: z.string(),
|
|
154
230
|
name: z.string(),
|
|
155
231
|
email: z.string().nullable(),
|
|
156
|
-
phone: z
|
|
232
|
+
phone: z
|
|
233
|
+
.string()
|
|
234
|
+
.nullable()
|
|
235
|
+
.describe("휴대폰. `privacy:r`이 있으면 전체 번호, 없으면 가운데를 가린 번호"),
|
|
157
236
|
joinedAt: z.string(),
|
|
158
237
|
point: z.number().int(),
|
|
159
238
|
marketingAgreed: z.boolean(),
|
package/src/schemas/misc.ts
CHANGED
|
@@ -169,6 +169,8 @@ export const eventTypeSchema = z
|
|
|
169
169
|
"ORDER.PAYMENT_ADDED",
|
|
170
170
|
// 흐름 노드 「결제 요청」이 결제 링크를 만듦(이슈 #117)
|
|
171
171
|
"ORDER.PAYMENT_REQUESTED",
|
|
172
|
+
// 판매자가 금액만 환불함(#365)
|
|
173
|
+
"ORDER.REFUNDED",
|
|
172
174
|
])
|
|
173
175
|
.describe(
|
|
174
176
|
"웹훅 이벤트 타입. `ORDER.ADDRESS_CHANGE_REQUESTED`는 현재 발송하지 않음(호환을 위해 값만 남았고, 구독해도 이벤트가 오지 않는다)",
|
package/src/schemas/order.ts
CHANGED
|
@@ -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("취소 후 누적 취소 수량"),
|
package/src/schemas/payment.ts
CHANGED
|
@@ -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(
|
|
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()
|
|
@@ -25,6 +25,10 @@ export const SCOPE_DESCRIPTIONS: Record<StoreScopeResource, { r: string; rw: str
|
|
|
25
25
|
r: "위치별 재고와 재고 변동 내역을 읽습니다",
|
|
26
26
|
rw: "재고를 조정·실사하고 위치 사이에서 이동합니다",
|
|
27
27
|
},
|
|
28
|
+
privacy: {
|
|
29
|
+
r: "구매자 개인정보(휴대폰 전체 번호)를 내보냅니다",
|
|
30
|
+
rw: "구매자 개인정보(휴대폰 전체 번호)를 내보냅니다",
|
|
31
|
+
},
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
/** `{resource}:{r|rw}` → 문장. 카탈로그에 없는 스코프는 null */
|
package/src/schemas/scope.ts
CHANGED
|
@@ -35,6 +35,8 @@ export const STORE_SCOPE_RESOURCES = {
|
|
|
35
35
|
flow: ["r", "rw"],
|
|
36
36
|
// 위치별 재고·재고 조정·이동·변동 내역(이슈 #132) — 기존 재고 표면(조합 재고 수정, 상품·옵션의 `stockQuantity`)은 product 스코프다
|
|
37
37
|
inventory: ["r", "rw"],
|
|
38
|
+
// 구매자 개인정보 원문(이슈 #217) — 내보내기의 휴대폰 전체 번호. 없으면 가린 번호다. 쓰기 API가 없어 `r`만 존재한다
|
|
39
|
+
privacy: ["r"],
|
|
38
40
|
} as const satisfies Record<string, readonly ScopeAccess[]>;
|
|
39
41
|
|
|
40
42
|
export type StoreScopeResource = keyof typeof STORE_SCOPE_RESOURCES;
|
|
@@ -74,6 +76,7 @@ export const STORE_ROLE_SCOPES = {
|
|
|
74
76
|
"storefront:rw",
|
|
75
77
|
"flow:rw",
|
|
76
78
|
"inventory:rw",
|
|
79
|
+
"privacy:r",
|
|
77
80
|
],
|
|
78
81
|
ADMIN: [
|
|
79
82
|
"product:rw",
|
|
@@ -92,6 +95,7 @@ export const STORE_ROLE_SCOPES = {
|
|
|
92
95
|
"storefront:rw",
|
|
93
96
|
"flow:rw",
|
|
94
97
|
"inventory:rw",
|
|
98
|
+
"privacy:r",
|
|
95
99
|
],
|
|
96
100
|
STAFF: [
|
|
97
101
|
"product:rw",
|
|
@@ -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를 조회한다",
|