네이버 커머스 API
Architecture
Stack
- Python 3.12
- FastAPI
- httpx (async)
- bcrypt
- Pydantic v2
- Gunicorn
External integrations
- Naver Commerce API (OAuth2)
- Cloud Run
Highlights
- bcrypt-signed OAuth2 (Naver-spec)
- 85min token cache
- Korean order status translator
문제정의
스마트스토어 챗봇(naver-smartstore)이 의도 분류와 응답 생성에 집중하려면, 네이버 커머스 API의 인증·페이지네이션·상태 코드 같은 사정은 챗봇 코드 바깥에 있어야 했다. 그런데 네이버 커머스 API의 OAuth2는 표준 client_secret 모델이 아니다 — bcrypt 해시 + base64 인코딩으로 직접 서명을 생성해 token endpoint에 보내야 한다.
| 사정 | 챗봇이 직접 다룰 때의 비용 |
|---|---|
| bcrypt 서명 알고리즘 | LangGraph 노드마다 서명 재계산 → 코드 복제 + 키 노출 위험 |
| 토큰 만료 | 호출마다 토큰 발급 → 호출 비용·레이트리밋 압박 |
| 응답 페이로드 한글 상태 코드 | DELIVERED·PAYMENT_WAITING 같은 영문 상태를 LLM 프롬프트로 그대로 노출 → 사용자 응답 일관성 저하 |
| 에러 형식 | Naver의 비표준 에러 응답을 챗봇 노드마다 파싱 → 분산된 핸들링 |
목표는 세 가지로 좁혔다.
- 인증을 한 곳에 격리 — bcrypt 서명·토큰 발급·캐싱을 챗봇 바깥의 단일 컴포넌트로.
- 상품/주문 조회를 LLM-친화 형식으로 정규화 — 영문 상태 코드·페이로드 차이를 챗봇 입력 전 흡수.
- 운영 가능한 컨테이너 산출물 — Cloud Run + Gunicorn 워커, 토큰 발급 실패 시에도 health endpoint가 살아 있도록.
구현
bcrypt 서명 + 토큰 캐시 매니저
핵심은 CommerceTokenManager 단일 클래스. 서명 생성·토큰 발급·만료 추적·재발급을 한 객체 안에 가두고, 라우터에서는 await token_manager.get_token() 한 호출로 받는다.
| 책임 | 구현 |
|---|---|
| 서명 생성 | bcrypt.hashpw(password, client_secret) → base64.urlsafe_b64encode (Naver 스펙) |
| 토큰 발급 | POST /v1/oauth2/token with client_credentials + signature |
| 캐시 | 메모리 dict + expires_at (발급 시각 + 85m, 실제 만료보다 보수적으로) |
| 재발급 | expires_at 도래 또는 401 응답 수신 시 자동 재발급 |
| 상태 노출 | GET /api/auth/status로 캐시 잔여 시간·토큰 prefix 노출 (운영 디버그용) |
85분(공식 만료 90분에서 5분 마진)으로 잡은 이유는 두 가지 — 만료 직전 호출이 401로 떨어지는 레이스 컨디션 회피, 그리고 워커가 여러 개일 때 캐시 만료 직후 동시 재발급이 몰리는 thundering-herd 완화.
상품·주문 조회 서비스 (async httpx)
ProductService와 OrderService는 동일한 패턴 — 토큰 매니저에서 Bearer 받고, httpx.AsyncClient로 호출하고, 결과를 정규화. 차이는 정규화 깊이다.
ProductService— 검색(/v1/products/search) + 채널 상품 상세(/v2/products/channel-products/{id})OrderService— 주문 검색(/v1/pay-order/seller/product-orders) + 상세 + 한글 상태 번역
주문 검색은 공식 필터 스펙(startSearchDate/endSearchDate/productOrderStatuses CSV 등)에 맞췄다. 04-03에 한 번 내부 변경한 시그니처를 공식 필터에 다시 정렬한 게 이 라운드의 핵심 정리 작업이었다 — 챗봇이 "Naver의 페이로드 형태"를 그대로 의식하지 않게 하기 위함.
한글 주문 상태 번역기
영문 상태 코드를 LLM 프롬프트에 그대로 노출하면, 모델이 PAYMENT_WAITING을 "결제 대기"로 의역할 때마다 표현이 흔들린다. 챗봇이 안정적으로 답하려면 백엔드가 한 가지 한국어 표현으로 고정해서 내려줘야 한다.
| 영문 상태 | 한글 (고정) |
|---|---|
PAYMENT_WAITING | 결제 대기 |
PAYED | 결제 완료 |
DELIVERING | 배송 중 |
DELIVERED | 배송 완료 |
PURCHASE_DECIDED | 구매 확정 |
CANCELED / RETURNED / EXCHANGED | 취소 / 반품 / 교환 |
번역은 _normalize_order_item에서 일괄 처리 — 챗봇 노드는 영문 상태를 볼 일이 없다.
운영 가능한 컨테이너 (Cloud Run + Gunicorn)
FastAPI 단일 프로세스만 띄우면 콜드스타트 후 첫 요청이 토큰 발급에 묶여 느려진다. Gunicorn으로 워커를 띄우고, 워커 단위로 토큰 캐시를 공유(메모리 dict)하는 방식 — 워커가 늘어나도 외부 토큰 호출은 한 번에 한 번씩만 추가된다.
배포는 04-08 단일 커밋으로 GitHub Actions → Cloud Run 워크플로우를 한 번에 박았다. gunicorn.conf.py가 워커 수·바인드·로그 포맷을 한 곳에서 관리한다.
출시
빠른 스캐폴드 + 운영 시그니처 정리 패턴.
| 시기 | 주요 변경 |
|---|---|
| Day 1 (2026-03-30) | bcrypt 인증 + 토큰 매니저 · 상품/주문 엔드포인트 (smartstore와 같은 날 푸시) |
| Week 1 (~04-03) | 인증 재시도 정리 · 공식 주문 조회 필터로 시그니처 재정렬 · PRD/Postman 검증 자산 갱신 |
| Week 2 (~04-07) | 상품 질문 생성 스크립트 추가 (smartstore 벤치마크 자산 연계) |
| Week 2 (04-08) | Cloud Run 배포 워크플로우 · gunicorn.conf.py · uv.lock 트래킹 |
특이점: 스캐폴드 첫날(2026-03-30)이 naver-smartstore의 Day 1과 같다. 두 저장소를 한 작업 세션에서 같이 푸시한 셈 — 챗봇이 호출할 표면이 명확했기에 가능했다.
결과학습
| 항목 | 결과 |
|---|---|
| 안정화 기간 | 10일 (Day 1 스캐폴드 → 04-08 prod 배포) |
| 토큰 호출 절감 | 매 호출 발급 → 85분당 1회 (워커당) |
| 인증 코드 격리 | bcrypt 서명·재발급·캐시가 CommerceTokenManager 단일 객체에 캡슐화 |
| 한글 상태 일관성 | 영문 상태 코드 0건 LLM 입력 (모든 노출 채널에서 고정 한국어) |
| 운영 산출물 | Containerfile · gunicorn.conf.py · GitHub Actions → Cloud Run · /api/auth/status |
기술·운영적으로 가져간 학습은 네 가지다.
- 챗봇 옆에 두는 API 래퍼는 "외부 SDK의 quirk를 흡수하는 막"이다. bcrypt 서명·토큰 만료·영문 상태 코드 같은 외부 사정을 챗봇이 의식하지 않게 하는 게 래퍼의 본업. 래퍼가 두꺼워질수록 챗봇 노드가 얇아진다.
- 토큰 만료 마진은 단순 안전이 아니라 thundering-herd 완화 장치. 공식 90분에 85분으로 잡으면, 만료 시각 직후 동시 재발급 폭주가 분산된다. 5분 마진은 비용이 아니라 보험.
- 공식 스펙과 내부 시그니처는 일찍 정렬해야 한다. Day 1 직후(04-03)에 주문 조회 시그니처를 공식 필터에 다시 맞춘 작업이 가장 ROI 높은 정리였다. 늦게 정렬할수록 챗봇이 잘못된 추상화에 묶인다.
- 상태 번역은 백엔드의 책임. LLM에 영문 상태를 의역하게 두면 표현 안정성을 잃는다. 번역 테이블은 작아도 백엔드에 두는 게 정답.
Next
- 워커 간 토큰 캐시 공유 — 현재는 워커별 메모리 dict. Redis 같은 외부 캐시로 옮기면 워커 수와 무관하게 토큰 호출이 한 번으로 수렴.
- 레이트리밋·서킷 브레이커 도입 — Naver 측 일시 장애나 레이트리밋 응답을 챗봇 노드까지 새지 않게,
select-hotel에서 검증한 5회/30초 패턴을 이식. /api/auth/status확장 → 운영 대시보드 — 토큰 잔여 시간·재발급 횟수·최근 401 발생을 메트릭으로 노출. 비정상 인증 사이클을 사후가 아니라 실시간으로 감지.