Accepting payments on WooCommerce in Morocco
WooCommerce store in Morocco: accept 3-D Secure cards and cash at an agency, in dirhams, with the ChariPay module, a payment link or the API checkout.
To accept payments on WooCommerce in Morocco, the ChariPay module for WooCommerce is available on GitHub. Without installing the module, you can also keep one of WooCommerce's offline payment methods, and your customers pay for the order on a ChariPay page — by card with 3-D Secure, or in cash at a Damane Cash, Chaabi Cash, Tasshilat, Barid Cash or Chari agency — in dirhams, with the money available instantly in your payment account. A payment link sent from the portal is enough to get started; to automate, your server creates a payment session for every order, and the WooCommerce REST API moves the order to "Processing" (processing) as soon as the webhook confirms payment. Here are the three steps of that route, then cash, refunds and reconciliation.
WooPayments in Morocco: where things stand
WooPayments, the built-in payment solution offered by WooCommerce, is reserved for individuals and businesses in the countries on its official list, and Morocco is not on it (list checked on October 2, 2026). A business located in Morocco therefore cannot use it to get paid. WooCommerce does ship three offline methods, which take no online payment at all: direct bank transfer, check payments and cash on delivery. The ChariPay module for WooCommerce, available on GitHub, lets your store take payments with ChariPay. Without installing the module, the payment gateway is the ChariPay page where your customer pays for the order: that is the route described below.
Those offline methods are enough to wire everything up. Each one is configured with a title shown to customers at checkout, a description shown if they choose it, and payment instructions. Their statuses differ, and that is what matters next; we give each one with its API slug in parentheses, because the label shown depends on your dashboard language and the slug does not. With direct bank transfer, as with check payments, WooCommerce sets the order to "On hold" (on-hold) while you confirm the payment. With cash on delivery, it sets the order to "Processing" (processing) until payment is collected, even though nothing has been paid yet. For a ChariPay payment, the first behavior is the right one: an order that waits for confirmation, and that you prepare only once the money is in.
What the ChariPay page brings to the store: cards — Visa, Mastercard, Maroc Pay — with 3-D Secure, cash at an agency for customers without a card, amounts in dirhams, and a payment account in your company's name, held by Chari Money, a Bank Al-Maghrib-licensed payment institution, where every successful payment is available instantly. To see where WooCommerce fits among the other ways to sell online — custom site, Shopify, hosted storefront — read our guide to e-commerce payments in Morocco.
Step 1 — the payment link on the order
The first level touches neither WordPress code nor a server, and you can rehearse it before opening it to customers: Start in test mode. What happens after the click: three fields — name, work e-mail, company — the activation link that arrives by e-mail, then choosing your password. In the portal, your developer creates the test key for step 2, and you create a test link for the amount of a typical order and pay it with the test card 4918 9141 0719 5005 (CVV 123, 3-D Secure code 555). It is free, with no time limit, and there is no approval to wait for: verification of your business (KYB) only gates going live.
From there, the flow comes down to five moves:
- 1Rename an offline method. In WooCommerce's payment settings, enable direct bank transfer and give it a title that describes the actual journey — "Card or cash at an agency" — then, as the description: "After you order, you will receive a secure payment link by WhatsApp or e-mail." Leave its bank account details empty: WooCommerce would show them on the order received page and in the order e-mail sent to the customer.
- 2The order arrives "On hold" (`on-hold`). WooCommerce waits for you to confirm payment; stock has already been reduced.
- 3Create the payment link in the ChariPay portal for the exact amount, with "Order no. 5871" as the description, single use, and an expiry: 24 hours, 7 days, 30 days or a specific date.
- 4Send it by WhatsApp, by e-mail or as a QR code. The customer pays on a hosted page with your name and logo: by card with 3-D Secure, or in cash at an agency.
- 5Move the order to "Processing" (`processing`) once the portal shows the link as paid. For WooCommerce, that status means payment has been received and the order is awaiting fulfillment; it becomes "Completed" (
completed) once fulfilled.
This flow holds as long as orders arrive at a pace you can follow by hand, and it works outside the store too: an accepted quote, a deposit, a sale closed on WhatsApp. The Payment links page presents the options — QR code, PDF poster, saved customers — and the guide payment link or checkout gives the criteria for moving up a level. On a showcase site without WooCommerce, a payment button does the same job: see payment button on a website.
Step 2 — the hosted checkout, one session per order
The second level removes the manual link creation, still without installing the module: nothing gets installed in WordPress. WooCommerce can already notify an outside server and accept an order update through its REST API; all it takes is a small service, on your server, between WooCommerce and ChariPay.
- 1WooCommerce notifies your server. Under WooCommerce > Settings > Advanced > Webhooks, add a webhook on order creation, pointing to an
https://address on your server, with a secret: WooCommerce uses it to generate a hash of each delivery, which your server checks as WooCommerce's documentation describes. - 2Your server creates a payment session for every order placed with your offline method.
- 3The customer receives the `checkoutUrl` by e-mail or WhatsApp, through your usual sending tools, and pays on the hosted checkout. A session only takes cards, with 3-D Secure: to keep cash, your service creates a single-use payment link instead (see below).
The session creation, in the sandbox, with a test key that never leaves your server:
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: woocommerce-5871-session' \
-d '{
"amount": 250.00,
"orderId": "WC-5871",
"externalId": "woocommerce-5871",
"config": {
"customer": {
"email": "amine.bennani@example.com",
"firstName": "Amine",
"lastName": "Bennani",
"phone": "+212600000000"
},
"urls": {
"accept": "https://your-store.ma/order/payment-received",
"decline": "https://your-store.ma/order/payment-declined",
"notification": "https://api.your-store.ma/webhooks/charipay"
}
},
"metadata": { "source": "woocommerce", "wcOrder": "5871" }
}'The 201 response returns sessionId, sessionToken, verifyKey, expiresAt and the checkoutUrl to pass on. The WooCommerce order number travels in orderId, the human-readable reference, returned in webhooks without any uniqueness constraint, and in externalId, unique per account and per environment, which makes the call safe to replay — if WooCommerce delivers the same order twice, the API returns the existing session with 200 instead of creating a second one. The four config.customer fields are required, every URL you declare must be https://, and metadata — 4 KB at most — comes back as is in the payment webhook: wcOrder carries the order ID that step 3 will need. The session is single-use and expires 72 hours after creation by default, unless a shorter expiresAt is set. The same call works in production: only the key changes, chari_sk_live_… instead of chari_sk_test_…. The Checkout sessions module details every field, and the hosted checkout page shows the screen your customer sees.
Step 3 — the webhook that updates the order
The redirect to your accept URL tells you the customer came back, not that they paid: a customer can pay, close the tab and never see your store again. So the WooCommerce order moves to "Processing" only when the ChariPay webhook arrives. The payment.succeeded event reaches the session's notification URL with X-CHARI-SIGNATURE, a lowercase hex HMAC-SHA256 computed over timestamp + "." + rawBody, and X-CHARI-TIMESTAMP, in milliseconds. The verification, as the API documentation publishes it, with its comments translated:
const crypto = require('crypto');
function verify(rawBody, signature, timestamp, secret) {
// ±5-minute anti-replay window — the timestamp is in milliseconds.
if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
// A malformed signature must return false — never throw (500 → retries).
if (!/^[0-9a-f]{64}$/i.test(signature)) return false;
// Constant-time comparison: never `===`.
return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(signature, 'hex'));
}Around that function, your service does five things, in this order:
- 1It verifies the signature over the raw body, before any parsing, and rejects a timestamp that is more than five minutes off.
- 2It deduplicates on `Chari-Event-Id`: the same notification may arrive several times;
Chari-Webhook-Ididentifies the delivery, not the event. - 3It finds the order through the
metadata, returned as is in the event:wcOrdergives its ID. - 4It moves the order to "Processing" through the WooCommerce REST API:
PUT /wp-json/wc/v3/orders/<id>with the statusprocessing. - 5It answers `2xx` once the update and the deduplication record are saved, and leaves the rest — e-mail, shipping label — to a background job.
Without a 2xx answer, ChariPay retries: immediately, then after 1 min, 5 min, 30 min, 1 h, and every 6 h, up to 16 attempts over roughly 72 hours. Shared hosting down for maintenance within that window therefore loses nothing: the portal's delivery log shows every attempt and your server's response, and lets you replay in one click. The classic traps — signature checked after parsing, no deduplication, heavy work inside the request — are covered in getting webhooks right.
Cash at an agency for customers without a card
A Moroccan store loses sales if it takes cards only: some customers have none, or refuse to type one in online. Cash on delivery answers that need, but it ships parcels nobody has paid for yet. Cash at an agency settles the question at order time. A payment session only takes cards: a cash payment always relies on a single-use payment link — one reference per deposit — either the step 1 link, on whose page the customer picks cash, or a link your service creates with POST /v1/payment-links and paymentMethod: CASH, carrying the same metadata as a session. The customer receives a reference and deposits the amount at a partner agency. As soon as the network confirms the deposit, the link turns "Paid", you are notified in the portal, and the amount is available instantly in your payment account. On the server side, read the link's state with GET /v1/payment-links/{reference}: once it is "Paid", your service moves the order to "Processing" with the same call as in step 3.
ChariPay is the only payment gateway in Morocco that also collects cash at agencies. For a WooCommerce store, three rules:
- No expiry on the cash link: create it without
expiresAt, as the portal does with "Agency payment" — the customer pays when they stop by an agency. For an "On hold" order that should not stay that way, cancel the link (POST /v1/payment-links/{reference}/cancel): a canceled link can no longer be paid. - Keep "On hold" (`on-hold`) until the deposit. That is exactly the difference with cash on delivery: in your store, "Processing" will always mean "paid".
- Check the amount received. An agency collects what it is handed; if the deposit differs from the order total, the gap must show up before you ship.
The reference is generated the first time the payment page opens: the page is what presents it to the customer. Payment by reference, its journey and its accounting trail are detailed in collecting cash by reference.
Refunds and reconciliation
An item sent back, an order canceled after payment: the refund goes out from the ChariPay portal or through POST /v1/refunds, pointing to the original operation by its operationId, with a reason and your refundReference, in full or partially with refundAmount. That reference guards against a double refund: creation answers 202, and replaying the same reference returns the existing refund with 200. The amount is taken from your payment account's available balance, and the operation is blocked if the balance falls short. The refund.succeeded event signals completion — a refund is not instant on the bank's side — and refunding a customer is free. The refunds API module gives the fields and responses; on the WooCommerce side, a full refund matches the "Refunded" (refunded) status, to be set on the order so both histories stay consistent.
For reconciliation, everything rests on the WooCommerce order number, present in orderId, in your externalId and in your metadata; a session's payment event sends back your metadata, verbatim, and your orderId. The portal's CSV export, or GET /v1/transactions/export.csv through the API, lists every movement — card, cash deposit, refund, payout — with its reference, and the portal separates the amount paid, the fees and the net for each operation. The export's timestamps are in UTC: convert them to Moroccan time before comparing them with your orders. The payout to your bank runs on its own schedule: automatic every night once a settlement account is configured, or by instant transfer on demand, with a reference that appears on your statement.
Frequently asked questions
Is there a ChariPay plugin for WooCommerce?
Yes: the ChariPay module for WooCommerce is available on GitHub and lets your store take payments with ChariPay. Without installing the module, a WooCommerce store also gets paid with ChariPay through two routes: the payment link, created in the portal and sent to the customer, then the API-driven hosted checkout, with one session per order and a signed webhook that moves the order to "Processing" (processing). Both start from one of WooCommerce's offline methods.
Is WooPayments available in Morocco?
No. Morocco is not on the official list of countries where WooPayments is offered to individuals and businesses, checked on October 2, 2026. Your WooCommerce store still works perfectly well in Morocco: you enable an offline method, and the customer pays on the ChariPay page, by card with 3-D Secure or in cash at a partner agency.
Can I get paid in dirhams?
Yes, and only in dirhams: the API expresses amounts in MAD, in major units, with no currency field — 250.00 means MAD 250.00. If your WooCommerce store is set to dirhams, the order total carries over unchanged into the link or the session. Your balance, refunds and payouts are in dirhams too: no conversion between the customer's payment and your books.
How do I handle an abandoned cart?
Here, the abandoned cart is an "On hold" (on-hold) order that was never paid. Give every session and every card-paid link an expiry — 72 hours by default for a session, expiresAt to shorten it — send a single reminder with a new link, then move the order to "Cancelled" (cancelled) in WooCommerce; a cash link does not expire: cancel it along with the order. A session created with notifyOnFailure: true also warns you of a declined card payment, through the payment.failed event.
When is the money available?
As soon as the payment succeeds, by card or in cash: the amount is credited instantly to your ChariPay payment account, held by Chari Money with a RIB in your company's name, and you can use it right away for a transfer, a bill or a refund. The payout to your bank is a separate operation, automatic every night once a settlement account is configured, or by instant transfer on demand.
Do I need to change WordPress code?
No. At the first level, you rename an offline method in the settings and create links from the ChariPay portal. At the second, the code lives on your server, not in WordPress: it receives WooCommerce's native webhooks, calls the ChariPay API, then updates the order through the WooCommerce REST API. Your WordPress and WooCommerce updates stay independent of how you get paid.
How much does it cost?
The sandbox is free, with no time limit and no commitment. Going live costs a one-time activation fee of MAD 6,000 including VAT; the commission per successful payment, as a percentage, and the deposit are set after your application has been reviewed. Refunding a customer costs nothing. The Pricing page details what is included at no extra cost, from the payment account with a RIB to cash at an agency.
Next step
Start in test mode: open your account, pay a link for the amount of a typical order with the test card, then open a cash link to see the journey offered to the customer. When your server receives its first payment.succeeded and moves a test order to "Processing", the integration is done: on go-live day, only the key and the webhook secret change. The guide to e-commerce payments in Morocco gives the full picture of methods and costs; for a Shopify store, read accepting payments on Shopify in Morocco.
Sources
- WooCommerce — WooPayments: supported countries — read on 2026-10-02
- WooCommerce — Core payment options — read on 2026-10-02
- WooCommerce — Direct Bank Transfer (BACS) — read on 2026-10-02
- WooCommerce — Check Payments — read on 2026-10-02
- WooCommerce — Cash on Delivery — read on 2026-10-02
- WooCommerce — Order statuses — read on 2026-10-02
- WooCommerce — Webhooks — read on 2026-10-02
- WooCommerce REST API — Orders — read on 2026-10-02
Written by ChariPay team.