Create a payment
POST /v1/payments — creates a payment and mints a Fonepay dynamic QR against your
merchant account.
Parameters
| Name | Type | Description |
|---|---|---|
amount required |
integer | Paisa. 25000 is रु 250.00. Minimum 1000 (रु 10.00). Decimals are rejected. |
currency | string | Only NPR. Defaults to NPR. |
reference | string | Your own order or invoice number. Searchable, and returned on the payment. |
description | string | Human-readable note for your dashboard. |
remarks1 | string | Shown in the payer's bank app. Defaults to your reference. Max 50 characters. |
remarks2 | string | Second remark line. Defaults to your business name. |
metadata | object | Up to 20 string key/value pairs, returned on the payment and on every webhook. |
Amounts are integer paisa. Sending 250 when you mean रु 250.00
charges रु 2.50. Sending 250.00 is rejected outright rather than silently rounded —
we would rather fail your request than take the wrong amount from a customer.
Request
curl
curl https://qora.laayo.com.np/v1/payments \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Idempotency-Key: order-1042" \
-d amount=25000 \
-d "reference=Order #1042" \
-d "metadata[table]=4"
PHP
$response = $client->post('https://qora.laayo.com.np/v1/payments', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('QORA_SECRET_KEY'),
'Idempotency-Key' => 'order-' . $order->id,
],
'json' => [
'amount' => $order->total_paisa,
'reference' => 'Order #' . $order->id,
'metadata' => ['table' => $order->table],
],
]);
$payment = json_decode($response->getBody(), class="tok-key">true);
Node
const res = await fetch('https://qora.laayo.com.np/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.QORA_SECRET_KEY}`,
'Idempotency-Key': `order-${order.id}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: order.totalPaisa, // integer paisa
reference: `Order #${order.id}`,
}),
});
const payment = await res.json();
Response
201 Created
{
"id": "pay_7Fh2kQ9mXwRt3vNbLp6sDc",
"object": "payment",
"mode": "test",
"amount": 25000,
"amount_formatted": "250.00",
"currency": "NPR",
"status": "requires_payment",
"prn": "Q260907091500A3KD9WQZ",
"description": null,
"reference": "Order #1042",
"remarks1": "Order 1042",
"remarks2": "Himalayan Coffee House",
"metadata": { "table": "4" },
"qr": {
"payload": "00020101021230590016A00555555555550208...",
"expires_at": "2026-09-07T09:20:00+05:45"
},
"fonepay": { "trace_id": null, "status": null, "verified_at": null },
"payer": {},
"fee": 200,
"succeeded_at": null,
"created_at": "2026-09-07T09:15:00+05:45"
}
Statuses
| Status | Meaning |
|---|---|
requires_payment | QR minted, waiting for the customer to scan and pay. |
processing | A bank event arrived but is not yet final. Transient. |
succeeded | Confirmed and final. The money is on its way to your bank. |
failed | The QR could not be minted, or the payment was declined. Terminal. |
expired | The QR was never paid before it expired. Terminal. |
canceled | You canceled it. Terminal. |
succeeded is set only after Qora has independently verified the payment. An event
arriving is treated as a signal to go and check, never as proof. That is why this field is worth
trusting — and why nothing earlier in the chain is.
Errors
| Status | Code | Meaning |
|---|---|---|
422 | parameter_invalid | A parameter failed validation; see error.errors. |
422 | credentials_missing | No usable Fonepay credentials for this mode. |
502 | gateway_unavailable | Fonepay declined or did not answer. Safe to retry with the same idempotency key. |
429 | rate_limit_exceeded | Too many QR requests this minute. |