Quand un client envoie un montant hors tolérance — trop peu ou trop — le paiement devient irrégulier. La part hors plage n’est ni acquise ni rendue : elle attend un arbitrage.
L’arbitrage se fait depuis le tableau de bord, pas par API. Encaisser ou rembourser engage une revue humaine : la décision n’est pas automatisable. Votre intégration détecte le cas et réconcilie le résultat ; la décision, elle, se prend dans Litiges.

Le piège à connaître d’abord

Un sous-paiement hors tolérance produit un intent au statut expired — alors que de l’argent a bien été reçu.Si votre intégration écoute seulement payment_intent.completed et traite expired comme un abandon, vous ignorerez de l’argent réellement encaissable. Le statut ne suffit pas : lisez irregularStatus.
paymentResult précise la nature : underpaid_accepted, underpaid_rejected, overpaid_accepted, overpaid_rejected. Les variantes _accepted sont dans la tolérance — elles se règlent seules, sans arbitrage.

1. Détecter — webhook

Abonnez-vous à dispute.created :
  • amountRejected — la part brute hors tolérance.
  • amountToRefund — ce qui serait effectivement remboursé, frais déduits.
Ajoutez aussi une garde sur payment_intent.expired : testez irregularStatus avant de conclure à un abandon.

2. Consulter — API

Les champs d’irrégularité sont exposés sur le payment intent. Lister ceux en écart :
Lire un cas précis :
La réponse porte irregularStatus, paymentResult, amountRejected et amountToRefund — de quoi afficher l’état dans votre back-office et suivre ce qui reste à arbitrer. Scope requis : payments:read.

3. Arbitrer — tableau de bord

Encaisser ou rembourser se fait dans Litiges. Pour un remboursement, l’adresse du client est reprise de celle qu’il a laissée sur la page de paiement ; elle peut être corrigée au moment de la décision.

Sous-paiement : laisser le client compléter

Sur un sous-paiement, une troisième issue est proposée : réactiver le paiement. Plutôt que de clore la transaction, elle la remet en cours avec un nouveau délai, sur la même adresse de dépôt — le client n’a qu’à envoyer le complément. Le paiement suit alors son cours normal : payment_intent.completed s’il est soldé, nouveau litige à l’échéance sinon. La réactivation est refusée si cette adresse de dépôt a entre-temps été attribuée à une autre transaction ; il faut alors attendre que celle-ci se termine.

4. Réconcilier — webhook

Le montant crédité est data.object.amountEncashed, net de frais — pas amountRejected, qui est le brut.
Après un encaissement, l’intent reste expired et aucun payment_intent.completed n’est émis. C’est volontaire : le paiement n’a jamais atteint le montant demandé. Votre comptabilité doit se fonder sur dispute.encashed, sinon cet argent restera invisible dans votre système alors qu’il est bien sur votre solde.
Pour un remboursement, payoutId permet de suivre le transfert on-chain via payout.confirmed ou GET /v1/payouts/{id}.

Récapitulatif

  1. Détecter — dispute.created, et garde sur irregularStatus pour tout payment_intent.expired.
  2. Consulter — GET /v1/payment-intents?status=irregular.
  3. Arbitrer — dans le tableau de bord : encaisser, rembourser, ou (sous-paiement) réactiver pour laisser le client compléter.
  4. Réconcilier — sur dispute.encashed / dispute.refunded, jamais sur le statut de l’intent. Sur dispute.reactivated, rien à réconcilier : gardez la commande ouverte et attendez l’issue normale du paiement.

Voir aussi

Litiges au tableau de bord

Où se prend la décision.

Événements webhook

Le format exact de dispute.*.