이 문서는 결제창을 빠르게 붙이기 위한 매뉴얼이에요. 최소 흐름은 여기서 바로 연결하고, 서버 승인·오픈 타입·결제수단 옵션처럼 운영에서 길어지는 내용은 결제창 상세 문서에서 확인해요.
아래 순서대로 진행하면 결제창 호출, receipt_id 전달, 서버 검증, 웹훅 수신의 기본 뼈대를 만들 수 있어요.
데모 영상
연동 흐름
결제창 호출은 프론트엔드가 담당하고, receipt_id를 받은 뒤부터는 백엔드가 검증해서 주문을 확정해야 해요.
이 빠른 매뉴얼의 기본 예제는 클라이언트 자동 승인 후 done으로 receipt_id를 받는 흐름이에요. 운영에서 extra.separately_confirmed: true를 쓰면 done 대신 confirm이 호출되고, 클라이언트의 onDone은 호출되지 않아요. 이때는 confirm에서 receipt_id를 서버로 보내고 서버가 승인 API를 호출해야 해요.
구현 순서
SDK 설치 (프론트엔드)
플랫폼별 표준 패키지 매니저로 설치해요.
<!-- Bootpay JS SDK (CDN) -->
<script src="https://js.bootpay.co.kr/bootpay-5.3.0.min.js"></script>
<!-- 또는 npm -->
<!-- npm install @bootpay/client-js -->html// Xcode → File → Add Package Dependencies
// Package URL: https://github.com/bootpay/ios_swift.git
// Dependency Rule: Up to Next Major
//
// 또는 Package.swift에 직접 추가:
dependencies: [
.package(url: "https://github.com/bootpay/ios_swift.git", from: "5.1.1")
]swift// app/build.gradle (Module: app)
dependencies {
implementation 'io.github.bootpay:android:5.1.1'
}flutter pub add bootpay
# pubspec.yaml의 dependencies에 bootpay: ^5.1.2가 추가된다bashnpm install react-native-bootpay-api
# iOS는 pod install까지 실행
cd ios && pod installbash결제 요청 (프론트엔드)
결제창은 아래와 같이 호출할 수 있어요. 국내 PG 특성상 WebKit 기반으로 호출해야 하기 때문에 Bootpay는 내부 WebView로 결제창을 띄워요.
async function requestPayment() {
const response = await Bootpay.requestPayment({
application_id: 'YOUR_APPLICATION_ID',
price: 1000,
order_name: '테스트 상품',
order_id: 'order_' + Date.now(), // 고유한 주문번호
pg: '나이스페이',
method: 'card', // card, phone, bank, vbank 등
user: {
username: '홍길동',
phone: '01012345678'
}
})
// 결제 성공 → 서버로 검증 요청
if (response.event === 'done') {
await verifyPayment(response.receipt_id)
}
}
async function verifyPayment(receiptId) {
const res = await fetch('/api/server/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ receipt_id: receiptId })
})
const result = await res.json()
if (result.success) {
alert('결제 완료!')
} else {
alert('결제 조회 실패')
}
}javascript// UIViewController 내부에서 호출
let payload = Payload()
payload.applicationId = BootpayConfig.applicationId
payload.price = 1000
payload.orderName = "테스트 상품"
payload.orderId = String(Date().timeIntervalSince1970)
payload.pg = "나이스페이"
payload.method = "카드"
Bootpay.requestPayment(viewController: self, payload: payload)
.onDone { data in
// data 안의 receipt_id를 백엔드로 보내 검증
self.verifyPayment(receiptId: data["receipt_id"] as? String ?? "")
}
.onError { data in print("결제 실패: \(data)") }
.onCancel { _ in print("취소") }swift// Activity 내부에서 호출
val payload = Payload()
.setApplicationId(BootpayConfig.applicationId)
.setOrderName("테스트 상품")
.setOrderId("order_${System.currentTimeMillis()}")
.setPg("나이스페이")
.setMethod("카드")
.setPrice(1000.0)
Bootpay.init(this)
.setPayload(payload)
.setEventListener(object : BootpayEventListener {
override fun onDone(data: String) {
// data의 receipt_id를 백엔드로 보내 검증
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()kotlin// State 안에서 호출
final payload = Payload()
..androidApplicationId = BootpayEnvConfig.androidApplicationId
..iosApplicationId = BootpayEnvConfig.iosApplicationId
..webApplicationId = BootpayEnvConfig.webApplicationId
..price = 1000
..orderName = '테스트 상품'
..orderId = DateTime.now().millisecondsSinceEpoch.toString()
..pg = '나이스페이'
..method = '카드';
Bootpay().request(
context: context,
payload: payload,
onDone: (data) {
// data의 receipt_id를 백엔드로 보내 검증
verifyPayment(data);
},
onError: (data) => debugPrint('error: $data'),
onCancel: (data) => debugPrint('cancel: $data'),
onClose: () {},
);dart// useRef로 잡은 Bootpay 컴포넌트 인스턴스에서 호출
const payload = {
pg: '나이스페이',
method: '카드',
order_name: '테스트 상품',
order_id: `order_${Date.now()}`,
price: 1000,
}
const user = { username: '홍길동', phone: '01012345678' }
bootpay.current?.requestPayment(payload, [], user, {})
// 컴포넌트 props로 콜백 등록 — onDone 안에서 receipt_id를 verify API로 전송
// <Bootpay ref={bootpay}
// ios_application_id={IOS_APPLICATION_ID}
// android_application_id={ANDROID_APPLICATION_ID}
// => verifyPayment(data.receipt_id)}
// />javascript상세 문서: 결제 요청 파라미터
결제 결과 데이터 예시
프론트엔드 이벤트는 상태별로 아래처럼 들어와요. 이 값은 주문 완료의 최종 근거가 아니라, 서버에서 결제 내역을 다시 조회하기 위한 키예요.
{
"event": "done",
"receipt_id": "6244f60c1fc19202e42e8c4e",
"order_id": "order_20260428_001",
"price": 1000,
"method": "card",
"method_symbol": "card",
"status": 1
}json{
"event": "confirm",
"receipt_id": "6244f60c1fc19202e42e8c4e",
"order_id": "order_20260428_001",
"price": 1000,
"method": "card"
}json{
"event": "issued",
"receipt_id": "6244f60c1fc19202e42e8c4e",
"order_id": "order_20260428_001",
"method": "vbank",
"status": 5
}json{
"event": "error",
"error_code": "RC_REQUEST_FAILED",
"message": "결제 요청이 실패했다."
}json서버에서 receipt_id로 다시 조회하면 결제 조회 응답 예시와 같은 상세 JSON을 받아요. 주문 확정은 그 응답의 status, price, order_id가 DB에 저장한 주문 기준값과 일치할 때만 처리해야 해요.
서버 SDK 설치 (백엔드)
사전 조건: PG 결제창 연동 → SDK 설치의 서버 SDK 단계를 먼저 완료하고 인증까지 마쳐요.
결제 조회 (백엔드)
서버에서는 조회한 결제 정보의 status가 결제 완료 상태인지 확인해요. DB에 저장해 둔 price, 주문 상태 등 기준값이 있다면 Bootpay 응답값과 일치하는지도 함께 검증해요.
app.post('/api/server/verify', async (req, res) => {
const { receipt_id } = req.body
// 결제 정보 조회
const receipt = await Bootpay.receiptPayment(receipt_id)
if (receipt.status === 1 && receipt.price === 1000) {
// 검증 성공 — DB에 결제 정보 저장
await db.payments.create({
receipt_id,
price: receipt.price,
method: receipt.method_symbol,
paid_at: receipt.purchased_at
})
res.json({ success: true })
} else {
// 검증 실패 — 결제 취소
await Bootpay.cancelPayment({
receipt_id,
cancel_price: receipt.price,
cancel_message: '검증 실패'
})
res.json({ success: false })
}
})javascript# Flask·Django 핸들러 안에서
receipt = bootpay.receipt_payment(receipt_id)
if receipt['status'] == 1 and receipt['price'] == 1000:
# DB 저장
pass
else:
# 검증 실패 → 취소
bootpay.cancel_payment(
receipt_id=receipt_id,
cancel_price=receipt['price'],
cancel_message='검증 실패',
)python// Laravel·Slim 컨트롤러 안에서
$receipt = BootpayApi::receiptPayment($receipt_id);
if ($receipt['status'] === 1 && $receipt['price'] === 1000) {
// DB 저장
} else {
// 검증 실패 → 취소
BootpayApi::cancelPayment([
'receipt_id' => $receipt_id,
'cancel_price' => $receipt['price'],
'cancel_message' => '검증 실패',
]);
}php// Spring 컨트롤러 안에서
var receipt = bootpay.getReceipt(receiptId);
if (((Number) receipt.get("status")).intValue() == 1 && ((Number) receipt.get("price")).intValue() == 1000) {
// DB 저장
} else {
// 검증 실패 → 취소
Cancel cancel = new Cancel();
cancel.receiptId = receiptId;
cancel.cancelPrice = ((Number) receipt.get("price")).doubleValue();
cancel.cancelUsername = "시스템";
cancel.cancelMessage = "검증 실패";
bootpay.receiptCancel(cancel);
}java# Rails 컨트롤러 안에서
receipt = bootpay.verify(receipt_id).data
if receipt['status'] == 1 && receipt['price'] == 1000
# DB 저장
else
# 검증 실패 → 취소
bootpay.cancel_payment(
receipt_id: receipt_id,
cancel_price: receipt['price'],
cancel_message: '검증 실패',
)
endruby// net/http·Gin 핸들러 안에서
receipt, err := api.GetReceipt(receiptID)
if err != nil { return }
status := int(receipt["status"].(float64))
price := receipt["price"].(float64)
if status == 1 && price == 1000 {
// DB 저장
} else {
// 검증 실패 → 취소
_, _ = api.ReceiptCancel(bootpay.CancelData{
ReceiptId: receiptID,
CancelPrice: price,
CancelUsername: "시스템",
CancelMessage: "검증 실패",
})
}go// ASP.NET Core 컨트롤러 안에서
var response = await bootpay.GetReceipt(receiptId);
var body = await response.Content.ReadAsStringAsync();
var receipt = JsonConvert.DeserializeObject<Dictionary<string, object>>(body);
var status = Convert.ToInt32(receipt["status"]);
var price = Convert.ToDouble(receipt["price"]);
if (status == 1 && price == 1000) {
// DB 저장
} else {
// 검증 실패 → 취소
await bootpay.ReceiptCancel(new Cancel {
receiptId = receiptId,
cancelPrice = price,
cancelUsername = "시스템",
cancelMessage = "검증 실패",
});
}csharp웹훅 수신 (백엔드, 선택)
결제 상태 변경을 실시간으로 받으려면 웹훅을 설정해요. Bootpay 관리자에서 결제가 취소되는 경우나 가상계좌처럼 고객이 나중에 입금하는 경우에 연동해야 해요.
app.post('/webhooks/bootpay', async (req, res) => {
res.status(200).json({ success: true })
const { event, data } = req.body
if (event === 'payment.done') {
// 결제 완료 후처리 (이메일 발송 등)
}
if (event === 'payment.cancelled') {
// 취소 후처리 (재고 복구 등)
}
})javascript상세 문서: 웹훅 처리 가이드
다음 단계
- 서버 승인·오픈 타입·결제수단 옵션까지 보려면 → 결제창 상세 문서
- 결제수단 선택 UI를 Bootpay가 관리하도록 하려면 → 결제위젯
- 고객 카드를 저장해 재결제를 빠르게 하려면 → 브랜드페이
- 서버에서 원하는 시점에 결제하려면 → 예약결제
- 매월 자동 결제를 붙이려면 → 자동결제
더 읽을거리
- 결제 UI 선택 가이드 — 결제창 vs 위젯 vs 브랜드페이
- 결제 완료 처리 설계 — 왜 서버 검증이 필수인가
- 취소·환불 정책 설계 — 전체/부분 취소·영업일 기준
