Recevoir (côté vendeur)

Protéger votre API avec un endpoint payant : émettre un challenge 402, vérifier les reçus de paiement, et choisir comment la commission est répartie.

Prérequis

  • Un compte marchand Paylet (votre identifiant de bénéficiaire).
  • Les SDK vendeur : from paylet import Vendor (Python) ou import { Vendor } from "paylet" (TypeScript).
  • Deux secrets partagés avec Paylet : challenge_secret (pour signer les challenges) et receipt_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çoit prix − commission.
  • add_to_price — le vendeur reçoit le prix net en entier ; l'acheteur paie prix + 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_modePrix net (price_minor)Payé par l’acheteur (amount_minor)Reçu par le vendeur (net_minor)
deduct_from_amount1,50 €1,50 €1,50 € − 0,0225 € = 1,4775 €
add_to_price1,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

codeHTTPSignification
challenge_invalid402Le fee_mode du challenge diverge de celui du marchand, ou le montant est incohérent.
invalid_request400Écart entre amount_minor attendu et calculé, ou prix sous le plancher.