체크아웃

주문서 요청

장바구니를 주문서로 바꿔 결제 직전 정보까지 고정해요.

이 페이지의 주문서 SDK 흐름과 함께 서버에서 청구서를 직접 만들거나 조회할 때는 청구서 API를 참고해요. POST /v1/invoices는 Basic 인증과 공개 client_key 호출의 응답 형태가 달라요.

Next.js 서버에서 v1 API를 연결할 때

이 페이지의 브라우저 체크아웃 호출과 서버 REST 주문 준비는 입력 계약이 달라요. 서버 BFF에서는 서버 주문 준비의 products[].option.product_option_id, 상품 선결제 금액, URL 응답 계약을 사용해요.

회원이 다른 도메인의 Bootpay 주문서로 이동할 때는 서버 prepare 요청에 고객 Bootpay-User-JWT 헤더와 response_type: 'url', use_auto_login: true를 함께 전달해요. API가 기존 /b/{client_key} 세션 경유 URL을 반환하면 브라우저는 그 URL로 이동해요. 가맹점이 암호화나 경유 URL 생성을 구현하지 않아요. 자동 로그인 계약과 배포 확인을 참고해요.

범용 /b는 기존 청구서 전용 /i/.../session과 달라요. 일반 주문번호로 청구서 경로를 조합하지 않고, __s가 포함된 API 응답 URL도 재작성하지 않아요. "세션 만료" 문구가 보이더라도 실제 오류가 ORDER_NOT_PAYMENT_READY이면 주문 상태를 확인해요. 경로·식별자 구분과 만료 후 재시도를 참고해요.

이럴 때 필요해요

상품 판매 페이지에 결제를 붙일 때, 구독 요금제 주문서를 만들 때예요.

내가 할 일: 관리자(상품 등록) + 프론트엔드(주문서 SDK) + 백엔드(주문 검증·웹훅)

Commerce SDK를 사용하여 구독/요금제 결제를 구현해요. 주문서를 통해 상품 선택부터 결제까지 한 번에 처리할 수 있어요.

빠르게 시작하려면?

체크아웃 빠른 매뉴얼 — JavaScript 최소 코드로 5분 안에 연동해요. 이 페이지는 전체 파라미터와 할인/프로모션을 다루는 Full Reference예요.

내가 하는 것

  • 관리자 상품 관리에서 상품 등록 (또는 API로 등록)
  • 서버에서 주문 완료 후 검증
  • 웹훅으로 주문 상태 동기화

Bootpay가 알아서 하는 것

  • 주문서 UI 호스팅, 결제 처리
  • 주문 생성·저장, 고객 데이터 관리
  • 웹훅 알림, 취소/환불 처리

연동 흐름

상품 등록 (관리자)

Bootpay 관리자 상품 대시보드에서 상품을 등록해요. 상품명, 가격, 구독 설정 등을 입력하면 product_id가 발급돼요.

API로 상품을 등록할 수도 있어요. 상품 등록 가이드를 참고해요.

주문서 요청 (개발자 · 프론트엔드)

// npm install @bootpay/bp-commerce-sdk
import BootpayCommerce from '@bootpay/bp-commerce-sdk'

const CLIENT_KEY = 'your-client-key'

async function selectPlan(planKey) {
    // 서버에 주문 생성 요청 (위변조 방지)
    const orderRes = await fetch('/api/orders/plan', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ plan_key: planKey })
    })
    const orderData = await orderRes.json()

    // Commerce 주문서 요청
    const response = await BootpayCommerce.requestCheckout({
        client_key: CLIENT_KEY,
        request_id: orderData.order_id,
        name: orderData.order_name,
        price: orderData.price,
        redirect_url: window.location.origin + '/result',
        user: {
            membership_type: 'guest',
            user_id: 'user_1234',
            name: '홍길동',
            phone: '01012345678',
            email: 'user@example.com'
        },
        products: [
            {
                product_id: 'product_id_here',
                quantity: 1
            }
        ],
        metadata: {
            order_id: orderData.order_id,
            plan_key: planKey
        },
        extra: {
            open_type: 'redirect',
            separately_confirmed: false,
            create_order_immediately: true
        }
    })
}javascript
구독 주문은 상품 1종만 담을 수 있어요

구독 상품이 들어 있는데 products 가 두 줄 이상이면 INVOICE_SUBSCRIPTION_REQUEST_ONLY_ONE 으로 거절돼요. 같은 상품을 두 줄로 나눠 넣어도 마찬가지예요 — 한 줄에 quantity 로 수량을 조절해요. 일반 주문에는 이 제약이 없어요.

모바일 앱 (Android · iOS · Flutter · React Native)

Commerce 주문서는 웹 리디렉트 방식이에요. 모바일 앱에서는 WebView로 주문서 페이지를 열거나, 결제 요청에서 일반 결제 SDK를 사용해요.

요청 파라미터

파라미터 타입 필수 설명
client_key String 필수 Commerce 클라이언트 키
request_id String 필수 가맹점 고유 식별자. 웹훅·주문 응답에 그대로 실려 와요
name String 필수 주문명 (예: "Professional 플랜")
price Integer 선택 서버에서 계산한 결제 금액. 생략하면 상품 기준으로 계산해요
tax_free_price Integer 선택 비과세 금액
delivery_price Integer 선택 배송비
memo String 선택 내부 메모
redirect_url String 선택 결제 완료 후 이동 URL. open_type: 'redirect' 면 사실상 필수예요
expired_at String 선택 주문서 만료 시각 (ISO 8601)
usage_api_url String 선택 사용량 기반 구독의 사용량 조회 URL
use_auto_login Boolean 선택 링크 진입 시 자동 로그인 여부
products Array 선택 주문 상품 배열
  └─ product_id String 선택 상품 ID
  └─ product_option_id String 선택 상품 옵션 ID. 옵션이 있는 상품이면 필수(INVOICE_PRODUCT_OPTION_ID_REQUIRED)
  └─ quantity Integer 선택 수량 (1 이상)
  └─ duration Integer 선택 구독 회차 수. 구독 기간이 설정된 구독 상품이면 필수 — 상품에 등록된 회차 중 하나여야 해요(INVOICE_PRODUCT_SUBSCRIPTION_DURATION_NOT_FOUND). 수시결제·사용량 구독은 생략해요
user Object 필수 구매자 정보. 없으면 INVOICE_TARGET_NOT_FOUND
  └─ membership_type String 선택 회원 유형 (member 기본 / guest)
  └─ user_id String 필수 member 면 부트페이에 등록된 고객의 user_id(몰 설정에 따라 로그인 ID·이메일도 가능) — 없는 회원이면 INVOICE_TARGET_NOT_FOUND. guest 면 가맹점 쪽 식별자이고, 처음 보는 값이면 비회원 고객을 새로 만들어요
  └─ corporate_type String 선택 member 일 때 개인·기업 구분 (individual 기본 / corporate)
  └─ name String 선택 구매자 이름
  └─ phone String 선택 구매자 전화번호
  └─ email String 선택 구매자 이메일
metadata Object 선택 가맹점 커스텀 데이터
extra Object 선택 추가 옵션
  └─ open_type String 필수 주문서를 여는 방식 (popup / iframe / redirect)
  └─ separately_confirmed Boolean 선택 분리 승인 모드. 켜면 결제창이 끝난 뒤 서버가 결제 승인을 불러야 결제가 확정돼요
  └─ create_order_immediately Boolean 선택 즉시 주문 생성 여부

결제 완료 처리 (개발자 · 프론트엔드)

결제 완료 후 redirect_url로 이동하며, URL 파라미터로 결과를 전달해요.

파라미터 설명
order_number Commerce 주문 고유 번호
event 성공 시에만 와요. done(결제 완료) · confirm(분리 승인 대기 — 서버가 결제 승인 호출)
request_id 주문서 요청 때 넘긴 가맹점 식별자
metadata 주문서 요청 때 넘긴 커스텀 데이터 (JSON 문자열)
error_code · message 실패·취소 시에만 와요 (event 없이). 결제창을 닫으면 error_code=ORDER_CLOSED 예요

프론트엔드 결과만으로 주문을 완료하면 안 돼요. 반드시 서버에서 주문 검증을 수행해요.

서버 검증 (개발자 · 서버)

GEThttps://api.bootapi.com/v1/orders/{order_number}Basic Auth
`order_id` 가 아니라 `order_number` 예요

리다이렉트 쿼리로 돌아온 order_number(예: 25071182085082524116)를 그대로 넣어요. 24자리 order_id 를 넣으면 ORDER_NOT_FOUND 가 돌아와요.

curl -X GET "https://api.bootapi.com/v1/orders/25071182085082524116" \
  -H "Authorization: Basic {base64(client_key:secret_key)}"bash
영수증 단위로 더 파고들어야 한다면

카드사·PG 응답까지 확인하려면 응답의 result_data.receipt_id 로 결제 API 를 한 번 더 조회해요 — 결제 매뉴얼: 서버 검증에서 다뤄요.

응답

주문 조회 응답

{
  "order_name": "구독상품 - 1회차",
  "price": 2800,
  "status": "payment_completed",
  "receipt_status": "receipt_success",
  "order_number": "25071182085082524116",
  "purchased_at": "2025-07-11T02:52:09Z",
  "created_at": "2025-07-11T02:52:09Z",
  "result_data": {
    "receipt_id": "68707c598bbe23d076af189c",
    "pg": "라이트페이",
    "method": "카드",
    "status": 1,
    "card_data": {
      "card_company": "KB국민",
      "card_no": "557042******1074"
    }
  },
  "order_subscriptions": [
    {
      "order_subscription_id": "68707c58b0eacea5cd974ef7",
      "status": "subscribing",
      "current_duration": 3,
      "total_subscription_duration": 4
    }
  ]
}json
필드 타입 필수 설명
order_name String 필수 주문명
price Integer 필수 결제 금액
status String 필수 주문 상태 (order_pending: 대기, payment_pending: 결제 진행 중, payment_completed: 결제 완료)
receipt_status String 필수 결제 영수증 상태 (receipt_success: 완료)
order_number String 필수 주문 고유번호
purchased_at String 필수 결제 일시 (ISO 8601)
created_at String 필수 주문 생성 일시
result_data Object 선택 결제 결과 상세
receipt_id String 필수 Bootpay 영수증 ID
pg String 필수 PG사 이름
method String 필수 결제 수단
status Integer 필수 결제 상태 (1: 완료)
card_data Object 선택 카드 결제 시 카드 정보
card_company String 필수 카드사
card_no String 필수 마스킹된 카드번호
order_subscriptions Array 선택 구독 계약 목록 (구독 주문일 때만 와요)
order_subscription_id String 필수 구독 ID
status String 필수 구독 상태 (subscribing: 구독 중)
current_duration Integer 필수 현재 구독 회차
total_subscription_duration Integer 필수 총 구독 회차
`order_id` 는 이 응답에 없어요

주문 조회 응답과 웹훅에는 주문 문서 ID(order_id)가 없어요. 가맹점 DB 와는 order_number 와 request_id 로 맞춰요.

주문 상태 관리

주문서 결제 후 가맹점 DB에서 주문 상태를 관리해요.

시점 가맹점 DB 상태 비고
주문서 요청 전 pending 주문 예비 생성 (서버에서 가격 계산)
서버 검증 성공 paid bootpay_order_number 저장
배송 시작 shipping 가맹점이 직접 변경
배송 완료 completed 가맹점이 직접 변경
환불 웹훅 수신 refunded order.cancelled 웹훅으로 확인
상세 가이드

주문 전체 흐름(시퀀스 다이어그램), DB 설계, 흔한 실수는 다음 문서를 참고해요:

상태·유형 값은 문자열이에요

응답의 status 같은 코드값은 정수가 아니라 문자열(enum)로 내려와요. 전체 목록은 커머스 Enum에서 확인해요.

에러 코드

공통 에러

인증·권한 관련 에러는 에러 코드표를 참고해요.

코드 메시지 대처 방법
INVOICE_TARGET_NOT_FOUND 주문서에 연결할 고객 정보가 없어요. user.user_id 를 넣었는지, member 라면 부트페이에 등록된 고객인지 확인해요
INVOICE_REQUEST_ID_BLANK 요청 ID가 필요해요. request_id 를 넣어요
INVOICE_EXPIRED_DATE_INVALID 만료일이 현재 시간보다 과거예요. expired_at 을 미래 시각으로 넣어요
INVOICE_PRODUCT_NOT_FOUND 존재하지 않는 상품이 포함되어 있어요. product_id 가 이 프로젝트의 판매 중 상품인지 확인해요
INVOICE_ITEM_QTY_LT_ZERO 상품 수량은 0보다 커야 해요. quantity 를 1 이상으로 넣어요
INVOICE_PRODUCT_OPTION_ID_REQUIRED 옵션이 필요한 상품이 포함되어 있어요. product_option_id 를 넣어요
INVOICE_PRODUCT_OPTION_NOT_FOUND 존재하지 않는 옵션이 포함되어 있어요. product_option_id 를 확인해요
INVOICE_SUBSCRIPTION_REQUEST_ONLY_ONE 구독 상품은 하나만 담을 수 있어요. products 를 한 줄로 줄여요
INVOICE_PRODUCT_SUBSCRIPTION_DURATION_NOT_FOUND 존재하지 않는 구독 기간이 포함되어 있어요. duration 을 상품에 등록된 회차로 넣어요
INVOICE_NEED_USAGE_API_URL 사용량 기반 구독 상품은 사용량 API URL이 필요해요. 상품에 사용량 조회 URL 을 등록해요

주문서 화면에서 결제하는 도중에 나는 에러(재고 소진·배송지 누락 등)는 이 호출의 응답이 아니라 결제 완료 처리의 error_code 로 돌아와요.

할인/프로모션 적용

서버에서 청구서를 미리 생성하여 할인을 적용할 수 있어요.

// 서버에서 청구서 생성 (할인 적용)
const invoice = await commerce.invoice.create({
    products: [{ product_id: 'product_123', quantity: 1 }],
    price: finalPrice,           // 할인 적용된 최종 금액
    original_price: originalPrice,
    user: { user_id: 'user_1234', name: '홍길동' },
    metadata: { promotion_code: 'SUMMER2024' }
})

// 프론트엔드에서 invoice_id를 request_id로 전달
BootpayCommerce.requestCheckout({
    request_id: invoice.invoice_id,
    // ...
})javascript