method / methods 값을 어떻게 보내느냐에 따라 결제창 노출 방식이 달라져요.
- 보내지 않으면 → 활성화된 모든 결제수단이 노출되는 통합결제창 이 열려요
- 단일 값으로 보내면 → 지정한 결제수단 창으로 바로 진입해요
- 배열로 보내면 → 지정한 결제수단만 노출되는 통합결제창 이 열려요
이 페이지는 method / methods 파라미터의 동작과 SDK별 표기 차이에 집중해요. 결제 요청 전체 코드와 다른 옵션은 결제창 클라이언트 연동 에서 다뤄요.
왜 통합결제창을 쓰면 좋은가
통합결제창은 부트페이 관리자에서 활성화한 결제수단을 그대로 노출하는 구조예요. 코드를 수정하지 않고도 결제수단 추가·제거가 가능해요 — 관리자에서 토글만 바꾸면 결제창이 따라 바뀌어요.
pg (PG사 코드) 도 미지정으로 두면 PG 교체 역시 코드 수정 없이 관리자 설정만으로 끝나요. PG 정책이 자주 바뀌거나 멀티 PG 운영을 염두에 두는 서비스라면 이 점이 유리해요.
pg 미지정 + 활성화된 PG가 여러 개일 경우, 어떤 PG 결제창이 뜰지는 부트페이가 랜덤으로 선택해요. 특정 PG 우선순위가 필요하면 pg를 명시해야 해요.
세 가지 노출 방식
| 방식 | 결제창 형태 | 사용 케이스 |
|---|---|---|
| 미지정 | 통합결제창 (활성화된 모든 결제수단 노출) | 결제수단 정책이 단순할 때, 모든 옵션을 구매자에게 보여줄 때 |
단일 ('card') |
결제수단 다이렉트 (선택 단계 생략) | 결제수단이 미리 정해진 시나리오 — 구독·자동결제·간편결제 버튼 |
복수 (['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' },
})
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}
ios_application_id={IOS_APPLICATION_ID}
android_application_id={ANDROID_APPLICATION_ID}
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 미지정 → 통합결제창 (관리자 활성화 기준)
})
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}
ios_application_id={IOS_APPLICATION_ID}
android_application_id={ANDROID_APPLICATION_ID}
onDone={(data) => verifyPayment(data.receipt_id)}
onError={(data) => console.log(data)}
onCancel={(data) => console.log(data)}
onClose={() => {}}
/>
</View>
)
}tsx결제수단을 지정할 때
결제수단을 하나만 지정하면 선택 화면 없이 해당 결제수단 창으로 바로 진입해요. 구독처럼 카드 결제가 고정된 흐름이나, 특정 간편결제 버튼을 따로 둔 화면에서 사용해요.
여러 결제수단을 지정하면 통합결제창은 유지하되, 배열에 포함된 결제수단만 노출돼요. 휴대폰결제 제외, 카드와 계좌이체만 허용 같은 정책을 결제창에 반영할 때 사용해요.
결제수단 코드는 결제연동 Enum > 결제수단 코드에서 확인해요. 관리자에서 비활성화한 결제수단은 요청값에 포함해도 결제창에 노출되지 않아요.
pg와의 조합
pg (PG사 코드)와 결제수단의 조합으로 라우팅이 결정돼요.
pg |
결제수단 | 결제창 동작 |
|---|---|---|
| 미지정 | 미지정 | 관리자에서 활성화한 PG와 결제수단 기준으로 통합결제창 노출. 여러 PG가 활성화되어 있으면 어느 PG가 뜰지는 랜덤 |
| 지정 | 미지정 | 지정한 PG에서 관리자 활성화된 결제수단만 노출 (PG 단위 통합결제창) |
| 지정 | 지정 | 지정한 PG에서 관리자 활성화된 해당 결제수단으로 다이렉트 (또는 배열로 필터된 통합창) |
일부 결제수단은 특정 PG에서만 지원돼요 (예: easy는 카카오·페이코 PG 한정). 호환되지 않는 pg × 결제수단 조합을 보내면 결제창이 열리지 않거나 오류 응답을 받아요. PG별 지원 결제수단은 PG별 코드 매핑에서 확인해요.
기획 차원의 의사결정 기준은 결제창 UX를 먼저 정하는 기준, 간편결제 추가 여부를 결정하는 기준 에서 더 상세히 다뤄요.
