Estimator (Cafe24 인쇄 견적 앱)
개요
책자·봉투·포스터·리플렛 같은 인쇄 상품은 일반 쇼핑몰의 옵션 구조로 표현하기 어렵습니다. 규격·용지·인쇄도수·후가공이 서로 의존하고, 가격은 수량 구간·판형·후가공 조합에 따라 다층으로 계산되며, 내지·표지 원본 파일은 수백 MB를 넘기기 일쑤입니다.
이 앱은 그 영역만 Cafe24 외부에서 처리합니다. 결제·회원·주문·배송·세금 같은 표준 흐름은 Cafe24 기본 기능을 그대로 쓰고, 옵션 트리·가격 계산·견적 UI·원본 파일 보관·운영자 도구만 자체 구현한 뒤, 견적 결과를 1회성 동적 상품으로 만들어 Cafe24 장바구니에 밀어 넣는 구조입니다.
특정 쇼핑몰 전용이 아니라 앱을 설치한 어떤 쇼핑몰에서도 동작하도록 Shop(업체) 단위 멀티테넌시로 설계했습니다.
아키텍처
Display / Purchase 이중 상품 구조
핵심 아이디어는 "보여주는 상품"과 "사는 상품"을 분리한 것입니다.
- Cafe24에는 옵션 없는 display 상품만 등록합니다. 복잡한 옵션 UI와 가격 계산은 상품 상세 페이지에 주입된 스크립트가 오버레이로 렌더합니다.
- 구매 클릭 시점에 서버가 견적 결과·상품 유형·의뢰 여부에 맞춰 Cafe24 options와 입력옵션을 동적으로 구성한 purchase 상품을 Admin API로 생성해 장바구니에 담습니다.
- 미결제로 이탈한 purchase 상품은 TTL이 지나면 정리 배치가 Cafe24에서 삭제합니다. 앱 DB에는 옵션·파일 메타가 남아 재주문 시 이전 선택을 복원합니다.
해시 세션
견적 도중의 화면 상태(선택 옵션·업로드 파일·편집 번호)는 (shopId, customerId, productId) 3축으로 만든 해시 키에 묶여 DB에 저장됩니다. URL은 해시만 들고 다니므로 새로고침·재진입에도 상태가 복원되고, 같은 데이터가 앱 DB에 구조화된 원본으로도 남아 재주문·운영자 조회에 쓰입니다.
임베드 스크립트
Cafe24 쇼핑몰 화면에 붙는 6개 슬롯을 esbuild로 각각 번들해 ScriptTag로 주입합니다.
| 슬롯 | 역할 |
|---|---|
| PRODUCT_DETAIL | 5섹션 견적 렌더러 (옵션·파일·표지·수량·요약) |
| CART | 입력옵션 파싱 → 사양·썸네일 렌더, 옵션 변경 재생성 |
| MAIN | 최근 주문 + 템플릿 탭 위젯 |
| MYPAGE_MAIN | 내 디자인 목록, 후불 미수금 섹션 |
| MYPAGE_ORDER_LIST | 분할배송·원본 건수·후불 배지 포함 주문 목록 |
| MYPAGE_ORDER_DETAIL | 배송지별 송장, 원본 파일 재서명 다운로드 |
초기 설계의 해시 파일명 + SRI integrity 방식은 배포마다 ScriptTag가 끊기는 문제로 폐기하고, 고정 파일명 방식으로 전환했습니다.
주요 기능
복합 옵션·가격 엔진
- 후가공 계층 조건, 2축 합성 select, 용지 스펙(PaperSpec), 파생 사양 자동 계산을 포함한 옵션 트리
- 6종 규칙 엔진(가시성·활성화·검증 등)과 16종 단가 테이블, 옵셋 2트랙 가격 체계
- 관리자 화면에서 옵션·단가·규칙을 직접 편집하고, 저장 전 가격 미리보기로 검증
- 옵션 메타 조립기를 위젯 API와 관리자 미리보기가 공유해 두 화면의 계산 결과가 어긋나지 않도록 구성
주문 운영 표면
운영자가 Cafe24 어드민을 오가지 않고 이 앱에서 주문 업무를 끝내도록, Cafe24 주문 API를 앱 UI로 감쌌습니다.
- 취소(접수·철회·요청 거부), 환불, 반품, 교환
- 송장 추가·수정·삭제·배치 등록, 묶음배송, 수거
- 주문 메모(중요·상단고정·상담분류), 결품 취소
- 상태별 탭 9종 + 일괄 액션 7종, 주문 상세의 견적·생산 탭(사양 전개·원본 재서명 다운로드·표지 디자인·배송지)
- 스냅샷이 없는 과거 주문은 Cafe24 입력옵션 원문을 역파싱해 동일하게 표시
분할 배송·퀵
- 한 주문을 여러 배송지로 나눠 발송, 배송지별 배송수단(택배·퀵·방문수령) 선택
- 상품 크기·무게 기반 박스 분할 계산
- 부릉(dver) API로 퀵 배송 실시간 운임 조회, 카카오 지오코딩으로 주소 좌표화
월결제 후불(여신)
운영자가 수기로 관리하던 외상·월말 정산을 시스템화했습니다.
- 후불 화이트리스트 회원이 무통장으로 주문하면 앱이 즉시 입금 완료 처리하고 미수금으로 적재
- 결제 예정일에 회원별 미수금을 합산해 결제용 상품을 자동 배치 발행하고 해당 회원에게만 결제 URL 제공
- LMS 통지, 연체 자동 마킹, 마이페이지 미수금 섹션, 회계 처리 방식 토글
디자인 연동
- 자사 편집기 Jarvis를 iframe으로 로드해 표지 디자인 작업, 결과물을 상품 파일 자산으로 연결
- display 상품별로 Jarvis 템플릿 카테고리와 레거시 이미지 샘플 풀을 매핑
- 4티어 디자인 의뢰(커미션) 접수·운영 화면, 전용 디자인의 공용 풀 전환
대용량 파일 저장소
- 내지·표지 원본(수백 MB~수 GB)을 S3 서명 URL로 브라우저에서 직접 업로드해 Cafe24 업로드 한계를 우회
- 업체별 프리픽스 격리, 운영자 저장소 화면에서 조회·다운로드·정리
- 수동 삭제는 휴지통 유예(기본 7일) 후 cron이 자동 정리
MES 게이트웨이
자사 MES가 붙는 경우를 위해 주문 큐·생산지시 상세·생산 상태·송장 등록/수정을 외부 게이트웨이 API로 노출합니다. 전용 토큰 인증과 멱등 처리를 적용했고, MES 없이 앱 단독으로 운영해도 같은 기능을 UI에서 그대로 제공합니다.
저장 견적
견적을 저장·복원할 수 있고, 견적번호(QT-{MALLID}-00001) 발급과 견적서 인쇄/PDF 출력을 지원합니다.
기술 스택
| 분류 | 기술 |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript (strict mode) |
| UI | React 19, Tailwind CSS 4, sonner |
| ORM | Prisma 7 (PostgreSQL 어댑터) |
| 인증 | NextAuth 5 + Jarvis SSO, Cafe24 OAuth (mall_id 단위) |
| 임베드 번들 | esbuild (슬롯별 IIFE 번들 → ScriptTag 주입) |
| 스토리지 | S3 (서명 URL 직접 업로드) |
| 배치 | node-cron, GitHub Actions (purchase TTL 정리) |
| 검증 | tsx 테스트 러너 (typecheck → lint → test → build 일괄) |
| 배포 | PM2 + 배포 스크립트 |
규모
| 항목 | 값 |
|---|---|
| 커밋 / 머지된 PR | 1,472 / 357 |
| Prisma 모델 | 94 |
| 관리자 화면 | 15종 (상품·옵션·단가·주문·배송·정산 등) |
| 페이지 / API 라우트 | 69 / 42 |
| 쇼핑몰 임베드 슬롯 | 6종 전부 구현 |
| Cafe24 주문 API 래핑 | 94개 엔드포인트 중 43개 |
담당 역할
3인 개발 중 전체 커밋의 약 85%를 담당한 주 개발자로, 제품 설계부터 구현·배포까지 주도했습니다.
- 제품·아키텍처 설계: Cafe24 앱 방식 확정, display/purchase 이중 상품 구조와 해시 세션 설계, 멀티테넌시 모델링
- 옵션·가격 엔진: 규칙 엔진, 단가 테이블, 파생 사양 계산, 관리자 편집·미리보기
- Cafe24 연동: OAuth 설치·토큰 자동 갱신, 앱 수명주기 Webhook, 주문 Webhook 멱등 처리, Admin/Front API 래핑
- 임베드 스크립트: 6개 슬롯 번들 파이프라인과 배포 방식 설계
- 운영 기능: 주문 운영 표면, 분할배송·퀵, 후불(여신), 저장소 관리, MES 게이트웨이
범위 경계
문서보다 코드를 기준으로 현황을 고정해 인수인계 문서를 남겼습니다. 아래는 의도적으로 남겨둔 범위입니다.
- 일반 타입 상품 렌더러: 설계만 존재하고 카탈로그 데이터가 없어 미구현
- 퀵 실제 배차: 운임 조회까지만 구현, 배차 API는 미연동
- 라우팅 provider: 거리 계산 모듈은 stub 상태이며 provider 미확정
- Prisma 마이그레이션 베이스라인: 스키마를
db push로만 반영해 마이그레이션 이력 없음
회고
가장 오래 고민한 부분은 "어디까지 직접 만들고 어디부터 플랫폼에 맡길 것인가"였습니다. 결제·회원·주문처럼 검증된 영역을 다시 만들면 인증·보안·세금 처리까지 전부 떠안게 되므로, Cafe24가 표현하지 못하는 옵션·가격·파일 영역만 외부로 떼어내고 나머지는 표준 흐름에 태우는 선택을 했습니다. display/purchase 이중 상품과 해시 세션은 그 경계를 실제로 성립시키기 위한 장치였습니다.
반대로 운영 도구는 플랫폼에 맡기지 않고 앱 안으로 끌어왔습니다. 운영자가 Cafe24 어드민과 앱을 오가며 일하면 결국 어느 쪽도 정본이 아니게 되기 때문입니다.
개발 막바지에는 문서를 믿지 않고 코드에서 직접 구현 현황을 대조해 스냅샷 문서를 만들고, 낡은 설계 문서를 함께 정정했습니다. "문서가 미구현이라 적었지만 실제로는 되어 있는 것"과 "문서가 침묵했지만 실제로 빠진 것"이 양쪽 다 나왔고, 인수인계에서 가장 값이 나간 산출물이 되었습니다.