이 문서는 고객의 결제수단을 등록하고 빌링키를 발급하는 방법을 설명해요.
고객이 결제창에 카드 정보를 등록하면 Bootpay/PG사가 빌링키를 발급해요. 가맹점 서버는 카드 정보를 저장하면 안 되고, 발급 결과를 조회해 billing_key와 표시용 카드 정보만 저장해야 해요.
핵심 요약
- 권장 방식은 프론트엔드에서 Bootpay 결제창을 열어 빌링키를 발급받는 방식이에요.
- 프론트엔드는 카드 정보를 직접 저장하면 안 되고, 서버는 발급 결과를 조회해
billing_key만 저장해야 해요. - 백엔드 직접 발급은 일부 PG에서만 지원하며 카드 정보 보안 책임이 커져요.
- 발급된 빌링키는 빌링키 결제 요청 또는 예약결제에 사용할 수 있어요.
발급 흐름 한눈에
프론트엔드에서 발급하기
PG 결제창을 통해 안전하게 발급하는 방식이에요. 카드 정보가 PG사에서 직접 처리되므로 보안에 유리해요.
지원 PG사
KCP, 다날, 이니시스, 나이스페이먼츠, 토스페이먼츠, 웰컴페이먼츠, 키움페이
발급 요청
Bootpay.requestSubscription 함수를 사용하여 빌링키 발급을 위한 결제창을 띄워요.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
subscription_id |
String | 필수 | 가맹점에서 관리하는 고유 주문번호 |
method |
String | 필수 | 카드자동 값을 지정 |
price |
Integer | 선택 | 0보다 큰 값이면 빌링키 발급 즉시 결제, 0이면 빌링키만 발급 |
extra.subscribe_test_payment |
Boolean | 선택 | true 지정 시 100원 테스트 결제 후 자동 취소 |
import { Bootpay } from '@bootpay/client-js'
const response = await Bootpay.requestSubscription({
client_key: '[ Client Key ]',
pg: 'nicepay',
method: '카드자동',
order_name: '정기결제 등록',
subscription_id: 'sub_' + Date.now(),
price: 0,
user: {
username: '홍길동',
phone: '01012345678'
},
extra: {
subscribe_test_payment: true
}
})
console.log(response)javascriptval payload = Payload().apply {
clientKey = "[ Client Key ]"
pg = "nicepay"
method = "card_auto"
orderName = "정기결제 등록"
subscriptionId = "SUB-" + System.currentTimeMillis()
price = 0.0
user = User().apply {
id = "user_123"
username = "홍길동"
phone = "01012345678"
}
extra = Extra().apply {
subscribeTestPayment = true
}
}
Bootpay.init(supportFragmentManager)
.setPayload(payload)
.setEventListener(object : BootpayEventListener {
override fun onDone(data: String) {
// 발급 완료 — 서버에서 빌링키 조회
Log.d("Bootpay", "빌링키 발급 완료: " + data)
}
override fun onConfirm(data: String): Boolean = true
override fun onCancel(data: String) {
Log.d("Bootpay", "사용자 취소: " + data)
}
override fun onError(data: String) {
Log.e("Bootpay", "발급 오류: " + data)
}
override fun onIssued(data: String) {
Log.d("Bootpay", "가상계좌 발급: " + data)
}
override fun onClose() {
Bootpay.dismiss()
}
})
.requestSubscription()kotlinlet payload = Payload()
payload.clientKey = "[ Client Key ]"
payload.pg = "nicepay"
payload.method = "card_auto"
payload.orderName = "정기결제 등록"
payload.subscriptionId = "SUB-\(Int(Date().timeIntervalSince1970 * 1000))"
payload.price = 0
let user = User()
user.username = "홍길동"
user.phone = "01012345678"
payload.user = user
let extra = Extra()
extra.subscribeTestPayment = true
payload.extra = extra
Bootpay.requestSubscription(
viewController: self,
payload: payload,
isModal: true,
modalPresentationStyle: .automatic,
animated: true
)
.onDone { data in
// 발급 완료 — 서버에서 빌링키 조회
print("빌링키 발급 완료: \(data)")
}
.onConfirm { data in
return true
}
.onCancel { data in
print("사용자 취소: \(data)")
}
.onError { data in
print("발급 오류: \(data)")
}
.onClose {
print("발급창 닫힘")
}swiftPayload payload = Payload();
payload.clientKey = '[ Client Key ]';
payload.pg = 'nicepay';
payload.method = 'card_auto';
payload.orderName = '정기결제 등록';
payload.subscriptionId = 'SUB-${DateTime.now().millisecondsSinceEpoch}';
payload.price = 0;
User user = User();
user.username = '홍길동';
user.phone = '01012345678';
payload.user = user;
Extra extra = Extra();
extra.subscribeTestPayment = true;
payload.extra = extra;
Bootpay().requestSubscription(
context: context,
payload: payload,
showCloseButton: false,
onDone: (String data) {
// 발급 완료 — 서버에서 빌링키 조회
print('빌링키 발급 완료: $data');
},
onConfirm: (String data) {
return true;
},
onCancel: (String data) {
print('사용자 취소: $data');
},
onError: (String data) {
print('발급 오류: $data');
},
onClose: () {
print('발급창 닫힘');
Bootpay().dismiss(context);
},
);dart// web/index.html <head>에 JS SDK 스크립트 추가 필수
Payload payload = Payload();
payload.clientKey = '[ Client Key ]';
payload.pg = 'nicepay';
payload.method = 'card_auto';
payload.orderName = '정기결제 등록';
payload.subscriptionId = 'SUB-${DateTime.now().millisecondsSinceEpoch}';
payload.price = 0;
User user = User();
user.username = '홍길동';
user.phone = '01012345678';
payload.user = user;
Extra extra = Extra();
extra.subscribeTestPayment = true;
extra.openType = 'redirect';
extra.redirectUrl = 'https://yoursite.com/billing/result';
payload.extra = extra;
Bootpay().requestSubscription(
context: context,
payload: payload,
showCloseButton: false,
onDone: (String data) {
// redirect 방식에서는 호출되지 않음
print('빌링키 발급 완료: $data');
},
onCancel: (String data) {
print('사용자 취소: $data');
},
onError: (String data) {
print('발급 오류: $data');
},
onClose: () {
Bootpay().dismiss(context);
},
);dartimport React, { useRef } from 'react';
import { Bootpay } from 'react-native-bootpay-api';
const bootpay = useRef<Bootpay>(null);
const payload = {
client_key: '[ Client Key ]',
pg: 'nicepay',
method: 'card_auto',
order_name: '정기결제 등록',
subscription_id: 'SUB-' + Date.now(),
price: 0,
user: {
id: 'user_123',
username: '홍길동',
phone: '01012345678',
},
extra: {
subscribe_test_payment: true,
}
};
if (bootpay != null && bootpay.current != null)
bootpay.current.requestSubscription(payload);
<Bootpay ref={bootpay}
client_key={'[ Client Key ]'}
onDone={(data) => {
// 발급 완료 — 서버에서 빌링키 조회
console.log('빌링키 발급 완료', data);
}}
onConfirm={(data) => true}
onCancel={(data) => { console.log('사용자 취소', data); }}
onError={(data) => { console.log('발급 오류', data); }}
onClose={() => { console.log('발급창 닫힘'); }}
/>tsx발급 결과 처리
빌링키 발급이 완료되면 done 이벤트를 전달받아요. 보안상 빌링키는 프론트엔드로 바로 전달되지 않으므로, 백엔드에서 빌링키 조회 API를 호출하여 빌링키를 확인하고 데이터베이스에 저장해야 해요.
백엔드에서 발급하기
가맹점이 직접 카드 정보를 수집하여 백엔드에서 빌링키를 발급하는 방식이에요.
지원 PG사
나이스페이먼츠, 페이앱, 웰컴페이먼츠, 토스페이먼츠, 키움페이
API 정보
POST
https://api.bootpay.co.kr/v2/request/subscribeBasic Auth필수 카드 정보
가맹점 결제창에서 수집해야 하는 카드 정보:
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
card_no |
String | 필수 | 카드번호 (하이픈 없이) |
card_pw |
String | 필수 | 카드 비밀번호 앞 2자리 |
card_identity_no |
String | 필수 | 생년월일 6자리 또는 사업자등록번호 10자리 |
card_expire_year |
String | 필수 | 카드 유효기간 년 2자리 |
card_expire_month |
String | 필수 | 카드 유효기간 월 2자리 |
import { Bootpay } from '@bootpay/backend-js'
Bootpay.setConfiguration({
client_key: '[ Client Key ]',
secret_key: '[ Secret Key ]'
})
try {
const response = await Bootpay.requestSubscribeBillingKey({
pg: 'nicepay',
subscription_id: 'sub_' + Date.now(),
card_no: '5570********1074',
card_pw: '00',
card_identity_no: '900101',
card_expire_year: '25',
card_expire_month: '12',
order_name: '정기결제 등록'
})
// billing_key를 데이터베이스에 저장
console.log(response)
} catch (e) {
console.log(e)
}javascriptfrom bootpay_backend import BootpayBackend
import time
bootpay = BootpayBackend('APPLICATION_ID', 'PRIVATE_KEY')
response = bootpay.request_subscribe_billing_key(
pg='nicepay',
subscription_id=f'sub_{int(time.time())}',
card_no='5570********1074',
card_pw='00',
card_identity_no='900101',
card_expire_year='25',
card_expire_month='12',
order_name='정기결제 등록'
)
print(response)python<?php
require_once 'vendor/autoload.php';
use Bootpay\ServerPhp\BootpayApi;
BootpayApi::setConfiguration('APPLICATION_ID', 'PRIVATE_KEY');
$response = BootpayApi::requestSubscribeBillingKey([
'pg' => 'nicepay',
'subscription_id' => 'sub_' . time(),
'card_no' => '5570********1074',
'card_pw' => '00',
'card_identity_no' => '900101',
'card_expire_year' => '25',
'card_expire_month' => '12',
'order_name' => '정기결제 등록'
]);
// billing_key를 데이터베이스에 저장
echo $response['data']['billing_key'];phpimport java.util.HashMap;
import kr.co.bootpay.pg.Bootpay;
import kr.co.bootpay.pg.model.request.Subscribe;
Bootpay bootpay = new Bootpay("APPLICATION_ID", "PRIVATE_KEY");
try {
Subscribe subscribe = new Subscribe();
subscribe.pg = "nicepay";
subscribe.subscriptionId = "sub_" + System.currentTimeMillis();
subscribe.cardNo = "5570********1074";
subscribe.cardPw = "00";
subscribe.cardIdentityNo = "900101";
subscribe.cardExpireYear = "25";
subscribe.cardExpireMonth = "12";
subscribe.orderName = "정기결제 등록";
HashMap<String, Object> response = bootpay.getBillingKey(subscribe);
// billing_key를 데이터베이스에 저장
System.out.println(response.get("billing_key"));
} catch (Exception e) {
e.printStackTrace();
}javarequire 'bootpay'
bootpay = Bootpay::Api.new(application_id: 'APPLICATION_ID', private_key: 'PRIVATE_KEY')
response = bootpay.request(
uri: 'request/subscribe',
payload: {
pg: 'nicepay',
subscription_id: "sub_#{Time.now.to_i}",
card_no: '5570********1074',
card_pw: '00',
card_identity_no: '900101',
card_expire_year: '25',
card_expire_month: '12',
order_name: '정기결제 등록'
}
)
# billing_key를 데이터베이스에 저장
puts response.data['billing_key']rubypackage main
import (
"fmt"
"time"
"github.com/bootpay/backend-go/bootpay"
)
func main() {
api := bootpay.NewAPI("APPLICATION_ID", "PRIVATE_KEY", nil, "")
payload := bootpay.BillingKeyPayload{
Pg: "nicepay",
SubscriptionId: fmt.Sprintf("sub_%d", time.Now().UnixMilli()),
CardNo: "5570********1074",
CardPw: "00",
CardIdentityNo: "900101",
CardExpireYear: "25",
CardExpireMonth: "12",
OrderName: "정기결제 등록",
}
response, err := api.GetBillingKey(payload)
if err != nil {
fmt.Println(err)
return
}
// billing_key를 데이터베이스에 저장
fmt.Println(response)
}gousing Bootpay;
using Bootpay.models;
var api = new BootpayApi("APPLICATION_ID", "PRIVATE_KEY");
var subscribe = new Subscribe
{
pg = "nicepay",
subscriptionId = $"sub_{DateTimeOffset.Now.ToUnixTimeMilliseconds()}",
cardNo = "5570********1074",
cardPw = "00",
cardIdentityNo = "900101",
cardExpireYear = "25",
cardExpireMonth = "12",
orderName = "정기결제 등록"
};
var response = await api.GetBillingKey(subscribe);
// billing_key를 데이터베이스에 저장
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);csharp카드 정보 보안 주의
고객의 카드 정보(카드번호, 비밀번호, 유효기간 등)는 절대 데이터베이스에 저장하면 안 돼요. 여전법 위반이에요.
에러 코드
공통 에러
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
SUBSCRIBE_NEED_PG_METHOD (2300) |
pg와 method 정보가 필수이다 | pg와 method 파라미터를 입력해요 |
RC_NAME_BLANK (2003) |
상품명을 입력한다 | order_name 값을 입력해요 |
RC_O_ID_BLANK (2005) |
order_id 값을 입력한다 | subscription_id 또는 order_id를 입력해요 |
RC_PRICE_LEAST_LT (2004) |
100원보다 큰 금액을 결제할 수 있다 | price를 100원 이상으로 설정해요 |
RC_NOT_SUBSCRIBE (2057) |
정기결제 요청 정보가 아니다 | 정기결제 요청 파라미터를 확인해요 |
RC_PROVIDER_NOT_ALLOW (2104) |
가맹점이 탈퇴/차단 상태 | 부트페이 관리자에서 가맹점 상태를 확인해요 |
RC_PROVIDER_PAYMENT_NOT_ALLOW (2102) |
서비스 이용료 미납으로 결제 불가 | 부트페이 서비스 이용료를 납부해요 |
SUBSCRIBE_CARD_NO_BLANK (2311) |
카드 번호를 입력한다 | card_no 값을 입력해요 |
SUBSCRIBE_CARD_PW_BLANK (2312) |
카드 비밀번호 앞 2자리를 입력한다 | card_pw 값을 입력해요 |
SUBSCRIBE_CARD_IDENTITY_BLANK (2313) |
생년월일/사업자등록번호를 입력한다 | card_identity_no 값을 입력해요 |
SUBSCRIBE_CARD_EX_YEAR_BLANK (2314) |
카드 만료 년도를 입력한다 | card_expire_year 값을 입력해요 |
SUBSCRIBE_CARD_EX_MONTH_BLANK (2315) |
카드 만료 월을 입력한다 | card_expire_month 값을 입력해요 |
SUBSCRIBE_REQUEST_FAILED (2301) |
빌링키 발급 요청 실패 | 카드 정보를 확인 후 재시도해요 |
RC_RESOURCE_NOT_CONFIG (2017) |
결제수단이 허가/설정되지 않았다 | 부트페이 관리자에서 정기결제 설정을 확인해요 |
SUBSCRIBE_PUBLISH_NOT_READY (2303) |
빌링키 발급 대기 상태가 아니다 | 발급 요청을 처음부터 다시 진행해요 |
SUBSCRIBE_PUBLISH_M_NOT_FOUND (2305) |
빌링키 발급 기능이 없는 PG | 빌링키 발급을 지원하는 PG를 사용해요 |
다음 단계
더 읽을거리
- 자동결제와 구독을 구분하는 기준 — 빌링키 기반 결제 모델 선택 조건
- 구독 도입 전에 설계할 항목 — 요금제·청구 주기·실패 처리 기준
