카드 수기결제(키인, keyin)는 결제창 없이 카드번호·유효기간 등 최소한의 카드 정보를 서버에서 직접 입력받아 결제하는 방식이에요. 공인인증서나 카드 비밀번호 전체가 필요 없어, 상담원이 전화 통화 중에 결제를 받거나 법인·기업 회원에게 월 사용액을 청구할 때 사용해요.
카드 정보만으로 빠르게 결제할 수 있는 만큼, 정보 관리가 부주의하면 위험이 커요. 그래서 PG사로부터 수기결제(키인) 이용 심사를 받아야 하며, 리스크 업종은 이용이 제한될 수 있어요. 사용 전 PG 계약과 부트페이 관리자 설정을 확인해요.
일반적인 사용 시나리오
- 상담원이 통화 중 고객에게 카드 정보를 전달받아 결제를 처리하는 경우
- 법인·기업 회원에게 해당 월의 사용 비용을 청구하는 경우
- 대면/유선 거래라 결제창을 띄우기 어려운 경우
동작 방식
카드 수기결제는 내부적으로 빌링키(자동결제 키) 방식을 그대로 사용해요. 카드 정보로 빌링키를 발급한 뒤, 그 빌링키로 원하는 금액을 결제해요.
| 단계 | 호출 | 문서 |
|---|---|---|
| 1. 카드 정보로 빌링키 발급 | POST /v2/request/subscribe |
빌링키 발급(백엔드) |
| 2. 빌링키로 결제 | POST /v2/subscribe/payment |
빌링키 결제 요청 |
- 1회 결제: 발급한 빌링키로 한 번만 결제하면 돼요.
- 반복 청구: 같은 빌링키를 저장해 두면 매월 같은 카드로 다시 청구할 수 있어요. 이 경우 자동결제 흐름과 동일해요.
카드 정보 자체는 절대 저장하지 말고, 발급된 billing_key와 표시용 카드 정보(마스킹된 카드번호·카드사)만 저장해요.
구현
서버에서 Basic Auth로 호출해요. 아래 예제는 카드 정보로 빌링키를 발급하고, 그 빌링키로 즉시 1회 결제하는 흐름이에요.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
pg |
String | Y | 수기결제 지원 PG (나이스페이먼츠 / 페이앱 / 웰컴페이먼츠 / 토스페이먼츠 등) |
method |
String | Y | 카드자동(REST) 를 지정해요. 미입력 시 서버 기본값은 카드자동(REST 미지원)이라 RC_ONLY_REST_API(2058)로 거절돼요 |
subscription_id |
String | Y | 가맹점이 부여하는 빌링키 고유 ID |
order_name |
String | Y | 주문/청구 이름 |
card_no |
String | Y | 카드번호 (하이픈 없이) |
card_pw |
String | Y | 카드 비밀번호 앞 2자리 |
card_identity_no |
String | Y | 카드 소유주 생년월일 6자리 또는 사업자등록번호 |
card_expire_year |
String | Y | 카드 만료 년도 (YY) |
card_expire_month |
String | Y | 카드 만료 월 (MM) |
# 1) 카드 정보로 빌링키 발급
curl -X POST "https://api.bootpay.co.kr/v2/request/subscribe" \
-H "Authorization: Basic $(printf '%s' 'CLIENT_KEY:SECRET_KEY' | base64)" \
-H "Content-Type: application/json" \
-d '{
"pg": "nicepay",
"method": "카드자동(REST)",
"subscription_id": "keyin_1700000000",
"order_name": "5월 이용료",
"card_no": "5570********1074",
"card_pw": "00",
"card_identity_no": "900101",
"card_expire_year": "27",
"card_expire_month": "12"
}'
# 2) 발급된 billing_key로 결제
curl -X POST "https://api.bootpay.co.kr/v2/subscribe/payment" \
-H "Authorization: Basic $(printf '%s' 'CLIENT_KEY:SECRET_KEY' | base64)" \
-H "Content-Type: application/json" \
-d '{
"billing_key": "BILLING_KEY",
"order_name": "5월 이용료",
"order_id": "keyin_order_1700000000",
"price": 50000
}'bashimport { Bootpay } from '@bootpay/backend-js'
Bootpay.setConfiguration({
client_key: process.env.BOOTPAY_CLIENT_KEY,
secret_key: process.env.BOOTPAY_SECRET_KEY,
})
// 1) 카드 정보로 빌링키 발급 (카드 정보는 저장하지 않아요)
const issued = await Bootpay.requestSubscribeBillingKey({
pg: 'nicepay',
method: '카드자동(REST)',
subscription_id: 'keyin_' + Date.now(),
order_name: '5월 이용료',
card_no: '5570********1074',
card_pw: '00',
card_identity_no: '900101', // 생년월일 6자리 또는 사업자번호
card_expire_year: '27',
card_expire_month: '12',
})
const billingKey = issued.billing_key
// 2) 빌링키로 결제 (원하는 금액만큼 1회 청구)
const paid = await Bootpay.requestSubscribePayment({
billing_key: billingKey,
order_name: '5월 이용료',
order_id: 'keyin_order_' + Date.now(),
price: 50000,
})
if (paid.status === 1) {
// 결제 완료 — 표시용 카드 정보와 billing_key만 저장 (카드 원본 정보 저장 금지)
await saveCharge({ receipt_id: paid.receipt_id, billing_key: billingKey })
}javascriptcard_no, card_pw, card_identity_no는 빌링키 발급에만 사용하고 절대 DB·로그에 남기지 않아요. 이후 청구는 발급된 billing_key로만 진행해요. 카드 정보 저장은 PCI-DSS 등 보안 규정 위반이 될 수 있어요.
빌링키 발급 응답의 주요 필드
| 필드 | 타입 | 설명 |
|---|---|---|
billing_key |
String | 이후 청구에 사용하는 빌링키 |
billing_data |
Object | 발급된 카드의 표시용 정보 (마스킹 카드번호·카드사 등). 카드 정보 원본은 담기지 않아요 |
billing_expire_at |
String | 빌링키 만료 시각 |
receipt_id |
String | 빌링키 발급 건의 영수증 ID |
subscription_id |
String | 요청 시 보낸 가맹점 빌링키 고유 ID |
status |
Number | 발급 완료는 11 |
status_locale |
String | 상태 한글 표기 |
pg |
String | 실제 발급이 이뤄진 PG명 |
method / method_symbol |
String | 결제수단 한글명 / 영문 심볼 |
method_origin / method_origin_symbol |
String | 간편결제 내부에서 실제 사용된 원 결제수단 |
published_at |
String | 빌링키 발급 시각 |
requested_at |
String | 발급 요청 시각 |
receipt_data |
Object | 발급 과정에서 별도 결제(1원 인증 등)가 있었던 경우의 결제 응답 |
빌링키 발급 응답에 카드 표시 정보가 담기는 필드는 card_data가 아니라 billing_data예요. 값이 없는 필드는 응답에서 제거되므로 필드 존재를 전제로 파싱하지 않아요.
빌링키 발급 응답과 결제 응답 구조는 빌링키 발급, 빌링키 결제 요청에서 더 자세히 확인해요.
위 예제는 billing_key, order_name, order_id, price만 쓴 최소 형태예요. POST /v2/subscribe/payment는 tax_free, card_quota(할부 개월), card_interest(무이자 여부), items, user, metadata, extra, feedback_url, content_type도 함께 받아요. 자세한 내용은 빌링키 결제 요청을 확인해요.
에러 코드
인증·권한 관련 에러는 에러 코드표를 참고해요.
| 코드 | 메시지 | 대처 방법 |
|---|---|---|
SUBSCRIBE_NEED_PG_METHOD (2300) |
정기결제 요청의 경우 pg와 method(결제수단) 정보가 반드시 있어야 합니다. | pg와 method를 모두 전달해요 |
RC_ONLY_REST_API (2058) |
현재 요청한 결제 수단은 REST API로 요청이 가능한 결제 수단이 아닙니다. | method를 카드자동(REST)로 지정해요 |
RC_INVALID_EXP_MONTH (2084) |
카드 만료월 정보를 다시 확인해주세요. ( 01월 ~ 12월까지 가능 ) | card_expire_month를 01~12로 보내요 |
SUBSCRIBE_CARD_NO_BLANK (2311) |
정기결제 요청할 카드 번호를 입력해주세요. | card_no를 확인해요 |
SUBSCRIBE_CARD_PW_BLANK (2312) |
정기결제 요청할 카드 비밀번호 앞에 2자리를 입력해주세요. | card_pw(앞 2자리)를 확인해요 |
SUBSCRIBE_CARD_IDENTITY_BLANK (2313) |
정기결제 소유주의 생년월일 혹은 사업자등록 번호를 입력해주세요. | card_identity_no를 확인해요 |
SUBSCRIBE_CARD_EX_YEAR_BLANK (2314) |
정기결제 카드 만료 년도를 입력해주세요. | card_expire_year(YY)를 확인해요 |
SUBSCRIBE_CARD_EX_MONTH_BLANK (2315) |
정기결제 카드 만료 월을 입력해주세요. | card_expire_month(MM)를 확인해요 |
RC_NAME_BLANK (2003) |
상품명을 입력해주세요. | order_name을 채워요 |
RC_O_ID_BLANK (2005) |
가맹점에서 식별 가능한 subscription_id를 입력해주세요 | subscription_id를 채워요 |
RC_NOT_SUBSCRIBE (2057) |
정기결제 요청 정보가 아닙니다. | 선택한 pg·method 조합이 빌링키 발급을 지원하는지 확인해요 |
SUBSCRIBE_REQUEST_FAILED (2301) |
정기결제 요청이 실패하였습니다. 실패사유: {원인} — 실제 실패 원인이 런타임에 붙어 내려와요 |
메시지의 실패사유를 그대로 확인해요. 수기결제 이용 심사 통과 여부와 PG 활성화도 함께 점검해요 |
SUBSCRIBE_PUBLISH_FAILED (2304) |
PG가 내려준 실패 사유가 그대로 실려요 (pg_error_code에 PG 응답 코드 동봉) |
카드 정보(번호·유효기간·식별번호)를 다시 확인하고, PG 원본 메시지를 함께 로깅해요 |
SUBSCRIBE_BK_NOT_FOUND (2309) |
빌링키 발급 내역을 찾지 못했습니다. | 발급 응답의 billing_key를 정확히 전달했는지 확인해요 |
