Storefront SDK
구매자 API 클라이언트 — createStorefrontClient 사용법
@shop24/storefront-sdk는 스토어프론트 API의 zod 스키마와
fetch 클라이언트를 제공합니다. 표준 응답 엔벨로프($.data)를 자동으로 언랩하고,
모든 응답을 zod 스키마로 파싱합니다.
npm install @shop24/storefront-sdk클라이언트 생성
import { createStorefrontClient } from "@shop24/storefront-sdk";
const sdk = createStorefrontClient({
baseUrl: "https://shop.api.avarlabs.com/storefront/v1",
/** 테넌트 지정 → X-Store-Code 헤더. 생략하면 서버가 요청 Host의 서브도메인으로 해석 */
storeCode: "demo",
/** 비회원 장바구니 토큰 → X-Cart-Token 헤더 */
cartToken: () => localStorage.getItem("cartToken"),
/** 서버가 새 X-Cart-Token을 발급했을 때 호출 */
onCartToken: (token) => localStorage.setItem("cartToken", token),
});옵션 정리 (StorefrontClientOptions):
| 옵션 | 타입 | 설명 |
|---|---|---|
baseUrl | string | API 베이스 URL (필수) |
storeCode | TokenSource | 테넌트 코드 → X-Store-Code 헤더. 생략 시 서버가 Host 서브도메인으로 테넌트를 해석 |
accessToken | TokenSource | Authorization: Bearer 헤더 |
auth | { accessToken?: string } | accessToken의 별칭 (한쪽만 쓰면 됩니다) |
session | StorefrontSession | 세션을 넘기면 Bearer 부착·401 refresh·인증 만료 정리가 자동 배선됩니다 |
cartToken | TokenSource | 비회원 카트 토큰 → X-Cart-Token 헤더 |
onCartToken | (token: string) => void | 응답의 X-Cart-Token 헤더 수신 콜백 |
fetch | typeof fetch | 커스텀 fetch (로깅 등) |
TokenSource는 string | null | undefined | (() => string | null | undefined)입니다.
함수로 넘기면 매 요청마다 재평가됩니다.
Access Token 소스는 여러 옵션을 동시에 줄 경우
accessToken → auth.accessToken → session.accessToken 순으로 우선합니다.
인증 — 세션
createStorefrontSession은 로그인/회원가입, 토큰 저장, 자동 갱신을 처리합니다.
구매자는 OAuth authorize 화면 없이 login() / signup()으로 바로 토큰 페어를 받습니다.
토큰을 어디에 저장할지는 앱이 SessionStorage 어댑터로 결정합니다 (get / set / remove).
import {
createStorefrontSession,
createStorefrontClient,
} from "@shop24/storefront-sdk";
const session = createStorefrontSession({
baseUrl: "https://shop.api.avarlabs.com/storefront/v1",
storeCode: "demo",
storage: {
get: (key) => localStorage.getItem(key),
set: (key, value) => localStorage.setItem(key, value),
remove: (key) => localStorage.removeItem(key),
},
});
await session.login({ email: "buyer@example.com", password: "buyer1234!" });
const me = await session.userinfo();
// 세션을 넘기면 클라이언트가 Bearer·401 refresh·만료 정리를 자동 처리한다
const sdk = createStorefrontClient({
baseUrl: "https://shop.api.avarlabs.com/storefront/v1",
storeCode: "demo",
session,
});StorefrontSession:
| 멤버 | 설명 |
|---|---|
login(LoginRequest) | 로그인 후 토큰 페어를 저장 |
signup(SignupRequest) | 회원가입 후 토큰 페어를 저장 |
refresh() | Access Token 갱신, 성공 여부(boolean) 반환. 동시 호출은 single-flight로 한 번만 나갑니다 |
accessToken / refreshToken | 현재 저장된 토큰 게터 |
userinfo() | 회원 정보(Member) 또는 null |
clear() | 저장 키 제거 + best-effort 로그아웃 |
subscribe(cb) | 인증 상태 변경 구독, 해제 함수 반환 |
session을 클라이언트에 연결하면 401 응답 시 자동으로 refresh()를 1회 시도하고,
갱신에 실패하면 세션을 정리합니다. 별도의 재시도 래퍼를 만들 필요가 없습니다.
session을 클라이언트에 넘긴 경우 sdk.accessToken / sdk.refreshToken 게터와
sdk.userinfo()로 세션에 바로 접근할 수 있습니다.
세션 없이 직접 호출
세션을 쓰지 않고 sdk.auth를 직접 호출할 수도 있습니다. 이때 토큰 저장·갱신은 앱이 관리합니다.
const tokens = await sdk.auth.login({
email: "buyer@example.com",
password: "buyer1234!",
});
// { accessToken, refreshToken, expiresIn } — expiresIn은 서버가 반환하는 만료 시간(초)
// Refresh는 rotation 방식 — 갱신하면 이전 refreshToken은 무효화된다
const rotated = await sdk.auth.refresh(tokens.refreshToken);
await sdk.auth.logout();signup은 약관 동의와 함께 회원가입 후 로그인과 동일한 토큰 페어를 반환합니다.
const tokens = await sdk.auth.signup({
email: "buyer@example.com",
password: "buyer1234!",
name: "홍길동",
phone: "01012345678",
agreements: { terms: true, privacy: true, marketing: false },
});카탈로그 — 인증 불필요
// 카테고리 트리
const categories = await sdk.catalog.listCategories();
// 상품 검색 (페이지네이션 + 필터 + 정렬)
const page = await sdk.catalog.searchProducts({
keyword: "티셔츠",
page: 1,
size: 20,
sort: "priceAsc",
});
console.log(page.totalElements, page.contents[0].name);
// 상품 상세
const product = await sdk.catalog.getProduct("prod_001");
// 리뷰 (평점/사진/정렬 필터)
const reviews = await sdk.catalog.listProductReviews("prod_001", { sort: "helpful" });
// 상품 문의 조회 / 작성
const inquiries = await sdk.catalog.listProductInquiries("prod_001", { page: 1 });
await sdk.catalog.createInquiry("prod_001", {
content: "언제 발송되나요?",
secret: true, // 비밀글 (기본 false)
});장바구니 — 비회원 토큰 자동 처리
비회원이 처음 장바구니에 담으면 서버가 X-Cart-Token을 발급하고,
SDK가 onCartToken 콜백으로 전달합니다. 이후 요청에는 저장된 토큰이 자동으로 실립니다.
// 담기 — 첫 호출 시 onCartToken이 호출되어 토큰이 저장된다
const cart = await sdk.cart.addItem({
productId: "prod_001",
optionId: "opt_001",
quantity: 2,
});
// 조회/수정/삭제
await sdk.cart.get();
await sdk.cart.updateItem(cart.items[0].cartItemId, { quantity: 3 });
await sdk.cart.removeItem(cart.items[0].cartItemId);체크아웃
체크아웃 세션 생성 → 결제 요청 → 결제 승인 3단계입니다.
// 1. 장바구니 항목으로 체크아웃 세션 생성
const checkout = await sdk.checkout.create({ cartItemIds: [cart.items[0].cartItemId] });
// 2. 배송지·결제수단으로 결제 파라미터 요청
const pay = await sdk.checkout.requestPayment(checkout.checkoutId, {
shippingAddress,
paymentMethod: "CARD",
});
// 3. PG 인증 후 받은 토큰으로 결제 승인
const result = await sdk.checkout.confirmPayment(pay.paymentId, pgToken);주문/마이페이지
// 내 주문 목록 (상태 필터) / 단건 조회
const orders = await sdk.myOrders.list({ status: "DELIVERED" });
const order = await sdk.myOrders.get("order_001");
// 비회원 주문 조회
const guest = await sdk.myOrders.getGuestOrder("order_001", {
phone: "01012345678",
orderPassword: "1234",
});
// 배송 조회 / 구매 확정
await sdk.myOrders.getDelivery("oi_001");
await sdk.myOrders.decidePurchase("oi_001");
// 클레임 (취소/반품/교환)
await sdk.myClaims.create("oi_001", {
type: "RETURN",
reason: "CHANGE_OF_MIND",
reasonDetail: "사이즈가 맞지 않아요",
});
const claims = await sdk.myClaims.list({ status: "REQUESTED" });
await sdk.myClaims.withdraw("claim_001");회원 · 리뷰 · 문의
// 회원 정보 / 수정
const me = await sdk.member.me();
await sdk.member.updateMe({ name: "홍길동" });
// 배송지
const addresses = await sdk.member.listAddresses();
await sdk.member.createAddress(address);
// 찜
const wishlist = await sdk.member.listWishlist({ page: 1 });
await sdk.member.addWishlist("prod_001");
await sdk.member.removeWishlist("prod_001");
// 작성 가능한 리뷰 조회 / 작성
const writable = await sdk.reviews.listWritable();
await sdk.reviews.create("oi_001", { rating: 5, content: "배송도 빠르고 품질도 좋았습니다" });
// 내 문의
const myInquiries = await sdk.inquiries.listMine({ answered: false });에러 처리 — ApiError
HTTP 에러는 항상 ApiError로 던져집니다. 표준 엔벨로프의 $.error(code/message/traceId)가 매핑됩니다.
import { ApiError } from "@shop24/storefront-sdk";
try {
await sdk.cart.addItem({ productId: "prod_x", quantity: 1 });
} catch (error) {
if (error instanceof ApiError) {
// error.status: HTTP 상태 코드
// error.code: 도메인 에러 코드 (예: OUT_OF_STOCK, UNAUTHORIZED)
// error.traceId: 서버 로그 추적용
if (error.code === "OUT_OF_STOCK") showToast("재고가 부족합니다");
}
}