npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bootpay/backend-js

v2.14.0

Published

Bootpay Server Side Package for Node.js

Readme

Bootpay Server Side Package for Node.js alt text

Bootpay Node.js Server Side Library

부트페이 공식 Node.js 라이브러리 입니다 (서버사이드 용)

node환경에서 작성된 어플리케이션, 프레임워크 등에서 사용가능합니다.

  • PG 결제창 연동은 클라이언트 라이브러리에서 수행됩니다. (Javascript, Android, iOS, React Native, Flutter 등)
  • 결제 검증 및 취소, 빌링키 발급, 본인인증 등의 수행은 서버사이드에서 진행됩니다. (Java, PHP, Python, Ruby, Node.js, Go, ASP.NET 등)

목차

npm으로 설치하기

npm install --save @bootpay/backend-js

환경변수 설정

예제와 테스트는 각 SDK 루트의 .env 파일을 우선 읽습니다. 먼저 .env.example을 복사한 뒤 필요한 키만 변경하세요. .env는 gitignore 처리되어 커밋되지 않습니다.

cp .env.example .env
# BOOTPAY_ENV=production 또는 development

주요 변수:

BOOTPAY_ENV=production
BOOTPAY_PG_CLIENT_KEY_PROD=...
BOOTPAY_PG_SECRET_KEY_PROD=...
BOOTPAY_PG_CLIENT_KEY_DEV=...
BOOTPAY_PG_SECRET_KEY_DEV=...
BOOTPAY_COMMERCE_CLIENT_KEY_PROD=...
BOOTPAY_COMMERCE_SECRET_KEY_PROD=...
BOOTPAY_COMMERCE_CLIENT_KEY_PROD=...
BOOTPAY_COMMERCE_SECRET_KEY_PROD=...

변수가 없으면 SDK 테스트용 기본값(NodeJS 기준 ck/sk)으로 fallback 합니다.

사용하기

권장 인증 방식은 client_key/secret_key입니다. 기존 application_id/private_key 설정도 하위 호환을 위해 계속 동작합니다. 둘 다 설정된 경우 client_key/secret_key가 우선됩니다.

// Legacy fallback:
// Bootpay.setConfiguration({
//     application_id: process.env.BOOTPAY_APPLICATION_ID,
//     private_key: process.env.BOOTPAY_PRIVATE_KEY
// })
import { Bootpay } from "@bootpay/backend-js";

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.cancelPayment({
            receipt_id: '628b2206d01c7e00209b6087',
            cancel_price: 1000,
            cancel_username: '테스트 사용자',
            cancel_message: '테스트 취소입니다.'
        })
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

1. 토큰 발급

부트페이와 서버간 통신을 하기 위해서는 부트페이 서버로부터 토큰을 발급받아야 합니다.
발급된 토큰은 30분간 유효하며, 최초 발급일로부터 30분이 지날 경우 토큰 발급 함수를 재호출 해주셔야 합니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        let response = await Bootpay.getAccessToken()
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

2. 결제 단건 조회

결제창 및 정기결제에서 승인/취소된 결제건에 대하여 올바른 결제건인지 서버간 통신으로 결제검증을 합니다.

(async () => {
    const Bootpay = require('@bootpay/backend-js').Bootpay
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.receiptPayment('62b12f4b6262500007629fec')
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

3. 결제 취소 (전액 취소 / 부분 취소)

price를 지정하지 않으면 전액취소 됩니다.

  • 휴대폰 결제의 경우 이월될 경우 이통사 정책상 취소되지 않습니다
  • 정산받으실 금액보다 취소금액이 클 경우 PG사 정책상 취소되지 않을 수 있습니다. 이때 PG사에 문의하시면 되겠습니다.
  • 가상계좌의 경우 CMS 특약이 되어있지 않으면 취소되지 않습니다. 그러므로 결제 테스트시에는 가상계좌로 테스트 하지 않길 추천합니다.

부분취는 카드로 결제된 건만 가능하며, 일부 PG사만 지원합니다. 요청시 price에 금액을 지정하시면 되겠습니다.

  • (지원가능 PG사: 이니시스, kcp, 다날, 페이레터, 나이스페이, 카카오페이, 페이코)

간혹 개발사에서 실수로 여러번 부분취소를 보내서 여러번 취소되는 경우가 있기때문에, 부트페이에서는 부분취소 중복 요청을 막기 위해 cancel_id 라는 필드를 추가했습니다. cancel_id를 지정하시면, 해당 건에 대해 중복 요청방지가 가능합니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.cancelPayment({
            receipt_id: '628b2206d01c7e00209b6087',
            cancel_price: 1000,
            cancel_username: '테스트 사용자',
            cancel_message: '테스트 취소입니다.'
        })
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

4. 자동/빌링/정기 결제

4-1. 카드 빌링키 발급

REST API 방식으로 고객으로부터 카드 정보를 전달하여, PG사에게 빌링키를 발급받을 수 있습니다. 발급받은 빌링키를 저장하고 있다가, 원하는 시점, 원하는 금액에 결제 승인 요청하여 좀 더 자유로운 결제시나리오에 적용이 가능합니다.

  • 비인증 정기결제(REST API) 방식을 지원하는 PG사만 사용 가능합니다.
(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.requestSubscribeBillingKey({
            pg: '나이스페이',
            order_name: '테스트결제',
            subscription_id: (new Date()).getTime(), 
            card_no: '5570********1074', //카드번호 
            card_pw: '**', //카드 비밀번호 2자리 
            card_identity_no: '******', //카드 소유주 생년월일 6자리 
            card_expire_year: '**', //카드 유효기간 년 2자리 
            card_expire_month: '**', //카드 유효기간 월 2자리 
            user: {
                username: '홍길동',
                phone: '01012345678'
            }
        })
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

4-2. 계좌 빌링키 발급

REST API 방식으로 고객의 계좌 정보를 전달하여, PG사에게 빌링키 발급을 요청합니다. 요청 후 빌링키가 바로 발급되진 않고, 출금동의 확인 절차까지 진행해야 빌링키가 발급됩니다. 먼저 빌링키를 요청합니다.

(async () => {
   Bootpay.setConfiguration({
      client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
      secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
   })
   try {
      await Bootpay.getAccessToken()
      const response = await Bootpay.requestSubscribeAutomaticTransferBillingKey({
         pg: '나이스페이',
         order_name: '테스트결제',
         subscription_id: (new Date()).getTime(),
         price: 1000,
         username: '홍길동',
         bank_name: '국민',
         bank_account: '67561234123492472',
         identity_no: '901014',
         cash_receipt_identity_no: '01012341234',
         phone: '01012341234',
         user: {
            username: '홍길동',
            phone: '01012345678'
         }
      })
      console.log(response)
   } catch (e) {
      console.log(e)
   }
})()

이후 빌링키 발급 요청시 응답받은 receipt_id로, 출금 동의 확인을 요청합니다.

try {
    await Bootpay.getAccessToken()
    const response = await Bootpay.publishAutomaticTransferBillingKey('6655069ca691573f1bb9c28a')
    console.log(response)
} catch (e) {
    console.log(e)
}

4-3. 결제 요청하기

발급된 빌링키로 원하는 시점에 원하는 금액으로 결제 승인 요청을 할 수 있습니다. 잔액이 부족하거나 도난 카드 등의 특별한 건이 아니면 PG사에서 결제를 바로 승인합니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.requestSubscribeCardPayment({
            billing_key: '62b3d166cf9f6d001bd20d59',
            order_name: '테스트 결제',
            order_id: (new Date()).getTime(),
            price: 100,
            tax_free: 0
        })
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

4-4. 결제 예약하기

발급된 빌링키로 결제를 예약합니다. (빌링키당 최대 10건)

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        // console.log(new Date((new Date()).getTime() + 5000))
        await Bootpay.getAccessToken()
        const response = await Bootpay.subscribePaymentReserve({
            billing_key: '62b3d166cf9f6d001bd20d59',
            order_name: '테스트 결제',
            order_id: (new Date()).getTime(),
            price: 1000,
            reserve_execute_at: new Date((new Date()).getTime() + 5000)
        })
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

4-5. 예약 조회하기

예약시 응답받은 reserveId로 예약된 건을 조회합니다.

const reserve_id = "5b8f6a4d396fa665fdc2b5ea"
await Bootpay.subscribePaymentReserveLookup(reserve_id)

4-6. 예약 취소하기

예약시 응답받은 reserveId로 예약된 건을 취소합니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        // console.log(new Date((new Date()).getTime() + 5000))
        await Bootpay.getAccessToken()
        const response = await Bootpay.subscribePaymentReserve({
            billing_key: '62b3d166cf9f6d001bd20d59',
            order_name: '테스트 결제',
            order_id: (new Date()).getTime(),
            price: 1000,
            reserve_execute_at: new Date((new Date()).getTime() + 5000)
        })
        if (response.reserve_id !== undefined) {
            const cancel = await Bootpay.cancelSubscribeReserve(response.reserve_id)
            console.log(cancel)
        }
    } catch (e) {
        console.log(e)
    }
})()

4-7. 빌링키 삭제하기

발급된 빌링키를 삭제합니다. 삭제하더라도 예약된 결제건은 취소되지 않습니다. 예약된 결제건 취소를 원하시면 예약 취소하기를 요청하셔야 합니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.destroyBillingKey('62b3d166cf9f6d001bd20d59')
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

4-8. 빌링키 조회하기

클라이언트에서 빌링키 발급시, 보안상 클라이언트 이벤트에 빌링키를 전달해주지 않습니다. 그러므로 이 API를 통해 조회해야 합니다. 다음은 빌링키 발급 요청했던 receiptId 로 빌링키를 조회합니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.lookupSubscribeBillingKey('62b3cbbecf9f6d001bd20ce8')
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

아래는 billingKey로 조회합니다.

const response = await Bootpay.lookupBillingKey('66542dfb4d18d5fc7b43e1b6')
console.log(response)

4-9. 우선순위 결제 빌링키 조회하기

우선순위(순차) 결제에 사용되는 빌링키를 위젯키·회원 ID 와 함께 조회합니다.

(async () => {
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.lookupSequentialBillingKey('WIDGET_KEY', '66542dfb4d18d5fc7b43e1b6', 'USER_ID')
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

5. 회원 토큰 발급요청

ㅇㅇ페이 사용을 위해 가맹점 회원의 토큰을 발급합니다. 가맹점은 회원의 고유번호를 관리해야합니다. 이 토큰값을 기반으로 클라이언트에서 결제요청(payload.user_token) 하시면 되겠습니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.requestUserToken({
            user_id: 'gosomi1',
            phone:'01012345678'
        })
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

6. 서버 승인 요청

결제승인 방식은 클라이언트 승인 방식과, 서버 승인 방식으로 총 2가지가 있습니다.

클라이언트 승인 방식은 웹, 앱에서 진행하는 일반적인 방법입니다만, 경우에 따라 서버 승인 방식이 필요할 수 있습니다.

필요한 이유

  1. 100% 안정적인 결제 후 고객 안내를 위해 - 클라이언트에서 PG결제 진행 후 승인 완료될 때 onDone이 수행되지 않아 (인터넷 환경 등), 결제 이후 고객에게 안내하지 못할 수 있습니다
  2. 단일 트랜잭션의 개념이 필요할 경우 - 재고파악이 중요한 커머스를 운영할 경우 트랜잭션 개념이 필요할 수 있겠으며, 이를 위해서는 서버 승인을 사용해야 합니다.
(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.confirmPayment('62876963d01c7e00209b6028')
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

7. 본인 인증 결과 조회

다날 본인인증 후 결과값을 조회합니다. 다날 본인인증에서 통신사, 외국인여부, 전화번호 이 3가지 정보는 다날에 추가로 요청하셔야 받으실 수 있습니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.certificate('625783a6cf9f6d001d0aed19')
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()
  1. (에스크로 이용시) PG사로 배송정보 보내기 현금 거래에 한해 구매자의 안전거래를 보장하는 방법으로, 판매자와 구매자의 온라인 전자상거래가 원활하게 이루어질 수 있도록 중계해주는 매매보호서비스입니다. 국내법에 따라 전자상거래에서 반드시 적용이 되어 있어야합니다. PG에서도 에스크로 결제를 지원하며, 에스크로 결제 사용을 원하시면 PG사 가맹시에 에스크로결제를 미리 얘기하고나서 진행을 하시는 것이 수월합니다.

PG사로 배송정보( 이니시스, KCP만 지원 )를 보내서 에스크로 상태를 변경하는 API 입니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.shippingStart({
            receipt_id: "62a9379ad01c7e001f7dc1f3",
            tracking_number: '123456',
            delivery_corp: 'CJ대한통운',
            user: {
                username: '테스트',
                phone: '01000000000',
                address: '서울특별시 종로구',
                zipcode: '08490'
            }
        })
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

9-1. 현금영수증 발행하기

결제와 별개로 현금영수증을 발행하는 API 입니다.

pg 는 선택값으로, 생략하면 가맹점에 설정된 기본 PG사로 발행됩니다.

(async () => { 
    Bootpay.setConfiguration({
        client_key: process.env.BOOTPAY_PG_CLIENT_KEY_PROD,
        secret_key: process.env.BOOTPAY_PG_SECRET_KEY_PROD
    })
    try {
        await Bootpay.getAccessToken()
        const response = await Bootpay.requestCashReceipt({
            price: 1000,
            tax_free: 0,
            order_name: '테스트 상품',
            cash_receipt_type: '소득공제',
            identity_no: '0100000000',
            order_id: '' + (new Date()).getTime(),
            user: {
                username: '부트페이',
                phone: '01000000000',
                email: '[email protected]'
            }
        })
        console.log(response)
    } catch (e) {
        console.log(e)
    }
})()

10. Commerce API

부트페이 Commerce API를 사용하여 사용자, 상품, 주문, 정기구독 등을 관리할 수 있습니다.

10-1. Commerce API 초기화

const { BootpayCommerce } = require('@bootpay/backend-js')

const commerce = new BootpayCommerce({
    client_key: process.env.BOOTPAY_COMMERCE_CLIENT_KEY_PROD,
    secret_key: process.env.BOOTPAY_COMMERCE_SECRET_KEY_PROD,
    mode: 'production' // 'production' | 'development' | 'stage'
})

// 토큰 발급
await commerce.getAccessToken()

10-2. 사용자 관리

// 사용자 목록 조회 — 회원등급 필터는 membership_type 입니다 (구 이름 member_type 도 계속 지원)
const users = await commerce.user.list({ page: 1, limit: 10, membership_type: 2 })

// 사용자 상세 조회
const user = await commerce.user.detail('USER_ID')

// 회원가입
const newUser = await commerce.user.join({
    login_id: '[email protected]',
    login_pw: 'password123',
    name: '홍길동',
    email: '[email protected]',
    phone: '010-1234-5678'
})

// 사용자 정보 수정
const updatedUser = await commerce.user.update({
    user_id: 'USER_ID',
    name: '수정된 이름'
})

10-3. 상품 관리

// 상품 목록 조회
const products = await commerce.product.list({ page: 1, limit: 10 })

// 상품 생성
const product = await commerce.product.create({
    name: '테스트 상품',
    price: 10000,
    description: '상품 설명'
})

// 상품 상세 조회 — 회원 JWT 를 넘기면 회원 컨텍스트로 조회합니다
const productDetail = await commerce.product.detail('PRODUCT_ID')

// 상품 수정
const updatedProduct = await commerce.product.update({
    product_id: 'PRODUCT_ID',
    name: '수정된 상품명',
    price: 15000
})

10-4. 주문 관리

// 주문 목록 조회
const orders = await commerce.order.list({ page: 1, limit: 10 })

// 주문 상세 조회
const order = await commerce.order.detail('ORDER_ID')

// 월별 주문 조회
const monthOrders = await commerce.order.month('USER_GROUP_ID', '2024-12')

10-5. 정기구독 관리

// 정기구독 목록 조회
const subscriptions = await commerce.orderSubscription.list()

// 주문번호로 구독 계약 역조회
const byOrderNumber = await commerce.orderSubscription.list({ order_number: 'ORDER_NUMBER' })

// 정기구독 상세 조회
const subscription = await commerce.orderSubscription.detail('ORDER_SUBSCRIPTION_ID')

// 정기구독 일시정지
await commerce.orderSubscription.pause({
    order_subscription_id: 'ORDER_SUBSCRIPTION_ID',
    pause_days: 30,
    reason: '일시정지 사유'
})

// 정기구독 재개
await commerce.orderSubscription.resume({
    order_subscription_id: 'ORDER_SUBSCRIPTION_ID'
})

// 정기구독 해지
await commerce.orderSubscription.termination({
    order_subscription_id: 'ORDER_SUBSCRIPTION_ID',
    reason: '해지 사유'
})

// 수시결제(온디맨드) charge_key 즉시 결제 — supervisor 전용
// charge_key 는 body 로만 전송됩니다 (URL/query 금지 — 액세스 로그 노출 방지)
await commerce.asSupervisor().orderSubscription.supervisorCharge({
    charge_key: 'CHARGE_KEY',
    price: 1000,
    tax_free_price: 0,
    user: { id: 'USER_ID' },
    metadata: { memo: '수시결제' }
})

// 수시결제(온디맨드) charge_key 해지 — 해지 이후 해당 키로의 재결제는 불가능합니다
await commerce.asSupervisor().orderSubscription.supervisorChargeRevoke({
    charge_key: 'CHARGE_KEY'
})

구독 가격(기준금액) 변경

price 는 회차별 결제 금액의 기준금액입니다. 변경하면 결제예정(READY) 회차의 청구액이 즉시 다시 계산되고, 이후 회차도 이 금액으로 생성됩니다. 이미 결제된 회차는 그대로입니다. 0 이하는 받지 않습니다.

await commerce.orderSubscription.update({
    order_subscription_id: 'ORDER_SUBSCRIPTION_ID',
    price: 19900
})

회차별 가감산 조정

특정 회차만 금액을 가감하려면 조정항목을 사용합니다. 회차 지정 방법은 3가지입니다.

// 5회차 한 건만
await commerce.orderSubscriptionAdjustment.create('ORDER_SUBSCRIPTION_ID', {
    duration: 5,
    price: -1000,
    name: '5회차 할인'
})

// 3~7회차 각각 한 건씩 (총 5건)
await commerce.orderSubscriptionAdjustment.create('ORDER_SUBSCRIPTION_ID', {
    duration_from: 3,
    duration_to: 7,
    price: -1000,
    name: '3~7회차 할인'
})

// 3회차부터 계약 끝까지 (레코드 1건, duration_to 는 무시)
await commerce.orderSubscriptionAdjustment.create('ORDER_SUBSCRIPTION_ID', {
    duration_from: 3,
    is_unlimited: true,
    price: -1000,
    name: '3회차 이후 상시 할인'
})

상한은 계약 총회차이며, 총회차가 무제한인 계약은 60회차까지입니다. 이미 결제가 끝난 회차는 거절되며, 범위 중 한 회차라도 최종 금액이 음수면 전부 거절됩니다 (부분 반영 없음).

10-6. 청구서 관리

// 청구서 목록 조회 — 응답은 { list, count } 구조이며 limit 기본값은 24 입니다.
const invoices = await commerce.invoice.list()
const filtered = await commerce.invoice.list({
    page: 1,
    limit: 24,
    keyword: '청구서',
    cs_type: 'CS_TYPE',
    user_id: 'USER_ID',
    product_type: 1,
    css_at: '2024-01-01',
    cse_at: '2024-12-31'
})

// 청구서 상세 조회
const invoiceDetail = await commerce.invoice.detail('INVOICE_ID')

// 청구서 생성
const invoice = await commerce.invoice.create({
    user_id: 'USER_ID',
    amount: 50000,
    title: '청구서 제목'
})

// 청구서 알림 재발송 — send_types 를 생략하면 서버가 빈 배열로 처리합니다.
// ⚠️ 실제 고객에게 알림이 발송되므로 테스트 호출에 주의하세요.
await commerce.invoice.notify('INVOICE_ID', [1, 2]) // 1: SMS, 2: Email

10-6-1. 테스트 웹훅 발송

등록된 웹훅 URL 로 테스트 페이로드를 보내 연동을 확인합니다.

await commerce.webhook.sendTest()
await commerce.webhook.sendTest({ header_content_type: 1 })

10-7. 몰 설정 관리

supervisor scope 토큰(또는 키)으로만 호출할 수 있습니다.

// 몰 설정 조회
const mallSetting = await commerce.mallSetting.getMallSetting()

// 몰 설정 수정 — 전달한 값(non-null)만 서버로 전송됩니다
await commerce.mallSetting.updateMallSetting({
    name: '부트페이몰',
    description: '몰 소개',
    use_cart: true,
    cart_max_limit: 100,
    use_point: true,
    point_rate: 1
})

10-8. 쇼핑몰 회원 세션 관리

쇼핑몰(Mall) 회원 API 입니다. 단수형 user/... 경로를 사용하며, 외부 회원 연동용 users/... API(user.login, user.join, user.checkExist)와는 별개의 endpoint 입니다.

// 회원가입 — 전달한 값(non-null)만 서버로 전송되며, corporate_type 미지정시 0(개인)
await commerce.user.userJoin({
    login_id: '[email protected]',
    password: 'password123',
    name: '테스트 사용자',
    email: '[email protected]',
    phone: '010-1234-5678'
})

// 회원가입 중복 확인 — email-exist, id-exist, phone-exist, group-business-number-exist
await commerce.user.userJoinCheck('email-exist', '[email protected]')

// 로그인
const login = await commerce.user.userLogin({
    login_id: '[email protected]',
    password: 'password123'
})

// 세션 조회 / 로그아웃 — 로그인시 발급받은 회원 JWT 를 Bootpay-User-JWT 헤더로 전달합니다
await commerce.user.userSession(userJwt)
await commerce.user.userLogout(userJwt)

// 회원 JWT 를 넘기면 상품 조회에도 회원 컨텍스트가 적용됩니다
await commerce.product.products({ page: 1, limit: 20, category_id: 'CATEGORY_ID', user_jwt: userJwt })
await commerce.product.productDetail('PRODUCT_ID', userJwt)
await commerce.product.detail('PRODUCT_ID', userJwt) // productDetail 과 동작이 같습니다

// 외부 UID 로 상품 찾기
await commerce.product.products({ ex_uid: 'EX_UID' })

10-9. 가맹점 정보 조회

// 가맹점 기본 정보
const store = await commerce.store.getStore()

// 가맹점 상세 정보
const storeDetail = await commerce.store.getStoreDetail()

10-10. 알림톡 (카카오 알림톡)

카카오 알림톡 발송·템플릿·발신프로필·수신거부·웹훅 API 입니다.

⚠️ 알림톡에는 샌드박스가 없습니다. 발송(alimtalkSend.*), 발신프로필 등록(alimtalkSender.otp/create), 템플릿 등록·검수(alimtalkTemplate.create/register/inspect), 웹훅 테스트(alimtalkWebhook.test)는 실제로 나가고 과금됩니다.

알림톡 endpoint 는 인스턴스 role 과 무관하게 항상 BOOTPAY-ROLE: user 로 요청하며, Idempotency-Key 를 보내지 않습니다(서버가 읽지 않습니다 — 멱등은 발송의 ref_id 로만 성립합니다).

알림톡 요청(alimtalk/…)은 커머스 API(/v1)가 아니라 메시지 API 호스트로 나갑니다(경로에 /v1 없음). development https://dev-m.bootapi.com · stage https://stage-m.bootapi.com · production https://message.bootapi.com 이며, 필요하면 commerce.setMessageApiUrl(url) 로 현재 mode 의 주소를 바꿀 수 있습니다.

발신프로필(카카오채널) 연동

// 1. 등록에 필요한 category_code 후보 조회
const categories = await commerce.alimtalkSender.categories()

// 2. 채널 관리자폰으로 OTP 발송 — ⚠️ 실제로 문자가 나갑니다
await commerce.alimtalkSender.otp({ yellow_id: '@bootpay', phone: '01012345678' })

// 3. 발신프로필 등록 — ⚠️ 카카오에 실제 등록됩니다
//    등록이 끝나면 서버가 그룹키 등록까지 수행하므로 공식 템플릿은 바로 발송할 수 있습니다
const sender = await commerce.alimtalkSender.create({
    otp: '123456',
    yellow_id: '@bootpay',
    phone: '01012345678',
    category_code: '001001'
})

// 연동 채널 목록 / 상세 (sync: true 면 벤더에서 채널 상태를 다시 읽습니다 — 느립니다)
await commerce.alimtalkSender.list()
await commerce.alimtalkSender.detail('KSP_ID', true)

// 템플릿 미리보기용 변수 예문 사전 갱신 — 표시용이며 발송값이 아닙니다 (보낸 키만 덮어씁니다)
await commerce.alimtalkSender.variableExamples('KSP_ID', { user_name: '홍길동' })

// 채널 연동 해지 — 이 프로젝트와의 연동만 끊고 채널·템플릿은 보존됩니다
await commerce.alimtalkSender.release('KSP_ID')

발송

// 단건 발송 — ⚠️ 실제로 카카오톡이 발송되고 과금됩니다
const receipt = await commerce.alimtalkSend.send({
    template_code: 'TEMPLATE_CODE',
    to: '01012345678',
    variables: { user_name: '홍길동' }, // 템플릿의 required_variables 를 모두 채워야 합니다(아니면 3017)
    ref_id: 'order-1001',              // 멱등 키 — 같은 ref_id 로 재요청하면 기존 receipt 를 돌려줍니다
    fallback: false,                   // ⚠️ 미지정과 false 는 다릅니다 (미지정 = 프로젝트 기본값)
    reserved_at: '2026-09-01T10:00:00+09:00', // 생략하면 즉시 발송
    webhook_url: 'https://example.com/hooks/alimtalk' // 이 건의 결과 웹훅만 이 주소로 받습니다
})
// webhook_url 을 주면 발송 성공·실패·문자 대체발송·예약취소 웹훅이 그 주소로만 가고
// 프로젝트 웹훅 설정은 쓰이지 않습니다. https 만 허용하며 2,000자를 넘으면 3028 입니다.
// 서명은 프로젝트 시크릿으로 하므로, 시크릿만 필요하면 alimtalkWebhook.rotateSecret() 으로 설정 없이 발급받습니다.
// ⚠️ 같은 ref_id 로 이미 접수·성공한 건을 다시 요청하면 기존 접수가 그대로 돌아와 새 주소는 무시됩니다.

// 벌크 발송 (1요청 = N수신자) — ⚠️ 수신자 수만큼 실제 발송되고 과금됩니다
// 쿼터를 넘으면 요청 시점에 전체 거부되고(3022), 수신거부 번호는 skipped 로 과금되지 않습니다
await commerce.alimtalkSend.bulk({
    template_code: 'TEMPLATE_CODE',
    recipients: [
        { to: '01012345678', ref_id: 'bulk-0001', variables: { user_name: '홍길동' } },
        { to: '01087654321', ref_id: 'bulk-0002', variables: { user_name: '김철수' } }
    ],
    // webhook_url 도 요청 단위 하나입니다 — 이 요청으로 나간 모든 수신자 건의 결과 웹훅이 그 주소로 갑니다.
    // 형식이 틀리면(https 아님·2,000자 초과) 요청 전체가 3028 로 거부됩니다.
    webhook_url: 'https://example.com/hooks/alimtalk'
})

// 예약 발송 취소 — 접수(READY) 상태의 예약 건만 취소할 수 있습니다(전송 시작 후에는 3023)
await commerce.alimtalkSend.cancel('RECEIPT_ID')

발송내역 · 집계

// 발송내역 — ⚠️ 기간 기본값은 최근 30일, 최대 조회 폭은 92일입니다.
//    초과분은 시작일을 당겨 잘라내므로 실제 적용 구간은 응답의 period 로 확인하세요.
await commerce.alimtalkMessage.list({ status: 'success', page: 1, limit: 20 })

// 기간 집계 — billing.unit_price_source 가 'default' 면 잠정 단가입니다(확정 청구액이 아닙니다)
await commerce.alimtalkMessage.stats({ s_at: '2026-08-01', e_at: '2026-08-27' })

// 단건 발송 결과 — 실패 사유는 error_code · error_message 에 담깁니다
await commerce.alimtalkMessage.detail('RECEIPT_ID')

부트페이 공식 템플릿

부트페이가 미리 카카오 승인을 받아 둔 템플릿이라, 채널을 연동했다면 검수 없이 즉시 발송할 수 있습니다.

// 검색 — msg_type 은 BA(기본형)·EX(부가정보형)만 존재합니다
await commerce.alimtalkOfficial.list({ keyword: '주문', page: 1, per: 20 })

// 보내려는 문구로 추천받기 (유사도 score 0~1 내림차순)
await commerce.alimtalkOfficial.recommend({ text: '주문하신 상품이 발송되었습니다.' })

// 상세 조회
await commerce.alimtalkOfficial.detail('OFFICIAL_CODE')

자체 템플릿

// ⚠️ register 를 false 로 주지 않으면 생성 즉시 대행사·카카오에 실제 등록됩니다.
//    초안으로 만들고 확인한 뒤 register() 로 올리는 것을 권장합니다.
const draft = await commerce.alimtalkTemplate.create({
    ksp_id: 'KSP_ID',
    name: '주문완료 안내',
    content: '#{user_name}님, 주문이 완료되었습니다.', // 변수는 #{변수명} 형식, 최대 40개
    register: false,
    msg_type: 'BA',        // BA(기본형)·EX(부가정보형)·AD(채널추가형)·MI(복합형)
    emphasize_type: 'NONE' // NONE·TEXT(강조표기형)·IMAGE(이미지형)·ITEM_LIST(아이템리스트형)
})

// 목록 — ins: 1 REG / 2 REQ / 3 APR(승인) / 4 KRR / 5 REJ. ⚠️ 페이지네이션이 없습니다
await commerce.alimtalkTemplate.list({ ins: 3, sort: 'latest' })

// 상세 — ⚠️ 서버 기본 sync 가 true 라 조회만 해도 벤더 동기화가 일어납니다.
//    초안은 sync 를 false 로 주세요
await commerce.alimtalkTemplate.detail('TEMPLATE_ID', false)

// 수정 — ⚠️ 부분 수정이 아닙니다. 보내지 않은 필드는 null 로 덮어써지므로 항상 전체 필드를 보내세요
await commerce.alimtalkTemplate.update('TEMPLATE_ID', {
    name: '주문완료 안내',
    content: '#{user_name}님, 주문이 완료되었습니다. 감사합니다.',
    msg_type: 'BA',
    emphasize_type: 'NONE'
})

// 대행사 등록 → 검수 요청 — ⚠️ 검수는 카카오에 실제 요청되며 취소할 수 없습니다
await commerce.alimtalkTemplate.register('TEMPLATE_ID')
await commerce.alimtalkTemplate.inspect('TEMPLATE_ID')

// 삭제 — 승인(APR) 템플릿은 카카오가 거부하므로 500(3013)이 오고 템플릿은 남습니다
await commerce.alimtalkTemplate.delete('TEMPLATE_ID')

// 이미지 업로드 — 돌려받은 image_url 을 storage_image_url 로 넘깁니다
//   본문 이미지: jpg/png · 500KB 이하 · 가로 500px 이상 · 2:1
//   하이라이트 썸네일: jpg/png · 500KB 이하 · 가로 108px 이상 · 1:1 (규격이 다릅니다)
await commerce.alimtalkTemplate.image('./banner.png')
await commerce.alimtalkTemplate.highlightImage('./thumb.png')

// 내보내기 — SDK 기본 format 은 json 입니다 (서버 기본 csv 는 JSON 이 아니라 파싱이 깨집니다)
await commerce.alimtalkTemplate.export({ scope: 'private', include_content: true })

// csv 는 파싱하지 않고 원문 문자열로 받습니다
const csv = await commerce.alimtalkTemplate.export({ format: 'csv' })
console.log(csv.content_type, csv.body)

수신거부

부트페이 전역(global) 차단과 내 프로젝트 차단을 발송 판정과 같은 기준으로 다룹니다.

// 목록 — phone 은 숫자만 남겨 부분일치로 찾습니다(정확 매칭이 아닙니다)
await commerce.alimtalkOptout.list({ page: 1 })

// 등록 (멱등)
await commerce.alimtalkOptout.create({ phone: '01012345678', reason: '고객 요청' })

// 발송 전 사전 확인 — 벌크에서 skipped 로 낭비될 건을 미리 뺄 수 있습니다 (1회 최대 1,000건)
await commerce.alimtalkOptout.check({ phones: ['01012345678', '01087654321'] })

// 해제 — ⚠️ 전역 차단은 해제되지 않고 global_blocked: true 로 알려 줍니다
await commerce.alimtalkOptout.release('01012345678')

알림톡 웹훅

⚠️ 주문·구독 통합 웹훅(commerce.webhook.sendTest)과 완전히 별개입니다. 알림톡 이벤트를 기존 주문 웹훅 URL 로 태우면 수신 서버가 모르는 payload 를 받아 기존 연동이 깨집니다.

서명 검증: X-Bootpay-Signature: sha256=HMAC_SHA256(secret, "{X-Bootpay-Timestamp}.{raw_body}") — 타임스탬프가 5분 이상 지난 요청은 거부하세요(replay 방지).

// 설정 조회 — 시크릿은 앞 12자만 노출됩니다. 미설정이면 { configured: false }
await commerce.alimtalkWebhook.detail()

// 설정 저장 — url 은 https 만 허용합니다(아니면 3028)
// events: 300 발송접수(기본 미구독) / 301 전달성공 / 302 전달실패 / 303 예약취소 /
//         304 문자(LMS) 대체발송 전환 / 310 검수승인 / 311 검수반려 / 320 수신거부 등록(기본 미구독)
// 비우면 기본 구독셋(301·302·303·304·310·311)이 적용됩니다
await commerce.alimtalkWebhook.update({
    url: 'https://example.com/alimtalk/webhook',
    events: [301, 302, 304, 310, 311],
    enabled: true
})

// 테스트 이벤트 1건 발송 — ⚠️ 설정된 URL 로 실제 HTTP 요청이 나갑니다
await commerce.alimtalkWebhook.test()

// 서명 시크릿 재발급 — ⚠️ 이 응답에서만 secret 원문을 돌려줍니다
await commerce.alimtalkWebhook.rotateSecret()

// 전송 이력 (성공·실패 모두)
await commerce.alimtalkWebhook.deliveries({ page: 1, limit: 20 })

10-11. 게시판 (공지사항 · FAQ · 1:1문의 · 상품문의 · 상품평)

몰 게시판 API 입니다. 조회는 고객 모드(BOOTPAY-ROLE: user)가 기본이고, 운영자로 볼 때는 각 메서드에 supervisor: true 를 넘깁니다(전송 값이 아니라 헤더로 나갑니다).

⚠️ 고객 모드 조회는 몰 설정에서 해당 게시판 사용여부가 꺼져 있으면 BOARD_FEATURE_DISABLED 로 거절됩니다. ⚠️ 회원 식별은 user_id · login_id · user_jwt 중 하나입니다. user_jwt 는 Bootpay-User-JWT 헤더로 나갑니다.

공지사항 · FAQ

등록·수정·삭제는 supervisor 전용입니다.

// 목록 — 기본 page 1 · limit 20. 운영자는 view: 'all' 로 비공개 글까지 봅니다
await commerce.notice.list({ page: 1, limit: 20 })
await commerce.notice.list({ view: 'all', supervisor: true })
await commerce.faq.list({ keyword: '배송' })

// 단건 — 비공개 글은 supervisor: true 일 때만 보입니다(아니면 404 POST_NOT_FOUND)
await commerce.notice.detail('NOTICE_ID')
await commerce.faq.detail('FAQ_ID', { supervisor: true })

// 등록 / 수정 / 삭제 (supervisor 전용)
// ⚠️ 수정은 보낸 필드만 바뀌고, images 는 보내면 목록 전체를 교체합니다([] 면 모두 삭제)
await commerce.notice.create({ title: 'SDK 테스트 공지', content: '내용', is_notice: true, is_display: true })
await commerce.notice.update('NOTICE_ID', { title: 'SDK 테스트 공지 (수정)', is_display: false })
await commerce.notice.delete('NOTICE_ID')

await commerce.faq.create({ title: '배송은 얼마나 걸리나요?', content: '2~3일 내 도착합니다.', is_display: true })
await commerce.faq.update('FAQ_ID', { content: '1~2일 내 도착합니다.' })
await commerce.faq.delete('FAQ_ID')

1:1 문의

// 회원 본인 문의만 조회 / 운영자는 몰 전체 (answered: true 답변완료 · false 미답변 · 미지정 전체)
await commerce.inquiry.list({ user_id: 'USER_ID', page: 1, limit: 20 })
await commerce.inquiry.list({ answered: false, supervisor: true })

await commerce.inquiry.detail('INQUIRY_ID', { user_id: 'USER_ID' })

// 작성 — product_id 는 이 몰의 상품, order_id 는 작성 회원의 주문이어야 합니다
await commerce.inquiry.create({ user_id: 'USER_ID', title: '배송 문의', content: '언제 출고되나요?' })

// 수정은 답변 전만 가능합니다 (답변이 달렸으면 409 INQUIRY_ALREADY_ANSWERED)
await commerce.inquiry.update('INQUIRY_ID', { user_id: 'USER_ID', content: '출고일을 알려주세요.' })
await commerce.inquiry.delete('INQUIRY_ID', { user_id: 'USER_ID' })

// 답변 등록·수정 (supervisor 전용) — 없으면 만들고 있으면 바꿉니다
await commerce.inquiry.answer('INQUIRY_ID', '내일 출고 예정입니다. 감사합니다.')

상품문의

// 고객 모드는 product_id 필수. 다른 사람의 비밀글은 is_viewable: false 로 본문 없이 옵니다
await commerce.productQna.list({ product_id: 'PRODUCT_ID', page: 1, limit: 20 })
await commerce.productQna.list({ view: 'all', supervisor: true })

await commerce.productQna.detail('PRODUCT_QNA_ID', { user_id: 'USER_ID' })

// 회원 작성
await commerce.productQna.create({
    product_id: 'PRODUCT_ID',
    user_id: 'USER_ID',
    title: '사이즈 문의',
    content: '어떤 사이즈가 맞을까요?',
    is_secret: false
})

// 비회원 작성 — 몰이 비회원 작성을 허용할 때만 가능합니다
await commerce.productQna.create({
    product_id: 'PRODUCT_ID',
    title: '재입고 문의',
    content: '재입고 예정이 있나요?',
    is_secret: true,
    guest_name: '비회원',
    guest_password: '1234'
})

await commerce.productQna.update('PRODUCT_QNA_ID', { user_id: 'USER_ID', content: '수정한 문의 내용' })
// guest_password 는 액세스 로그 노출을 막기 위해 쿼리가 아니라 본문으로 나갑니다
await commerce.productQna.delete('PRODUCT_QNA_ID', { guest_password: '1234' })

// 답변 등록·수정 (supervisor 전용)
await commerce.productQna.answer('PRODUCT_QNA_ID', '정사이즈로 M 을 추천드립니다.')

상품평

상품 상세에 노출하는 공개 상품평 목록은 기존 GET /v1/products/{product_id}/reviews 를 씁니다.

// 회원이 쓴 상품평 / 운영자는 몰 전체
await commerce.productReview.list({ user_id: 'USER_ID', page: 1, limit: 20 })
await commerce.productReview.list({ view: 'all', supervisor: true, product_id: 'PRODUCT_ID' })

await commerce.productReview.detail('PRODUCT_REVIEW_ID', { user_id: 'USER_ID' })

// 작성 — order_id 는 회원의 구매확정 주문, product_id 는 그 주문에 담긴 상품이어야 합니다
// images 는 사진 URL 최대 5개입니다 (파일 업로드는 지원하지 않습니다)
await commerce.productReview.create({
    user_id: 'USER_ID',
    order_id: 'ORDER_ID',
    product_id: 'PRODUCT_ID',
    rating: 5,
    content: '배송도 빠르고 품질도 좋아요.',
    images: ['https://example.com/review-1.jpg']
})

// 수정은 작성 후 7일 이내만 가능합니다 (지나면 REVIEW_EDIT_TIME_EXPIRED)
await commerce.productReview.update('PRODUCT_REVIEW_ID', { user_id: 'USER_ID', rating: 4, content: '수정한 후기' })

// 삭제 — 지급된 적립금 회수·상품 통계 차감이 함께 일어납니다
await commerce.productReview.delete('PRODUCT_REVIEW_ID', { user_id: 'USER_ID' })
await commerce.productReview.delete('PRODUCT_REVIEW_ID', { reason: '부적절한 내용', supervisor: true })

// 판매자 답글 등록·수정 (supervisor 전용, 10자 이상)
await commerce.productReview.reply('PRODUCT_REVIEW_ID', '소중한 후기 감사합니다. 또 방문해주세요!')

더 자세한 Commerce API 사용 예제는 test/commerce 디렉토리를 참고해주세요.

Example 프로젝트

적용한 샘플 프로젝트을 참조해주세요

Documentation

기술문의

부트페이 홈페이지에서 문의해주세요!

License

MIT License.