Guide de prise en main
Encaisser en XPF
API REST, montants en francs pacifiques, webhooks signés. Le mode test est ouvert à tous et ne touche jamais un prestataire bancaire.
Cette page est un parcours guidé, pas le catalogue.
Elle raconte un premier encaissement de bout en bout et les pièges qui vont avec. Pour tous les endpoints (paramètres, types, contraintes, réponses et codes d'erreur), la référence est générée depuis la spécification OpenAPI et ne peut pas être incomplète.
Ouvrir la référence API complètePas encore de clé ?
Récupérez-en une en un clic, sans inscription. Elle sera insérée automatiquement dans les exemples ci-dessous.
Obtenir une clé de testVotre premier paiement
Trois requêtes : créer le paiement, forcer son issue, constater le résultat. En mode test, c'est vous qui décidez si le paiement réussit, ce qui permet d'éprouver vos chemins d'échec autant que vos chemins heureux.
1. Créez un lien de paiement :
curl -X POST https://api.tupay.pf/api/v1/payment-links \
-H "Authorization: Bearer tpk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{
"amountXpf": 4500,
"description": "Location paddle 2 h"
}'Langage : bashLa réponse contient une url à envoyer à votre client, et un identifiant de lien.
Les montants sont des entiers XPF
Le franc pacifique n'a pas de centimes. amountXpf: 4500 vaut 4 500 XPF, pas 45. Ne divisez jamais par 100.
Le champ amountEurCents existe uniquement pour la réconciliation avec le prestataire bancaire, au taux légal fixe de 119,3317. Il ne sert pas à l'affichage client.
Authentification
Un en-tête Authorization: Bearer, et deux familles de clés :
tpk_test_…: aucun argent ne bouge, aucun appel au prestataire bancaire.tpk_live_…: encaissement réel.
Les deux mondes sont étanches.Une clé de test ne peut ni lire, ni rembourser, ni même voir une transaction réelle, y compris en connaissant son identifiant. Elle ne peut pas non plus créer de clé live : un secret de test qui fuite ne donne aucun accès à l'argent.
Encaisser
Deux façons : un lien de paiement à partager, ou un paiement créé depuis votre code.
# Créer un paiement
curl -X POST https://api.tupay.pf/api/v1/payments/intent \
-H "Authorization: Bearer tpk_test_VOTRE_CLE" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amountXpf": 4500, "merchantId": "VOTRE_ID_MARCHAND"}'
# Forcer l'issue (mode test uniquement)
curl -X POST https://api.tupay.pf/api/v1/test/simulate \
-H "Authorization: Bearer tpk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{"transactionId": "…", "outcome": "succeeded"}'Langage : bashL'en-tête Idempotency-Key est obligatoire sur les paiements et les remboursements. Rejouer la même clé renvoie le résultat mémorisé sans réexécuter l'opération : un timeout réseau suivi d'un nouvel essai ne débitera jamais deux fois.
Montants magiques pour provoquer un échec dès la création : 402 carte refusée, 403 provision insuffisante, 429 surcharge.
Webhooks
Déclarez une URL, Tupay l'appelle à chaque changement d'état. En mode test, http://localhost est accepté.
curl -X POST https://api.tupay.pf/api/v1/webhook-endpoints \
-H "Authorization: Bearer tpk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{"url": "http://localhost:3000/tupay/webhook"}'Langage : bashLa réponse contient un secret whsec_… affiché une seule fois. Chaque livraison porte un en-tête Tupay-Signature à vérifier.
const crypto = require('node:crypto')
function verifier(corpsBrut, entete, secret) {
const p = Object.fromEntries(
entete.split(',').map((x) => x.split('=').map((s) => s.trim())),
)
const t = Number.parseInt(p.t, 10)
// Anti-rejeu : on refuse un événement trop ancien.
if (Math.abs(Math.floor(Date.now() / 1000) - t) > 300) return false
const attendu = crypto
.createHmac('sha256', secret)
.update(`${t}.${corpsBrut}`, 'utf8')
.digest('hex')
return crypto.timingSafeEqual(Buffer.from(attendu), Buffer.from(p.v1))
}Langage : javascriptLe piège n° 1 : signez le corps brut de la requête, jamais un JSON reparsé puis réencodé. Le moindre espace d'écart casse la signature. En Express : express.raw({ type: 'application/json' }) .
Répondez un code 2xx en moins de 10 secondes, et dédupliquez sur event.id : une même livraison peut se répéter, c'est le prix d'une livraison garantie. En cas d'échec, Tupay réessaie sept fois sur environ 34 heures.
Remboursements
Total ou partiel. Les remboursements partiels s'accumulent : après 1 500 XPF remboursés sur 4 500, le statut reste succeeded et c'est refundedAmountXpf qui fait foi.
Ne testez pas status === 'refunded' pour savoir si un remboursement a eu lieu : ce statut ne bascule qu'au remboursement intégral.
Erreurs
Testez toujours le champ error : code machine stable. Le champ message est destiné à un humain, rédigé en français, et peut changer sans préavis.
{
"error": "validation_error",
"message": "Le montant minimum est de 100 XPF.",
"code": 400
}Langage : jsonUn 404 plutôt qu'un 403 sur une ressource d'un autre marchand est délibéré : nous ne confirmons pas l'existence de ce qui ne vous appartient pas.
La liste complète des codes machine, avec le statut HTTP et un exemple de corps pour chacun, est au chapitre des erreurs de la référence.