← 여행 eSIM 기획서

🛠️ 봉이 여행 eSIM — 개발 명세 (프론트·어드민 샘플)

개발자가 이 문서만으로 착수 가능 · 유심사 API 구조 벤치마크 · Mock→플래그 교체 · 2026-07
유심사 3단 API(products→plans→options) 구조를 표준으로 채택. 공급사(CMLink/eSIM Access)는 services/esimProvider.js 어댑터로 추상화 → env ESIM_PROVIDER=mock|cmlink|esimaccess로 교체.
1 · 아키텍처
// 데이터 흐름 [고객 프론트 client/src/pages/esim/] │ fetch /api/esim/* ▼ [Express routes/esim.js] ──▶ [services/esimProvider.js // 어댑터(Mock→실API)] │ │ ESIM_PROVIDER 플래그로 mock|cmlink|esimaccess 선택 ▼ ▼ [Supabase cs.esim_*] [공급사 API (CMLink/eSIM Access)] product·option·order products/plans/options/order │ ▼ [어드민 docs/esim-admin.html] // 상품 동기화·마진·주문/발급 현황
2 · DB 스키마 (Supabase cs 스키마)
create table cs.esim_products ( id bigserial primary key, provider text, -- 'cmlink'|'esimaccess'|'mock' provider_pid text, -- 공급사 상품ID destination text, -- '일본','유럽 42개국' dest_type text, -- 'single'|'multiple' supplier text, carrier text, -- 'CMI-DW', 'Softbank/KDDI' keyword text, -- 검색용(도쿄;오사카…) is_active boolean default true, synced_at timestamptz ); create table cs.esim_options ( id bigserial primary key, product_id bigint references cs.esim_products, provider_oid text, -- 공급사 옵션ID(주문 시 사용) name text, -- '5일 / 완전 무제한' days int, data_type text, -- 'unlimited'|'fixed' quota_text text, is_unlimited boolean, wholesale_usd numeric, -- 도매(계약 후 채움, 없으면 null) fx_rate numeric, margin_rate numeric, -- 정책 fetch retail_krw int, -- 소매가(계산 or 수동) is_hotspot boolean, free_googlemap boolean, care_amount int -- 유심사 벤치 ); create table cs.esim_orders ( id bigserial primary key, customer_id bigint, option_id bigint references cs.esim_options, retail_krw int, status text, -- 'paid'|'provisioning'|'active'|'failed' provider_oid text, provider_order_id text, qr_url text, install_code text, -- LPA:1$... eSIM 프로파일 ordered_at timestamptz, activated_at timestamptz );
3 · API 명세 (routes/esim.js) — 요청/응답 샘플

GET /api/esim/destinations 목적지 목록

// res 200 { "data": [ { "id":1, "destination":"일본", "dest_type":"single", "from_krw":1500, "keyword":"도쿄;오사카" }, { "id":2, "destination":"유럽 42개국", "dest_type":"multiple", "from_krw":1700 } ] }

GET /api/esim/products/:destId/options 옵션(일수·데이터·가격)

// res 200 { "data": [ { "id":11, "name":"5일 / 완전 무제한", "days":5, "data_type":"unlimited", "retail_krw":16100, "is_hotspot":true, "free_googlemap":true, "care_amount":300000 }, { "id":12, "name":"1일 / 2GB", "days":1, "data_type":"fixed", "retail_krw":2600 } ] }

POST /api/esim/orders 주문·결제 후 발급

// req { "option_id":11, "email":"user@x.com", "payment_token":"..." } // res 200 { "order_id":1001, "status":"active", "qr_url":"https://.../qr/1001.png", "install_code":"LPA:1$smdp..." }
플로우: 결제 성공 → provider.order() → QR/프로파일 저장 → 고객 마이페이지. 실패 시 status=failed + 재발급 큐.
4 · 공급사 어댑터 (services/esimProvider.js) — Mock 샘플
// env ESIM_PROVIDER=mock|cmlink|esimaccess const providers = { mock, cmlink, esimaccess }; export const esim = providers[process.env.ESIM_PROVIDER || 'mock']; // --- mock 어댑터 (계약 전 전 기능 개발용) --- const mock = { async listDestinations() { return [{id:1,destination:'일본',from_krw:1500}]; }, async listOptions(destId) { return [{id:11,name:'5일/무제한',retail_krw:16100}]; }, async order(optionId, customer) { return { order_id: Date.now(), status:'active', qr_url:'https://mock/qr.png', install_code:'LPA:1$mock' }; }, async status(orderId) { return { status:'active' }; } }; // --- cmlink 어댑터 (계약 후: 실 API 호출로 동일 인터페이스 구현) --- const cmlink = { async listOptions(destId){ const r=await fetch(CMLINK_API+..., {headers:{Authorization:key}}); ... } // order/status 동일 시그니처 };
인터페이스 고정(listDestinations·listOptions·order·status) → Mock으로 프론트·어드민 완성 후, 계약되면 cmlink/esimaccess 구현만 채우고 플래그 전환. cti.js와 동일 패턴.
5 · 프론트 (client/src/pages/esim/) — 컴포넌트·샘플

화면 구성 (라우트)

경로컴포넌트API역할
/esimDestinationGridGET /destinations목적지 선택(검색·인기)
/esim/:destIdOptionListGET /products/:id/options일수·데이터 선택
/esim/checkoutCheckoutPOST /ordersPASS·결제
/esim/complete/:orderIdQrInstallGET /orders/:idQR·설치가이드(iOS/Android)
/mypage/esimMyEsimListGET /orders?mine사용현황

샘플 — OptionList.jsx

function OptionList({ destId }) { const [opts, setOpts] = useState([]); useEffect(() => { api.get(`/esim/products/${destId}/options`).then(r=>setOpts(r.data)); }, [destId]); return opts.map(o => ( <OptionCard key={o.id} title={o.name} price={o.retail_krw} badges={[o.free_googlemap&&'구글맵무료', o.care_amount&&'안심케어'].filter(Boolean)} onSelect={() => nav(`/esim/checkout?option=${o.id}`)} /> )); }
유심사 벤치 반영: 옵션 카드에 구글맵무료·안심케어(care_amount)·핫스팟 배지, "로밍 대비 최대 70%↓" 라벨, cs 로밍상담 딥링크(?from=cs).
6 · 어드민 (docs/esim-admin.html) — 구성·샘플

① 상품 관리

// 공급사 상품 동기화 [동기화] → provider.listProducts() → upsert cs.esim_products/options [마진 설정] retail = round( wholesale_usd × fx × (1+margin_rate)) // margin_rate=정책 fetch(하드코딩X)

② 주문·발급 현황

order_id│고객│상품│소매│발급│상태 1001│홍*동│일본5일│16,100│✅QR│active 1002│김*수│유럽5일│19,900│—│paid // 실패 시 [재발급] → provider.order 재시도

어드민 기능 체크리스트

기능설명
상품 동기화공급사 API pull → esim_products/options upsert (수동/cron)
마진율 정책목적지·데이터타입별 margin_rate 설정(하드코딩 금지)
주문/발급 모니터status별 필터·재발급·환불(refund)
정산 연동기존 페이백/포인트 트리거 재사용, 티켓 prefix E
7 · 유심사 벤치마크 (반영 포인트)
유심사 요소봉이 반영
API 3단(products→plans→options)동일 채택(routes/esim.js)
목적지 68(multiple 19 지역묶음·single 49)dest_type 컬럼으로 구분·검색
usimsaCare(30만원 보상)care_amount 컬럼 → 안심케어 배지·부가상품화
구글맵 데이터 무료(isFreeGoogleMap)free_googlemap 배지 노출
무제한(데일리)+정량 210옵션data_type(unlimited/fixed)·일수×데이터 매트릭스
핫스팟·음성·QoS 필드is_hotspot 등 옵션 속성 노출
봉이 차별화(유심사 대비): 통신 상담 고객 크로스셀(cs 무인센터 로밍상담 → eSIM 딥링크) + 기존 페이백/포인트 결합.