In-app checkout

Draw the checkout inside your own app — the QR, a live stream of the payment, and the rules your screen has to keep.

Sending the customer to checkout_url is one way to take a payment. The other is to draw the checkout yourself, in your own app and your own design. You get the same data the hosted page draws from, and the gateway decides the parts a screen most easily gets wrong.

The shape of it

  1. Your server creates the payment with your secret key, exactly as for the hosted page.
  2. It passes your app checkout_stream_url, checkout_data_url, and each route's qr_url from the response.
  3. Your app opens the stream. The first frame is everything it needs to draw: the address, the exact amount, the QR code, the time left.
  4. Another frame arrives whenever any of that changes — the transfer landing, a confirmation, the window closing. Your app redraws from the newest one.
  5. Your server fulfils the order from the payment.paid webhook. Never from the app.

The three URLs are safe on the customer's device. Each carries the payment's checkout token, which opens the payer's view of this one payment — what checkout_url already shows them — and nothing about your account. Your secret key stays on your server.

Read the stream

const stream = new EventSource(payment.checkout_stream_url);

stream.onmessage = (event) => {
  const checkout = JSON.parse(event.data); // the whole object, every time
  render(checkout);
};

Every frame is the whole checkout — the object checkout_data_url returns — so draw from the latest and keep no state of your own. When EventSource reconnects, its first frame brings you up to date.

Status changes arrive as they commit. Confirmation counts are re-read every 25 seconds, so they can trail the chain by that much. After 30 minutes the server sends event: reconnect and closes, and EventSource reconnects on its own.

Some networks cut long-lived connections. While the stream is down, poll checkout_data_url every few seconds — it is the same object. The hosted page does exactly this.

Outside a browser, any Server-Sent Events client works. All three URLs answer from any origin with no API key.

Draw only what is payable

This is the part that costs a payer money when it is wrong, so the gateway decides it.

payable turns false the moment anything arrives — including an underpayment. Do not ask for the difference: on an escrow route the gateway collects a payment's money once, and a top-up that lands after that is never collected — it stays at the deposit address. Show what arrived, and ask the payer to contact you.

It also turns false at expires_at, by the clock, before anything else has moved. The stream sends a frame when it does.

The QR code

qr_url is an image: PNG, or SVG with ?format=svg. It is black on white with its full quiet zone, so it scans on a dark page too.

<img src="https://pay.paygatehq.com/c/cs_…/qr/bsc" width="240" height="240" alt="Scan to pay">

It encodes the route's uri — the wallet link, with the exact amount in it. To draw the code yourself, encode uri with any QR library; it is the same string. Once the route closes the image answers 409 payment_not_payable, so a screen left open on an old frame cannot reload it into a code somebody scans.

When a route has no uri, the gateway cannot name the network for certain. Offer the address to copy, with no QR code and no wallet button.

What else the screen should say

Test it

In test mode a payment has a sim route. Its qr_url encodes the bare address — the simulated chain has no wallet — so you can lay out the screen before you go live. Pay it from your server and watch the frames arrive:

curl -X POST $PAYGATE/v1/test/payments/{id}/pay \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" -d '{}'

Pass {"amount":10000000} instead — micro-units, so 10 USDT — to see what an underpayment does to payable. The rest of the cases are in Test mode.