Valsoria Pay Console →
Documentation

Démarrer

IntroductionDémarrageAuthentification

API

PaiementsRemboursementsWebhooksRéférenceErreurs

Outils

Numéros de testSDK JavaScriptVersions

Encaisser son premier paiement

Ce guide part de zéro et s'arrête quand vous avez encaissé, puis reçu la confirmation. Comptez une vingtaine de minutes.

Où en est l'API. Encaissement, lecture et remboursement fonctionnent. Décaissements, lots, bénéficiaires et règlements n'existent pas encore : ne codez pas contre eux. La spécification référence de l'API ne décrit que ce qui répond aujourd'hui.

1. Vos clés

Dans la console marchand, onglet Compte. Une clé secrète vp_sk_test_… s'affiche une seule fois : elle n'est stockée que hachée chez nous, et nous ne pourrons pas vous la redonner.

La clé détermine l'environnement. Il n'y a aucun paramètre de mode : vp_sk_test_… ne touche jamais d'argent réel, vp_sk_live_… ne touche jamais de données de test, et rien ne traverse la frontière. C'est volontaire — le paramètre de mode est la façon la plus courante de débiter un vrai client en croyant tester.

export VALSORIA_CLE="vp_sk_test_…"
export VALSORIA_BASE="https://qkmvexkljdcfarulydao.supabase.co/functions/v1"

2. Trois conventions à connaître avant d'écrire une ligne

Les montants sont des entiers, en unité mineure. Le franc CFA n'a pas de subdivision : 10000 vaut 10 000 F. Un montant en chaîne ("10000") est refusé en invalid_amount, et c'est délibéré — deviner l'intention derrière un montant est le début des ennuis.

Idempotency-Key est obligatoire sur tout appel qui déplace de l'argent. Rejouez la même clé avec le même corps : vous recevez la réponse d'origine, statut compris, et aucun second mouvement n'a lieu. La clé doit venir de votre commande (cmd-2026-00412), pas d'un uuid() régénéré à chaque tentative — sinon vous générez une clé neuve par tentative et l'idempotence ne vous protège de rien.

Une réponse 201 ne veut pas dire que l'argent est arrivé. Elle dit qu'un paiement existe. Le statut réel suit le cycle created → pending → succeeded | failed, et vous l'apprenez par webhook.

3. Encaisser

curl -X POST "$VALSORIA_BASE/v1-payments" \
  -H "Authorization: Bearer $VALSORIA_CLE" \
  -H "Idempotency-Key: cmd-2026-00412" \
  -H "Content-Type: application/json" \
  -d '{
        "amount": 10000,
        "currency": "XOF",
        "operator": "orange_ci",
        "counterparty": { "msisdn": "0779149021" },
        "merchant_reference": "CMD-2026-00412"
      }'
{ "id": "txn_6INMt2lIL1Pv_y6RgqZvag", "object": "payment", "amount": 10000,
  "currency": "XOF", "status": "created", "livemode": false, … }

merchant_reference est unique par marchand : une seconde transaction portant la même valeur est refusée en 409 duplicate_reference. C'est une protection contre le double encaissement, pas une contrainte administrative.

En sandbox, ajoutez "simulate": "succeed" pour obtenir un paiement réussi immédiatement. Pour éprouver les refus et les pannes, utilisez plutôt les numéros de test : ils traversent toute la chaîne, webhook compris.

4. Recevoir le résultat

Déclarez une URL d'endpoint dans la console. Le détail complet est sur la page Webhooks. Chaque événement arrive signé :

Valsoria-Signature: t=1755770000,v1=<hmac-sha256 hexadécimal>

La signature porte sur t + "." + corps, pas sur le corps seul : sans l'horodatage dans le message signé, un ancien appel authentique pourrait être rejoué en changeant simplement le t affiché.

Trois règles, dans l'ordre d'importance :

  1. Vérifiez la signature avant de lire le contenu. Un endpoint de webhook est une URL publique ; sans vérification, n'importe qui vous annonce un paiement réussi.
  2. Signez sur le corps BRUT, tel qu'il est arrivé. Un corps re-sérialisé après JSON.parse change d'un octet — ordre des clés, espaces — et la signature, elle, porte sur les octets reçus. C'est la première cause d'échec de vérification.
  3. Dédupliquez sur id. La livraison est « au moins une fois » : un même événement peut arriver deux fois, et un retard réseau suffit.

Répondez 2xx dès que l'événement est enregistré, pas quand il est traité. Nous réessayons sur tout autre code, avec un délai croissant.

5. Rembourser

curl -X POST "$VALSORIA_BASE/v1-refunds" \
  -H "Authorization: Bearer $VALSORIA_CLE" \
  -H "Idempotency-Key: rbt-cmd-2026-00412" \
  -H "Content-Type: application/json" \
  -d '{ "payment_id": "txn_6INMt2lIL1Pv_y6RgqZvag" }'

Sans amount, le reste remboursable est rendu en totalité.

Tous les canaux ne savent pas rembourser. Un paiement encaissé par un canal sans opération inverse est refusé en 422 refund_unsupported, avant toute écriture. Le refus est immédiat plutôt que différé : mieux vaut ne rien promettre au client que lui promettre un remboursement qui n'aura pas lieu.

6. Quand ça ne marche pas

Toute réponse porte Valsoria-Request-Id. Il apparaît à l'identique dans Journal API de votre console : méthode, route, code HTTP, latence, et le code d'erreur exact. Commencez toujours par là — la réponse à « est-ce moi ou vous ? » s'y trouve sans nous écrire. Si vous nous écrivez, citez cet identifiant : il désigne votre requête, et elle seule.

Les erreurs ont toutes la même forme :

{ "error": { "code": "invalid_amount", "message": "…" } }

Testez le code, jamais le message. Le code est stable ; le message est rédigé pour un humain et peut être reformulé.

Et ensuite

Version d'API 2026-08-25 · changements · spécification OpenAPI