Online booking payments in Morocco: take payment for a slot, a table or a pre-order
Online booking payment in Morocco: a time-limited session or a single-use link, cancellations and refunds, no-shows, pre-orders, and never a double charge.
Online booking payment in Morocco means collecting at the moment the customer books, so that the payment holds the spot: a time slot, a table, a class or a pre-order. With a website, you create a checkout session that expires at the same time as the held slot; without one, you send a single-use payment link per booking, with its own expiry date if it is paid by card. Either way, the booking is confirmed on proof of payment, never on the customer simply coming back to your page. This guide covers both setups, pre-orders, the double click, the cancellation policy, refunds and no-shows.
Online booking payment: collect at the moment of booking
ChariPay is a Moroccan payment platform, a PSP operated by Chari Money, a payment institution licensed by Bank Al-Maghrib. For a booking, its job is simple: get the customer to pay when they pick their spot, and tell you reliably that the payment is secured. A padel court on Thursday evening, a meeting room for a morning, a surf lesson, a table for a group dinner, an order of pastries for Eid: once the spot is paid for, it belongs to that customer and nobody else.
One rule prevents disputes. Confirm the order when the webhook arrives, never on the redirect alone: the redirect says the buyer came back, not that the payment is secured. A customer who lands back on your site has not necessarily paid: they may have abandoned authentication with their bank; a signed payment.succeeded webhook leaves no doubt.
Two neighboring cases have their own guide. If you ask for a deposit on a quote, then the balance on delivery, read collecting a deposit. If you sell places for a dated event with many participants — a tournament, a gala, a workshop — read online ticketing. This guide covers one spot, one time, one customer, paid in full.
With a website: a session that expires with the slot
If your website or app runs a calendar, the checkout session is the natural tool. An order becomes a session: you create the session server-side, redirect the buyer to the hosted checkout — card with 3-D Secure — and receive the result through a signed webhook. Each session has a limited lifetime — 72 hours by default, expiresAt to shorten it — and is consumed only once. For a booking, that is exactly what you need: the session's lifetime becomes the time during which you hold the slot.
Step by step:
- 1Put the slot on hold in your calendar, in the customer's name, as soon as they pick it.
- 2Create the session server-side, with the amount, your
orderIdand anexpiresAtset at the end of the hold time you accept — short for a slot in high demand; withoutexpiresAt, the session lives 72 hours. - 3Redirect the customer to the hosted checkout: they pay by card, with 3-D Secure authentication with their bank.
- 4Confirm the booking when the successful-payment webhook arrives, and only then.
- 5Release the slot at expiry: if the
expiresAttime has passed without payment, read the session withGET /v1/payment-sessions/{sessionId}, then put the slot back in the calendar.
The creation call, with the test key:
curl -X POST 'https://api-psp.charipay.ma/v1/payment-sessions' \
-H 'X-CHARI-PAY-API-KEY: chari_sk_test_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-2026-0421-session' \
-d '{
"amount": 250.00,
"orderId": "ORD-2026-0421",
"externalId": "order-2026-0421",
"config": {
"customer": {
"email": "amine.bennani@example.com",
"firstName": "Amine",
"lastName": "Bennani",
"phone": "+212600000000"
},
"urls": {
"accept": "https://votre-boutique.ma/paiement/succes",
"decline": "https://votre-boutique.ma/paiement/echec",
"notification": "https://votre-boutique.ma/webhooks/charipay"
}
},
"metadata": { "cartId": "c_987", "source": "web" }
}'For a booking, add expiresAt (an ISO-8601 UTC timestamp, in the future) set to the end of the hold you chose, and put the slot identifier in metadata: it comes back to you verbatim in the payment event, together with your orderId. The response contains sessionId, sessionToken, verifyKey, expiresAt and checkoutUrl, nothing else; the status is then read with GET /v1/payment-sessions/{sessionId}. Expiry, cancellation and return pages are detailed in checkout session expiry and return, and the full offer is on the hosted checkout page.
You can run this whole scenario before you open bookings: Start in test mode. Three fields (name, work email, company), an activation link received by email to choose your password, then the portal, where you create your own test key. Create a session with a short expiry, pay it with the test card, and watch your slot get confirmed when the webhook arrives: no real money moves.
Without a website: one single-use link per booking
No booking engine? You take requests by phone, on WhatsApp or on Instagram, and you keep the schedule yourself. The payment link then does the session's job. A payment link is an amount and a description; the customer pays on a page hosted by ChariPay. It is shared as a link, a QR code, a printable PDF poster, by email or WhatsApp.
For a booking:
- One link per booking, single-use, for the exact amount.
- A description that identifies the booking: "Court 2 — Thursday 7 pm — Amine B."
- An expiry that follows the spot, for a link paid by card: 24 hours, 7 days, 30 days, never, or an exact date — pick a close date for a sought-after slot, always before the slot itself.
- A status to watch: active, paid, canceled, expired. You are notified in the portal when the payment comes in; an expired link is a spot to put back on sale.
The single-use link has one advantage the session lacks: on its page, the customer can also pay in cash at an agency. They choose cash, receive a reference, pay it at an agency of the Chari network, and you are notified as soon as the network confirms the deposit; the link turns "Paid." On the server side, read the link's state with GET /v1/payment-links/{reference}. A checkout session, by contrast, only takes cards. For customers who like paying in cash, that can decide the setup.
One setting to know before you send the link. In the portal, turning on "Agency payment" on a link disables its expiry: the link stays payable with no time limit. A link with an expiry date is therefore presented as a link to be paid by card; for a customer who pays in cash, create a link with "Agency payment", without an expiry. The overall choice between the two tools is covered in payment link or checkout, and the offer on the payment links page.
Pre-orders and online payment: stock follows payment
A pre-order works like a slot, with a quantity instead of a time: pastries for a celebration, a limited series, a seasonal basket. The practical rule fits in one sentence: your stock only moves on a successful payment, not when the session or link is created.
In practice:
- 1At creation of the session or link, you set the quantity aside in your own tool, without touching the stock, for the length of the expiry.
- 2On the successful-payment event, you decrement the stock and start preparing.
- 3At expiry without payment, the stock has not moved: you simply put the set-aside quantity back on sale.
That avoids the classic pre-order trap: stock sold out on paper by carts that never paid, while real customers wait. If you sell without a website, the hosted storefront also lets you publish a product with optional stock and its sales page; keep the same discipline and only start preparing once the order is paid.
A double click does not charge twice
A customer who clicks "Book" twice, a connection that drops and your server resending the request: those are the cases that create two payments for one spot, if nothing stops them. Two tools stop them at creation.
- The `Idempotency-Key` header: replaying the same value returns the first result, instead of creating a second session or link.
- The `externalId` field, unique per account and per environment: a creation with an existing
externalIdreturns the existing resource with200 OKinstead of201 Created. Derive it from your booking number, and test the status: both are successes.
On the customer side, the protection is already there: a session is consumed only once, and a single-use link can only be paid once. The general principle, with its pitfalls, is explained in payment idempotency.
Cancellations: write your policy, then refund
Cancellations are only handled well when the rule is written before the sale. A clear cancellation policy says two things: until when the customer can cancel, and how much they get back depending on timing. The numbers are yours — a full refund up to the day before, part of it on the day, nothing once the slot has started: it is your call as a merchant, to show on your website or in the message that comes with the link.
Then, two moves depending on timing:
- Before payment, there is nothing to refund: cancel the link (a canceled link can no longer be paid) or the session, then release the spot.
- After a card payment, refund in full or partially, from the portal or through the API. Refunds are free, the amount is deducted from your available balance, and a refund is not instantaneous on the bank side: let the customer know without promising a date.
The full how-to — portal, API call, tracking, customer message — is in refunding a customer.
No-shows: what paying in advance protects
The customer who does not show up is the very reason to take payment at booking. When the spot was paid in advance, a no-show does not cost you the slot: the amount is already in your payment account, available the instant the payment succeeded.
What paying in advance does not allow, on the other hand, is collecting later an amount the customer has not paid themselves. For a later collection — a top-up, fees set out in your terms — send them a new payment link, which they pay themselves; if the payments are regular, such as a monthly class package, create a subscription: a payment link is then emailed to them at every due date. So there is no "no-show fee" collected automatically after the fact: whatever you want to protect must be paid at the moment of booking.
What remains is deciding what your policy says about no-shows: nothing is returned, part is returned, or the booking is moved once. Write it down in black and white, and apply it the same way to everyone; if you choose to return part of a card payment, it is an ordinary partial refund.
Frequently asked questions
How do I take payment for a booking without a website?
With one single-use payment link per booking: an amount, a description (slot, customer name) and, for a card payment, an expiry set before the slot. You share it by WhatsApp or email, the customer pays on a page hosted by ChariPay, and you are notified in the portal when the payment comes in. For a customer who pays in cash, create a link with "Agency payment" instead, without an expiry.
How long is the spot held during payment?
As long as you decide. For a session, expiresAt sets the lifetime; without it, the default is 72 hours. For a link paid by card, you choose 24 hours, 7 days, 30 days, never, or an exact date. Align that duration with your own hold time, and release the slot as soon as it has passed without payment.
Can a customer pay for a booking in cash?
Yes, with a single-use payment link, created in the portal with "Agency payment" and therefore without an expiry: on its page, the customer chooses cash, receives a reference and pays it at an agency of the Chari network; as soon as the network confirms the deposit, the link turns "Paid" and you are notified in the portal. A checkout session created through the API, by contrast, only takes cards.
Can a double click make someone pay twice for a booking?
No, if you use the tools built for it. At creation, the Idempotency-Key header and an externalId derived from the booking number prevent two sessions or two links from being created: a replay returns the existing resource. On the customer side, a session is consumed only once and a single-use link can only be paid once.
How do I refund a canceled booking?
For a booking paid by card, according to your cancellation policy, in full or partially, from the portal or through the API. Refunds are free, the amount is deducted from your available balance, and a refund is not instantaneous on the bank side. If the booking was not paid yet, there is nothing to refund: simply cancel the link or the session, then release the spot.
Can I make a no-show customer pay later?
Not automatically: a later collection goes through a new payment link, which the customer pays themselves, if they agree to. Whatever you want to protect against no-shows must therefore be paid at the moment of booking; your policy then says whether part of it is returned. For regular payments, a subscription emails the customer a payment link at every due date.
When should I confirm the booking to the customer?
When the successful-payment webhook arrives, never on the redirect alone: the redirect says the buyer came back, not that the payment is secured. For a link paid in cash, wait until the link turns "Paid" in the portal. In between, show "payment being verified" rather than a premature confirmation.
Next step
Pick your setup (a session if your website runs a calendar, a single-use link otherwise), write your cancellation policy, then run a complete booking in test mode: Start in test mode. For the finer mechanics of sessions, read checkout session expiry and return; for paid cancellations, see refunding a customer.
Written by ChariPay team.