Skip to main content
All articles
Guide13 min read

Accepting payments on Shopify in Morocco

Shopify store with Moroccan customers: accept 3-D Secure cards and cash at an agency, in dirhams, by payment link then API checkout, with no plugin.

To accept payments on Shopify in Morocco, there is no plugin to install: you create a manual payment method in Shopify, 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, without a line of code; once volume picks up, the API-driven hosted checkout takes over, with a webhook that marks the order paid. This guide walks through the three steps, then cash, refunds and reconciliation.

Shopify Payments in Morocco: where things stand

Shopify Payments, Shopify's built-in payment processing, can only be used by businesses located in the countries on its official list, and Morocco is not on it (list checked on October 2, 2026). A Moroccan store therefore cannot turn it on. Shopify does accept other providers inside its own checkout, but its developer documentation reserves building those payments extensions for partners it approves and for some Shopify Plus merchants. There is no ChariPay plugin for Shopify — no app, no checkout extension — and we are not promising one.

What remains open to every store is manual payment methods. Shopify lets you create a custom one with three fields: a name, additional details shown next to the method when the customer chooses it, and payment instructions shown on the order confirmation page once the order is placed. The order is recorded as unpaid, and you mark it paid from the order page once the money is in. That mechanism, documented by Shopify, is where ChariPay plugs in: the customer orders in your store, pays on a ChariPay page, and the order is marked paid — by hand at the first level, automatically after that.

What the ChariPay page brings to your 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. For the big picture — which methods to offer, what cash on delivery really costs, custom-built sites — read our guide to e-commerce payments in Morocco.

The first level needs no developer and no technical access, and you can rehearse it before opening it to customers: Start in test mode. After the click come three fields — name, work e-mail, company — then the activation link that arrives by e-mail so you can choose your password. In the portal, your developer creates the test key for step 2, and you create a test payment link for the amount of a real 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 no approval to wait for: verification of your business (KYB) only gates going live.

From there, the flow comes down to five moves:

  1. 1Create the manual payment method in Shopify. Give it a name that tells customers what will happen — "Card or cash at an agency", for example. In the additional details, announce the next step: "After you order, you will receive a secure payment link." In the payment instructions, name the channel: WhatsApp, text message or e-mail.
  2. 2Receive the order, unpaid. Shopify records every manual-payment order that way: do not prepare it yet.
  3. 3Create the payment link from the ChariPay portal for the exact amount, with the order number in the description ("Order #1042"), single use, and a fitting expiry: 24 hours, 7 days, 30 days or a specific date.
  4. 4Send it by WhatsApp, by e-mail or as a QR code. The customer pays on a hosted page showing your name and logo: by card with 3-D Secure, or in cash at an agency.
  5. 5Mark the order paid in Shopify once the portal shows the link as paid, then ship. Shopify provides that action on the order page, for a payment received outside Shopify.

This flow is enough while you handle orders one at a time, and it works outside the store too: a sale closed on Instagram, a deposit, an order taken over the phone. The Payment links page covers the options — QR code, PDF poster, saved customers — and the guide payment link or checkout helps you decide when to move up a level.

Step 2 — the hosted checkout, one session per order

When orders keep coming, creating every link by hand becomes the bottleneck. The second level automates that move, still with no plugin: nothing gets installed in Shopify, and a small service on your own server bridges the two platforms.

  1. 1Shopify notifies your server. In Shopify's notification settings, create a webhook on order creation, in JSON format, pointing to an https:// address on your server, and verify those notifications as Shopify's documentation describes.
  2. 2Your server creates a payment session for every order placed with your manual payment method.
  3. 3The customer receives the `checkoutUrl` by e-mail, text message 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).

Here is the session creation in the sandbox, with a test key; the key travels in the X-CHARI-PAY-API-KEY header and never leaves your server.

bash
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: shopify-1042-session' \
  -d '{
    "amount": 249.00,
    "orderId": "#1042",
    "externalId": "shopify-1042",
    "config": {
      "customer": {
        "email": "amine.bennani@example.com",
        "firstName": "Amine",
        "lastName": "Bennani",
        "phone": "+212600000000"
      },
      "urls": {
        "accept": "https://your-store.ma/pages/payment-received",
        "decline": "https://your-store.ma/pages/payment-declined",
        "notification": "https://api.your-store.ma/webhooks/charipay"
      }
    },
    "metadata": { "source": "shopify", "shopifyOrderId": "5512345678901" }
  }'

The 201 response returns sessionId, sessionToken, verifyKey, expiresAt and the checkoutUrl to pass on to the customer. orderId holds the order number your customer sees; it comes back in webhooks and is not uniqueness-enforced. externalId, derived from the order number, makes the call safe to replay: if the same order reaches you 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: it carries the Shopify order ID that step 3 will need. A session is single-use and expires 72 hours after creation by default; expiresAt shortens that. The base URL is the same in the sandbox and in production: the key, chari_sk_test_… or chari_sk_live_…, selects the environment. Every field is described in the Checkout sessions module, and the hosted checkout page shows what your customer sees.

Step 3 — the webhook that updates the order

A customer who has paid does not always come back to your store: they close the tab, lose their connection, lock their phone. So the order is marked paid only when the ChariPay webhook arrives, never on the redirect to your accept URL alone. ChariPay sends the payment.succeeded event to the session's notification URL with two headers: X-CHARI-SIGNATURE, a lowercase hex HMAC-SHA256 computed over the string timestamp + "." + rawBody, and X-CHARI-TIMESTAMP, the timestamp in milliseconds. Here is the verification as the API documentation publishes it, with its comments translated:

javascript
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'));
}

Your service then runs five moves:

  1. 1Verify the signature over the raw bytes, before any parsing: re-serialized JSON no longer produces the bytes that were signed.
  2. 2Deduplicate on `Chari-Event-Id`, not on Chari-Webhook-Id, which identifies the delivery, not the event.
  3. 3Find the Shopify order through the metadata, returned as is in the event: shopifyOrderId gives the order's ID.
  4. 4Mark it paid through the Shopify Admin API: the orderMarkAsPaid mutation, called with the order's global ID (gid://shopify/Order/…), records a transaction for the outstanding balance and requires write access to orders (write_orders).
  5. 5Answer `2xx` once the update and the deduplication record are both committed; do the rest — customer e-mail, fulfillment — in the background.

If your server does not 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. The portal's delivery log shows every attempt, your server's response and the exact event body, and lets you replay in one click. The five mistakes found in almost every integration are covered in getting webhooks right.

Cash at an agency for customers without a card

Some of your customers have no card, or would rather not use it online. Instead of sending them back to cash on delivery, offer them cash 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 marks the Shopify order paid with the same mutation as in step 3.

ChariPay is the only payment gateway in Morocco that also collects cash at agencies. Three habits for a Shopify store:

  • 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 order that should no longer stay open, cancel the link (POST /v1/payment-links/{reference}/cancel): a canceled link can no longer be paid.
  • Prepare the order only once payment is confirmed. Between the order and the deposit, it stays unpaid in Shopify, and that is expected.
  • Check the amount deposited. An agency collects what it is handed: if the amount differs from the order, the gap must be visible on your side before you ship.

The reference is generated the first time the payment page opens: let the page show it to the customer rather than copying it yourself. You can keep cash on delivery as an option, but an order settled at an agency ships already paid, with no cash riding along with the courier. The full journey, for the customer and for your books, is in collecting cash by reference.

Refunds and reconciliation

A return, an out-of-stock item, a goodwill gesture: the refund goes out from the ChariPay portal or through POST /v1/refunds, identifying the original operation by its operationId, with a reason and your own refundReference — in full, or for a partial amount with refundAmount. The reference doubles as an idempotency key: creation answers 202, and replaying the same reference returns the existing refund with 200, without debiting twice. The amount is deducted from your payment account's available balance; if the balance does not cover it, the operation is blocked rather than creating a silent overdraft. The refund.succeeded event confirms completion, because a refund is not instant on the bank's side, and refunding a customer is free. Fields and response codes are in the refunds API module; on the Shopify side, record the refund on the order so both histories tell the same story.

Reconciliation comes down to one discipline: the same reference everywhere. The Shopify order number travels in orderId, which a session's payment event sends back to you, and the order's ID in your metadata, echoed verbatim. The CSV export, from the portal or through GET /v1/transactions/export.csv, lists every movement — card payment, 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 matching them against your orders. The payout to your bank remains a separate operation, automatic every night once a settlement account is configured, or by instant transfer on demand, with a reference that shows up on your bank statement.

Frequently asked questions

Is there a ChariPay plugin for Shopify?

No, there is none, and we are not promising one: no app, no checkout extension. A Shopify store gets paid with ChariPay through two real 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 marks the order paid. Both rely on a manual payment method, which Shopify lets you create.

Is Shopify Payments available in Morocco?

No. Morocco is not on the official list of countries where Shopify Payments is offered, checked on October 2, 2026, so a business located in Morocco cannot turn it on. Your Shopify store remains fully usable: you add a manual payment 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: API amounts are in MAD, in major units, with no currency field — 249.00 means MAD 249.00. If your store shows its prices in dirhams, the order total carries over unchanged into the link or the session. Balances, refunds and payouts are in dirhams too, with no conversion between what the customer pays and what your accountant reads.

How do I handle an abandoned cart?

In this setup, the abandoned cart is a Shopify order that was placed but never paid. Give every session and every card-paid link an expiry — 72 hours by default for a session, expiresAt to shorten it — follow up once with a new link, then cancel the order in Shopify; a cash link does not expire: cancel it along with the order. A session created with notifyOnFailure: true also tells you about a declined card payment through the payment.failed event.

When is the money available?

The instant the payment succeeds, by card or in cash: the amount is credited to your ChariPay payment account, held by Chari Money with a RIB in your company's name, and usable right away for a transfer, a bill payment 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 a developer to get started?

No. The manual payment method is set up in Shopify, and the payment link is created in the ChariPay portal and sent by message: no code at all. A developer becomes useful when you want to automate, meaning create a session for every order and mark the order paid when the webhook arrives. Many stores start without one and bring one in once orders keep coming.

How much does it cost?

The sandbox is free, with no time limit and no commitment. When you go live, activation costs MAD 6,000 including VAT, paid once; the commission per successful payment, as a percentage, and the deposit are set after your application has been reviewed. Refunding a customer is free. The Pricing page also lists what is included at no extra cost, such as the payment account with a RIB and cash at an agency.

Next step

Start in test mode: create your account, pay a link for the amount of a real order with the test card, then open a second link in cash to see the journey your customer goes through. When your developer sees payment.succeeded land on their server, step 3 is within reach. For the full picture of methods and costs, read the guide to e-commerce payments in Morocco; if your store runs on WordPress, see accepting payments on WooCommerce in Morocco.

Sources

Written by ChariPay team.

Read next

Guide5 min read

Payment link or checkout: which to pick

Payment link or hosted checkout: two ways to get paid online, two very different workloads. The criteria that decide — volume, amount, team, customisation.

Guide5 min read

3-D Secure: what makes a payment fail

3-D Secure and payment success rates: why card payments fail, the real causes in order of frequency, and what you can actually fix on your side.

Ready to try it yourself?

Create your account and take test-mode payments today: it is free, with no approval to wait for. A question? Our team supports merchants and developers alike.

  • Free, no commitment
  • Test mode from sign-up
  • No approval to wait for