Recevoir (côté vendeur)
Prérequis
- Un compte marchand Paylet (votre identifiant de bénéficiaire).
- Les SDK vendeur :
from paylet import Vendor(Python) ouimport { Vendor } from "paylet"(TypeScript). - Deux secrets partagés avec Paylet :
challenge_secret(pour signer les challenges) etreceipt_secret(pour vérifier les reçus). Ils ne transitent jamais côté client.
Émettre un challenge
Le cœur du côté vendeur tient en une logique simple : si la requête n'a pas de header X-Payment, vous répondez 402 avec un challenge signé ; sinon vous vérifiez le reçu et livrez la ressource.
from paylet import Vendor
vendor = Vendor(
payee_merchant_id="<votre-id-marchand>",
challenge_secret="<challenge-secret>",
receipt_secret="<receipt-secret>",
)
def handle_request(headers):
# 1. Pas de preuve de paiement -> émettre un challenge 402.
payment = headers.get("X-Payment")
if payment is None:
challenge = vendor.challenge(
"https://vendor.example.com/data/42",
"GET",
1500000, # montant TOTAL payé par l'acheteur (micro-crédits)
)
return 402, {"X-Payment-Challenge": challenge}, {
"error": {
"code": "payment_required",
"message": "Paiement requis pour accéder à cette ressource.",
"details": {
"price_minor": 1500000,
"amount_minor": 1500000,
"currency": "EUR",
"fee_mode": "deduct_from_amount",
"payee_merchant_id": "<votre-id-marchand>",
"resource": "https://vendor.example.com/data/42",
},
}
}
# 2. Preuve présente -> vérifier le reçu localement, sans appel réseau.
vendor.verify_receipt(
payment,
resource="https://vendor.example.com/data/42",
amount_minor=1500000,
)
# 3. Délivrer la ressource.
return 200, {}, {"data": {"title": "Jeu de données 42", "rows": 1200}}Structure du challenge
Le challenge signé porte price_minor (prix net du vendeur), amount_minor (total payé par l'acheteur), la resource, un nonce et une expiration de 5 minutes. Les deux montants et le fee_mode sont signés, donc infalsifiables.
Vérifier un reçu
Deux modes de vérification. En mode local (recommandé), le SDK vérifie la signature HMAC du reçu avec receipt_secret et contrôle que la ressource, le montant et le bénéficiaire correspondent — aucun appel réseau. En mode online, vous pouvez récupérer un reçu perdu via l'API.
curl https://api.paylet.fr/v1/payments/<payment-id>/receipt \ -H "X-API-Key: ak_live_<votre-cle>"
Le fee_mode : les deux modes de commission
La commission de Paylet est toujours de 1,5 % du prix net, avec un minimum de 0,002 €. Le champ fee_mode détermine simplement qui la supporte : l'acheteur ou le vendeur.
deduct_from_amount(défaut) — l'acheteur paie le prix net ; le vendeur reçoitprix − commission.add_to_price— le vendeur reçoit le prix net en entier ; l'acheteur paieprix + commission.
Vous choisissez votre mode via PATCH /v1/me (champ fee_mode) ou depuis le dashboard.
Exemple chiffré
Prix net P = 1,50 €, soit 1 500 000 micro-crédits. Commission : max(round(1 500 000 × 0,015), 2 000) = 22 500 micro-crédits, soit 0,0225 €.
| fee_mode | Prix net (price_minor) | Payé par l’acheteur (amount_minor) | Reçu par le vendeur (net_minor) |
|---|---|---|---|
deduct_from_amount | 1,50 € | 1,50 € | 1,50 € − 0,0225 € = 1,4775 € |
add_to_price | 1,50 € | 1,50 € + 0,0225 € = 1,5225 € | 1,50 € |
Dans les deux cas, Paylet prélève exactement la même commission (0,0225 €) : seule la répartition acheteur/vendeur change. Le montant du challenge est toujours le total payé par l'acheteur (amount_minor).
Plancher effectif de transaction
Si la commission atteint ou dépasse le prix net (c'est-à-dire price_minor ≤ 2 000, soit 0,002 €), le règlement est refusé (400 invalid_request). Le prix net minimum viable est donc légèrement supérieur à 0,002 €.
Erreurs courantes
| code | HTTP | Signification |
|---|---|---|
| challenge_invalid | 402 | Le fee_mode du challenge diverge de celui du marchand, ou le montant est incohérent. |
| invalid_request | 400 | Écart entre amount_minor attendu et calculé, ou prix sous le plancher. |