- Français
- English
Contrat Billing — SDK 1.3.0 — cycle de vie, rejeu et pagination
Billing facture vos clients ; il ne conditionne pas votre accès marchand à SahelPay. Tous les SDK 1.3.0 utilisent/v1/billing/*. Orange Money Mali/XOF est le seul moyen
intégré, chaque règlement exige une action du client : aucun prélèvement automatique.Un plan définit un montant et une fréquence WEEKLY, MONTHLY, QUARTERLY ou YEARLY.
Un abonnement porte une identité client et des périodes ISO UTC. Une facture porte
le montant à régler et son lien de paiement. Utilisez une clé de la même application
et du même mode pour toutes les opérations d’un cycle.Identité sans téléphone
La création accepte au moins une identité :customer_id, customer_external_ref,
customer_email ou customer_phone. customer_name est facultatif. Le téléphone
peut être absent ou null dans les réponses ; ne le remplacez jamais par un numéro
inventé. Les profils sont créés par les parcours d’abonnement/portail ; l’API clients
expose la liste, sans CRUD direct. external_ref est unique dans l’application.customer_external_ref identifie votre utilisateur ; external_ref identifie
l’opération d’abonnement. Avec external_ref sur with-payment, fournissez
customer_external_ref et omettez customer_id et customer_phone. L’import suit
la même règle et exige une clé API d’application. Les profils clients restent
partagés entre TEST et LIVE : utilisez des références de test distinctes.Parcours en mode TEST
Utilisez le SDK 1.3.0. Vérifieznpm view @sahelpay/sdk version : si le registre
propose 1.3.0, installez npm install @sahelpay/sdk@^1.3.0. Si npm est encore à
1.1.0, installez les sources taguées en attendant la publication :SAHELPAY_SECRET_KEY. Les exemples ci-dessous ne contiennent aucune vraie clé,
aucun téléphone et n’envoient aucun message au client.trial_end (date ISO UTC future) à createWithPayment.
La réponse est ACTIVE, avec invoice: null et payment_link: null. La première
facture sera préparée à la fin de l’essai par le worker horaire. create() accepte
start_date et trial_end, mais ne produit pas de lien immédiat.Pour vérifier un paiement sans opérateur ni personne réelle, utilisez le
simulateur SahelPay avec une clé TEST, mock: true, montant
4000, hosted_checkout: false. Un paiement simulé indépendant ne règle pas
à lui seul votre facture Billing : ne déduisez jamais les droits d’un simple
payment.success. Les fixtures du dépôt rejouent hors réseau essais, factures,
signatures, doublons et réponses de reprise. Aucun test clock public n’est promis.
webhook.test vérifie la connectivité, pas le cycle Billing.Périodes payées, essais et impayés (backend #214)
Relisez le GET canonique avant d’accorder les droits : ses états sont calculés à l’heure de lecture, même lorsque le passage horaire a du retard.La réponse historique de création d’essai reste
ACTIVE avec trial_end ; son
GET canonique renvoie TRIALING. La création sans essai et sans paiement est
INCOMPLETE. Un paiement partiel ne prolonge pas les droits. Reçu, facture PAID
et période sont écrits atomiquement ; le rejeu n’accorde pas de seconde période.Le premier paiement complet et le paiement après la grâce accordent un intervalle
entier depuis le règlement, sans arriérés. Dans la grâce, le renouvellement garde
l’échéance et l’ancre UTC : 31 janvier → 28/29 février → 31 mars. Même règle pour
le trimestre et l’année. L’import accepte une ancre historique billing_anchor_at
antérieure ou égale au début de période ; les deux bornes importées restent exactes.status reste PAST_DUE après la grâce pour permettre le règlement de la même
facture ; utilisez access_status et les dates pour les droits. Le passage du temps
ne change pas updated_at : planifiez une relecture à la fin de période/grâce,
en plus des signaux webhook. Les champs ajoutés sont facultatifs dans les types
SDK pour permettre la lecture d’une ancienne version du backend.Reprise, idempotence et pagination
Les six créations Billing (plans.create, subscriptions.create, checkout, import,
plans.sendLink, invoices.create) acceptent idempotency_key : en-tête
X-Idempotency-Key de 1 à 128 caractères, jamais dans le JSON. Le backend réserve
l’identité avant la création et conserve la clé sans expiration. Même portée
marchand/application/mode/route et même JSON (ordre des propriétés indifférent) :
même ressource. Corps différent : 409 IDEMPOTENCY_KEY_REUSED ; clé invalide :
400 INVALID_IDEMPOTENCY_KEY. Gardez la clé stable lors des reprises réseau.
Le rejeu renvoie l’état actuel, pas une copie de la première réponse.Sans en-tête, les garanties précédentes subsistent : external_ref déduplique
checkout/import, les autres créations ne sont pas dédupliquées. Une référence de
checkout exige customer_external_ref, sans ID/téléphone. Après règlement ou
annulation de la facture, checkout et plans.sendLink peuvent renvoyer un lien
null ; sendLink ajoute alors invoice_status. Aucun nouvel encaissement n’est
créé. Renouvelez via invoice_id/payment_url du GET canonique.subscriptions.import() (Python : import_period, PHP : importPeriod) reprend
une période courante avec plan_id, external_ref, customer_external_ref,
current_period_start, current_period_end. trial_end, si fourni, doit être égal
à la fin de période. Aucun paiement, facture ou message n’est créé par l’import.Toutes les listes acceptent
take entre 1 et 200 (défaut 50, clients 20), et un
cursor opaque. La pagination contient total, take, next_cursor nullable et
has_more. Réutilisez le curseur avec les mêmes filtres, application et mode ;
total compte tous les résultats filtrés avant le curseur. L’ordre est
created_at DESC, id DESC. La page suivante n’insère pas les objets plus récents ;
un curseur reste utilisable après suppression de sa ligne.plans.list_page() pour les métadonnées ; plans.list() garde sa
liste. Les nouveaux champs de pagination sont facultatifs pour lire les anciens
serveurs. En PHP, les factures sont dans
getData() et la pagination dans getRawData()['pagination']. Les noms de méthodes
Python utilisent snake_case, PHP utilise all() pour les listes.Webhooks : vérifier, dédupliquer, relire
X-SahelPay-Signature: t=TIMESTAMP,v1=HMAC_HEX, HMAC-SHA256 sur
timestamp + '.' + corps_brut, tolérance par défaut 300 secondes. Les trois SDK
vérifient la signature et rejettent les corps modifiés ou trop anciens. Les événements
sont des signaux de relecture, pas une preuve suffisante de paiement. Dédupliquez
sur event.id, supportez le désordre et ignorez les événements inconnus.cancel() émet subscription.cancelled avec un event.id stable. Si votre
endpoint filtre les événements, abonnez-le explicitement à ce type, puis relisez
le GET canonique. La relecture après l’appel et le polling restent disponibles.
Les relances e-mail restent pilotées par le backend ; le SDK n’active aucun envoi.Prévisualiser une relance, sans envoi
client.subscriptions.preview_reminder(application_id, subscription_id) ;
PHP $sahelpay->subscriptions->previewReminder($applicationId, $subscriptionId).
Référence de la preview.La lecture vérifie marchand, application et mode. Elle exige une clé déjà
enregistrée : sinon 401, sans import/migration, création d’application ni mise
à jour de la clé. Les tests utilisent une clé TEST existante. La preview ne crée
aucun lien, contact, consentement ou événement et n’envoie rien.Le responsable est configuré séparément par l’opérateur dans
subscription_reminders_application:<applicationId> : owner et éventuellement
renewal_url. Une configuration absente/invalide/inactive ou owner: application
ne déclenche aucun rappel SahelPay. Il n’existe pas de setter SDK pour l’activer.Le CTA utilise seulement une facture collectable et un lien existant sûrs dans la
même portée. Sans facture, seule l’URL de renouvellement explicitement configurée,
HTTPS et de même origine que le site de l’application, peut servir de repli. Une
dette historique au mode indéterminé ou un SUCCESS sans reçu bloque ce repli et le
rendu éligible : ne construisez pas de lien de remplacement. Les flags restent
desactive et aucun transport WhatsApp n’est disponible.Erreurs
Les messages exacts peuvent varier selon la validation. Voir les codes d’erreur
et les types et versions SDK.