이 문서는 발급받은 billing_key로 미래의 특정 시점에 결제될 예약을 등록하는 방법을 설명해요.
가맹점 서버가 reserve_execute_at으로 실행 시각을 지정하고 Bootpay API에 예약을 등록하면, 예약 시각이 되면 부트페이가 저장된 빌링키로 결제를 실행해요. 가맹점 서버는 예약 등록 응답의 reserve_id를 저장하고, 실제 결제 결과는 웹훅과 결제 조회로 확정해야 해요.
핵심 요약
- 이 API는
billing_key로 미래 결제 한 건을 예약해요. - 예약 등록 성공은 결제 성공이 아니에요. 실제 결제 결과는 예약 시각 이후 웹훅으로 확인해요.
reserve_id,order_id,billing_key,reserve_execute_at,price는 조회·취소·웹훅 매칭을 위해 저장해야 해요.- 같은 빌링키로 반복 예약을 만들 수 있지만, 매 회차 예약 등록과 상태 관리는 가맹점 서버가 해야 해요.
- 결제수단 변경이나 해지 가능성이 있다면 운영 주의사항을 먼저 읽어요.
예약 API 응답은 “미래 결제 예약이 등록됐다”는 뜻이에요. 주문을 결제 완료로 바꾸는 시점은 예약 실행 후 웹훅을 받고, receipt_id로 결제를 다시 조회한 뒤예요.
예약 등록 흐름
API 엔드포인트
https://api.bootpay.co.kr/v2/subscribe/payment/reserveBasic Auth요청 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
billing_key |
String | 필수 | 부트페이에서 발급한 빌링키 |
price |
Number | 필수 | 결제 금액. 100원 이상이면서 해당 PG가 요구하는 최소 결제금액 이상이어야 해요 |
reserve_execute_at |
Date | 필수 | 예약 실행 시간 |
order_name |
String | 필수 | 상품명, 주문명. 비어 있으면 SR_ON_BLANK로 거절돼요 |
order_id |
String | 필수 | 가맹점 고유 주문번호. 비어 있으면 RC_O_ID_BLANK로 거절돼요 |
tax_free |
Number | 선택 | 비과세 금액 |
metadata |
Object | 선택 | 커스텀 데이터 (결제 완료/취소 시 반환) |
user |
Object | 선택 | 구매자 정보. 생략하면 빌링키 발급 때 입력한 구매자 정보가 쓰여요 |
items |
Array | 선택 | 상품 정보 (일부 PG/결제수단에서 필요) |
extra |
Object | 선택 | 결제 부가 옵션 |
feedback_url |
String | 선택 | 웹훅 수신 URL |
webhook_url |
String | 선택 | 웹훅 URL 필수 검사에만 쓰이고 예약에는 저장되지 않아요. 실제 웹훅은 feedback_url로 전송돼요 |
content_type |
String | 선택 | 웹훅 데이터 타입 |
예약 실행 시 부트페이가 할부 개월 수를 00(일시불)으로 고정해 결제해요. 할부 예약은 지원하지 않고, card_quota·card_interest를 보내도 반영되지 않아요.
order_id는 같은 빌링키 안에서 중복될 수 없어요. 이미 취소되었거나 실행이 끝난 예약의 order_id도 재사용할 수 없으니(SR_EXIST), 매 예약마다 새로운 값을 만들어야 해요.
tax_free는 서버가 price와 대조해 검증하지 않아요. 값이 어긋나도 등록은 되지만 정산 데이터가 틀어지므로 price 이하로 맞춰 보내는 것을 권장해요.
items를 전달하면 각 항목의 id, name, qty(1 이상), price(0 이상)가 모두 필수예요. 그리고 모든 항목의 price × qty 합계가 요청한 price와 정확히 같아야 해요.
reserve_execute_at 시간 형식
reserve_execute_at은 고객에게 안내한 결제 예정 시각과 정확히 맞춰야 해요. DB 저장 기준과 화면 표시 기준을 분리해 두면 운영이 편해요.
- UTC:
2024-06-01 12:00:00 UTC→ 한국시간 2024-06-01 21:00:00에 결제 - Timezone 포함:
2024-06-01T21:00:00 +0900→ 한국시간 2024-06-01 21:00:00에 결제
실행 시각은 현재 시각 이후부터 2개월 이내만 등록할 수 있어요. 과거 시각이면 SR_RESERVE_TIME_PAST(2500), 2개월을 넘기면 SR_RESERVE_TIME_SO_FAR_FUTURE(2516)로 거절돼요. 2개월보다 먼 미래의 청구가 필요하면 예약결제 대신 자동결제로 설계해요.
코드 예제
feedback_url, webhook_url, 관리자 통합 웹훅 URL 중 하나도 없으면 SR_WEBHOOK_URL_BLANK(2515)로 거절돼요. 관리자에 통합 웹훅을 등록하지 않았다면 요청에 feedback_url을 반드시 넣어야 해요.
import { Bootpay } from '@bootpay/backend-js'
Bootpay.setConfiguration({
client_key: '[ Client Key ]',
secret_key: '[ Secret Key ]'
})
try {
const response = await Bootpay.subscribePaymentReserve({
billing_key: '[ billing_key ]',
order_name: '예약 잔금 결제',
order_id: 'reserve_' + Date.now(),
price: 9900,
reserve_execute_at: '2025-02-01T09:00:00 +0900'
})
// response.reserve_id를 데이터베이스에 저장해요.
console.log(response)
} catch (e) {
console.log(e)
}javascriptfrom bootpay_backend import BootpayBackend
import time
bootpay = BootpayBackend(client_key='CLIENT_KEY', secret_key='SECRET_KEY')
response = bootpay.subscribe_payment_reserve(
billing_key='[ billing_key ]',
order_name='예약 잔금 결제',
order_id=f'reserve_{int(time.time())}',
price=9900,
reserve_execute_at='2025-02-01T09:00:00 +0900'
)
# response['reserve_id']를 데이터베이스에 저장해요.
print(response)pythonuse Bootpay\ServerPhp\BootpayApi;
BootpayApi::setClientKeyConfiguration('CLIENT_KEY', 'SECRET_KEY');
$response = BootpayApi::subscribePaymentReserve([
'billing_key' => '[ billing_key ]',
'order_name' => '예약 잔금 결제',
'order_id' => 'reserve_' . time(),
'price' => 9900,
'reserve_execute_at' => '2025-02-01T09:00:00 +0900',
]);
// $response['reserve_id']를 데이터베이스에 저장해요.
print_r($response);phpimport kr.co.bootpay.pg.Bootpay;
import kr.co.bootpay.pg.model.request.SubscribePayload;
Bootpay bootpay = Bootpay.withClientKey("CLIENT_KEY", "SECRET_KEY");
SubscribePayload payload = new SubscribePayload();
payload.billingKey = "[ billing_key ]";
payload.orderName = "예약 잔금 결제";
payload.orderId = "reserve_" + System.currentTimeMillis();
payload.price = 9900;
payload.reserveExecuteAt = "2025-02-01T09:00:00 +0900";
var response = bootpay.reserveSubscribe(payload);
// response.get("reserve_id")를 데이터베이스에 저장해요.
System.out.println(response);javabootpay = Bootpay::Api.new(client_key: 'CLIENT_KEY', secret_key: 'SECRET_KEY')
response = bootpay.request(
uri: 'subscribe/payment/reserve',
payload: {
billing_key: '[ billing_key ]',
order_name: '예약 잔금 결제',
order_id: "reserve_#{Time.now.to_i}",
price: 9900,
reserve_execute_at: '2025-02-01T09:00:00 +0900'
}
)
# response.data['reserve_id']를 데이터베이스에 저장해요.
puts response.datarubyimport "github.com/bootpay/backend-go/v2"
api := bootpay.NewAPIWithClientKey("CLIENT_KEY", "SECRET_KEY", nil, "")
response, err := api.ReserveSubscribe(bootpay.SubscribePayload{
BillingKey: "[ billing_key ]",
OrderName: "예약 잔금 결제",
OrderId: fmt.Sprintf("reserve_%d", time.Now().Unix()),
Price: 9900,
ReserveExecuteAt: "2025-02-01T09:00:00 +0900",
})
if err != nil {
log.Fatal(err)
}
// response["reserve_id"]를 데이터베이스에 저장해요.
fmt.Println(response)gousing Bootpay;
using Bootpay.models;
var bootpay = BootpayApi.WithClientKey("CLIENT_KEY", "SECRET_KEY");
var response = await bootpay.ReserveSubscribe(new SubscribePayload
{
billingKey = "[ billing_key ]",
orderName = "예약 잔금 결제",
orderId = $"reserve_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}",
price = 9900,
reserveExecuteAt = "2025-02-01T09:00:00 +0900"
});
var content = await response.Content.ReadAsStringAsync();
// content에서 reserve_id를 추출해 데이터베이스에 저장해요.
Console.WriteLine(content);csharp응답
성공 응답
{
"reserve_id": "6261104e1fc19202e6f9420e",
"reserve_execute_at": "2025-02-01T09:00:00+09:00"
}json저장해야 하는 값
| 필드 | 이유 |
|---|---|
reserve_id |
예약 조회·취소의 기준 ID |
order_id |
가맹점 주문/예약과 결제 결과 매칭 |
billing_key 또는 내부 billing key ID |
어떤 결제수단으로 예약했는지 추적 |
reserve_execute_at |
고객 안내와 실행 전 취소 가능성 판단 |
price |
웹훅·결제 조회 시 금액 검증 |
status |
reserved, cancelled, paid, failed 등 내부 상태 관리 |
결제 실행 결과
예약된 시간에 결제가 진행되면 웹훅으로 결과가 전달돼요.
feedback_url과content_type을 요청에 넣으면 해당 값으로 웹훅이 전송돼요.- 요청값이 비어 있으면 부트페이 관리자 → 웹훅 설정에 등록된 웹훅 URL로 전송돼요.
- 관리자 설정도 없으면 웹훅은 전달되지 않아요.
- 웹훅을 받으면
receipt_id로 결제 조회을 호출하고, DB에 저장한 금액·주문 상태와 비교한 뒤 주문을 확정해야 해요.
에러 코드
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
SUBSCRIBE_BK_NOT_FOUND (2309) |
빌링키 발급 내역을 찾지 못했다 | billing_key가 올바른지 확인해요 |
SUBSCRIBE_BK_EXPIRED (2310) |
빌링키 유효기간 만료 | 빌링키를 새로 발급받아요 |
SR_RESERVE_TIME_PAST (2500) |
예약 결제 시간이 과거이다 | reserve_execute_at을 미래 시간으로 설정해요 |
SR_RESERVE_TIME_SO_FAR_FUTURE (2516) |
예약은 2개월 이내만 가능하다 | reserve_execute_at을 현재로부터 2개월 이내로 설정해요 |
SR_EXPIRED_BK_EXECUTE_OVER (2517) |
예약 시간이 빌링키 만료시간보다 미래이다 | 빌링키 만료일 이전으로 예약하거나 빌링키를 새로 발급받아요 |
DATE_TYPE_VALID (-17) |
날짜 타입이 잘못되었다 | reserve_execute_at을 파싱 가능한 형식으로 보내요 |
SR_ON_BLANK (2502) |
자동결제 예약 상품명을 입력한다 | order_name 값을 입력해요 |
SR_PRICE_LT_100 (2514) |
예약 결제 금액은 100원 이상 | price를 100원 이상으로 설정해요 |
RC_REQUIRE_LEAST_PRICE (2080) |
PG가 요구하는 최소 결제금액보다 작다 | 메시지에 해당 PG의 최소 결제금액이 함께 내려와요. 그 금액 이상으로 설정해요 |
RC_ITEM_TYPE_INVALID (2081) |
items가 배열 형태가 아니다 | items를 객체 배열로 보내요 |
SR_BK_RESERVE_OVER (2501) |
빌링키당 대기 중인 예약이 10건 초과 | 대기(status 0) 상태 예약만 세요. 기존 예약을 취소한 후 다시 등록해요 |
SR_EXIST (2508) |
동일 빌링키에 중복 order_id | 다른 order_id를 사용해요. 취소·완료된 예약의 order_id도 재사용할 수 없어요 |
SR_WEBHOOK_URL_BLANK (2515) |
웹훅 URL을 입력한다 | feedback_url을 입력하거나 관리자에서 웹훅 URL을 설정해요 |
RC_O_ID_BLANK (2005) |
order_id 값을 입력한다 | order_id를 유니크한 값으로 입력해요 |
SR_READY_ERROR (2503) |
예약 등록 처리 중 서버 오류 | 잠시 후 재시도하고, 반복되면 부트페이에 문의해요 |
다음 단계
더 읽을거리
- 예약결제 기획 전에 정할 기준 — 사용 사례와 예약·취소 정책 설계
