# 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

  1. Backend Creates Order:Call the Unified Order API with widget: true to obtain widgetToken
  2. Frontend Includes SDK:Include the SDK script in the merchant's page
  3. 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

  • widgetToken must be obtained by the backend, the frontend should not generate it
  • A new widgetToken should be used for each payment
  • widgetToken is 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 open within a user click event to avoid browser popup blocking
  • A new widgetToken should be used for each payment
  • Prevent duplicate submissions: set the payment button to loading state 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

  1. Frontend receives RESULT event
  2. Frontend prompts user "Payment completed" or "Processing"
  3. Frontend requests merchant backend to query order status (Critical step)
  4. Merchant backend returns final business state based on server results
  5. 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.

Last Updated: 6/13/2026, 6:48:06 PM