단건결제위젯 연동의 SDK별 세부 동작을 정리합니다. 플랫폼을 고르면 그 플랫폼 본문만 보입니다.
플랫폼별 상세 코드는 공통적으로 위젯 삽입 → 콜백에서 결제수단·약관 상태 병합 → 결제 버튼 활성화 → 결제 후 재로드/종료 처리 순서입니다.
Web JS 상세 API
주문서 전체 구현은 주문서 전체 흐름을 기준으로 하고, 아래는 Web 전용 API입니다.
const widgetEl = document.querySelector('#bootpay-widget')
// Web JS는 위젯 상태를 DOM 이벤트로 받습니다.
widgetEl.addEventListener('bootpay-widget-ready', () => {
console.log('위젯 준비 완료')
})
widgetEl.addEventListener('bootpay-widget-resize', (e) => {
console.log('위젯 크기 변경:', e.detail.height)
})javascript// 금액 변경
BootpayWidget.update({
price: 2000,
tax_free: 0,
extra: {
card_quota: '0,2,3'
}
})
// 렌더링 없이 데이터만 업데이트
BootpayWidget.update({ price: 3000 }, false)javascriptconst params = BootpayWidget.currentPaymentParameters()
// {
// pg: 'danal',
// method: 'card',
// wallet_id: null,
// terms: [...],
// widget_key: 'default-widget',
// widget_sandbox: true,
// extra: { card_quota: '0,2,3' }
// }javascript// 카드 결제 선택
BootpayWidget.selectPayment({ payment_type: 'card' })
// 특정 간편결제 선택
BootpayWidget.selectPayment({
payment_type: 'easy',
method_alias: 'kakaopay' // naverpay, payco, samsungpay 등
})javascriptconst terms = BootpayWidget.currentTermsCondition()
// [
// { pk: 'term1', title: '이용약관', agree: true },
// { pk: 'term2', title: '개인정보 처리방침', agree: false }
// ]
const allAgreed = terms.every(term => term.agree)javascriptif (BootpayWidget.isRendered()) {
BootpayWidget.requestPayment({
order_id: 'order_' + Date.now(), // 테스트용. 실제로는 서버에서 생성한 주문번호 사용
order_name: '테스트 결제'
})
}
function navigateToOtherPage() {
if (BootpayWidget.isRendered()) {
BootpayWidget.destroy()
}
}javascript| API | 설명 |
|---|---|
BootpayWidget.render(selector, options) |
위젯 렌더링 |
BootpayWidget.requestPayment(options) |
결제 요청 |
BootpayWidget.update(options) |
위젯 정보 업데이트 (금액 변경 등) |
BootpayWidget.destroy() |
위젯 제거 |
BootpayWidget.isRendered() |
위젯 렌더링 상태 확인 |
BootpayWidget.currentPaymentParameters() |
현재 선택된 결제 정보 조회 |
BootpayWidget.currentTermsCondition() |
현재 약관 동의 상태 조회 |
BootpayWidget.selectPayment(options) |
결제수단 프로그래밍 방식 선택 |
Android 위젯 상세
Android 위젯은 WebView 기반으로 결제 UI를 표시하며, 결제 시 전체화면으로 확장됩니다. BootpayWidgetController로 위젯 상태를 관리합니다.
| 콜백 | 설명 | 파라미터 |
|---|---|---|
setOnReady |
위젯 준비 완료 | 없음 |
setOnResize |
위젯 높이 변경 | height: Double (dp 단위) |
setOnChangePayment |
결제수단 변경 | data: WidgetData |
setOnChangeAgreeTerm |
약관동의 변경 | data: WidgetData |
setOnDone |
결제 완료 | data: String (JSON) |
setOnError |
결제 에러 | data: String (JSON) |
setOnCancel |
결제 취소 | data: String (JSON) |
setOnConfirm |
결제 확인 (검증) | data: String → Boolean 반환 |
setOnIssued |
가상계좌 발급 | data: String (JSON) |
setOnClose |
위젯 닫기 | 없음 |
onChangePayment과 onChangeAgreeTerm 콜백에서 payload.mergeWidgetData(data)를 호출하면, 위젯에서 선택한 결제수단/약관동의 상태가 Payload에 자동 반영됩니다. payload.widgetIsCompleted가 true이면 결제수단 선택과 약관동의가 모두 완료된 상태입니다.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<FrameLayout
android:id="@+id/webViewContainer"
android:layout_width="match_parent"
android:layout_height="516dp" />
</ScrollView>
<Button
android:id="@+id/payButton"
android:layout_width="match_parent"
android:layout_height="56dp"
android:enabled="false"
android:text="결제하기"
android:onClick="goPayment" />
</LinearLayout>xmlcontroller = BootpayWidgetController()
.setCloseAction(WidgetCloseAction.NONE) // 직접 처리 (권장)kotlin// 결제 완료 시 — 축소 후 결과 페이지로 이동
.setOnDone { data ->
BootpayWidget.collapseAndFinish(this)
runOnUiThread { finish() }
}
// 결제 에러/취소 시 — 축소 후 위젯 재로드 (재시도 가능)
.setOnError { data ->
BootpayWidget.collapseAndReload(this)
}kotlinoverride fun onBackPressed() {
val webView = BootpayWidget.getView(this, supportFragmentManager)
if (webView?.isExpanded == true) {
BootpayWidget.collapseAndReload(this)
} else {
super.onBackPressed()
}
}kotlinpayload.extra = BootExtra().apply {
appScheme = "yourAppScheme"
displaySuccessResult = true
displayErrorResult = true
}kotlin<activity
android:name=".WidgetActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourAppScheme" />
</intent-filter>
</activity>xml| 항목 | 핵심 |
|---|---|
WidgetCloseAction.NONE |
위젯 닫기 이후 화면 이동을 앱 코드에서 직접 처리할 때 권장 |
collapseAndReload(activity) |
취소·에러 후 축소하고 위젯을 재로드 |
collapseAndFinish(activity) |
결제 완료 후 축소하고 Activity 종료 |
displaySuccessResult / displayErrorResult |
false가 기본값. 앱에서 결과 화면을 직접 구현할 때 권장 |
- 위젯 높이:
onResize콜백의 높이 값은 dp 단위입니다.density를 곱하여 px로 변환 후 적용합니다. - 결제 버튼:
payload.widgetIsCompleted가true일 때만 결제 버튼을 활성화합니다. - 앱 스킴:
extra.appScheme과 AndroidManifest.xml의 scheme이 일치해야 합니다. - 메모리 관리:
onDestroy()에서BootpayWidget.destroy()를 호출합니다. - 뒤로가기:
onBackPressed()에서 전체화면 상태를 확인 후 적절히 처리합니다.
iOS 위젯 상세
iOS 위젯은 BootpayWidgetView(UIView 서브클래스)를 화면에 삽입하고, BootpayWidgetController로 이벤트를 관리합니다.
| 클래스 | 역할 |
|---|---|
BootpayWidgetView |
결제위젯을 표시하는 UIView |
BootpayWidgetController |
위젯 상태 관리 및 이벤트 콜백 처리 |
Payload |
결제 정보 데이터 |
| 콜백 | 설명 | 파라미터 |
|---|---|---|
onReady |
위젯 준비 완료 | 없음 |
onResize |
위젯 높이 변경 | height: CGFloat |
onChangePayment |
결제수단 변경 | data: WidgetData |
onChangeAgreeTerm |
약관동의 변경 | data: WidgetData |
onDone |
클라이언트 자동 승인 시 결제 완료 | data: [String: Any] |
onError |
결제 에러 | data: [String: Any] |
onCancel |
결제 취소 | data: [String: Any] |
onConfirm |
결제 확인 (검증) | data: [String: Any] → Bool 반환 |
onIssued |
가상계좌 발급 | data: [String: Any] |
onClose |
위젯 닫기 | 없음 |
// 1. WidgetView 생성 및 화면에 추가
widgetView = BootpayWidgetView()
view.addSubview(widgetView)
// 2. Controller 생성 및 콜백 설정
widgetController = BootpayWidgetController()
widgetController.onChangePayment = { data in ... }
// 3. Controller를 View에 연결
widgetView.controller = widgetController
// 4. Payload 설정 후 위젯 시작
widgetView.payload = payload
widgetView.startWidget()swift// NavigationController 사용 시
widgetController.closeAction = .popViewController
// Modal 사용 시
widgetController.closeAction = .dismissViewController
// 직접 처리 (권장)
widgetController.closeAction = .none
widgetController.onClose = { [weak self] in
self?.navigationController?.popToRootViewController(animated: true)
}swiftwidgetHeightConstraint = widgetView.heightAnchor.constraint(equalToConstant: 516)
widgetHeightConstraint.isActive = true
widgetController.onResize = { [weak self] height in
DispatchQueue.main.async {
self?.widgetHeightConstraint.constant = height
self?.view.layoutIfNeeded()
}
}swift// 기본값: 앱에서 직접 결과 화면 구현 (권장)
payload.extra = BootExtra()
payload.extra?.appScheme = "yourAppScheme"
// 빠른 연동: 웹뷰에서 결과 화면 표시
payload.extra?.displaySuccessResult = true
payload.extra?.displayErrorResult = trueswiftwidgetController.onError = { [weak self] data in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self?.widgetView.reloadWidget()
}
}swiftclass WidgetData {
var pg: String?
var method: String?
var termPassed: Bool
var completed: Bool
var cardQuota: String?
var methodSymbol: String?
var easyPay: String?
}swiftonChangePayment과 onChangeAgreeTerm 콜백에서 payload.mergeWidgetData(data)를 호출하면, 위젯에서 선택한 결제수단/약관동의 상태가 Payload에 자동 반영됩니다. payload.widgetIsCompleted가 true이면 결제수단 선택과 약관동의가 모두 완료된 상태입니다.
- 위젯 높이:
onResize콜백에서widgetHeightConstraint를 업데이트해야 합니다. - 결제 버튼:
payload.widgetIsCompleted가true일 때만 결제 버튼을 활성화합니다. - 앱 스킴:
payload.extra?.appScheme을 설정하고 Info.plist에 URL Scheme을 등록합니다. - 메모리 관리: 클로저에서
[weak self]를 사용하여 순환 참조를 방지합니다.
Flutter 위젯 상세
Flutter 위젯은 BootpayWidget StatefulWidget을 화면에 삽입하고, BootpayWidgetController로 이벤트와 결제를 관리합니다. 위젯 전용 BootpayWidgetWebView를 내부적으로 사용합니다.
| 클래스 | 역할 |
|---|---|
BootpayWidget |
결제위젯을 표시하는 StatefulWidget |
BootpayWidgetController |
위젯 상태 관리, 결제 요청, 이벤트 콜백 |
Payload |
결제 정보 데이터 |
| 콜백 | 설명 | 파라미터 |
|---|---|---|
onWidgetReady |
위젯 준비 완료 | 없음 |
onWidgetResize |
위젯 높이 변경 | height: double |
onWidgetChangePayment |
결제수단 변경 | data: WidgetData |
onWidgetChangeAgreeTerm |
약관동의 변경 | data: WidgetData |
onDone |
클라이언트 자동 승인 시 결제 완료 | data: String (JSON) |
onError |
결제 에러 | data: String (JSON) |
onCancel |
결제 취소 | data: String (JSON) |
onConfirm |
결제 확인 (검증) | data: String → bool 반환 |
onIssued |
가상계좌 발급 | data: String (JSON) |
onClose |
위젯 닫기 | 없음 |
double _widgetHeight = 516.0;
_controller.onWidgetResize = (height) {
if (_widgetHeight == height) return;
setState(() { _widgetHeight = height; });
};
SizedBox(
height: _widgetHeight,
child: BootpayWidget(
payload: _payload,
controller: _controller,
),
)dart_controller.reloadWidget();dart// 기본값: 앱에서 직접 결과 화면 구현 (권장)
_payload.extra = Extra()..appScheme = 'yourAppScheme';
// 빠른 연동: 웹뷰에서 결과 화면 표시
_payload.extra = Extra()
..appScheme = 'yourAppScheme'
..displaySuccessResult = true
..displayErrorResult = true;dartclass WidgetData {
String? pg;
String? method;
String? walletId;
List<WidgetTerm>? selectTerms;
String? currency;
bool? termPassed;
bool? completed;
WidgetExtra? extra;
String? methodSymbol;
String? easyPay;
String? cardQuota;
}dart<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourAppScheme" />
</intent-filter>
</activity>xml<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourAppScheme</string>
</array>
</dict>
</array>xmlonWidgetChangePayment과 onWidgetChangeAgreeTerm 콜백에서 _payload.mergeWidgetData(widgetData)를 호출하면, 위젯에서 선택한 결제수단/약관동의 상태가 Payload에 자동 반영됩니다. _payload.widgetIsCompleted가 true이면 결제수단 선택과 약관동의가 모두 완료된 상태입니다.
- 위젯 높이:
onWidgetResize콜백에서setState로SizedBox높이를 업데이트해야 합니다. - 결제 버튼:
_payload.widgetIsCompleted가true일 때만 결제 버튼을 활성화합니다. - ScrollView: 위젯 높이가 동적으로 변경되므로
SingleChildScrollView를 사용합니다. - dismiss:
onClose콜백에서Bootpay().dismiss(context)를 호출합니다. 웹에서는kIsWeb체크 후 skip합니다. - 앱 스킴: Android
AndroidManifest.xml과 iOSInfo.plist에 모두 등록해야 합니다.
React Native 위젯 상세
React Native 위젯은 BootpayWidget 컴포넌트와 ref를 통한 명령형 API로 동작합니다. 다른 SDK와 달리 onWidgetReady 콜백에서 renderWidget()을 명시적으로 호출해야 합니다.
| 메서드 | 설명 | 호출 시점 |
|---|---|---|
renderWidget(payload) |
위젯 초기 렌더링 | onWidgetReady 콜백 내에서 |
updateWidget(payload, refresh?) |
위젯 데이터 업데이트 | 금액/상품 변경 시 |
requestPayment() |
결제 요청 (전체화면 전환) | 결제 버튼 클릭 시 |
transactionConfirm() |
프론트엔드 승인 확정 | onConfirm 콜백 내에서. 서버 승인에서는 호출하지 않음 |
reloadWidget() |
위젯 재로드 | 결제 완료/에러/취소 후 |
| 콜백 | 설명 | 파라미터 |
|---|---|---|
onWidgetReady |
위젯 준비 완료 | 없음 |
onWidgetResize |
위젯 높이 변경 | height: number |
onWidgetChangePayment |
결제수단 변경 | data: WidgetData | null |
onWidgetChangeTerms |
약관동의 변경 | data: WidgetData | null |
onDone |
클라이언트 자동 승인 시 결제 완료 | data: unknown (JSON) |
onError |
결제 에러 | data: unknown (JSON) |
onCancel |
결제 취소 | data: unknown (JSON) |
onConfirm |
결제 승인 확인 | data: unknown → boolean 반환 |
onIssued |
가상계좌 발급 | data: unknown (JSON) |
onClose |
결제창 닫힘 | 없음 |
위젯 렌더링 흐름은 마운트 → onWidgetReady → renderWidget(payload) → 결제수단/약관 변경 → 버튼 활성화 → requestPayment() 순서입니다.
const widgetPlaceholderRef = useRef<View>(null);
const [widgetTop, setWidgetTop] = useState(0);
const measureWidgetPosition = useCallback(() => {
widgetPlaceholderRef.current?.measureInWindow((x, y) => {
if (y > 0) setWidgetTop(y);
});
}, []);
<View ref={widgetPlaceholderRef}
style={{ minHeight: widgetHeight }}
onLayout={() => setTimeout(measureWidgetPosition, 100)} />
<BootpayWidget widgetTop={widgetTop} height={widgetHeight} ... />tsxconst [widgetData, setWidgetData] = useState<WidgetData | null>(null);
const isCompleted =
(widgetData?.term_passed ?? false) && (widgetData?.completed ?? false);
<TouchableOpacity
onPress={goPayment}
disabled={!isCompleted}
style={[styles.payButton, !isCompleted && styles.disabled]}>
<Text>결제하기</Text>
</TouchableOpacity>tsxconst updatePrice = (newPrice: number) => {
const payload = getWidgetPayload();
payload.price = newPrice;
bootpayWidget.current?.updateWidget(payload);
};
// 전체 새로고침
bootpayWidget.current?.updateWidget(payload, true);
// 에러/취소 후 위젯 재로드
setTimeout(() => bootpayWidget.current?.reloadWidget(), 500);tsxinterface WidgetData {
pg?: string;
method?: string;
wallet_id?: string;
select_terms?: WidgetTerm[];
currency?: string;
term_passed?: boolean;
completed?: boolean;
extra?: WidgetExtra;
method_symbol?: string;
easy_pay?: string;
card_quota?: string;
}typescript<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourAppScheme" />
</intent-filter>
</activity>xml<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourAppScheme</string>
</array>
</dict>
</array>xml| 옵션 | false (기본값, 권장) |
true |
|---|---|---|
display_success_result |
클라이언트 자동 승인 기준: 즉시 onDone 호출 → 앱에서 결과 처리 |
웹뷰 결과 화면 → 닫기 클릭 → onClose |
display_error_result |
즉시 onError 호출 → 위젯 재로드 가능 |
웹뷰 에러 화면 → 닫기 클릭 → onClose |
onWidgetReady: 위젯 준비 후renderWidget(payload)을 반드시 호출해야 합니다.widgetTop:position: absolute기반이므로 placeholder의measureInWindow로 Y 좌표를 측정합니다.- 결제 버튼:
widgetData?.completed && widgetData?.term_passed가 모두true일 때 활성화합니다. - 서버 승인:
extra.separately_confirmed: true사용 시onConfirm에서receipt_id를 서버로 전달합니다. 이 방식에서는onDone이 호출되지 않으며,transactionConfirm()도 클라이언트에서 호출하지 않습니다. - 앱 스킴: Android
AndroidManifest.xml과 iOSInfo.plist에 모두 등록해야 합니다. - iOS 스와이프 백: 전체화면 결제 모드에서 좌측 스와이프 지원을 위해
react-native-gesture-handler가 필요합니다.



