JS SDK — Live

PlzPay JS SDK

Accept USDT, TRX, BTC and ETH payments on any website. One script tag, a publishable key, and your checkout is live.

Installation

No npm install needed. Load the SDK with a single script tag, before </body>:

<script src="https://plzpayme.com/sdk/plzpay.js"></script>

Or load from your own domain if self-hosting.

SDK URL: https://plzpayme.com/sdk/plzpay.js — always up to date, served with proper cache headers.

Quick Start

Complete working example — copy, paste, done:

<!-- 1. Your button -->
<button id="pay-btn">Pay 10 USDT</button>

<!-- 2. Load SDK before </body> -->
<script src="https://plzpayme.com/sdk/plzpay.js"></script>

<script>
  document.getElementById('pay-btn').addEventListener('click', function() {
    new PlzPay({
      token: 'plzpay_pub_YOUR_KEY',
      amount: 10,
      currency: 'USDT',
      orderId: 'order-' + Date.now(),
      description: 'Order #123',
      callbackUrl: 'https://yoursite.com/webhook',
      comebackUrl: 'https://yoursite.com/success',
      iframe: true,
      ttl: 30,
      sandbox: false,        // true = test order, no real payment
      onSuccess: function(data) { console.log('Paid!', data); },
      onError: function(err) { console.error(err); },
      onClose: function() { console.log('Modal closed'); },
    }).open();
  });
</script>
⚠️ Create new PlzPay({...}) inside the click handler, not at page load. This guarantees the SDK is fully loaded and the DOM is ready.
<!-- Add data-plzpay attributes to any button -->
<button data-plzpay
  data-token="plzpay_pub_YOUR_KEY"
  data-amount="10"
  data-currency="USDT"
  data-order-id="order-123"
  data-comeback-url="https://yoursite.com/success"
  data-iframe="true">
  Pay 10 USDT
</button>

<!-- Load SDK — auto-attaches click handlers to all [data-plzpay] buttons -->
<script src="https://plzpayme.com/sdk/plzpay.js"></script>

The SDK auto-scans [data-plzpay] elements on DOMContentLoaded and attaches click handlers. No JS needed on your end.

Constructor options

All options passed to new PlzPay({...}):

OptionTypeRequiredDescription
tokenstringYour publishable key (plzpay_pub_…). Safe to use in browser.
amountnumberPayment amount (e.g. 10).
currencystringUSDT · TRX · BTC · ETH. Default: USDT.
orderIdstringYour unique order ID for idempotency. Same ID → same checkout session returned.
descriptionstringShown on the checkout page to the payer.
callbackUrlstringServer-side webhook URL. POST on payment confirmation.
comebackUrlstringRedirect URL after payment. Shown as "Return to merchant" button.
iframebooleantrue = modal overlay (default). false = full page redirect.
ttlnumberCheckout TTL in minutes. Range: 5–1440. Default: 60.
sandboxbooleanTest mode — no real payment. Order is marked as test and a sandbox invoice is sent to the payment provider. Default: false.
onSuccessfunctionCalled when payment confirmed: (data) => {}. data.orderId, data.status.
onErrorfunctionCalled on error: (err) => {}.
onClosefunctionCalled when the iframe modal is closed by user.

Methods

pay.open()

Creates a checkout session via POST /api/merchant/checkout, then opens an iframe modal overlay or redirects (depending on the iframe option). Returns a Promise resolving with the session data.

const pay = new PlzPay({ ... });
const session = await pay.open();
// session = { orderId, checkoutUrl, expiresAt, status }

pay.close()

Programmatically closes and removes the iframe modal overlay.

pay.close();

Button mode — data attributes

All constructor options are available as data-* attributes on any element with data-plzpay. The SDK initializes them automatically on page load.

AttributeCorresponding option
data-tokentoken
data-amountamount
data-currencycurrency
data-order-idorderId
data-descriptiondescription
data-callback-urlcallbackUrl
data-comeback-urlcomebackUrl
data-iframeiframe ("true" / "false")
data-ttlttl

Product Catalog (Multiple Buy Buttons) #

When your page has many products each with its own price, you can wire up all "Buy" buttons without repeating boilerplate. Three patterns — choose the one that fits your setup:

Pattern A — HTML attributes (simplest)

Add data-plzpay attributes directly on each button. No JavaScript required. The SDK auto-attaches click handlers and protects against double-clicks:

<button data-plzpay data-token="plzpay_pub_..." data-amount="19.99" data-description="T-Shirt">Buy</button>
<button data-plzpay data-token="plzpay_pub_..." data-amount="49.99" data-description="Hoodie">Buy</button>
<button data-plzpay data-token="plzpay_pub_..." data-amount="9.99"  data-description="Mug">Buy</button>

<script src="https://plzpayme.com/sdk/plzpay.js"></script>
The SDK automatically disables each button while its checkout is loading and restores it when the modal opens. Double-clicks are safely ignored.

Pattern B — data-order-id-prefix (idempotent IDs, no JS)

If you want a unique, traceable orderId per click without writing JS, add data-order-id-prefix. The SDK generates {prefix}-{timestamp} on every click:

<!-- orderId will be e.g. "shirt-1708000000000" on each click -->
<button data-plzpay
  data-token="plzpay_pub_..."
  data-amount="19.99"
  data-description="T-Shirt"
  data-order-id-prefix="shirt">Buy</button>

<button data-plzpay
  data-token="plzpay_pub_..."
  data-amount="49.99"
  data-description="Hoodie"
  data-order-id-prefix="hoodie">Buy</button>

<script src="https://plzpayme.com/sdk/plzpay.js"></script>
data-order-id-prefix takes priority over data-order-id. Generated format: {prefix}-{Date.now()}.

Pattern C — JS loop (full control)

Use this when you need custom logic per product — conditional callbacks, server-side order creation before checkout, etc.:

<div class="products">
  <button class="buy-btn" data-product-id="1" data-price="19.99" data-name="T-Shirt">Buy</button>
  <button class="buy-btn" data-product-id="2" data-price="49.99" data-name="Hoodie">Buy</button>
  <button class="buy-btn" data-product-id="3" data-price="9.99"  data-name="Mug">Buy</button>
</div>

<script src="https://plzpayme.com/sdk/plzpay.js"></script>
<script>
var TOKEN = 'plzpay_pub_YOUR_KEY';

document.querySelectorAll('.buy-btn').forEach(function(btn) {
  btn.addEventListener('click', function() {
    if (btn._loading) return;          // block double-click
    btn._loading = true;
    var orig = btn.textContent;
    btn.disabled = true;
    btn.textContent = 'Loading\u2026';

    new PlzPay({
      token: TOKEN,
      amount: parseFloat(btn.getAttribute('data-price')),
      description: btn.getAttribute('data-name'),
      orderId: 'product-' + btn.getAttribute('data-product-id') + '-' + Date.now(),
      onSuccess: function(data) { console.log('Paid!', data); },
      onError:   function(err)  { console.error(err); },
      onClose:   function()     {},
    }).open().then(
      function()    { btn._loading = false; btn.disabled = false; btn.textContent = orig; },
      function(err) { btn._loading = false; btn.disabled = false; btn.textContent = orig; }
    );
  });
});
</script>
Try all three patterns live in the SDK Sandbox → Catalog Demo tab.

Events & postMessage

When using iframe mode, the checkout page sends postMessage events to the parent window. Listen for them to react to payment status changes:

window.addEventListener('message', function(event) {
  if (!event.data || typeof event.data !== 'object') return;
  const { type, orderId, status } = event.data;

  if (type === 'plzpay:status') {
    // status = 'completed' | 'failed' | 'expired'
    console.log('Payment status:', status, 'for order', orderId);
  }
  if (type === 'plzpay:close') {
    console.log('Checkout closed');
  }
});

Webhooks

When a payment is confirmed, PlzPay sends a POST request to your callbackUrl. Respond with HTTP 2xx to acknowledge. Failed deliveries are retried: immediately → +30 seconds → +5 minutes.

Payload

{
  "event": "payment.completed",
  "orderId": "ORD-ABCD1234",
  "merchantOrderId": "your-order-123",
  "amount": 10.00,
  "currency": "USDT",
  "status": "completed",
  "txId": "abc123...",
  "timestamp": "2026-02-22T12:00:00.000Z"
}

Verify signature

Every request includes the header X-PlzPay-Signature: sha256=<hmac>. Verify it using your secret key (from the API Keys tab in your merchant cabinet):

const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secretKey) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secretKey)
    .update(rawBody)            // raw string, NOT parsed JSON
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express.js example:
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-plzpay-signature'];
  if (!verifyWebhook(req.body, sig, process.env.PLZPAY_SECRET_KEY)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  if (event.event === 'payment.completed') {
    console.log('Payment confirmed:', event.orderId);
  }
  res.json({ ok: true });
});
⚠️ Use express.raw() (or equivalent) to get the raw body string — parsing JSON before computing HMAC will break signature verification.

Interactive Sandbox

Test all SDK parameters live, see postMessage events in real time, and simulate payments — no real money needed.

Open Sandbox →