@sayren/storefront-sdk 0.19.0 → 0.20.1
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/auth/index.mjs +1 -1
- package/dist/{checkout-CCsO8PY2.mjs → checkout-DSAf758s.mjs} +41 -8
- package/dist/{client-Dgpp9T8r.mjs → client-jOhA2sZX.mjs} +51 -4
- package/dist/{index-CfOZjg2U.d.mts → index-q0XQ08--.d.mts} +185 -16
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -3
- package/dist/payments/index.d.mts +6 -1
- package/dist/payments/index.mjs +24 -3
- package/package.json +1 -1
- package/src/client.ts +14 -0
- package/src/index.ts +1 -0
- package/src/payments/index.ts +26 -2
- package/src/schemas/checkout.ts +85 -3
- package/src/schemas/claim.ts +18 -0
- package/src/schemas/coupon.ts +39 -0
- package/src/schemas/order.ts +20 -3
- package/src/schemas/points.ts +63 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dr as PaymentOption, et as createStorefrontClient, fr as PaymentStart, pr as PaymentStatus, qn as CashReceiptRequest, tr as GuestInfo, vr as ShippingAddressInput, yr as StartPaymentRequest } from "../index-q0XQ08--.mjs";
|
|
2
2
|
//#region src/payments/index.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* 스토어프론트 결제 — 브라우저 전용(`@sayren/storefront-sdk/payments`).
|
|
@@ -9,6 +9,9 @@ import { Bn as CashReceiptRequest, Jn as GuestInfo, cr as ShippingAddressInput,
|
|
|
9
9
|
* if (result.status === "COMPLETED") location.assign(`/orders/${result.orderId}`);
|
|
10
10
|
* ```
|
|
11
11
|
*
|
|
12
|
+
* 결제 금액 전체를 적립금으로 낸 주문은 결제창이 없다. 결제 시작 응답이 `status: "completed"`·`payUrl: null`이면
|
|
13
|
+
* SDK는 창을 열지 않고(미리 연 팝업은 닫는다) 바로 `COMPLETED`를 돌려준다. 화면 분기는 그대로다.
|
|
14
|
+
*
|
|
12
15
|
* 결제창은 결제 서비스(`payUrl`)가 그린다. SDK는 그 창을 팝업으로 열고 결과 메시지를 받아 결제 상태 조회로 확정한다.
|
|
13
16
|
* 모바일(또는 `mode: "redirect"`)에서는 현재 탭을 `payUrl`로 보내고, 결제가 끝나면 구매자가 `returnUrl`로 돌아온다 —
|
|
14
17
|
* 복귀 화면에서 {@link Payments.result}를 부른다.
|
|
@@ -160,6 +163,8 @@ export interface Payments {
|
|
|
160
163
|
/**
|
|
161
164
|
* 이미 받은 결제 시작 값으로 결제창만 연다 — 결제 시작을 서버 함수로 하는 SSR 앱용.
|
|
162
165
|
* 클릭 핸들러에서 {@link Payments.prepareWindow}로 창을 먼저 열고 그 핸들을 `window`로 넘긴다.
|
|
166
|
+
* 결제 시작 값이 `status: "completed"`(결제창 없이 끝난 결제, `payUrl: null`)면 창을 열지 않고 미리 연 창을 닫은 뒤
|
|
167
|
+
* 바로 `COMPLETED`를 돌려준다. `start`·`retry`도 같다.
|
|
163
168
|
*/
|
|
164
169
|
open(start: PaymentStart, options?: {
|
|
165
170
|
mode?: PaymentMode;
|
package/dist/payments/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { y as paymentMessageSchema } from "../checkout-DSAf758s.mjs";
|
|
2
2
|
//#region src/payments/index.ts
|
|
3
3
|
/** 복귀 주소에 결제 서비스가 붙이는 쿼리 */
|
|
4
4
|
const RETURN_PAYMENT_ID = "sayrenPaymentId";
|
|
@@ -252,13 +252,34 @@ function createPayments(options) {
|
|
|
252
252
|
cancelWatch = cancelThis;
|
|
253
253
|
});
|
|
254
254
|
}
|
|
255
|
+
/** 결제창을 열지 않는 결제 시작 응답의 결과 */
|
|
256
|
+
async function settleWithoutWindow(start) {
|
|
257
|
+
if (start.status === "completed" && start.orderId) return {
|
|
258
|
+
status: "COMPLETED",
|
|
259
|
+
paymentId: start.paymentId,
|
|
260
|
+
orderId: start.orderId
|
|
261
|
+
};
|
|
262
|
+
try {
|
|
263
|
+
return paymentResultOf(await client.payments.getStatus(start.paymentId));
|
|
264
|
+
} catch {
|
|
265
|
+
return {
|
|
266
|
+
status: "PROCESSING",
|
|
267
|
+
paymentId: start.paymentId
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
255
271
|
function open(start, openOptions = {}) {
|
|
256
272
|
stopWatch();
|
|
257
273
|
const popup = resolvePrepared(openOptions.window, openOptions.mode).popup;
|
|
258
|
-
const
|
|
274
|
+
const payUrl = start.payUrl;
|
|
275
|
+
if (start.status !== "pending" || !payUrl) {
|
|
276
|
+
popup?.close();
|
|
277
|
+
return settleWithoutWindow(start);
|
|
278
|
+
}
|
|
279
|
+
const url = popup ? popupUrl(payUrl) : null;
|
|
259
280
|
if (!popup || !url) {
|
|
260
281
|
popup?.close();
|
|
261
|
-
env.navigate(
|
|
282
|
+
env.navigate(payUrl);
|
|
262
283
|
return new Promise(() => {});
|
|
263
284
|
}
|
|
264
285
|
popup.navigate(url);
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -91,6 +91,11 @@ import {
|
|
|
91
91
|
updateProfileRequestSchema,
|
|
92
92
|
} from "./schemas/member";
|
|
93
93
|
import { deliveryTrackingSchema, myOrderSchema } from "./schemas/order";
|
|
94
|
+
import {
|
|
95
|
+
memberPointEntryPageSchema,
|
|
96
|
+
memberPointsSchema,
|
|
97
|
+
storefrontPointPolicySchema,
|
|
98
|
+
} from "./schemas/points";
|
|
94
99
|
import {
|
|
95
100
|
type CreateReviewRequest,
|
|
96
101
|
createReviewRequestSchema,
|
|
@@ -248,6 +253,8 @@ export function createStorefrontClient(options: StorefrontClientOptions) {
|
|
|
248
253
|
|
|
249
254
|
catalog: {
|
|
250
255
|
listCategories: () => http.request("GET", "/categories", z.array(categoryNodeSchema)),
|
|
256
|
+
/** `GET /point-policy` — 상점 적립 정책(리뷰·구매 적립, 유효 기간, 리뷰 작성·수정 기한, #93). 리뷰 혜택 안내에 쓴다 */
|
|
257
|
+
getPointPolicy: () => http.request("GET", "/point-policy", storefrontPointPolicySchema),
|
|
251
258
|
searchProducts: (params?: ProductSearchParams) =>
|
|
252
259
|
http.request("GET", "/products", productPageSchema, { query: params as Query }),
|
|
253
260
|
getProduct: (productId: string) =>
|
|
@@ -399,6 +406,13 @@ export function createStorefrontClient(options: StorefrontClientOptions) {
|
|
|
399
406
|
|
|
400
407
|
member: {
|
|
401
408
|
me: () => http.request("GET", "/me", memberSchema),
|
|
409
|
+
/** `GET /me/points` — 내 적립금 잔액과 30일 안 소멸 예정액(#93) */
|
|
410
|
+
getPoints: () => http.request("GET", "/me/points", memberPointsSchema),
|
|
411
|
+
/** `GET /me/points/history` — 내 적립금 내역(최신순) */
|
|
412
|
+
listPointHistory: (params?: PageParams) =>
|
|
413
|
+
http.request("GET", "/me/points/history", memberPointEntryPageSchema, {
|
|
414
|
+
query: params as Query,
|
|
415
|
+
}),
|
|
402
416
|
updateMe: (body: UpdateProfileRequest) =>
|
|
403
417
|
http.requestVoid("PATCH", "/me", { body: updateProfileRequestSchema.parse(body) }),
|
|
404
418
|
/** 약관·개인정보·마케팅 동의의 현재 상태와 재동의 필요 여부 */
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from "./schemas/fulfillment";
|
|
|
17
17
|
export * from "./schemas/inquiry";
|
|
18
18
|
export * from "./schemas/member";
|
|
19
19
|
export * from "./schemas/order";
|
|
20
|
+
export * from "./schemas/points";
|
|
20
21
|
export * from "./schemas/product-options";
|
|
21
22
|
export * from "./schemas/review";
|
|
22
23
|
export * from "./schemas/store";
|
package/src/payments/index.ts
CHANGED
|
@@ -19,6 +19,9 @@ import {
|
|
|
19
19
|
* if (result.status === "COMPLETED") location.assign(`/orders/${result.orderId}`);
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
|
+
* 결제 금액 전체를 적립금으로 낸 주문은 결제창이 없다. 결제 시작 응답이 `status: "completed"`·`payUrl: null`이면
|
|
23
|
+
* SDK는 창을 열지 않고(미리 연 팝업은 닫는다) 바로 `COMPLETED`를 돌려준다. 화면 분기는 그대로다.
|
|
24
|
+
*
|
|
22
25
|
* 결제창은 결제 서비스(`payUrl`)가 그린다. SDK는 그 창을 팝업으로 열고 결과 메시지를 받아 결제 상태 조회로 확정한다.
|
|
23
26
|
* 모바일(또는 `mode: "redirect"`)에서는 현재 탭을 `payUrl`로 보내고, 결제가 끝나면 구매자가 `returnUrl`로 돌아온다 —
|
|
24
27
|
* 복귀 화면에서 {@link Payments.result}를 부른다.
|
|
@@ -294,6 +297,8 @@ export interface Payments {
|
|
|
294
297
|
/**
|
|
295
298
|
* 이미 받은 결제 시작 값으로 결제창만 연다 — 결제 시작을 서버 함수로 하는 SSR 앱용.
|
|
296
299
|
* 클릭 핸들러에서 {@link Payments.prepareWindow}로 창을 먼저 열고 그 핸들을 `window`로 넘긴다.
|
|
300
|
+
* 결제 시작 값이 `status: "completed"`(결제창 없이 끝난 결제, `payUrl: null`)면 창을 열지 않고 미리 연 창을 닫은 뒤
|
|
301
|
+
* 바로 `COMPLETED`를 돌려준다. `start`·`retry`도 같다.
|
|
297
302
|
*/
|
|
298
303
|
open(
|
|
299
304
|
start: PaymentStart,
|
|
@@ -484,6 +489,18 @@ export function createPayments(options: PaymentsOptions): Payments {
|
|
|
484
489
|
});
|
|
485
490
|
}
|
|
486
491
|
|
|
492
|
+
/** 결제창을 열지 않는 결제 시작 응답의 결과 */
|
|
493
|
+
async function settleWithoutWindow(start: PaymentStart): Promise<PaymentResult> {
|
|
494
|
+
if (start.status === "completed" && start.orderId) {
|
|
495
|
+
return { status: "COMPLETED", paymentId: start.paymentId, orderId: start.orderId };
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
return paymentResultOf(await client.payments.getStatus(start.paymentId));
|
|
499
|
+
} catch {
|
|
500
|
+
return { status: "PROCESSING", paymentId: start.paymentId };
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
487
504
|
function open(
|
|
488
505
|
start: PaymentStart,
|
|
489
506
|
openOptions: { mode?: PaymentMode; window?: PreparedPaymentWindow } = {},
|
|
@@ -492,10 +509,17 @@ export function createPayments(options: PaymentsOptions): Payments {
|
|
|
492
509
|
stopWatch();
|
|
493
510
|
const prepared = resolvePrepared(openOptions.window, openOptions.mode);
|
|
494
511
|
const popup = prepared.popup;
|
|
495
|
-
const
|
|
512
|
+
const payUrl = start.payUrl;
|
|
513
|
+
if (start.status !== "pending" || !payUrl) {
|
|
514
|
+
// 결제창 없이 끝난 결제(적립금 전액 결제, #93)이거나 이 SDK가 모르는 상태다 — 창을 열지 않는다.
|
|
515
|
+
// 주문 번호가 오면 그대로 완료이고, 아니면 결제 상태 조회로 확정한다(원천은 언제나 `GET /payments/{id}`)
|
|
516
|
+
popup?.close();
|
|
517
|
+
return settleWithoutWindow(start);
|
|
518
|
+
}
|
|
519
|
+
const url = popup ? popupUrl(payUrl) : null;
|
|
496
520
|
if (!popup || !url) {
|
|
497
521
|
popup?.close();
|
|
498
|
-
env.navigate(
|
|
522
|
+
env.navigate(payUrl);
|
|
499
523
|
// 페이지가 떠난다 — 결과는 복귀 화면의 result()가 읽는다
|
|
500
524
|
return new Promise<PaymentResult>(() => {});
|
|
501
525
|
}
|
package/src/schemas/checkout.ts
CHANGED
|
@@ -102,7 +102,35 @@ export const checkoutSessionSchema = z.object({
|
|
|
102
102
|
.optional()
|
|
103
103
|
.default(0)
|
|
104
104
|
.describe("배송비 쿠폰 할인. 쿠폰이 없으면 0"),
|
|
105
|
+
pointAmount: z
|
|
106
|
+
.number()
|
|
107
|
+
.int()
|
|
108
|
+
.optional()
|
|
109
|
+
.default(0)
|
|
110
|
+
.describe(
|
|
111
|
+
"사용 적립금(#93). 주문서는 적립금 전이라 0이고, 결제 시작 응답은 쓴 적립금이다. `totalAmount`는 적립금을 뺀 금액이다",
|
|
112
|
+
),
|
|
105
113
|
}),
|
|
114
|
+
points: z
|
|
115
|
+
.object({
|
|
116
|
+
enabled: z
|
|
117
|
+
.boolean()
|
|
118
|
+
.describe("이 주문서에서 적립금을 쓸 수 있는가. 비회원·사용 꺼짐이면 false"),
|
|
119
|
+
balance: z.number().int().describe("내 적립금 잔액"),
|
|
120
|
+
maxUsable: z
|
|
121
|
+
.number()
|
|
122
|
+
.int()
|
|
123
|
+
.describe(
|
|
124
|
+
"이 주문서(쿠폰 전, 배송지 반영 전)에서 쓸 수 있는 최대 금액. 쿠폰·배송지를 바꾸면 금액 미리보기로 다시 받는다",
|
|
125
|
+
),
|
|
126
|
+
unit: z.number().int().describe("사용 단위(원)"),
|
|
127
|
+
minAmount: z.number().int().describe("한 번에 쓰는 최소 금액(원)"),
|
|
128
|
+
minBalance: z.number().int().describe("쓸 수 있는 최소 보유액(원)"),
|
|
129
|
+
})
|
|
130
|
+
.nullable()
|
|
131
|
+
.optional()
|
|
132
|
+
.default(null)
|
|
133
|
+
.describe("적립금 사용 안내(#93). 비회원이거나 상점이 적립금 사용을 끄면 null"),
|
|
106
134
|
delivery: checkoutDeliverySchema.describe(
|
|
107
135
|
"배송비 내역. 배송지를 입력하면 금액이 바뀔 수 있다 — 최종 금액은 결제 시작 응답의 `amounts`다",
|
|
108
136
|
),
|
|
@@ -165,6 +193,20 @@ export const paymentMethodSchema = z.enum([
|
|
|
165
193
|
|
|
166
194
|
export type PaymentMethod = z.infer<typeof paymentMethodSchema>;
|
|
167
195
|
|
|
196
|
+
/**
|
|
197
|
+
* 응답에 실리는 결제수단 — 아는 값(`PaymentMethod`)이거나 이 SDK가 모르는 새 값(문자열)이다. 서버가 결제수단을 더해도
|
|
198
|
+
* 응답 파싱이 깨지지 않게 넓게 받는다(`responsePlanCodeSchema`·`easyPayProviderResponseSchema`와 같은 방식, #93 P0).
|
|
199
|
+
* 적립금 전액 결제 주문은 `POINT`가 온다. 화면은 아는 값으로 분기하고 모르는 값은 그대로 표시하거나 「기타」로 다룬다.
|
|
200
|
+
* 요청(결제 옵션)은 값을 골라 보내는 자리라 `paymentMethodSchema`(enum) 그대로다.
|
|
201
|
+
*/
|
|
202
|
+
export const responsePaymentMethodSchema = z
|
|
203
|
+
.union([paymentMethodSchema, z.string()])
|
|
204
|
+
.describe(
|
|
205
|
+
"결제수단. `CARD` 카드 · `BANK_TRANSFER` 계좌이체 · `VIRTUAL_ACCOUNT` 가상계좌 · `MOBILE` 휴대폰 · `EASY_PAY` 간편결제 · " +
|
|
206
|
+
"`POINT` 적립금 전액 결제. 값이 추가될 수 있어 문자열로 받는다",
|
|
207
|
+
);
|
|
208
|
+
export type ResponsePaymentMethod = PaymentMethod | (string & {});
|
|
209
|
+
|
|
168
210
|
/** 결제를 처리하는 PG. 셀러가 직접 계약하고 상점 플랫폼 설정 › 결제에서 연결한다 */
|
|
169
211
|
export const pgProviderSchema = z.enum(["tosspayments", "portone"]);
|
|
170
212
|
export type PgProvider = z.infer<typeof pgProviderSchema>;
|
|
@@ -398,6 +440,16 @@ export const startPaymentRequestSchema = z.object({
|
|
|
398
440
|
"적용하지 못하는 쿠폰이 하나라도 있으면 400 `COUPON_NOT_APPLICABLE`(`details`에 사유)이다. 결제가 실패하거나 만료되면 예약이 풀린다. " +
|
|
399
441
|
"쿠폰을 바꾸려면 새로 결제를 시작한다(재시도는 같은 쿠폰을 쓴다)",
|
|
400
442
|
),
|
|
443
|
+
pointAmount: z
|
|
444
|
+
.number()
|
|
445
|
+
.int()
|
|
446
|
+
.min(0)
|
|
447
|
+
.optional()
|
|
448
|
+
.describe(
|
|
449
|
+
"쓸 적립금(#93, 회원만). 결제 시작이 예약하고 쿠폰 뒤 금액에서 뺀다 — 응답 `amounts.pointAmount`가 실제 사용액이다(단위로 내리고 결제 금액 100원을 남긴다). " +
|
|
450
|
+
"적립금 사용이 꺼져 있으면 409 `POINTS_UNAVAILABLE`, 쓸 수 없으면 400 `POINT_NOT_APPLICABLE`(`details.reason`), 그 사이 잔액이 줄었으면 409 `POINT_BALANCE_CHANGED`다. " +
|
|
451
|
+
"결제가 실패하거나 만료되면 예약이 풀린다",
|
|
452
|
+
),
|
|
401
453
|
});
|
|
402
454
|
|
|
403
455
|
export type StartPaymentRequest = z.infer<typeof startPaymentRequestSchema>;
|
|
@@ -415,14 +467,44 @@ export const retryPaymentRequestSchema = z.object({
|
|
|
415
467
|
|
|
416
468
|
export type RetryPaymentRequest = z.infer<typeof retryPaymentRequestSchema>;
|
|
417
469
|
|
|
470
|
+
/**
|
|
471
|
+
* 결제 시작 상태 — `pending`이면 `payUrl`로 결제창을 연다. `completed`면 결제창 없이 결제가 끝나 주문이 만들어졌고
|
|
472
|
+
* `orderId`가 있다(적립금으로 결제 금액 전체를 낸 주문, #93). 값이 추가될 수 있어 응답은 문자열로 받는다.
|
|
473
|
+
*/
|
|
474
|
+
export const PAYMENT_START_STATUS_VALUES = ["pending", "completed"] as const;
|
|
475
|
+
export type KnownPaymentStartStatus = (typeof PAYMENT_START_STATUS_VALUES)[number];
|
|
476
|
+
|
|
418
477
|
export const paymentStartSchema = z.object({
|
|
419
478
|
paymentId: z.string().describe("결제 id. 상태 조회·재시도의 키다"),
|
|
420
|
-
|
|
421
|
-
|
|
479
|
+
status: z
|
|
480
|
+
.string()
|
|
481
|
+
.optional()
|
|
482
|
+
.default("pending")
|
|
483
|
+
.describe(
|
|
484
|
+
"결제 시작 상태. `pending`이면 `payUrl`로 결제창을 연다. `completed`면 결제창 없이 결제가 끝났고(적립금 전액 결제) `orderId`가 주문 번호다. " +
|
|
485
|
+
"그 밖의 값은 결제 상태 조회(`GET /payments/{paymentId}`)로 확정한다. 이 필드가 없던 서버 응답은 `pending`으로 읽는다",
|
|
486
|
+
),
|
|
487
|
+
orderId: z
|
|
488
|
+
.string()
|
|
489
|
+
.nullable()
|
|
490
|
+
.optional()
|
|
491
|
+
.default(null)
|
|
492
|
+
.describe("만들어진 주문 번호. `status`가 `completed`일 때만 값이 있고 그 밖에는 null"),
|
|
493
|
+
attemptId: z
|
|
494
|
+
.string()
|
|
495
|
+
.nullable()
|
|
496
|
+
.describe(
|
|
497
|
+
"결제 시도 id. PG 주문번호(토스 orderId, 포트원 paymentId)다. 결제창 없이 끝난 결제(`completed`)는 null",
|
|
498
|
+
),
|
|
499
|
+
option: paymentOptionSchema
|
|
500
|
+
.nullable()
|
|
501
|
+
.describe("이 시도의 결제 옵션. 결제창 없이 끝난 결제(`completed`)는 null"),
|
|
422
502
|
payUrl: z
|
|
423
503
|
.string()
|
|
504
|
+
.nullable()
|
|
424
505
|
.describe(
|
|
425
|
-
"결제 서비스 주소. 팝업으로 열려면 쿼리 `mode=popup`을 붙이고, 그대로 이동하면 리다이렉트
|
|
506
|
+
"결제 서비스 주소. 팝업으로 열려면 쿼리 `mode=popup`을 붙이고, 그대로 이동하면 리다이렉트 결제다. " +
|
|
507
|
+
"결제창 없이 끝난 결제(`completed`)는 null이다",
|
|
426
508
|
),
|
|
427
509
|
expiresAt: z.string().describe("결제 요청 만료 시각"),
|
|
428
510
|
testPayment: z
|
package/src/schemas/claim.ts
CHANGED
|
@@ -71,6 +71,15 @@ export const createClaimResultSchema = z.object({
|
|
|
71
71
|
"되돌릴 쿠폰 차감 예상액 — 앞선 취소·반품에서 뺀 쿠폰 조건 미달 차감 중 이번에 돌려받을 몫이다. " +
|
|
72
72
|
"예상 환불액에 이미 반영돼 있다. 서버는 항상 싣는다",
|
|
73
73
|
),
|
|
74
|
+
pointRefundAmount: z
|
|
75
|
+
.number()
|
|
76
|
+
.int()
|
|
77
|
+
.optional()
|
|
78
|
+
.default(0)
|
|
79
|
+
.describe(
|
|
80
|
+
"돌려받을 적립금 예상액 (원, #93) — 결제에 쓴 적립금 중 이번 취소·반품 수량의 몫이다. 예상 환불액(현금)과 별개다. " +
|
|
81
|
+
"차감은 현금에서 먼저 빼고 모자란 몫만 여기서 뺀다. 적립금을 쓰지 않은 주문은 0이다. 서버는 항상 싣는다",
|
|
82
|
+
),
|
|
74
83
|
});
|
|
75
84
|
|
|
76
85
|
export type CreateClaimResult = z.infer<typeof createClaimResultSchema>;
|
|
@@ -117,6 +126,15 @@ export const myClaimSchema = z.object({
|
|
|
117
126
|
"예상 배송비 환불액. 배송 묶음이 모두 취소되거나 판매자 귀책일 때만 0보다 크다. " +
|
|
118
127
|
"이 필드가 생기기 전에 접수된 클레임은 null이다",
|
|
119
128
|
),
|
|
129
|
+
expectedPointRefundAmount: z
|
|
130
|
+
.number()
|
|
131
|
+
.int()
|
|
132
|
+
.optional()
|
|
133
|
+
.default(0)
|
|
134
|
+
.describe(
|
|
135
|
+
"예상 반환 적립금 (원, #93) — 결제에 쓴 적립금 중 이 수량의 몫이다. 예상 환불액(현금)과 별개이고, 차감은 현금에서 먼저 뺀다. " +
|
|
136
|
+
"교환·적립금을 쓰지 않은 주문은 0이다. 서버는 항상 싣는다",
|
|
137
|
+
),
|
|
120
138
|
rejectReason: z.string().nullable(),
|
|
121
139
|
returnAddress: claimReturnAddressSchema
|
|
122
140
|
.nullable()
|
package/src/schemas/coupon.ts
CHANGED
|
@@ -96,6 +96,12 @@ export const pricingAmountsSchema = z.object({
|
|
|
96
96
|
deliveryFee: z.number().int(),
|
|
97
97
|
couponDiscountAmount: z.number().int(),
|
|
98
98
|
deliveryDiscountAmount: z.number().int(),
|
|
99
|
+
pointAmount: z
|
|
100
|
+
.number()
|
|
101
|
+
.int()
|
|
102
|
+
.optional()
|
|
103
|
+
.default(0)
|
|
104
|
+
.describe("사용 적립금(#93). 요청에 `pointAmount`가 없으면 0. 서버는 항상 싣는다"),
|
|
99
105
|
totalAmount: z.number().int().describe("결제 금액"),
|
|
100
106
|
});
|
|
101
107
|
|
|
@@ -107,6 +113,14 @@ export const checkoutPricingRequestSchema = z.object({
|
|
|
107
113
|
.optional()
|
|
108
114
|
.describe("배송지 우편번호. 주면 제주·도서산간 추가 배송비까지 반영한다"),
|
|
109
115
|
coupons: couponApplicationListSchema,
|
|
116
|
+
pointAmount: z
|
|
117
|
+
.number()
|
|
118
|
+
.int()
|
|
119
|
+
.min(0)
|
|
120
|
+
.optional()
|
|
121
|
+
.describe(
|
|
122
|
+
"쓸 적립금(#93, 회원만). 결제 시작과 같은 규칙으로 실제 사용액을 계산한다(예약하지 않는다)",
|
|
123
|
+
),
|
|
110
124
|
});
|
|
111
125
|
export type CheckoutPricingRequest = z.infer<typeof checkoutPricingRequestSchema>;
|
|
112
126
|
|
|
@@ -125,9 +139,34 @@ export const checkoutPricingSchema = z.object({
|
|
|
125
139
|
.optional()
|
|
126
140
|
.describe("주문서 줄의 `lineId`(이슈 #59). 서버는 항상 싣는다"),
|
|
127
141
|
couponDiscountAmount: z.number().int().describe("이 항목에 들어간 쿠폰 할인"),
|
|
142
|
+
pointAllocation: z
|
|
143
|
+
.number()
|
|
144
|
+
.int()
|
|
145
|
+
.optional()
|
|
146
|
+
.default(0)
|
|
147
|
+
.describe("이 항목에 배분한 적립금(#93). 서버는 항상 싣는다"),
|
|
128
148
|
}),
|
|
129
149
|
)
|
|
130
150
|
.describe("주문서 항목별 쿠폰 할인 — 주문서 `items`와 같은 순서"),
|
|
151
|
+
points: z
|
|
152
|
+
.object({
|
|
153
|
+
balance: z.number().int().describe("내 적립금 잔액"),
|
|
154
|
+
maxUsable: z.number().int().describe("이 금액(쿠폰·배송지 반영)에서 쓸 수 있는 최대 적립금"),
|
|
155
|
+
adjustReason: z
|
|
156
|
+
.string()
|
|
157
|
+
.nullable()
|
|
158
|
+
.describe("요청보다 적게 쓴 이유. `MIN_PAYMENT`: 결제 금액 100원을 남기려고 줄였다"),
|
|
159
|
+
rejectReason: z
|
|
160
|
+
.string()
|
|
161
|
+
.nullable()
|
|
162
|
+
.describe(
|
|
163
|
+
"요청했지만 쓰지 못한 이유 — `MIN_BALANCE`·`MIN_AMOUNT`·`NOT_PAYABLE`. 값이 추가될 수 있다",
|
|
164
|
+
),
|
|
165
|
+
})
|
|
166
|
+
.nullable()
|
|
167
|
+
.optional()
|
|
168
|
+
.default(null)
|
|
169
|
+
.describe("적립금(#93). 비회원이거나 적립금 사용이 꺼져 있으면 null"),
|
|
131
170
|
payable: z
|
|
132
171
|
.boolean()
|
|
133
172
|
.describe(
|
package/src/schemas/order.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { easyPayProviderResponseSchema,
|
|
2
|
+
import { easyPayProviderResponseSchema, responsePaymentMethodSchema } from "./checkout";
|
|
3
3
|
import { orderItemStatusSchema } from "./common";
|
|
4
4
|
import { myOrderItemFulfillmentSnapshotSchema } from "./fulfillment";
|
|
5
5
|
import { lineSelectionResponseShape } from "./product-options";
|
|
@@ -32,6 +32,14 @@ export const myOrderItemSchema = z.object({
|
|
|
32
32
|
.describe(
|
|
33
33
|
"이 주문상품에 배분된 쿠폰 할인. 실결제 금액은 `totalPrice − couponDiscountAmount`다. 쿠폰이 없으면 0. 서버는 항상 싣는다",
|
|
34
34
|
),
|
|
35
|
+
pointAllocation: z
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.optional()
|
|
39
|
+
.default(0)
|
|
40
|
+
.describe(
|
|
41
|
+
"이 주문상품에 쓴 적립금 (원, #93). 현금 실결제는 `totalPrice − couponDiscountAmount − pointAllocation`이다. 적립금을 쓰지 않았으면 0. 서버는 항상 싣는다",
|
|
42
|
+
),
|
|
35
43
|
status: orderItemStatusSchema,
|
|
36
44
|
fulfillmentSnapshot: myOrderItemFulfillmentSnapshotSchema.describe(
|
|
37
45
|
"주문 시점 이행 스냅샷(이슈 #57). `type`이 주문 시점 상품 유형이다. 배송 없는 상품(`MANUAL`)이면 `DELIVERED`는 제공 완료다",
|
|
@@ -90,8 +98,9 @@ export const myOrderSchema = z.object({
|
|
|
90
98
|
),
|
|
91
99
|
shippingAddress: orderShippingAddressSchema,
|
|
92
100
|
payment: z.object({
|
|
93
|
-
method:
|
|
94
|
-
"실제 결제수단(PG가 보고한 수단, 모르면 주문서에서 고른 수단). 결제창에서 바뀔 수 있다(카드 결제창의 간편결제 탭 등)"
|
|
101
|
+
method: responsePaymentMethodSchema.describe(
|
|
102
|
+
"실제 결제수단(PG가 보고한 수단, 모르면 주문서에서 고른 수단). 결제창에서 바뀔 수 있다(카드 결제창의 간편결제 탭 등). " +
|
|
103
|
+
"적립금 전액 결제 주문은 `POINT`다. 값이 추가될 수 있어 문자열로 받는다",
|
|
95
104
|
),
|
|
96
105
|
easyPayProvider: easyPayProviderResponseSchema
|
|
97
106
|
.nullable()
|
|
@@ -114,6 +123,14 @@ export const myOrderSchema = z.object({
|
|
|
114
123
|
.optional()
|
|
115
124
|
.default(0)
|
|
116
125
|
.describe("배송비 쿠폰 할인. 쿠폰이 없으면 0. 서버는 항상 싣는다"),
|
|
126
|
+
pointAmount: z
|
|
127
|
+
.number()
|
|
128
|
+
.int()
|
|
129
|
+
.optional()
|
|
130
|
+
.default(0)
|
|
131
|
+
.describe(
|
|
132
|
+
"사용 적립금 (원, #93). `totalAmount`는 적립금을 뺀 결제 금액이다. 적립금을 쓰지 않았으면 0. 서버는 항상 싣는다",
|
|
133
|
+
),
|
|
117
134
|
discounts: z
|
|
118
135
|
.array(
|
|
119
136
|
z.object({
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { pageSchema } from "./common";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 적립금 (이슈 #93) — 상점 적립 정책 안내와 구매자 잔액·내역. 금액은 원 단위 정수다.
|
|
6
|
+
* 적립금 사용(주문서·결제)은 다음 단계에서 가산한다.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** `GET /point-policy` — 상점 적립 정책(인증 없음, 상점 단위). 리뷰 작성·주문 화면의 혜택 안내에 쓴다 */
|
|
10
|
+
export const storefrontPointPolicySchema = z.object({
|
|
11
|
+
reviewReward: z
|
|
12
|
+
.object({
|
|
13
|
+
enabled: z.boolean().describe("리뷰 적립 사용 여부. false면 리뷰 혜택 안내를 그리지 않는다"),
|
|
14
|
+
textPoint: z.number().int().describe("리뷰를 작성하면 주는 적립금(원)"),
|
|
15
|
+
photoPoint: z
|
|
16
|
+
.number()
|
|
17
|
+
.int()
|
|
18
|
+
.describe("사진 리뷰에 더 주는 적립금(원). 사진 리뷰는 `textPoint + photoPoint`다"),
|
|
19
|
+
})
|
|
20
|
+
.describe("리뷰 적립"),
|
|
21
|
+
purchaseEarn: z
|
|
22
|
+
.object({
|
|
23
|
+
enabled: z.boolean().describe("구매 적립 사용 여부"),
|
|
24
|
+
rateBp: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.describe("상점 기본 구매 적립률(만분율, 100 = 1%). 상품마다 다를 수 있다"),
|
|
28
|
+
})
|
|
29
|
+
.describe("구매 적립 — 구매확정 때 실결제 금액(배송비 제외) × 적립률을 준다"),
|
|
30
|
+
expiryDays: z.number().int().nullable().describe("적립금 유효 기간(일). 정하지 않았으면 null"),
|
|
31
|
+
review: z
|
|
32
|
+
.object({
|
|
33
|
+
writableDays: z.number().int().describe("구매확정 뒤 리뷰를 쓸 수 있는 기간(일)"),
|
|
34
|
+
editableDays: z.number().int().describe("리뷰를 쓴 뒤 고칠 수 있는 기간(일)"),
|
|
35
|
+
})
|
|
36
|
+
.describe("리뷰 작성·수정 기한"),
|
|
37
|
+
});
|
|
38
|
+
export type StorefrontPointPolicy = z.infer<typeof storefrontPointPolicySchema>;
|
|
39
|
+
|
|
40
|
+
/** `GET /me/points` — 내 적립금 */
|
|
41
|
+
export const memberPointsSchema = z.object({
|
|
42
|
+
balance: z.number().int().describe("잔액"),
|
|
43
|
+
expiringSoon: z.number().int().describe("30일 안에 소멸할 금액"),
|
|
44
|
+
});
|
|
45
|
+
export type MemberPoints = z.infer<typeof memberPointsSchema>;
|
|
46
|
+
|
|
47
|
+
/** 내 적립금 내역 한 줄 — 셀러 메모는 싣지 않는다 */
|
|
48
|
+
export const memberPointEntrySchema = z.object({
|
|
49
|
+
entryId: z.string(),
|
|
50
|
+
type: z
|
|
51
|
+
.string()
|
|
52
|
+
.describe(
|
|
53
|
+
"유형. `EARN_REVIEW` 리뷰 적립 · `REVOKE_REVIEW` 리뷰 삭제 회수 · `EARN_PURCHASE` 구매 적립 · `EXPIRE` 소멸 · `ADJUST` 상점 지급·차감 · " +
|
|
54
|
+
"`OPENING` 기존 적립금 · `USE_RESERVE` 결제 사용 · `USE_RELEASE` 결제가 실패·만료되어 되돌림 · `REFUND_RESTORE` 취소·반품 환불로 반환. " +
|
|
55
|
+
"값이 추가될 수 있어 문자열로 받는다",
|
|
56
|
+
),
|
|
57
|
+
amount: z.number().int().describe("변동액. 적립 +, 회수·차감·소멸 −"),
|
|
58
|
+
balanceAfter: z.number().int().describe("이 변동 직후 잔액"),
|
|
59
|
+
createdAt: z.string(),
|
|
60
|
+
});
|
|
61
|
+
export type MemberPointEntry = z.infer<typeof memberPointEntrySchema>;
|
|
62
|
+
|
|
63
|
+
export const memberPointEntryPageSchema = pageSchema(memberPointEntrySchema);
|