모여바 — USJ 티켓·e-SIM 여행 바우처 셀프 관리 웹
발권·예약·방문일 변경을 수기로 감당하던 여행 상품을 무회원 셀프서비스 구조로 옮겨 요구사항부터 출시·운영까지 수행
역할 Frontend 개발 · FSD 아키텍처 · API 계층 설계
Key Result
회원가입 없는 인증코드 로그인부터 다단계 예약·방문일 변경까지 셀프서비스로 구현해 수기 CS 처리 부담 축소
핵심 요약
네이버 스마트스토어에서 USJ 티켓·익스프레스 패스·닌텐도 파크·e-SIM 등을 구매한 고객이, 회원가입 없이 알림톡 인증코드로 로그인해 바우처를 수령·예약·활성화하고 방문일까지 직접 바꾸는 모바일 웹입니다. 수기 CS로 감당하던 구매 후 처리를 셀프서비스로 옮기고, 서로 다른 상품 5종을 같은 아키텍처 패턴으로 확장할 수 있게 설계했습니다.
프로젝트 배경
스마트스토어에서 여행 상품을 파는 동안 발권·예약·방문일 변경·e-SIM 활성화 같은 구매 후 처리를 전부 수기 CS로 감당해야 했습니다. 회원가입 없이 구매자가 스스로 바우처를 수령·관리·변경하는 창구가 필요했고, 요구사항 정의부터 출시·운영까지 프론트엔드를 맡아 수행했습니다. Next.js 15 App Router 위에 Feature-Sliced Design을 얹어, 상품 5종을 같은 패턴으로 확장할 수 있게 했습니다.
담당 범위
- 인증코드 로그인 — 알림톡 6자리 코드 기반 무회원 로그인
- 주문 목록·상세 — USJ 입장권/익스프레스 패스/닌텐도/JTR/e-SIM 5종
- USJ 입장권 방문일 변경 — 티켓 선택 → 재고·차액 캘린더 → 환불·입금정보 → 약관동의 → 확정 다단계 플로우
- e-SIM 활성화 — QR 발급, 사용량·잔량 표시, 기기별 설치 가이드
- 공통 API 계층·인증·관측성(에러 로깅·analytics)
문제 정의
- 비회원 셀프서비스의 인증 공백 — 스마트스토어 구매자는 우리 서비스 계정이 없습니다. 회원가입 없이도 "이 주문의 주인"임을 증명하고 라우트를 보호할 방법이 필요했습니다.
- 상품 5종의 확장 비용 — 입장권·패스·닌텐도·JTR·e-SIM은 데이터와 화면이 조금씩 다릅니다. 상품마다 별도 구조로 짜면 6번째 상품이 추가될 때마다 비용이 선형으로 늘어납니다.
- 모바일 실사용 환경 — 카카오·네이버 인앱 브라우저에서 결제·인증이 깨지고, 오류가 나도 사용자 화면 밖에서는 무슨 일이 일어났는지 알 수 없었습니다.
기술적 의사결정
1. Feature-Sliced Design으로 상품 5종을 같은 패턴으로 확장
- 배경 — 상품마다 데이터·화면이 달라, 공통과 차이를 구분하지 않으면 코드가 상품 수만큼 갈라집니다.
- 선택 — app / widgets / features / entities / shared 5계층으로 책임을 갈랐습니다. entities는 순수 API·타입·상수, features는 도메인 단위 UI·쿼리·모델, widgets는 이를 조합한 뷰로 두어 상품 5종을 같은 골격 위에 얹었습니다.
- 트레이드오프 — 소규모 서비스에 FSD는 초기 보일러플레이트가 과합니다. 그럼에도 상품 추가가 예정돼 있어, 초기 구조 비용을 내고 확장 비용을 낮추는 쪽을 택했습니다.
2. 인터셉터 기반 Fetcher 계층 · 서버/클라이언트 이원화
- 배경 — JWT 주입·401 처리·에러 정규화가 기능 코드 곳곳에 흩어지면, 인증 규칙 하나를 바꿔도 여러 파일을 건드려야 합니다.
- 선택 — BaseFetcher에 요청·응답 인터셉터 파이프라인을 두고 서버·클라이언트 Fetcher로 나눴습니다. 요청 인터셉터가 JWT를 자동 주입하고, 응답 인터셉터가 401에서 자동 로그아웃·쿠키 정리를 처리합니다.
- 보완 — fetch가 4xx/5xx에 reject하지 않아 mutation 성공 콜백이 잘못 호출되는 문제를, 명시적 에러 throw로 보정해 실패를 실패로 다루게 했습니다.
3. 인증코드 기반 무회원 로그인
- 배경 — 회원 개념이 없는 구매자를 인증하고 라우트를 보호해야 했습니다.
- 선택 — 알림톡으로 받은 코드를 검증해 accessToken을 발급하고, NextAuth v5 Credentials + JWT 세션에 담아 미들웨어에서 라우트를 가드했습니다. 미인증이면 callbackUrl을 붙여 로그인으로 보내고, 인증 상태에서 로그인 페이지에 접근하면 홈으로 되돌립니다.
- 트레이드오프 — 정식 회원 체계 대신 인증코드 세션을 택해 진입 장벽을 없앴습니다. 대신 세션 수명·재인증 흐름을 짧게 잡아, 편의와 보안 사이의 균형을 코드 유효기간으로 조정했습니다.
4. 모바일 실사용 환경 대응 + 관측성
- 배경 — 인앱 브라우저에서 인증·결제가 깨지고, 화면 밖 오류는 추적할 수단이 없었습니다.
- 선택 — 카카오 인앱 브라우저를 감지해 외부 브라우저로 자동 이탈시키고(타임아웃·취소·재시도·URL 복사 fallback), 전역 에러는 Slack Webhook으로 프록시 전송했습니다. GA4 기반 커스텀 analytics 계층으로 클릭·유저플로우·페이지를 추적해, 셀프서비스가 실제로 완주되는지 관측했습니다.
결과
- 상품 5종을 같은 패턴으로 확장 가능한 프론트 아키텍처 확보 (6번째 상품 추가 비용 절감)
- 회원가입 없이 인증코드만으로 로그인·라우트 가드 동작
- 방문일 변경·발권·e-SIM 활성화까지 셀프서비스로 처리해 수기 CS 부담 축소
- 인앱 브라우저 이탈 처리·Slack 에러 로깅·GA4로 실사용 관측성 확보
회고
"작은 서비스에 FSD는 과하지 않은가"를 스스로 가장 많이 물었던 프로젝트입니다. 결론은 미래에 늘어날 것이 분명한 축(상품 종류)을 기준으로만 구조 비용을 낸다였습니다. 상품은 확실히 늘어날 예정이라 계층을 나눴고, 반대로 확실치 않은 부분은 얇게 두었습니다. 또 인증에서 "회원가입을 없앤다"는 요구를 세션 설계로 풀면서, 편의와 보안이 대립할 때 코드 유효기간·재인증 주기 같은 조절 가능한 파라미터로 균형점을 옮기는 접근을 익혔습니다.