Paiement par agent
Un agent IA peut réserver un court dans un club Sporty et le payer avec une carte que l'acheteur lui a confiée. Il existe deux points d'entrée, qui reposent sur le même processus de paiement :
- ACP : l'API de paiement de l'Agentic Commerce Protocol,
version
2026-04-17, à l'adressehttps://api.sporty.plus/api/acp. - MCP : un serveur MCP à l'adresse
https://api.sporty.plus/mcp(Streamable HTTP), avec des outils pour trouver des clubs et du temps de court libre, en plus des outils de paiement ACP.
Les deux sont décrits par https://api.sporty.plus/.well-known/acp.json.
Le paiement se fait avec un jeton de paiement partagé Stripe
(spt_…). Le débit est effectué sur le compte Stripe du club lui-même ; le club est le vendeur
officiel (merchant of record).
403 not_sellable pour un club qui ne l'a pas fait.Qui peut acheter
Chaque requête porte l'un de nos jetons d'accès OAuth dans Authorization: Bearer. C'est la
nature du jeton qui détermine qui est l'acheteur.
| Jeton | Scope | L'acheteur |
|---|---|---|
| Celui d'un membre, issu du flux du code d'autorisation | me.purchases.write | Le membre. Il paie le prix de sa propre adhésion, et la réservation est la sienne. |
Le jeton client_credentials d'une plateforme validée | club.checkout.write, accordé par le club | Un invité, désigné par son e-mail. Il paie ce que paie un nouveau venu. |
me.purchases.write est distinct de me.reservations.write : un membre qui laisse une
application réserver pour lui ne l'a pas autorisée pour autant à dépenser son argent.
Le paiement en tant qu'invité est réservé aux applications examinées par Sporty, car il crée
des comptes. Le club doit aussi accorder club.checkout.write à l'application, dans son CMS,
sous Clients API. Lorsque l'invité paie, nous créons un compte Sporty pour son e-mail et lui
envoyons un lien pour choisir un mot de passe.
Si l'e-mail appartient déjà à un compte Sporty, le paiement est refusé avec requires_sign_in.
Faites suivre OAuth à la personne et achetez plutôt en tant que membre. Sinon, n'importe qui
pourrait ajouter des réservations au compte de quelqu'un d'autre en tapant son adresse.
Trouver du temps de court
Les articles du paiement sont des créneaux de court. L'identifiant d'un créneau indique le court, le début en UTC et la durée :
slot_812_20261003T1700Z_90
Obtenez-les avec l'outil MCP search_availability, qui liste les débuts libres pour l'acheteur
avec ce que chacun lui coûte :
{
"club_id": 214,
"currency": "usd",
"timezone": "America/New_York",
"slots": [
{
"item_id": "slot_812_20261003T1700Z_90",
"court": "Court 2",
"sports": ["pickleball"],
"starts_at": "2026-10-03T13:00:00-04:00",
"ends_at": "2026-10-03T14:30:00-04:00",
"minutes": 90,
"total": 6533,
"amounts": { "price": 6000, "tax": 533, "fee": 0, "total": 6533 }
}
]
}
Trouvez d'abord le club avec search_clubs (par nom, ville ou adresse, et éventuellement par
sport).
Les montants sont toujours des entiers en unités mineures : 6533 correspond à 65,33 $.
Le paiement, via ACP
Les points de terminaison sont ceux d'ACP. Chaque requête envoie API-Version: 2026-04-17, et
chaque POST envoie une Idempotency-Key.
POST /api/acp/checkout_sessions | Ouvrir un paiement pour un créneau. |
GET /api/acp/checkout_sessions/{id} | Le lire. |
POST /api/acp/checkout_sessions/{id} | Changer le créneau ou l'acheteur. |
POST /api/acp/checkout_sessions/{id}/complete | Payer et réserver. |
POST /api/acp/checkout_sessions/{id}/cancel | L'abandonner. |
Ouvrir ou modifier un paiement ne réserve rien. Cela calcule le prix du créneau, et la
session est ready_for_payment ou not_ready_for_payment, avec des messages qui expliquent
pourquoi. Rien n'est bloqué tant que l'acheteur n'a pas payé : un paiement que personne ne
finalise ne coûte donc aucun court au club. Une session expire 30 minutes après sa dernière
utilisation.
curl -X POST https://api.sporty.plus/api/acp/checkout_sessions \
-H "Authorization: Bearer $TOKEN" \
-H 'API-Version: 2026-04-17' \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{
"line_items": [{ "id": "slot_812_20261003T1700Z_90" }],
"currency": "usd",
"capabilities": {},
"buyer": { "email": "jamie@example.com", "first_name": "Jamie", "last_name": "Rivera" }
}'
La réponse est une session de paiement ACP. Les éléments importants ici :
line_items[0]: le court, le club et l'horaire, ainsi que sestotals.totals:items_base_amount,subtotal,tax,fee(la surcharge carte d'un club, lorsqu'il en applique une) ettotal. Un club américain ajoute la taxe de vente en sus. Un club dont les prix incluent la TVA affiche la TVA comprise dans le prix, en tant quetaxavec « (included) ».fulfillment_options: toujours une seule optiondigital. Rien n'est expédié ; la réservation apparaît dans l'application Sporty.capabilities.payment.handlers: un seul gestionnaire,stripe_spt(dev.acp.tokenized.card). Sonconfig.merchant_idest le compte Stripe du club, pour les informations vendeur du jeton.links: nos conditions d'utilisation et notre politique de confidentialité. Montrez-les à l'acheteur.
Pour payer, envoyez le jeton :
{
"payment_data": {
"handler_id": "stripe_spt",
"instrument": { "type": "card", "credential": { "type": "spt", "token": "spt_123" } }
}
}
Une finalisation réussie répond status: "completed" avec un order, dont la permalink_url
est la page des réservations du membre sur le site du club.
Quand le paiement n'aboutit pas
Ces cas reviennent sous forme de messages sur la session, et non d'erreurs HTTP, car
l'acheteur peut y remédier :
code | Ce qui s'est passé | Session |
|---|---|---|
payment_declined | La carte a été refusée. Rien n'a été réservé. | ready_for_payment : essayez un autre jeton. |
requires_3ds | La carte exige 3-D Secure, que le paiement par agent ne prend pas encore en charge. | ready_for_payment |
out_of_stock | Quelqu'un a pris le créneau depuis qu'il a été proposé. | not_ready_for_payment : choisissez un autre créneau. |
price_change (avertissement) | Le prix a changé depuis le devis, par exemple parce que la première adhésion d'un invité est tarifée différemment. Rien n'a été débité. | ready_for_payment au nouveau total : faites-le confirmer par l'acheteur. |
missing | L'e-mail ou le nom d'un invité manque. | not_ready_for_payment |
requires_sign_in | L'e-mail d'un invité correspond déjà à un compte Sporty. | not_ready_for_payment |
Les erreurs HTTP utilisent le corps d'erreur d'ACP, {type, code, message, param}, et
concernent les requêtes que l'agent a mal formulées : un article inconnu
(400 invalid_item_id), un club qui ne vend pas via des agents ou n'a pas autorisé cette
application (403), une session qui appartient à quelqu'un d'autre (404), l'annulation d'une
session finalisée (405).
Idempotence
Un POST répété avec la même Idempotency-Key reçoit la première réponse, avec
Idempotent-Replayed: true, et rien ne se produit deux fois. Cela inclut le débit de la carte.
La même clé avec un corps différent répond 422 idempotency_conflict. Tant que la première
requête est en cours, une répétition répond 409 idempotency_in_flight. Les clés sont
conservées pendant 24 heures, par application et par membre.
Le paiement, via MCP
Le serveur MCP s'authentifie avec les mêmes jetons OAuth. Un client qui n'en a pas reçoit 401
avec WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp",
et y trouve notre serveur d'autorisation. Le client peut
s'enregistrer lui-même et demander me.purchases.write au
membre.
| Outil | |
|---|---|
search_clubs | Les clubs qui vendent du temps de court via des agents. |
search_availability | Les débuts libres dans un club, au prix applicable à l'acheteur. |
create_checkout_session | Création ACP. |
get_checkout_session | Lecture ACP. |
update_checkout_session | Mise à jour ACP. |
complete_checkout_session | Finalisation ACP. |
cancel_checkout_session | Annulation ACP. |
Les outils de paiement suivent la liaison MCP d'ACP : les arguments sont {meta, id, payload},
où meta contient api_version (obligatoire) et idempotency_key, et payload est le corps
REST. Le structuredContent du résultat est la même session de paiement que celle renvoyée par
l'API REST.
Un refus revient sous forme d'erreur d'outil (isError: true) dont le texte est l'objet
d'erreur ACP, et non sous forme d'erreur JSON-RPC.
Être informé de la commande ensuite
Indiquez où envoyer les événements de vos commandes :
curl -X PUT https://api.sporty.plus/api/acp/webhook \
-H "Authorization: Bearer $TOKEN" -H 'API-Version: 2026-04-17' \
-H 'Content-Type: application/json' \
-d '{ "url": "https://agent.example/agentic_commerce/webhooks/order_events", "secret": "…" }'
Fournissez votre propre secret d'au moins 24 caractères, ou omettez-le et un secret sera créé et
affiché une seule fois. GET affiche le webhook et sa dernière livraison ; DELETE le
supprime. L'URL doit être en https sur une adresse publique.
Nous envoyons les événements de commande ACP, chacun avec la commande complète :
order_createlorsqu'un paiement est effectué ;order_updatelorsque la réservation est annulée et remboursée. Lestatusde la commande devientcanceled, et sesadjustmentsdétaillent le remboursement.
POST /agentic_commerce/webhooks/order_events
Merchant-Signature: t=1790000000,v1=5f2c…
Content-Type: application/json
{"type":"order_create","data":{"type":"order","id":"ord_48213","checkout_session_id":"cs_…","status":"confirmed", …}}
v1 est le HMAC-SHA256 en hexadécimal de {t}.{body} avec votre secret. Refusez un t datant
de plus de cinq minutes. La livraison est garantie au moins une fois : une livraison échouée est
retentée avec un délai qui double à chaque fois, jusqu'à dix fois.
Annulations et remboursements
Une réservation achetée via un agent est remboursée sur la carte qui a servi à la payer
lorsqu'elle est annulée dans le délai d'annulation du club. L'acheteur peut annuler dans
l'application Sporty, ou via DELETE /api/partner/v1/me/reservations/{id} si l'agent détient
me.reservations.write. Le remboursement inclut l'éventuelle surcharge carte.
Pas encore pris en charge
- Tout ce qui n'est pas du temps de court : adhésions, événements et articles de la boutique.
- Les vérifications 3-D Secure.
- Le partage du prix entre joueurs. L'acheteur réserve le court et le paie en totalité ; des partenaires peuvent être ajoutés dans l'application.
- Plus d'un créneau par paiement.
- Un flux de produits. Le temps de court change à la minute, les agents le trouvent donc avec
search_availability.