Outfit Fashion Store
AI 가상 피팅 이커머스 플랫폼Outfit Fashion Store
AI 가상 피팅과 스타일 공유 이커머스 플랫폼
- Next.js 15
- NestJS
- TypeORM
- PostgreSQL
- Toss Payments
- OAuth 2.0
-
ProjectOutfit Fashion Store
-
Period2025 (진행중)
-
TypeFullstack E-commerce Platform
-
Role풀스택 개발 (Frontend + Backend)
- 프로젝트 유형
- Fullstack / 여성 패션 이커머스 플랫폼
- Frontend
- Next.js 15, TypeScript, Tailwind CSS, Radix UI, React Hook Form
- Backend
- NestJS, TypeORM, PostgreSQL, Passport.js, JWT
- 결제 시스템
- Toss Payments SDK 연동 (신용카드, 무통장입금)
- 인증 시스템
- OAuth 2.0 SSO (Google, Naver, Kakao) + JWT + httpOnly Cookie
- 핵심 성과
- 15개 엔티티, 41개 페이지, 실결제 연동 풀스택 플랫폼 구축
Project
Overview
"온라인에서 옷을 사면 나한테 어울릴지 알 수 없다" — 이 문제는 패션 이커머스의 가장 큰 구매 장벽입니다.
이 불편함에서 출발해, AI 가상 피팅으로 구매 전 시착 경험을 제공하고, 구매 후에는 Outfitgram에서 자신의 코디를 공유하는 쇼핑-피팅-커뮤니티 순환 구조를 설계했습니다.
소셜 로그인, 실결제, SNS까지 실서비스 수준의 풀스택 시스템을 직접 구축하며, "진짜 사용자가 쓸 수 있는 서비스"를 목표로 개발했습니다.
Key
Features
• AI 피팅룸 — 구매 전 "나한테 어울릴까?"를 해소하기 위해, 사용자 사진에 의류를 합성하는 가상 시착 기능을 구현했습니다.
• SSO 소셜 로그인 — 가입 허들을 낮추기 위해 Google, Naver, Kakao 3종 OAuth 2.0을 지원합니다.
• JWT 인증 + Toss Payments — 결제가 있는 서비스이기에, httpOnly 쿠키 기반 인증과 실결제(신용카드·무통장) 연동으로 보안과 신뢰를 확보했습니다.
• Outfitgram — "입어본 옷"을 공유하고 다른 사용자의 코디를 참고하는 SNS로, 쇼핑 → 피팅 → 공유의 순환을 만듭니다.
• 커뮤니티 — 공지·이벤트·Q&A·리뷰 게시판으로 고객 소통 채널을 통합했습니다.
Challenge 1 | JWT 토큰 보안 강화
상황: 결제 정보를 다루는 이커머스 특성상, 일반 웹앱보다 높은 수준의 인증 보안이 요구되었습니다. 초기에는 localStorage에 JWT를 저장했지만, XSS 공격 시 토큰이 그대로 노출되는 구조적 취약점이 있었습니다.
판단: "결제가 있는 서비스에서 토큰 탈취는 곧 금전적 피해"라는 판단 하에, 클라이언트에서 토큰에 접근할 수 없는 저장 방식으로 전환이 필요했습니다. 메모리 저장 방식도 검토했지만 새로고침 시 로그아웃되는 UX 문제가 있어 httpOnly 쿠키를 선택했습니다.
액션: Access/Refresh Token 모두 httpOnly 쿠키로 이전하고, secure: true와 sameSite: 'strict' 옵션을 적용해 CSRF를 차단했습니다. 추가로 Token Rotation을 구현해, Refresh 시마다 새 토큰 쌍을 발급하도록 했습니다.
결과: JavaScript에서 토큰 접근이 불가능해져 XSS 탈취 경로를 원천 차단했고, Token Rotation으로 토큰 유출 시에도 피해 범위를 최소화하는 방어 체계를 구축했습니다.
Challenge 2 | OAuth 콜백 처리 통합
상황: Google, Naver, Kakao 3개 소셜 로그인을 각각 구현하다 보니, 콜백 URL 처리·토큰 교환·사용자 프로필 매핑 로직이 provider마다 별도 파일로 존재했습니다. 새 provider 추가나 공통 로직 수정 시 3곳을 모두 수정해야 하는 유지보수 부담이 커지고 있었습니다.
판단: 각 provider의 OAuth 흐름은 "인가 코드 → 토큰 교환 → 프로필 조회 → 회원 매핑"이라는 동일한 구조를 따릅니다. 차이점은 API 엔드포인트와 프로필 응답 형식뿐이므로, 공통 흐름을 추상화하고 차이점만 분리하면 해결할 수 있다고 판단했습니다.
액션: 프론트엔드에서는 Next.js 동적 라우트 [provider]로 콜백 페이지를 단일화하고, 백엔드에서는 Passport 전략 패턴을 적용해 provider별 설정만 주입하는 구조로 리팩토링했습니다.
결과: 콜백 관련 코드가 3벌에서 1벌로 줄었고, 새 provider 추가 시 전략 설정 파일 하나만 추가하면 되는 확장 가능한 구조가 되었습니다.
Challenge 3 | Toss Payments 결제 금액 불일치
상황: Toss Payments 실결제 테스트에서 "결제 금액 불일치" 오류가 간헐적으로 발생했습니다. 프론트엔드가 보낸 금액과 백엔드가 계산한 금액이 일치하지 않아 결제 승인이 거부되는 현상이었습니다.
판단: 금액 자체가 다를 리 없는 상황에서 "비교가 실패"한다면 타입 불일치를 의심해야 했습니다. 결제 로직에 상세 로그를 추가해 양쪽 값의 타입까지 기록하기로 했습니다.
액션: 디버깅 결과, PostgreSQL의 DECIMAL 컬럼이 문자열 "15000"으로 반환되어 숫자 15000과 === 비교가 실패하는 것이 원인이었습니다. Number() 변환을 적용하고, 프론트엔드와 백엔드의 금액 계산 로직을 동일한 함수로 통일했습니다.
결과: 결제 승인 오류가 완전히 해소되었고, 추가한 상세 로깅 덕분에 이후 결제 관련 이슈 발생 시에도 원인 파악 시간을 크게 단축할 수 있게 되었습니다.
What I Learned | 배운 점
• 보안은 기능이 아니라 기본값이다 — 결제가 있는 서비스를 만들며, 인증 토큰 저장 방식 하나가 사용자의 금전적 피해로 이어질 수 있음을 체감했습니다. "일단 동작하게" 이후 반드시 "안전하게"가 따라와야 합니다.
• 추상화의 타이밍 — OAuth 3사 통합 과정에서, 중복 코드를 "언제 묶을 것인가"의 판단이 중요함을 배웠습니다. 너무 이르면 과설계, 너무 늦으면 레거시가 됩니다.
• 타입은 계약이다 — DECIMAL→string 이슈에서, DB 타입과 애플리케이션 타입 간 변환을 명시적으로 관리하지 않으면 "정상인데 실패하는" 버그가 생긴다는 교훈을 얻었습니다.
• 풀스택의 가치 — 프론트엔드부터 결제·인증·DB까지 직접 구축하며, 한쪽에서 내린 결정이 다른 쪽에 미치는 영향을 입체적으로 이해하게 되었습니다.