Skip to content

Accept payments

Hosted checkout and return URLs

Send the payer to the hosted page, bring them back to your site or app, and confirm the result on your server.

When you create a payment, Raïs returns a checkout_url. That address is a hosted payment page: you never handle card details. This guide describes what the payer sees, where they go afterwards and how to confirm the result.

Where the page lives

The payer's pages live on https://pay.rais.app: checkout_url is https://pay.rais.app/pay/{payment_id}, and the receipt_url of a paid payment is https://pay.rais.app/pay/{payment_id}/receipt.pdf. The API lives on https://api.rais.app (every endpoint is under https://api.rais.app/v1): call it from your server, never from the payer's browser.

Use the checkout_url you are given as it is, and do not build it yourself. If your site or your app restricts where it may redirect, or which hosts an in-app browser may open, allow https://pay.rais.app.

The hosted page

Redirect the payer to checkout_url with a normal HTTP redirect (status 303 after a POST) or a link. The page:

  • shows your store name and logo (upload the logo in the dashboard, under the store's Settings; a store without one shows its initial), the amount with commas between the thousands and a dot before the decimals (5,000.00 DA), the description and your reference;
  • is available in English, French and Arabic, with right-to-left layout for Arabic. Set the language with language when you create the payment;
  • collects the card number, expiry, CVC and name, then runs 3-D Secure through SATIM;
  • lets the payer leave through a Back to link that returns to your cancel_url;
  • marks a sandbox payment with a "Test mode" banner. It never lists test cards (they are in Test cards and sandbox), and a live payment never shows any test wording.

The page sets a strict content security policy and forbids being framed: it cannot be embedded in an <iframe>. Redirect the payer, or open the page in a browser tab or a secure in-app browser. See Mobile and app integrations.

Raïs page or SATIM page

payment_page chooses where the card is entered. rais is Raïs' own checkout, described above. satim sends the payer to SATIM's payment page; they come back to a Raïs result page that shows the outcome. Return URLs and parameters work the same way for both. When you omit it, your store's default applies.

Return URLs

success_url and cancel_url say where the payer goes when they leave the checkout. Set them on each payment; a payment without them uses the defaults of your store, and with neither, the result page tells the payer they can close it.

URLUsed when
success_urlThe payment was paid.
cancel_urlThe payment failed, was cancelled, or the payer pressed Back to.

An address is accepted when it:

  • starts with https://, in both environments, or http://, in the sandbox only;
  • or uses your own app scheme, such as myapp://pay/result, in both environments (a scheme of 2 to 31 characters: letters, digits, +, . and -);
  • has at most 512 characters, no spaces, no non-ASCII characters (percent-encode them), no backslashes and no credentials (user:password@host).

javascript:, data:, file:, vbscript:, blob:, about: and ftp: are never accepted. An invalid address answers invalid_url with the parameter name.

What Raïs adds to the address

Raïs merges four parameters into your address and keeps everything else as you wrote it: your own query parameters, a fragment, and a custom scheme.

ParameterValue
payment_idThe id of the payment.
statuspaid, failed or cancelled: what the payer's browser saw. failed does not always close the payment (see below). Never trust it on its own.
referenceYour reference, only when the payment has one.
order_numberThe Raïs order number of the payer's attempt. Each new attempt on the same payment gets a new one: identify the payment with payment_id.

A parameter of yours with one of these names is replaced, so an address cannot pre-fill the result.

Raïs also adds lang (en, fr or ar), the language the payer used on the checkout, so your page can keep it, unless your address already has a lang of its own. A payer who picked the light or dark theme on the checkout is sent back with theme=light or theme=dark too (on the Return to and Back to links, never when the theme is on Auto). Both are only a convenience for your page and say nothing about the payment.

You setThe payer is sent to
https://shop.example/thankshttps://shop.example/thanks?payment_id=5b1c2f4e-9d3a-4c61-8a7e-0f2d6b9e1a33&status=paid&reference=order-1042&order_number=RS12AB34CD
https://shop.example/thanks?cart=7#tophttps://shop.example/thanks?cart=7&payment_id=5b1c2f4e-9d3a-4c61-8a7e-0f2d6b9e1a33&status=paid&reference=order-1042&order_number=RS12AB34CD#top
myapp://pay/result?x=1myapp://pay/result?x=1&payment_id=5b1c2f4e-9d3a-4c61-8a7e-0f2d6b9e1a33&status=paid&reference=order-1042&order_number=RS12AB34CD

The older orderId parameter is no longer sent.

status=failed does not always end the payment

status describes what the payer's browser saw. A payer who leaves the card form, or whose attempt did not go through, comes back with status=failed (or cancelled after pressing Back to) although the payment is still pending: they can open the payment page again, press Try again and pay a minute later. So do not cancel your order because of the address. Wait for the payment.failed or payment.cancelled event, for expires_at, or retrieve the payment and look at its status.

What the payer sees afterwards

Whatever the outcome, the payer first lands on a Raïs result page in the language of the payment, and stays on it: the page never sends the payer anywhere by itself, so they can read the details of the payment. A button leads back to your site.

OutcomeBehaviour
Paid, with a return URLThe page shows the payment details (including the Raïs order ID, never the name on the card) and a button labelled Return to your store name. There is no automatic redirect and no countdown.
Paid, without a return URLThe page says the payer can close it.
Failed or cancelledThe page shows what happened, with no order ID. A Try again button leads back to the payment page for as long as the payment can still be paid, and a Return to button leads to your cancel_url when there is one.
Opened again laterA paid payment shows its paid page again. A refunded one shows "Payment refunded". An expired one shows an expired page.

A paid result page shows the payment method as CIB/Edahabia and never a card number. It also offers Download receipt (PDF), and on one row Print and Email receipt: the receipt goes to the payer's e-mail address when the payment has one, otherwise the payer types one; a receipt is e-mailed once per payment. See Receipts.

Never trust the redirect

The address the payer returns to can be opened by anybody, and the parameters in it can be typed by hand. status=paid proves nothing. And the redirect may never happen: a payer who pays and closes the tab never reaches your site, and Raïs still settles the payment in the background and sends payment.succeeded (see Payments). Before you ship anything, confirm the payment from your server, with one of these:

  1. The webhook. A signed payment.succeeded event is the best trigger. See Webhooks.
  2. The API. Retrieve the payment by the payment_id you stored when you created it, and check status, amount and reference against your order.
// GET /thanks?payment_id=...&status=paid
const stored = await db.orders.findByPaymentId(req.query.payment_id); // the id you saved at creation
const response = await fetch(`https://api.rais.app/v1/payments/${stored.paymentId}`, {
  headers: { Authorization: `Bearer ${process.env.RAIS_SECRET_KEY}` },
});
const payment = await response.json();

if (payment.status === "paid" && payment.amount === stored.amount) {
  // fulfil the order, once
}

Use the id you saved, not the one in the address, and make fulfilment idempotent: the webhook and the return page may both arrive.

Expired and unavailable payments

A payment that passed expires_at unpaid, was cancelled, or was declined by the bank opens a page that says it can no longer be paid (with a Contact the merchant button), and cannot be paid. A payment of a suspended store shows an unavailable page. In every case, create a new payment when the payer wants to try again.

A payment link stays payable until its own expires_at, however long the payer takes: if the gateway's own payment session ran out meanwhile, the checkout opens a new one by itself when the payer starts paying. A payment without an expiry, such as a link created in the dashboard with "Never", is never closed because of its age.