Relier un paiement à sa commande : orderId, externalId, metadata et les autres identifiants
Relier un paiement à sa commande : qui crée orderId, externalId, metadata ou operationId, où chacun revient, jusqu'au remboursement et à l'export CSV.
Pour relier un paiement à sa commande, placez votre propre référence dans le champ metadata du paiement : il vous revient tel quel dans l'événement de paiement, et, pour une session, l'événement porte aussi votre orderId. L'externalId, lui, sert à ne jamais créer deux fois la même ressource ; il ne revient pas dans l'événement d'une session ou d'un lien. Ce guide range les sept identifiants d'un paiement — qui les crée, à quoi ils servent, où ils reviennent —, puis les applique à la session, au lien, à l'abonnement et à la boutique hébergée, jusqu'au remboursement et à la ligne d'export CSV.
Relier un paiement à sa commande : la table des identifiants
ChariPay est une plateforme de paiement marocaine, un PSP opéré par Chari Money, établissement de paiement agréé par Bank Al-Maghrib. Un paiement y traverse plusieurs systèmes — votre site, la page de paiement, nos webhooks, l'export comptable —, et chacun a son identifiant. Les confondre, c'est fabriquer des paiements « orphelins » : l'argent est là, la commande attend encore. Voici les sept à connaître.
| Identifiant | Créé par | Sert à | Revient où |
|---|---|---|---|
orderId | vous, à la création d'une session | votre référence métier, sans contrainte d'unicité | dans l'événement de paiement de la session |
externalId | vous, sur un lien, une session, un abonnement ou un produit | ne jamais créer deux fois la même ressource ; unique par compte et par environnement | dans les lectures du lien ; dans l'événement d'un abonnement ; pas dans l'événement d'une session ni d'un lien |
metadata | vous | porter vos références libres : panier, commande, canal | tel quel, sur les lectures et dans l'événement de paiement |
reference (pl_…) | ChariPay, à la création d'un lien | identifier le lien et lire son état | réponse de création ; GET /v1/payment-links/{reference} |
sessionId (ps_…) | ChariPay, à la création d'une session | identifier la session et lire son statut | réponse de création ; GET /v1/payment-sessions/{sessionId} |
operationId | ChariPay, au paiement | identifier le paiement, notamment pour le rembourser | GET /v1/transactions ; GET /v1/transactions/{operationId} |
refundReference | vous, à la demande de remboursement | rendre le remboursement rejouable sans double débit | GET /v1/refunds/{reference}, qui accepte votre refundReference |
Deux règles résument la table. La première : retrouvez votre commande par votre metadata, renvoyé tel quel dans l'événement ; pour une session, l'événement porte aussi votre orderId. La seconde : l'externalId d'une session ou d'un lien ne revient pas dans l'événement — il sert à l'anti-doublon, pas au rattachement ; seul l'abonnement fait exception. Ce que l'externalId protège, et comment, est expliqué dans l'idempotence des paiements.
La metadata de paiement : plate, légère, renvoyée telle quelle
Le champ metadata est un objet que vous joignez à la création d'un lien, d'une session, d'un abonnement ou d'un produit. Ses règles sont strictes :
- plat : des chaînes, des nombres, des booléens ; ni objet imbriqué ni tableau, sinon la création échoue en
400; - léger : 4 Ko au plus ;
- à la racine : envoyé une fois, au niveau racine de la requête, pas dans
config; - renvoyé tel quel : sur les lectures et dans l'événement de paiement, sans transformation.
| À mettre | À éviter |
|---|---|
vos références internes : cartId, orderNumber | le nom, le téléphone ou l'e-mail du client, qui ont leurs propres champs |
le canal de vente : "source": "web" ou "source": "whatsapp" | un numéro de carte, un CVV, toute donnée de carte |
| un identifiant de magasin ou de caisse | un secret, un jeton, une clé |
un indicateur métier booléen : "gift": true | un objet imbriqué ou une liste |
Pourquoi ces précautions ? Parce que votre metadata revient tel quel, dans des événements qui transitent par vos journaux, vos files de messages et parfois vos outils d'analyse. N'y mettez rien que vous ne voudriez pas lire dans un journal : des identifiants opaques, que seule votre base sait relier à une personne.
Vérifiez-le vous-même : créez dans la sandbox une session porteuse d'un metadata, payez-la avec la carte de test, et lisez l'événement qui arrive sur votre endpoint. Commencer en mode test : trois champs — nom, e-mail professionnel, entreprise —, un lien d'activation reçu par e-mail pour choisir votre mot de passe, puis le portail, où vous créez vous-même votre clé de test après une ré-authentification. Le mode test est gratuit, sans limite de durée, et la vérification de votre entreprise ne conditionne que la production.
Session de paiement : orderId et metadata dans l'événement
Une session porte vos trois identifiants dès sa création :
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" }
}'ORD-2026-0421 est votre référence de commande, order-2026-0421 votre clé anti-doublon, et cartId et source vos références libres. La réponse ne contient que cinq champs — sessionId, sessionToken, verifyKey, expiresAt, checkoutUrl — : enregistrez le sessionId en face de la commande, il vous servira à relire la session. Les champs de création sont décrits dans le module « Sessions de paiement » de la documentation.
Quand le paiement aboutit, l'événement payment.succeeded vous apporte votre metadata, tel quel, et votre orderId — ou, si vous l'avez renseignée, la valeur de config.urls.externalReference, qui en tient lieu. L'identifiant de l'événement et son type ne se lisent pas dans le corps mais dans les en-têtes Chari-Event-Id et Chari-Event-Type. Dans votre récepteur, la ligne qui touche à votre commande ressemble à ceci :
await orders.markPaid(event.metadata.cartId); // your metadata, echoed verbatimElle s'insère dans un récepteur complet — vérification de la signature sur le corps brut, dédoublonnage, accusé de réception — décrit dans réussir son intégration des webhooks. Le corps de l'événement est un JSON plat dont le site ne publie pas la liste des champs : appuyez votre rattachement sur votre metadata, et cherchez l'identifiant de l'événement dans Chari-Event-Id, jamais ailleurs.
Lien de paiement : la reference et la metadata du lien
Un lien suit la même logique, avec ses propres identifiants :
curl -X POST 'https://api-psp.charipay.ma/v1/payment-links' \
-H 'X-CHARI-PAY-API-KEY: chari_sk_test_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-1042-link' \
-d '{
"amount": 149.90,
"description": "Commande #1042",
"singleUse": true,
"externalId": "order-1042",
"customerEmail": "amine.bennani@example.com",
"customerPhone": "+212600000000",
"metadata": { "cartId": "c_987" }
}'La réponse — 201, ou 200 si vous rejouez le même externalId — vous donne la reference du lien (pl_…) et son payUrl, le lien à partager. Enregistrez la reference en face de la commande : c'est elle qui permet de lire l'état du lien par GET /v1/payment-links/{reference}.
Quand le client paie par carte, l'événement de paiement porte le metadata du lien — ici cartId —, pas son externalId. Votre rattachement repose donc sur le metadata, ou sur la reference que vous avez gardée. Pour un lien à usage unique réglé en espèces en agence, suivez l'état du lien : dès que le réseau confirme le dépôt, le lien passe « Payé » et vous êtes notifié dans le portail ; côté serveur, l'état du lien se lit par GET /v1/payment-links/{reference}. Le parcours du client est détaillé dans encaisser en espèces en agence.
Abonnements et boutique hébergée : deux exceptions à connaître
Abonnements. Pour un abonnement, votre externalId et votre metadata reviennent dans les événements subscription.payment_succeeded et subscription.payment_failed : c'est documenté, et c'est la seule exception à la règle de l'externalId. Chaque échéance se rattache donc directement à l'abonnement de votre système, sans table de correspondance. Les appels et les événements sont détaillés dans intégrer les abonnements par l'API.
Boutique hébergée. Une commande payée par carte sur la boutique hébergée s'accompagne de deux événements : order.paid, qui signale la commande payée, et le payment.succeeded compagnon, qui porte le metadata du produit. Si votre rattachement repose sur le metadata — un identifiant d'entrepôt, une référence de catalogue —, lisez-le dans payment.succeeded, pas dans order.paid. L'outil lui-même est présenté dans boutique hébergée.
Du paiement au remboursement : retrouver l'operationId
Pour rembourser, il faut désigner le paiement d'origine. Préférez son operationId, l'identifiant du paiement côté ChariPay. Il se lit dans GET /v1/transactions — liste filtrable par status, type, method, channel, from, to et search, parcourue avec le curseur limit / cursor / hasMore — ou dans GET /v1/transactions/{operationId}, qui détaille une transaction ; GET /v1/transactions/{operationId}/timeline en retrace la chronologie. Chaque mouvement porte la référence de la vente qui l'explique : identifiez le paiement de votre commande une fois, puis conservez son operationId en face d'elle pour ne plus avoir à le chercher.
À défaut d'operationId, la demande de remboursement accepte un externalId : il désigne alors la référence métier du paiement — pour une session, la valeur de votre orderId, ou celle de config.urls.externalReference si vous l'avez renseignée —, jamais l'externalId d'idempotence que vous aviez donné à la session. Le piège est là : le même nom de champ désigne deux choses différentes selon l'appel.
Votre refundReference, enfin, se dérive de la commande — refund-ORD-2026-0421-1 —, jamais d'un tirage aléatoire : c'est elle qui rend le remboursement rejouable, avec 202 à la création et 200 au rejeu, puis refund.succeeded ou refund.failed à l'issue. La procédure complète, depuis le portail ou par l'API, est dans rembourser un client.
Jusqu'à la ligne d'export CSV
Chaque mouvement du compte porte la référence de la vente, du remboursement ou du versement qui l'explique ; le portail distingue montant payé, frais et net, et l'export CSV reprend ces références — depuis le portail ou par GET /v1/transactions/export.csv (jusqu'à 10 000 lignes ; au-delà, découpez par période avec from/to). Les horodatages sont en UTC ; le Maroc est en UTC+1 : votre outil de rapprochement doit convertir.
Trois conséquences pour votre rattachement :
- un remboursement est un mouvement distinct, lié à la transaction d'origine, et il peut réduire le versement suivant sans ligne séparée sur le relevé bancaire ;
- l'export par l'API exige une clé dotée de la permission `transaction:export` : sans elle, l'appel répond
403 FORBIDDEN; - un paiement passé juste après minuit, heure du Maroc, figure à la veille dans l'export brut, tant que vous ne convertissez pas.
La méthode quotidienne — frais, décalage de versement, remboursements — appartient à rapprocher ses encaissements : ce guide s'arrête aux données qui la rendent possible. Les paramètres de la liste et de l'export sont dans le module « Transactions » de la documentation.
Cinq erreurs de rattachement à éviter
- 1Attendre l'`externalId` dans l'événement. Pour une session ou un lien, il n'y revient pas : rattachez par
metadata, et parorderIdpour une session. - 2Imbriquer un objet dans `metadata`. La création échoue en
400: gardez un objet plat, fait de chaînes, de nombres et de booléens. - 3Réutiliser un même `orderId` pour deux commandes. L'API l'accepte, puisque l'
orderIdn'a pas de contrainte d'unicité ; votre rattachement, lui, devient ambigu. - 4Rembourser avec l'`externalId` d'idempotence d'une session. Utilisez l'
operationId, ou à défaut la référence métier du paiement : votreorderId, ouconfig.urls.externalReferencesi vous l'avez renseignée. - 5Dédupliquer sur `Chari-Webhook-Id`. Dédupliquez sur
Chari-Event-Id, pas surChari-Webhook-Id— ce dernier identifie la livraison, pas l'événement.
Les erreurs 1 et 5 ont la même racine : prendre un identifiant pour un autre — la clé anti-doublon pour la référence de commande, l'identifiant de la livraison pour celui de l'événement. La différence entre éviter un doublon et retrouver une commande est développée dans l'idempotence des paiements.
Questions fréquentes
Quelle différence entre orderId et externalId ?
L'orderId est votre référence métier pour une session : il n'a pas de contrainte d'unicité et l'événement de paiement le porte. L'externalId est une clé anti-doublon, unique par compte et par environnement : une création avec un externalId existant renvoie la ressource existante en 200. Pour une session ou un lien, l'externalId ne revient pas dans l'événement.
L'externalId revient-il dans le webhook de paiement ?
Pas pour une session ni pour un lien : retrouvez votre commande par votre metadata, renvoyé tel quel dans l'événement ; pour une session, l'événement porte aussi votre orderId. La seule exception est l'abonnement : votre externalId et votre metadata reviennent dans subscription.payment_succeeded et subscription.payment_failed, ce que le contrat de l'API documente.
Que peut-on mettre dans la metadata d'un paiement ?
Un objet plat — chaînes, nombres, booléens —, de 4 Ko au plus, sans objet imbriqué ni tableau, sous peine de 400. Mettez-y des références opaques : identifiant de panier, numéro de commande, canal de vente, magasin. N'y mettez jamais de donnée de carte ni de secret, et évitez les données personnelles, puisque le contenu revient tel quel dans les événements.
Comment retrouver l'operationId d'un paiement ?
Par GET /v1/transactions, en filtrant sur la période, le statut, le type ou le moyen de paiement, et en parcourant la liste avec le curseur ; ou par GET /v1/transactions/{operationId} pour le détail. Chaque mouvement porte la référence de la vente qui l'explique : identifiez le paiement une fois, puis gardez son operationId en face de la commande.
Comment savoir quelle commande a été payée par un lien de paiement ?
Gardez la reference du lien (pl_…) en face de la commande dès sa création. L'événement de paiement porte le metadata du lien, pas son externalId, et l'état du lien se lit à tout moment par GET /v1/payment-links/{reference}. Pour un paiement en espèces en agence, le lien passe « Payé » et vous êtes notifié dans le portail.
Où lire l'identifiant d'un événement webhook ?
Dans l'en-tête Chari-Event-Id, stable sur toutes les tentatives et sur tous vos endpoints ; le type de l'événement est dans Chari-Event-Type. Ne le cherchez pas dans le corps. Chari-Webhook-Id, lui, identifie la livraison : il ne sert pas à dédupliquer. C'est sur Chari-Event-Id que votre table de dédoublonnage doit porter sa contrainte d'unicité.
Faut-il un orderId différent pour chaque session ?
Pas forcément. L'orderId n'a pas de contrainte d'unicité : une nouvelle session créée pour la même commande, après une expiration ou une annulation, peut garder le même orderId. Son externalId, en revanche, doit changer, sinon l'API renvoie l'ancienne session ; changez aussi l'Idempotency-Key, sinon l'API répond 409 IDEMPOTENCY_CONFLICT. Deux commandes différentes ne doivent jamais partager un orderId, sous peine de rattachement ambigu.
Prochaine étape
Créez dans la sandbox une session et un lien porteurs d'un metadata, payez-les avec la carte de test, puis retrouvez chaque identifiant dans l'événement, dans GET /v1/transactions et dans l'export : Commencer en mode test. La méthode comptable qui prend le relais est dans rapprocher ses encaissements, et la procédure de remboursement dans rembourser un client.
Écrit par Équipe ChariPay.