REST + JSON
HTTPS uniquement, JSON UTF-8. Aucune dépendance XML ou formulaire hérité côté consommateur — la traduction vers le format natif du PSP est faite par Ts-Payment.
API Ts-Payment
Un endpoint unique, un format de payload normalisé, un journal d’événements signés. Peu importe que le paiement passe par PayZen, Stripe, Adyen ou Mollie — votre code ne bouge pas.
Principes
HTTPS uniquement, JSON UTF-8. Aucune dépendance XML ou formulaire hérité côté consommateur — la traduction vers le format natif du PSP est faite par Ts-Payment.
Chaque POST accepte un header Idempotency-Key. Rejouer la requête renvoie exactement la même réponse pendant 24h — y compris en cas de timeout réseau.
Chaque événement est signé avec votre secret de webhook. Deux signatures en rotation simultanée pour rotation à chaud sans downtime.
L’en-tête Ts-Payment-Version: 2026-04-01 fige le schéma. Nous maintenons chaque version 24 mois après sa dépréciation annoncée.
type + code + psp_code + message, toujours présents. Corrélation des erreurs PSP sur un code unifié — votre logique métier n’a jamais à connaître les codes de chaque PSP.
200 req/s en rafale, 100 req/s soutenu par clé API. Quota relevé sur demande pour les gros volumes — contactez-nous.
Authentification
Chaque environnement dispose de deux clés : une clé tpay_test_* pour la sandbox (route tous les PSP vers leurs sandbox respectives) et une clé tpay_live_* pour la production. Les clés sont rotables à chaud sans interruption.
Toute requête POST accepte un en-tête Idempotency-Key (UUIDv4 recommandé). Deux appels avec la même clé en moins de 24 h renvoient la même réponse — y compris si le premier appel s’est soldé par un timeout. C’est votre filet de sécurité en cas de panne réseau entre vos serveurs et Ts-Payment.
POST /v1/payments HTTP/1.1
Host: api.ts-payment.org
Authorization: Bearer tpay_live_a1B2c3D4e5F6...
Ts-Payment-Version: 2026-04-01
Idempotency-Key: 8d4e6f2a-9c1b-4a7d-b3e2-5f90a1c2d3e4
Content-Type: application/jsonQuickstart
Votre serveur crée le paiement, le client est redirigé vers le formulaire du PSP, Ts-Payment vous notifie l’issue par webhook. La capture est toujours une seconde étape explicite.
PCI DSS — frontière stricte
L’API Ts-Payment n’accepte jamais un PAN, une date d’expiration ni un CVC en clair. Le formulaire de saisie carte est hébergé par le PSP sélectionné (PayZen, Stripe, Adyen…). La gestion 3DS est du ressort du PSP — Ts-Payment n’arbitre rien sur ce sujet et se contente de vous restituer le résultat d’authentification. Vous restez en SAQ A.
Montants en unité mineure (centimes) : 4 990 € = 499000. Convention alignée sur Stripe, Adyen, PayZen, Mollie — pas d’ambiguïté sur les devises à 3 décimales.
Le backend marchand déclare l’intention de paiement. La réponse contient une checkout_url vers laquelle rediriger le client.
{
"amount": 499000,
"currency": "EUR",
"reference": "ORDER-2026-0042",
"payment_method_types": ["card", "ideal", "bancontact", "sepa_debit"],
"customer": {
"email": "marie.dupont@example.com",
"name": "Marie Dupont",
"locale": "fr-FR"
},
"billing_address": {
"line1": "10 rue de Duclus",
"postal_code": "74300",
"city": "Cluses",
"country": "FR"
},
"return_url": "https://merchant.example.com/checkout/return",
"routing": {
"strategy": "cost_optimized",
"preferred_psp": "payzen",
"fallback_psp": ["stripe", "adyen"]
},
"metadata": {
"order_id": "42",
"cart_hash": "a3f2e91c"
}
}{
"id": "pay_01HYX7K3Z4N8Q2PVM6R5J9B2AE",
"object": "payment",
"status": "requires_payment",
"amount": 499000,
"currency": "EUR",
"reference": "ORDER-2026-0042",
"created_at": "2026-04-19T14:32:01.412Z",
"expires_at": "2026-04-19T14:47:01.412Z",
"checkout_url": "https://checkout.ts-payment.org/c/pay_01HYX7K3Z4N8Q2PVM6R5J9B2AE",
"psp": {
"name": "payzen",
"transaction_id": "709821342"
},
"routing": {
"strategy_used": "cost_optimized",
"selected_psp": "payzen",
"fallback_psp": ["stripe", "adyen"]
},
"links": {
"self": "/v1/payments/pay_01HYX7K3Z4N8Q2PVM6R5J9B2AE",
"cancel": "/v1/payments/pay_01HYX7K3Z4N8Q2PVM6R5J9B2AE/cancel"
},
"metadata": { "order_id": "42", "cart_hash": "a3f2e91c" }
}payment.authorizedLe client a saisi sa carte chez le PSP, l’authentification (3DS, SCA) a été gérée par le PSP, l’autorisation bancaire est obtenue. Le webhook porte les infos de la carte masquées et le résultat 3DS.
{
"id": "evt_01HYX7K9R2T8VW4JX6F3M2PB5Q",
"type": "payment.authorized",
"api_version": "2026-04-01",
"created_at": "2026-04-19T14:32:48.120Z",
"livemode": true,
"data": {
"object": {
"id": "pay_01HYX7K3Z4N8Q2PVM6R5J9B2AE",
"object": "payment",
"status": "authorized",
"amount": 499000,
"amount_authorized": 499000,
"amount_captured": 0,
"currency": "EUR",
"reference": "ORDER-2026-0042",
"authorized_at": "2026-04-19T14:32:48.102Z",
"psp": {
"name": "payzen",
"acquirer": "credit-agricole",
"transaction_id": "709821342",
"authorization_code": "A87C2D",
"response_code": "00"
},
"payment_method": {
"type": "card",
"card": {
"brand": "visa",
"number_masked": "4000 XXXX XXXX 3155",
"bin": "400000",
"last4": "3155",
"exp_month": 12,
"exp_year": 2028,
"country": "FR",
"funding": "credit",
"holder_name": "Marie D."
}
},
"three_d_secure": {
"version": "2.2.0",
"authenticated": true,
"eci": "05",
"liability_shift": true
},
"metadata": { "order_id": "42" }
}
}
}La capture est toujours explicite et découplée de l’autorisation. On capture généralement à l’expédition ou à la prestation effective. Montant optionnel pour une capture partielle.
{
"amount": 499000
}{
"id": "pay_01HYX7K3Z4N8Q2PVM6R5J9B2AE",
"status": "captured",
"amount": 499000,
"amount_captured": 499000,
"amount_refunded": 0,
"authorized_at": "2026-04-19T14:32:48.102Z",
"captured_at": "2026-04-19T14:38:12.304Z",
"psp": {
"name": "payzen",
"transaction_id": "709821342"
}
}Un webhook payment.captured est émis en miroir vers votre endpoint, avec le même format que l’étape 2.
Si vous avez déjà un payment_method_token (issu d’un précédent paiement avec save_payment_method, d’une session SCA ou d’un abonnement), vous appelez directement /v1/payments sans passer parcheckout_url.
Pas de fallback possible.
Un token est lié au PSP qui l’a émis (PayZen, Stripe, Adyen…). Ts-Payment ne peut pas le rejouer sur un autre PSP. Si le PSP émetteur est indisponible, le paiement échoue — le fallback_psp est ignoré pour ce type d’appel.
{
"amount": 499000,
"currency": "EUR",
"reference": "SUB-2026-0007-APR",
"payment_method": {
"type": "card",
"token": "pmtok_01HYX7K1P4M9Q2PVM6R5J9B2AD"
},
"customer_id": "cus_01HXZ4R9M6T3K2B8N1P7VX5A0C",
"merchant_initiated": true,
"metadata": {
"subscription_id": "sub_42",
"period": "2026-04"
}
}{
"id": "pay_01HYX7M2P5Q9R3S4T5U6V7W8X9Y",
"status": "authorized",
"amount": 499000,
"amount_authorized": 499000,
"currency": "EUR",
"authorized_at": "2026-04-19T14:32:48.102Z",
"psp": {
"name": "payzen",
"transaction_id": "709821555",
"authorization_code": "B42F3A"
},
"routing": {
"strategy_used": "token_bound",
"selected_psp": "payzen",
"fallback_available": false
},
"payment_method": {
"type": "card",
"card": {
"brand": "visa",
"number_masked": "4000 XXXX XXXX 3155",
"bin": "400000",
"last4": "3155",
"exp_month": 12,
"exp_year": 2028,
"holder_name": "Marie D."
}
}
}Endpoints
Douze endpoints couvrent 100 % des cas e-commerce et SaaS courants.
| Méthode | Chemin | Description |
|---|---|---|
| POST | /v1/payments | Créer un paiement (authorize ou authorize+capture) |
| GET | /v1/payments/:id | Récupérer un paiement |
| GET | /v1/payments | Lister / filtrer (pagination cursor) |
| POST | /v1/payments/:id/capture | Capturer une autorisation (total ou partiel) |
| POST | /v1/payments/:id/cancel | Annuler une autorisation non capturée |
| POST | /v1/payments/:id/refunds | Rembourser (total ou partiel) |
| GET | /v1/payments/:id/refunds | Lister les remboursements d’un paiement |
| POST | /v1/customers | Créer un client (réutilisable pour abonnements / tokens) |
| POST | /v1/payment_methods | Tokeniser un moyen de paiement (token cross-PSP) |
| POST | /v1/subscriptions | Créer un abonnement récurrent |
| GET | /v1/events | Journal d’événements (rejeu, audit trail) |
| POST | /v1/webhooks | Enregistrer un endpoint de webhook |
Webhooks
Chaque événement est envoyé en POST JSON sur votre endpoint, avec un en-tête de signature et un nonce anti-rejeu. Réessai exponentiel sur 72h puis dead-letter queue consultable via l’API.
Content-Type: application/json
Ts-Signature: t=1744989122,v1=5e8c4a...f2,v1=9d3b7e...c1
Ts-Event-Id: evt_01HYX7K9R2T8VW4JX6F3M2PB5Q
Ts-Delivery-Attempt: 1
{
"id": "evt_01HYX7K9R2T8VW4JX6F3M2PB5Q",
"type": "payment.captured",
"api_version": "2026-04-01",
"created_at": "2026-04-19T14:32:02.105Z",
"livemode": true,
"data": {
"object": {
"id": "pay_01HYX7K3Z4N8Q2PVM6R5J9B2AE",
"object": "payment",
"status": "captured",
"amount": 499000,
"amount_captured": 499000,
"currency": "EUR",
"psp": { "name": "payzen", "transaction_id": "709821342" },
"reference": "ORDER-2026-0042",
"metadata": { "order_id": "42" }
},
"previous_attributes": {
"status": "authorized",
"amount_captured": 0
}
}
}Chaque webhook est livré au moins une fois. Utilisez Ts-Event-Id pour dédupliquer côté consommateur. Le journal /v1/events permet de rejouer les 90 derniers jours.
Erreurs
Chaque erreur PSP est traduite dans une taxonomie unifiée. Votre code applicatif ne connaît que les codes Ts-Payment — le code PSP natif reste exposé pour le debug.
{
"error": {
"type": "card_error",
"code": "card_declined",
"decline_code": "insufficient_funds",
"message": "Votre carte a été refusée : provision insuffisante.",
"psp_code": "51",
"psp_message": "Insufficient funds",
"psp": "payzen",
"doc_url": "https://docs.ts-payment.org/errors#insufficient_funds",
"request_id": "req_01HYX7KC8N3P9R2VM4J5B6A7Q",
"payment_id": "pay_01HYX7L0M4R9Q2PVM6R5J9B2BG"
}
}| Code | Description |
|---|---|
| authentication_error | Clé API manquante ou invalide. |
| invalid_request_error | Payload mal formé ou champ obligatoire manquant. |
| card_error | Carte refusée (insufficient_funds, do_not_honor, expired_card…). |
| three_d_secure_error | Authentification 3DS échouée ou abandonnée. |
| psp_error | Le PSP sous-jacent a retourné une erreur non gérée — voir psp_code / psp_message. |
| rate_limit_error | Quota dépassé — consulter l’en-tête Retry-After. |
| routing_error | Aucun PSP disponible pour cette combinaison pays / méthode / montant. |
| api_error | Erreur interne Ts-Payment (5xx) — corrigée automatiquement, réessayer. |
Smart routing
Définissez vos règles une fois, elles s’appliquent à chaque requête. Exemples de policies fréquentes, déclinables dans votre dashboard ou via l’API.
[
{
"when": { "country": "FR", "method": "card", "amount_max": 5000 },
"route": { "primary": "payzen", "fallback": ["stripe"] }
},
{
"when": { "country": "FR", "method": "card", "amount_min": 5000 },
"route": { "primary": "adyen", "fallback": ["payzen"] }
},
{
"when": { "method": "ideal" },
"route": { "primary": "mollie" }
},
{
"when": { "method": "bnpl" },
"route": { "primary": "alma", "fallback": ["klarna"] }
},
{
"when": { "currency": "USD" },
"route": { "primary": "stripe", "fallback": ["checkoutcom"] }
}
]Stratégies supportées : primary_only, cost_optimized, auth_rate_optimized, round_robin, weighted (A/B test), custom (règles DSL).
Un circuit breaker coupe automatiquement un PSP dont le taux d’autorisation tombe sous votre seuil ou dont la latence p95 dépasse 3 secondes, et réouvre par sondes toutes les 30 secondes.
Chaque tentative de routage est horodatée dans le champ routing.attempts[] de la réponse — vous gardez la traçabilité complète, y compris pour les audits DAF.
SDK officiels
Nos SDK sont générés depuis la spec OpenAPI 3.1. Ils restent synchronisés avec chaque version de l’API.
TypeScript / Node
npm i @ts-payment/node
PHP
composer require ts-payment/php
Python
pip install ts-payment
Java
implementation("org.tspayment:client")
Plugins e-commerce : WooCommerce, Prestashop, Magento 2. Shopify sur la roadmap 2026. Tous open-source, accès sur demande.
Parlez-nous de votre intégration. Accès sandbox sous 24h ouvrées, avec vos clés test PayZen / Stripe / Mollie pré-connectées.