브랜드페이는 가맹점에서 관리하는 로그인 회원에게 저장된 결제수단을 연결해 주는 기능이에요. 별도 결제 SDK를 새로 붙이는 방식이 아니라, 이미 연동한 결제위젯을 회원 기반 결제로 확장해요.
이 문서는 브랜드페이를 붙이기 위해 결제위젯 위에 추가해야 하는 설정과 토큰 전달만 다뤄요. 아직 기본 위젯 연동이 끝나지 않았다면 결제위젯 연동을 먼저 진행해요.
| 위치 | 해야 할 일 |
|---|---|
| 관리자 | 사용할 위젯에서 브랜드페이 결제수단을 켜요 |
| 백엔드 | 로그인 회원 기준으로 user_token을 발급해요 |
| 프론트엔드/앱 | 결제위젯 렌더링 payload에 user_token을 전달해요 |
| 주문서 | 로그인 여부와 저장 수단 존재 여부에 따라 UX를 확인해요 |
0전체 동작 방식
요청 순서는 로그인 회원의 user_token을 받아 위젯 렌더링에 넣는 흐름으로 시작해요. 아래 흐름에서 브랜드페이가 추가로 관여하는 지점은 회원 식별 → 저장 수단 표시까지예요.
요청 흐름
결제 요청 이후에는 결제위젯 문서의 서버 검증 흐름으로 돌아가요. 결제 결과는 서버에서 receipt_id 기준으로 처리해야 해요.
1관리자에서 브랜드페이 켜기
브랜드페이는 위젯 단위로 켜요. 결제위젯 생성과 결제수단 설정은 위젯 생성을 먼저 끝낸 뒤, 같은 위젯의 브랜드페이 옵션을 활성화해요.
관리자 → 결제위젯 → 사용할 위젯의 라이브 진입 → 좌측 사이드바 브랜드페이 탭.
1-1. 결제수단별 브랜드페이 활성화
좌측 목록에는 브랜드페이 카드, 브랜드페이 계좌이체처럼 결제수단별 항목이 나열돼요. 항목을 켜면 해당 결제수단이 브랜드페이 흐름(저장된 수단 우선 노출)으로 동작해요.
| 상태 | 의미 |
|---|---|
| 활성화 | 위젯 안에서 저장된 카드·계좌가 우선 노출되고, 첫 결제 시 저장 동의 UI가 떠요 |
| 비활성화 | 같은 결제수단으로 결제는 가능하지만 저장 흐름은 동작하지 않아요 |
1-2. 브랜드페이 지갑 기능 설정
좌측 하단 브랜드페이 지갑 기능설정 항목.
| 옵션 | 설명 |
|---|---|
| 비밀번호 6자리 사용 | 저장된 결제수단으로 결제할 때 사용자 인증용 6자리 PIN 사용 여부. 모바일·PC 공통 |
| 빌링키 결제 방식 (구독 위젯 한정) | 하나만 선택 (사용자가 매번 결제수단 선택) / 우선순위대로 결제 (등록된 우선순위에 따라 자동 결제) |
정기결제 흐름에 브랜드페이를 쓸 거면 위젯 생성 시 구독 결제 타입으로 만들어요. 빌링키 결제 방식 옵션은 구독 위젯에서만 노출돼요.
2서버에서 회원 토큰 발급
결제 페이지가 로드될 때 서버에서 로그인 세션을 확인하고 user_token을 발급해요. user_id에는 가맹점 DB의 회원 PK, user idx, UUID처럼 같은 회원을 다시 식별할 수 있는 고유값을 넣어요. 토큰은 1시간 유효하므로 결제 페이지 진입 시마다 새로 발급하면 만료 처리가 단순해져요.
import { Bootpay } from '@bootpay/backend-js'
// 결제 페이지 API — 회원 토큰을 발급하여 프론트엔드에 전달
app.get('/api/checkout', async (req, res) => {
Bootpay.setConfiguration({
client_key: '[ Client Key ]',
secret_key: '[ Secret Key ]'
})
const tokenResponse = await Bootpay.requestUserToken({
user_id: req.user.id, // 로그인된 회원 고유 ID
email: req.user.email,
username: req.user.name,
phone: req.user.phone
})
res.json({
user_token: tokenResponse.user_token,
order: req.order
})
})javascriptfrom bootpay_backend import BootpayBackend
# 결제 페이지 API — 회원 토큰을 발급하여 프론트엔드에 전달
@app.route('/api/checkout')
@login_required
def checkout():
bootpay = BootpayBackend('APPLICATION_ID', 'PRIVATE_KEY')
token_response = bootpay.request_user_token(
user_id=current_user.id,
email=current_user.email,
username=current_user.name,
phone=current_user.phone
)
return jsonify({
'user_token': token_response.data['user_token'],
'order': order_data
})pythonuse Bootpay\ServerPhp\BootpayApi;
// 결제 페이지 API — 회원 토큰을 발급하여 프론트엔드에 전달
BootpayApi::setConfiguration('APPLICATION_ID', 'PRIVATE_KEY');
$tokenResponse = BootpayApi::requestUserToken([
'user_id' => $user->id,
'email' => $user->email,
'username' => $user->name,
'phone' => $user->phone,
]);
echo json_encode([
'user_token' => $tokenResponse['user_token'],
]);phpimport kr.co.bootpay.pg.Bootpay;
import kr.co.bootpay.pg.model.request.UserToken;
// 결제 페이지 API — 회원 토큰을 발급하여 프론트엔드에 전달
Bootpay bootpay = new Bootpay("APPLICATION_ID", "PRIVATE_KEY");
UserToken params = new UserToken();
params.userId = user.getId();
params.email = user.getEmail();
params.username = user.getName();
params.phone = user.getPhone();
var tokenResponse = bootpay.getUserToken(params);
// tokenResponse.get("user_token") → 프론트엔드에 전달javabootpay = Bootpay::Api.new(application_id: 'APPLICATION_ID', private_key: 'PRIVATE_KEY')
# 결제 페이지 API — 회원 토큰을 발급하여 프론트엔드에 전달
token_response = bootpay.get_user_token(
user_id: current_user.id,
email: current_user.email,
name: current_user.name,
phone: current_user.phone
)
render json: { user_token: token_response.data['user_token'] }rubyimport "github.com/bootpay/backend-go/v2"
// 결제 페이지 API — 회원 토큰을 발급하여 프론트엔드에 전달
api := bootpay.NewAPI("APPLICATION_ID", "PRIVATE_KEY", nil, "")
tokenResponse, _ := api.RequestUserToken(bootpay.UserTokenRequest{
UserId: user.ID,
Email: user.Email,
Username: user.Name,
Phone: user.Phone,
})
// tokenResponse["user_token"] → 프론트엔드에 전달gousing Bootpay;
using Bootpay.models;
// 결제 페이지 API — 회원 토큰을 발급하여 프론트엔드에 전달
var bootpay = new BootpayApi("APPLICATION_ID", "PRIVATE_KEY");
var tokenResponse = await bootpay.GetUserToken(new UserToken
{
userId = user.Id,
email = user.Email,
username = user.Name,
phone = user.Phone
});
// user_token → 프론트엔드에 전달csharp회원 토큰 API의 전체 파라미터·응답 스펙은 회원 토큰 문서를 참고해요.
3결제위젯에 user_token 전달
서버에서 받은 user_token을 결제위젯 렌더링 payload에 전달해요. 아래 예제는 기존 렌더링 코드에서 토큰이 들어가는 위치만 확인하면 돼요.
import { BootpayWidget } from '@bootpay/client-js'
// 서버에서 받은 user_token
const { user_token } = await fetch('/api/checkout').then(r => r.json())
BootpayWidget.render('#bootpay-widget', {
client_key: '[ Client Key ]',
price: 39000,
sandbox: true,
use_terms: true,
user_token: user_token, // 로그인 회원의 브랜드페이 식별 토큰
hooks: {
ready: (data) => console.log('위젯 준비 완료', data),
allTermsAccepted: () => {
document.getElementById('pay-button').disabled = false
}
}
})javascript<div id="bootpay-widget"></div>
<button id="pay-button" disabled>결제하기</button>
<script src="https://js.bootpay.co.kr/bootpay-5.3.0.min.js"></script>
<script>
fetch('/api/checkout')
.then(function(r) { return r.json() })
.then(function(data) {
BootpayWidget.render('#bootpay-widget', {
client_key: '[ Client Key ]',
price: 39000,
sandbox: true,
use_terms: true,
user_token: data.user_token, // 로그인 회원의 브랜드페이 식별 토큰
hooks: {
ready: function() { console.log('위젯 준비 완료') },
allTermsAccepted: function() {
document.getElementById('pay-button').disabled = false
}
}
})
})
</script>html// 가맹점 서버 API에서 user_token을 받아온 후
val userToken = api.getUserToken()
val payload = Payload()
payload.clientKey = "[ Client Key ]"
payload.price = 39000.0
payload.isSandbox = true
payload.userToken = userToken // 로그인 회원의 브랜드페이 식별 토큰
controller.renderWidget(payload)kotlin// 가맹점 서버 API에서 user_token을 받아온 후
let userToken = api.getUserToken()
let payload = Payload()
payload.clientKey = "[ Client Key ]"
payload.price = 39000
payload.isSandbox = true
payload.userToken = userToken // 로그인 회원의 브랜드페이 식별 토큰
widgetController.renderWidget(payload: payload)swiftfinal userToken = await api.getUserToken();
final payload = Payload()
..clientKey = '[ Client Key ]'
..price = 39000
..isSandbox = true
..userToken = userToken; // 로그인 회원의 브랜드페이 식별 토큰
_controller.renderWidget(payload: payload);dartfinal userToken = await api.getUserToken();
final payload = Payload()
..clientKey = '[ Client Key ]'
..price = 39000
..isSandbox = true
..userToken = userToken; // 로그인 회원의 브랜드페이 식별 토큰
_controller.renderWidget(payload: payload);
// Flutter Web은 web/index.html에 JS SDK 스크립트 추가 필수dartconst { user_token } = await api.getUserToken()
<BootpayWidget
ref={bootpayWidget}
payload={{
clientKey: '[ Client Key ]',
price: 39000,
isSandbox: true,
userToken: user_token, // 로그인 회원의 브랜드페이 식별 토큰
}}
onDone={(data) => console.log('Done:', data)}
onError={(data) => console.log('Error:', data)}
/>tsx4로그인 상태와 저장 수단에 따른 분기
같은 위젯이라도 로그인 여부와 저장 수단 존재 여부에 따라 사용자 경험이 달라져요. 전체 요청 흐름은 앞에서 봤으므로, 여기서는 주문서에서 판단해야 하는 분기만 정리해요.
| 분기 | 사용자 경험 | 구현 포인트 |
|---|---|---|
user_token 미전달 |
일반 결제위젯 | 비로그인 주문서는 토큰 없이 렌더링 |
user_token 전달 + 저장된 수단 없음 |
일반 결제수단 선택 후 저장 동의 | 첫 결제에서 저장 동의 UI 노출 |
user_token 전달 + 저장된 수단 있음 |
저장된 카드·계좌 우선 노출 | 재방문 회원은 원클릭 결제 가능 |
5FAQ
user_token이 만료되면 어떻게 되나요?
위젯 렌더 시점에 토큰이 만료돼 있으면 브랜드페이가 비활성화되고 일반 결제위젯으로 동작해요. 토큰은 1시간 유효하므로 결제 페이지 진입 시마다 새로 발급하는 것을 권장해요.
같은 회원이 여러 디바이스에서 결제하면 저장된 수단이 공유되나요?
user_token의 식별자(user_id)가 같으면 디바이스가 달라도 같은 회원으로 보고 저장된 결제수단을 함께 보여줘요. 회원 식별자를 바꾸지 않는 것이 전제예요.
