DPS Store (디플샵 스토어)
개요
DPS Store는 업체(테넌트)별 독립적인 온라인 스토어를 운영할 수 있는 멀티테넌트 팝업 스토어 플랫폼입니다. DPS API와 연동하여 상품·주문을 실시간으로 동기화하며, 테넌트별로 테마·페이지·정책을 자유롭게 커스터마이징할 수 있습니다.
고정형(JSON 레이아웃)과 자유형(노드 기반 비주얼 빌더) 두 가지 방식으로 스토어를 구축합니다. 현재 43개 Prisma 모델 / 129개 API 라우트 규모입니다.
온라인 스토어를 넘어, 팝업 매장 현장에서 운영되는 출력장비(접수증·가먼트·머그 프린터)와 키오스크까지 연동하는 방향으로 최근 크게 확장했습니다. 웹 주문이 현장 장비에서 자동으로 출력·제작으로 이어지는 O2O 운영 파이프라인을 구축했습니다.
주요 기능
멀티테넌트 아키텍처
- 테넌트별 독립적인 테마(로고, 컬러, 폰트), 정책, 페이지, 약관 설정
- 43개 DB 테이블로 테넌트·상품·주문·결제·고객·스탬프·출력큐 데이터 분리 관리
- 4가지 도메인 접근 방식 지원:
| 방식 | 예시 | 설명 |
|---|---|---|
| 경로 기반 | store.dpl.shop/musinsa | 기본 접근 방식 |
| 3차 서브도메인 | musinsa.store.dpl.shop | 와일드카드 DNS + On-Demand TLS |
| 서브도메인 | musinsa.dpl-shop.store | 복수 호스트 지원 |
| 커스텀 도메인 | popup.musinsa.com | 테넌트가 직접 등록 |
- Next.js 16
proxy.ts에서 호스트명 분석 → 서브도메인 추출 → 내부 경로 rewrite - Caddy On-Demand TLS로 서브도메인/커스텀 도메인 자동 SSL 인증서 발급
/api/caddy/check-domain엔드포인트로 도메인 유효성 검증 (DB 조회 기반)
테넌트 타입
- 고정형(FIXED): JSON 기반 레이아웃/스타일 설정 + 관리자 에디터로 페이지 구성
- 자유형(FLEXIBLE): 노드 기반 드래그앤드롭 비주얼 빌더로 자유로운 페이지 구축
- 생성형(GENERATIVE): 타입 선택지와 렌더링 분기만 준비된 확장 슬롯. 현재 렌더링은 자유형과 동일 경로를 사용하며, AI 자동 생성은 도입하지 않았습니다.

관리자 페이지 빌더. 왼쪽에서 탭·색상·다국어 텍스트를 설정하면 오른쪽 미리보기가 모바일·태블릿·키오스크·PC 4개 뷰포트로 즉시 반영됩니다. (테넌트 상호는 모자이크 처리)
노드 기반 비주얼 빌더
- 드래그앤드롭으로 페이지 요소 배치·편집하는 비주얼 에디터
- 트리 구조 노드 시스템으로 중첩 레이아웃 표현
- 노드별 속성 편집 (텍스트, 이미지 업로드, Props, 스타일)
- 다국어(한/영/일/베/중) 텍스트 콘텐츠 편집 지원
- 보안 검증 (허용 요소/CSS 제한) 및 스타일 정제 처리
페이지 시스템
8개 페이지 타입으로 고객 플로우 구성:
| 페이지 | 설명 |
|---|---|
| HERO | 스토어 메인 진입 화면 |
| SIGNUP | 간편 회원가입 (전화번호/이메일) |
| PRODUCT_LIST | 카테고리별 상품 목록 |
| OPTION_SELECT | 옵션 선택 + 외부 에디터 연동 |
| CART | 장바구니 |
| ORDER_FORM | 주문서 작성 |
| ORDER_RESULT | 주문 완료 |
| MY_PAGE | 내 주문 조회 |
페이지별 활성화/비활성화 설정이 가능하며, 비활성화된 페이지는 자동으로 건너뜁니다.


같은 플랫폼 위에서 테넌트마다 다른 테마·페이지 구성으로 열린 실서비스 스토어 두 곳. (테넌트 상호·로고는 모자이크 처리)
상품·주문 관리
- DPS API 연동 상품 동기화 및 실시간 조회
- 트리 구조 옵션 시스템 (옵션 그룹 → 옵션값 → SKU)
- 상품 이미지 라이브러리·상세 페이지 분리, 옵션값별 썸네일
- 장바구니 → 주문서 → 주문 완료 전체 플로우
- DPS 양방향 주문 동기화
- 작업지시서 PDF 생성 + 디자인 파일 일괄 다운로드 (ZIP)
- 주문 항목(OrderItem)별 제작 상태를 주문 상태와 분리 관리하고, 상태 이력을 남겨 주문 상태에 자동 반영
- 배송·수령 완료 주문의 자동 구매확정 (cron)
- 다면 디자인 미리보기: 양면·목뒤·어깨처럼 인쇄 면이 여러 개인 디자인을 정규화 유틸(
parsePreviewImages)과 순환 표시 컴포넌트로 통일 처리. 장바구니·주문서·내 주문·작업자·캐셔·관리자·작업지시서 PDF까지 전 표면에서 인쇄 면 전부를 노출 - 접수 라인 매핑(
TerminalLineMapping): 주문 단말(키오스크)과 가먼트 PC를 라인으로 묶어, 접수한 단말 기준으로 출력 장비를 배정. 주문 목록·상세에 접수 장비명을 표기하고 라인별 탭으로 조회
고객 인증
- 전화번호/이메일 기반 간편 회원가입 + 인증코드(알림톡·이메일) 검증
- 카카오·네이버·구글 소셜 로그인 (자체 OAuth 프로바이더 구현,
CustomerSocialAccount연결) - 관리자/작업자/고객 세션 완전 분리 (NextAuth 4-세션 구조)
- 게스트 세션: 간편 가입 화면조차 쓰지 않는 현장 전용 매장을 위해, 진입 시
guest_{tenantId}_{uuid}식별자로 세션을 자동 발급. Server Action으로 발급하며 페이지 활성 토글과 정책이 양방향 동기화되고, 게스트 식별자가 수령인 연락처 자리로 새지 않도록 노출 지점을 차단
현장 결제 및 캐셔 시스템
- QR 코드 기반 현장 결제 프로세스
- 캐셔 전용 인터페이스 (장바구니 확인 + 결제 확인)
- 작업자(Worker) 전용 페이지로 주문 처리
- Innopay 간편결제(Epay) 연동: 결제 세션(
InnopaySession) 상태 관리, 콜백·실패 라우트, 결제 이력 적재 - 결제 취소 자동화: 주문 취소(단건·일괄) 시 Innopay 결제를 함께 취소하고 캐셔 화면에 가드 적용
고객 이미지 업로드
- 주문 과정에서 고객이 자신의 휴대폰으로 이미지를 올릴 수 있도록 QR 기반 업로드 링크(
/upload/[hash]) 제공 - 업로드 토큰(
CustomerUploadToken)·주문 접근 토큰(OrderAccessToken)으로 접근 범위를 제한 - 업로드된 이미지는 Jarvis 디자인 에디터로 이어져 제작 파일로 사용
출력장비 연동 (프린터 클라이언트)
팝업 매장 현장의 전용 프린터를 웹 주문과 직접 연동했습니다. 관리자가 브라우저에서 수동 출력하던 방식을 넘어, 현장 장비가 서버 큐를 자동 풀링해 직접 출력하는 구조로 확장했습니다.
- 3종 프린터 모듈: 접수증/라벨(감열), 가먼트(Brother GTX-4 DTG), 머그 전사지 프린터를 각각 전용 Windows 클라이언트로 연동
- 출력 큐 시스템: 서버에
ReceiptPrintQueue/GarmentPrintQueue/MugTransferPrintQueue3종 큐를 두고 상태 머신(PENDING → PRINTING/DOWNLOADING → PRINTED/SENT, FAILED/RESOLVE_FAILED)으로 관리 - 자동 풀링 출력: 클라이언트가
/api/printer/{receipt|garment|mug}엔드포인트를 폴링 → 프린터를 직접 제어해 출력 →printed/failed/downloaded콜백으로 상태 반환 - 작업자 게이팅 출력: 가먼트는 다운로드와 전송을 분리해, 작업자가 준비된 건만 장비로 보내는 수동 전송 워크플로우 지원
- 관리자 운영 화면: 출력 큐 통합 조회(상태별 필터·재출력), 장비 설정(라벨기·가먼트·머그·설치형 4개 탭), 장비 인증 승인 화면 제공
장비 클라이언트 (별도 레포 4종)
현장 장비 쪽은 DPS Store 서버와 짝을 이루는 별도 레포로 개발하고, 설계·운영 문서는 dps-store에서 통합 관리합니다.
| 레포 | 장비 | 구현 |
|---|---|---|
equip-sync-l-module | SLK TS200 감열 라벨 프린터 | 접수증 자동 출력. 담당자 수기 기입란, 고객 사본에 주문 상세 QR 렌더링 |
equip-sync-g-module | Brother GTX-4 가먼트 프린터 | GTX4CMD 연동(direct/gtx4cmd 모드), 다중 프린터·플래튼/잉크 파라미터, 작업지시서 A4 출력 병행 |
equip-sync-m-module | 머그 전사지 프린터 | 좌우 반전 출력, A4 가로 2-up 합본 배치(전사지 절약), _qtyN 수량 규칙, 합본 오버레이 |
dps-store-desktop | 현장 PC 설치형 셸 | Electron 기반, 테넌트 고정 접속·부팅 시 프린터 자가복구/워밍업·캐시 무력화 단축키 |
- 3개 장비 모듈은 Python + PyInstaller로 Watcher와 Agent를 단일 EXE로 통합했고, GUI(설정·실시간 큐 대시보드·테마)를 공통 규칙으로 통일했습니다.
- 태그 push 시 GitHub Actions가 자동 빌드·릴리즈하여 현장 배포를 단순화했습니다.
- 폴더 감시(Watcher) 방식과 서버 API 풀링(Agent) 방식을 한 프로그램에서 함께 지원해, 네트워크 상황이나 장비 특성에 따라 선택할 수 있습니다.
장비 인증 (Device Authorization Flow)
전용 프린터 클라이언트는 admin 세션 대신 장비 전용 API 키로 인증합니다. OAuth Device Authorization Flow(흔히 보는 "기기 코드 로그인") 패턴을 차용했습니다.
- 클라이언트가
POST /api/printer/auth/request→deviceCode/userCode/ 인증 URL 발급, 콘솔에 표시 - 관리자가 브라우저에서 인증 URL 접속 → admin 로그인 → 인증 코드 확인 후 승인
- 클라이언트는
POST /api/printer/auth/poll을 2초 간격으로 폴링하다가 승인 시 API 키(pk_...) 수령 - 이후 모든 출력 API 호출에 API 키 사용, 만료 토큰은 Cron으로 정리
키오스크 / 현장 운영
- 키오스크 모드: PWA 기반 키오스크에서 세션 타임아웃 시 자동 로그아웃으로 다음 고객 보호
- 현장 결제·캐셔·작업자 분리: QR 현장 결제 → 캐셔 결제 확인 → 작업자 게이팅 출력으로 이어지는 현장 운영 플로우
- 웹 주문 → 현장 출력 일원화: 온라인/현장 주문이 동일한 출력 큐로 수렴해 장비에서 자동 제작
스탬프 수집 시스템
- 고객 로열티 스탬프 적립 및 관리
- 관리자 스탬프 통계 대시보드 (브랜드·캐릭터별 사용량 집계)
- 브랜드별 스탬프 설정과 테넌트별 스탬프 그룹 매핑
- Jarvis 디자인 에디터의 라이브러리 카테고리 API와 연동해, 주문 디자인에 실제 사용된 스탬프를 역추적 집계
- 한 주문에 여러 디자인이 담긴 경우 식별자를 분해해 개별 조회하고, 브랜드가 매핑되지 않은 스탬프는 "미분류" 행으로 누락 없이 집계
다국어 (i18n)
- 한국어, 영어, 일본어, 베트남어, 중국어 5개 언어 지원
- UI 텍스트, 약관, 상품명, 옵션명까지 전체 다국어 대응
- 테넌트별 지원 언어 및 기본 언어 설정
- MyMemory API 연동 자동 번역 기능
관리자 페이지
- 페이지 에디터 (레이아웃/스타일/콘텐츠 설정)
- 상품·전시·카테고리 관리 (드래그앤드롭 정렬), 이미지 라이브러리
- 주문 관리 (상태·제작 상태, 취소·결제 취소, 작업지시서/디자인 다운로드)
- 고객 관리 및 고객 업로드 이미지 관리
- 테마 설정 (로고, 컬러, 폰트, 공통 스타일)
- 정책 설정 (배송비, 에디터 탭, 세션 타임아웃, 간편 가입 사용 여부 등)
- 약관 관리 (다국어, 4가지 타입)
- 알림 수신자 관리 (알림톡/이메일)
- 언어 설정 (지원 언어, 기본 언어)
- 출력 큐·장비 설정·장비 인증 승인
- 스탬프 통계 대시보드
- 폰트 관리
PWA 지원
- 멀티테넌트 매니페스트 엔드포인트 (테넌트별 홈 화면 설치)
- 서비스 워커 등록 (즉시 활성화:
skipWaiting/clients.claim) - 키오스크 모드 대응 (자동 로그아웃)
기술 스택
| 분류 | 기술 |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| Language | TypeScript 6 (strict mode) |
| UI | React 19, Tailwind CSS 4, lucide-react |
| ORM | Prisma 7 (MariaDB 어댑터) |
| 인증 | NextAuth 5 (관리자·작업자·캐셔·고객 세션 분리) |
| 결제 | Innopay 간편결제(Epay) |
| 스토리지 | Object Storage (S3 호환, AWS SDK) + sharp 이미지 처리 |
| QR | qrcode(생성), html5-qrcode·jsQR(스캔) |
| 인프라 | Caddy (리버스 프록시, On-Demand TLS) |
| jsPDF, html-to-image | |
| 배포 | PM2 (Blue-Green 무중단 배포) |
| PWA | Service Worker, 멀티테넌트 Manifest |
| 장비 클라이언트 | Python + PyInstaller (Watcher/Agent 단일 EXE), Electron (설치형 셸) |
아키텍처
도메인 라우팅 플로우
배포 아키텍처
Blue-Green 무중단 배포 방식을 적용했습니다.
- PM2로 Blue(:4033), Green(:4034) 두 인스턴스를 각각 독립 빌드 디렉토리(
.next-blue,.next-green)로 운영 - 배포 시 비활성 인스턴스에 빌드 → 헬스 체크(30초 타임아웃, 307/308 리다이렉트 허용) → Caddy
ACTIVE_PORT환경변수 전환 →systemctl reload caddy - Cron 프로세스(자동 주문확인)도 배포 시 재등록하여 활성 포트 반영
- 롤백 스크립트로 이전 인스턴스 헬스 체크 후 즉시 트래픽 복구
담당 역할
1인 풀스택 개발로 기획부터 설계, 개발, 배포까지 전체를 담당했습니다. 웹 서버(전체 커밋의 약 96%)와 장비 클라이언트 4개 레포를 함께 개발했습니다.
- 시스템 설계: 멀티테넌트 아키텍처 설계, DB 스키마 설계 (43개 테이블), API 설계 (129개 라우트)
- 프론트엔드: 8개 페이지 타입별 고객 UI, 관리자 에디터, 노드 기반 비주얼 빌더, 5개 언어 다국어 대응
- 백엔드: Next.js API 라우트, DPS API 양방향 동기화, 소셜 로그인·게스트 세션, Innopay 결제·취소 연동, 파일 업로드/다운로드, 스탬프/알림 시스템
- 장비 연동: 접수증·가먼트·머그 3종 출력 큐 설계, 장비 Device Auth(API 키) 플로우, 장비 클라이언트 3종(Python)과 설치형 데스크톱 앱(Electron) 직접 개발. 웹 주문과 현장 출력장비를 잇는 O2O 파이프라인 구축
- 인프라: Caddy 리버스 프록시 설정, PM2 Blue-Green 무중단 배포, Object Storage 연동, PWA 구성, GitHub Actions 장비 EXE 릴리즈 자동화
트러블슈팅
서브도메인 경로 중복 문제
- 상황:
musinsa.store.dpl.shop/signup접속 시 내부 링크가/musinsa/signup으로 생성되어 경로가 중복됨 - 원인: 도메인 판별 로직에서 MAIN_DOMAINS를 하드코딩하여 서브도메인 호스트를 메인 도메인으로 오인
- 해결: 환경변수 기반으로
MAIN_DOMAINS/SUBDOMAIN_HOSTS를 분리하고,useTenantPath훅에서 서브도메인 접속 시 테넌트 prefix를 생략하도록 수정
Caddy On-Demand TLS 인증서 미발급
- 상황: 커스텀 도메인 등록 후 HTTPS 접속 시 인증서 오류 발생
- 원인: Caddy의
on_demand_tls.ask엔드포인트가 비활성 포트를 바라보고 있어 검증 실패 - 해결: Caddy의
{$ACTIVE_PORT}환경변수를 systemdEnvironmentFile로 주입하여 배포 시 자동 반영되도록 구성
회고
멀티테넌트 스토어 플랫폼을 설계부터 배포까지 사실상 전담한 프로젝트입니다. 고정형(JSON 레이아웃)과 자유형(노드 기반 비주얼 빌더)은 페이지를 만드는 방식이 전혀 다른데, 이 둘을 별도 제품으로 가르지 않고 한 시스템 안에서 타입으로만 갈라지도록 잡은 것이 초기 설계의 대부분이었습니다.
도메인 기반 라우팅은 Next.js 16에서 middleware가 proxy로 바뀐 직후라 참고할 사례가 거의 없었고, Host를 파싱해 내부 경로로 rewrite하는 방식을 직접 잡아야 했습니다. Prisma 7의 브라우저 안전 Enum도 이때 처음 썼습니다.
이후 현장 결제(QR·캐셔), 스탬프 로열티, 5개 언어, PWA, Blue-Green 무중단 배포가 차례로 붙었습니다.
이후에는 웹 플랫폼을 넘어 현장 출력장비와 키오스크 연동으로 범위를 크게 넓혔습니다. 접수증·가먼트·머그 프린터를 전용 클라이언트로 연동하고, 출력 큐와 장비 Device Auth(API 키) 플로우를 설계하면서, 단순 온라인 스토어가 아니라 팝업 매장의 주문·제작·출력을 하나로 잇는 O2O 운영 시스템으로 진화시킨 것이 이 프로젝트에서 가장 의미 있는 확장이었습니다.
웹은 TypeScript, 장비 클라이언트는 Python(PyInstaller), 설치형 셸은 Electron으로 서로 다른 세 런타임을 오가며 하나의 출력 파이프라인을 맞추는 경험도 남았습니다. 특히 장비 쪽은 현장에서 사람이 직접 쓰는 프로그램이라, 설정 즉시 반영·프린터 목록 자동 탐지·부팅 시 자가복구처럼 "운영자가 개발자를 부르지 않아도 되는" 장치를 계속 추가하게 됐습니다.
첫 배포 이후 반년 넘게 운영하면서 소셜 로그인·간편결제·결제 취소·게스트 세션처럼 실제 매장 요구에서 출발한 기능이 계속 붙었고, 그때마다 멀티테넌트 정책 토글로 흡수해 기존 테넌트 동작을 깨지 않고 확장하는 방식을 유지했습니다.