Qora by Laayo
Get API keys

Displaying the QR

qr.payload is the string your QR code has to encode. Render it, and any bank or wallet app in Nepal can scan it. Treat it as opaque: do not parse it, do not build one yourself, and do not reuse one.

Rendering it

Do not send the payload to a third-party QR image service — it identifies your merchant account and the amount. Render it locally.

Browser (qrcode)
import QRCode from 'qrcode';

await QRCode.toCanvas(document.getElementById('qr'), payment.qr.payload, {
  width: 240,
  margin: 2,
  errorCorrectionLevel: 'M',
});
PHP (bacon/bacon-qr-code)
class="tok-key">use BaconQrCode\Renderer\ImageRenderer;
class="tok-key">use BaconQrCode\Renderer\Image\SvgImageBackEnd;
class="tok-key">use BaconQrCode\Renderer\RendererStyle\RendererStyle;
class="tok-key">use BaconQrCode\Writer;

$writer = class="tok-key">new Writer(class="tok-key">new ImageRenderer(class="tok-key">new RendererStyle(class="tok-num">240), class="tok-key">new SvgImageBackEnd()));

class="tok-key">echo $writer->writeString($payment['qr']['payload']);
Python (qrcode)
import qrcode

img = qrcode.make(payment["qr"]["payload"], error_correction=qrcode.ERROR_CORRECT_M)
img.save("payment.png")

Practical rules for a scannable code

  • Error correction M. Payment payloads are long; level H pushes the module count high enough that phone cameras struggle.
  • At least 200×200 px on screen, or 2.5 cm printed.
  • Keep the quiet zone. Two modules of clear space on every side, or scanners fail to lock on.
  • Dark on light, high contrast. Do not invert for dark mode — many scanners will not read a light-on-dark code. Keep the code itself on a white tile.
  • Never put a logo over it. The extra data density leaves no redundancy to spare.

Show the amount next to it

A dynamic QR already carries the amount, so the customer cannot underpay. Displaying the figure beside the code anyway is what stops the far more common problem: someone paying the wrong order's QR.

Expiry

A QR is payable for five minutes. After qr.expires_at, create a new payment rather than re-displaying the old payload.

When a payment reaches a terminal state, stop showing the QR. Cover or replace it. A stale code left on a counter screen is the easiest way for a customer to pay an order that was already settled or abandoned.

Polling for status

Webhooks are the right answer. Where you genuinely cannot receive them, poll:

bash
curl https://qora.laayo.com.np/v1/payments/pay_7Fh2kQ9mXwRt3vNbLp6sDc/qr \
  -H "Authorization: Bearer sk_test_YOUR_KEY"
json
{
  "id": "pay_7Fh2kQ9mXwRt3vNbLp6sDc",
  "status": "requires_payment",
  "amount": 25000,
  "qr": {
    "payload": "00020101021230590016A0055...",
    "expires_at": "2026-09-07T09:20:00+05:45",
    "expired": false
  }
}

Poll every two seconds while the QR is fresh, then back off. Do not poll faster: it will not make confirmation arrive sooner, and it will hit the rate limit.