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.
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>
new PlzPay({...}) inside the click handler, not at page load.
This guarantees the SDK is fully loaded and the DOM is ready.
Constructor options
All options passed to new PlzPay({...}):
| Option | Type | Required | Description |
|---|---|---|---|
| token | string | ✓ | Your publishable key (plzpay_pub_…). Safe to use in browser. |
| amount | number | ✓ | Payment amount (e.g. 10). |
| currency | string | USDT · TRX · BTC · ETH. Default: USDT. | |
| orderId | string | Your unique order ID for idempotency. Same ID → same checkout session returned. | |
| description | string | Shown on the checkout page to the payer. | |
| callbackUrl | string | Server-side webhook URL. POST on payment confirmation. | |
| comebackUrl | string | Redirect URL after payment. Shown as "Return to merchant" button. | |
| iframe | boolean | true = modal overlay (default). false = full page redirect. | |
| ttl | number | Checkout TTL in minutes. Range: 5–1440. Default: 60. | |
| sandbox | boolean | Test mode — no real payment. Order is marked as test and a sandbox invoice is sent to the payment provider. Default: false. | |
| onSuccess | function | Called when payment confirmed: (data) => {}. data.orderId, data.status. | |
| onError | function | Called on error: (err) => {}. | |
| onClose | function | Called 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.
| Attribute | Corresponding option |
|---|---|
| data-token | token |
| data-amount | amount |
| data-currency | currency |
| data-order-id | orderId |
| data-description | description |
| data-callback-url | callbackUrl |
| data-comeback-url | comebackUrl |
| data-iframe | iframe ("true" / "false") |
| data-ttl | ttl |
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>
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>
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 });
});
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.