배포와 인프라10 분 읽기

브라우저를 조종하던 주문봇을 공식 API로 갈아탔다 — 그리고 일부러 자동화하지 않은 것

AppleScript로 브라우저 세션을 조종하던 주문 모니터를 공식 커머스API로 옮겼습니다. bcrypt 서명 인증, launchd 상시 운영, 그리고 환불·반품을 끝까지 자동 승인하지 않은 이유.

#automation#api#launchd#gotchas
개념 도식: 브라우저 세션 기반 주문 모니터에서 공식 커머스API 백엔드로 전환
자동화 사슬은 가장 약한 수동 고리만큼만 자동이다.

스마트스토어에 124개를 자동 등록하고 나면 다음 문제가 바로 옵니다. 주문이 들어오면 누가 처리합니까?

혼자 하는 스토어에서 주문 이후 구간은 상품 등록보다 위험합니다. 주문을 놓치면 지연 페널티, 재고 확인 없이 발주확인하면 품절취소, 송장 없이 발송처리하면 허위 발송, 취소·반품·교환은 그대로 돈입니다.

그래서 주문 모니터를 만들었는데, 처음 만든 버전은 오래 못 살 물건이었습니다.

1차: 로그인된 브라우저를 조종하는 방식

가장 빨리 만들 수 있는 건 이미 로그인돼 있는 판매자센터 세션을 재활용하는 것이었습니다.

  • AppleScript로 브라우저 제어
  • 판매자센터 주문 페이지를 열거나 재사용
  • 내부 GraphQL 호출로 주문 목록 조회
  • 신규 주문을 텔레그램으로 알림, 인라인 버튼으로 승인/거절
  • 5분 무응답이면 승인 처리

반나절이면 도는 구조입니다. 그리고 이런 게 왜 오래 못 사는지도 명확했습니다.

  • 맥이 켜져 있고 로그인돼 있어야 한다
  • 쿠키가 만료되면 조용히 멈춘다
  • AppleScript 타임아웃이 난다
  • 공식 인터페이스가 아니라, 내부 화면이 바뀌면 같이 죽는다

당신이라면 이걸 그대로 두시겠습니까? 돌아가긴 하니까요. 저는 "돌아가는 것"과 "돈이 걸린 채로 무인 운영되는 것"의 기준이 다르다고 봤습니다. 알림봇이면 그냥 뒀겠지만, 이건 발주확인을 대신 눌러주는 물건입니다.

2차: 공식 커머스API로 전환

네이버 커머스API는 스마트스토어 기능을 HTTP로 노출합니다. 쓴 엔드포인트는 이렇습니다.

GET  /v1/pay-order/seller/product-orders/last-changed-statuses
POST /v1/pay-order/seller/product-orders/query
POST /v1/pay-order/seller/product-orders/confirm
POST /v1/pay-order/seller/product-orders/dispatch
POST /v1/pay-order/seller/product-orders/:id/claim/cancel/approve
POST /v1/pay-order/seller/product-orders/:id/claim/return/approve

기존 모니터는 지우지 않고 백엔드 스위치를 달았습니다.

ORDER_OPS_BACKEND=commerce_api   # 또는 browser

구버전을 남겨두면 신규 백엔드가 인증·권한 문제로 막혔을 때 되돌아갈 곳이 생깁니다. 실제로 전환 중에 한 번 필요했습니다.

인증 함정 — client_secret을 보내지 않는다

여기서 시간을 제일 많이 썼습니다. OAuth2 client credentials인데, client_secret 원문을 보내지 않습니다.

발급 흐름:

  1. client_id와 현재 timestamp를 밑줄로 연결
  2. 그 문자열을 client_secret을 솔트로 bcrypt 해시
  3. 결과를 base64 인코딩
  4. client_secret_sign으로 전달
  5. 받은 access token을 이후 Authorization: Bearer로 사용

요청 규격도 까다롭습니다.

  • POST /v1/oauth2/token, Content-Type: application/x-www-form-urlencoded
  • body: client_id, timestamp, client_secret_sign, grant_type=client_credentials, type=SELF
  • type=SELFaccount_id보내지 않는다. type=SELLER일 때만 account id가 필요합니다.

해시 입력에 쓰는 timestamp와 body에 넣는 timestamp가 같은 값이어야 한다는 것, 그리고 type에 따라 필수 필드가 달라진다는 것 — 이 둘이 전형적인 실패 지점입니다. 서명 방식 인증은 어느 플랫폼이든 문서를 글자 그대로 따라야 하는데, 증권사 API에서도 똑같이 헤맸던 종류의 함정입니다.

폴링 루프와 상태 파일

커머스API 모드의 한 사이클:

  1. last-changed-statuses로 변경된 상품주문 조회
  2. 변경된 상품주문번호 수집
  3. product-orders/query로 상세 조회
  4. 신규 주문인지 클레임인지 판단
  5. 텔레그램 알림
  6. 승인 또는 타임아웃 → 발주확인 API
  7. 송장번호가 들어오면 발송처리 API

상태는 state.json 하나에 둡니다: 이미 본 주문, 승인 대기 주문, 텔레그램 update offset, 마지막 변경조회 시각. 마지막 항목이 핵심입니다. 이게 없으면 재시작할 때마다 조회 창이 틀어져서 주문을 놓치거나 중복 처리합니다. 폴링 파이프라인의 커서는 메모리가 아니라 디스크에 있어야 합니다.

launchd와 venv

모니터는 launchd로 상시 실행합니다. 여기서 한 번 걸렸는데, macOS/Homebrew 파이썬은 외부관리 환경이라 전역 pip install이 막힙니다. 서명에 bcrypt가 필요했기 때문에 프로젝트 안에 .venv를 만들고, plist의 실행 경로를 그 venv 파이썬으로 직접 지정했습니다.

launchctl unload ~/Documents/.../com.ootssu.smartstore-order-ops.plist
launchctl load   ~/Documents/.../com.ootssu.smartstore-order-ops.plist
launchctl list | rg 'smartstore-order-ops'
tail -n 80 ops.log

launchd 잡은 당신의 셸 환경을 물려받지 않습니다 — PATH도, 활성화된 venv도, 작업 디렉터리도. 전부 명시해야 합니다. 예전에 정리한 launchd 함정들이 여기서도 그대로 적용됐습니다.

일부러 자동화하지 않은 것

발주확인은 5분 무응답 시 자동 승인합니다. 하지만 취소·반품·교환은 명시 명령 없이는 처리하지 않습니다.

/approve  <상품주문번호>
/ship     <상품주문번호> <택배사코드> <송장번호>
/cancel_approve <상품주문번호>
/return_approve <상품주문번호>
/exchange_collect <상품주문번호>

이유는 기술이 아니라 비용입니다. 클레임은 돈이 나가고, 판매자 페널티와 연결되고, 공급처 책임인지 판매자 책임인지 확인이 필요합니다. API 호출 난이도로 보면 발주확인과 반품승인은 똑같이 한 줄입니다. 갈라야 하는 기준은 호출 난이도가 아니라 틀렸을 때의 회수 가능성입니다.

자동화 설계에서 반복적으로 확인한 규칙이 이겁니다. 되돌릴 수 있는 액션은 자동으로, 되돌릴 수 없는 액션은 승인 게이트로.

자가진단 3줄

무인으로 도는 운영 자동화가 있다면:

  1. 인증이 세션/쿠키에 의존합니까, 만료되면 조용히 멈춥니까 시끄럽게 실패합니까?
  2. 폴링 커서가 재시작을 견딥니까? (프로세스 죽으면 조회 창이 어떻게 됩니까?)
  3. 자동으로 실행되는 액션 중 되돌릴 수 없는 것이 섞여 있지 않습니까?

솔직한 부분

네이버 쪽은 붙었지만, 이 파이프라인은 아직 무부하 상태에서 검증됐을 뿐입니다. 로그는 monitor started backend=commerce_api, 토큰 발급 성공, 변경조회 성공, 그리고 last_changed_count=0. 주문이 0이니까요.

그리고 더 정직하게 말하면, 가장 큰 조각이 남아 있습니다. 네이버 API로 주문 감지·발주확인·발송처리·클레임 승인이 되지만, 공급처 실제 발주·결제·송장 확보는 여기 포함되지 않습니다. 공급처에 API가 없으면 결국 브라우저 자동화로 돌아가야 합니다. 방금 브라우저 자동화를 걷어낸 그 자리로요.

주문부터 배송까지 "자동화됐다"고 말하려면 사슬 전체가 필요한데, 지금 이 사슬에는 사람 손이 들어가는 고리가 하나 남아 있습니다. 자동화율은 가장 약한 고리로 계산하는 게 맞습니다.

지금 돌리는 무인 자동화 중에, 로그인 세션에 목숨을 걸고 있는 게 하나쯤 있지 않나요? 오늘 그것부터 확인해 보세요.

관련 글