Return URLs
Use merchantUrl to tell the hosted checkout where to send the customer when they finish. It is optional and works the same on every order-creating call: POST /api/v1/pay/session/create, POST /api/v1/pay/pos/create, POST /api/v1/pay/qr/create and POST /api/v1/merchant/checkout-links.
"merchantUrl": {
"success": "https://shop.example/qasimha/return?order=10001",
"failure": "https://shop.example/qasimha/return?order=10001",
"cancel": "https://shop.example/cart"
}| Field | When the customer is sent there | Typical value |
|---|---|---|
success | The order is confirmed | Your return page |
failure | The order was declined, expired, or failed | The same return page |
cancel | The customer pressed "Cancel and return to store" before paying | Your cart |
notification | Reserved. Don't send it | — |
Rules
- Each URL must be absolute and at most 1,024 characters, with no
#fragment. - Allowed schemes:
https;httponly when the host islocalhostor127.0.0.1; or a custom app scheme such asmyshop://for mobile apps. A custom scheme must be at least 3 characters, andjavascript,data,file,blobandvbscriptare refused. successandfailuremust be sent together. Sending only one, or onlycancel, is refused.canceldefaults tofailureif you leave it out.notificationis reserved and refused. Use webhooks instead.- You can keep your own query string, such as
?order=10001. Qasimha adds its parameters to it.
A URL that breaks a rule returns 400 VALIDATION_ERROR, and errors names the field and the rule:
{
"success": false,
"code": "VALIDATION_ERROR",
"message": "One or more validation errors occurred.",
"data": null,
"errors": [
{ "field": "MerchantUrl.Success", "message": "must use https, http on localhost, or a custom app scheme." }
]
}What Qasimha adds to the URL
At the end of the journey the hosted page sends the customer to your URL with these parameters appended (? or & is chosen for you):
| Parameter | Value |
|---|---|
orderId | The Qasimha order id (UUID) |
orderReferenceId | Your reference, URL-encoded |
status | confirmed on the success URL; declined, expired, or failed on the failure URL; cancelled on the cancel URL |
For example:
https://shop.example/qasimha/return?order=10001&orderId=9a2e6c4b-1111-4c2d-8a1f-000000000001&orderReferenceId=SHOP-ORDER-10001&status=confirmedWhat the customer sees:
- Success, decline and expiry screens show a "Return to store" button and redirect on their own after 5 seconds.
- "Cancel and return to store" is shown on every step before payment. It sends the customer to
cancel. The order staysAwaitingCheckoutuntil it expires after two hours, or until you cancel it withPOST /api/v1/merchant/orders/{id}/cancel.
A return is not proof of payment. The return isn't signed, and anyone can open your return URL with
status=confirmedin it. Before you treat an order as paid, read it from your server withGET /api/v1/merchant/orders/{orderId}and check thatstatusisConfirmedandorderReferenceIdis yours.
Build the return route
Use status only to choose which page to show while you check. Decide on the order you read back.
app.get('/qasimha/return', async (req, res) => {
const orderId = String(req.query.orderId ?? '');
const stored = await findOrderByQasimhaId(orderId); // the id you saved at creation
if (!stored) return res.redirect('/cart');
const order = await qasimha.orders.get(orderId);
if (order.status === 'Confirmed' && order.orderReferenceId === stored.reference) {
await markPaid(stored.reference); // safe to repeat: the webhook may have done it
return res.redirect(`/orders/${stored.reference}/thank-you`);
}
return res.redirect(`/checkout?payment=${encodeURIComponent(order.status ?? "")}`);
});$orderId = $_GET['orderId'] ?? '';
$stored = findOrderByQasimhaId($orderId); // the id you saved at creation
if (!$stored) { header('Location: /cart'); exit; }
$order = $qasimha->orders()->get($orderId);
if ($order->status === \Qasimha\Model\Enum\OrderStatus::CONFIRMED && $order->orderReferenceId === $stored['reference']) {
markPaid($stored['reference']);
header('Location: /orders/' . rawurlencode($stored['reference']) . '/thank-you');
} else {
header('Location: /checkout?payment=' . rawurlencode((string) $order->status));
}curl https://api-uat.qasimha.com/api/v1/merchant/orders/9a2e6c4b-1111-4c2d-8a1f-000000000001 \
-H "X-Api-Key: $QASIMHA_API_KEY"If you only kept your own reference, read the order with GET /api/v1/merchant/orders/by-reference/{orderReferenceId}. The customer may also never come back (for example, they close the browser), so always handle the webhook too.
Per environment
Build the URLs from each deployment's own base URL, so a UAT order returns to your test site and a production order to your live site. Never point production orders at a test host.
Mobile apps
Use one of these as the return URL, and register the same value in your app:
- An https URL on your own domain that opens the app (Android App Links, iOS Universal Links). This is the secure choice.
- A custom scheme such as
myshop://qasimha/return. It is simpler, but another app can claim the same scheme.
The mobile SDKs open paymentUrl in the system browser, watch for navigation to any of your three URLs, and return a typed result: success (confirmed), failure (declined, expired, failed) or cancelled (cancelled, or the customer closing the sheet). Your app then asks your server to confirm the order.
Troubleshooting
400 VALIDATION_ERROR on MerchantUrl.Failure
You sent success without failure (or the reverse). Send both. They can be the same URL.
400 VALIDATION_ERROR on MerchantUrl.Notification
notification is reserved. Remove it and register a webhook endpoint instead.
My http:// URL is refused
Plain http is allowed only for localhost and 127.0.0.1. Use https everywhere else, including UAT test sites.
The customer returned with status=confirmed but the order isn't Confirmed
Trust the order you read, not the parameter. Show a "payment pending" page and wait for the webhook, or read the order again shortly.
Related
Updated about 23 hours ago
