Driver di pagamento
Un driver di pagamento collega Arenile a un circuito (Stripe, Satispay, un POS virtuale…). Il core include
manual (nessun pagamento online, saldo in cassa) e stripe. Ogni stabilimento sceglie il provider attivo e
salva le proprie credenziali, cifrate.
L’interfaccia
Sezione intitolata “L’interfaccia”type PaymentProvider interface { Code() string // identificativo stabile, es. "stripe" Capabilities() PaymentCapabilities // Online, Refunds, PartialRefunds, Wallets, EmbeddedMode CreatePayment(ctx context.Context, req CreatePaymentRequest) (*CreatePaymentResult, error) ParseWebhook(ctx context.Context, r *http.Request, creds Credentials) (*PaymentEvent, error) Refund(ctx context.Context, req RefundRequest) (*RefundResult, error)}| Metodo | Contratto |
|---|---|
CreatePayment |
Riceve importo in centesimi, valuta, descrizione, email, URL di ritorno e di annullamento, modalità preferita, IdempotencyKey (<booking_id>:<tentativo>, da inoltrare al provider), metadati e credenziali decifrate dello stabilimento. Restituisce ProviderRef, Mode (redirect, embedded, none), RedirectURL o ClientSecret, scadenza. |
ParseWebhook |
Verifica la firma con le credenziali dello stabilimento dell’URL e normalizza l’evento in PaymentEvent (payment.succeeded, payment.failed, payment.refunded, payment.pending) con l’id evento del provider per la deduplica. Firma assente o errata → ext.ErrInvalidSignature; evento autentico ma irrilevante → ext.ErrIgnoredEvent. |
Refund |
Rimborso totale o parziale, idempotente; ext.ErrNotSupported se il provider non rimborsa. |
Capacità facoltative
Sezione intitolata “Capacità facoltative”Il gateway riconosce alcune capacità in più, dichiarate con soli tipi di pkg/ext: un driver esterno le
implementa senza importare pacchetti interni (structural typing).
| Interfaccia | Metodo | A cosa serve |
|---|---|---|
PaymentStatusChecker |
PaymentStatus(ctx, providerRef, creds) (*ext.PaymentEvent, error) |
Riconciliazione: interroga un pagamento per riferimento. Senza, il driver compare come “provider non raggiungibile” nel report. |
CredentialsVerifier |
VerifyCredentials(ctx, creds) (accountLabel string, err error) |
Il pulsante di verifica delle credenziali nel pannello. |
PaymentCanceller |
CancelPayment(ctx, providerRef, creds) error |
Chiude un pagamento sostituito da un nuovo tentativo, così il cliente non può pagare due volte. |
Procedura
Sezione intitolata “Procedura”- Leggi la specifica (sezioni 13, 18, 22), le invarianti dei pagamenti (INV-19, INV-22, INV-23, INV-24) e i
driver
manualestripe(internal/domain/payment/stripe). - Crea il pacchetto: nel core sotto
internal/domain/payment/<code>, oppure in un tuo modulo che importa solopkg/ext. Non modificare le interfacce dipkg/ext. - Implementa i cinque metodi. Metti
establishment_id,booking_idebooking_codenei metadati del provider: servono a ricondurre l’evento alla prenotazione. - Rendi l’URL base dell’API del provider configurabile e l’orologio iniettabile: i test girano contro un
httptest.Servere verificano la tolleranza della firma con un orologio finto. - Registra il driver: nel registry di
internal/appper un driver del core, oppure conapp.WithExtensions(ext.Extensions{PaymentProviders: …})in un tuomain(vedi Estensioni). - Scrivi i test (senza rete):
CreatePaymentcontro il server finto; webhook firmati validi, con firma errata, scaduti, duplicati e di tipo sconosciuto; rimborsi secondo le capacità; integrazione: un webhook conferma la prenotazione una sola volta, in una transazione, e una consegna duplicata non ha effetti. - Documenta i campi delle credenziali, l’URL del webhook e gli eventi da abilitare, aggiungi una voce al
CHANGELOG.mde lanciamake check-full.
In Claude Code la skill /new-payment-driver guida esattamente questa procedura.
Regole non negoziabili
Sezione intitolata “Regole non negoziabili”- La conferma arriva solo da
ParseWebhook(o dalla riconciliazione), mai dall’URL di ritorno del browser. - Un evento duplicato non conta mai due volte il pagato: la deduplica usa l’id evento che restituisci.
- Nessun segreto nei log né nei messaggi d’errore: niente chiavi, firme, corpi grezzi, email o token.
Per i fallimenti restituisci solo un codice breve (
FailureReason), mai il messaggio del provider. - Errori di rete,
5xxe429del provider vanno segnalati come temporanei: il cliente vede “riprova tra poco” (503 payment_provider_unavailable).
Come lo fa il driver Stripe
Sezione intitolata “Come lo fa il driver Stripe”Il driver Stripe del core è un buon esempio: nessun SDK (solo net/http con richieste form e una
Stripe-Version fissata), Checkout Session in redirect con scadenza a 30 minuti o PaymentIntent per il modulo
incorporato, verifica HMAC di Stripe-Signature con tolleranza di 5 minuti e più valori v1, rimborsi con
POST /v1/refunds idempotenti, PaymentStatus per la riconciliazione e VerifyCredentials con
GET /v1/account.

