A dynamic UPI QR should represent one priced checkout attempt, not a reusable merchant poster. Your backend calculates the amount, creates a stable client transaction ID, stores that pending order, and then asks VyaparGateway for the payment session.
The browser receives only customer-safe payment fields. The API key stays on your server, and a redirect or QR scan never marks the order paid. Final fulfilment follows a signed payment event or an authenticated status check that matches the stored amount and reference.
Design principles
The boundaries that keep this flow reliable
Price on your server
Load the cart from your database and calculate the INR total server-side. Never accept a browser-supplied amount as payment truth.
Reuse one transaction ID
If the create request times out, retry with the same client_txn_id. This keeps an uncertain network result from becoming two checkout attempts.
Persist before presentation
Save the pending order and expected amount before returning the QR, payment URL, or UPI Intent to the customer.
Implementation workflow
From request to a durable result
- 1
Create your local order
Generate a non-guessable order reference, store the amount in integer paise, and set the local state to payment_pending.
- 2
Call create_order
Send client_txn_id, amount, product information, callback URL, and redirect URL from your backend with X-API-Key.
- 3
Render customer-safe fields
Show the returned QR or payment URL. Do not expose the API key, webhook secret, internal tenant data, or provider credentials.
- 4
Confirm asynchronously
Verify the signed webhook or call check_order_status from your backend, then compare order ID, client transaction ID, amount, and status before fulfilment.
Create an order from a Node.js backend
tsconst response = await fetch(
"https://vyapargateway.com/api/v1/create_order",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.VYAPARGATEWAY_API_KEY!,
},
body: JSON.stringify({
client_txn_id: order.paymentReference,
amount: (order.amountPaise / 100).toFixed(2),
p_info: "Website order",
callback_url: "https://shop.example/webhooks/vyapargateway",
redirect_url: `https://shop.example/orders/${order.id}`,
}),
},
);
if (!response.ok) throw new Error(`Gateway returned ${response.status}`);
const result = await response.json(); Production checklist
Verify before going live
- API key exists only in a server secret store
- client_txn_id has a unique database constraint per merchant
- Amount is derived from integer paise and compared again at confirmation
- Customer page displays pending until server verification completes
- Timeout retry reuses the original client_txn_id
Failure recovery
Common QR checkout failures
Two QRs after a timeout
The application generated a new reference on retry. Reuse the original client_txn_id until the outcome is reconciled.
Order paid at a different amount
Do not fulfil. Hold the event for review and compare the expected amount with the verified payment record.
Customer returns but order is pending
This is normal when the redirect arrives before payment verification. Show a pending screen and let your backend reconcile status.
FAQ
Questions developers ask
Is a dynamic QR different for every order?
Yes. The checkout creates a payment session tied to that order reference and amount. It should not be reused as a permanent merchant QR.
Can the browser call create_order directly?
No. X-API-Key is a server credential, and the amount must be calculated by your trusted backend.
Does displaying the QR confirm payment?
No. Fulfil only after a verified webhook or authenticated status response matches your stored order.