Aller au contenu principal
Tous les articles
Technique13 min de lecture

Session de paiement expirée, page fermée, retour : le cycle de vie complet d'une session

Session de paiement expirée, client qui ferme la page, URL de retour : chronologie d'une session, 72 heures par défaut, annulation et codes 404 à 410.

Une session de paiement expirée ne se paie plus : par défaut, une session expire 72 heures après sa création, ou à la date que vous indiquez dans expiresAt, et une nouvelle session doit alors être créée. Elle ne se consomme qu'une fois, s'annule avant paiement, et le client qui ferme la page ne la clôt pas : rien n'est confirmé sans webhook, et la session reste valide jusqu'à son expiration. Ce guide suit une session de la création au webhook, puis traite l'expiration, la page fermée, les pages de retour, l'annulation, la lecture du statut, les codes 404, 409 et 410, et la réconciliation après une panne.

La chronologie d'une session, de la création au webhook

ChariPay est une plateforme de paiement marocaine, un PSP opéré par Chari Money, établissement de paiement agréé par Bank Al-Maghrib. Sur un site marchand, une commande devient une session : vous créez la session côté serveur, vous redirigez l'acheteur vers le checkout hébergé — carte avec 3-D Secure —, et vous recevez le résultat par webhook signé. Pas à pas :

  1. 1Création, côté serveur. Votre back-office appelle POST /v1/payment-sessions avec le montant, votre orderId, le client et vos URL, puis enregistre le sessionId renvoyé en face de la commande, à l'état « en attente de paiement ».
  2. 2Redirection. Vous envoyez l'acheteur vers checkoutUrl, tel que la réponse le donne, en pleine page, jamais dans une iframe.
  3. 3Paiement. L'acheteur saisit sa carte sur la page hébergée et s'authentifie auprès de sa banque par 3-D Secure.
  4. 4Retour. Le navigateur revient sur votre URL accept après un paiement réussi, sur decline après un échec.
  5. 5Webhook. Votre serveur reçoit l'événement signé — payment.succeeded pour un paiement réussi — et c'est lui qui fait foi : vous confirmez la commande à ce moment-là.

L'appel de création, avec vos URL de retour :

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: 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 le même externalId a déjà servi — contient ces cinq champs, et rien d'autre :

json
{
  "sessionId": "ps_5Kd0Rn",
  "sessionToken": "st_…",
  "verifyKey": "vk_5Kd0Rn",
  "expiresAt": "2026-10-05T10:15:00Z",
  "checkoutUrl": "https://…/checkout/ps_5Kd0Rn"
}

Le statut et le montant se lisent ensuite par GET /v1/payment-sessions/{sessionId}. La verifyKey ne vous concerne qu'en checkout direct : avec la page hébergée, checkoutUrl suffit. Les quatre endpoints de la session sont décrits dans le module « Sessions de paiement » de la documentation. Retenez enfin la règle qui gouverne tout le reste : 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. Le récepteur lui-même se code avec réussir son intégration des webhooks.

Chacune de ces étapes se voit de vos yeux dans la sandbox : créez une session, payez-la, puis laissez-en une autre expirer. 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, avant de payer avec la carte de test 4918 9141 0719 5005 et le code 3-D Secure 555. Gratuit, sans limite de durée, sans attendre la vérification de votre entreprise.

Session de paiement expirée : 72 heures par défaut, expiresAt pour raccourcir

Chaque session a une durée de vie limitée — 72 heures par défaut, expiresAt pour la raccourcir — et n'est consommée qu'une seule fois.

ParamètreValeur par défautCe qu'il règle
expiresAt72 heures après la créationle moment au-delà duquel la session ne se paie plus ; ISO-8601 UTC, dans le futur, sinon 400
singleUsetrueune session sert à un seul paiement

expiresAt s'exprime en UTC ; le Maroc étant en UTC+1, convertissez l'heure locale avant de l'envoyer. Raccourcir la durée a du sens quand la commande bloque quelque chose — un article en stock limité, une place, un créneau : la session expire en même temps que le blocage, et votre système libère la ressource. Le montage métier de ce cas, avec la politique d'annulation, est traité dans faire payer une réservation en ligne. Ce guide suppose la session déjà choisie ; si vous hésitez encore avec le lien de paiement, la comparaison est dans lien de paiement ou checkout.

Une session expirée ne se paie plus : en checkout direct, POST /checkout/verify et POST /checkout/submit répondent 410 SESSION_EXPIRED. Aucun des vingt événements documentés ne porte l'expiration d'une session : c'est votre système qui compare expiresAt à l'heure courante, ou relit le statut, pour passer la commande à l'état prévu — « expirée », « à relancer », ou libérée.

Le client ferme la page de paiement : ce qui se passe

Un client qui ferme l'onglet — avant de saisir sa carte, pendant l'authentification ou juste après — ne déclenche rien de définitif de votre côté :

  • Rien ne se confirme sans webhook. Tant que l'événement signé n'est pas arrivé, la commande reste « en attente de paiement ».
  • La session reste valide jusqu'à son expiration, ou jusqu'à ce que vous l'annuliez.
  • Si le paiement a abouti avant la fermeture, payment.succeeded arrive quand même : le client ne verra jamais votre page accept, mais l'argent est bien là.
  • Pour savoir où en est la session, lisez son statut par GET /v1/payment-sessions/{sessionId}.
  • `payment.failed` n'arrive que si vous l'avez demandé à la création de la session (notifyOnFailure) : ne bâtissez aucun flux sur lui.

Que faire de la commande ? Rien d'urgent. Laissez-la en attente jusqu'à l'expiration de la session. Si le client revient vers vous pour payer — par téléphone, par message —, annulez d'abord l'ancienne session, puis créez-en une nouvelle : deux sessions valides pour une même commande, ce sont deux paiements possibles. C'est la précaution à ne pas oublier.

Pages de retour accept et decline

URLRôleSi vous ne la précisez pas
config.urls.acceptpage où revient l'acheteur après un paiement réussila valeur par défaut de votre compte, puis celle de la plateforme
config.urls.declinepage où revient l'acheteur après un échecla valeur par défaut de votre compte, puis celle de la plateforme
config.urls.notificationURL qui reçoit les webhooks signés de cette sessionla valeur par défaut de votre compte seulement, sans repli plateforme

Toutes doivent être en https:// publique : une URL en http:// ou privée vaut un 400 dès la création.

La page accept n'est pas une page de succès. Elle affiche « nous vérifions votre paiement » jusqu'à la réception du webhook, puis bascule sur la confirmation. Concrètement, elle interroge votre propre serveur — qui sait si l'événement est arrivé — et jamais l'API de paiement directement : la clé d'API ne quitte pas votre serveur. Si l'événement tarde, votre serveur peut relire le statut de la session ; le client, lui, voit un message honnête plutôt qu'une confirmation prématurée.

La page decline explique, sans jargon, que le paiement n'a pas abouti, et propose de recommencer la commande — avec une nouvelle session. N'y promettez pas de cause précise : l'échec peut venir d'une authentification abandonnée, d'un refus de la banque émettrice ou de données erronées, et beaucoup de cartes marocaines sont, par défaut, fermées au paiement en ligne. Une phrase qui invite à vérifier la carte auprès de sa banque rend plus service qu'un code technique.

Annuler une session avant le paiement

POST /v1/payment-sessions/{sessionId}/cancel annule la session : elle ne peut plus être payée, et la réponse renvoie son statut mis à jour. Trois situations l'appellent :

  • le panier a changé — un article retiré, une quantité modifiée — et le montant n'est plus le bon ;
  • le stock est épuisé avant le paiement ;
  • la commande est retirée par le vendeur, ou remplacée par une autre.

Annulez toujours l'ancienne session avant d'en créer une nouvelle pour la même commande. Créez cette nouvelle session avec un nouvel externalId : une création avec un externalId déjà utilisé renvoie la session existante en 200, pas une nouvelle. Changez aussi la valeur de l'en-tête Idempotency-Key si vous en envoyez un : la même clé avec un corps différent répond 409 IDEMPOTENCY_CONFLICT. L'orderId, lui, peut rester le même, puisqu'il n'a pas de contrainte d'unicité. Le détail de ces mécanismes est dans l'idempotence des paiements. Une fois le paiement réussi, ce n'est plus d'annulation qu'il s'agit. Annuler une session déjà payée répond 409 SESSION_ALREADY_CONSUMED : passez par un remboursement.

Lire le statut d'une session

GET /v1/payment-sessions/{sessionId} renvoie l'état courant d'une session : son statut, son montant et son expiration. GET /v1/payment-sessions liste les sessions de l'environnement de votre clé, des plus récentes aux plus anciennes, avec la pagination commune aux listes — page à partir de zéro et size, à parcourir jusqu'à totalPages — et un filtre d'origine, origin=API ou origin=DASHBOARD.

Le statut se lit, il ne se devine pas. N'en codez pas la liste en dur, et tolérez les valeurs nouvelles : c'est ainsi que l'API évolue sans casser votre intégration. Surtout, le sondage est un repli, pas la source de vérité. Interrogez le statut à trois moments utiles — quand le client revient sur accept avant l'événement, à l'approche de expiresAt pour une commande toujours en attente, et pendant une réconciliation —, pas toutes les secondes en attendant le client : des appels trop serrés finissent en 429 RATE_LIMITED.

Les codes 404, 409 et 410 d'une session

Quatre codes accompagnent la vie d'une session. Ils répondent notamment aux appels qui ouvrent et paient la session — ceux de la page hébergée ou, en checkout direct, ceux de votre serveur :

CodeMoment du cycle de vieQue faire
404 SESSION_NOT_FOUNDla session est inconnuevérifier le sessionId envoyé
409 SESSION_ALREADY_CONSUMEDla session a déjà été payéene plus la soumettre ; pour un nouveau paiement, créer une nouvelle session, et pour rendre l'argent, rembourser
409 SESSION_NOT_ACTIVEla session a été annuléeen créer une nouvelle si la commande doit encore être payée
410 SESSION_EXPIREDla session a dépassé son expiresAtcréer une nouvelle session, avec un nouvel externalId et une nouvelle Idempotency-Key

Une clé de vérification vk invalide ou déjà consommée, elle, répond 401 INVALID_TOKEN, pas un 409. Aucun de ces codes ne justifie une nouvelle tentative à l'identique. La liste complète, avec les codes qui ne concernent pas les sessions, est dans codes d'erreur de l'API de paiement.

Après une panne : réconcilier par GET /v1/transactions

Les livraisons de webhooks font jusqu'à 16 tentatives sur environ 72 heures. Si votre serveur est resté indisponible plus longtemps, n'attendez plus l'événement : réconciliez par GET /v1/transactions.

  1. 1Bornez la période de la panne avec from et to.
  2. 2Parcourez les transactions avec la pagination par curseur : limit, puis cursor = nextCursor tant que hasMore est vrai. Elle ne saute ni ne répète aucune ligne quand de nouvelles transactions arrivent pendant le parcours.
  3. 3Relisez chaque session dont la commande est restée « en attente de paiement » sur la période, par GET /v1/payment-sessions/{sessionId} — d'où l'intérêt d'avoir enregistré le sessionId en face de la commande dès la création.
  4. 4Confirmez les commandes payées, et libérez celles dont la session a expiré.
  5. 5Si vous rejouez ensuite des livraisons échouées depuis le portail, assurez-vous que votre traitement reconnaît une commande déjà confirmée et ne la confirme pas deux fois.

Le rattachement d'un paiement à sa commande — metadata, orderId, sessionId, operationId — est détaillé dans relier un paiement à sa commande.

Questions fréquentes

Combien de temps dure une session de paiement ?

72 heures après sa création par défaut. Vous pouvez la raccourcir avec expiresAt, une date ISO-8601 en UTC située dans le futur. Une session ne se consomme qu'une fois, et une session expirée ne se paie plus : pour encaisser la même commande après l'expiration, créez une nouvelle session avec un nouvel externalId et une nouvelle Idempotency-Key.

Que se passe-t-il si le client ferme la page de paiement ?

Rien de définitif. Tant que l'événement signé n'est pas arrivé, la commande reste en attente, et la session reste valide jusqu'à son expiration ou son annulation. Si le paiement avait abouti avant la fermeture, payment.succeeded arrive quand même. Pour savoir où en est la session, lisez son statut par GET /v1/payment-sessions/{sessionId}.

Que faut-il afficher sur la page de retour après le paiement ?

Sur la page accept, un message du type « nous vérifions votre paiement », jusqu'à ce que votre serveur ait reçu le webhook ; la confirmation vient ensuite. La page interroge votre serveur, jamais l'API directement. Sur la page decline, une explication sans jargon et la possibilité de recommencer la commande, avec une nouvelle session.

Comment annuler une session de paiement ?

Par POST /v1/payment-sessions/{sessionId}/cancel, avant le paiement : la session est annulée et ne peut plus être payée, et la réponse renvoie son statut mis à jour. Annulez toujours l'ancienne session avant d'en créer une nouvelle pour la même commande, et donnez à la nouvelle un nouvel externalId et une nouvelle Idempotency-Key.

Que signifie l'erreur 410 SESSION_EXPIRED ?

Que la session a dépassé son expiresAt — 72 heures après sa création par défaut — et ne peut plus être payée. Ne rejouez pas l'appel : créez une nouvelle session, avec un nouvel externalId et une nouvelle Idempotency-Key, et redirigez l'acheteur vers son checkoutUrl. L'orderId peut rester le même, puisqu'il n'a pas de contrainte d'unicité.

Peut-on réutiliser une session de paiement déjà payée ?

Non. Une session ne se consomme qu'une fois — singleUse vaut true par défaut —, et un nouvel essai de paiement sur une session déjà payée reçoit 409 SESSION_ALREADY_CONSUMED. Pour un nouveau paiement, créez une nouvelle session. Pour rendre de l'argent sur un paiement réussi, il s'agit d'un remboursement, pas d'une session.

Que se passe-t-il si l'on ne précise pas d'URL de retour ?

Pour accept et decline, la valeur par défaut configurée sur votre compte s'applique, puis celle de la plateforme. Pour notification, seule la valeur par défaut de votre compte s'applique, sans repli sur la plateforme ; vos endpoints déclarés reçoivent, eux, les événements auxquels ils sont abonnés. Toute URL précisée doit être en https:// publique, sous peine de 400.

Comment savoir si une session a été payée quand le webhook n'arrive pas ?

Lisez son statut par GET /v1/payment-sessions/{sessionId}, et consultez le journal des livraisons : il dit si l'événement est parti et ce que votre serveur a répondu. Les livraisons font jusqu'à 16 tentatives sur environ 72 heures ; après une panne plus longue, réconciliez par GET /v1/transactions.

Prochaine étape

Rejouez dans la sandbox les trois issues d'une session — paiement réussi, page fermée, expiration courte — avant de câbler vos pages de retour : Commencer en mode test. Codez ensuite le récepteur avec réussir son intégration des webhooks, et rattachez chaque paiement à sa commande avec relier un paiement à sa commande.

Écrit par Équipe ChariPay.

À lire ensuite

Guide6 min de lecture

Lien de paiement ou checkout : lequel choisir

Lien de paiement ou checkout : deux façons d'encaisser en ligne, deux charges de travail, et les critères qui tranchent — volume, montant, équipe.

Technique5 min de lecture

Réussir son intégration des webhooks

Réussir son intégration des webhooks : les cinq erreurs qui reviennent dans presque toutes les intégrations, et le traitement en quinze lignes qui les évite.

Prêt à essayer par vous-même ?

Créez votre compte et encaissez en mode test dès aujourd'hui : c'est gratuit, sans validation à attendre. Une question ? Notre équipe répond aux commerçants comme aux développeurs.

  • Gratuit, sans engagement
  • Mode test dès l'inscription
  • Aucune validation à attendre