Embedded QR widget
Embedding the UPINOW QR widget on your own checkout page, its options, and how status polling works.
Keep the customer on your own page by embedding the QR widget instead of redirecting to the hosted pay page. Add a container and the script anywhere on your checkout page:
<div id="upinow-pay"></div>
<script src="https://app.upinow.in/embed/upinow.js"></script>
<script>
UPINOW.mount("#upinow-pay", {
// Your server creates the order with your API key (POST /api/v1/orders) and returns its JSON.
createOrderUrl: "/api/create-order",
// title: "Pay for your order", // optional; default "Pay with UPI"
onSuccess: function (order) {
// Screen update only. Mark the order paid when the signed webhook arrives.
}
});
</script>Options
| Option | Type | Meaning |
|---|---|---|
createOrderUrl |
string | Recommended. Your own server endpoint. It receives POST { amount } and must return the same JSON POST /api/v1/orders returns. |
statusUrl |
string | Optional. Your own server endpoint: GET statusUrl + order_id returns the order JSON. Without it the widget reads the order's public status, the same one the hosted pay page reads, so no key is needed in the browser. |
apiKey |
string | Your API key, sent as X-API-Key. Testing only; a production page should proxy through createOrderUrl instead so the key never reaches the browser. |
origin |
string | Optional. Your UPINOW address, when the script was not loaded from it. The older name gateway still means the same thing. |
title |
string | Optional. The heading shown above the amount field. Defaults to "Pay with UPI". |
merchantOrderId |
string | Only used with apiKey and no createOrderUrl. Defaults to order_<timestamp>. |
successUrl, failureUrl |
string | Only used with apiKey and no createOrderUrl. Default to the current page. |
webhookUrl |
string | Only used with apiKey and no createOrderUrl. Defaults to <page origin>/api/webhooks/autoupi; set your own, since that default almost never points at a real endpoint on your server. |
customer |
object | Only used with apiKey and no createOrderUrl. Passed straight through to create-order. |
paymentAccountId |
string | Optional. Which of your UPI accounts the order is created on. Only used with apiKey and no createOrderUrl, sent as payment_account_id; with createOrderUrl, your own server decides the account when it calls create-order. Leave it out to use your default account. |
onSuccess |
function | Called with the order once its status becomes paid. For screen updates only; never mark anything paid here. |
The createOrderUrl contract
Your server, not the browser, should hold the API key. createOrderUrl receives a POST with { "amount": ... } and must call POST /api/v1/orders itself (adding merchant_order_id, success_url, failure_url and webhook_url) and return that response as its own JSON body. See the Plain PHP guide's create-order.php for a working example. For a real cart, work out the amount on your own server; never trust an amount the browser sends you.
Status polling
While the QR is shown, the widget checks the order every 2 seconds: statusUrl if you gave one, otherwise the X-API-Key lookup if you gave apiKey, otherwise the order's public status with no key needed. It stops polling once the order is paid or failed, on a 401, 403 or 404 response, and 10 minutes after the order's expiry (a late bank alert can still pay it inside that window).
At your plan's limit, create-order answers 402 plan_limit_reached and the widget shows: "This shop can't take new UPI payments here right now. Please contact the shop." A paused UPI account (402 payment_account_paused, when paymentAccountId names an account beyond your plan's UPI account limit) shows the same friendly line.
Older pages
/embed/panme-pay.js is byte-identical to /embed/upinow.js and window.PanMePay is the same object as window.UPINOW, so a page written against the old name keeps working with no changes.
See the Plain PHP guide for a full create-order.php proxy you can copy.