method / methods 값을 어떻게 보내느냐에 따라 결제창 노출 방식이 달라져요.
- 보내지 않으면 → 활성화된 모든 결제수단이 노출되는 통합결제창 이 열려요
- 단일 값으로 보내면 (
pg를 함께 지정한 경우) → 지정한 결제수단 창으로 바로 진입해요 - 배열로 보내면 → 지정한 결제수단만 노출되는 통합결제창 이 열려요
결제수단 다이렉트는 pg 와 결제수단이 둘 다 지정됐을 때만 동작해요. pg 없이 method 만 보내면 서버가 결제수단 라우팅을 하지 않아서 그 값이 무시되고 통합결제창 이 열려요.
이 페이지는 method / methods 파라미터의 동작과 SDK별 표기 차이에 집중해요. 결제 요청 전체 코드와 다른 옵션은 결제창 클라이언트 연동 에서 다뤄요.
왜 통합결제창을 쓰면 좋은가
통합결제창은 부트페이 관리자 → 결제 설정에서 활성화한 결제수단을 그대로 노출하는 구조예요. 코드를 수정하지 않고도 결제수단 추가·제거가 가능해요 — 관리자에서 토글만 바꾸면 결제창이 따라 바뀌어요.
pg (PG사 코드) 도 미지정으로 두면 PG 교체 역시 코드 수정 없이 관리자 설정만으로 끝나요. PG 정책이 자주 바뀌거나 멀티 PG 운영을 염두에 두는 서비스라면 이 점이 유리해요.
pg 미지정 + 활성화된 PG가 여러 개일 경우, 어떤 PG 결제창이 뜰지는 부트페이가 랜덤으로 선택해요. 특정 PG 우선순위가 필요하면 pg를 명시해야 해요.
세 가지 노출 방식
| 방식 | 결제창 형태 | 사용 케이스 |
|---|---|---|
| 미지정 | 통합결제창 (활성화된 모든 결제수단 노출) | 결제수단 정책이 단순할 때, 모든 옵션을 구매자에게 보여줄 때 |
단일 ('card') |
pg 를 함께 지정하면 결제수단 다이렉트 (선택 단계 생략). pg 를 안 보내면 무시되고 통합결제창 |
결제수단이 미리 정해진 시나리오 — 구독·자동결제·간편결제 버튼 |
복수 (['card', 'bank']) |
통합결제창 (지정한 결제수단만 노출) | 일부 결제수단만 허용 — 정책상 휴대폰결제 제외 등 |
복수 지정 시에도 결제창 형태는 통합결제창이에요. 배열 길이가 1 이어도 통합결제창 UI 가 열려요. 단일 결제수단 다이렉트로 가려면 배열이 아닌 단일 값으로 보내요.
SDK별 결제수단 지정 위치
import { Bootpay } from '@bootpay/client-js'
async function requestDirectCardPayment() {
const response = await Bootpay.requestPayment({
client_key: '[ Client Key ]',
price: 50000,
order_name: '나이키 운동화 외 2건',
order_id: 'order_' + Date.now(),
pg: 'nicepay',
// JavaScript는 method 하나에 문자열(단일) 또는 배열(복수)을 넣어요.
method: 'card', // 단일 결제수단: 카드 결제창으로 다이렉트
// 복수 결제수단만 노출하려면 배열로 바꿔요.
// method: ['card', 'bank'],
user: { username: '홍길동', phone: '01012345678' },
extra: {
// SDK 5.x 기본 진행방식이 redirect라 redirect_url이 없으면 RC_REDIRECT_URL_INVALID가 발생해요.
open_type: 'redirect',
redirect_url: 'https://your-service.com/payment/redirect',
},
})
if (response.event === 'done') {
await verifyPayment(response.receipt_id)
}
}javascriptlet payload = Payload()
payload.clientKey = "[ Client Key ]"
payload.price = 50000
payload.orderName = "나이키 운동화 외 2건"
payload.orderId = "order_\(Int(Date().timeIntervalSince1970 * 1000))"
payload.pg = "nicepay"
payload.method = "card" // 단일 결제수단: 카드 결제창으로 다이렉트
// iOS는 복수 결제수단을 methods에 따로 넣어요. method와 methods를 같이 채우지 않아요.
// payload.methods = ["card", "bank"]
Bootpay.requestPayment(viewController: self, payload: payload)
.onDone { data in
verifyPayment(receiptId: data["receipt_id"] as? String ?? "")
}
.onError { data in print("결제 실패: \(data)") }
.onCancel { _ in print("결제 취소") }swiftval payload = Payload()
.setClientKey("[ Client Key ]")
.setPrice(50000.0)
.setOrderName("나이키 운동화 외 2건")
.setOrderId("order_${System.currentTimeMillis()}")
.setPg("nicepay")
.setMethod("card") // 단일 결제수단: 카드 결제창으로 다이렉트
// Android는 복수 결제수단을 setMethods(...)로 따로 넣어요. setMethod와 setMethods를 같이 쓰지 않아요.
// .setMethods(listOf("card", "bank"))
Bootpay.init(this)
.setPayload(payload)
.setEventListener(object : BootpayEventListener {
override fun onDone(data: String) {
verifyPayment(data)
}
override fun onError(data: String) {}
override fun onCancel(data: String) {}
override fun onClose() { Bootpay.removePaymentWindow() }
override fun onIssued(data: String) {}
override fun onConfirm(data: String) = true
}).requestPayment()kotlinfinal payload = Payload()
..clientKey = '[ Client Key ]'
..price = 50000
..orderName = '나이키 운동화 외 2건'
..orderId = DateTime.now().millisecondsSinceEpoch.toString()
..pg = 'nicepay'
..method = 'card'; // 단일 결제수단: 카드 결제창으로 다이렉트
// Flutter는 복수 결제수단을 methods에 따로 넣어요. method와 methods를 같이 채우지 않아요.
// payload.methods = ['card', 'bank'];
Bootpay().requestPayment(
context: context,
payload: payload,
onDone: (data) {
verifyPayment(data);
},
onError: (data) => debugPrint('error: $data'),
onCancel: (data) => debugPrint('cancel: $data'),
onClose: () {},
);dartimport React, { useRef } from 'react'
import { Button, View } from 'react-native'
import Bootpay from 'react-native-bootpay-api'
export default function PaymentScreen() {
const bootpay = useRef<Bootpay>(null)
const requestPayment = () => {
const payload = {
pg: 'nicepay',
order_name: '나이키 운동화 외 2건',
order_id: 'order_' + Date.now(),
price: 50000,
method: 'card', // 단일 결제수단: 카드 결제창으로 다이렉트
// React Native는 복수 결제수단을 methods에 따로 넣어요. method와 methods를 같이 보내지 않아요.
// methods: ['card', 'bank']
}
const user = { username: '홍길동', phone: '01012345678' }
bootpay.current?.requestPayment(payload, [], user, {})
}
return (
<View>
<Button title="결제하기" onPress={requestPayment} />
<Bootpay
ref={bootpay}
client_key={'[ Client Key ]'}
onDone={(data) => verifyPayment(data.receipt_id)}
onError={(data) => console.log(data)}
onCancel={(data) => console.log(data)}
onClose={() => {}}
/>
</View>
)
}tsx통합결제창 (결제수단 미지정)
결제수단을 보내지 않으면 관리자에서 활성화한 모든 결제수단이 결제창에 노출되어 구매자가 직접 선택해요. pg까지 미지정으로 두면 활성화된 PG 중에서 자동 라우팅돼요.
import { Bootpay } from '@bootpay/client-js'
async function requestTotalPayment() {
const response = await Bootpay.requestPayment({
client_key: '[ Client Key ]',
price: 50000,
order_name: '나이키 운동화 외 2건',
order_id: 'order_' + Date.now(),
user: { username: '홍길동', phone: '01012345678' },
// pg / method 미지정 → 통합결제창 (관리자 활성화 기준)
extra: {
// SDK 5.x 기본 진행방식이 redirect라 redirect_url이 없으면 RC_REDIRECT_URL_INVALID가 발생해요.
open_type: 'redirect',
redirect_url: 'https://your-service.com/payment/redirect',
},
})
if (response.event === 'done') {
await verifyPayment(response.receipt_id)
}
}javascriptfunc requestTotalPayment() {
let payload = Payload()
payload.clientKey = "[ Client Key ]"
payload.price = 50000
payload.orderName = "나이키 운동화 외 2건"
payload.orderId = "order_\(Int(Date().timeIntervalSince1970 * 1000))"
// pg / method / methods 미설정 → 통합결제창
Bootpay.requestPayment(viewController: self, payload: payload)
.onDone { data in
verifyPayment(receiptId: data["receipt_id"] as? String ?? "")
}
.onError { data in print("결제 실패: \(data)") }
.onCancel { _ in print("결제 취소") }
}swiftfun requestTotalPayment() {
val payload = Payload().apply {
clientKey = "[ Client Key ]"
price = 50000.0
orderName = "나이키 운동화 외 2건"
orderId = "order_${System.currentTimeMillis()}"
// pg / method / methods 미설정 → 통합결제창
}
Bootpay.init(this)
.setPayload(payload)
.setEventListener(object : BootpayEventListener {
override fun onDone(data: String) {
verifyPayment(data)
}
override fun onError(data: String) {}
override fun onCancel(data: String) {}
override fun onClose() { Bootpay.removePaymentWindow() }
override fun onIssued(data: String) {}
override fun onConfirm(data: String) = true
}).requestPayment()
}kotlinvoid requestTotalPayment(BuildContext context) {
final payload = Payload()
..clientKey = '[ Client Key ]'
..price = 50000
..orderName = '나이키 운동화 외 2건'
..orderId = DateTime.now().millisecondsSinceEpoch.toString();
// pg / method / methods 미설정 → 통합결제창
Bootpay().requestPayment(
context: context,
payload: payload,
onDone: (data) {
verifyPayment(data);
},
onError: (data) => debugPrint('error: $data'),
onCancel: (data) => debugPrint('cancel: $data'),
onClose: () {},
);
}dartimport React, { useRef } from 'react'
import { Button, View } from 'react-native'
import Bootpay from 'react-native-bootpay-api'
export default function TotalPaymentScreen() {
const bootpay = useRef<Bootpay>(null)
const requestPayment = () => {
const payload = {
order_name: '나이키 운동화 외 2건',
order_id: 'order_' + Date.now(),
price: 50000,
// pg / method / methods 미설정 → 통합결제창
}
const user = { username: '홍길동', phone: '01012345678' }
bootpay.current?.requestPayment(payload, [], user, {})
}
return (
<View>
<Button title="결제하기" onPress={requestPayment} />
<Bootpay
ref={bootpay}
client_key={'[ Client Key ]'}
onDone={(data) => verifyPayment(data.receipt_id)}
onError={(data) => console.log(data)}
onCancel={(data) => console.log(data)}
onClose={() => {}}
/>
</View>
)
}tsx결제수단을 지정할 때
pg 와 결제수단을 하나씩 지정하면 선택 화면 없이 해당 결제수단 창으로 바로 진입해요. 구독처럼 카드 결제가 고정된 흐름이나, 특정 간편결제 버튼을 따로 둔 화면에서 사용해요. pg 없이 결제수단만 보내면 그 값은 라우팅에 반영되지 않고 통합결제창이 열리니, 다이렉트가 필요하면 pg 를 반드시 함께 보내요.
여러 결제수단을 지정하면 통합결제창은 유지하되, 배열에 포함된 결제수단만 노출돼요. 휴대폰결제 제외, 카드와 계좌이체만 허용 같은 정책을 결제창에 반영할 때 사용해요.
결제수단 코드는 결제연동 Enum > 결제수단 코드에서 확인해요. 관리자에서 비활성화한 결제수단은 요청값에 포함해도 결제창에 노출되지 않아요.
pg와의 조합
pg (PG사 코드)와 결제수단의 조합으로 라우팅이 결정돼요.
pg |
결제수단 | 결제창 동작 |
|---|---|---|
| 미지정 | 미지정 | 관리자에서 활성화한 PG와 결제수단 기준으로 통합결제창 노출. 여러 PG가 활성화되어 있으면 어느 PG가 뜰지는 랜덤 |
| 미지정 | 지정 | 결제수단 지정이 무시되고 통합결제창이 열려요 |
| 지정 | 미지정 | 지정한 PG에서 관리자 활성화된 결제수단만 노출 (PG 단위 통합결제창) |
| 지정 | 지정 | 지정한 PG에서 관리자 활성화된 해당 결제수단으로 다이렉트 (또는 배열로 필터된 통합창) |
일부 결제수단은 특정 PG에서만 지원돼요 (예: easy는 카카오·페이코 PG 한정). 호환되지 않는 pg × 결제수단 조합을 보내면 결제창이 열리지 않거나 오류 응답을 받아요. PG별 지원 결제수단은 PG별 코드 매핑에서 확인해요.
해외 결제수단 함께 노출하기
통합결제창에 currencies를 보내면 원화 결제수단과 함께 PayPal 같은 해외 결제수단을 한 화면에 보여줄 수 있어요. 구매자는 결제창의 해외결제 영역에서 결제수단을 고르고, 금액은 그 결제수단의 통화로 표시돼요.
const response = await Bootpay.requestPayment({
client_key: '[ Client Key ]',
price: 10000,
order_name: '나이키 운동화 외 2건',
order_id: 'order_' + Date.now(),
pg: 'nicepay',
method: ['card', 'bank'],
currencies: ['KRW', 'USD'], // 원화 결제수단 + USD 해외 결제수단
currency_prices: { USD: 7.99 }, // 생략하면 환율로 환산한 금액(올림)
items: [
{ id: 'item_01', name: '나이키 운동화', qty: 1, price: 10000 }
],
extra: {
open_type: 'redirect',
redirect_url: 'https://your-service.com/payment/redirect',
locale: 'en' // 통합결제창 초기 언어
}
})javascript노출 규칙
currencies |
원화(요청 통화) 결제수단 | 해외 결제수단 |
|---|---|---|
| 미지정 | pg·method 기준으로 노출 |
노출 안 함 |
['KRW', 'USD'] |
pg·method 기준으로 노출 |
USD 결제수단 노출 |
['USD'] |
노출 안 함 | USD 결제수단만 노출 |
- 원화 결제수단은 지금처럼
pg·method로 걸러져요. - 해외 결제수단은
pg·method와 상관없이, 관리자에서 활성화한 PG 중 해당 통화를 지원하는 결제수단이 모두 노출돼요.method: 'card'를 보냈더라도 해외 카드 결제수단(예: PayPal)은 따로 보여요. currencies가 받는 통화 코드는KRW,USD,JPY,CNY,EUR이에요. 목록에 없는 값은 무시돼요.- 코드를 넣어도 그 통화를 지원하는 결제수단이 관리자에서 활성화돼 있지 않으면 해외결제 영역에 아무것도 보이지 않아요. 프로젝트 기본 통화로 고를 수 있는 건
KRW·USD두 가지예요.
통화별 금액
해외 결제수단을 고르면 결제 금액이 그 통화로 바뀌어요.
currency_prices에 해당 통화 금액을 보냈다면 그 금액을 그대로 써요.- 보내지 않았다면 부트페이 환율로 환산하고 올림해요.
USD·CNY·EUR은 소수점 둘째 자리,KRW·JPY는 정수로 올려요. 예: 100원 → 0.08 USD tax_free도 같은 비율로 환산되고, 결제 금액을 넘지 않아요.items가 있으면 각 상품 금액도 같은 비율로 환산하고, 합계가 결제 금액과 정확히 맞도록 반올림 차이를 상품 하나에 반영해요. PayPal처럼 상품 합계를 검증하는 PG에서ITEM_TOTAL_MISMATCH가 나지 않게 하기 위해서예요.
해외 결제수단으로 결제하면 영수증의 금액과 통화가 구매자가 고른 통화 기준으로 바뀌어요. 백엔드에서 receipt_id로 금액을 검증할 때는 원화 주문 금액과 바로 비교하지 말고, 통화를 확인한 뒤 currency_prices로 보낸 금액(또는 환산 금액)과 비교해요.
결제창 언어
통합결제창은 extra.locale 값으로 처음 보여줄 언어를 정하고, 값이 없으면 한국어로 열려요. 구매자는 결제창 왼쪽 위의 EN / KO / JP / CN 버튼으로 언어를 바꿀 수 있어요.
extra.locale |
언어 |
|---|---|
ko (기본값) |
한국어 |
en |
영어 |
ja, jp |
일본어 |
zh, cn |
중국어 |
기획 차원의 의사결정 기준은 결제창 UX를 먼저 정하는 기준, 간편결제 추가 여부를 결정하는 기준 에서 더 상세히 다뤄요.




