빠른 매뉴얼

자동결제

저장된 결제수단으로 원하는 시점에 원하는 금액을 청구해요.

이 문서는 빌링키 발급부터 저장, 첫 결제, 회차 청구까지 빠르게 이어보는 매뉴얼이에요. 빌링키 저장 설계·조회·삭제·청구 API 세부 옵션은 자동결제 상세 문서에서 확인해요.

자동결제는 빌링키​​로 원하는 시점에 결제 승인을 요청하는 방식이에요. 고객이 결제창에 카유생비​(카드번호, 유효기간, 생년월일, 비밀번호 앞 2자리)를 입력하면 PG/카드사가 카드 정보를 확인하고 빌링키를 발급해요. 가맹점 서버는 카드 정보를 직접 저장할 수 없기 때문에, 실제 카드 정보 대신 결제에 사용할 수 있는 암호화된 식별값을 저장해야 해요. 이 값이 빌링키예요.

빌링키 발급 방식은 두 가지예요. 보통은 프론트엔드에서 PG 결제창을 띄워 고객이 직접 입력하는 방식​​을 써요. 일부 PG에서는 백엔드 REST API로 카드 정보를 전달해 직접 발급하는 방식​​도 지원해요. 어느 방식이든 최종적으로는 백엔드가 billing_key를 저장해야 해요. 이후 서버는 저장된 빌링키로 Bootpay API에 결제 승인을 요청해야 해요. 빌링키가 유효하면 청구 금액과 요청 주기는 가맹점 정책에 맞게 정할 수 있어요.

데모 영상

자동결제 운영 방식 두 가지

빌링키를 저장한 뒤 자동결제를 운영하는 방식은 두 가지예요. 차이는 누가 회차 실행 시점을 들고 있느냐예요.

  • A. 서버 cron으로 매번 빌링키 결제 요청 (이 빠른 매뉴얼) — 매 회차마다 서버 스케줄러가 Bootpay 승인 API를 호출해야 해요. 회차 주기 변경·일할 청구·재시도 정책을 자유롭게 바꿔야 할 때 적합.
  • B. 예약결제를 회차마다 반복 등록 (예약결제) — 결제일마다 예약을 걸어두면 PG사가 시간 맞춰 자체 실행해요. 자체 스케줄러 없이 굴리고 싶을 때 적합.

구독 시 금액 변경, 취소가 빈번히 발생하면 A, 결제일이 고정된 멤버십·구독이면 B를 권장해요.

구독 SDK를 쓸지

회차 관리·조정·해지까지 Bootpay에 맡기려면 커머스 구독 가이드로 가요. 이 빠른 매뉴얼은 "회차 로직을 서비스가 직접 짜요"는 전제예요.

연동 흐름

빌링키 발급은 A 또는 B 중 하나를 선택해요. 그 뒤로는 서버가 스케줄러·청구·재시도·해지를 모두 직접 처리해야 해요​. 예약결제와 달리 PG사가 회차 결제를 자동 실행하지 않아요.

가맹점 측 DB 스키마(빌링키 + 회차 관리 테이블·인덱스·status 전이)는 데이터 저장 설계에서 다뤄요. 아래 코드는 그 스키마를 사용한다고 가정해요.

구현 순서

빌링키 발급 A — PG 결제창에서 발급 (프론트엔드)

권장 방식이에요. 프론트엔드에서 Bootpay SDK로 PG 결제창을 띄우고, 고객이 결제창에 카유생비를 입력하면 빌링키 발급이 진행돼요. 발급 완료 후에는 receipt_id를 백엔드로 보내고, 서버에서 빌링키를 조회해 저장해야 해요.

async function registerCard() {
    const response = await Bootpay.requestSubscription({
        application_id: 'YOUR_CLIENT_KEY',
        pg: '나이스페이',
        method: '카드자동',
        subscription_id: 'sub_' + Date.now(),
        price: 0,  // 빌링키만 발급
        order_name: '정기결제 카드 등록',
        user: { username: '홍길동', phone: '01012345678' },
        extra: { subscribe_test_payment: true },
    })

    if (response.event === 'done') {
        await fetch('/api/recurring/register', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                receipt_id: response.receipt_id,
                plan: 'monthly',
            }),
        })
    }
}javascript

상세 문서: 빌링키 발급

빌링키 발급 B — REST API로 직접 발급 (백엔드, 선택)

일부 PG는 백엔드 REST API로 빌링키를 직접 발급할 수 있어요. 이 방식은 가맹점 화면에서 카드 정보를 입력받아 서버로 전달하고, 서버가 Bootpay API에 빌링키 발급을 요청해요.

REST API 직접 발급 지원 PG사

나이스페이먼츠, 페이앱, 웰컴페이먼츠, 토스페이먼츠, 키움페이

카드번호·유효기간·생년월일·비밀번호 앞 2자리를 가맹점 시스템이 직접 취급하므로, 지원 PG 여부와 보안 책임을 먼저 확인해야 해요. 가능하면 A 방식처럼 PG 결제창에서 입력받는 흐름을 우선 검토해요.

app.post('/api/billing-key/issue', auth, async (req, res) => {
    const { card_no, card_pw, card_identity_no, card_expire_year, card_expire_month } = req.body

    const info = await Bootpay.requestSubscribeBillingKey({
        pg: '나이스페이',
        subscription_id: `sub_${req.user.id}_${Date.now()}`,
        order_name: '정기결제 카드 등록',
        card_no,
        card_pw,
        card_identity_no,
        card_expire_year,
        card_expire_month,
    })

    await db.billingKeys.create({
        user_id: req.user.id,
        billing_key: info.billing_key,
        card_name: info.card_name,
        card_last4: info.card_no?.slice(-4),
        status: 'active'
    })

    res.json({ success: true })
})javascript

상세 문서: 백엔드에서 발급하기

빌링키 저장 + 첫 결제 (백엔드)

A 방식은 프론트엔드에서 받은 receipt_id로 빌링키를 조회한 뒤 저장해야 해요. B 방식은 발급 API 응답에 billing_key가 들어오므로 조회 단계 없이 저장할 수 있어요.

아래 예시는 A 방식 기준이에요. lookupSubscribeBillingKey로 발급된 빌링키를 조회하고, requestSubscribePayment로 첫 결제를 실행해요.

const PLANS = {
    monthly: { name: '월간 구독', price: 9900, interval_days: 30 },
    yearly: { name: '연간 구독', price: 99000, interval_days: 365 }
}

app.post('/api/recurring/register', auth, async (req, res) => {
    const { receipt_id, plan } = req.body
    const planInfo = PLANS[plan]
    if (!planInfo) return res.status(400).json({ error: '잘못된 플랜' })

    // 빌링키 조회·저장
    const info = await Bootpay.lookupSubscribeBillingKey(receipt_id)

    await db.billingKeys.create({
        user_id: req.user.id,
        billing_key: info.billing_key,
        card_name: info.card_name,
        card_last4: info.card_no.slice(-4)
    })

    // 첫 결제 실행
    const payment = await Bootpay.requestSubscribePayment({
        billing_key: info.billing_key,
        price: planInfo.price,
        order_name: planInfo.name,
        order_id: `recurring_${req.user.id}_${Date.now()}`
    })

    if (payment.status === 1) {
        // 다음 결제일 등록
        const nextDate = new Date()
        nextDate.setDate(nextDate.getDate() + planInfo.interval_days)

        await db.recurringPayments.create({
            user_id: req.user.id,
            plan,
            amount: planInfo.price,
            next_payment_at: nextDate,
            receipt_id: payment.receipt_id,
            status: 'active'
        })

        res.json({ success: true, next_payment_at: nextDate })
    } else {
        res.json({ success: false, error: '첫 결제 실패' })
    }
})javascript

상세 문서: 빌링키 조회 · 빌링키 결제 요청 — 7개 언어 예제를 확인해요.

회차 자동 결제 — 스케줄러 (백엔드)

매일 실행되는 스케줄러(cron)로 결제일이 된 건을 처리해야 해요. 스케줄러·큐·재시도·이메일·일시정지 정책은 각 백엔드 프레임워크의 방식대로 구현하고, 스케줄러 안에서는 저장된 billing_key로 결제 요청 API를 호출해야 해요.

const payment = await Bootpay.requestSubscribePayment({
    billing_key: recurring.billing_key,
    price: recurring.amount,
    order_name: recurring.order_name,
    order_id: `recurring_${recurring.user_id}_${Date.now()}`
})

if (payment.status === 1) {
    // receipt_id 저장, 다음 결제일 갱신
    await markRecurringPaid(recurring, payment.receipt_id)
} else {
    await handlePaymentFailure(recurring)
}javascript

스케줄러는 서버 스택에서 쓰는 표준 도구를 붙이면 돼요. Python은 APSchedulerCelery beat, Java는 Spring @ScheduledQuartz, Ruby는 wheneverSidekiq, Go는 robfig/cron, .NET은 IHostedService를 사용할 수 있어요.

대안 — 자체 cron 대신 예약결제 반복

이 cron 코드를 굴리기 싫다면, 같은 빌링키로 결제일마다 예약결제를 등록​​해 PG사가 실행하게 할 수도 있어요. → 예약결제

구독 해지 (백엔드)

app.post('/api/recurring/cancel', auth, async (req, res) => {
    const recurring = await db.recurringPayments.findOne({
        where: { user_id: req.user.id, status: ['active', 'paused'] }
    })
    if (!recurring) return res.status(404).json({ error: '활성 구독 없음' })

    // 구독 상태 변경 (현재 주기 끝까지는 서비스 유지 가능)
    await recurring.update({ status: 'cancelled' })

    // 빌링키 삭제 (선택 — 재구독 가능하게 하려면 유지)
    // await Bootpay.destroyBillingKey(billingKey.billing_key)

    res.json({ success: true, message: '구독이 해지되었습니다' })
})javascript

상세 문서: 빌링키 삭제

마이페이지 — 구독 조회 (백엔드)

app.get('/api/my/recurring', auth, async (req, res) => {
    const recurring = await db.recurringPayments.findOne({
        where: { user_id: req.user.id, status: ['active', 'paused'] }
    })

    if (!recurring) return res.json({ active: false })

    const billingKey = await db.billingKeys.findOne({
        where: { user_id: req.user.id, status: 'active' }
    })

    res.json({
        active: true,
        plan: recurring.plan,
        amount: recurring.amount,
        status: recurring.status,
        next_payment_at: recurring.next_payment_at,
        card: billingKey ? {
            name: billingKey.card_name,
            last4: billingKey.card_last4
        } : null
    })
})javascript

다음 단계

더 읽을거리