@sayren/store-sdk 0.2.0 → 0.4.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 +252 -3
- package/dist/index.mjs +142 -5
- package/package.json +1 -1
- package/src/client.ts +43 -0
- package/src/index.ts +1 -0
- package/src/schemas/customer.ts +119 -0
- package/src/schemas/scope.ts +12 -1
- package/src/schemas/store.ts +61 -0
package/dist/index.d.mts
CHANGED
|
@@ -1108,6 +1108,145 @@ export declare const approveClaimResultSchema: z.ZodObject<{
|
|
|
1108
1108
|
}, z.core.$strip>;
|
|
1109
1109
|
export type ApproveClaimResult = z.infer<typeof approveClaimResultSchema>;
|
|
1110
1110
|
//#endregion
|
|
1111
|
+
//#region src/schemas/customer.d.ts
|
|
1112
|
+
/** 구매자 목록 정렬 */
|
|
1113
|
+
export declare const customerSortSchema: z.ZodEnum<{
|
|
1114
|
+
joined_desc: "joined_desc";
|
|
1115
|
+
joined_asc: "joined_asc";
|
|
1116
|
+
total_paid_desc: "total_paid_desc";
|
|
1117
|
+
order_count_desc: "order_count_desc";
|
|
1118
|
+
last_ordered_desc: "last_ordered_desc";
|
|
1119
|
+
}>;
|
|
1120
|
+
export type CustomerSort = z.infer<typeof customerSortSchema>;
|
|
1121
|
+
/** `GET /v1/customers` 쿼리 */
|
|
1122
|
+
export declare const customerListQuerySchema: z.ZodObject<{
|
|
1123
|
+
page: z.ZodOptional<z.ZodString>;
|
|
1124
|
+
size: z.ZodOptional<z.ZodString>;
|
|
1125
|
+
q: z.ZodOptional<z.ZodString>;
|
|
1126
|
+
joinedFrom: z.ZodOptional<z.ZodString>;
|
|
1127
|
+
joinedTo: z.ZodOptional<z.ZodString>;
|
|
1128
|
+
marketing: z.ZodOptional<z.ZodEnum<{
|
|
1129
|
+
agreed: "agreed";
|
|
1130
|
+
declined: "declined";
|
|
1131
|
+
}>>;
|
|
1132
|
+
sort: z.ZodOptional<z.ZodEnum<{
|
|
1133
|
+
joined_desc: "joined_desc";
|
|
1134
|
+
joined_asc: "joined_asc";
|
|
1135
|
+
total_paid_desc: "total_paid_desc";
|
|
1136
|
+
order_count_desc: "order_count_desc";
|
|
1137
|
+
last_ordered_desc: "last_ordered_desc";
|
|
1138
|
+
}>>;
|
|
1139
|
+
}, z.core.$strip>;
|
|
1140
|
+
/** 구매 통계 — 구매 금액은 취소된 주문 상품을 빼고, 완료된 반품은 수량 비율로 뺀 금액이다 */
|
|
1141
|
+
export declare const customerStatsSchema: z.ZodObject<{
|
|
1142
|
+
orderCount: z.ZodNumber;
|
|
1143
|
+
totalPaid: z.ZodNumber;
|
|
1144
|
+
lastOrderedAt: z.ZodNullable<z.ZodString>;
|
|
1145
|
+
}, z.core.$strip>;
|
|
1146
|
+
/** 목록 행 — 휴대폰은 가운데를 가린다(`010-****-1234`) */
|
|
1147
|
+
export declare const customerSummarySchema: z.ZodObject<{
|
|
1148
|
+
customerId: z.ZodString;
|
|
1149
|
+
name: z.ZodString;
|
|
1150
|
+
email: z.ZodString;
|
|
1151
|
+
maskedPhone: z.ZodNullable<z.ZodString>;
|
|
1152
|
+
joinedAt: z.ZodString;
|
|
1153
|
+
point: z.ZodNumber;
|
|
1154
|
+
marketingAgreed: z.ZodBoolean;
|
|
1155
|
+
orderCount: z.ZodNumber;
|
|
1156
|
+
totalPaid: z.ZodNumber;
|
|
1157
|
+
lastOrderedAt: z.ZodNullable<z.ZodString>;
|
|
1158
|
+
}, z.core.$strip>;
|
|
1159
|
+
export type CustomerSummary = z.infer<typeof customerSummarySchema>;
|
|
1160
|
+
export declare const customerAddressSchema: z.ZodObject<{
|
|
1161
|
+
addressId: z.ZodString;
|
|
1162
|
+
alias: z.ZodString;
|
|
1163
|
+
isDefault: z.ZodBoolean;
|
|
1164
|
+
receiverName: z.ZodString;
|
|
1165
|
+
phone: z.ZodString;
|
|
1166
|
+
zipCode: z.ZodString;
|
|
1167
|
+
address1: z.ZodString;
|
|
1168
|
+
address2: z.ZodNullable<z.ZodString>;
|
|
1169
|
+
}, z.core.$strip>;
|
|
1170
|
+
/** 구매자 상세 — 휴대폰 전체를 보여 준다 */
|
|
1171
|
+
export declare const customerDetailSchema: z.ZodObject<{
|
|
1172
|
+
customerId: z.ZodString;
|
|
1173
|
+
name: z.ZodString;
|
|
1174
|
+
email: z.ZodString;
|
|
1175
|
+
phone: z.ZodNullable<z.ZodString>;
|
|
1176
|
+
joinedAt: z.ZodString;
|
|
1177
|
+
point: z.ZodNumber;
|
|
1178
|
+
marketingAgreed: z.ZodBoolean;
|
|
1179
|
+
loginMethods: z.ZodArray<z.ZodEnum<{
|
|
1180
|
+
password: "password";
|
|
1181
|
+
}>>;
|
|
1182
|
+
addresses: z.ZodArray<z.ZodObject<{
|
|
1183
|
+
addressId: z.ZodString;
|
|
1184
|
+
alias: z.ZodString;
|
|
1185
|
+
isDefault: z.ZodBoolean;
|
|
1186
|
+
receiverName: z.ZodString;
|
|
1187
|
+
phone: z.ZodString;
|
|
1188
|
+
zipCode: z.ZodString;
|
|
1189
|
+
address1: z.ZodString;
|
|
1190
|
+
address2: z.ZodNullable<z.ZodString>;
|
|
1191
|
+
}, z.core.$strip>>;
|
|
1192
|
+
stats: z.ZodObject<{
|
|
1193
|
+
orderCount: z.ZodNumber;
|
|
1194
|
+
totalPaid: z.ZodNumber;
|
|
1195
|
+
lastOrderedAt: z.ZodNullable<z.ZodString>;
|
|
1196
|
+
canceledOrReturnedCount: z.ZodNumber;
|
|
1197
|
+
reviewCount: z.ZodNumber;
|
|
1198
|
+
inquiryCount: z.ZodNumber;
|
|
1199
|
+
}, z.core.$strip>;
|
|
1200
|
+
}, z.core.$strip>;
|
|
1201
|
+
export type CustomerDetail = z.infer<typeof customerDetailSchema>;
|
|
1202
|
+
/** 구매자의 주문 — 전체 기간 */
|
|
1203
|
+
export declare const customerOrderSchema: z.ZodObject<{
|
|
1204
|
+
orderId: z.ZodString;
|
|
1205
|
+
orderedAt: z.ZodString;
|
|
1206
|
+
totalAmount: z.ZodNumber;
|
|
1207
|
+
testPayment: z.ZodBoolean;
|
|
1208
|
+
items: z.ZodArray<z.ZodObject<{
|
|
1209
|
+
orderItemId: z.ZodString;
|
|
1210
|
+
productName: z.ZodString;
|
|
1211
|
+
optionName: z.ZodNullable<z.ZodString>;
|
|
1212
|
+
quantity: z.ZodNumber;
|
|
1213
|
+
status: z.ZodEnum<{
|
|
1214
|
+
PAID: "PAID";
|
|
1215
|
+
PAYMENT_WAITING: "PAYMENT_WAITING";
|
|
1216
|
+
CONFIRMED: "CONFIRMED";
|
|
1217
|
+
DISPATCHED: "DISPATCHED";
|
|
1218
|
+
DELIVERING: "DELIVERING";
|
|
1219
|
+
DELIVERED: "DELIVERED";
|
|
1220
|
+
PURCHASE_DECIDED: "PURCHASE_DECIDED";
|
|
1221
|
+
CANCELED: "CANCELED";
|
|
1222
|
+
}>;
|
|
1223
|
+
claimStatus: z.ZodNullable<z.ZodEnum<{
|
|
1224
|
+
CANCEL_REQUESTED: "CANCEL_REQUESTED";
|
|
1225
|
+
RETURN_REQUESTED: "RETURN_REQUESTED";
|
|
1226
|
+
EXCHANGE_REQUESTED: "EXCHANGE_REQUESTED";
|
|
1227
|
+
}>>;
|
|
1228
|
+
}, z.core.$strip>>;
|
|
1229
|
+
}, z.core.$strip>;
|
|
1230
|
+
export type CustomerOrder = z.infer<typeof customerOrderSchema>;
|
|
1231
|
+
/** `POST /v1/customers/{id}/withdraw` 본문 */
|
|
1232
|
+
export declare const withdrawCustomerRequestSchema: z.ZodObject<{
|
|
1233
|
+
reason: z.ZodEnum<{
|
|
1234
|
+
BUYER_REQUEST: "BUYER_REQUEST";
|
|
1235
|
+
DORMANT: "DORMANT";
|
|
1236
|
+
ABUSE: "ABUSE";
|
|
1237
|
+
OTHER: "OTHER";
|
|
1238
|
+
}>;
|
|
1239
|
+
memo: z.ZodOptional<z.ZodString>;
|
|
1240
|
+
}, z.core.$strip>;
|
|
1241
|
+
export type WithdrawCustomerRequest = z.infer<typeof withdrawCustomerRequestSchema>;
|
|
1242
|
+
/** `WITHDRAWAL_BLOCKED`의 `error.details` — 탈퇴를 막는 항목 수(스토어프런트 탈퇴와 같다) */
|
|
1243
|
+
export declare const customerWithdrawalBlockedDetailsSchema: z.ZodObject<{
|
|
1244
|
+
inProgressOrderItems: z.ZodNumber;
|
|
1245
|
+
openClaims: z.ZodNumber;
|
|
1246
|
+
pendingPayments: z.ZodNumber;
|
|
1247
|
+
}, z.core.$strip>;
|
|
1248
|
+
export type CustomerWithdrawalBlockedDetails = z.infer<typeof customerWithdrawalBlockedDetailsSchema>;
|
|
1249
|
+
//#endregion
|
|
1111
1250
|
//#region src/schemas/misc.d.ts
|
|
1112
1251
|
export declare const inquirySchema: z.ZodObject<{
|
|
1113
1252
|
inquiryId: z.ZodString;
|
|
@@ -3320,6 +3459,22 @@ export declare const STORE_CODE_PATTERN: RegExp;
|
|
|
3320
3459
|
export declare const RESERVED_STORE_CODES: readonly string[];
|
|
3321
3460
|
export declare function isReservedStoreCode(code: string): boolean;
|
|
3322
3461
|
export declare const storeCodeSchema: z.ZodString;
|
|
3462
|
+
/** `https://shop.example.com`처럼 경로·쿼리 없는 http(s) 오리진인가 */
|
|
3463
|
+
export declare function isWebOrigin(value: string): boolean;
|
|
3464
|
+
/** 방문 분석을 보낼 수 있는 스토어프런트 오리진 — 스킴·호스트(·포트)만. 서버가 소문자·끝 슬래시 제거로 정규화한다 */
|
|
3465
|
+
export declare const analyticsOriginSchema: z.ZodString;
|
|
3466
|
+
/** 허용 오리진 상한 */
|
|
3467
|
+
export declare const MAX_ANALYTICS_ALLOWED_ORIGINS = 20;
|
|
3468
|
+
/** `PATCH /v1/store` 본문 — 바꿀 필드만 보낸다 */
|
|
3469
|
+
export declare const updateStoreRequestSchema: z.ZodObject<{
|
|
3470
|
+
name: z.ZodOptional<z.ZodString>;
|
|
3471
|
+
logoUrl: z.ZodOptional<z.ZodURL>;
|
|
3472
|
+
customerCenterPhone: z.ZodOptional<z.ZodString>;
|
|
3473
|
+
businessHours: z.ZodOptional<z.ZodString>;
|
|
3474
|
+
analyticsAllowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
3475
|
+
guestCheckoutEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
3476
|
+
}, z.core.$strip>;
|
|
3477
|
+
export type UpdateStoreRequest = z.infer<typeof updateStoreRequestSchema>;
|
|
3323
3478
|
export declare const storeSchema: z.ZodObject<{
|
|
3324
3479
|
storeId: z.ZodString;
|
|
3325
3480
|
name: z.ZodString;
|
|
@@ -3333,6 +3488,8 @@ export declare const storeSchema: z.ZodObject<{
|
|
|
3333
3488
|
logoUrl: z.ZodNullable<z.ZodURL>;
|
|
3334
3489
|
businessNumber: z.ZodNullable<z.ZodString>;
|
|
3335
3490
|
customerCenterPhone: z.ZodNullable<z.ZodString>;
|
|
3491
|
+
analyticsAllowedOrigins: z.ZodArray<z.ZodString>;
|
|
3492
|
+
guestCheckoutEnabled: z.ZodBoolean;
|
|
3336
3493
|
memberCount: z.ZodNumber;
|
|
3337
3494
|
createdAt: z.ZodString;
|
|
3338
3495
|
}, z.core.$strip>;
|
|
@@ -4448,6 +4605,76 @@ export declare function createStoreClient(options: StoreClientOptions): {
|
|
|
4448
4605
|
answer: (inquiryId: string, content: string) => Promise<void>;
|
|
4449
4606
|
updateAnswer: (inquiryId: string, content: string) => Promise<void>;
|
|
4450
4607
|
};
|
|
4608
|
+
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(콘솔 OWNER·ADMIN) */
|
|
4609
|
+
customers: {
|
|
4610
|
+
list: (params?: PageParams & Omit<z.input<typeof customerListQuerySchema>, "page" | "size">) => Promise<{
|
|
4611
|
+
page: number;
|
|
4612
|
+
size: number;
|
|
4613
|
+
totalElements: number;
|
|
4614
|
+
totalPages: number;
|
|
4615
|
+
contents: {
|
|
4616
|
+
customerId: string;
|
|
4617
|
+
name: string;
|
|
4618
|
+
email: string;
|
|
4619
|
+
maskedPhone: string | null;
|
|
4620
|
+
joinedAt: string;
|
|
4621
|
+
point: number;
|
|
4622
|
+
marketingAgreed: boolean;
|
|
4623
|
+
orderCount: number;
|
|
4624
|
+
totalPaid: number;
|
|
4625
|
+
lastOrderedAt: string | null;
|
|
4626
|
+
}[];
|
|
4627
|
+
}>;
|
|
4628
|
+
get: (customerId: string) => Promise<{
|
|
4629
|
+
customerId: string;
|
|
4630
|
+
name: string;
|
|
4631
|
+
email: string;
|
|
4632
|
+
phone: string | null;
|
|
4633
|
+
joinedAt: string;
|
|
4634
|
+
point: number;
|
|
4635
|
+
marketingAgreed: boolean;
|
|
4636
|
+
loginMethods: "password"[];
|
|
4637
|
+
addresses: {
|
|
4638
|
+
addressId: string;
|
|
4639
|
+
alias: string;
|
|
4640
|
+
isDefault: boolean;
|
|
4641
|
+
receiverName: string;
|
|
4642
|
+
phone: string;
|
|
4643
|
+
zipCode: string;
|
|
4644
|
+
address1: string;
|
|
4645
|
+
address2: string | null;
|
|
4646
|
+
}[];
|
|
4647
|
+
stats: {
|
|
4648
|
+
orderCount: number;
|
|
4649
|
+
totalPaid: number;
|
|
4650
|
+
lastOrderedAt: string | null;
|
|
4651
|
+
canceledOrReturnedCount: number;
|
|
4652
|
+
reviewCount: number;
|
|
4653
|
+
inquiryCount: number;
|
|
4654
|
+
};
|
|
4655
|
+
}>;
|
|
4656
|
+
orders: (customerId: string, params?: PageParams) => Promise<{
|
|
4657
|
+
page: number;
|
|
4658
|
+
size: number;
|
|
4659
|
+
totalElements: number;
|
|
4660
|
+
totalPages: number;
|
|
4661
|
+
contents: {
|
|
4662
|
+
orderId: string;
|
|
4663
|
+
orderedAt: string;
|
|
4664
|
+
totalAmount: number;
|
|
4665
|
+
testPayment: boolean;
|
|
4666
|
+
items: {
|
|
4667
|
+
orderItemId: string;
|
|
4668
|
+
productName: string;
|
|
4669
|
+
optionName: string | null;
|
|
4670
|
+
quantity: number;
|
|
4671
|
+
status: "PAID" | "PAYMENT_WAITING" | "CONFIRMED" | "DISPATCHED" | "DELIVERING" | "DELIVERED" | "PURCHASE_DECIDED" | "CANCELED";
|
|
4672
|
+
claimStatus: "CANCEL_REQUESTED" | "RETURN_REQUESTED" | "EXCHANGE_REQUESTED" | null;
|
|
4673
|
+
}[];
|
|
4674
|
+
}[];
|
|
4675
|
+
}>;
|
|
4676
|
+
withdraw: (customerId: string, body: WithdrawCustomerRequest) => Promise<void>;
|
|
4677
|
+
};
|
|
4451
4678
|
customerInquiries: {
|
|
4452
4679
|
list: (params?: PageParams & {
|
|
4453
4680
|
answered?: boolean;
|
|
@@ -5706,6 +5933,26 @@ export declare function createStoreClient(options: StoreClientOptions): {
|
|
|
5706
5933
|
logoUrl: string | null;
|
|
5707
5934
|
businessNumber: string | null;
|
|
5708
5935
|
customerCenterPhone: string | null;
|
|
5936
|
+
analyticsAllowedOrigins: string[];
|
|
5937
|
+
guestCheckoutEnabled: boolean;
|
|
5938
|
+
memberCount: number;
|
|
5939
|
+
createdAt: string;
|
|
5940
|
+
}>;
|
|
5941
|
+
/**
|
|
5942
|
+
* `PATCH /v1/store` — 스코프 `store:rw`. 바꿀 필드만 보낸다. `analyticsAllowedOrigins`는 방문 분석 수집을 받을
|
|
5943
|
+
* 스토어프런트 오리진 목록이다(빈 배열이면 모든 오리진 허용).
|
|
5944
|
+
*/
|
|
5945
|
+
update: (body: UpdateStoreRequest) => Promise<{
|
|
5946
|
+
storeId: string;
|
|
5947
|
+
name: string;
|
|
5948
|
+
storeCode: string;
|
|
5949
|
+
storefrontUrl: string;
|
|
5950
|
+
status: "ACTIVE" | "SUSPENDED" | "CLOSED";
|
|
5951
|
+
logoUrl: string | null;
|
|
5952
|
+
businessNumber: string | null;
|
|
5953
|
+
customerCenterPhone: string | null;
|
|
5954
|
+
analyticsAllowedOrigins: string[];
|
|
5955
|
+
guestCheckoutEnabled: boolean;
|
|
5709
5956
|
memberCount: number;
|
|
5710
5957
|
createdAt: string;
|
|
5711
5958
|
}>;
|
|
@@ -5815,6 +6062,7 @@ export declare const STORE_SCOPE_RESOURCES: {
|
|
|
5815
6062
|
readonly webhook: readonly ["r", "rw"];
|
|
5816
6063
|
readonly store: readonly ["r", "rw"];
|
|
5817
6064
|
readonly member: readonly ["r", "rw"];
|
|
6065
|
+
readonly customer: readonly ["r", "rw"];
|
|
5818
6066
|
};
|
|
5819
6067
|
export type StoreScopeResource = keyof typeof STORE_SCOPE_RESOURCES;
|
|
5820
6068
|
export declare const storeScopeResourceSchema: z.ZodEnum<{
|
|
@@ -5829,6 +6077,7 @@ export declare const storeScopeResourceSchema: z.ZodEnum<{
|
|
|
5829
6077
|
webhook: "webhook";
|
|
5830
6078
|
store: "store";
|
|
5831
6079
|
member: "member";
|
|
6080
|
+
customer: "customer";
|
|
5832
6081
|
}>;
|
|
5833
6082
|
/** 카탈로그상 존재하는 스코프 문자열인가 (예: `settlement:rw`는 false) */
|
|
5834
6083
|
export declare function isStoreScope(scope: string): boolean;
|
|
@@ -5838,9 +6087,9 @@ export declare function isStoreScope(scope: string): boolean;
|
|
|
5838
6087
|
* 발급 시 지정한 스코프만 갖는다(발급자 보유 스코프 이내).
|
|
5839
6088
|
*/
|
|
5840
6089
|
export declare const STORE_ROLE_SCOPES: {
|
|
5841
|
-
readonly OWNER: readonly ["product:rw", "category:rw", "order:rw", "claim:rw", "inquiry:rw", "review:rw", "settlement:r", "analytics:r", "webhook:rw", "store:rw", "member:rw"];
|
|
5842
|
-
readonly ADMIN: readonly ["product:rw", "category:rw", "order:rw", "claim:rw", "inquiry:rw", "review:rw", "analytics:r", "webhook:rw", "store:rw", "member:rw"];
|
|
5843
|
-
readonly STAFF: readonly ["product:rw", "category:rw", "order:rw", "claim:rw", "inquiry:rw", "review:rw"];
|
|
6090
|
+
readonly OWNER: readonly ["product:rw", "category:rw", "order:rw", "claim:rw", "inquiry:rw", "review:rw", "settlement:r", "analytics:r", "webhook:rw", "store:rw", "member:rw", "customer:rw"];
|
|
6091
|
+
readonly ADMIN: readonly ["product:rw", "category:rw", "order:rw", "claim:rw", "inquiry:rw", "review:rw", "analytics:r", "webhook:rw", "store:rw", "member:rw", "customer:rw"];
|
|
6092
|
+
readonly STAFF: readonly ["product:rw", "category:rw", "order:rw", "claim:rw", "inquiry:rw", "review:rw", "customer:r"];
|
|
5844
6093
|
};
|
|
5845
6094
|
//#endregion
|
|
5846
6095
|
//#region src/schemas/webhook-event.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -698,6 +698,103 @@ const approveClaimResultSchema = z.object({
|
|
|
698
698
|
refundedAt: z.string()
|
|
699
699
|
});
|
|
700
700
|
//#endregion
|
|
701
|
+
//#region src/schemas/customer.ts
|
|
702
|
+
/** 구매자 목록 정렬 */
|
|
703
|
+
const customerSortSchema = z.enum([
|
|
704
|
+
"joined_desc",
|
|
705
|
+
"joined_asc",
|
|
706
|
+
"total_paid_desc",
|
|
707
|
+
"order_count_desc",
|
|
708
|
+
"last_ordered_desc"
|
|
709
|
+
]).describe("정렬. `joined_desc`(기본, 최근 가입순)·`joined_asc`·`total_paid_desc`(구매 금액순)·`order_count_desc`(주문 수순)·`last_ordered_desc`(최근 주문순)");
|
|
710
|
+
/** `GET /v1/customers` 쿼리 */
|
|
711
|
+
const customerListQuerySchema = z.object({
|
|
712
|
+
q: z.string().optional().describe("검색어 — 이름·이메일(포함). 숫자 4자리면 휴대폰 뒤 4자리로도 찾는다"),
|
|
713
|
+
joinedFrom: z.string().optional().describe("가입일 시작(YYYY-MM-DD, KST, 포함)"),
|
|
714
|
+
joinedTo: z.string().optional().describe("가입일 끝(YYYY-MM-DD, KST, 포함)"),
|
|
715
|
+
marketing: z.enum(["agreed", "declined"]).optional().describe("마케팅 수신 동의 필터. 생략하면 전체"),
|
|
716
|
+
sort: customerSortSchema.optional(),
|
|
717
|
+
...pageQueryShape()
|
|
718
|
+
});
|
|
719
|
+
/** 구매 통계 — 구매 금액은 취소된 주문 상품을 빼고, 완료된 반품은 수량 비율로 뺀 금액이다 */
|
|
720
|
+
const customerStatsSchema = z.object({
|
|
721
|
+
orderCount: z.number().int().describe("주문 수(취소 포함)"),
|
|
722
|
+
totalPaid: z.number().int().describe("구매 금액(원) — 취소·완료된 반품 제외"),
|
|
723
|
+
lastOrderedAt: z.string().nullable().describe("최근 주문 시각")
|
|
724
|
+
});
|
|
725
|
+
/** 목록 행 — 휴대폰은 가운데를 가린다(`010-****-1234`) */
|
|
726
|
+
const customerSummarySchema = z.object({
|
|
727
|
+
customerId: z.string(),
|
|
728
|
+
name: z.string(),
|
|
729
|
+
email: z.string(),
|
|
730
|
+
maskedPhone: z.string().nullable(),
|
|
731
|
+
joinedAt: z.string(),
|
|
732
|
+
point: z.number().int(),
|
|
733
|
+
marketingAgreed: z.boolean()
|
|
734
|
+
}).extend(customerStatsSchema.shape);
|
|
735
|
+
const customerAddressSchema = z.object({
|
|
736
|
+
addressId: z.string(),
|
|
737
|
+
alias: z.string(),
|
|
738
|
+
isDefault: z.boolean(),
|
|
739
|
+
receiverName: z.string(),
|
|
740
|
+
phone: z.string(),
|
|
741
|
+
zipCode: z.string(),
|
|
742
|
+
address1: z.string(),
|
|
743
|
+
address2: z.string().nullable()
|
|
744
|
+
});
|
|
745
|
+
/** 구매자 상세 — 휴대폰 전체를 보여 준다 */
|
|
746
|
+
const customerDetailSchema = z.object({
|
|
747
|
+
customerId: z.string(),
|
|
748
|
+
name: z.string(),
|
|
749
|
+
email: z.string(),
|
|
750
|
+
phone: z.string().nullable(),
|
|
751
|
+
joinedAt: z.string(),
|
|
752
|
+
point: z.number().int(),
|
|
753
|
+
marketingAgreed: z.boolean(),
|
|
754
|
+
loginMethods: z.array(z.enum(["password"])).describe("로그인 방법. 지금은 이메일·비밀번호(`password`)뿐이다"),
|
|
755
|
+
addresses: z.array(customerAddressSchema),
|
|
756
|
+
stats: customerStatsSchema.extend({
|
|
757
|
+
canceledOrReturnedCount: z.number().int().describe("취소된 주문 상품 + 완료된 반품 수"),
|
|
758
|
+
reviewCount: z.number().int(),
|
|
759
|
+
inquiryCount: z.number().int().describe("상품 문의 + 1:1 문의")
|
|
760
|
+
})
|
|
761
|
+
});
|
|
762
|
+
/** 구매자의 주문 — 전체 기간 */
|
|
763
|
+
const customerOrderSchema = z.object({
|
|
764
|
+
orderId: z.string(),
|
|
765
|
+
orderedAt: z.string(),
|
|
766
|
+
totalAmount: z.number().int().describe("결제 금액"),
|
|
767
|
+
testPayment: z.boolean(),
|
|
768
|
+
items: z.array(z.object({
|
|
769
|
+
orderItemId: z.string(),
|
|
770
|
+
productName: z.string(),
|
|
771
|
+
optionName: z.string().nullable(),
|
|
772
|
+
quantity: z.number().int(),
|
|
773
|
+
status: orderItemStatusSchema,
|
|
774
|
+
claimStatus: z.enum([
|
|
775
|
+
"CANCEL_REQUESTED",
|
|
776
|
+
"RETURN_REQUESTED",
|
|
777
|
+
"EXCHANGE_REQUESTED"
|
|
778
|
+
]).nullable()
|
|
779
|
+
}))
|
|
780
|
+
});
|
|
781
|
+
/** `POST /v1/customers/{id}/withdraw` 본문 */
|
|
782
|
+
const withdrawCustomerRequestSchema = z.object({
|
|
783
|
+
reason: z.enum([
|
|
784
|
+
"BUYER_REQUEST",
|
|
785
|
+
"DORMANT",
|
|
786
|
+
"ABUSE",
|
|
787
|
+
"OTHER"
|
|
788
|
+
]).describe("처리 사유 — 구매자 요청·장기 미이용·부정 이용·기타. 감사 로그에 남는다"),
|
|
789
|
+
memo: z.string().max(200).optional().describe("사유 보충(선택)")
|
|
790
|
+
});
|
|
791
|
+
/** `WITHDRAWAL_BLOCKED`의 `error.details` — 탈퇴를 막는 항목 수(스토어프런트 탈퇴와 같다) */
|
|
792
|
+
const customerWithdrawalBlockedDetailsSchema = z.object({
|
|
793
|
+
inProgressOrderItems: z.number().int(),
|
|
794
|
+
openClaims: z.number().int(),
|
|
795
|
+
pendingPayments: z.number().int()
|
|
796
|
+
});
|
|
797
|
+
//#endregion
|
|
701
798
|
//#region src/schemas/misc.ts
|
|
702
799
|
const inquirySchema = z.object({
|
|
703
800
|
inquiryId: z.string(),
|
|
@@ -1710,6 +1807,28 @@ function isReservedStoreCode(code) {
|
|
|
1710
1807
|
return RESERVED_STORE_CODES.includes(code.toLowerCase());
|
|
1711
1808
|
}
|
|
1712
1809
|
const storeCodeSchema = z.string().regex(STORE_CODE_PATTERN, "소문자·숫자·하이픈 3~30자여야 하고 하이픈으로 시작하거나 끝날 수 없어요").refine((code) => !isReservedStoreCode(code), "플랫폼이 쓰는 이름이라 스토어 코드로 쓸 수 없어요");
|
|
1810
|
+
/** `https://shop.example.com`처럼 경로·쿼리 없는 http(s) 오리진인가 */
|
|
1811
|
+
function isWebOrigin(value) {
|
|
1812
|
+
try {
|
|
1813
|
+
const url = new URL(value);
|
|
1814
|
+
return (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash && !value.includes("*");
|
|
1815
|
+
} catch {
|
|
1816
|
+
return false;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
/** 방문 분석을 보낼 수 있는 스토어프런트 오리진 — 스킴·호스트(·포트)만. 서버가 소문자·끝 슬래시 제거로 정규화한다 */
|
|
1820
|
+
const analyticsOriginSchema = z.string().trim().max(255).refine(isWebOrigin, "https://shop.example.com 형식(경로 없이)으로 입력해 주세요").describe("스토어프런트 오리진. 예: `https://shop.example.com`");
|
|
1821
|
+
/** 허용 오리진 상한 */
|
|
1822
|
+
const MAX_ANALYTICS_ALLOWED_ORIGINS = 20;
|
|
1823
|
+
/** `PATCH /v1/store` 본문 — 바꿀 필드만 보낸다 */
|
|
1824
|
+
const updateStoreRequestSchema = z.object({
|
|
1825
|
+
name: z.string().min(2).max(50).optional(),
|
|
1826
|
+
logoUrl: z.url().optional(),
|
|
1827
|
+
customerCenterPhone: z.string().optional(),
|
|
1828
|
+
businessHours: z.string().max(100).optional(),
|
|
1829
|
+
analyticsAllowedOrigins: z.array(analyticsOriginSchema).max(20).optional().describe("방문 분석 수집을 받을 스토어프런트 오리진(최대 20개). 비우면 모든 오리진에서 받는다. 넣으면 목록에 없는 오리진의 수집 요청은 403 `ORIGIN_NOT_ALLOWED`이고, 페이지 URL이 목록 밖인 이벤트는 버린다"),
|
|
1830
|
+
guestCheckoutEnabled: z.boolean().optional().describe("비회원 주문 허용. false(회원만)면 로그인하지 않은 구매자의 주문서 생성·결제 요청이 403 `GUEST_CHECKOUT_DISABLED`다. 장바구니 담기와 이미 받은 비회원 주문의 조회·취소는 막지 않는다")
|
|
1831
|
+
}).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" });
|
|
1713
1832
|
const storeSchema = z.object({
|
|
1714
1833
|
storeId: z.string(),
|
|
1715
1834
|
name: z.string(),
|
|
@@ -1723,6 +1842,8 @@ const storeSchema = z.object({
|
|
|
1723
1842
|
logoUrl: z.url().nullable(),
|
|
1724
1843
|
businessNumber: z.string().nullable(),
|
|
1725
1844
|
customerCenterPhone: z.string().nullable(),
|
|
1845
|
+
analyticsAllowedOrigins: z.array(z.string()).describe("방문 분석 수집을 받는 스토어프런트 오리진. 비어 있으면 모든 오리진에서 받는다"),
|
|
1846
|
+
guestCheckoutEnabled: z.boolean().describe("비회원 주문 허용. false면 회원만 주문할 수 있다"),
|
|
1726
1847
|
memberCount: z.number().int(),
|
|
1727
1848
|
createdAt: z.string()
|
|
1728
1849
|
});
|
|
@@ -2030,6 +2151,13 @@ function createStoreClient(options) {
|
|
|
2030
2151
|
answer: (inquiryId, content) => storeHttp.requestVoid("POST", path`/inquiries/${inquiryId}/answer`, { body: { content } }),
|
|
2031
2152
|
updateAnswer: (inquiryId, content) => storeHttp.requestVoid("PUT", path`/inquiries/${inquiryId}/answer`, { body: { content } })
|
|
2032
2153
|
},
|
|
2154
|
+
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(콘솔 OWNER·ADMIN) */
|
|
2155
|
+
customers: {
|
|
2156
|
+
list: (params) => storeHttp.request("GET", "/customers", pageSchema(customerSummarySchema), { query: params }),
|
|
2157
|
+
get: (customerId) => storeHttp.request("GET", path`/customers/${customerId}`, customerDetailSchema),
|
|
2158
|
+
orders: (customerId, params) => storeHttp.request("GET", path`/customers/${customerId}/orders`, pageSchema(customerOrderSchema), { query: params }),
|
|
2159
|
+
withdraw: (customerId, body) => storeHttp.requestVoid("POST", path`/customers/${customerId}/withdraw`, { body: withdrawCustomerRequestSchema.parse(body) })
|
|
2160
|
+
},
|
|
2033
2161
|
customerInquiries: {
|
|
2034
2162
|
list: (params) => storeHttp.request("GET", "/customer-inquiries", pageSchema(sellerCustomerInquirySchema), { query: params }),
|
|
2035
2163
|
get: (inquiryId) => storeHttp.request("GET", path`/customer-inquiries/${inquiryId}`, sellerCustomerInquirySchema),
|
|
@@ -2212,6 +2340,11 @@ get: (params) => storeHttp.request("GET", "/sales-report", salesReportSchema, {
|
|
|
2212
2340
|
},
|
|
2213
2341
|
store: {
|
|
2214
2342
|
get: () => storeHttp.request("GET", "/store", storeSchema),
|
|
2343
|
+
/**
|
|
2344
|
+
* `PATCH /v1/store` — 스코프 `store:rw`. 바꿀 필드만 보낸다. `analyticsAllowedOrigins`는 방문 분석 수집을 받을
|
|
2345
|
+
* 스토어프런트 오리진 목록이다(빈 배열이면 모든 오리진 허용).
|
|
2346
|
+
*/
|
|
2347
|
+
update: (body) => storeHttp.request("PATCH", "/store", storeSchema, { body: updateStoreRequestSchema.parse(body) }),
|
|
2215
2348
|
listMembers: () => storeHttp.request("GET", "/store/members", z.array(storeMemberSchema)),
|
|
2216
2349
|
listInvitations: () => storeHttp.request("GET", "/store/invitations", z.array(invitationSchema))
|
|
2217
2350
|
}
|
|
@@ -2317,7 +2450,8 @@ const STORE_SCOPE_RESOURCES = {
|
|
|
2317
2450
|
analytics: ["r"],
|
|
2318
2451
|
webhook: ["r", "rw"],
|
|
2319
2452
|
store: ["r", "rw"],
|
|
2320
|
-
member: ["r", "rw"]
|
|
2453
|
+
member: ["r", "rw"],
|
|
2454
|
+
customer: ["r", "rw"]
|
|
2321
2455
|
};
|
|
2322
2456
|
const storeScopeResourceSchema = z.enum(Object.keys(STORE_SCOPE_RESOURCES)).describe("스코프 리소스");
|
|
2323
2457
|
/** 카탈로그상 존재하는 스코프 문자열인가 (예: `settlement:rw`는 false) */
|
|
@@ -2343,7 +2477,8 @@ const STORE_ROLE_SCOPES = {
|
|
|
2343
2477
|
"analytics:r",
|
|
2344
2478
|
"webhook:rw",
|
|
2345
2479
|
"store:rw",
|
|
2346
|
-
"member:rw"
|
|
2480
|
+
"member:rw",
|
|
2481
|
+
"customer:rw"
|
|
2347
2482
|
],
|
|
2348
2483
|
ADMIN: [
|
|
2349
2484
|
"product:rw",
|
|
@@ -2355,7 +2490,8 @@ const STORE_ROLE_SCOPES = {
|
|
|
2355
2490
|
"analytics:r",
|
|
2356
2491
|
"webhook:rw",
|
|
2357
2492
|
"store:rw",
|
|
2358
|
-
"member:rw"
|
|
2493
|
+
"member:rw",
|
|
2494
|
+
"customer:rw"
|
|
2359
2495
|
],
|
|
2360
2496
|
STAFF: [
|
|
2361
2497
|
"product:rw",
|
|
@@ -2363,7 +2499,8 @@ const STORE_ROLE_SCOPES = {
|
|
|
2363
2499
|
"order:rw",
|
|
2364
2500
|
"claim:rw",
|
|
2365
2501
|
"inquiry:rw",
|
|
2366
|
-
"review:rw"
|
|
2502
|
+
"review:rw",
|
|
2503
|
+
"customer:r"
|
|
2367
2504
|
]
|
|
2368
2505
|
};
|
|
2369
2506
|
//#endregion
|
|
@@ -2594,4 +2731,4 @@ function createStoreSession(options) {
|
|
|
2594
2731
|
};
|
|
2595
2732
|
}
|
|
2596
2733
|
//#endregion
|
|
2597
|
-
export { ApiError, MAX_OPTION_GROUPS, MAX_VALUES_PER_GROUP, MAX_VARIANTS, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, RESERVED_STORE_CODES, STORE_CODE_PATTERN, STORE_ROLE_SCOPES, STORE_SCOPE_RESOURCES, STORE_SESSION_KEYS, TOSS_WIDGET_CLIENT_KEY_PATTERN, TOSS_WIDGET_KEY_MESSAGE, TOSS_WIDGET_SECRET_KEY_PATTERN, VARIANT_STOCK_EXCLUSIVE_MESSAGE, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, addressChangeRequestListSchema, addressTypeSchema, analyticsChannelSchema, analyticsFunnelStepSchema, analyticsOverviewSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiClientListQuerySchema, apiClientSchema, apiClientSecretRotatedSchema, apiClientStatusSchema, apiClientTypeSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, apiTokenCreateRequestSchema, apiTokenCreatedSchema, apiTokenSchema, approveClaimResultSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, createPkcePair, createStoreClient, createStoreSession, customerInquiryCategorySchema, customerInquiryListQuerySchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, effectivePaymentRoutingModeSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationListQuerySchema, invitationSchema, isReservedStoreCode, isStoreScope, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, maskedSecretSchema, myAccountSchema, myStoreSchema, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionValueRequestSchema, optionValueSchema, orderItemListQuerySchema, orderItemMemoResultSchema, orderItemPageSchema, orderItemSchema, orderItemStatusSchema, orderListQuerySchema, orderSchema, orderSummaryPageSchema, orderSummarySchema, orderWebhookEventDataSchema, orderWebhookEventSchema, pageInfoSchema, pageQueryShape, pageSchema, paymentAttemptRecordSchema, paymentAttemptStatusSchema, paymentCredentialSetSchema, paymentListQuerySchema, paymentProviderSchema, paymentProviderVerificationSchema, paymentRecordDetailSchema, paymentRecordPageSchema, paymentRecordSchema, paymentRecordStatusSchema, paymentRefundRecordSchema, paymentRoutingModeSchema, paymentSettingsSchema, pgEnvironmentSchema, pgPaymentMethodSchema, pgProviderSchema, pgVerificationResultSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productImageUploadResultSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, refreshUserToken, rejectClaimRequestSchema, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, scopeAccessSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, tossPaymentsCredentialsInputSchema, trackingUpdateResultSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, uploadRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookRedeliverFailedResultSchema, webhookRequestSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema };
|
|
2734
|
+
export { ApiError, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_OPTION_GROUPS, MAX_VALUES_PER_GROUP, MAX_VARIANTS, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, RESERVED_STORE_CODES, STORE_CODE_PATTERN, STORE_ROLE_SCOPES, STORE_SCOPE_RESOURCES, STORE_SESSION_KEYS, TOSS_WIDGET_CLIENT_KEY_PATTERN, TOSS_WIDGET_KEY_MESSAGE, TOSS_WIDGET_SECRET_KEY_PATTERN, VARIANT_STOCK_EXCLUSIVE_MESSAGE, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, addressChangeRequestListSchema, addressTypeSchema, analyticsChannelSchema, analyticsFunnelStepSchema, analyticsOriginSchema, analyticsOverviewSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiClientListQuerySchema, apiClientSchema, apiClientSecretRotatedSchema, apiClientStatusSchema, apiClientTypeSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, apiTokenCreateRequestSchema, apiTokenCreatedSchema, apiTokenSchema, approveClaimResultSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, createPkcePair, createStoreClient, createStoreSession, customerAddressSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, effectivePaymentRoutingModeSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationListQuerySchema, invitationSchema, isReservedStoreCode, isStoreScope, isWebOrigin, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, maskedSecretSchema, myAccountSchema, myStoreSchema, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionValueRequestSchema, optionValueSchema, orderItemListQuerySchema, orderItemMemoResultSchema, orderItemPageSchema, orderItemSchema, orderItemStatusSchema, orderListQuerySchema, orderSchema, orderSummaryPageSchema, orderSummarySchema, orderWebhookEventDataSchema, orderWebhookEventSchema, pageInfoSchema, pageQueryShape, pageSchema, paymentAttemptRecordSchema, paymentAttemptStatusSchema, paymentCredentialSetSchema, paymentListQuerySchema, paymentProviderSchema, paymentProviderVerificationSchema, paymentRecordDetailSchema, paymentRecordPageSchema, paymentRecordSchema, paymentRecordStatusSchema, paymentRefundRecordSchema, paymentRoutingModeSchema, paymentSettingsSchema, pgEnvironmentSchema, pgPaymentMethodSchema, pgProviderSchema, pgVerificationResultSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productImageUploadResultSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, refreshUserToken, rejectClaimRequestSchema, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, scopeAccessSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, tossPaymentsCredentialsInputSchema, trackingUpdateResultSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookRedeliverFailedResultSchema, webhookRequestSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema };
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -52,6 +52,14 @@ import {
|
|
|
52
52
|
rejectClaimRequestSchema,
|
|
53
53
|
} from "./schemas/claim";
|
|
54
54
|
import { bulkResultSchema, type PageParams, pageSchema } from "./schemas/common";
|
|
55
|
+
import {
|
|
56
|
+
customerDetailSchema,
|
|
57
|
+
type customerListQuerySchema,
|
|
58
|
+
customerOrderSchema,
|
|
59
|
+
customerSummarySchema,
|
|
60
|
+
type WithdrawCustomerRequest,
|
|
61
|
+
withdrawCustomerRequestSchema,
|
|
62
|
+
} from "./schemas/customer";
|
|
55
63
|
import {
|
|
56
64
|
type CustomerInquiryCategory,
|
|
57
65
|
inquirySchema,
|
|
@@ -144,6 +152,8 @@ import {
|
|
|
144
152
|
sellerAddressSchema,
|
|
145
153
|
storeMemberSchema,
|
|
146
154
|
storeSchema,
|
|
155
|
+
type UpdateStoreRequest,
|
|
156
|
+
updateStoreRequestSchema,
|
|
147
157
|
} from "./schemas/store";
|
|
148
158
|
import { type UploadRequest, uploadRequestSchema, uploadTicketSchema } from "./schemas/upload";
|
|
149
159
|
import type { AuthorizeOptions, StoreSession } from "./session";
|
|
@@ -557,6 +567,31 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
557
567
|
storeHttp.requestVoid("PUT", path`/inquiries/${inquiryId}/answer`, { body: { content } }),
|
|
558
568
|
},
|
|
559
569
|
|
|
570
|
+
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(콘솔 OWNER·ADMIN) */
|
|
571
|
+
customers: {
|
|
572
|
+
list: (
|
|
573
|
+
params?: PageParams & Omit<z.input<typeof customerListQuerySchema>, "page" | "size">,
|
|
574
|
+
) =>
|
|
575
|
+
storeHttp.request("GET", "/customers", pageSchema(customerSummarySchema), {
|
|
576
|
+
query: params as Query,
|
|
577
|
+
}),
|
|
578
|
+
get: (customerId: string) =>
|
|
579
|
+
storeHttp.request("GET", path`/customers/${customerId}`, customerDetailSchema),
|
|
580
|
+
orders: (customerId: string, params?: PageParams) =>
|
|
581
|
+
storeHttp.request(
|
|
582
|
+
"GET",
|
|
583
|
+
path`/customers/${customerId}/orders`,
|
|
584
|
+
pageSchema(customerOrderSchema),
|
|
585
|
+
{
|
|
586
|
+
query: params as Query,
|
|
587
|
+
},
|
|
588
|
+
),
|
|
589
|
+
withdraw: (customerId: string, body: WithdrawCustomerRequest) =>
|
|
590
|
+
storeHttp.requestVoid("POST", path`/customers/${customerId}/withdraw`, {
|
|
591
|
+
body: withdrawCustomerRequestSchema.parse(body),
|
|
592
|
+
}),
|
|
593
|
+
},
|
|
594
|
+
|
|
560
595
|
customerInquiries: {
|
|
561
596
|
list: (
|
|
562
597
|
params?: PageParams & {
|
|
@@ -869,6 +904,14 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
869
904
|
|
|
870
905
|
store: {
|
|
871
906
|
get: () => storeHttp.request("GET", "/store", storeSchema),
|
|
907
|
+
/**
|
|
908
|
+
* `PATCH /v1/store` — 스코프 `store:rw`. 바꿀 필드만 보낸다. `analyticsAllowedOrigins`는 방문 분석 수집을 받을
|
|
909
|
+
* 스토어프런트 오리진 목록이다(빈 배열이면 모든 오리진 허용).
|
|
910
|
+
*/
|
|
911
|
+
update: (body: UpdateStoreRequest) =>
|
|
912
|
+
storeHttp.request("PATCH", "/store", storeSchema, {
|
|
913
|
+
body: updateStoreRequestSchema.parse(body),
|
|
914
|
+
}),
|
|
872
915
|
listMembers: () => storeHttp.request("GET", "/store/members", z.array(storeMemberSchema)),
|
|
873
916
|
listInvitations: () =>
|
|
874
917
|
storeHttp.request("GET", "/store/invitations", z.array(invitationSchema)),
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from "./schemas/auth";
|
|
|
18
18
|
export * from "./schemas/category";
|
|
19
19
|
export * from "./schemas/claim";
|
|
20
20
|
export * from "./schemas/common";
|
|
21
|
+
export * from "./schemas/customer";
|
|
21
22
|
export * from "./schemas/misc";
|
|
22
23
|
export * from "./schemas/order";
|
|
23
24
|
export * from "./schemas/payment";
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { orderItemStatusSchema, pageQueryShape } from "./common";
|
|
3
|
+
|
|
4
|
+
/** 구매자 목록 정렬 */
|
|
5
|
+
export const customerSortSchema = z
|
|
6
|
+
.enum(["joined_desc", "joined_asc", "total_paid_desc", "order_count_desc", "last_ordered_desc"])
|
|
7
|
+
.describe(
|
|
8
|
+
"정렬. `joined_desc`(기본, 최근 가입순)·`joined_asc`·`total_paid_desc`(구매 금액순)·`order_count_desc`(주문 수순)·`last_ordered_desc`(최근 주문순)",
|
|
9
|
+
);
|
|
10
|
+
export type CustomerSort = z.infer<typeof customerSortSchema>;
|
|
11
|
+
|
|
12
|
+
/** `GET /v1/customers` 쿼리 */
|
|
13
|
+
export const customerListQuerySchema = z.object({
|
|
14
|
+
q: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("검색어 — 이름·이메일(포함). 숫자 4자리면 휴대폰 뒤 4자리로도 찾는다"),
|
|
18
|
+
joinedFrom: z.string().optional().describe("가입일 시작(YYYY-MM-DD, KST, 포함)"),
|
|
19
|
+
joinedTo: z.string().optional().describe("가입일 끝(YYYY-MM-DD, KST, 포함)"),
|
|
20
|
+
marketing: z
|
|
21
|
+
.enum(["agreed", "declined"])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("마케팅 수신 동의 필터. 생략하면 전체"),
|
|
24
|
+
sort: customerSortSchema.optional(),
|
|
25
|
+
...pageQueryShape(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/** 구매 통계 — 구매 금액은 취소된 주문 상품을 빼고, 완료된 반품은 수량 비율로 뺀 금액이다 */
|
|
29
|
+
export const customerStatsSchema = z.object({
|
|
30
|
+
orderCount: z.number().int().describe("주문 수(취소 포함)"),
|
|
31
|
+
totalPaid: z.number().int().describe("구매 금액(원) — 취소·완료된 반품 제외"),
|
|
32
|
+
lastOrderedAt: z.string().nullable().describe("최근 주문 시각"),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** 목록 행 — 휴대폰은 가운데를 가린다(`010-****-1234`) */
|
|
36
|
+
export const customerSummarySchema = z
|
|
37
|
+
.object({
|
|
38
|
+
customerId: z.string(),
|
|
39
|
+
name: z.string(),
|
|
40
|
+
email: z.string(),
|
|
41
|
+
maskedPhone: z.string().nullable(),
|
|
42
|
+
joinedAt: z.string(),
|
|
43
|
+
point: z.number().int(),
|
|
44
|
+
marketingAgreed: z.boolean(),
|
|
45
|
+
})
|
|
46
|
+
.extend(customerStatsSchema.shape);
|
|
47
|
+
export type CustomerSummary = z.infer<typeof customerSummarySchema>;
|
|
48
|
+
|
|
49
|
+
export const customerAddressSchema = z.object({
|
|
50
|
+
addressId: z.string(),
|
|
51
|
+
alias: z.string(),
|
|
52
|
+
isDefault: z.boolean(),
|
|
53
|
+
receiverName: z.string(),
|
|
54
|
+
phone: z.string(),
|
|
55
|
+
zipCode: z.string(),
|
|
56
|
+
address1: z.string(),
|
|
57
|
+
address2: z.string().nullable(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** 구매자 상세 — 휴대폰 전체를 보여 준다 */
|
|
61
|
+
export const customerDetailSchema = z.object({
|
|
62
|
+
customerId: z.string(),
|
|
63
|
+
name: z.string(),
|
|
64
|
+
email: z.string(),
|
|
65
|
+
phone: z.string().nullable(),
|
|
66
|
+
joinedAt: z.string(),
|
|
67
|
+
point: z.number().int(),
|
|
68
|
+
marketingAgreed: z.boolean(),
|
|
69
|
+
loginMethods: z
|
|
70
|
+
.array(z.enum(["password"]))
|
|
71
|
+
.describe("로그인 방법. 지금은 이메일·비밀번호(`password`)뿐이다"),
|
|
72
|
+
addresses: z.array(customerAddressSchema),
|
|
73
|
+
stats: customerStatsSchema.extend({
|
|
74
|
+
canceledOrReturnedCount: z.number().int().describe("취소된 주문 상품 + 완료된 반품 수"),
|
|
75
|
+
reviewCount: z.number().int(),
|
|
76
|
+
inquiryCount: z.number().int().describe("상품 문의 + 1:1 문의"),
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
export type CustomerDetail = z.infer<typeof customerDetailSchema>;
|
|
80
|
+
|
|
81
|
+
/** 구매자의 주문 — 전체 기간 */
|
|
82
|
+
export const customerOrderSchema = z.object({
|
|
83
|
+
orderId: z.string(),
|
|
84
|
+
orderedAt: z.string(),
|
|
85
|
+
totalAmount: z.number().int().describe("결제 금액"),
|
|
86
|
+
testPayment: z.boolean(),
|
|
87
|
+
items: z.array(
|
|
88
|
+
z.object({
|
|
89
|
+
orderItemId: z.string(),
|
|
90
|
+
productName: z.string(),
|
|
91
|
+
optionName: z.string().nullable(),
|
|
92
|
+
quantity: z.number().int(),
|
|
93
|
+
status: orderItemStatusSchema,
|
|
94
|
+
claimStatus: z
|
|
95
|
+
.enum(["CANCEL_REQUESTED", "RETURN_REQUESTED", "EXCHANGE_REQUESTED"])
|
|
96
|
+
.nullable(),
|
|
97
|
+
}),
|
|
98
|
+
),
|
|
99
|
+
});
|
|
100
|
+
export type CustomerOrder = z.infer<typeof customerOrderSchema>;
|
|
101
|
+
|
|
102
|
+
/** `POST /v1/customers/{id}/withdraw` 본문 */
|
|
103
|
+
export const withdrawCustomerRequestSchema = z.object({
|
|
104
|
+
reason: z
|
|
105
|
+
.enum(["BUYER_REQUEST", "DORMANT", "ABUSE", "OTHER"])
|
|
106
|
+
.describe("처리 사유 — 구매자 요청·장기 미이용·부정 이용·기타. 감사 로그에 남는다"),
|
|
107
|
+
memo: z.string().max(200).optional().describe("사유 보충(선택)"),
|
|
108
|
+
});
|
|
109
|
+
export type WithdrawCustomerRequest = z.infer<typeof withdrawCustomerRequestSchema>;
|
|
110
|
+
|
|
111
|
+
/** `WITHDRAWAL_BLOCKED`의 `error.details` — 탈퇴를 막는 항목 수(스토어프런트 탈퇴와 같다) */
|
|
112
|
+
export const customerWithdrawalBlockedDetailsSchema = z.object({
|
|
113
|
+
inProgressOrderItems: z.number().int(),
|
|
114
|
+
openClaims: z.number().int(),
|
|
115
|
+
pendingPayments: z.number().int(),
|
|
116
|
+
});
|
|
117
|
+
export type CustomerWithdrawalBlockedDetails = z.infer<
|
|
118
|
+
typeof customerWithdrawalBlockedDetailsSchema
|
|
119
|
+
>;
|
package/src/schemas/scope.ts
CHANGED
|
@@ -27,6 +27,7 @@ export const STORE_SCOPE_RESOURCES = {
|
|
|
27
27
|
webhook: ["r", "rw"],
|
|
28
28
|
store: ["r", "rw"],
|
|
29
29
|
member: ["r", "rw"],
|
|
30
|
+
customer: ["r", "rw"],
|
|
30
31
|
} as const satisfies Record<string, readonly ScopeAccess[]>;
|
|
31
32
|
|
|
32
33
|
export type StoreScopeResource = keyof typeof STORE_SCOPE_RESOURCES;
|
|
@@ -61,6 +62,7 @@ export const STORE_ROLE_SCOPES = {
|
|
|
61
62
|
"webhook:rw",
|
|
62
63
|
"store:rw",
|
|
63
64
|
"member:rw",
|
|
65
|
+
"customer:rw",
|
|
64
66
|
],
|
|
65
67
|
ADMIN: [
|
|
66
68
|
"product:rw",
|
|
@@ -73,6 +75,15 @@ export const STORE_ROLE_SCOPES = {
|
|
|
73
75
|
"webhook:rw",
|
|
74
76
|
"store:rw",
|
|
75
77
|
"member:rw",
|
|
78
|
+
"customer:rw",
|
|
79
|
+
],
|
|
80
|
+
STAFF: [
|
|
81
|
+
"product:rw",
|
|
82
|
+
"category:rw",
|
|
83
|
+
"order:rw",
|
|
84
|
+
"claim:rw",
|
|
85
|
+
"inquiry:rw",
|
|
86
|
+
"review:rw",
|
|
87
|
+
"customer:r",
|
|
76
88
|
],
|
|
77
|
-
STAFF: ["product:rw", "category:rw", "order:rw", "claim:rw", "inquiry:rw", "review:rw"],
|
|
78
89
|
} as const satisfies Record<StoreRole, readonly string[]>;
|
package/src/schemas/store.ts
CHANGED
|
@@ -73,6 +73,63 @@ export const storeCodeSchema = z
|
|
|
73
73
|
"플랫폼이 쓰는 이름이라 스토어 코드로 쓸 수 없어요",
|
|
74
74
|
);
|
|
75
75
|
|
|
76
|
+
/** `https://shop.example.com`처럼 경로·쿼리 없는 http(s) 오리진인가 */
|
|
77
|
+
export function isWebOrigin(value: string): boolean {
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(value);
|
|
80
|
+
return (
|
|
81
|
+
(url.protocol === "https:" || url.protocol === "http:") &&
|
|
82
|
+
!url.username &&
|
|
83
|
+
!url.password &&
|
|
84
|
+
url.pathname === "/" &&
|
|
85
|
+
!url.search &&
|
|
86
|
+
!url.hash &&
|
|
87
|
+
!value.includes("*")
|
|
88
|
+
);
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** 방문 분석을 보낼 수 있는 스토어프런트 오리진 — 스킴·호스트(·포트)만. 서버가 소문자·끝 슬래시 제거로 정규화한다 */
|
|
95
|
+
export const analyticsOriginSchema = z
|
|
96
|
+
.string()
|
|
97
|
+
.trim()
|
|
98
|
+
.max(255)
|
|
99
|
+
.refine(isWebOrigin, "https://shop.example.com 형식(경로 없이)으로 입력해 주세요")
|
|
100
|
+
.describe("스토어프런트 오리진. 예: `https://shop.example.com`");
|
|
101
|
+
|
|
102
|
+
/** 허용 오리진 상한 */
|
|
103
|
+
export const MAX_ANALYTICS_ALLOWED_ORIGINS = 20;
|
|
104
|
+
|
|
105
|
+
/** `PATCH /v1/store` 본문 — 바꿀 필드만 보낸다 */
|
|
106
|
+
export const updateStoreRequestSchema = z
|
|
107
|
+
.object({
|
|
108
|
+
name: z.string().min(2).max(50).optional(),
|
|
109
|
+
logoUrl: z.url().optional(),
|
|
110
|
+
customerCenterPhone: z.string().optional(),
|
|
111
|
+
businessHours: z.string().max(100).optional(),
|
|
112
|
+
analyticsAllowedOrigins: z
|
|
113
|
+
.array(analyticsOriginSchema)
|
|
114
|
+
.max(MAX_ANALYTICS_ALLOWED_ORIGINS)
|
|
115
|
+
.optional()
|
|
116
|
+
.describe(
|
|
117
|
+
"방문 분석 수집을 받을 스토어프런트 오리진(최대 20개). 비우면 모든 오리진에서 받는다. " +
|
|
118
|
+
"넣으면 목록에 없는 오리진의 수집 요청은 403 `ORIGIN_NOT_ALLOWED`이고, 페이지 URL이 목록 밖인 이벤트는 버린다",
|
|
119
|
+
),
|
|
120
|
+
guestCheckoutEnabled: z
|
|
121
|
+
.boolean()
|
|
122
|
+
.optional()
|
|
123
|
+
.describe(
|
|
124
|
+
"비회원 주문 허용. false(회원만)면 로그인하지 않은 구매자의 주문서 생성·결제 요청이 403 `GUEST_CHECKOUT_DISABLED`다. " +
|
|
125
|
+
"장바구니 담기와 이미 받은 비회원 주문의 조회·취소는 막지 않는다",
|
|
126
|
+
),
|
|
127
|
+
})
|
|
128
|
+
.refine((value) => Object.values(value).some((v) => v !== undefined), {
|
|
129
|
+
message: "최소 1개 필드가 필요합니다",
|
|
130
|
+
});
|
|
131
|
+
export type UpdateStoreRequest = z.infer<typeof updateStoreRequestSchema>;
|
|
132
|
+
|
|
76
133
|
export const storeSchema = z.object({
|
|
77
134
|
storeId: z.string(),
|
|
78
135
|
name: z.string(),
|
|
@@ -82,6 +139,10 @@ export const storeSchema = z.object({
|
|
|
82
139
|
logoUrl: z.url().nullable(),
|
|
83
140
|
businessNumber: z.string().nullable(),
|
|
84
141
|
customerCenterPhone: z.string().nullable(),
|
|
142
|
+
analyticsAllowedOrigins: z
|
|
143
|
+
.array(z.string())
|
|
144
|
+
.describe("방문 분석 수집을 받는 스토어프런트 오리진. 비어 있으면 모든 오리진에서 받는다"),
|
|
145
|
+
guestCheckoutEnabled: z.boolean().describe("비회원 주문 허용. false면 회원만 주문할 수 있다"),
|
|
85
146
|
memberCount: z.number().int(),
|
|
86
147
|
createdAt: z.string(),
|
|
87
148
|
});
|