Tester un paiement en ligne en mode test : carte de test, scénarios et limites de la sandbox
Tester un paiement en ligne sans argent réel : la carte de test, le code 3-D Secure, un plan de recette par scénarios et ce que la sandbox ne couvre pas.
Pour tester un paiement en ligne sans argent réel, créez une clé chari_sk_test_…, appelez la même API qu'en production et payez avec la carte de test 4918 9141 0719 5005, le CVV 123, n'importe quelle date d'expiration future et le code 3-D Secure 555. Le parcours est complet — page de paiement hébergée, authentification, webhook signé, remboursement, export — mais aucun dirham ne circule. Ce guide donne la carte et le code, ce que la sandbox couvre module par module, un plan de recette en six scénarios à dérouler avant tout lancement, et ce qui ne se teste pas, avec la façon de le vérifier autrement.
Le mode test : la même API, une clé de test
ChariPay est une plateforme de paiement marocaine, un PSP opéré par Chari Money, établissement de paiement agréé par Bank Al-Maghrib. Son mode test n'est pas une maquette : c'est la même API, sur la même base https://api-psp.charipay.ma, avec les mêmes chemins, les mêmes payloads et les mêmes codes d'erreur qu'en production. Ce qui change, c'est la clé. Une clé qui commence par chari_sk_test_ travaille en sandbox, une clé chari_sk_live_ en production : vous ne choisissez l'environnement ni dans l'URL ni dans un en-tête, vous le choisissez en choisissant la clé. Les deux environnements sont isolés : un même externalId peut exister une fois dans chacun.
Le mode test s'ouvre dès l'activation de votre compte, sans validation à attendre : 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. La clé complète ne s'affiche qu'une seule fois et n'est jamais envoyée par e-mail : rangez-la aussitôt dans une variable d'environnement ou un coffre, jamais dans le dépôt. La vérification de votre entreprise (KYB) se fait en parallèle et ne conditionne que le passage en production.
Le mode test est gratuit, sans limite de durée et sans engagement ; la page tarification détaille ce qui ne s'applique qu'au passage en production. Pour les conventions communes à tous les appels — authentification, pagination, idempotence, erreurs —, l'espace développeurs et la documentation de l'API font référence.
La carte de test et le code 3-D Secure
Voici la carte à saisir sur la page de paiement, comme le ferait un client ; elle déclenche le parcours réel — page de paiement, authentification 3-D Secure, webhook.
| Champ | Valeur à saisir |
|---|---|
| Numéro de carte | 4918 9141 0719 5005 |
| CVV | 123 |
| Date d'expiration | n'importe quelle date future |
| Code 3-D Secure | 555 |
Seule cette carte est acceptée, et aucun argent réel ne circule. Toute autre carte — la vôtre, celle d'un collègue ou un numéro de test copié d'une autre plateforme — est rejetée avec un 502 et le code BAAS_CHARI_ERROR. Si vous voyez cette réponse pendant vos tests, vérifiez la carte saisie avant de chercher un bug dans votre code ; les autres réponses d'erreur sont expliquées une à une dans codes d'erreur de l'API de paiement.
Aucune carte « de refus » n'est publiée : la sandbox n'en accepte qu'une, celle-ci. Les échecs se provoquent donc par le parcours lui-même — abandon, expiration, requête mal formée —, comme le montre le plan de recette plus bas. L'étape que vous traversez avec le code 555 est celle que vos clients traverseront avec leur banque. Les paiements par carte passent par l'authentification 3-D Secure : le porteur confirme auprès de sa banque, ce qui protège le commerçant contre l'essentiel des impayés et des contestations. Ce que cette étape change sur vos ventes réelles est détaillé dans 3-D Secure et taux de réussite.
Ce que couvre la sandbox de paiement, module par module
Six familles se testent de bout en bout avec une clé de test. Pour chacune, voici comment la déclencher et ce qu'il faut vérifier :
| Module | Comment le déclencher | Ce que vous vérifiez |
|---|---|---|
| Liens de paiement | POST /v1/payment-links, ou depuis le portail | le payUrl s'ouvre, la carte de test passe, le lien passe « Payé » |
| Sessions de checkout | POST /v1/payment-sessions, puis redirection vers checkoutUrl | retour sur accept, événement signé, statut lu par GET /v1/payment-sessions/{sessionId} |
| Abonnements | POST /v1/clients, puis POST /v1/subscriptions en DAILY avec une startDate du jour | premier lien de paiement dans currentCharge, payé avec la carte de test, subscription.payment_succeeded, deuxième échéance le lendemain |
| Remboursements | POST /v1/refunds, ou depuis le portail | 202 à la création, 200 au rejeu, refund.succeeded ou refund.failed |
| Webhooks | endpoint déclaré avec la clé de test | événements envoyés normalement, signature vérifiée, journal des livraisons |
| Exports | portail, ou GET /v1/transactions/export.csv | références présentes, horodatages en UTC |
Cette liste est le périmètre promis de la sandbox. Les webhooks y partent comme en production, signés avec le secret de l'endpoint de sandbox : votre récepteur peut s'y tromper sans conséquence.
Tester un paiement en ligne de bout en bout, étape par étape
Commencez par le parcours d'une commande passée sur votre site : une session de paiement. Créez-la côté serveur, avec votre clé de test :
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" }
}'La réponse — 201, ou 200 si vous rejouez le même externalId — contient ces cinq champs, et rien d'autre :
{
"sessionId": "ps_5Kd0Rn",
"sessionToken": "st_…",
"verifyKey": "vk_5Kd0Rn",
"expiresAt": "2026-10-05T10:15:00Z",
"checkoutUrl": "https://…/checkout/ps_5Kd0Rn"
}Le statut et le montant ne figurent pas dans cette réponse : ils se lisent par GET /v1/payment-sessions/{sessionId}. Déroulez ensuite le parcours comme votre client le vivra :
- 1Enregistrez le `sessionId` en face de votre commande, à l'état « en attente de paiement ».
- 2Ouvrez `checkoutUrl` tel que la réponse le donne, dans un navigateur, comme le ferait l'acheteur.
- 3Payez avec la carte de test, puis saisissez 555 à l'étape 3-D Secure.
- 4Revenez sur l'URL `accept` : votre page affiche un état d'attente, pas encore une confirmation.
- 5Recevez l'événement signé
payment.succeededsur votre endpoint de sandbox, vérifiez sa signature, et confirmez la commande à ce moment-là. - 6Relisez le statut par
GET /v1/payment-sessions/{sessionId}pour vérifier que votre base et la nôtre disent la même chose.
L'ordre des étapes 4 et 5 n'est pas garanti, et c'est précisément ce que le test doit montrer : confirmez la commande sur réception du webhook, jamais sur la seule redirection — la redirection dit que l'acheteur est revenu, pas que le paiement est acquis. Retenez aussi que l'URL notification, comme tout endpoint déclaré, doit être joignable en HTTPS public : sur un poste de développement, il faut un tunnel.
Variante sans session : le lien de paiement.
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 donne la reference du lien (pl_…) et son payUrl : ouvrez le payUrl, payez avec la carte de test, puis lisez l'état du lien par GET /v1/payment-links/{reference}. Le guide API de paiement au Maroc replace ces deux appels dans l'intégration complète.
Un plan de recette en six scénarios
Un paiement réussi ne prouve pas grand-chose : ce sont les cas limites qui cassent une intégration le jour du lancement. Rejouez-les en sandbox, dans cet ordre.
| Scénario | Comment le provoquer | Résultat attendu |
|---|---|---|
| 1. Paiement réussi | session, carte de test, code 555 | retour sur accept, payment.succeeded signé, commande confirmée à la réception de l'événement |
| 2. Abandon pendant l'authentification | saisir la carte, puis fermer l'onglet à l'étape 3-D Secure | aucune confirmation, commande toujours « en attente », statut relu par GET /v1/payment-sessions/{sessionId} |
| 3. Session expirée | créer une session avec un expiresAt à quelques minutes, attendre qu'il passe | la session ne se paie plus ; en checkout direct, POST /checkout/verify répond 410 SESSION_EXPIRED ; votre commande passe à l'état prévu |
| 4. Double clic | rejouer la création avec la même Idempotency-Key, puis avec le même externalId | le premier résultat revient ; 200 au lieu de 201 sur externalId ; aucune seconde session |
| 5. Remboursements | total sur un premier paiement ; partiel avec refundAmount sur un second ; rejeu de la même refundReference | 202 à la création, 200 au rejeu, refund.succeeded, aucun double débit |
| 6. Événement de test | depuis le portail ou l'API, sur l'endpoint de sandbox | signature acceptée, réponse 2xx rapide, aucun effet métier |
Quelques règles rendent ce plan fiable. Le remboursement partiel se teste sur un autre paiement que le remboursement total, et chaque remboursement désigne son paiement par l'operationId lu dans GET /v1/transactions. Sur le scénario 4, rejouez aussi la même Idempotency-Key avec un corps modifié, y compris un nouvel externalId : la réponse attendue est 409 IDEMPOTENCY_CONFLICT, et elle doit être traitée comme une erreur de votre code, pas comme un incident réseau. Si l'externalId reste le même, c'est la session existante qui revient, en 200. Le mécanisme complet est expliqué dans l'idempotence des paiements.
N'écrivez aucun flux métier qui dépende de payment.failed : cet événement n'est envoyé que si vous l'avez demandé à la création de la session (notifyOnFailure). Un échec se constate en lisant le statut, et une commande sans confirmation reste en attente jusqu'à l'expiration de sa session. Enfin, le scénario 6 se déroule en détail dans tester ses webhooks en local : tunnel HTTPS, événement de test, journal des livraisons.
Abonnements, webhooks et exports : les vérifications oubliées
Abonnements. Un abonnement ChariPay est une suite de liens de paiement, un par échéance, envoyés par e-mail au client : il se teste en raccourcissant le calendrier. Créez le client (POST /v1/clients) puis l'abonnement (POST /v1/subscriptions) avec une startDate du jour et la périodicité DAILY : le premier lien est créé immédiatement et renvoyé dans currentCharge, et l'abonnement reste INCOMPLETE jusqu'à son paiement. Payez ce lien avec la carte de test, sur la page hébergée avec 3-D Secure : l'abonnement passe ACTIVE et vous recevez subscription.payment_succeeded. Le lendemain, une deuxième échéance s'ouvre avec un nouveau lien, à payer de la même façon. Les champs, les événements et le plan de recette complet sont détaillés dans intégrer les abonnements par l'API.
Webhooks. Déclarez un endpoint avec votre clé de test : il appartient à l'environnement de cette clé et reçoit son propre secret de signature (whsec_…), distinct de celui de production. Votre code doit lire ce secret dans la configuration de l'environnement, jamais en dur, sinon, après la bascule en production, votre vérification rejettera chaque notification.
Exports. Exportez les transactions de votre recette — depuis le portail ou par GET /v1/transactions/export.csv — et vérifiez deux choses. Chaque mouvement porte la référence de la vente, du remboursement ou du versement qui l'explique, et un remboursement apparaît comme un mouvement distinct lié à la transaction d'origine. Les horodatages sont en UTC ; le Maroc est en UTC+1 : votre outil de rapprochement doit convertir, faute de quoi un paiement passé juste après minuit tombe sur la veille.
Ce qui ne se teste pas, et comment le vérifier autrement
La sandbox ne promet pas tout. Trois choses restent hors de son périmètre :
- Un dépôt d'espèces réel en agence. Créez le lien à usage unique avec
paymentMethod: "CASH"et vérifiez que votre code sait lire son état. En production, 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 parGET /v1/payment-links/{reference}. Le dépôt lui-même ne se joue pas en sandbox. - Un virement bancaire ou un paiement de facture réels. Le parcours de virement du portail se déroule — en sandbox, n'importe quel code à six chiffres valide l'envoi —, mais aucun bénéficiaire ni aucun créancier ne reçoit quoi que ce soit.
- Une carte autre que la carte de test. Elle reçoit
502 BAAS_CHARI_ERROR. Le comportement d'une vraie carte — authentification auprès de la banque émettrice, ouverture au paiement en ligne — ne s'observe qu'en production.
Ces trois parcours se vérifient sur de vraies opérations, une fois votre compte activé : dans le portail et dans l'export. La sandbox, elle, ne disparaît pas après le lancement : elle reste votre environnement de recette. Gardez deux jeux de variables d'environnement et testez chaque évolution en sandbox avant de la livrer. Au passage en production, vous ne changez que la clé et le secret de webhook : les URL, les payloads et les codes d'erreur sont identiques. Tant que la production n'est pas activée, une clé chari_sk_live_… reçoit 403 PRODUCTION_ACCESS_NOT_ENABLED. La liste de bascule du jour J est dans passer de la sandbox à la production.
Questions fréquentes
Quelle carte de test utiliser pour tester un paiement en ligne ?
La carte 4918 9141 0719 5005, avec le CVV 123, n'importe quelle date d'expiration future et le code 3-D Secure 555. C'est la seule carte acceptée en sandbox : elle déclenche le parcours complet — page de paiement, authentification, webhook signé — sans qu'aucun argent réel ne circule. Toute autre carte est rejetée avec un 502 et le code BAAS_CHARI_ERROR.
Quel code 3-D Secure saisir en mode test ?
Saisissez 555 à l'étape d'authentification, après avoir entré la carte de test. Cette étape reproduit celle que vos clients traversent en production avec leur banque : elle vérifie que votre parcours supporte la redirection vers l'authentification puis le retour sur votre URL accept. Aucun autre code n'est documenté pour la sandbox.
Peut-on tester un paiement avec sa vraie carte dans la sandbox ?
Non. En sandbox, seule la carte de test est acceptée : une carte réelle, quelle que soit sa banque, est rejetée avec un 502 et le code BAAS_CHARI_ERROR. Le comportement d'une vraie carte — authentification auprès de la banque émettrice, ouverture au paiement en ligne — ne s'observe qu'en production, une fois votre compte activé.
Faut-il attendre la validation de son entreprise pour utiliser le mode test ?
Non. Le mode test s'ouvre dès l'activation de votre compte, sans validation à attendre : vous créez votre clé de test depuis le portail et payez avec la carte de test. La vérification de votre entreprise (KYB) — documents d'identité et registre de commerce — se fait en parallèle et ne conditionne que le passage en production. Votre équipe technique peut donc bâtir et tester l'intégration pendant l'étude du dossier.
Comment simuler un paiement non abouti en mode test ?
Aucune carte « de refus » n'est publiée : l'échec se provoque par le parcours. Fermez l'onglet à l'étape 3-D Secure pour simuler un abandon, ou créez une session avec un expiresAt proche et laissez-la expirer. Vérifiez ensuite qu'aucune commande n'est confirmée — en attente après l'abandon, à l'état prévu après l'expiration — et lisez le statut par GET /v1/payment-sessions/{sessionId}. N'appuyez aucun traitement sur payment.failed, envoyé seulement si vous l'avez demandé.
Peut-on tester un paiement en espèces en agence dans la sandbox ?
Pas le dépôt lui-même : aucun dépôt d'espèces réel ne se fait en sandbox. Vous pouvez créer le lien à usage unique avec paymentMethod: "CASH" et vérifier que votre code lit son état par GET /v1/payment-links/{reference}. En production, dès que le réseau confirme le dépôt, le lien passe « Payé » et vous êtes notifié dans le portail.
Comment tester plusieurs échéances d'abonnement ?
Créez l'abonnement avec une startDate du jour et la périodicité DAILY, avec votre clé de sandbox. Le premier lien de paiement est créé immédiatement et renvoyé dans currentCharge : payez-le avec la carte de test, code 3-D Secure 555, et vous observez subscription.payment_succeeded sur votre endpoint. Le lendemain, une deuxième échéance s'ouvre, avec un nouveau lien : en trois jours, vous avez vu trois échéances. Le plan de recette complet figure dans le guide des abonnements par l'API.
Les webhooks sont-ils envoyés en mode test ?
Oui, normalement : paiements, remboursements et échéances de test déclenchent leurs événements signés vers l'endpoint déclaré avec votre clé de test. Cet endpoint appartient à la sandbox et a son propre secret, distinct de celui de production. Pour vérifier votre récepteur sans paiement, envoyez l'événement de test : un payment.succeeded synthétique portant Test: true, qui ne correspond à aucun paiement réel.
Prochaine étape
Créez votre compte et votre clé de test — Commencer en mode test —, puis déroulez les six scénarios de ce plan sur votre propre intégration, dans l'ordre, avec la carte de test. Le récepteur d'événements se règle ensuite avec tester ses webhooks en local, et la liste de bascule viendra en dernier, avec passer de la sandbox à la production.
Écrit par Équipe ChariPay.