# Widget Integration Guide
The merchant's frontend page integrates our JavaScript SDK, uses widgetToken to initialize the Token component, launches the cashier, and completes payment.
# Integration Flow
Three-Step Integration
- Backend Creates Order:Call the Unified Order API with
widget: trueto obtainwidgetToken - Frontend Includes SDK:Include the SDK script in the merchant's page
- Frontend Launches Component:Initialize and launch the cashier using
widgetToken, listen for payment results
# Prerequisites
- Merchant number (mchNo) and App ID (appId) are registered with the payment gateway
- Backend has completed Unified Order API integration
- Test and production environment domains
# Step 1: Backend Obtains widgetToken
The merchant backend can obtain widgetToken by passing widget: true parameter in either the Unified Order Creation or Cashier Order Creation API.
Important
widgetTokenmust be obtained by the backend, the frontend should not generate it- A new
widgetTokenshould be used for each payment widgetTokenis short-lived and bound to a single payment
# Request Example
Add widget: true to the unified order or cashier order API:
{
"mchNo": "M1623984572",
"appId": "60cc09bce4b0f1c0b83761c9",
"mchOrderNo": "mho1624005107281",
"amount": 100,
"currency": "BRL",
"country": "BR",
"widget": true
}
# Response Example
Response format is consistent with the Unified Order / Cashier Order API, with widgetToken added to the data object:
{
"code": 0,
"data": {
"payOrderId": "P202106181642329900002",
"mchOrderNo": "mho1624005107281",
"orderState": 0,
"widgetToken": "wt_abc123xyz"
},
"msg": "SUCCESS"
}
The merchant backend returns widgetToken to the frontend, which only uses this Token to launch the cashier without knowing other internal parameters.
# Step 2: Include SDK
Include the SDK in the merchant's page via <script> tag:
# Production Environment
<script type="module" src="https://cashier-hub.enjoypayment.com/sdk/v1/cashier.js"></script>
# Sandbox Environment
<script type="module" src="https://cashier-hub.sandbox.enjoypayment.com/sdk/v1/cashier.js"></script>
CSP Configuration
If the page has a Content Security Policy (CSP) configured, please ensure it allows loading SDK scripts from the cashier domain and opening the cashier popup.
Note: The SDK automatically detects the environment based on the domain, no additional configuration needed.
# Step 3: Launch Cashier
Called when user clicks the payment button:
document.getElementById('pay-btn').addEventListener('click', async function () {
try {
// widgetToken should be provided by backend
const widgetToken = 'wt_abc123xyz';
const result = await cashier.open({
widgetToken: widgetToken
});
console.log('Cashier completed:', result);
} catch (error) {
console.error('Cashier error:', error.payload || error);
}
});
Notes
- Must call
openwithin a user click event to avoid browser popup blocking - A new
widgetTokenshould be used for each payment - Prevent duplicate submissions: set the payment button to
loadingstate after click - Do not expose internal order parameters to the third-party page
# Event Listening and Result Handling
# Listen for Events
SDK listens for events via cashier.on(eventName, handler):
| Event Name | Description | Recommended Action |
|---|---|---|
READY | SDK loaded | Close the merchant page's loading state |
RESULT | Payment result | Key handler. Use as final payment state, but requires backend confirmation |
ERROR | Loading or payment error | Show clear prompt, allow user to retry |
CLOSE | Cashier requests close | Handle based on business logic |
REDIRECT | Redirecting to external page/channel | Handle based on business logic |
STATE_CHANGE | User preference changes | Usually no special handling needed |
# Payment Result Processing Flow
- Frontend receives
RESULTevent - Frontend prompts user "Payment completed" or "Processing"
- Frontend requests merchant backend to query order status (Critical step)
- Merchant backend returns final business state based on server results
- Frontend displays success, failure, or processing page based on backend response
cashier.on('RESULT', async function (payload) {
console.log('Payment result:', payload);
// Must confirm final state with backend
const orderStatus = await fetch(`/merchant-api/order/status?mchOrderNo=${encodeURIComponent(mchOrderNo)}`)
.then(function (res) { return res.json(); });
if (orderStatus.status === 'SUCCESS') {
window.location.href = '/payment-success';
} else {
window.location.href = '/payment-processing';
}
});
Security Warning
Never complete shipment, activate membership, or deliver virtual goods based solely on frontend callbacks. Must rely on server-side results.
# Complete Example
Here is a complete HTML/JS example with full interaction logic:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Widget Integration Example</title>
</head>
<body>
<button id="pay-btn">Pay Now</button>
<!-- Production environment -->
<script type="module" src="https://cashier-hub.enjoypayment.com/sdk/v1/cashier.js"></script>
<!-- Sandbox environment: https://cashier-hub.sandbox.enjoypayment.com/sdk/v1/cashier.js -->
<script>
// 2. Listen for events
window.cashier.on('READY', function () {
console.log('SDK loaded');
});
window.cashier.on('RESULT', async function (payload) {
console.log('Payment result:', payload);
// Request backend to confirm order status
const res = await fetch('/merchant-api/order/confirm');
const data = await res.json();
if (data.success) {
window.location.href = '/success';
}
});
window.cashier.on('ERROR', function (payload) {
console.error('Payment error:', payload);
alert('Payment failed, please try again later');
});
// 3. Bind payment button
document.getElementById('pay-btn').addEventListener('click', async function () {
const btn = this;
btn.disabled = true;
try {
// Request backend to create order and get widgetToken
const res = await fetch('/merchant-api/payment/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
mchOrderNo: 'ORD_' + Date.now(),
amount: 100,
currency: 'USD',
widget: true
})
});
const data = await res.json();
// Launch cashier
await window.cashier.open({
widgetToken: data.widgetToken
});
} catch (error) {
console.error(error);
alert('Unable to launch cashier, please try again later');
} finally {
btn.disabled = false;
}
});
</script>
</body>
</html>
# FAQ
Q: Where does widgetToken come from?
A: When the backend calls Unified Order API or Cashier Order API with widget: true, the payment gateway generates the widgetToken in the response.
Q: Can I reuse the same widgetToken?
A: Not recommended. A new Token should be obtained for each payment.
Q: Can I ship goods directly based on frontend RESULT?
A: No. Frontend results are only for user experience. The final order status must be confirmed by server-side query or callback.
Q: popup doesn't open, what should I do?
A: Ensure it's called within a user click event. If it still fails, switch to redirect mode.
Q: Does the merchant frontend need to know internal payment parameters?
A: No. The frontend only needs widgetToken.
Q: What if the cashier doesn't respond?
A: Handle the ERROR event and timeout. After timeout, prompt the user to check later or request the backend to query the status.