이 문서는 “이 스키마 그대로 만들라”는 정답지가 아니에요. Bootpay 결제 SDK를 붙일 때 가맹점 서버가 어떤 값을 확인하고, 어떤 값을 DB에 남겨야 하는지를 예시 코드와 테이블로 표현한 안내예요.
이미 주문 테이블이 있다면 그대로 쓰고, 아래 필드는 서비스 상태·정산·CS·취소 처리에 필요한 만큼만 매핑해요.
먼저 나눌 책임
결제 API는 결제 트랜잭션을 처리하고, 주문·상품·회원·구독 회차 같은 서비스 로직은 가맹점 서버가 관리해요.
| 구분 | Bootpay가 제공하는 값/기능 | 가맹점 서버가 해야 할 일 |
|---|---|---|
| 단건 결제 | receipt_id, 결제 상태, 금액, 결제수단, 취소 API, 웹훅 |
내부 주문과 매핑, 금액·상태 검증, 주문 상태 저장 |
| 자동결제 | billing_key, 빌링키 조회, 빌링키 결제 요청 |
빌링키 저장, 회원/구독과 연결, 청구 시점 결정 |
| 예약결제 | 예약 등록·조회·취소 API, 실행 결과 웹훅 | 예약 ID 저장, 예약 상태 관리, 실행 결과 반영 |
| 카드 원본 정보 | PG/카드사가 처리 | 카드번호 전체·CVC·비밀번호·유효기간을 저장하지 않음 |
주문(Order)·구독(OrderSubscription) 같은 비즈니스 엔티티까지 Bootpay 쪽 모델로 관리하려면 결제 SDK가 아니라 커머스 SDK를 봐요 → 데이터 모델
가맹점 DB에 보통 남기는 값
| Bootpay 값 | 가맹점 저장 위치 예시 | 저장 이유 |
|---|---|---|
receipt_id |
orders.bootpay_receipt_id 또는 order_payments.receipt_id |
결제 조회·취소·웹훅 처리 기준 |
order_id |
orders.order_id |
Bootpay 결제 건과 내부 주문 매핑 |
price |
orders.paid_amount |
결제 금액 기록과 정산/CS 확인 |
status |
orders.payment_status 또는 원문 JSON |
Bootpay 결제 상태 확인용 |
method_symbol |
orders.payment_method |
결제수단 표시·운영 분석 |
pg |
orders.pg |
PG별 장애·정산 확인 |
purchased_at |
orders.paid_at |
결제 완료 시각 기록 |
cancelled_price |
orders.cancelled_amount |
부분 취소 금액 추적 |
billing_key |
billing_keys.billing_key |
자동결제·예약결제 요청 시 사용 |
billing_data.card_company |
billing_keys.card_company |
마이페이지 표시용 |
billing_data.card_no |
billing_keys.masked_card_no |
마스킹 카드번호 표시용 |
Bootpay의 status는 결제 상태예요. 서비스의 주문 상태(pending, paid, cancelled, shipped 등)와 1:1로 복사하기보다, 원문 결제 상태를 참고해 가맹점 주문 상태를 별도로 전이시키는 편이 안전해요.
주문 테이블에 결제 필드를 더하는 예시
아래 SQL은 기존 주문 테이블에 결제 식별값을 붙이는 예시예요. 실제 컬럼명, 금액 타입, 인덱스, 상태값은 서비스의 주문 모델에 맞게 바꿔요.
-- 예시: 기존 orders 테이블에 결제 처리에 필요한 필드 추가
ALTER TABLE orders
ADD COLUMN bootpay_receipt_id VARCHAR(100), -- Bootpay 영수증 ID
ADD COLUMN paid_amount INTEGER, -- 실제 결제 금액(KRW 기준 예시)
ADD COLUMN payment_status INTEGER, -- Bootpay 원문 status 저장용
ADD COLUMN payment_method VARCHAR(30), -- card, bank, vbank 등
ADD COLUMN pg VARCHAR(50),
ADD COLUMN paid_at DATETIME,
ADD INDEX idx_bootpay_receipt (bootpay_receipt_id);sql주문 테이블이 아직 없다면 — 최소 예시
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
order_id VARCHAR(100) NOT NULL, -- 가맹점 주문번호
order_name VARCHAR(200),
total_amount INTEGER NOT NULL, -- 가맹점이 계산한 주문 금액
order_status VARCHAR(30) DEFAULT 'pending',
bootpay_receipt_id VARCHAR(100),
paid_amount INTEGER,
payment_status INTEGER,
payment_method VARCHAR(30),
pg VARCHAR(50),
paid_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
UNIQUE INDEX idx_order_id (order_id),
INDEX idx_bootpay_receipt (bootpay_receipt_id)
);sql저장 시점은 결제 흐름에 따라 달라요.
| 흐름 | 저장 기준 |
|---|---|
| 클라이언트 승인 | done 이후 서버에서 receipt_id로 결제 조회 → price, status 검증 후 저장 |
| 서버 승인 | confirm 이후 서버에서 금액·상태를 확인하고 승인 API 호출 → 승인 성공 후 저장 |
| 가상계좌 | status: 5 발급 시점에는 입금 대기 상태로 저장 → 입금 완료 웹훅(status: 1)에서 결제 완료로 전이 |
| 취소·환불 | 취소 API 응답 또는 웹훅 수신 후 취소 금액·주문 상태 업데이트 |
// 예시: 결제 조회/검증 성공 후 내부 주문 상태를 갱신
if (receipt.status === 1 && receipt.price === order.total_amount) {
await db.orders.update({
bootpay_receipt_id: receipt.receipt_id,
paid_amount: receipt.price,
payment_status: receipt.status,
payment_method: receipt.method_symbol,
pg: receipt.pg,
order_status: 'paid',
paid_at: receipt.purchased_at ? new Date(receipt.purchased_at) : new Date(),
}, { where: { order_id: order.order_id } })
}javascript결제 조회 응답에서 확인할 값
결제 조회 응답은 주문 확정의 근거로 사용해요. 모든 필드를 저장할 필요는 없고, 주문 처리·CS·취소에 필요한 값만 남겨요.
| 응답 필드 | 처리 기준 | 용도 |
|---|---|---|
receipt_id |
저장 | 결제 조회, 취소, 웹훅 중복 처리 기준 |
order_id |
저장/비교 | 내부 주문번호와 일치하는지 확인 |
price |
비교 + 저장 | DB 주문 금액과 일치하는지 확인 |
status |
비교 + 저장 | 결제완료(1), 입금/승인대기(2), 가상계좌발급완료(5), 결제취소완료(20) 등 흐름별 상태 판단 |
method_symbol |
선택 | 결제수단 표시와 운영 분석 |
pg |
선택 | PG별 장애·정산 확인 |
purchased_at |
선택 | 결제 완료 시각 |
cancelled_price |
선택 | 부분 취소 금액 추적 |
tax_free |
필요 시 | 면세 금액을 운영·회계에서 따로 봐야 할 때 |
빌링키 테이블 예시
빌링키는 카드번호 원본이 아니라, 저장 결제수단으로 결제를 요청할 때 쓰는 식별값이에요. 가맹점은 billing_key와 표시용 마스킹 정보만 저장하고, 카드번호 전체·비밀번호·CVC·유효기간은 저장하지 않아요.
CREATE TABLE billing_keys (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
billing_key VARCHAR(100) NOT NULL,
status VARCHAR(20) DEFAULT 'active',
card_company VARCHAR(50),
masked_card_no VARCHAR(50),
billing_expired_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_status (user_id, status),
UNIQUE INDEX idx_billing_key (billing_key)
);sql| Bootpay 빌링키 조회 필드 | 가맹점 컬럼 예시 | 저장 이유 |
|---|---|---|
billing_key |
billing_key |
빌링키 결제 요청에 필요 |
billing_data.card_company |
card_company |
표시용 카드사명 |
billing_data.card_no |
masked_card_no |
마스킹 카드번호 표시 |
billing_expired_at |
billing_expired_at |
만료 전 재등록 안내 |
status |
status 또는 원문 JSON |
빌링키 유효 상태 확인 |
빌링키만 저장해도 “다시 결제 요청”은 할 수 있어요. 다만 매월 청구, 실패 재시도, 해지, 다음 결제일 관리는 별도 테이블이 필요해요. 예시는 데이터 저장 설계에서 확인해요.
저장하면 안 되는 것
아래 값은 가맹점 DB에 저장하지 않아요.
- 카드 번호 전체(full PAN)
- CVV/CVC
- 카드 비밀번호
- 유효기간(월/년)
- 고객이 빌링키 발급 화면에 입력한 카유생비 원문
마스킹된 카드번호와 카드사명처럼 응답으로 내려오는 표시용 값은 저장할 수 있어요.
데이터 흐름 요약
결제 API — Receipt → 주문 상태
| 단계 | 흐름 | 가맹점이 확인할 것 |
|---|---|---|
| 1 | 프론트엔드 또는 리다이렉트 결과에서 receipt_id 확보 |
내부 order_id와 함께 서버로 전달 |
| 2 | 서버가 결제 조회 API 호출 | 실제 price, status, order_id 확인 |
| 3 | 서버가 내부 주문과 비교 | 금액·주문 상태·중복 처리 확인 |
| 4 | 검증 성공 시 주문 상태 저장 | paid 등 서비스 상태로 전이 |
| 5 | 이후 취소·입금 완료 웹훅 수신 | receipt_id 기준으로 같은 주문 갱신 |
빌링 — BillingKey → 청구/예약
| 단계 | 흐름 | 가맹점이 확인할 것 |
|---|---|---|
| 1 | 빌링키 발급 후 receipt_id 또는 billing_key 확보 |
발급 방식에 따라 조회 API 호출 |
| 2 | 서버가 빌링키 조회 | billing_key, 표시용 카드/계좌 정보 확인 |
| 3 | 가맹점 DB에 빌링키 저장 | 회원·구독·예약과 연결 |
| 4 | 필요한 시점에 빌링키 결제 또는 예약 등록 | 금액·주문번호·실행 시점은 가맹점이 결정 |
| 5 | 결제 결과 응답/웹훅 수신 | 주문·회차·예약 상태 업데이트 |
