@sayren/storefront-sdk 0.1.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 +1383 -0
- package/dist/index.mjs +880 -0
- package/package.json +43 -0
- package/src/client.ts +349 -0
- package/src/http.ts +178 -0
- package/src/index.ts +22 -0
- package/src/schemas/auth.ts +41 -0
- package/src/schemas/cart.ts +47 -0
- package/src/schemas/catalog.ts +101 -0
- package/src/schemas/checkout.ts +183 -0
- package/src/schemas/claim.ts +51 -0
- package/src/schemas/common.ts +46 -0
- package/src/schemas/customer-inquiry.ts +39 -0
- package/src/schemas/inquiry.ts +25 -0
- package/src/schemas/member.ts +50 -0
- package/src/schemas/order.ts +76 -0
- package/src/schemas/review.ts +65 -0
- package/src/session.ts +212 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { PageParams } from "./common";
|
|
3
|
+
|
|
4
|
+
export interface CategoryNode {
|
|
5
|
+
categoryId: string;
|
|
6
|
+
name: string;
|
|
7
|
+
depth: number;
|
|
8
|
+
children: CategoryNode[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const categoryNodeSchema: z.ZodType<CategoryNode> = z.lazy(() =>
|
|
12
|
+
z.object({
|
|
13
|
+
categoryId: z.string(),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
depth: z.number().int().min(1).max(4),
|
|
16
|
+
children: z.array(categoryNodeSchema),
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const productCardSchema = z.object({
|
|
21
|
+
productId: z.string(),
|
|
22
|
+
name: z.string(),
|
|
23
|
+
thumbnailUrl: z.url(),
|
|
24
|
+
salePrice: z.number().int(),
|
|
25
|
+
discountedPrice: z.number().int().nullable(),
|
|
26
|
+
discountRate: z.number().int().nullable(),
|
|
27
|
+
freeShipping: z.boolean(),
|
|
28
|
+
averageRating: z.number(),
|
|
29
|
+
reviewCount: z.number().int(),
|
|
30
|
+
soldOut: z.boolean(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type ProductCard = z.infer<typeof productCardSchema>;
|
|
34
|
+
|
|
35
|
+
export const productDetailSchema = productCardSchema.extend({
|
|
36
|
+
description: z.string(),
|
|
37
|
+
images: z.array(z.url()),
|
|
38
|
+
/** 옵션 축(그룹). 배열 순서가 곧 표시 순서다 */
|
|
39
|
+
optionGroups: z.array(
|
|
40
|
+
z.object({
|
|
41
|
+
groupId: z.string(),
|
|
42
|
+
name: z.string(),
|
|
43
|
+
values: z.array(z.object({ valueId: z.string(), name: z.string() })),
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
/** 판매 단위(SKU). variantId는 카트·주문의 optionId와 같은 값 공간이다 */
|
|
47
|
+
variants: z.array(
|
|
48
|
+
z.object({
|
|
49
|
+
variantId: z.string(),
|
|
50
|
+
/** optionGroups 순서와 1:1 대응. 옵션 없는 상품은 [] */
|
|
51
|
+
valueIds: z.array(z.string()),
|
|
52
|
+
/** 값 이름을 " / "로 이은 표시명. 옵션 없으면 "" */
|
|
53
|
+
name: z.string(),
|
|
54
|
+
additionalPrice: z.number().int(),
|
|
55
|
+
soldOut: z.boolean(),
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
delivery: z.object({
|
|
59
|
+
type: z.enum(["FREE", "PAID", "CONDITIONAL_FREE"]),
|
|
60
|
+
fee: z.number().int(),
|
|
61
|
+
conditionalFreeAmount: z.number().int().nullable(),
|
|
62
|
+
estimatedDays: z.string(),
|
|
63
|
+
}),
|
|
64
|
+
returnPolicy: z.object({
|
|
65
|
+
returnFee: z.number().int(),
|
|
66
|
+
exchangeFee: z.number().int(),
|
|
67
|
+
periodDays: z.number().int(),
|
|
68
|
+
}),
|
|
69
|
+
categoryPath: z.array(z.object({ categoryId: z.string(), name: z.string() })),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export type ProductDetail = z.infer<typeof productDetailSchema>;
|
|
73
|
+
|
|
74
|
+
export const productSortSchema = z.enum([
|
|
75
|
+
"recommend",
|
|
76
|
+
"latest",
|
|
77
|
+
"priceAsc",
|
|
78
|
+
"priceDesc",
|
|
79
|
+
"reviewCount",
|
|
80
|
+
"ratingDesc",
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
export type ProductSort = z.infer<typeof productSortSchema>;
|
|
84
|
+
|
|
85
|
+
export interface ProductSearchParams extends PageParams {
|
|
86
|
+
keyword?: string;
|
|
87
|
+
categoryId?: string;
|
|
88
|
+
minPrice?: number;
|
|
89
|
+
maxPrice?: number;
|
|
90
|
+
freeShipping?: boolean;
|
|
91
|
+
sort?: ProductSort;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const productFacetsSchema = z.object({
|
|
95
|
+
categories: z
|
|
96
|
+
.array(z.object({ categoryId: z.string(), name: z.string(), count: z.number().int() }))
|
|
97
|
+
.optional(),
|
|
98
|
+
priceRange: z.object({ min: z.number().int(), max: z.number().int() }).optional(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export type ProductFacets = z.infer<typeof productFacetsSchema>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const createCheckoutRequestSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
cartItemIds: z.array(z.string()).min(1).optional(),
|
|
6
|
+
directItem: z
|
|
7
|
+
.object({
|
|
8
|
+
productId: z.string(),
|
|
9
|
+
optionId: z.string().optional(),
|
|
10
|
+
quantity: z.number().int().min(1),
|
|
11
|
+
})
|
|
12
|
+
.optional(),
|
|
13
|
+
})
|
|
14
|
+
.refine((value) => (value.cartItemIds ? !value.directItem : !!value.directItem), {
|
|
15
|
+
message: "cartItemIds와 directItem 중 하나만 전달해야 합니다",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type CreateCheckoutRequest = z.infer<typeof createCheckoutRequestSchema>;
|
|
19
|
+
|
|
20
|
+
export const checkoutSessionSchema = z.object({
|
|
21
|
+
checkoutId: z.string(),
|
|
22
|
+
items: z.array(
|
|
23
|
+
z.object({
|
|
24
|
+
productId: z.string(),
|
|
25
|
+
productName: z.string(),
|
|
26
|
+
/** variantId. 옵션 없는 상품은 default variant id */
|
|
27
|
+
optionId: z.string().nullable(),
|
|
28
|
+
optionName: z.string().nullable(),
|
|
29
|
+
quantity: z.number().int(),
|
|
30
|
+
unitPrice: z.number().int(),
|
|
31
|
+
totalPrice: z.number().int(),
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
amounts: z.object({
|
|
35
|
+
productAmount: z.number().int(),
|
|
36
|
+
deliveryFee: z.number().int(),
|
|
37
|
+
totalAmount: z.number().int(),
|
|
38
|
+
}),
|
|
39
|
+
expiresAt: z.string(),
|
|
40
|
+
testPayment: z
|
|
41
|
+
.boolean()
|
|
42
|
+
.describe(
|
|
43
|
+
"지금 결제하면 테스트 결제(샌드박스)인가. true면 실제로 돈이 나가지 않아요 — 주문서에 테스트 결제 안내를 표시해요. 결제 요청 시점에 스토어 결제 설정으로 다시 정해지니 최종 값은 결제 요청 응답의 `testPayment`예요",
|
|
44
|
+
),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export type CheckoutSession = z.infer<typeof checkoutSessionSchema>;
|
|
48
|
+
|
|
49
|
+
export const paymentMethodSchema = z.enum([
|
|
50
|
+
"CARD",
|
|
51
|
+
"BANK_TRANSFER",
|
|
52
|
+
"VIRTUAL_ACCOUNT",
|
|
53
|
+
"MOBILE",
|
|
54
|
+
"EASY_PAY",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
export type PaymentMethod = z.infer<typeof paymentMethodSchema>;
|
|
58
|
+
|
|
59
|
+
export const shippingAddressInputSchema = z.object({
|
|
60
|
+
addressId: z.string().optional(),
|
|
61
|
+
receiverName: z.string().min(1, "수령인명을 입력해주세요").max(50),
|
|
62
|
+
phone: z.string().regex(/^01[0-9]{8,9}$/, "연락처 형식이 아닙니다 (예: 01012345678)"),
|
|
63
|
+
zipCode: z.string().regex(/^[0-9]{5}$/, "우편번호는 5자리 숫자입니다"),
|
|
64
|
+
address1: z.string().min(1, "기본 주소를 입력해주세요"),
|
|
65
|
+
address2: z.string().optional(),
|
|
66
|
+
deliveryMemo: z.string().max(100).optional(),
|
|
67
|
+
entranceCode: z.string().max(20).optional(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export type ShippingAddressInput = z.infer<typeof shippingAddressInputSchema>;
|
|
71
|
+
|
|
72
|
+
export const guestInfoSchema = z.object({
|
|
73
|
+
name: z.string().min(1),
|
|
74
|
+
phone: z.string().regex(/^01[0-9]{8,9}$/),
|
|
75
|
+
email: z.email(),
|
|
76
|
+
orderPassword: z.string().min(6, "주문 조회 비밀번호는 6자 이상이어야 합니다"),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export type GuestInfo = z.infer<typeof guestInfoSchema>;
|
|
80
|
+
|
|
81
|
+
export const requestPaymentRequestSchema = z.object({
|
|
82
|
+
shippingAddress: shippingAddressInputSchema,
|
|
83
|
+
paymentMethod: paymentMethodSchema,
|
|
84
|
+
guest: guestInfoSchema.optional(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export type RequestPaymentRequest = z.infer<typeof requestPaymentRequestSchema>;
|
|
88
|
+
|
|
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` 실결제). 팝업 내부 값"),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export type PgParams = z.infer<typeof pgParamsSchema>;
|
|
116
|
+
|
|
117
|
+
export const paymentParamsSchema = z.object({
|
|
118
|
+
paymentId: z.string(),
|
|
119
|
+
pgProvider: z
|
|
120
|
+
.string()
|
|
121
|
+
.describe(
|
|
122
|
+
"첫 결제 시도의 PG. 현재 값은 `tosspayments`·`portone`이다. PG가 추가될 수 있어 문자열로 받는다",
|
|
123
|
+
),
|
|
124
|
+
pgParams: pgParamsSchema,
|
|
125
|
+
expiresAt: z.string(),
|
|
126
|
+
testPayment: z
|
|
127
|
+
.boolean()
|
|
128
|
+
.describe(
|
|
129
|
+
"테스트 결제(샌드박스) 여부. true면 실제로 돈이 나가지 않는다 — 결제 화면에 테스트 결제 안내를 표시한다",
|
|
130
|
+
),
|
|
131
|
+
});
|
|
132
|
+
|
|
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
|
+
});
|
|
152
|
+
|
|
153
|
+
export type ConfirmPaymentResult = z.infer<typeof confirmPaymentResultSchema>;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 결제 상태 조회의 현재 상태 값 — 응답 스키마는 서버가 상태를 추가해도 파싱이 깨지지 않게 문자열로 받는다
|
|
157
|
+
* (`pgProvider`와 같은 방식). 화면 분기는 이 목록으로 하고 모르는 값은 확인 중으로 다룬다.
|
|
158
|
+
*/
|
|
159
|
+
export const PAYMENT_STATUS_VALUES = [
|
|
160
|
+
"pending",
|
|
161
|
+
"processing",
|
|
162
|
+
"completed",
|
|
163
|
+
"failed",
|
|
164
|
+
"expired",
|
|
165
|
+
] as const;
|
|
166
|
+
export type KnownPaymentStatus = (typeof PAYMENT_STATUS_VALUES)[number];
|
|
167
|
+
|
|
168
|
+
export const paymentStatusSchema = z.object({
|
|
169
|
+
paymentId: z.string(),
|
|
170
|
+
status: z
|
|
171
|
+
.string()
|
|
172
|
+
.describe(
|
|
173
|
+
"결제 상태. 현재 값은 `pending` 결제 대기 · `processing` 승인 확인 중 · `completed` 결제 완료(주문 생성) · `failed` 실패 · " +
|
|
174
|
+
"`expired` 만료예요. 상태가 추가될 수 있어 문자열로 받아요 — 모르는 값은 `processing`처럼 다루고 계속 조회하세요",
|
|
175
|
+
),
|
|
176
|
+
orderId: z.string().nullable().describe("결제가 완료됐으면 주문 번호, 그 외에는 null"),
|
|
177
|
+
testPayment: z
|
|
178
|
+
.boolean()
|
|
179
|
+
.describe("테스트 결제(샌드박스)인가. true면 실제로 돈이 나가지 않았어요"),
|
|
180
|
+
expiresAt: z.string().describe("결제 요청 만료 시각"),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
export type PaymentStatus = z.infer<typeof paymentStatusSchema>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { claimStatusSchema } from "./common";
|
|
3
|
+
|
|
4
|
+
export const claimTypeSchema = z.enum(["CANCEL", "RETURN", "EXCHANGE"]);
|
|
5
|
+
|
|
6
|
+
export type ClaimType = z.infer<typeof claimTypeSchema>;
|
|
7
|
+
|
|
8
|
+
export const claimReasonSchema = z.enum([
|
|
9
|
+
"CHANGE_OF_MIND",
|
|
10
|
+
"WRONG_ORDER",
|
|
11
|
+
"DEFECTIVE",
|
|
12
|
+
"WRONG_DELIVERY",
|
|
13
|
+
"DIFFERENT_FROM_PAGE",
|
|
14
|
+
"DELAY",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export type ClaimReason = z.infer<typeof claimReasonSchema>;
|
|
18
|
+
|
|
19
|
+
export const createClaimRequestSchema = z.object({
|
|
20
|
+
type: claimTypeSchema,
|
|
21
|
+
reason: claimReasonSchema,
|
|
22
|
+
reasonDetail: z.string().max(1000).optional(),
|
|
23
|
+
evidenceImages: z.array(z.url()).max(5).optional(),
|
|
24
|
+
exchangeOptionId: z.string().optional(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type CreateClaimRequest = z.infer<typeof createClaimRequestSchema>;
|
|
28
|
+
|
|
29
|
+
export const createClaimResultSchema = z.object({
|
|
30
|
+
claimId: z.string(),
|
|
31
|
+
status: z.literal("REQUESTED"),
|
|
32
|
+
expectedRefundAmount: z.number().int().nullable(),
|
|
33
|
+
claimDeliveryFee: z.number().int(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export type CreateClaimResult = z.infer<typeof createClaimResultSchema>;
|
|
37
|
+
|
|
38
|
+
export const myClaimSchema = z.object({
|
|
39
|
+
claimId: z.string(),
|
|
40
|
+
type: claimTypeSchema,
|
|
41
|
+
status: claimStatusSchema,
|
|
42
|
+
orderItemId: z.string(),
|
|
43
|
+
productName: z.string(),
|
|
44
|
+
reason: z.string(),
|
|
45
|
+
expectedRefundAmount: z.number().int().nullable(),
|
|
46
|
+
rejectReason: z.string().nullable(),
|
|
47
|
+
requestedAt: z.string(),
|
|
48
|
+
completedAt: z.string().nullable(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export type MyClaim = z.infer<typeof myClaimSchema>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const pageInfoSchema = z.object({
|
|
4
|
+
page: z.number().int(),
|
|
5
|
+
size: z.number().int(),
|
|
6
|
+
totalElements: z.number().int(),
|
|
7
|
+
totalPages: z.number().int(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export type PageInfo = z.infer<typeof pageInfoSchema>;
|
|
11
|
+
|
|
12
|
+
export function pageSchema<T extends z.ZodType>(item: T) {
|
|
13
|
+
return pageInfoSchema.extend({ contents: z.array(item) });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PageParams {
|
|
17
|
+
page?: number;
|
|
18
|
+
size?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const orderItemStatusSchema = z.enum([
|
|
22
|
+
"PAYMENT_WAITING",
|
|
23
|
+
"PAID",
|
|
24
|
+
"CONFIRMED",
|
|
25
|
+
"DISPATCHED",
|
|
26
|
+
"DELIVERING",
|
|
27
|
+
"DELIVERED",
|
|
28
|
+
"PURCHASE_DECIDED",
|
|
29
|
+
"CANCELED",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
export type OrderItemStatus = z.infer<typeof orderItemStatusSchema>;
|
|
33
|
+
|
|
34
|
+
export const claimStatusSchema = z.enum([
|
|
35
|
+
"REQUESTED",
|
|
36
|
+
"HELD",
|
|
37
|
+
"COLLECTING",
|
|
38
|
+
"COLLECTED",
|
|
39
|
+
"REDELIVERING",
|
|
40
|
+
"APPROVED",
|
|
41
|
+
"REJECTED",
|
|
42
|
+
"COMPLETED",
|
|
43
|
+
"WITHDRAWN",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
export type ClaimStatus = z.infer<typeof claimStatusSchema>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const customerInquiryCategorySchema = z.enum([
|
|
4
|
+
"DELIVERY",
|
|
5
|
+
"RETURN",
|
|
6
|
+
"EXCHANGE",
|
|
7
|
+
"PAYMENT",
|
|
8
|
+
"ETC",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export type CustomerInquiryCategory = z.infer<typeof customerInquiryCategorySchema>;
|
|
12
|
+
|
|
13
|
+
/** 구매자 본인 뷰 — 마스킹 없이 본인 데이터, 셀러 답변(수정횟수 제외) */
|
|
14
|
+
export const customerInquirySchema = z.object({
|
|
15
|
+
inquiryId: z.string(),
|
|
16
|
+
category: customerInquiryCategorySchema,
|
|
17
|
+
title: z.string(),
|
|
18
|
+
content: z.string(),
|
|
19
|
+
orderItemId: z.string().nullable(),
|
|
20
|
+
answered: z.boolean(),
|
|
21
|
+
answer: z
|
|
22
|
+
.object({
|
|
23
|
+
content: z.string(),
|
|
24
|
+
answeredAt: z.string(),
|
|
25
|
+
})
|
|
26
|
+
.nullable(),
|
|
27
|
+
createdAt: z.string(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type CustomerInquiry = z.infer<typeof customerInquirySchema>;
|
|
31
|
+
|
|
32
|
+
export const createCustomerInquiryRequestSchema = z.object({
|
|
33
|
+
category: customerInquiryCategorySchema,
|
|
34
|
+
title: z.string().min(1).max(100),
|
|
35
|
+
content: z.string().min(1).max(2000),
|
|
36
|
+
orderItemId: z.string().optional(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export type CreateCustomerInquiryRequest = z.infer<typeof createCustomerInquiryRequestSchema>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const publicInquirySchema = z.object({
|
|
4
|
+
inquiryId: z.string(),
|
|
5
|
+
content: z.string().nullable(),
|
|
6
|
+
secret: z.boolean(),
|
|
7
|
+
mine: z.boolean(),
|
|
8
|
+
writerMaskedName: z.string(),
|
|
9
|
+
answer: z
|
|
10
|
+
.object({
|
|
11
|
+
content: z.string().nullable(),
|
|
12
|
+
answeredAt: z.string(),
|
|
13
|
+
})
|
|
14
|
+
.nullable(),
|
|
15
|
+
createdAt: z.string(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export type PublicInquiry = z.infer<typeof publicInquirySchema>;
|
|
19
|
+
|
|
20
|
+
export const createInquiryRequestSchema = z.object({
|
|
21
|
+
content: z.string().min(1).max(1000),
|
|
22
|
+
secret: z.boolean().default(false),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type CreateInquiryRequest = z.infer<typeof createInquiryRequestSchema>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const memberSchema = z.object({
|
|
4
|
+
memberId: z.string(),
|
|
5
|
+
email: z.email(),
|
|
6
|
+
name: z.string(),
|
|
7
|
+
phone: z.string().nullable(),
|
|
8
|
+
point: z.number().int(),
|
|
9
|
+
marketingAgreed: z.boolean(),
|
|
10
|
+
joinedAt: z.string(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export type Member = z.infer<typeof memberSchema>;
|
|
14
|
+
|
|
15
|
+
export const updateProfileRequestSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
name: z.string().max(50).optional(),
|
|
18
|
+
phone: z.string().optional(),
|
|
19
|
+
marketingAgreed: z.boolean().optional(),
|
|
20
|
+
})
|
|
21
|
+
.refine((value) => Object.values(value).some((v) => v !== undefined), {
|
|
22
|
+
message: "최소 1개 필드가 필요합니다",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type UpdateProfileRequest = z.infer<typeof updateProfileRequestSchema>;
|
|
26
|
+
|
|
27
|
+
export const memberAddressSchema = z.object({
|
|
28
|
+
addressId: z.string(),
|
|
29
|
+
alias: z.string(),
|
|
30
|
+
isDefault: z.boolean(),
|
|
31
|
+
receiverName: z.string(),
|
|
32
|
+
phone: z.string(),
|
|
33
|
+
zipCode: z.string(),
|
|
34
|
+
address1: z.string(),
|
|
35
|
+
address2: z.string().nullable(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export type MemberAddress = z.infer<typeof memberAddressSchema>;
|
|
39
|
+
|
|
40
|
+
export const memberAddressRequestSchema = z.object({
|
|
41
|
+
alias: z.string().min(1).max(20),
|
|
42
|
+
receiverName: z.string().min(1).max(50),
|
|
43
|
+
phone: z.string().regex(/^01[0-9]{8,9}$/),
|
|
44
|
+
zipCode: z.string().regex(/^[0-9]{5}$/),
|
|
45
|
+
address1: z.string().min(1),
|
|
46
|
+
address2: z.string().optional(),
|
|
47
|
+
isDefault: z.boolean().default(false),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export type MemberAddressRequest = z.infer<typeof memberAddressRequestSchema>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { paymentMethodSchema } from "./checkout";
|
|
3
|
+
import { orderItemStatusSchema } from "./common";
|
|
4
|
+
|
|
5
|
+
export const myOrderItemSchema = z.object({
|
|
6
|
+
orderItemId: z.string(),
|
|
7
|
+
productId: z.string(),
|
|
8
|
+
productName: z.string(),
|
|
9
|
+
thumbnailUrl: z.url(),
|
|
10
|
+
optionName: z.string().nullable(),
|
|
11
|
+
quantity: z.number().int(),
|
|
12
|
+
totalPrice: z.number().int(),
|
|
13
|
+
status: orderItemStatusSchema,
|
|
14
|
+
claimStatus: z.string().nullable(),
|
|
15
|
+
reviewWritten: z.boolean(),
|
|
16
|
+
autoDecisionDate: z.string().nullable(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type MyOrderItem = z.infer<typeof myOrderItemSchema>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 주문 응답의 배송지 — 결제 요청 시 저장된 스냅샷. 요청 스키마(`shippingAddressInputSchema`)와 분리한 응답 스키마라
|
|
23
|
+
* 형식 검증(regex·길이)을 하지 않는다. 입력하지 않은 선택 필드는 null로 오거나 빠질 수 있다.
|
|
24
|
+
*/
|
|
25
|
+
export const orderShippingAddressSchema = z.object({
|
|
26
|
+
receiverName: z.string(),
|
|
27
|
+
phone: z.string(),
|
|
28
|
+
zipCode: z.string(),
|
|
29
|
+
address1: z.string(),
|
|
30
|
+
address2: z.string().nullish().describe("상세 주소. 입력하지 않았으면 null이거나 없다"),
|
|
31
|
+
deliveryMemo: z.string().nullish().describe("배송 메모. 입력하지 않았으면 null이거나 없다"),
|
|
32
|
+
entranceCode: z
|
|
33
|
+
.string()
|
|
34
|
+
.nullish()
|
|
35
|
+
.describe("공동현관 출입번호. 입력하지 않았으면 null이거나 없다"),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export type OrderShippingAddress = z.infer<typeof orderShippingAddressSchema>;
|
|
39
|
+
|
|
40
|
+
export const myOrderSchema = z.object({
|
|
41
|
+
orderId: z.string(),
|
|
42
|
+
orderedAt: z.string(),
|
|
43
|
+
items: z.array(myOrderItemSchema),
|
|
44
|
+
shippingAddress: orderShippingAddressSchema,
|
|
45
|
+
payment: z.object({
|
|
46
|
+
method: paymentMethodSchema.describe(
|
|
47
|
+
"실제 결제수단(PG가 보고한 수단, 모르면 주문서에서 고른 수단). 결제창에서 바뀔 수 있다(카드 결제창의 간편결제 탭 등)",
|
|
48
|
+
),
|
|
49
|
+
totalAmount: z.number().int(),
|
|
50
|
+
paidAt: z.string().nullable(),
|
|
51
|
+
receiptUrl: z.url().nullable(),
|
|
52
|
+
}),
|
|
53
|
+
testPayment: z
|
|
54
|
+
.boolean()
|
|
55
|
+
.describe(
|
|
56
|
+
"테스트 결제(샌드박스 결제) 주문인가. true면 실제로 돈이 나가지 않았어요 — 주문 화면에 테스트 결제 배지를 표시해요",
|
|
57
|
+
),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export type MyOrder = z.infer<typeof myOrderSchema>;
|
|
61
|
+
|
|
62
|
+
export const deliveryTrackingSchema = z.object({
|
|
63
|
+
carrierName: z.string(),
|
|
64
|
+
trackingNumber: z.string(),
|
|
65
|
+
trackingUrl: z.url(),
|
|
66
|
+
status: z.enum(["DISPATCHED", "DELIVERING", "DELIVERED"]),
|
|
67
|
+
trackingEvents: z.array(
|
|
68
|
+
z.object({
|
|
69
|
+
status: z.string(),
|
|
70
|
+
location: z.string(),
|
|
71
|
+
occurredAt: z.string(),
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export type DeliveryTracking = z.infer<typeof deliveryTrackingSchema>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const publicReviewSchema = z.object({
|
|
4
|
+
reviewId: z.string(),
|
|
5
|
+
rating: z.number().int().min(1).max(5),
|
|
6
|
+
content: z.string(),
|
|
7
|
+
images: z.array(z.url()),
|
|
8
|
+
optionName: z.string().nullable(),
|
|
9
|
+
writerMaskedName: z.string(),
|
|
10
|
+
sellerReply: z
|
|
11
|
+
.object({
|
|
12
|
+
content: z.string(),
|
|
13
|
+
repliedAt: z.string(),
|
|
14
|
+
})
|
|
15
|
+
.nullable(),
|
|
16
|
+
createdAt: z.string(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export type PublicReview = z.infer<typeof publicReviewSchema>;
|
|
20
|
+
|
|
21
|
+
export const reviewSummarySchema = z.object({
|
|
22
|
+
averageRating: z.number(),
|
|
23
|
+
totalCount: z.number().int(),
|
|
24
|
+
distribution: z.record(z.string(), z.number().int()),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type ReviewSummary = z.infer<typeof reviewSummarySchema>;
|
|
28
|
+
|
|
29
|
+
export const writableReviewSchema = z.object({
|
|
30
|
+
orderItemId: z.string(),
|
|
31
|
+
productId: z.string(),
|
|
32
|
+
productName: z.string(),
|
|
33
|
+
optionName: z.string().nullable(),
|
|
34
|
+
thumbnailUrl: z.url(),
|
|
35
|
+
writableUntil: z.string(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export type WritableReview = z.infer<typeof writableReviewSchema>;
|
|
39
|
+
|
|
40
|
+
export const createReviewRequestSchema = z.object({
|
|
41
|
+
rating: z.number().int().min(1).max(5),
|
|
42
|
+
content: z.string().min(10, "리뷰는 10자 이상 작성해주세요").max(2000),
|
|
43
|
+
images: z.array(z.url()).max(5).optional(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export type CreateReviewRequest = z.infer<typeof createReviewRequestSchema>;
|
|
47
|
+
|
|
48
|
+
export const createReviewResultSchema = z.object({
|
|
49
|
+
reviewId: z.string(),
|
|
50
|
+
rewardPoint: z.number().int(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type CreateReviewResult = z.infer<typeof createReviewResultSchema>;
|
|
54
|
+
|
|
55
|
+
export const updateReviewRequestSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
rating: z.number().int().min(1).max(5).optional(),
|
|
58
|
+
content: z.string().min(10).max(2000).optional(),
|
|
59
|
+
images: z.array(z.url()).max(5).optional(),
|
|
60
|
+
})
|
|
61
|
+
.refine((value) => Object.values(value).some((v) => v !== undefined), {
|
|
62
|
+
message: "최소 1개 필드가 필요합니다",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export type UpdateReviewRequest = z.infer<typeof updateReviewRequestSchema>;
|