Comment vérifier un paiement via l’API sans webhook

Vérification des factures côté serveur via l’API : statuts, polling limité, erreurs, exécution unique de la commande et reprise après redémarrage.

Mis à jour:

Vous pouvez vérifier un paiement sans webhook depuis le serveur de votre application : lire périodiquement la facture via l’API Cryptanio, la rapprocher de la commande et exécuter celle-ci lorsque le statut actuel est paid. Cette interrogation périodique s’appelle le polling. Elle exige une file de vérifications, la gestion des erreurs et une protection contre les exécutions en double : une réponse HTTP réussie ne confirme pas à elle seule le paiement.

Cette solution convient si l’application peut exécuter des tâches en arrière-plan, mais qu’il n’est pas encore pratique de recevoir des notifications entrantes. Les changements sont détectés à la lecture suivante ; le polling ne fournit pas de notification immédiate.

Ce dont vous avez besoin

Enregistrez dans votre base le lien entre la commande et la facture : son id, le projet, votre reference, le prix convenu et le mode de fonctionnement. Conservez les identifiants sous forme de chaînes. Vérifiez la facture de cette commande précise, et non une transaction trouvée avec un montant similaire.

Préparez une clé API côté serveur autorisée à lire invoices, à accéder au service payments et au projet voulu. Une commande réelle nécessite le mode de production correspondant. Ne placez pas la clé dans le navigateur, le lien de l’acheteur ou un journal public. Un accès en lecture seule suffit à un processus distinct de rapprochement.

Étape 1. Récupérez l’état actuel

L’exemple lit une seule facture. Définissez CRYPTANIO_API_KEY et CRYPTANIO_RESOURCE_ID sur le serveur ; la seconde valeur est l’id de facture enregistré, et non le numéro de commande ni le TXID.

#!/usr/bin/env bash
set -euo pipefail
: "${CRYPTANIO_RESOURCE_ID:?Set CRYPTANIO_RESOURCE_ID}"
: "${CRYPTANIO_API_KEY:?Set CRYPTANIO_API_KEY}"
urlencode() {
  local LC_ALL=C text="$1" char index
  for ((index=0; index<${#text}; index++)); do
    char="${text:index:1}"
    case "$char" in [a-zA-Z0-9.~_-]) printf '%s' "$char" ;; *) printf '%%%02X' "'$char" ;; esac
  done
}
response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
transfer_status=0
http_status="$(curl \
  --silent --show-error --max-time 30 --request GET \
  --header "Authorization: Bearer $CRYPTANIO_API_KEY" \
  --header 'Accept: application/json' \
  "https://api.cryptanio.com/v1/invoices/$(urlencode "$CRYPTANIO_RESOURCE_ID")" \
  --output "$response_file" --write-out '%{http_code}')" || transfer_status=$?
if [[ "$http_status" == 2?? ]]; then
  cat "$response_file"
  exit "$transfer_status"
fi
printf 'HTTP %s\n' "$http_status" >&2
cat "$response_file" >&2
exit 1

Télécharger l’exemple

Après HTTP 200, analysez le JSON et comparez id, project_id, reference et sandbox à vos données. Une commande réelle doit avoir sandbox=false. Si vous avez enregistré un prix dans invoice_currency et invoice_amount, vérifiez également ces champs. Une facture étrangère à la commande ou une entrée d’un autre mode ne doivent pas modifier celle-ci.

Le lien checkout_url de GET sert à consulter. Ne remplacez pas par ce lien le lien interactif enregistré lors de la création de la facture : lire le statut ne crée pas de nouveau paiement et ne fournit pas de nouveau checkout interactif.

Étape 2. Distinguez attente et paiement réussi

Prenez votre décision selon le status de la facture :

  • pending — le paiement est attendu ; seen — un transfert a été détecté, mais le paiement n’est pas encore terminé ;
  • partial — une partie du montant est confirmée et l’attente continue selon les règles du paiement ;
  • underpaid, overpaid, late — un examen est nécessaire ; tenez compte de needs_decision et de la décision du vendeur ;
  • paid — la facture est comptabilisée comme payée ; après vérification du lien avec la commande, vous pouvez préparer son exécution ;
  • expired, canceled — mettez fin à l’attente active de l’acheteur, mais conservez l’entrée pour un rapprochement ultérieur.

L’expiration n’exclut pas une réception tardive. Une décision manuelle et une annulation de transaction liée à un rollback peuvent aussi changer le traitement ultérieur. Ne déclarez pas un état définitivement final et ne supprimez pas le lien entre la commande et la facture à la fermeture de la page.

Une transaction confirmée et une facture payée sont deux choses différentes. Les confirmations ne prouvent pas que l’actif, le montant et les coordonnées requis correspondent à cette commande précise. Le guide de diagnostic d’une commande impayée explique ces différences en détail.

Étape 3. Tenez compte du montant et de la tentative de paiement

Un changement de moyen de paiement peut créer plusieurs tentatives pour une facture. Pour une facture payée, tenez compte de settled_attempt_id et de settled_attempt. Lors de l’examen de l’historique, vérifiez attempt_history.has_more et le curseur de continuation ; utilisez payment_history pour les transferts de la tentative sélectionnée. La première page ne contient pas nécessairement tout l’historique.

amount est le montant exact en cryptomonnaie à envoyer. amount_raw et paid sont des chaînes contenant des entiers dans les plus petites unités de l’actif ; decimals définit l’échelle. Ne les comparez pas comme des nombres à virgule flottante ni dans l’ordre alphabétique de simples chaînes. Votre propre contrôle du montant ne doit pas remplacer le statut de la facture et les règles du projet : un trop-perçu peut être accepté ou soumis à examen. Additionner des paiements insuffisants sans vérifier les conditions est également à proscrire.

Étape 4. Limitez le polling et les nouvelles tentatives

Placez les vérifications dans une file côté serveur à concurrence limitée. Plusieurs onglets de l’acheteur ne doivent pas créer de boucles d’interrogation indépendantes. Tenez compte du trafic total du compte : les différentes clés API et les différents modes partagent le quota. Les limites actuelles figurent dans la documentation API.

Choisissez les délais selon votre processus en gardant de la capacité pour les autres requêtes. En cas d’erreur réseau ou de HTTP 500/503, limitez les nouvelles tentatives, avec un délai croissant et une variation aléatoire. Pour HTTP 429, réduisez le trafic total ; si Retry-After est présent, respectez la pause indiquée. L’absence de cet en-tête ne doit pas entraîner une nouvelle tentative immédiate. Chaque requête doit avoir un délai d’expiration et chaque série de tentatives une limite, après laquelle la tâche reste disponible pour un examen ultérieur.

HTTP 401/403 imposent de vérifier la clé et les autorisations. Pour 404, vérifiez l’ID, le projet et le mode : les restrictions d’accès peuvent masquer une facture. Ces erreurs ne signifient pas que le client n’a rien payé.

Étape 5. Rendez l’exécution et la reprise persistantes

Lorsque le statut est paid, enregistrez de manière cohérente l’état de la commande et la tâche d’exécution. Utilisez une clé de commande unique et une protection contre le traitement simultané. Relire paid ne doit pas livrer l’accès ou le produit une seconde fois ; une panne après la livraison ne doit pas créer une deuxième exécution.

Conservez la dernière vérification réussie et la tâche suivante dans un stockage durable. Après un redémarrage, reprenez les vérifications et tâches inachevées au lieu de lancer le processus uniquement à l’ouverture de la page. Limitez l’attente active selon les délais de votre processus ; un rapprochement distinct en arrière-plan doit tenir compte des paiements tardifs, des décisions et des rollbacks, y compris pour les commandes déjà payées. Avant une exécution encore en attente, revérifiez qu’elle est autorisée ; prévoyez un réexamen des accès déjà accordés.

Sur des réponses préparées, vérifiez la répétition de paid, un délai d’expiration, un redémarrage et une transition après rollback. Le résultat est correct quand la commande est exécutée une seule fois et qu’un paiement incertain ou litigieux reste à examiner. Vous pourrez ensuite compléter le polling par un webhook, en conservant la même protection de l’exécution.