참고

데이터 모델 설계

저장할 값과 저장하지 말아야 할 값을 먼저 나눠봐요.

이 문서는 “이 스키마 그대로 만들라”는 정답지가 아니에요. Bootpay 결제 SDK를 붙일 때 가맹점 서버가 어떤 값을 확인하고, 어떤 값을 DB에 남겨야 하는지​​를 예시 코드와 테이블로 표현한 안내예요.

이미 주문 테이블이 있다면 그대로 쓰고, 아래 필드는 서비스 상태·정산·CS·취소 처리에 필요한 만큼만 매핑해요.

먼저 나눌 책임

결제 API는 결제 트랜잭션을 처리하고, 주문·상품·회원·구독 회차 같은 서비스 로직은 가맹점 서버가 관리해요.

구분 Bootpay가 제공하는 값/기능 가맹점 서버가 해야 할 일
단건 결제 receipt_id, 결제 상태, 금액, 결제수단, 취소 API, 웹훅 내부 주문과 매핑, 금액·상태 검증, 주문 상태 저장
자동결제 billing_key, 빌링키 조회, 빌링키 결제 요청 빌링키 저장, 회원/구독과 연결, 청구 시점 결정
예약결제 예약 등록·조회·취소 API, 실행 결과 웹훅 예약 ID 저장, 예약 상태 관리, 실행 결과 반영
카드 원본 정보 PG/카드사가 처리 카드번호 전체·CVC·비밀번호·유효기간을 저장하지 않음
커머스 SDK를 쓰는 경우

주문(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 결제 결과 응답/웹훅 수신 주문·회차·예약 상태 업데이트