주문 서버 DB 스키마
사용자 앱 → 주문 서버 → 요기요 API 자동 대행을 위한 DB 설계. 관리자 계정/카드/디바이스 정보는 .env 로 분리.
- 필수 4개 테이블:
external_orders,order_items,yogiyo_orders,api_call_logs - 선택 2개:
auth_tokens(Redis 대체 가능),carts(재시도용) - 출처 색상 코딩: 어디서 온 데이터인지 한눈에 확인 — 사용자 앱 입력, 좌표 변환, 각 요기요 API 응답
개요
주문 1건당 데이터의 흐름:
[사용자 앱] POST /api/orders → external_orders + order_items
[좌표 변환] 주소 → lat/lng → external_orders.delivery_lat/lng
[요기요 API] Step 1~7 자동 호출 → yogiyo_orders + api_call_logs
[상태 확인] 외부 앱이 결과 조회 → yogiyo_orders.status
요기요 API 호출 단계 (Step 1 ~ get-order)
주문 서버가 1건의 주문을 처리하면서 순차적으로 호출하는 요기요 API. 본 문서에서 "Step N" 으로 참조되는 단계의 정의.
1
refresh — 액세스 토큰 갱신
POST
authyo.yogiyo.co.kr/api/v1/auth/refresh?customer_id={customer_id}
입력:
.env 의 YGY_REFRESH_TOKEN · 역할: 1시간 만료되는 access_token 새로 발급. 캐시된 토큰이 유효하면 호출 생략.→ 저장: auth_tokens(ACCESS).token_value, expires_at
2
customer-info — 회원 정보 + WebView 인증 토큰
GET
memberyo.yogiyo.co.kr/v3/customers/{customer_id}
입력: access_token · 역할: 관리자 계정의 회원 정보(이메일/전화/주소) + 1초결제 WebView 가 쓰는 별도의 RS256 JWT(authyo_token, 2시간 만료) 받기. Step 6 의 submit body 안
customer 필드 구성에 사용.→ 저장: auth_tokens(AUTHYO), carts.customer_payload
3
create-cart — 카트 생성 (메뉴 담기)
POST
api.yogiyo.co.kr/cart/v3/food/customers/{customer_id}/restaurants/{vendor_id}/{vd|od}/
입력: lat, lng, products[]{item_id, quantity, options}, coupon, referral · 역할: 요기요 서버에 새 카트 생성.
cart_uuid 와 cart_signature 를 발급받음. 여기서부터 사용자 주소(lat/lng) 가 매장 배달 범위와 비교됨 — 범위 초과 시 에러.→ 저장: carts.cart_uuid, carts.cart_signature
4
read-cart — 카트 검증 + 최종 금액 확정
GET
api.yogiyo.co.kr/cart/v3/food/{cart_uuid}-b24b/{vd|od}/?customer_id&lat&lng
입력: cart_uuid · 역할: 카트 재조회로 최종 결제 금액 확정(음식+배달비). 사용자 앱이 보낸
expected_total 과 비교해 ±오차 범위 초과 시 주문 중단.→ 저장: carts.cart_total, carts.delivery_fee, external_orders.actual_total
5a
vendor-info — 매장 메타 조회
GET
api.yogiyo.co.kr/order/checkout/vendors/{vendor_id}
입력: vendor_id · 역할: 매장 정보(이름, franchise_id 등) 조회. 응답 JSON 전체를 Step 6 submit body 의
vendor 필드에 그대로 넣어야 함.→ 저장: carts.vendor_payload
5b
card-info 선택 — 등록 카드 검증 (env 사용 시 생략 가능)
GET
payo.yogiyo.co.kr/v2/payments/card-info?customer_id={customer_id}
역할: 관리자 계정에 등록된 1초결제 카드 목록 + 토큰 조회.
.env 의 YGY_CARD_TOKEN 이 살아있는지 주기적으로 확인할 때만 호출.
6
⭐ zero-payment submit — 실제 결제 발생 (1초결제 진입점)
POST
api.yogiyo.co.kr/order/cart/submit/food/{customer_id}/{lat}/{lng}
입력: cart_uuid, cart_signature, customer(Step 2), vendor(Step 5a), delivery_info, payment{ygypay_info.token=env 의 카드토큰}, agreements{efinance:true, tos:true} · 역할: 실제 결제 트리거. 등록된 1초결제 카드 사용 시 PG redirect 없이 즉시 결제 완료 →
purchase_id, order_number 응답.→ 저장: yogiyo_orders.{order_number, payment_no, purchase_id, total_paid, payment_url}
7
PG follow 선택 — KG이니시스 PG 체인 (zero payment 면 미발생)
GET
payo.yogiyo.co.kr/v2/payments/{payment_no}/checkout → wpay.inicis.com → orderyo.yogiyo.co.kr/callback/payment/result/
입력: Step 6 응답의
payment_url · 역할: 등록 카드 첫 사용 등 신규 결제 수단일 때만 발생하는 PG redirect chain. 1초결제 zero payment 응답에는 payment_url 이 없어 이 단계 자체가 생략됨.→ 저장: yogiyo_orders.ygy_order_id (있을 때만)
✓
get-order — 주문 활성화 확인
GET
api.yogiyo.co.kr/order/api/v2/orders/{order_number}/
입력: Step 6 응답의
order_number · 역할: 주문이 매장 측에 ACTIVE 로 도달했는지 확인. 결제 직후 1~3초 폴링하여 상태 확정. 실패 시 cancel-order 호출.→ 저장: yogiyo_orders.status, raw_response, external_orders.status=COMPLETED
중요: Step 1 과 Step 2 의 토큰은 캐시되어 매 주문마다 호출하지 않음. Step 5a/Step 3/Step 4/Step 6/get-order 는 주문 1건마다 반드시 순서대로 호출. Step 6 만 실제 결제를 발생시키므로, Step 6 직전까지의 검증 실패는 결제 없이 안전하게 중단 가능.
저장소 분리
.env (정적 시크릿)
운영자가 직접 관리, 코드 배포 시 주입
- YGY_CUSTOMER_ID
- YGY_REFRESH_TOKEN
- YGY_DEVICE_ID
- YGY_PHONE_UUID
- YGY_CARD_CODE
- YGY_CARD_TOKEN
- KAKAO_GEOCODE_API_KEY
DB (동적 데이터)
주문별 영속 데이터
- external_orders
- order_items
- yogiyo_orders
- api_call_logs
Redis (단기 캐시, 선택)
TTL 기반, 재시작 시 손실 OK
- auth:access_token (1h)
- auth:authyo_token (2h)
- cart:{order_id} (30m)
출처 색상 범례
사용자 앱 입력
좌표 변환
요기요 API 응답
FK 참조
서버 상태머신
auto / 코드 기록
필수 테이블
1
external_orders
사용자 앱이 보낸 주문 요청 + 서버 진행 상태 (메인 테이블, 주문당 1 row)
| 컬럼 | 타입 | 출처 | 예시값 |
|---|---|---|---|
id | BIGINT PK | auto | 1001 |
external_ref UNIQUE | VARCHAR | 사용자 앱 입력 · 멱등성 키 | "ext-20260512-0001" |
external_user_id | VARCHAR | 사용자 앱 입력 | "app_user_12345" |
vendor_id | BIGINT | 사용자 앱 입력 · 요기요 식당 코드 | 1234567 |
serving_type | ENUM | 사용자 앱 입력 | 'vd' / 'od' |
delivery_address | VARCHAR | 사용자 앱 입력 | "서울시 강남구 역삼동 123-45" |
delivery_detail | VARCHAR | 사용자 앱 입력 | "101동 202호" |
delivery_lat | DECIMAL(10,7) | 좌표 변환 · 주소 → lat | 37.5012345 |
delivery_lng | DECIMAL(10,7) | 좌표 변환 · 주소 → lng | 127.0396789 |
recipient_phone | VARCHAR | 사용자 앱 입력 | "01012345678" |
delivery_request | TEXT | 사용자 앱 입력 | "문 앞에 놔주세요" |
store_request | TEXT | 사용자 앱 입력 | "덜 맵게 해주세요" |
expected_total | INTEGER | 사용자 앱 입력 · 검증용 | 23000 |
actual_total | INTEGER | GET api.yogiyo.co.kr/cart/v3/food/{uuid}-b24b/{vd|od}/ | 23000 |
coupon_code | VARCHAR | 사용자 앱 입력 · 선택 | null |
client_app_version | VARCHAR | 사용자 앱 입력 | "1.0.0" |
client_platform | VARCHAR | 사용자 앱 입력 | "ios" / "android" |
status | ENUM | 서버 상태머신 | 'RECEIVED' → ... → 'COMPLETED' |
current_step | TINYINT | 서버 상태머신 · 재시도 위치 | 0~7 |
failure_reason | TEXT | 서버 상태머신 · 실패 시 | "out_of_range" / null |
retry_count | INTEGER | 서버 상태머신 | 0 |
received_at | TIMESTAMP | auto | 2026-05-12 14:30:00 |
submitted_at | TIMESTAMP | 코드 기록 · Step 6 완료 시 | 2026-05-12 14:30:08 |
completed_at | TIMESTAMP | 코드 기록 · 활성화 확인 시 | 2026-05-12 14:30:12 |
2
order_items
주문 메뉴 항목 (주문당 메뉴 수만큼 N rows). 카트 생성 시 products[] 로 직결.
| 컬럼 | 타입 | 출처 | 예시값 |
|---|---|---|---|
id | BIGINT PK | auto | 5001 |
external_order_id | BIGINT | FK external_orders.id | 1001 |
item_id | BIGINT | 사용자 앱 입력 · 요기요 menu item_id | 9877 |
item_name | VARCHAR | 사용자 앱 입력 · 표시용 | "김치찌개" |
quantity | INTEGER | 사용자 앱 입력 | 2 |
unit_price | INTEGER | 사용자 앱 입력 | 9000 |
options | JSON | 사용자 앱 입력 | [{"id":11,"name":"공기밥","price":1000}] |
per_item_request | TEXT | 사용자 앱 입력 · 선택 | "" |
subtotal | INTEGER | 서버 계산 · unit_price×quantity + Σ options | 20000 |
3
yogiyo_orders
요기요 결제 결과 (성공 주문당 1 row). 외부 앱의 주문 상태 조회 대상.
| 컬럼 | 타입 | 출처 | 예시값 |
|---|---|---|---|
id | BIGINT PK | auto | 1 |
external_order_id UNIQUE | BIGINT | FK external_orders.id | 1001 |
order_number UNIQUE | VARCHAR | POST api.yogiyo.co.kr/order/cart/submit/food/{id}/{lat}/{lng} | "F2605121430JXXX4B" |
payment_no | VARCHAR | POST api.yogiyo.co.kr/order/cart/submit/food/{id}/{lat}/{lng} | "20260512143005XXXXXX" |
purchase_id | BIGINT | POST api.yogiyo.co.kr/order/cart/submit/food/{id}/{lat}/{lng} | 147296710660 |
ygy_order_id | BIGINT | orderyo.yogiyo.co.kr/callback/payment/result/ 또는 GET api.yogiyo.co.kr/order/api/v2/orders/{order_number}/ | 1459590930 |
payment_method | VARCHAR | 코드 상수 | 'YGYPAY' |
used_card_code | CHAR(2) | .env (YGY_CARD_CODE) | "01" |
total_paid | INTEGER | POST api.yogiyo.co.kr/order/cart/submit/food/{id}/{lat}/{lng} | 23000 |
payment_url | TEXT | POST api.yogiyo.co.kr/order/cart/submit/food/{id}/{lat}/{lng} · zero payment 시 null | null |
status | ENUM | GET api.yogiyo.co.kr/order/api/v2/orders/{order_number}/ | 'PAID' → 'ACTIVE' → 'DELIVERED' |
paid_at | TIMESTAMP | 코드 기록 · Step 6 성공 시 | 2026-05-12 14:30:08 |
active_at | TIMESTAMP | 코드 기록 · ACTIVE 도달 시 | 2026-05-12 14:30:12 |
cancelled_at | TIMESTAMP | 코드 기록 · 취소 시 | null |
raw_response | JSON | GET api.yogiyo.co.kr/order/api/v2/orders/{order_number}/ 응답 전체 | {"order_number":"...","items":[...]} |
4
api_call_logs
요기요 API 호출 로그 (호출당 1 row, 주문당 7~10 rows). 디버깅 + 감사용.
| 컬럼 | 타입 | 출처 | 예시값 |
|---|---|---|---|
id | BIGINT PK | auto | 9001 |
external_order_id | BIGINT NULL | FK external_orders.id · 토큰 갱신 시 null 가능 | 1001 |
step | ENUM | 코드 상수 | 'CREATE_CART' / 'SUBMIT' / ... |
method | VARCHAR | 코드 기록 | 'POST' |
url | TEXT | 코드 기록 | "https://api.yogiyo.co.kr/cart/v3/food/customers/..." |
req_headers | JSON | 코드 기록 · 민감값 마스킹 | {"Authorization":"Bearer <REDACTED>"} |
req_body | JSON | 코드 기록 · 카드토큰 마스킹 | {"lat":37.5012,"products":[...]} |
resp_status | INTEGER | 호출 대상 응답 | 200 |
resp_body | JSON | 호출 대상 응답 · 민감값 마스킹 | {"customer_cart":{...}} |
duration_ms | INTEGER | 서버 측정 | 342 |
error | TEXT | 코드 기록 · 실패 시 | null / "timeout" |
created_at | TIMESTAMP | auto · INDEX | 2026-05-12 14:30:03.123 |
선택 테이블
5
auth_tokens 선택
access/authyo 토큰 캐시. Redis 로 대체 가능 (TTL 자동 처리).
| 컬럼 | 타입 | 출처 | 예시값 |
|---|---|---|---|
id | BIGINT PK | auto | 1 |
token_type UNIQUE | ENUM | 코드 상수 | 'ACCESS' / 'AUTHYO' |
token_value | TEXT ENCRYPTED | POST authyo.yogiyo.co.kr/api/v1/auth/refresh?customer_id={id} (ACCESS) 또는 GET memberyo.yogiyo.co.kr/v3/customers/{id} (AUTHYO) | "eyJhbGciOiJSUzI1NiIs..." |
issued_at | TIMESTAMP | 코드 기록 | 2026-05-12 14:30:00 |
expires_at | TIMESTAMP | JWT exp claim 파싱 · ACCESS=1h, AUTHYO=2h | 2026-05-12 15:30:00 |
6
carts 선택
카트 생성 ~ 결제 submit 사이의 중간 상태. Step 6 실패 시 Step 3 부터 재시작 안 하고 재사용. TTL 짧으므로 Redis 도 적합.
| 컬럼 | 타입 | 출처 | 예시값 |
|---|---|---|---|
id | BIGINT PK | auto | 1 |
external_order_id UNIQUE | BIGINT | FK external_orders.id | 1001 |
cart_uuid | VARCHAR | POST api.yogiyo.co.kr/cart/v3/food/customers/{id}/restaurants/{vid}/{vd|od}/ · raw uuid | "a1b2c3d4-e5f6-..." |
cart_signature | VARCHAR | POST api.yogiyo.co.kr/cart/v3/food/customers/{id}/restaurants/{vid}/{vd|od}/ · sha1 hex | "abc123def456..." |
cart_total | INTEGER | GET api.yogiyo.co.kr/cart/v3/food/{uuid}-b24b/{vd|od}/ | 23000 |
delivery_fee | INTEGER | GET api.yogiyo.co.kr/cart/v3/food/{uuid}-b24b/{vd|od}/ | 3000 |
vendor_payload | JSON | GET api.yogiyo.co.kr/order/checkout/vendors/{vid} · submit body 재구성용 | {"id":1234567,"name":"...",...} |
customer_payload | JSON | GET memberyo.yogiyo.co.kr/v3/customers/{id} · submit body 의 customer 필드 | {"id":21620000,"email":"...",...} |
expires_at | TIMESTAMP | 코드 기록 · 보통 +30분 | 2026-05-12 15:00:00 |
state | ENUM | 서버 상태머신 | 'CREATED' / 'VERIFIED' / 'SUBMITTED' |
created_at | TIMESTAMP | auto | 2026-05-12 14:30:03 |
1주문 흐름 — 시간별 DB 변화
외부 앱 요청 도착 후 약 3초 안에 모든 단계 완료.
T+0.00s
외부 앱 POST /api/orders
INSERT
external_orders (status='RECEIVED') + order_items N rowsT+0.05s
서버 검증 + 좌표 변환
UPDATE
external_orders (lat/lng, status='VALIDATED')T+0.10s
Step 1: refresh (토큰 만료 시만)
UPSERT
auth_tokens(ACCESS) + INSERT api_call_logsT+0.30s
Step 2: customer-info
UPSERT
auth_tokens(AUTHYO) + carts.customer_payload + logT+0.50s
Step 5a: vendor-info
UPDATE
carts.vendor_payload + logT+1.00s
Step 3: create-cart
INSERT
carts (state='CREATED') + logT+1.30s
Step 4: read-cart + 금액 검증
UPDATE
carts (cart_total, state='VERIFIED') + logT+1.50s
Step 6: zero-payment submit ⭐
INSERT
yogiyo_orders (status='PAID') + UPDATE carts (state='SUBMITTED') + logT+3.00s
get-order ACTIVE 확인
UPDATE
yogiyo_orders (status='ACTIVE', raw_response) + UPDATE external_orders (status='COMPLETED') + log
실패 시: 어느 단계에서 실패하든
external_orders.failure_reason 기록 + status='FAILED' + current_step 으로 재시도 위치 표시. 재시도 시 carts 가 살아 있으면 Step 6 부터 재개 가능.
요기요 API 출처 ↔ 컬럼 매핑
각 요기요 API 응답에서 어느 컬럼으로 들어가는지 역참조.
| 요기요 API 응답 | 저장 위치 |
|---|---|
| POST authyo.yogiyo.co.kr/api/v1/auth/refresh?customer_id={id} | auth_tokens(ACCESS) |
| GET memberyo.yogiyo.co.kr/v3/customers/{id} | auth_tokens(AUTHYO) · carts.customer_payload |
| GET api.yogiyo.co.kr/order/checkout/vendors/{vendor_id} | carts.vendor_payload |
| POST api.yogiyo.co.kr/cart/v3/food/customers/{id}/restaurants/{vid}/{vd|od}/ | carts.cart_uuid · carts.cart_signature |
| GET api.yogiyo.co.kr/cart/v3/food/{uuid}-b24b/{vd|od}/ | carts.cart_total · carts.delivery_fee · external_orders.actual_total |
| POST api.yogiyo.co.kr/order/cart/submit/food/{id}/{lat}/{lng} | yogiyo_orders.order_number · payment_no · purchase_id · total_paid · payment_url |
| orderyo.yogiyo.co.kr/callback/payment/result/ (PG callback) | yogiyo_orders.ygy_order_id (선택) |
| GET api.yogiyo.co.kr/order/api/v2/orders/{order_number}/ | yogiyo_orders.status · ygy_order_id · raw_response |