@sayren/storefront-sdk 0.4.0 → 0.6.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.
@@ -1,5 +1,28 @@
1
- import { a as AnonymousSession, c as TokenPair, l as WithdrawRequest, o as LoginRequest, s as SignupRequest, t as ApiError, u as WithdrawalBlockedDetails } from "../http-CxqZxTfE.mjs";
1
+ import { a as AnonymousSession, c as LoginRequest, d as TokenPair, f as WithdrawRequest, l as SignupRequest, p as WithdrawalBlockedDetails, t as ApiError, u as SocialProvider } from "../http-CqAR0_w4.mjs";
2
2
  //#region src/auth/index.d.ts
3
+ /** 소셜 로그인 시작 결과 — `codeVerifier`와 `state`는 돌아올 때까지 서버 세션(httpOnly 쿠키 등)에 보관한다 */
4
+ export interface IdpStart {
5
+ /** 구매자를 보낼 공급자 로그인 주소 */
6
+ url: string;
7
+ state: string;
8
+ codeVerifier: string;
9
+ }
10
+ export interface IdpCallbackInput {
11
+ /** 돌아온 주소의 `code`(성공) — 실패면 `error`가 온다 */
12
+ code: string | null;
13
+ /** 돌아온 주소의 `state` */
14
+ state: string | null;
15
+ /** `idp()`가 준 값 — 보관해 둔 것 */
16
+ expectedState: string;
17
+ codeVerifier: string;
18
+ /** 비회원 장바구니 토큰 — 주면 회원 장바구니로 합친다 */
19
+ cartToken?: string;
20
+ }
21
+ /** 소셜 로그인 실패 — 구매자가 취소했거나(`access_denied`) state가 맞지 않는다 */
22
+ export declare class IdpCallbackError extends Error {
23
+ readonly reason: "access_denied" | "state_mismatch" | "missing_code" | string;
24
+ constructor(reason: "access_denied" | "state_mismatch" | "missing_code" | string);
25
+ }
3
26
  export interface StorefrontAuthOptions {
4
27
  /** 예: https://api.sayren.app/storefront/v1 */
5
28
  baseUrl: string;
@@ -30,6 +53,21 @@ export declare function createStorefrontAuth(options: StorefrontAuthOptions): {
30
53
  session(body: LoginRequest): Promise<TokenPair>;
31
54
  /** 회원가입 — 가입과 동시에 로그인 토큰을 받는다. `cartToken`을 주면 장바구니를 병합한다 */
32
55
  signUp(body: SignupRequest): Promise<TokenPair>;
56
+ /**
57
+ * 소셜 로그인 시작 — PKCE(S256)와 state를 만들고 공급자 로그인 주소를 받는다. 반환한 `state`·`codeVerifier`를
58
+ * 서버 세션에 보관하고 구매자를 `url`로 보낸다. 로그인을 마치면 `redirectUri`로 `?code=&state=`가 붙어 돌아온다.
59
+ * `redirectUri`는 스토어프런트 도메인 목록 안이어야 한다
60
+ */
61
+ idp(provider: SocialProvider, options: {
62
+ redirectUri: string;
63
+ }): Promise<IdpStart>;
64
+ /**
65
+ * 소셜 로그인 마무리 — 돌아온 `code`를 구매자 토큰으로 바꾼다. state가 다르거나 구매자가 취소했으면
66
+ * `IdpCallbackError`. code는 1회용이고 1분 안에 바꿔야 한다
67
+ */
68
+ idpCallback(input: IdpCallbackInput & {
69
+ error?: string | null;
70
+ }): Promise<TokenPair>;
33
71
  /** 토큰 갱신 — refreshToken은 한 번 쓰면 폐기되고 새 쌍이 온다(회전) */
34
72
  refresh(refreshToken: string): Promise<TokenPair>;
35
73
  /**
@@ -46,4 +84,4 @@ export declare function createStorefrontAuth(options: StorefrontAuthOptions): {
46
84
  };
47
85
  export type StorefrontAuth = ReturnType<typeof createStorefrontAuth>;
48
86
  //#endregion
49
- export { type AnonymousSession, ApiError, type LoginRequest, type SignupRequest, type TokenPair, type WithdrawRequest, type WithdrawalBlockedDetails };
87
+ export { type AnonymousSession, ApiError, type LoginRequest, type SignupRequest, type SocialProvider, type TokenPair, type WithdrawRequest, type WithdrawalBlockedDetails };
@@ -1,5 +1,26 @@
1
- import { rt as ApiError, t as createStorefrontClient } from "../client-DHISrvXq.mjs";
1
+ import { ct as ApiError, t as createStorefrontClient } from "../client-Do9-LG5X.mjs";
2
2
  //#region src/auth/index.ts
3
+ /** 소셜 로그인 실패 — 구매자가 취소했거나(`access_denied`) state가 맞지 않는다 */
4
+ var IdpCallbackError = class extends Error {
5
+ reason;
6
+ constructor(reason) {
7
+ super(`소셜 로그인을 마치지 못했어요 (${reason})`);
8
+ this.reason = reason;
9
+ this.name = "IdpCallbackError";
10
+ }
11
+ };
12
+ function base64url(bytes) {
13
+ let binary = "";
14
+ for (const byte of bytes) binary += String.fromCharCode(byte);
15
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
16
+ }
17
+ function randomToken(bytes = 32) {
18
+ return base64url(globalThis.crypto.getRandomValues(new Uint8Array(bytes)));
19
+ }
20
+ async function s256(verifier) {
21
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
22
+ return base64url(new Uint8Array(digest));
23
+ }
3
24
  /**
4
25
  * 구매자 인증 — 화면은 스토어프런트가 그리고, 계정 저장과 인증 처리는 sayren이 한다.
5
26
  * 어느 방식으로 로그인해도 결과는 같은 구매자 토큰(`TokenPair`)이고 `/me/*`·장바구니·주문 API에 그대로 쓴다.
@@ -36,6 +57,39 @@ function createStorefrontAuth(options) {
36
57
  signUp(body) {
37
58
  return client().auth.signup(body);
38
59
  },
60
+ /**
61
+ * 소셜 로그인 시작 — PKCE(S256)와 state를 만들고 공급자 로그인 주소를 받는다. 반환한 `state`·`codeVerifier`를
62
+ * 서버 세션에 보관하고 구매자를 `url`로 보낸다. 로그인을 마치면 `redirectUri`로 `?code=&state=`가 붙어 돌아온다.
63
+ * `redirectUri`는 스토어프런트 도메인 목록 안이어야 한다
64
+ */
65
+ async idp(provider, options) {
66
+ const state = randomToken(24);
67
+ const codeVerifier = randomToken(32);
68
+ const { url } = await client().auth.idpAuthorize(provider, {
69
+ redirectUri: options.redirectUri,
70
+ codeChallenge: await s256(codeVerifier),
71
+ state
72
+ });
73
+ return {
74
+ url,
75
+ state,
76
+ codeVerifier
77
+ };
78
+ },
79
+ /**
80
+ * 소셜 로그인 마무리 — 돌아온 `code`를 구매자 토큰으로 바꾼다. state가 다르거나 구매자가 취소했으면
81
+ * `IdpCallbackError`. code는 1회용이고 1분 안에 바꿔야 한다
82
+ */
83
+ idpCallback(input) {
84
+ if (input.error) return Promise.reject(new IdpCallbackError(input.error));
85
+ if (!input.state || input.state !== input.expectedState) return Promise.reject(new IdpCallbackError("state_mismatch"));
86
+ if (!input.code) return Promise.reject(new IdpCallbackError("missing_code"));
87
+ return client().auth.idpToken({
88
+ code: input.code,
89
+ codeVerifier: input.codeVerifier,
90
+ cartToken: input.cartToken
91
+ });
92
+ },
39
93
  /** 토큰 갱신 — refreshToken은 한 번 쓰면 폐기되고 새 쌍이 온다(회전) */
40
94
  refresh(refreshToken) {
41
95
  return client().auth.refresh(refreshToken);
@@ -63,4 +117,4 @@ function createStorefrontAuth(options) {
63
117
  };
64
118
  }
65
119
  //#endregion
66
- export { ApiError, createStorefrontAuth };
120
+ export { ApiError, IdpCallbackError, createStorefrontAuth };
@@ -155,7 +155,12 @@ const anonymousSessionSchema = z.object({
155
155
  * 진행 중인 주문(결제 대기~배송 중)·클레임·결제가 있으면 409 `WITHDRAWAL_BLOCKED`, 비밀번호가 틀리면
156
156
  * 403 `PASSWORD_MISMATCH`다(로그인과 같은 5회 잠금).
157
157
  */
158
- const withdrawRequestSchema = z.object({ password: z.string().min(1) });
158
+ const withdrawRequestSchema = z.object({
159
+ /**
160
+ * 비밀번호로 가입한 구매자는 필수. 소셜 로그인으로만 가입한 구매자는 비밀번호가 없어 생략하고, 대신 최근 10분 안에
161
+ * 로그인한 액세스 토큰이어야 한다(아니면 403 `REAUTH_REQUIRED` — 다시 로그인한 뒤 호출)
162
+ */
163
+ password: z.string().min(1).optional() });
159
164
  /** `WITHDRAWAL_BLOCKED`의 `error.details` — 탈퇴를 막는 항목 수 */
160
165
  const withdrawalBlockedDetailsSchema = z.object({
161
166
  /** 결제 대기~배송 중인 주문 상품 */
@@ -165,6 +170,31 @@ const withdrawalBlockedDetailsSchema = z.object({
165
170
  /** 승인 중이거나 결제창이 열려 있을 수 있는 결제(결제창은 만료되면 풀린다) */
166
171
  pendingPayments: z.number().int()
167
172
  });
173
+ /** 소셜 로그인 공급자 */
174
+ const socialProviderSchema = z.enum([
175
+ "kakao",
176
+ "naver",
177
+ "google"
178
+ ]);
179
+ /** `POST /auth/idp/{provider}/authorize` 본문 — PKCE(S256)와 state는 SDK가 만든다 */
180
+ const idpAuthorizeRequestSchema = z.object({
181
+ /** 로그인을 마치고 돌아올 스토어프런트 주소. 스토어프런트 도메인 목록 안이어야 한다 */
182
+ redirectUri: z.url(),
183
+ /** base64url(SHA-256(codeVerifier)) */
184
+ codeChallenge: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
185
+ /** 스토어프런트가 만든 값 — 돌아올 때 그대로 붙어 온다. 세션에 저장해 두고 비교한다 */
186
+ state: z.string().min(16).max(128)
187
+ });
188
+ const idpAuthorizeResponseSchema = z.object({
189
+ /** 구매자를 보낼 공급자 로그인 주소 */
190
+ url: z.url() });
191
+ /** `POST /auth/idp/token` 본문 — 돌아온 1회용 code를 구매자 토큰으로 바꾼다 */
192
+ const idpTokenRequestSchema = z.object({
193
+ code: z.string().min(1),
194
+ codeVerifier: z.string().regex(/^[A-Za-z0-9._~-]{43,128}$/),
195
+ /** 비회원 장바구니 토큰 — 주면 회원 장바구니로 합친다 */
196
+ cartToken: z.string().optional()
197
+ });
168
198
  //#endregion
169
199
  //#region src/schemas/cart.ts
170
200
  const cartItemSchema = z.object({
@@ -512,7 +542,8 @@ const createInquiryRequestSchema = z.object({
512
542
  //#region src/schemas/member.ts
513
543
  const memberSchema = z.object({
514
544
  memberId: z.string(),
515
- email: z.email(),
545
+ /** 소셜 가입에서 공급자가 이메일을 주지 않았으면 null */
546
+ email: z.email().nullable(),
516
547
  name: z.string(),
517
548
  phone: z.string().nullable(),
518
549
  point: z.number().int(),
@@ -642,6 +673,32 @@ const updateReviewRequestSchema = z.object({
642
673
  images: z.array(z.url()).max(5).optional()
643
674
  }).refine((value) => Object.values(value).some((v) => v !== void 0), { message: "최소 1개 필드가 필요합니다" });
644
675
  //#endregion
676
+ //#region src/schemas/store.ts
677
+ /**
678
+ * 스토어 공개 설정 — `GET /store`. 스토어프런트가 화면을 그릴 때 읽는 값만 담는다.
679
+ */
680
+ const storefrontStoreSchema = z.object({
681
+ storeCode: z.string(),
682
+ name: z.string(),
683
+ logoUrl: z.string().nullable(),
684
+ customerCenterPhone: z.string().nullable(),
685
+ businessHours: z.string().nullable(),
686
+ /**
687
+ * 비회원 주문 허용. false면 로그인하지 않은 구매자의 주문서 생성·결제 요청이 403 `GUEST_CHECKOUT_DISABLED`다.
688
+ * 장바구니 담기는 된다 — 주문 버튼을 로그인 안내로 바꾸면 된다
689
+ */
690
+ guestCheckout: z.boolean(),
691
+ /**
692
+ * 켜진 로그인 방법 — 화면에 이 순서대로 버튼을 그리면 된다. `password`가 없으면 이메일·비밀번호 가입·로그인이 꺼져 있다
693
+ */
694
+ loginMethods: z.array(z.enum([
695
+ "password",
696
+ "kakao",
697
+ "naver",
698
+ "google"
699
+ ]))
700
+ });
701
+ //#endregion
645
702
  //#region src/client.ts
646
703
  const productPageSchema = pageSchema(productCardSchema).extend({ facets: productFacetsSchema.optional() });
647
704
  const reviewPageSchema = pageSchema(publicReviewSchema).extend({ summary: reviewSummarySchema.optional() });
@@ -695,9 +752,16 @@ function createStorefrontClient(options) {
695
752
  login: (body) => http.request("POST", "/auth/login", tokenPairSchema, { body: loginRequestSchema.parse(body) }),
696
753
  refresh: (refreshToken) => http.request("POST", "/auth/token/refresh", tokenPairSchema, { body: { refreshToken } }),
697
754
  logout: () => http.requestVoid("POST", "/auth/logout"),
755
+ /** 소셜 로그인 시작 — 공급자 로그인 주소를 받는다. PKCE·state는 `@sayren/storefront-sdk/auth`의 `idp()`가 만든다 */
756
+ idpAuthorize: (provider, body) => http.request("POST", path`/auth/idp/${provider}/authorize`, idpAuthorizeResponseSchema, { body: idpAuthorizeRequestSchema.parse(body) }),
757
+ /** 소셜 로그인 마무리 — 돌아온 1회용 code + codeVerifier를 구매자 토큰으로 바꾼다 */
758
+ idpToken: (body) => http.request("POST", "/auth/idp/token", tokenPairSchema, { body: idpTokenRequestSchema.parse(body) }),
698
759
  /** 구매자 탈퇴 — 로그인 상태(accessToken)에서 비밀번호로 본인 확인. 성공하면 토큰은 모두 무효 */
699
760
  withdraw: (body) => http.requestVoid("POST", "/auth/withdraw", { body: withdrawRequestSchema.parse(body) })
700
761
  },
762
+ store: {
763
+ /** 스토어 공개 설정 — 이름·고객센터·비회원 주문 허용 여부 */
764
+ get: () => http.request("GET", "/store", storefrontStoreSchema) },
701
765
  catalog: {
702
766
  listCategories: () => http.request("GET", "/categories", z.array(categoryNodeSchema)),
703
767
  searchProducts: (params) => http.request("GET", "/products", productPageSchema, { query: params }),
@@ -790,4 +854,4 @@ getStatus: (paymentId) => http.request("GET", path`/payments/${paymentId}`, paym
790
854
  };
791
855
  }
792
856
  //#endregion
793
- export { signupRequestSchema as $, PAYMENT_STATUS_VALUES as A, requestPaymentRequestSchema as B, createClaimRequestSchema as C, orderItemStatusSchema as D, claimStatusSchema as E, guestInfoSchema as F, productFacetsSchema as G, categoryNodeSchema as H, paymentMethodSchema as I, cartItemSchema as J, productSortSchema as K, paymentParamsSchema as L, confirmPaymentRequestSchema as M, confirmPaymentResultSchema as N, pageInfoSchema as O, createCheckoutRequestSchema as P, loginRequestSchema as Q, paymentStatusSchema as R, claimTypeSchema as S, myClaimSchema as T, productCardSchema as U, shippingAddressInputSchema as V, productDetailSchema as W, updateCartItemRequestSchema as X, cartSchema as Y, anonymousSessionSchema as Z, publicInquirySchema as _, reviewSummarySchema as a, toApiError as at, customerInquirySchema as b, deliveryTrackingSchema as c, orderShippingAddressSchema as d, tokenPairSchema as et, memberAddressRequestSchema as f, createInquiryRequestSchema as g, updateProfileRequestSchema as h, publicReviewSchema as i, buildQuery as it, checkoutSessionSchema as j, pageSchema as k, myOrderItemSchema as l, memberSchema as m, createReviewRequestSchema as n, withdrawalBlockedDetailsSchema as nt, updateReviewRequestSchema as o, unwrapData as ot, memberAddressSchema as p, addCartItemRequestSchema as q, createReviewResultSchema as r, ApiError as rt, writableReviewSchema as s, createStorefrontClient as t, withdrawRequestSchema as tt, myOrderSchema as u, createCustomerInquiryRequestSchema as v, createClaimResultSchema as w, claimReasonSchema as x, customerInquiryCategorySchema as y, pgParamsSchema as z };
857
+ export { idpAuthorizeRequestSchema as $, pageSchema as A, pgParamsSchema as B, claimTypeSchema as C, claimStatusSchema as D, myClaimSchema as E, createCheckoutRequestSchema as F, productDetailSchema as G, shippingAddressInputSchema as H, guestInfoSchema as I, addCartItemRequestSchema as J, productFacetsSchema as K, paymentMethodSchema as L, checkoutSessionSchema as M, confirmPaymentRequestSchema as N, orderItemStatusSchema as O, confirmPaymentResultSchema as P, anonymousSessionSchema as Q, paymentParamsSchema as R, claimReasonSchema as S, createClaimResultSchema as T, categoryNodeSchema as U, requestPaymentRequestSchema as V, productCardSchema as W, cartSchema as X, cartItemSchema as Y, updateCartItemRequestSchema as Z, createInquiryRequestSchema as _, publicReviewSchema as a, tokenPairSchema as at, customerInquiryCategorySchema as b, writableReviewSchema as c, ApiError as ct, myOrderSchema as d, unwrapData as dt, idpAuthorizeResponseSchema as et, orderShippingAddressSchema as f, updateProfileRequestSchema as g, memberSchema as h, createReviewResultSchema as i, socialProviderSchema as it, PAYMENT_STATUS_VALUES as j, pageInfoSchema as k, deliveryTrackingSchema as l, buildQuery as lt, memberAddressSchema as m, storefrontStoreSchema as n, loginRequestSchema as nt, reviewSummarySchema as o, withdrawRequestSchema as ot, memberAddressRequestSchema as p, productSortSchema as q, createReviewRequestSchema as r, signupRequestSchema as rt, updateReviewRequestSchema as s, withdrawalBlockedDetailsSchema as st, createStorefrontClient as t, idpTokenRequestSchema as tt, myOrderItemSchema as u, toApiError as ut, publicInquirySchema as v, createClaimRequestSchema as w, customerInquirySchema as x, createCustomerInquiryRequestSchema as y, paymentStatusSchema as z };
@@ -41,7 +41,7 @@ type AnonymousSession = z.infer<typeof anonymousSessionSchema>;
41
41
  * 403 `PASSWORD_MISMATCH`다(로그인과 같은 5회 잠금).
42
42
  */
43
43
  declare const withdrawRequestSchema: z.ZodObject<{
44
- password: z.ZodString;
44
+ password: z.ZodOptional<z.ZodString>;
45
45
  }, z.core.$strip>;
46
46
  type WithdrawRequest = z.infer<typeof withdrawRequestSchema>;
47
47
  /** `WITHDRAWAL_BLOCKED`의 `error.details` — 탈퇴를 막는 항목 수 */
@@ -51,6 +51,30 @@ declare const withdrawalBlockedDetailsSchema: z.ZodObject<{
51
51
  pendingPayments: z.ZodNumber;
52
52
  }, z.core.$strip>;
53
53
  type WithdrawalBlockedDetails = z.infer<typeof withdrawalBlockedDetailsSchema>;
54
+ /** 소셜 로그인 공급자 */
55
+ declare const socialProviderSchema: z.ZodEnum<{
56
+ kakao: "kakao";
57
+ naver: "naver";
58
+ google: "google";
59
+ }>;
60
+ type SocialProvider = z.infer<typeof socialProviderSchema>;
61
+ /** `POST /auth/idp/{provider}/authorize` 본문 — PKCE(S256)와 state는 SDK가 만든다 */
62
+ declare const idpAuthorizeRequestSchema: z.ZodObject<{
63
+ redirectUri: z.ZodURL;
64
+ codeChallenge: z.ZodString;
65
+ state: z.ZodString;
66
+ }, z.core.$strip>;
67
+ type IdpAuthorizeRequest = z.infer<typeof idpAuthorizeRequestSchema>;
68
+ declare const idpAuthorizeResponseSchema: z.ZodObject<{
69
+ url: z.ZodURL;
70
+ }, z.core.$strip>;
71
+ /** `POST /auth/idp/token` 본문 — 돌아온 1회용 code를 구매자 토큰으로 바꾼다 */
72
+ declare const idpTokenRequestSchema: z.ZodObject<{
73
+ code: z.ZodString;
74
+ codeVerifier: z.ZodString;
75
+ cartToken: z.ZodOptional<z.ZodString>;
76
+ }, z.core.$strip>;
77
+ type IdpTokenRequest = z.infer<typeof idpTokenRequestSchema>;
54
78
  //#endregion
55
79
  //#region src/http.d.ts
56
80
  declare class ApiError extends Error {
@@ -68,4 +92,4 @@ type TokenSource = string | null | undefined | (() => string | null | undefined)
68
92
  type Query = Record<string, string | number | boolean | undefined>;
69
93
  declare function buildQuery(query?: Query): string;
70
94
  //#endregion
71
- export { AnonymousSession as a, TokenPair as c, anonymousSessionSchema as d, loginRequestSchema as f, withdrawalBlockedDetailsSchema as g, withdrawRequestSchema as h, buildQuery as i, WithdrawRequest as l, tokenPairSchema as m, Query as n, LoginRequest as o, signupRequestSchema as p, TokenSource as r, SignupRequest as s, ApiError as t, WithdrawalBlockedDetails as u };
95
+ export { withdrawalBlockedDetailsSchema as C, withdrawRequestSchema as S, idpTokenRequestSchema as _, AnonymousSession as a, socialProviderSchema as b, LoginRequest as c, TokenPair as d, WithdrawRequest as f, idpAuthorizeResponseSchema as g, idpAuthorizeRequestSchema as h, buildQuery as i, SignupRequest as l, anonymousSessionSchema as m, Query as n, IdpAuthorizeRequest as o, WithdrawalBlockedDetails as p, TokenSource as r, IdpTokenRequest as s, ApiError as t, SocialProvider as u, loginRequestSchema as v, tokenPairSchema as x, signupRequestSchema as y };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AnonymousSession, c as TokenPair, d as anonymousSessionSchema, f as loginRequestSchema, g as withdrawalBlockedDetailsSchema, h as withdrawRequestSchema, i as buildQuery, l as WithdrawRequest, m as tokenPairSchema, n as Query, o as LoginRequest, p as signupRequestSchema, r as TokenSource, s as SignupRequest, t as ApiError, u as WithdrawalBlockedDetails } from "./http-CxqZxTfE.mjs";
1
+ import { C as withdrawalBlockedDetailsSchema, S as withdrawRequestSchema, _ as idpTokenRequestSchema, a as AnonymousSession, b as socialProviderSchema, c as LoginRequest, d as TokenPair, f as WithdrawRequest, g as idpAuthorizeResponseSchema, h as idpAuthorizeRequestSchema, i as buildQuery, l as SignupRequest, m as anonymousSessionSchema, n as Query, o as IdpAuthorizeRequest, p as WithdrawalBlockedDetails, r as TokenSource, s as IdpTokenRequest, t as ApiError, u as SocialProvider, v as loginRequestSchema, x as tokenPairSchema, y as signupRequestSchema } from "./http-CqAR0_w4.mjs";
2
2
  import { z } from "zod";
3
3
  //#region src/analytics-ids.d.ts
4
4
  /**
@@ -489,7 +489,7 @@ export type CreateInquiryRequest = z.infer<typeof createInquiryRequestSchema>;
489
489
  //#region src/schemas/member.d.ts
490
490
  export declare const memberSchema: z.ZodObject<{
491
491
  memberId: z.ZodString;
492
- email: z.ZodEmail;
492
+ email: z.ZodNullable<z.ZodEmail>;
493
493
  name: z.ZodString;
494
494
  phone: z.ZodNullable<z.ZodString>;
495
495
  point: z.ZodNumber;
@@ -672,7 +672,7 @@ export declare function createStorefrontClient(options: StorefrontClientOptions)
672
672
  /** session.userinfo() 위임 — session 미전달 시 에러 */
673
673
  userinfo(): Promise<{
674
674
  memberId: string;
675
- email: string;
675
+ email: string | null;
676
676
  name: string;
677
677
  phone: string | null;
678
678
  point: number;
@@ -701,9 +701,31 @@ export declare function createStorefrontClient(options: StorefrontClientOptions)
701
701
  expiresIn: number;
702
702
  }>;
703
703
  logout: () => Promise<void>;
704
+ /** 소셜 로그인 시작 — 공급자 로그인 주소를 받는다. PKCE·state는 `@sayren/storefront-sdk/auth`의 `idp()`가 만든다 */
705
+ idpAuthorize: (provider: SocialProvider, body: IdpAuthorizeRequest) => Promise<{
706
+ url: string;
707
+ }>;
708
+ /** 소셜 로그인 마무리 — 돌아온 1회용 code + codeVerifier를 구매자 토큰으로 바꾼다 */
709
+ idpToken: (body: IdpTokenRequest) => Promise<{
710
+ accessToken: string;
711
+ refreshToken: string;
712
+ expiresIn: number;
713
+ }>;
704
714
  /** 구매자 탈퇴 — 로그인 상태(accessToken)에서 비밀번호로 본인 확인. 성공하면 토큰은 모두 무효 */
705
715
  withdraw: (body: WithdrawRequest) => Promise<void>;
706
716
  };
717
+ store: {
718
+ /** 스토어 공개 설정 — 이름·고객센터·비회원 주문 허용 여부 */
719
+ get: () => Promise<{
720
+ storeCode: string;
721
+ name: string;
722
+ logoUrl: string | null;
723
+ customerCenterPhone: string | null;
724
+ businessHours: string | null;
725
+ guestCheckout: boolean;
726
+ loginMethods: ("password" | "kakao" | "naver" | "google")[];
727
+ }>;
728
+ };
707
729
  catalog: {
708
730
  listCategories: () => Promise<CategoryNode[]>;
709
731
  searchProducts: (params?: ProductSearchParams) => Promise<{
@@ -1129,7 +1151,7 @@ export declare function createStorefrontClient(options: StorefrontClientOptions)
1129
1151
  member: {
1130
1152
  me: () => Promise<{
1131
1153
  memberId: string;
1132
- email: string;
1154
+ email: string | null;
1133
1155
  name: string;
1134
1156
  phone: string | null;
1135
1157
  point: number;
@@ -1485,4 +1507,24 @@ export declare const deliveryTrackingSchema: z.ZodObject<{
1485
1507
  }, z.core.$strip>;
1486
1508
  export type DeliveryTracking = z.infer<typeof deliveryTrackingSchema>;
1487
1509
  //#endregion
1488
- export { AnonymousSession, ApiError, LoginRequest, type Query, type SessionStorage, SignupRequest, type StorefrontClient, type StorefrontClientOptions, type StorefrontSession, type StorefrontSessionOptions, TokenPair, type TokenSource, WithdrawRequest, WithdrawalBlockedDetails, anonymousSessionSchema, buildQuery, loginRequestSchema, signupRequestSchema, tokenPairSchema, withdrawRequestSchema, withdrawalBlockedDetailsSchema };
1510
+ //#region src/schemas/store.d.ts
1511
+ /**
1512
+ * 스토어 공개 설정 — `GET /store`. 스토어프런트가 화면을 그릴 때 읽는 값만 담는다.
1513
+ */
1514
+ export declare const storefrontStoreSchema: z.ZodObject<{
1515
+ storeCode: z.ZodString;
1516
+ name: z.ZodString;
1517
+ logoUrl: z.ZodNullable<z.ZodString>;
1518
+ customerCenterPhone: z.ZodNullable<z.ZodString>;
1519
+ businessHours: z.ZodNullable<z.ZodString>;
1520
+ guestCheckout: z.ZodBoolean;
1521
+ loginMethods: z.ZodArray<z.ZodEnum<{
1522
+ password: "password";
1523
+ kakao: "kakao";
1524
+ naver: "naver";
1525
+ google: "google";
1526
+ }>>;
1527
+ }, z.core.$strip>;
1528
+ export type StorefrontStore = z.infer<typeof storefrontStoreSchema>;
1529
+ //#endregion
1530
+ export { AnonymousSession, ApiError, IdpAuthorizeRequest, IdpTokenRequest, LoginRequest, type Query, type SessionStorage, SignupRequest, SocialProvider, type StorefrontClient, type StorefrontClientOptions, type StorefrontSession, type StorefrontSessionOptions, TokenPair, type TokenSource, WithdrawRequest, WithdrawalBlockedDetails, anonymousSessionSchema, buildQuery, idpAuthorizeRequestSchema, idpAuthorizeResponseSchema, idpTokenRequestSchema, loginRequestSchema, signupRequestSchema, socialProviderSchema, tokenPairSchema, withdrawRequestSchema, withdrawalBlockedDetailsSchema };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { a as ANALYTICS_VISITOR_HEADER, c as analyticsClientEventSchema, i as ANALYTICS_VISITOR_COOKIE, l as analyticsIdSchema, n as ANALYTICS_SESSION_COOKIE, o as analyticsBatchSchema, r as ANALYTICS_SESSION_HEADER, s as analyticsClientEventNameSchema, t as ANALYTICS_MAX_BATCH_BYTES, u as analyticsServerEventNameSchema } from "./analytics-CgD70OqH.mjs";
2
2
  import { t as analyticsIdsFromCookie } from "./analytics-ids-COElZiI3.mjs";
3
- import { $ as signupRequestSchema, A as PAYMENT_STATUS_VALUES, B as requestPaymentRequestSchema, C as createClaimRequestSchema, D as orderItemStatusSchema, E as claimStatusSchema, F as guestInfoSchema, G as productFacetsSchema, H as categoryNodeSchema, I as paymentMethodSchema, J as cartItemSchema, K as productSortSchema, L as paymentParamsSchema, M as confirmPaymentRequestSchema, N as confirmPaymentResultSchema, O as pageInfoSchema, P as createCheckoutRequestSchema, Q as loginRequestSchema, R as paymentStatusSchema, S as claimTypeSchema, T as myClaimSchema, U as productCardSchema, V as shippingAddressInputSchema, W as productDetailSchema, X as updateCartItemRequestSchema, Y as cartSchema, Z as anonymousSessionSchema, _ as publicInquirySchema, a as reviewSummarySchema, at as toApiError, b as customerInquirySchema, c as deliveryTrackingSchema, d as orderShippingAddressSchema, et as tokenPairSchema, f as memberAddressRequestSchema, g as createInquiryRequestSchema, h as updateProfileRequestSchema, i as publicReviewSchema, it as buildQuery, j as checkoutSessionSchema, k as pageSchema, l as myOrderItemSchema, m as memberSchema, n as createReviewRequestSchema, nt as withdrawalBlockedDetailsSchema, o as updateReviewRequestSchema, ot as unwrapData, p as memberAddressSchema, q as addCartItemRequestSchema, r as createReviewResultSchema, rt as ApiError, s as writableReviewSchema, t as createStorefrontClient, tt as withdrawRequestSchema, u as myOrderSchema, v as createCustomerInquiryRequestSchema, w as createClaimResultSchema, x as claimReasonSchema, y as customerInquiryCategorySchema, z as pgParamsSchema } from "./client-DHISrvXq.mjs";
3
+ import { $ as idpAuthorizeRequestSchema, A as pageSchema, B as pgParamsSchema, C as claimTypeSchema, D as claimStatusSchema, E as myClaimSchema, F as createCheckoutRequestSchema, G as productDetailSchema, H as shippingAddressInputSchema, I as guestInfoSchema, J as addCartItemRequestSchema, K as productFacetsSchema, L as paymentMethodSchema, M as checkoutSessionSchema, N as confirmPaymentRequestSchema, O as orderItemStatusSchema, P as confirmPaymentResultSchema, Q as anonymousSessionSchema, R as paymentParamsSchema, S as claimReasonSchema, T as createClaimResultSchema, U as categoryNodeSchema, V as requestPaymentRequestSchema, W as productCardSchema, X as cartSchema, Y as cartItemSchema, Z as updateCartItemRequestSchema, _ as createInquiryRequestSchema, a as publicReviewSchema, at as tokenPairSchema, b as customerInquiryCategorySchema, c as writableReviewSchema, ct as ApiError, d as myOrderSchema, dt as unwrapData, et as idpAuthorizeResponseSchema, f as orderShippingAddressSchema, g as updateProfileRequestSchema, h as memberSchema, i as createReviewResultSchema, it as socialProviderSchema, j as PAYMENT_STATUS_VALUES, k as pageInfoSchema, l as deliveryTrackingSchema, lt as buildQuery, m as memberAddressSchema, n as storefrontStoreSchema, nt as loginRequestSchema, o as reviewSummarySchema, ot as withdrawRequestSchema, p as memberAddressRequestSchema, q as productSortSchema, r as createReviewRequestSchema, rt as signupRequestSchema, s as updateReviewRequestSchema, st as withdrawalBlockedDetailsSchema, t as createStorefrontClient, tt as idpTokenRequestSchema, u as myOrderItemSchema, ut as toApiError, v as publicInquirySchema, w as createClaimRequestSchema, x as customerInquirySchema, y as createCustomerInquiryRequestSchema, z as paymentStatusSchema } from "./client-Do9-LG5X.mjs";
4
4
  //#region src/session.ts
5
5
  /** 세션 키 상수 — SDK가 소유한다. store-sdk와 겹치지 않는 별도 네임스페이스. */
6
6
  const STOREFRONT_SESSION_KEYS = {
@@ -124,4 +124,4 @@ function createStorefrontSession(options) {
124
124
  };
125
125
  }
126
126
  //#endregion
127
- export { ANALYTICS_MAX_BATCH_BYTES, ANALYTICS_SESSION_COOKIE, ANALYTICS_SESSION_HEADER, ANALYTICS_VISITOR_COOKIE, ANALYTICS_VISITOR_HEADER, ApiError, PAYMENT_STATUS_VALUES, STOREFRONT_SESSION_KEYS, addCartItemRequestSchema, analyticsBatchSchema, analyticsClientEventNameSchema, analyticsClientEventSchema, analyticsIdSchema, analyticsIdsFromCookie, analyticsServerEventNameSchema, anonymousSessionSchema, buildQuery, cartItemSchema, cartSchema, categoryNodeSchema, checkoutSessionSchema, claimReasonSchema, claimStatusSchema, claimTypeSchema, confirmPaymentRequestSchema, confirmPaymentResultSchema, createCheckoutRequestSchema, createClaimRequestSchema, createClaimResultSchema, createCustomerInquiryRequestSchema, createInquiryRequestSchema, createReviewRequestSchema, createReviewResultSchema, createStorefrontClient, createStorefrontSession, customerInquiryCategorySchema, customerInquirySchema, deliveryTrackingSchema, guestInfoSchema, loginRequestSchema, memberAddressRequestSchema, memberAddressSchema, memberSchema, myClaimSchema, myOrderItemSchema, myOrderSchema, orderItemStatusSchema, orderShippingAddressSchema, pageInfoSchema, pageSchema, paymentMethodSchema, paymentParamsSchema, paymentStatusSchema, pgParamsSchema, productCardSchema, productDetailSchema, productFacetsSchema, productSortSchema, publicInquirySchema, publicReviewSchema, requestPaymentRequestSchema, reviewSummarySchema, shippingAddressInputSchema, signupRequestSchema, tokenPairSchema, updateCartItemRequestSchema, updateProfileRequestSchema, updateReviewRequestSchema, withdrawRequestSchema, withdrawalBlockedDetailsSchema, writableReviewSchema };
127
+ export { ANALYTICS_MAX_BATCH_BYTES, ANALYTICS_SESSION_COOKIE, ANALYTICS_SESSION_HEADER, ANALYTICS_VISITOR_COOKIE, ANALYTICS_VISITOR_HEADER, ApiError, PAYMENT_STATUS_VALUES, STOREFRONT_SESSION_KEYS, addCartItemRequestSchema, analyticsBatchSchema, analyticsClientEventNameSchema, analyticsClientEventSchema, analyticsIdSchema, analyticsIdsFromCookie, analyticsServerEventNameSchema, anonymousSessionSchema, buildQuery, cartItemSchema, cartSchema, categoryNodeSchema, checkoutSessionSchema, claimReasonSchema, claimStatusSchema, claimTypeSchema, confirmPaymentRequestSchema, confirmPaymentResultSchema, createCheckoutRequestSchema, createClaimRequestSchema, createClaimResultSchema, createCustomerInquiryRequestSchema, createInquiryRequestSchema, createReviewRequestSchema, createReviewResultSchema, createStorefrontClient, createStorefrontSession, customerInquiryCategorySchema, customerInquirySchema, deliveryTrackingSchema, guestInfoSchema, idpAuthorizeRequestSchema, idpAuthorizeResponseSchema, idpTokenRequestSchema, loginRequestSchema, memberAddressRequestSchema, memberAddressSchema, memberSchema, myClaimSchema, myOrderItemSchema, myOrderSchema, orderItemStatusSchema, orderShippingAddressSchema, pageInfoSchema, pageSchema, paymentMethodSchema, paymentParamsSchema, paymentStatusSchema, pgParamsSchema, productCardSchema, productDetailSchema, productFacetsSchema, productSortSchema, publicInquirySchema, publicReviewSchema, requestPaymentRequestSchema, reviewSummarySchema, shippingAddressInputSchema, signupRequestSchema, socialProviderSchema, storefrontStoreSchema, tokenPairSchema, updateCartItemRequestSchema, updateProfileRequestSchema, updateReviewRequestSchema, withdrawRequestSchema, withdrawalBlockedDetailsSchema, writableReviewSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayren/storefront-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "exports": {
package/src/auth/index.ts CHANGED
@@ -4,6 +4,7 @@ import type {
4
4
  AnonymousSession,
5
5
  LoginRequest,
6
6
  SignupRequest,
7
+ SocialProvider,
7
8
  TokenPair,
8
9
  WithdrawRequest,
9
10
  } from "../schemas/auth";
@@ -14,11 +15,60 @@ export type {
14
15
  AnonymousSession,
15
16
  LoginRequest,
16
17
  SignupRequest,
18
+ SocialProvider,
17
19
  TokenPair,
18
20
  WithdrawalBlockedDetails,
19
21
  WithdrawRequest,
20
22
  } from "../schemas/auth";
21
23
 
24
+ /** 소셜 로그인 시작 결과 — `codeVerifier`와 `state`는 돌아올 때까지 서버 세션(httpOnly 쿠키 등)에 보관한다 */
25
+ export interface IdpStart {
26
+ /** 구매자를 보낼 공급자 로그인 주소 */
27
+ url: string;
28
+ state: string;
29
+ codeVerifier: string;
30
+ }
31
+
32
+ export interface IdpCallbackInput {
33
+ /** 돌아온 주소의 `code`(성공) — 실패면 `error`가 온다 */
34
+ code: string | null;
35
+ /** 돌아온 주소의 `state` */
36
+ state: string | null;
37
+ /** `idp()`가 준 값 — 보관해 둔 것 */
38
+ expectedState: string;
39
+ codeVerifier: string;
40
+ /** 비회원 장바구니 토큰 — 주면 회원 장바구니로 합친다 */
41
+ cartToken?: string;
42
+ }
43
+
44
+ /** 소셜 로그인 실패 — 구매자가 취소했거나(`access_denied`) state가 맞지 않는다 */
45
+ export class IdpCallbackError extends Error {
46
+ constructor(
47
+ public readonly reason: "access_denied" | "state_mismatch" | "missing_code" | string,
48
+ ) {
49
+ super(`소셜 로그인을 마치지 못했어요 (${reason})`);
50
+ this.name = "IdpCallbackError";
51
+ }
52
+ }
53
+
54
+ function base64url(bytes: Uint8Array): string {
55
+ let binary = "";
56
+ for (const byte of bytes) binary += String.fromCharCode(byte);
57
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
58
+ }
59
+
60
+ function randomToken(bytes = 32): string {
61
+ return base64url(globalThis.crypto.getRandomValues(new Uint8Array(bytes)));
62
+ }
63
+
64
+ async function s256(verifier: string): Promise<string> {
65
+ const digest = await globalThis.crypto.subtle.digest(
66
+ "SHA-256",
67
+ new TextEncoder().encode(verifier),
68
+ );
69
+ return base64url(new Uint8Array(digest));
70
+ }
71
+
22
72
  export interface StorefrontAuthOptions {
23
73
  /** 예: https://api.sayren.app/storefront/v1 */
24
74
  baseUrl: string;
@@ -65,6 +115,37 @@ export function createStorefrontAuth(options: StorefrontAuthOptions) {
65
115
  signUp(body: SignupRequest): Promise<TokenPair> {
66
116
  return client().auth.signup(body);
67
117
  },
118
+ /**
119
+ * 소셜 로그인 시작 — PKCE(S256)와 state를 만들고 공급자 로그인 주소를 받는다. 반환한 `state`·`codeVerifier`를
120
+ * 서버 세션에 보관하고 구매자를 `url`로 보낸다. 로그인을 마치면 `redirectUri`로 `?code=&state=`가 붙어 돌아온다.
121
+ * `redirectUri`는 스토어프런트 도메인 목록 안이어야 한다
122
+ */
123
+ async idp(provider: SocialProvider, options: { redirectUri: string }): Promise<IdpStart> {
124
+ const state = randomToken(24);
125
+ const codeVerifier = randomToken(32);
126
+ const { url } = await client().auth.idpAuthorize(provider, {
127
+ redirectUri: options.redirectUri,
128
+ codeChallenge: await s256(codeVerifier),
129
+ state,
130
+ });
131
+ return { url, state, codeVerifier };
132
+ },
133
+ /**
134
+ * 소셜 로그인 마무리 — 돌아온 `code`를 구매자 토큰으로 바꾼다. state가 다르거나 구매자가 취소했으면
135
+ * `IdpCallbackError`. code는 1회용이고 1분 안에 바꿔야 한다
136
+ */
137
+ idpCallback(input: IdpCallbackInput & { error?: string | null }): Promise<TokenPair> {
138
+ if (input.error) return Promise.reject(new IdpCallbackError(input.error));
139
+ if (!input.state || input.state !== input.expectedState) {
140
+ return Promise.reject(new IdpCallbackError("state_mismatch"));
141
+ }
142
+ if (!input.code) return Promise.reject(new IdpCallbackError("missing_code"));
143
+ return client().auth.idpToken({
144
+ code: input.code,
145
+ codeVerifier: input.codeVerifier,
146
+ cartToken: input.cartToken,
147
+ });
148
+ },
68
149
  /** 토큰 갱신 — refreshToken은 한 번 쓰면 폐기되고 새 쌍이 온다(회전) */
69
150
  refresh(refreshToken: string): Promise<TokenPair> {
70
151
  return client().auth.refresh(refreshToken);
package/src/client.ts CHANGED
@@ -4,7 +4,13 @@ import { ANALYTICS_SESSION_HEADER, ANALYTICS_VISITOR_HEADER } from "./schemas/an
4
4
  import type { LoginRequest, SignupRequest } from "./schemas/auth";
5
5
  import {
6
6
  anonymousSessionSchema,
7
+ type IdpAuthorizeRequest,
8
+ type IdpTokenRequest,
9
+ idpAuthorizeRequestSchema,
10
+ idpAuthorizeResponseSchema,
11
+ idpTokenRequestSchema,
7
12
  loginRequestSchema,
13
+ type SocialProvider,
8
14
  signupRequestSchema,
9
15
  tokenPairSchema,
10
16
  type WithdrawRequest,
@@ -73,6 +79,7 @@ import {
73
79
  updateReviewRequestSchema,
74
80
  writableReviewSchema,
75
81
  } from "./schemas/review";
82
+ import { storefrontStoreSchema } from "./schemas/store";
76
83
  import type { StorefrontSession } from "./session";
77
84
 
78
85
  export interface StorefrontClientOptions {
@@ -183,11 +190,26 @@ export function createStorefrontClient(options: StorefrontClientOptions) {
183
190
  refresh: (refreshToken: string) =>
184
191
  http.request("POST", "/auth/token/refresh", tokenPairSchema, { body: { refreshToken } }),
185
192
  logout: () => http.requestVoid("POST", "/auth/logout"),
193
+ /** 소셜 로그인 시작 — 공급자 로그인 주소를 받는다. PKCE·state는 `@sayren/storefront-sdk/auth`의 `idp()`가 만든다 */
194
+ idpAuthorize: (provider: SocialProvider, body: IdpAuthorizeRequest) =>
195
+ http.request("POST", path`/auth/idp/${provider}/authorize`, idpAuthorizeResponseSchema, {
196
+ body: idpAuthorizeRequestSchema.parse(body),
197
+ }),
198
+ /** 소셜 로그인 마무리 — 돌아온 1회용 code + codeVerifier를 구매자 토큰으로 바꾼다 */
199
+ idpToken: (body: IdpTokenRequest) =>
200
+ http.request("POST", "/auth/idp/token", tokenPairSchema, {
201
+ body: idpTokenRequestSchema.parse(body),
202
+ }),
186
203
  /** 구매자 탈퇴 — 로그인 상태(accessToken)에서 비밀번호로 본인 확인. 성공하면 토큰은 모두 무효 */
187
204
  withdraw: (body: WithdrawRequest) =>
188
205
  http.requestVoid("POST", "/auth/withdraw", { body: withdrawRequestSchema.parse(body) }),
189
206
  },
190
207
 
208
+ store: {
209
+ /** 스토어 공개 설정 — 이름·고객센터·비회원 주문 허용 여부 */
210
+ get: () => http.request("GET", "/store", storefrontStoreSchema),
211
+ },
212
+
191
213
  catalog: {
192
214
  listCategories: () => http.request("GET", "/categories", z.array(categoryNodeSchema)),
193
215
  searchProducts: (params?: ProductSearchParams) =>
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export * from "./schemas/inquiry";
15
15
  export * from "./schemas/member";
16
16
  export * from "./schemas/order";
17
17
  export * from "./schemas/review";
18
+ export * from "./schemas/store";
18
19
  export {
19
20
  createStorefrontSession,
20
21
  type SessionStorage,
@@ -61,7 +61,11 @@ export type AnonymousSession = z.infer<typeof anonymousSessionSchema>;
61
61
  * 403 `PASSWORD_MISMATCH`다(로그인과 같은 5회 잠금).
62
62
  */
63
63
  export const withdrawRequestSchema = z.object({
64
- password: z.string().min(1),
64
+ /**
65
+ * 비밀번호로 가입한 구매자는 필수. 소셜 로그인으로만 가입한 구매자는 비밀번호가 없어 생략하고, 대신 최근 10분 안에
66
+ * 로그인한 액세스 토큰이어야 한다(아니면 403 `REAUTH_REQUIRED` — 다시 로그인한 뒤 호출)
67
+ */
68
+ password: z.string().min(1).optional(),
65
69
  });
66
70
 
67
71
  export type WithdrawRequest = z.infer<typeof withdrawRequestSchema>;
@@ -77,3 +81,32 @@ export const withdrawalBlockedDetailsSchema = z.object({
77
81
  });
78
82
 
79
83
  export type WithdrawalBlockedDetails = z.infer<typeof withdrawalBlockedDetailsSchema>;
84
+
85
+ /** 소셜 로그인 공급자 */
86
+ export const socialProviderSchema = z.enum(["kakao", "naver", "google"]);
87
+ export type SocialProvider = z.infer<typeof socialProviderSchema>;
88
+
89
+ /** `POST /auth/idp/{provider}/authorize` 본문 — PKCE(S256)와 state는 SDK가 만든다 */
90
+ export const idpAuthorizeRequestSchema = z.object({
91
+ /** 로그인을 마치고 돌아올 스토어프런트 주소. 스토어프런트 도메인 목록 안이어야 한다 */
92
+ redirectUri: z.url(),
93
+ /** base64url(SHA-256(codeVerifier)) */
94
+ codeChallenge: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
95
+ /** 스토어프런트가 만든 값 — 돌아올 때 그대로 붙어 온다. 세션에 저장해 두고 비교한다 */
96
+ state: z.string().min(16).max(128),
97
+ });
98
+ export type IdpAuthorizeRequest = z.infer<typeof idpAuthorizeRequestSchema>;
99
+
100
+ export const idpAuthorizeResponseSchema = z.object({
101
+ /** 구매자를 보낼 공급자 로그인 주소 */
102
+ url: z.url(),
103
+ });
104
+
105
+ /** `POST /auth/idp/token` 본문 — 돌아온 1회용 code를 구매자 토큰으로 바꾼다 */
106
+ export const idpTokenRequestSchema = z.object({
107
+ code: z.string().min(1),
108
+ codeVerifier: z.string().regex(/^[A-Za-z0-9._~-]{43,128}$/),
109
+ /** 비회원 장바구니 토큰 — 주면 회원 장바구니로 합친다 */
110
+ cartToken: z.string().optional(),
111
+ });
112
+ export type IdpTokenRequest = z.infer<typeof idpTokenRequestSchema>;
@@ -2,7 +2,8 @@ import { z } from "zod";
2
2
 
3
3
  export const memberSchema = z.object({
4
4
  memberId: z.string(),
5
- email: z.email(),
5
+ /** 소셜 가입에서 공급자가 이메일을 주지 않았으면 null */
6
+ email: z.email().nullable(),
6
7
  name: z.string(),
7
8
  phone: z.string().nullable(),
8
9
  point: z.number().int(),
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * 스토어 공개 설정 — `GET /store`. 스토어프런트가 화면을 그릴 때 읽는 값만 담는다.
5
+ */
6
+ export const storefrontStoreSchema = z.object({
7
+ storeCode: z.string(),
8
+ name: z.string(),
9
+ logoUrl: z.string().nullable(),
10
+ customerCenterPhone: z.string().nullable(),
11
+ businessHours: z.string().nullable(),
12
+ /**
13
+ * 비회원 주문 허용. false면 로그인하지 않은 구매자의 주문서 생성·결제 요청이 403 `GUEST_CHECKOUT_DISABLED`다.
14
+ * 장바구니 담기는 된다 — 주문 버튼을 로그인 안내로 바꾸면 된다
15
+ */
16
+ guestCheckout: z.boolean(),
17
+ /**
18
+ * 켜진 로그인 방법 — 화면에 이 순서대로 버튼을 그리면 된다. `password`가 없으면 이메일·비밀번호 가입·로그인이 꺼져 있다
19
+ */
20
+ loginMethods: z.array(z.enum(["password", "kakao", "naver", "google"])),
21
+ });
22
+
23
+ export type StorefrontStore = z.infer<typeof storefrontStoreSchema>;