결제창

PG 결제창 연동

결제창 호출부터 주문 확정까지 전체 흐름을 살펴봐요.

시작 전 확인

사전에 읽어볼만한 문서는 아래와 같아요.

  • 환경설정 — 관리자 가입, 프로젝트 생성, PG 활성화, 샌드박스 모드까지 한 번에
  • 연동키 발급 — Client Key·Secret Key 발급과 권한·만료 설정
  • 결제 연동 시작하기 — SDK 호출 → 서버 검증·승인 → 결과 처리 전체 흐름

PG 가맹 자체에 대한 의사결정(어떤 PG를 고를지, 사업군별 수수료, 가맹 심사 진행 방식)은 블로그에서 다뤄요.

핵심 요약

  • 결제 요청은 프론트엔드의 Bootpay.requestPayment() 호출로 시작해요.
  • pg·method 값으로 특정 PG사와 결제수단을 지정할 수 있고, 미지정 시 활성화된 결제수단을 고르는 통합결제창이 열려요.
  • 카드사 인증이 완료되면 confirm이 호출돼요. 이 시점은 결제 완료가 아니라 승인 직전이므로, 백엔드에서 receipt_id로 주문 상태·금액을 확인한 뒤 결제를 승인해야 해요.
  • 운영 환경에서는 백엔드가 승인 API를 호출하는 서버 승인(extra.separately_confirmed: true) 방식을 권장해요. 단, PG사에 따라 서버 승인을 지원하지 않아 클라이언트에서 Bootpay.confirm()을 호출해야 할 수 있어요.
  • 가상계좌는 결제창에서 계좌 발급까지만 끝나요. 실제 입금 완료는 웹훅으로 받아 백엔드에서 확정해야 해요.

결제 흐름 한눈에

이 문서의 핵심 흐름은 아래와 같아요. 결제창을 띄우는 일은 프론트엔드가 담당하고, 카드사 인증이 끝나면 confirm이 전달돼요. 주문 확정은 백엔드가 receipt_id 기준으로 검증·승인한 뒤 처리해야 해요.

이 문서에서는 권장 방식인 서버 승인​(extra.separately_confirmed: true)을 기준으로 설명해요. 카드사 인증이 완료되면 confirm이 호출되고, 백엔드가 승인 직전에 금액·주문·재고를 한 번 더 검증한 뒤 결제를 승인해요.

클라이언트 이벤트만 믿으면 안 돼요

confirm이나 done 같은 클라이언트 이벤트만으로 주문을 확정하면 안 돼요. 웹훅을 함께 설정해 백엔드에서 결과를 한 번 더 받아야 해요.

1SDK 설치

결제창 연동은 클라이언트 SDK​​와 서버 SDK​​를 나눠 설치해요.

  • 클라이언트 SDK: 결제창 호출, 구매자 입력, 결제 이벤트 수신
  • 서버 SDK: 결제 조회, 서버 승인, 취소, 영수증 조회, 웹훅 후속 처리

클라이언트 SDK

npm install @bootpay/client-jsbash

서버 SDK

npm install @bootpay/backend-jsbash

설치 후 호출에 사용할 Client Key가 아직 없다면 연동키 발급을 먼저 진행해요.

2결제 요청

코드 예제

아래 예제는 모두 extra.separately_confirmed: true 기준이에요. 구매자가 카드사 인증을 마치면 confirm이 호출되고, 그 다음 단계에서 결제를 승인해요.

import { Bootpay } from '@bootpay/client-js'

await Bootpay.requestPayment({
    client_key: '[ Client Key ]',
    price: 50000,
    order_name: '나이키 운동화 외 2건',
    order_id: 'order_' + Date.now(), // 테스트용. 실제로는 서버에서 생성한 주문번호 사용
    pg: 'nicepay',
    method: 'card',
    user: {
        username: '홍길동',
        phone: '01012345678',
        email: 'user@example.com'
    },
    extra: {
        separately_confirmed: true,
        open_type: 'redirect',
        // Web redirect에서는 인증 완료 후 이 서버 URL이 confirm을 먼저 받는다.
        redirect_url: 'https://yoursite.com/order/result'
    }
})javascript

요청 파라미터

파라미터 타입 필수 설명
client_key String 필수 Client Key. 부트페이 관리자 > 개발자 설정 > API 연동키에서 확인
price Integer 필수 결제 금액 (원 단위)
order_name String 필수 주문명 (예: "나이키 운동화 외 2건")
order_id String 필수 가맹점 고유 주문번호. 유니크한 값으로 생성하고 DB에 저장 권장
pg String 선택 PG사 코드 (예: "nicepay", "kcp"). 미입력 시 통합결제창으로 진행
method String / Array 선택 결제수단 코드 (예: "card", "bank", "phone", "easy"). Array로 입력 시 통합결제창으로 진행
tax_free Integer 선택 비과세 금액 (기본값: 0). 면세인 경우 price와 동일하게 입력
metadata Object 선택 결제 요청 시 전달할 추가 데이터. 결제 결과에 그대로 반환됨
user Object 선택 구매자 정보. 일부 PG는 필수
user.id String 선택 회원 아이디
user.username String 선택 회원 이름. 휴대폰결제·가상계좌·본인인증 시 선입력
user.phone String 선택 연락 가능한 전화번호. 휴대폰결제·본인인증 시 선입력. 페이앱 결제 시 필수
user.email String 선택 회원 이메일 주소
user.addr String 선택 배송지 주소. 일부 에스크로 결제 시 필요하다
items Array 선택 상품 정보 목록. qty × price 합계가 price와 일치해야 함. 페이코 직연동 시 필수
items[].id String 선택 상품 고유 아이디
items[].name String 선택 상품명
items[].qty Integer 선택 수량 (0보다 커야 함)
items[].price Integer 선택 상품 가격 (0보다 커야 함)
extra Object 선택 추가 결제 옵션. 아래 상세 참고

extra 옵션 상세

UI 노출
파라미터 타입 기본값 설명
extra.open_type String iframe 결제창 진행방식. redirect, iframe, popup. 오픈 타입 상세
extra.locale String ko 언어 설정. ko, en
extra.show_close_button Boolean false iFrame 결제 시 닫기 버튼 활성화 (SDK 4.1.5 이상)
extra.display_cash_receipt Boolean true PG 현금영수증 입력창 표시 여부
extra.display_success_result Boolean false 결제 완료 결과를 부트페이 제공 페이지로 표시
extra.display_error_result Boolean false 오류 발생 시 부트페이 제공 결과 UI에 에러 내용 표시
extra.offer_period String - 결제창에 노출되는 제공기간 정보
결제수단 제약
파라미터 타입 기본값 설명
extra.card_quota String - 5만원 이상 카드 할부 개월수. "0,2,3,4,5,6,7,8,9,10,11,12"
extra.escrow Boolean - 에스크로 결제인 경우 true
extra.enable_card_companies Array null 노출할 카드사 선택. 예: ["국민", "신한"] (KCP, 이니시스, 웰컴페이먼츠, 나이스페이만 가능)
extra.except_card_companies Array null 제외할 카드사. 예: ["국민", "신한"]
extra.enable_easy_payments Array null 노출할 간편결제. 예: ["카카오페이", "페이코"] (웰컴페이먼츠만 가능)
extra.easy_payment_method String 카드 네이버페이 결제 시 포인트/카드 선택 (나이스페이먼츠만 가능)
extra.deposit_expiration String 오늘 + 3일 가상계좌 입금 만료일. yyyy-MM-dd HH:mm:ss
extra.test_deposit Boolean false 샌드박스 가상계좌 테스트 시 true면 발급과 동시에 모의입금
실패·웹훅
파라미터 타입 기본값 설명
extra.common_event_webhook Boolean false 결제창 닫힘·만료 이벤트를 웹훅으로 수신
extra.enable_error_webhook Boolean false 결제 승인 실패 이벤트를 웹훅으로 수신
extra.separately_confirmed Boolean false true: 인증 결과로 confirm을 받고 4번 결제 승인 요청 단계에서 승인 처리
시간·복귀
파라미터 타입 기본값 설명
extra.timeout Integer - 결제 만료 시간 (분)
extra.redirect_url String - open_type이 redirect인 경우 결제 결과를 받을 URL
extra.app_scheme String - iOS 결제 후 앱 복귀를 위한 스키마

3인증 결과 수신

구매자가 카드사 인증을 마치면 인증 결과로 confirm이 호출돼요. 이 단계는 인증 결과를 받는 단계이지, 아직 결제 승인이나 주문 확정이 아니에요.

# Web SDK에서 open_type: 'redirect'를 쓰는 경우의 인증 결과 수신 방식이다.
# Web redirect에서는 confirm 콜백을 프론트엔드에서 먼저 받지 않는다.
# 구매자 인증이 완료되면 Bootpay가 아래 서버 URL로 사용자를 이동시키며, 서버가 query로 confirm을 받는다.
GET /order/result

query:
  event: confirm
  receipt_id: 6244f60c1fc19202e42e8c4e
  order_id: order_1648686604470

# 서버는 아직 주문을 확정하면 안 돼요.
# receipt_id로 주문 금액·상태·재고를 검증한 뒤 다음 단계에서 결제 승인 요청을 해야 한다.

4결제 승인 요청

인증 결과(confirm) 다음 단계는 결제 승인 요청​​이에요. 이 문서에서는 운영 환경에서 우선 권장하는 서버 승인 흐름을 기준으로 설명해요. 서버는 승인 API를 호출하기 전에 주문 기준값을 검증하고, 승인 응답까지 확인한 뒤 주문을 확정해야 해요.

서버 승인 API 호출

백엔드가 Bootpay 승인 API로 결제 승인 요청을 해요. 이 방식은 주문 검증과 결제 승인을 서버 흐름 안에서 이어서 처리할 수 있어요.

import { Bootpay } from '@bootpay/backend-js'

Bootpay.setConfiguration({
    client_key: '[ Client Key ]',
    secret_key: '[ Secret Key ]'
})

app.all('/order/result', async (req, res) => {
    // Web redirect는 인증 완료 후 GET query로 confirm이 들어온다.
    // App SDK는 onConfirm에서 같은 URL로 POST body를 보내면 된다.
    const input = req.method === 'GET' ? req.query : req.body
    const event = input.event || 'confirm'
    const { receipt_id, order_id } = input

    if (event === 'cancel') {
        return res.render('payment/cancel')
    }

    if (event === 'error') {
        return res.render('payment/fail', { message: '결제 중 오류가 발생했다.' })
    }

    if (event !== 'confirm' || !receipt_id || !order_id) {
        return res.render('payment/fail', { message: '결제 결과를 확인할 수 없다.' })
    }

    const order = await db.orders.findById(order_id)
    if (!order || order.status !== 'pending') {
        return res.render('payment/fail', { message: '승인할 수 없는 주문' })
    }

    if (!checkInventory(order)) {
        return res.render('payment/fail', { message: '재고가 부족하다.' })
    }

    const confirmed = await Bootpay.confirmPayment(receipt_id)
    if (confirmed.status !== 1 || confirmed.price !== order.price) {
        return res.render('payment/fail', { message: '결제 조회에 실패했다.' })
    }

    await db.orders.update(
        { status: 'done', bootpay_receipt_id: receipt_id },
        { where: { id: order_id } }
    )

    return res.render('payment/complete', { order_id })
})javascript
서버 승인 API를 지원하지 않는 PG라면

일부 PG사는 백엔드 승인 API를 통한 서버 승인을 지원하지 않을 수 있어요. 이때도 confirm에서 바로 승인하지 말고, 먼저 서버에 receipt_idorder_id를 보내 주문 금액·상태·재고를 검증해야 해요. 서버에서 주문 금액·상태 확인이 통과한 경우에만 클라이언트에서 Bootpay.confirm()으로 결제 승인 요청을 진행해요.

자세한 예외 흐름과 코드는 프론트엔드 승인에서 확인해요.

5결제 결과 수신

결제 승인 요청 이후에는 성공 또는 실패 결과를 수신해요. 성공하면 승인 응답 또는 done 결과를 기준으로 서버에서 receipt_id를 다시 조회하고 주문을 확정해야 해요. 실패하면 error 또는 승인 실패 응답이 들어올 수 있으므로, 주문은 미결제 상태로 유지하고 실패 사유를 화면에 안내해요.

결제 결과 데이터 예시

프론트엔드 이벤트에서 받는 데이터는 주문 확정을 위한 전체 영수증 데이터가 아니라 다음 처리를 이어갈 식별값​​이에요. 핵심은 event, receipt_id, order_id이고, PG·결제수단·SDK에 따라 부가 필드가 더 붙을 수 있어요.

{
  "event": "confirm",
  "receipt_id": "6244f60c1fc19202e42e8c4e",
  "order_id": "order_20260428_001",
  "price": 1000,
  "pg": "kcp",
  "method": "card"
}json

confirm은 결제 완료가 아니라 인증 완료/승인 직전 상태예요. done은 승인 완료 이벤트지만, 그래도 이 데이터만으로 주문을 완료하면 안 돼요. 서버에서 같은 receipt_id결제 조회를 호출해야 해요.

서버에서 주문 확정에 사용하는 상세 JSON은 결제 조회 응답 예시와 같은 형태예요. 결제창, 결제위젯, 웹훅 모두 최종 판단은 이 조회 응답의 status, price, order_id, receipt_id를 내부 DB 값과 비교해서 처리해요.

승인 실패 시

승인 API 호출이 실패했다면 결제 승인이 완료되지 않은 상태예요. 승인된 결제 건이 아니므로 가맹점이 별도로 취소 API를 호출할 대상도 없어요.

다만 카카오페이 같은 간편결제에서는 인증 과정에서 연결 계좌 → 카카오페이 포인트로 충전이 먼저 일어날 수 있어요. 이후 승인이 실패하거나 구매자가 중단하면 은행 계좌에서는 출금처럼 보일 수 있지만, 실제 결제 승인은 되지 않았고 금액은 카카오페이 포인트로 남아 있어요. 이 경우 구매자에게 “결제 취소”가 아니라 “간편결제 잔액 충전만 완료된 상태”라고 안내해야 해요.

가상계좌 입금 흐름

가상계좌(method: vbank)를 사용하지 않는다면 이 섹션은 건너뛰어도 돼요. 가상계좌는 결제창 종료 시점에 계좌 발급만 끝나며​, 실제 결제 확정은 사용자가 입금한 뒤 PG → Bootpay → 가맹점 백엔드로 전달되는 웹훅​​으로 처리해야 해요.

주문 상태는 issued 시점에 WAIT_DEPOSIT으로 저장하고, 웹훅을 받은 시점에 DONE으로 전환해요. 입금이 만기 시간 안에 들어오지 않으면 PG 측에서 자동 만료되며, extra.common_event_webhook = true를 켜 두면 만료 이벤트도 함께 받을 수 있어요. 입금 완료 웹훅을 받으려면 PG사별 입금통보 URL 설정이 필요할 수 있어요. 운영 전 PG사별 가상계좌 입금통보 설정 가이드를 확인해야 해요.

6에러 처리

결제 결과 수신 단계에서는 error도 받을 수 있어요. SDK 에러는 화면 안내와 재시도 흐름으로 연결하고, 서버·PG 에러는 에러 코드표에서 원인을 확인해요.

부트페이 에러 결과 UI 사용

결제 요청 시 extra.display_error_result: true를 함께 보내면, 오류가 발생했을 때 부트페이가 제공하는 결과 UI에서 에러 내용을 보여줘요. 초기 연동처럼 별도 에러 화면을 아직 만들지 않았다면 이 옵션으로 구매자에게 실패 사유를 안내할 수 있어요.

다만 UI 표시와 별개로, 주문 상태는 서버에서 receipt_id 기준으로 검증하거나 웹훅을 받아 확정해야 해요.

전체 에러 코드

이 페이지는 SDK에서 자주 발생하는 에러만 다뤄요. PG사·서버 에러를 포함한 전체 코드는 에러 코드표를 참고해요.

에러 처리 코드

import { Bootpay } from '@bootpay/client-js'

try {
    await Bootpay.requestPayment({
        client_key: '[ Client Key ]',
        price: 50000,
        order_name: '나이키 운동화 외 2건',
        order_id: 'order_' + Date.now(),
        pg: 'nicepay',
        method: 'card',
        extra: {
            separately_confirmed: true,
            open_type: 'redirect',
            redirect_url: 'https://yoursite.com/order/result',
            // true면 오류 발생 시 부트페이 제공 결과 UI에 에러 내용을 표시해요.
            display_error_result: true
        }
    })
} catch (data) {
    // cancel: 사용자가 결제창을 닫은 경우. 주문은 미결제 상태로 유지해야 한다.
    // error: 결제 요청·승인 과정에서 오류가 발생한 경우. error_code, pg_error_code, message를 로그로 남긴다.
    if (data.event === 'cancel') {
        keepOrderUnpaid(data)
        return
    }

    if (data.event === 'error') {
        logPaymentError({
            error_code: data.error_code,
            pg_error_code: data.pg_error_code,
            message: data.message
        })
        showPaymentError(data.message)
    }
}javascript

에러 응답 예시

{
  "event": "error",
  "error_code": "RC_RESOURCE_NOT_CONFIG",
  "message": "결제 요청하신 결제 수단이 사용 허가가 되지 않았거나 부트페이 관리자에서 설정되지 않아 결제 진행을 할 수 없다."
}json

에러 응답 필드

필드 타입 설명
event String 이벤트 유형 (error, cancel)
error_code String 부트페이 에러 코드
pg_error_code String PG사 에러 코드
message String 에러 상세 메시지

에러 발생 시나리오

1결제 요청 실패

결제 요청 시 PG사가 요구하는 필수 파라미터가 누락된 경우 발생해요.

에러 코드 설명
RC_REQUEST_FAILED 결제 요청 실패
RC_REQUEST_ERROR 결제 요청 오류

2승인 전 오류

결제 승인 직전에 PG사 측 사유로 승인이 불가능한 경우 발생해요. 카드사 점검, PG사 내부 점검, 휴대폰 소액결제 서비스 미가입 등이 해당돼요.

에러 코드 설명
RC_CONFIRM_READY_SERVER_ERROR 승인 준비 서버 오류

3승인 중 오류

결제 승인 도중 PG사 측에서 승인이 거부되는 경우예요. 한도 초과, 도난·분실 카드, 미등록 카드, 계좌이체 점검 시간, 휴대폰결제 점검 시간 등이 해당돼요.

에러 코드 설명
RC_CONFIRM_FAILED 결제 승인 실패

→ 관련 문서​: 결제 결과 수신 · 전체 에러 코드표

더 읽을거리