@sayren/store-sdk 0.3.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 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;
@@ -3333,6 +3472,7 @@ export declare const updateStoreRequestSchema: z.ZodObject<{
3333
3472
  customerCenterPhone: z.ZodOptional<z.ZodString>;
3334
3473
  businessHours: z.ZodOptional<z.ZodString>;
3335
3474
  analyticsAllowedOrigins: z.ZodOptional<z.ZodArray<z.ZodString>>;
3475
+ guestCheckoutEnabled: z.ZodOptional<z.ZodBoolean>;
3336
3476
  }, z.core.$strip>;
3337
3477
  export type UpdateStoreRequest = z.infer<typeof updateStoreRequestSchema>;
3338
3478
  export declare const storeSchema: z.ZodObject<{
@@ -3349,6 +3489,7 @@ export declare const storeSchema: z.ZodObject<{
3349
3489
  businessNumber: z.ZodNullable<z.ZodString>;
3350
3490
  customerCenterPhone: z.ZodNullable<z.ZodString>;
3351
3491
  analyticsAllowedOrigins: z.ZodArray<z.ZodString>;
3492
+ guestCheckoutEnabled: z.ZodBoolean;
3352
3493
  memberCount: z.ZodNumber;
3353
3494
  createdAt: z.ZodString;
3354
3495
  }, z.core.$strip>;
@@ -4464,6 +4605,76 @@ export declare function createStoreClient(options: StoreClientOptions): {
4464
4605
  answer: (inquiryId: string, content: string) => Promise<void>;
4465
4606
  updateAnswer: (inquiryId: string, content: string) => Promise<void>;
4466
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
+ };
4467
4678
  customerInquiries: {
4468
4679
  list: (params?: PageParams & {
4469
4680
  answered?: boolean;
@@ -5723,6 +5934,7 @@ export declare function createStoreClient(options: StoreClientOptions): {
5723
5934
  businessNumber: string | null;
5724
5935
  customerCenterPhone: string | null;
5725
5936
  analyticsAllowedOrigins: string[];
5937
+ guestCheckoutEnabled: boolean;
5726
5938
  memberCount: number;
5727
5939
  createdAt: string;
5728
5940
  }>;
@@ -5740,6 +5952,7 @@ export declare function createStoreClient(options: StoreClientOptions): {
5740
5952
  businessNumber: string | null;
5741
5953
  customerCenterPhone: string | null;
5742
5954
  analyticsAllowedOrigins: string[];
5955
+ guestCheckoutEnabled: boolean;
5743
5956
  memberCount: number;
5744
5957
  createdAt: string;
5745
5958
  }>;
@@ -5849,6 +6062,7 @@ export declare const STORE_SCOPE_RESOURCES: {
5849
6062
  readonly webhook: readonly ["r", "rw"];
5850
6063
  readonly store: readonly ["r", "rw"];
5851
6064
  readonly member: readonly ["r", "rw"];
6065
+ readonly customer: readonly ["r", "rw"];
5852
6066
  };
5853
6067
  export type StoreScopeResource = keyof typeof STORE_SCOPE_RESOURCES;
5854
6068
  export declare const storeScopeResourceSchema: z.ZodEnum<{
@@ -5863,6 +6077,7 @@ export declare const storeScopeResourceSchema: z.ZodEnum<{
5863
6077
  webhook: "webhook";
5864
6078
  store: "store";
5865
6079
  member: "member";
6080
+ customer: "customer";
5866
6081
  }>;
5867
6082
  /** 카탈로그상 존재하는 스코프 문자열인가 (예: `settlement:rw`는 false) */
5868
6083
  export declare function isStoreScope(scope: string): boolean;
@@ -5872,9 +6087,9 @@ export declare function isStoreScope(scope: string): boolean;
5872
6087
  * 발급 시 지정한 스코프만 갖는다(발급자 보유 스코프 이내).
5873
6088
  */
5874
6089
  export declare const STORE_ROLE_SCOPES: {
5875
- 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"];
5876
- readonly ADMIN: readonly ["product:rw", "category:rw", "order:rw", "claim:rw", "inquiry:rw", "review:rw", "analytics:r", "webhook:rw", "store:rw", "member:rw"];
5877
- 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"];
5878
6093
  };
5879
6094
  //#endregion
5880
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(),
@@ -1729,7 +1826,8 @@ const updateStoreRequestSchema = z.object({
1729
1826
  logoUrl: z.url().optional(),
1730
1827
  customerCenterPhone: z.string().optional(),
1731
1828
  businessHours: z.string().max(100).optional(),
1732
- analyticsAllowedOrigins: z.array(analyticsOriginSchema).max(20).optional().describe("방문 분석 수집을 받을 스토어프런트 오리진(최대 20개). 비우면 모든 오리진에서 받는다. 넣으면 목록에 없는 오리진의 수집 요청은 403 `ORIGIN_NOT_ALLOWED`이고, 페이지 URL이 목록 밖인 이벤트는 버린다")
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`다. 장바구니 담기와 이미 받은 비회원 주문의 조회·취소는 막지 않는다")
1733
1831
  }).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" });
1734
1832
  const storeSchema = z.object({
1735
1833
  storeId: z.string(),
@@ -1745,6 +1843,7 @@ const storeSchema = z.object({
1745
1843
  businessNumber: z.string().nullable(),
1746
1844
  customerCenterPhone: z.string().nullable(),
1747
1845
  analyticsAllowedOrigins: z.array(z.string()).describe("방문 분석 수집을 받는 스토어프런트 오리진. 비어 있으면 모든 오리진에서 받는다"),
1846
+ guestCheckoutEnabled: z.boolean().describe("비회원 주문 허용. false면 회원만 주문할 수 있다"),
1748
1847
  memberCount: z.number().int(),
1749
1848
  createdAt: z.string()
1750
1849
  });
@@ -2052,6 +2151,13 @@ function createStoreClient(options) {
2052
2151
  answer: (inquiryId, content) => storeHttp.requestVoid("POST", path`/inquiries/${inquiryId}/answer`, { body: { content } }),
2053
2152
  updateAnswer: (inquiryId, content) => storeHttp.requestVoid("PUT", path`/inquiries/${inquiryId}/answer`, { body: { content } })
2054
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
+ },
2055
2161
  customerInquiries: {
2056
2162
  list: (params) => storeHttp.request("GET", "/customer-inquiries", pageSchema(sellerCustomerInquirySchema), { query: params }),
2057
2163
  get: (inquiryId) => storeHttp.request("GET", path`/customer-inquiries/${inquiryId}`, sellerCustomerInquirySchema),
@@ -2344,7 +2450,8 @@ const STORE_SCOPE_RESOURCES = {
2344
2450
  analytics: ["r"],
2345
2451
  webhook: ["r", "rw"],
2346
2452
  store: ["r", "rw"],
2347
- member: ["r", "rw"]
2453
+ member: ["r", "rw"],
2454
+ customer: ["r", "rw"]
2348
2455
  };
2349
2456
  const storeScopeResourceSchema = z.enum(Object.keys(STORE_SCOPE_RESOURCES)).describe("스코프 리소스");
2350
2457
  /** 카탈로그상 존재하는 스코프 문자열인가 (예: `settlement:rw`는 false) */
@@ -2370,7 +2477,8 @@ const STORE_ROLE_SCOPES = {
2370
2477
  "analytics:r",
2371
2478
  "webhook:rw",
2372
2479
  "store:rw",
2373
- "member:rw"
2480
+ "member:rw",
2481
+ "customer:rw"
2374
2482
  ],
2375
2483
  ADMIN: [
2376
2484
  "product:rw",
@@ -2382,7 +2490,8 @@ const STORE_ROLE_SCOPES = {
2382
2490
  "analytics:r",
2383
2491
  "webhook:rw",
2384
2492
  "store:rw",
2385
- "member:rw"
2493
+ "member:rw",
2494
+ "customer:rw"
2386
2495
  ],
2387
2496
  STAFF: [
2388
2497
  "product:rw",
@@ -2390,7 +2499,8 @@ const STORE_ROLE_SCOPES = {
2390
2499
  "order:rw",
2391
2500
  "claim:rw",
2392
2501
  "inquiry:rw",
2393
- "review:rw"
2502
+ "review:rw",
2503
+ "customer:r"
2394
2504
  ]
2395
2505
  };
2396
2506
  //#endregion
@@ -2621,4 +2731,4 @@ function createStoreSession(options) {
2621
2731
  };
2622
2732
  }
2623
2733
  //#endregion
2624
- 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, customerInquiryCategorySchema, customerInquiryListQuerySchema, 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 };
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayren/store-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "exports": {
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,
@@ -559,6 +567,31 @@ export function createStoreClient(options: StoreClientOptions) {
559
567
  storeHttp.requestVoid("PUT", path`/inquiries/${inquiryId}/answer`, { body: { content } }),
560
568
  },
561
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
+
562
595
  customerInquiries: {
563
596
  list: (
564
597
  params?: PageParams & {
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
+ >;
@@ -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[]>;
@@ -117,6 +117,13 @@ export const updateStoreRequestSchema = z
117
117
  "방문 분석 수집을 받을 스토어프런트 오리진(최대 20개). 비우면 모든 오리진에서 받는다. " +
118
118
  "넣으면 목록에 없는 오리진의 수집 요청은 403 `ORIGIN_NOT_ALLOWED`이고, 페이지 URL이 목록 밖인 이벤트는 버린다",
119
119
  ),
120
+ guestCheckoutEnabled: z
121
+ .boolean()
122
+ .optional()
123
+ .describe(
124
+ "비회원 주문 허용. false(회원만)면 로그인하지 않은 구매자의 주문서 생성·결제 요청이 403 `GUEST_CHECKOUT_DISABLED`다. " +
125
+ "장바구니 담기와 이미 받은 비회원 주문의 조회·취소는 막지 않는다",
126
+ ),
120
127
  })
121
128
  .refine((value) => Object.values(value).some((v) => v !== undefined), {
122
129
  message: "최소 1개 필드가 필요합니다",
@@ -135,6 +142,7 @@ export const storeSchema = z.object({
135
142
  analyticsAllowedOrigins: z
136
143
  .array(z.string())
137
144
  .describe("방문 분석 수집을 받는 스토어프런트 오리진. 비어 있으면 모든 오리진에서 받는다"),
145
+ guestCheckoutEnabled: z.boolean().describe("비회원 주문 허용. false면 회원만 주문할 수 있다"),
138
146
  memberCount: z.number().int(),
139
147
  createdAt: z.string(),
140
148
  });