Un cliente che paga non è la stessa cosa di un ordine consegnato. I browser si chiudono, le connessioni cadono, e il redirect di successo che il cliente vede è un dettaglio estetico — non una prova di pagamento. L'unico segnale affidabile è il webhook che Stripe invia al tuo server.
Non fidarti mai del redirect
Considera success_url pura decorazione. La fonte di verità è l'evento checkout.session.completed, consegnato da server a server e firmato. Costruisci la consegna intorno a quell'evento e l'esperienza utente degrada con eleganza invece di corrompere gli ordini.
@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
payload = await request.body()
sig = request.headers.get("stripe-signature", "")
event = stripe.Webhook.construct_event(
payload, sig, WEBHOOK_SECRET
)
if event["type"] == "checkout.session.completed":
fulfill(event["data"]["object"])
return {"received": True}Verifica la firma, sempre
Stripe firma ogni webhook con HMAC-SHA256 e un timestamp. Verificare la firma prova che il payload arriva da Stripe, e la finestra temporale (cinque minuti di default) blocca gli attacchi di replay. Saltare questo controllo significa che chiunque trovi il tuo endpoint può segnare ordini come pagati.
Consegna idempotente
Stripe ritenta l'invio dei webhook, quindi il tuo handler vedrà lo stesso evento più di una volta. Salva l'id dell'evento e salta il lavoro già fatto — invia l'email di consegna una volta, incrementa il contatore vendite una volta, genera la licenza una volta.
async def fulfill(session):
if await db.events.find_one({"_id": session["id"]}):
return # già processato
await db.events.insert_one({"_id": session["id"]})
await send_delivery_email(session["customer_email"])Metti a posto questi tre dettagli — eventi lato server, verifica della firma, idempotenza — e il tuo store sopravvive alla realtà caotica del traffico di produzione. Vendiamo esattamente questo cablaggio come asset pronto perché ogni venditore indipendente lo riscopre nel modo più duro.