Riscossione dei pagamenti

  • Integra le sessioni di pagamento per raccogliere pagamenti una tantum nella tua app.
  • Implementa la fatturazione degli abbonamenti per i modelli di entrate ricorrenti.
  • Configura i gestori webhook per una verifica dei pagamenti affidabile
  • Segui le migliori pratiche per la sicurezza dei pagamenti
  • Procedura di pagamento: un pulsante "Paga" che apre una pagina di pagamento sicura
  • Pagamento dell'abbonamento: opzioni di pagamento una tantum e ricorrente
  • Webhook Handler – un gestore dei pagamenti sicuro e affidabile per il tuo MVP
  • Gli studenti hanno messo a punto una strategia di prezzo e un budget semplice per il loro progetto.
  • Gli studenti hanno un repository GitHub MVP con un ambiente Codespaces.
  • Gli studenti hanno implementato l'autenticazione degli utenti nel loro MVP.

La tua app funziona. Gli utenti l'hanno provata e hai sistemato i bug. L'hai messa su una piattaforma tipo Vercel, così è disponibile per tutti. Ora arriva la domanda che ogni startup si fa: 

Come veniamo pagati?

Nella lezione sull'alfabetizzazione finanziaria, hai creato un budget semplice con le previsioni di entrate e hai scelto una strategia di prezzo per la tua impresa. Hai stimato quanti utenti ti servono, quanto far pagare e quando pensi di raggiungere il pareggio. Questi numeri erano solo stime. In questa unità, trasformerai quelle stime in un vero e proprio sistema di pagamento.

Nota: per questo acceleratore non è obbligatorio accettare pagamenti. Tuttavia, è un aspetto che dovresti considerare come un passo successivo necessario.

Useremo Stripe, una piattaforma di pagamento usata da aziende in più di 40 paesi. Stripe si occupa delle parti più complicate e delicate dell'elaborazione dei pagamenti, come la convalida delle carte, il rilevamento delle frodi, la conversione di valuta, la conformità normativa e la sicurezza, così tu non devi farlo. Il tuo compito è collegare la tua app a Stripe, dirle cosa addebitare e gestire il risultato.

Stripe è lì per aiutare le aziende in più di 40 paesi, ma la disponibilità cambia a seconda di dove sei. Se Stripe non è disponibile dove vivi, ci sono altre opzioni. Controlla la disponibilità nella tua zona e le alternative che funzionano bene. Ecco qualche consiglio:

  • Nigeria: Prova Paystack: funziona allo stesso modo (sessioni di checkout, webhook, abbonamenti) con un'API simile. Dai un'occhiata alla loro guida rapida Accetta pagamenti.
  • India: Stripe funziona, ma devi verificare la tua attività. Se non puoi farlo per il tuo MVP, usa Razorpay — dai un'occhiata alla loro guida rapida Standard Checkout.
  • Messico: Stripe funziona bene qui, ma Conekta è un'ottima alternativa locale che gestisce meglio i pagamenti OXXO (in contanti).

I modelli di codice in questa unità (creazione di sessioni di checkout, webhook e fatturazione degli abbonamenti) funzionano allo stesso modo sulla maggior parte delle piattaforme di pagamento, quindi quello che impari qui lo puoi usare direttamente. Segui le attività per imparare i concetti, poi adattali usando la documentazione della tua piattaforma.

In questa unità continueremo a usare TutorMatch come esempio. In questa unità, TutorMatch aggiunge la possibilità di pagare una singola sessione di tutoraggio o di abbonarsi a un piano mensile.

Seguirai gli esempi di TutorMatch, adattando i modelli al tuo MVP.

Alla fine di questa unità, il tuo MVP sarà pronto per ricevere pagamenti veri. Userai la modalità di prova per tutto il tempo, quindi non ci saranno soldi veri in giro, ma il codice che scrivi potrà essere usato se deciderai di ricevere pagamenti in futuro.

Come iniziare a usare la tua piattaforma di pagamento

Nelle lezioni precedenti hai configurato GitHub come base per il tuo codice. Hai anche configurato Supabase come database cloud. Ora configurerai Stripe (o un'alternativa, a seconda del Paese) come piattaforma di pagamento. Il procedimento è lo stesso:

  1. creare un account
  2. prendi le chiavi API
  3. collegalo alla tua app
stripe logo

Stripe è per i pagamenti quello che Supabase è per l'archiviazione dei database: si occupa delle parti più complicate, così puoi concentrarti sul tuo prodotto. Quando un utente paga tramite Stripe, Stripe:

  • Controlla se il numero della carta è corretto e cerca di capire se c'è qualche trucchetto.
  • Cripta i dettagli di pagamento in modo che non vengano mai scoperti
  • Ti manda i soldi sul tuo conto (togliendo una commissione per la transazione)
  • Gestisce la conversione di valuta se il tuo utente paga in una valuta diversa
  • Si occupa di far rispettare le regole in ogni paese.

Senza una piattaforma come Stripe, dovresti fare tutto da solo, il che richiederebbe mesi e certificazioni di sicurezza. Stripe si occupa di queste cose complicate, così puoi aggiungere i pagamenti alla tua app con un po' di codice.

Ci sono diverse piattaforme di pagamento disponibili. Ecco come si confrontano per chi sta creando un MVP:

Piattaforma

Configurazione

Inizia gratis

Costo della transazione

Ideale per

Stripe

Semplice

Sì

~2,9% + commissione per transazione (varia a seconda del Paese)

MVP, app web, abbonamenti

Paypal

Semplice

Sì

~3,49% + commissione

Mercati, protezione dell'acquirente

Piazza

Semplice

Sì

~2,6% + commissione

Pagamenti di persona, vendita al dettaglio

Costruisci il tuo

Molto complicato

Non disponibile

Nessuno (tranne i costi del server)

Mai consigliato per gli MVP

Per queste cose, useremo Stripe perché:

  • È pensato per gli sviluppatori e si integra con Express.js (il tuo backend delle lezioni precedenti).
  • Il piano gratuito va bene per testare e sviluppare MVP in fase iniziale senza costi mensili.
  • Paghi le commissioni di transazione solo quando incassi pagamenti reali.
  • Lo stesso codice funziona in più di 40 paesi e con più di 135 valute.
  • Offre la fatturazione degli abbonamenti integrata per i modelli di entrate ricorrenti.

Ancora una volta, dai un'occhiata alla migliore opzione di elaborazione dei pagamenti nella tua zona o nel tuo Paese e usa lo stesso processo per la piattaforma che hai scelto.

Collegare una piattaforma di pagamento alla tua app

Ci sono diversi modi per integrare l'elaborazione dei pagamenti. Capire le opzioni disponibili ti aiuta a fare la scelta giusta per il tuo MVP.

Approccio

Cosa vuol dire

Tempo di configurazione

Sicurezza

Personalizzazione

Ideale per

Checkout ospitato

La piattaforma ha la pagina dei pagamenti. Tu mandi lì gli utenti.

~30 min

Massimo — La piattaforma si occupa di tutto

Basso — La piattaforma decide come appare

MVP e startup (la nostra scelta)

Checkout integrato

L'interfaccia utente della piattaforma appare nella tua pagina come un componente.

Circa 1 ora

Alta — La piattaforma continua a gestire i dati delle carte

Medio: si adatta al layout della tua pagina

App che richiedono coerenza del marchio

Elemento di pagamento

Componente UI sicuro e già pronto (un iframe) fornito dalla piattaforma di pagamento che puoi "inserire" nel tuo codice frontend.

Circa 2 ore

Alto, ma più codice da gestire

Alto — controllo completo del layout

App che richiedono una procedura di checkout unica

Modulo personalizzato

Costruisci tutto da zero

Giorni

Ti occupi della conformità PCI

Controllo totale

Non consigliato per gli MVP

Userai Hosted Checkout. Ecco perché:

Il più veloce da costruire

Tu crei una sessione di checkout sul tuo server e la piattaforma di pagamento si occupa di tutto il resto: la pagina di pagamento, la convalida della carta, i messaggi di errore e la ricevuta.

Il più sicuro

La tua app non tocca mai i numeri delle carte. La piattaforma li prende sulla sua pagina, quindi sei automaticamente a posto con gli standard PCI.

Apprezzato dagli utenti

La pagina di pagamento della piattaforma mostra il loro marchio, che gli utenti conoscono e di cui si fidano.

Funzioni integrate

La pagina ospitata da Stripe supporta automaticamente diverse valute, Apple Pay, Google Pay, UPI e altri metodi di pagamento a seconda di dove si trova il cliente. La maggior parte delle piattaforme di pagamento ha caratteristiche simili.

Ricevere il tuo primo pagamento

Ogni sistema di pagamento inizia con una singola transazione. In questa prima attività, creerai un flusso di checkout nella tua app.

Le attività che seguono ti spiegheranno come aggiungere un pulsante "Paga" nella tua app che reindirizzi gli utenti alla pagina di pagamento ospitata da Stripe: una pagina professionale e sicura dove gli utenti inseriscono i propri dati di pagamento.

Una volta completato il pagamento, tornano alla tua pagina di conferma che attesta l'acquisto.

Pianificare cosa succede prima, durante e dopo il pagamento ti evita di dover rifare tutto dopo. Se salti questo passaggio, potresti creare un flusso di checkout che non va d'accordo con il tuo modello di prezzi basato sul tuo budget semplice, e dovrai rifarlo.

Se usi una piattaforma di pagamento diversa, puoi adattarti a come funziona, ma segui le loro istruzioni.

 

Apri il tuo “Bilancio Semplice” della lezione sull’alfabetizzazione finanziaria e dai un’occhiata alla tua strategia di prezzi.

Rispondi a queste domande:

  • Cosa stai facendo pagare? (Un prodotto, un servizio, l'accesso a una funzione?)
  • È un pagamento unico o ricorrente? (In questa attività iniziamo con un pagamento unico, poi aggiungiamo gli abbonamenti nell'attività 3).
  • Quanto costa? (Usa il prezzo dalle tue previsioni di entrate nel Budget Semplice)
  • Cosa succede dopo il pagamento? (L'utente ha accesso subito? Riceve un'email di conferma? Può scaricare qualcosa?)

Per il nostro esempio di TutorMatch, le risposte sono:

  • Costo: un unico pagamento per una sessione di tutoraggio
  • Tipo: pagamento unico (aggiungeremo gli abbonamenti aggiunti nell'attività 3)
  • Prezzo: adattalo al tuo mercato 

Scrivi le tue risposte. Ti serviranno per l'attività che segue.

NOTA: Per usare Stripe, devi fornire l'URL di qualsiasi sito web, profilo social o app mobile che usi per promuovere la tua attività o vendere prodotti. Inoltre, dovrai indicare un conto corrente e verificare la tua identità. Per l'attività che segue puoi saltare questo passaggio, ma dovrai seguire questi passaggi per ricevere pagamenti veri e propri.

ATTIVITÀ 1

Configura Stripe per accettare pagamenti

Tempo stimato: 20 minuti

  1. Vai su stripe.com e clicca su Inizia ora per fare un account gratis usando la tua email.
  2. Conferma la tua email e accedi alla Dashboard di Stripe.
  3. Scegli i pagamenti una tantum tra le opzioni di pagamento. Puoi modificare questa impostazione in qualsiasi momento.
  4. Per ora scegli la versione sandbox. Per attivare il tuo account e poter effettivamente ricevere pagamenti, dovrai fornire un conto bancario, l'URL di un sito web o di un'app mobile e verificare la tua identità.
  5. Arriverai alla pagina iniziale della Dashboard: prenditi un attimo per dare un'occhiata in giro.

La Dashboard Stripe è il tuo centro di controllo per i pagamenti. Ecco le sezioni principali che userai:

  • Pagamenti: Guarda tutte le transazioni (di prova e reali)
  • Abbonamenti: gestisci la fatturazione ricorrente (lo userai nell'attività 3)
  • Sviluppatori → Chiavi API: le tue credenziali API
  • Sviluppatori → Webhook: notifiche di pagamento al tuo server (lo configurerai nell'Attività 2)

Stripe ti dà due set di chiavi API, un po' come le chiavi pubblicabili e segrete di Supabase. La chiave pubblicabile è sicura da usare nel frontend, mentre quella segreta va usata solo nel backend.

  1. Nella Dashboard di Stripe, clicca su Sviluppatori nella barra laterale sinistra.
  2. Clicca su Chiavi API
  3. Copia entrambe le chiavi: ti serviranno entrambe.

Il test nel prefisso della chiave vuol dire che sei in modalità test. Non ti verrà addebitato denaro reale. Quando sei pronto ad accettare pagamenti reali, passa alle chiavi live (pk_live_, sk_live_).

Aggiungeremo i prodotti relativi ai servizi che intendi far pagare nella tua app.

Per TutorMatch, aggiungeremo un pagamento una tantum per le sessioni di tutoraggio e un pagamento ricorrente per l'abbonamento all'app.

  1. Vai al Catalogo prodotti dal menu principale.
  2. Clicca Crea un prodotto.
    • Dai un nome al tuo prodotto 
    • Aggiungi una descrizione
    • Scegli tra pagamento ricorrente o una tantum
    • Inserisci un prezzo e scegli la valuta
  3. Aggiungi il prodotto.
adding a stripe product
  1. Quando il prodotto compare nell'elenco, cliccaci sopra per visualizzarlo.
  2. Sulla riga del prezzo, clicca sui 3 puntini e seleziona “Copia ID prezzo”.
stripe price id
  1. Salva l'ID del prezzo. Ti servirà quando dovrai programmare la tua app.

Aggiungi qualsiasi altra voce che addebiti, assicurandoti di copiare l'ID del prezzo e indicando a quale prodotto si riferisce.

Ora che hai configurato il tuo account Stripe e i tuoi prodotti, puoi iniziare ad aggiungere la procedura di pagamento alla tua app. 

Per prima cosa, dovresti considerare il flusso di navigazione nella tua app.

Nella nostra app seguiremo questa procedura:

  1. L'utente si registra o effettua l'accesso.
  2. Nella schermata principale compare un nuovo pulsante " Paga la sessione di tutoraggio ".
  3. L'utente clicca sul pulsante e viene reindirizzato alla piattaforma Stripe.
  4. L'utente inserisce i dati di pagamento.
  5. L'utente torna all'app.
  6. L'utente riceve un messaggio che conferma il pagamento.
  7. Se l'utente annulla il pagamento sulla piattaforma Stripe:
    1. Vengono riportati nell'app
    2. Viene visualizzato un messaggio di annullamento

Per le attività che seguono, partiamo dal presupposto che tu abbia già pubblicato la tua app su Vercel. Questo significa che c'è una nuova architettura serverless, quindi modificare il codice ed eseguirlo in Codespace sarà un po' diverso dal solito. In passato, in Codespace eseguivamo il frontend separatamente dal backend Express. Due processi, due ambienti.

Con Vercel serverless:

  • Non c'è nessun processo server in esecuzione
  • Ogni funzione in api/index.js si avvia su richiesta quando arriva una richiesta
  • Vercel inserisce le variabili d'ambiente dal file .env di root ( in locale) o dalla dashboard di Vercel (in produzione) direttamente in ogni chiamata alla funzione
  • Anche il frontend (Vite) legge dallo stesso file .env alla radice , ma al browser vengono esposte solo le variabili con il prefisso VITE_. Tutte le altre variabili d'ambiente sono protette. 
ATTIVITÀ 2

Accetta pagamenti nella tua app

Tempo stimato: 45 minuti

Prima di scrivere qualsiasi codice, dovrai dare un'occhiata alle app di esempio open source di Stripe. Queste sono implementazioni di riferimento pubblicate su github.com/stripe-samples: gli stessi modelli di codice che Stripe consiglia per le app di produzione. Studiare questi esempi ti darà un'idea di come funziona l'integrazione dei pagamenti, rendendo più veloce la tua implementazione e dandoti una base di riferimento per il debug se qualcosa va storto.

Prima di scrivere qualsiasi codice, dai un'occhiata a come gli ingegneri di Stripe mettono insieme un'integrazione di pagamento.

  1. Apri questo repository nel tuo browser: github.com/stripe-samples/accept-a-payment
  2. Vai alla cartella prebuilt-checkout-page e guarda due file:
    • Il server ( server/node/server.js) — circa 100 righe in totale. Il modello di base è:
				
					// The sample creates a Checkout Session on the server
app.post('/create-checkout-session', async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    line_items: [{ price: process.env.PRICE, quantity: 1 }],
    success_url: `${domainURL}/success.html?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${domainURL}/canceled.html`,
  });
  return res.redirect(303, session.url);
});

				
			
    • Il cliente — due parti diverse
      • Il comando per il pagamento è un singolo pulsante del modulo.
				
					<form  action="/it/create-checkout-session/" method="POST">
  <button id="submit">Buy</button>
</form>
				
			
      • Il file App.jsx principale deve gestire le risposte provenienti da Stripe.
				
					
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/success.html" element={<Success />} />
        <Route path="/canceled.html" element={<Canceled />} />
        <Route path="/" element={<Checkout />} />
      </Routes>
    </BrowserRouter>
  );
}
				
			

Questo è tutto il codice client-side per un pulsante di pagamento. Ecco come funziona:

  • la tua app manda una richiesta al tuo server →
  • il tuo server crea una sessione di checkout →
  • Stripe crea una pagina di pagamento ospitata →
  • l'utente viene reindirizzato lì →
  • dopo aver pagato, tornano alla tua app →
  • l'app indirizza l'utente nel modo giusto a seconda che l'operazione vada a buon fine o venga annullata

Notate tre cose su questa struttura:

  • La tua app non tocca mai i dati delle carte. La pagina di pagamento di Stripe raccoglie i dettagli del pagamento. Così sei sempre a posto con gli standard PCI, senza doverti preoccupare della sicurezza delle carte di credito.
  • Il server decide quanto far pagare. Il prodotto, il prezzo e la valuta sono impostati dal server e gli utenti non possono cambiarli.
  • L'URL di successo ha un ID di sessione. Questo permette alla tua pagina di successo di recuperare i dettagli del pagamento e mostrare una conferma corretta.

Questo è il tuo codice di riferimento. Se qualcosa non funziona quando crei la tua integrazione Stripe, puoi confrontare il tuo codice con questo esempio per capire dove sta il problema.

Anche se puoi aggiungere le tue chiavi al tuo .env in Codespace, aggiungi ciò che puoi a Codespace Secrets per il tuo repository. 

  1. Vai su GitHub e clicca sul tuo profilo nell'angolo in alto a destra.
  2. Clicca su Impostazioni
  3. Nel menu a sinistra, clicca su Codespaces.
  4. Clicca su "Nuovo segreto".
  5. Inserisci la tua STRIPE_SECRET_KEY, copiando la chiave che hai ricevuto da Stripe.
  6. Aggiungi un altro segreto per il tuo PRICE_ID. Assegnagli un nome descrittivo. Ad esempio, per Tutormatch, il nome del nostro segreto è STRIPE_PRICE_ID_TUTOR.
  1. Vai sul tuo repository Github e crea un nuovo ramo. Ricorda, ora che hai fatto il deployment tramite Vercel, è meglio fare qualsiasi nuovo sviluppo su un ramo separato dal ramo principale.
  2. Apri il tuo Codespace (o creane uno nuovo) e passa al tuo nuovo ramo.

Quando hai distribuito la tua app, hai aggiunto un file file vite.config.js per consentire lo sviluppo locale con un server sia frontend che backend. Ora che siamo passati a un’architettura serverless con Vercel, rimuoveremo il codice proxy in vite.config.js.

Aggiorna il file. Ecco tutto il codice che ti serve adesso:

				
					import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({ 
    plugins: [react()], 
})
				
			

Ci sono alcune variabili d'ambiente che ti serviranno per il pagamento. Stripe deve conoscere l'URL a cui tornare una volta che il pagamento è andato a buon fine. Si tratta del nostro URL principale del frontend.

  1. Installa Vercel per poter utilizzare l'ambiente di sviluppo Vercel. Nella finestra del terminale, digita:
    npm install -g vercel
    vercel link
  2. Ti verrà chiesto di collegare il tuo account GitHub e il tuo repository.
    Rispondi “sì” per collegare il progetto e importare le variabili d’ambiente.
  3. Potrebbe anche essere necessario eseguire il comando ` npm install ` per reinstallare i moduli Node nel tuo codespace.
  4. nel tuo root .env Nel file, aggiungi questa riga:
    FRONTEND_URL=il_tuo_URL_GitHub
    Possiamo usare il file .env di root dato che siamo passati all’architettura serverless di Vercel. Esegui l’app per recuperare l’URL di GitHub da inserire qui.

    IMPORTANTE: Rimuovi eventuali / alla fine dell'URL!

  5. Anche se abbiamo già aggiunto la chiave Stripe e il price_id nei segreti di Codespace, aggiungiamoli anche a .env e anche questo, per fare qualche prova. Aggiungi anche
    STRIPE_SECRET_KEY=la_tua_chiave_segreta_stripe
    STRIPE_PRICE_ID_TUTOR=il_tuo_ID_prezzo_di_pagamento 
    a .env. Assegna un nome appropriato al tuo PRICE_ID.

    Puoi tranquillamente aggiungere la tua chiave Stripe e il tuo price_id alla directory principale .env. Finché la variabile non contiene VITE_, rimane protetta.

    Assicurati che i valori dell'ambiente corrispondano tra Codespace Secrets e .env. Codespace Secrets ha la precedenza su .env. Se aggiorni una chiave in un posto, aggiornala anche nell'altro: i valori non corrispondenti sono spesso causa di errori difficili da risolvere.

Per assicurarci che l'app indirizzi l'utente alla pagina giusta, a seconda che abbia effettuato l'accesso, che abbia già pagato e che il pagamento sia andato a buon fine o meno, useremo la libreria Router. 

  1. Nella finestra del terminale, digita npm install react-router-dom

Si tratta di un argomento piuttosto complesso, quindi procederemo passo dopo passo, chiedendo in particolare a Copilot (o a un altro LLM) di generare il codice per te. 

  1. Chiedi all'LLM di aggiornare il backend api/index.js (o qualunque sia il nome del file del tuo server backend). Ricorda, quando passeremo all’ambiente di produzione, sposteremo qui il codice del backend.
    Chiederemo all’LLM di scrivere la richiesta POST a Stripe, fornendogli le specifiche relative alla tua struttura di pagamento specifica.

Aggiungi un endpoint di pagamento Stripe al mio server Express nel file api/index.js.

  • Usa la chiave STRIPE_SECRET_KEY dal file .env
  • Crea una route POST /api/create-checkout-session.
  • Dovrebbe usare un priceid, utilizzando STRIPE_PRICE_ID_TUTOR dal file .env
  • Usa `stripe.checkout.sessions.create` con:
    • line_items: un array contenente STRIPE_PRICE_ID_TUTOR da .env e quantità: 1.
    • modalità: imposta questa opzione su “pagamento” (visto che si tratta di una lezione privata una tantum).
    • success_url: Construct the base URL using:
      const baseURL = process.env.FRONTEND_URL || `https://${process.env.VERCEL_URL}`
      Then use baseURL + ‘/success’ for the success_url
    • cancel_url: Usa baseURL (lo stesso URL di base, senza percorso)
  •  Restituisci l'URL della sessione in formato JSON, così il frontend potrà reindirizzare l'utente. Per ora non modificare il codice del frontend.

Nota: non complicarti troppo la vita: per ora niente database né codice Supabase.

Importante: NON includere i tipi di metodo di pagamento (lascia che sia Stripe a sceglierli dinamicamente)

L'LLM dovrebbe aggiornare il codice in index.js. 

  1. Controlla le modifiche e accettale (oppure chiedi di apportare modifiche o chiarimenti, se necessario).
  2. Chiedi all'LLM di aggiornare il file principale del frontend, App.jsx, per gestire i pagamenti.
  3. Copilot potrebbe farlo al posto tuo, ma devi installare Stripe con il comando ` npm install stripe`.

Nota: Guarda come abbiamo chiesto a Copilot di creare un baseURL per gli URL di reindirizzamento in caso di successo e di annullamento. Abbiamo specificato di usare FRONTEND_URL o process.env.VERCEL_URL. FRONTEND_URL è la nostra variabile d’ambiente e qui su Codespaces la usiamo per i test. Quando passeremo all’ambiente di produzione, il reindirizzamento dovrà puntare all’URL della nostra app su Vercel. Vercel crea automaticamente quella variabile. Dobbiamo solo aggiungerla nel nostro codice in modo che funzioni sia in fase di test che in produzione.

Controlla il tuo file index.js per assicurarti che contenga FRONTEND_URL e VERCEL_URL.

L'LLM dovrebbe aggiornare il codice in index.js. 

  1. Controlla le modifiche e accettale (oppure chiedi di apportare modifiche o chiarimenti, se necessario).
  2. Chiedi all'LLM di aggiornare il file principale del frontend, App.jsx, per gestire i pagamenti.
  3. Copilot potrebbe farlo al posto tuo, ma devi installare Stripe con il comando ` npm install stripe`.

Aggiorna il mio file App.jsx per gestire i pagamenti. Per favore, aggiungi quanto segue:

  • Aggiorna il mio file App.jsx per gestire il routing di base e il trigger di pagamento.
  • Configura due route: / (Home) e /success (pagina di conferma).
  • Nella pagina /, se un utente ha effettuato l'accesso, mostra il pulsante "Prenota una sessione ($60)".
  • Crea una funzione `handlePay` per quel pulsante. Dovrebbe:
    • Recupera l'URL di Stripe dal mio backend (/api/create-checkout-session).
    • Reindirizza all'URL restituito dal server.
  • Se l'utente non ha effettuato l'accesso, mostra il modulo di autenticazione predefinito.
  1. Chiedi all'LLM di creare le pagine "Success" e "Cancel" al ritorno da Stripe.

Crea una semplice pagina React chiamata Success.jsx e impostala come route di successo.

  • Dovrebbe comparire il messaggio: “Pagamento effettuato con successo!”.
  • Aggiungi un breve messaggio: “Grazie per aver prenotato la tua lezione di tutoraggio. Ti invieremo i dettagli via e-mail a breve.”
  • Aggiungi un pulsante “Torna alla dashboard” che utilizzi useNavigate di react-router-dom per riportare l’utente alla route /

Crea una semplice pagina React chiamata Cancel.jsx.

  • Dovrebbe comparire un messaggio del tipo: “Pagamento annullato. L'addebito sulla tua carta non è stato effettuato”.
  • Aggiungi un pulsante "Riprova" che utilizzi useNavigate per riportare l'utente alla dashboard principale (/).
  • Cerca di mantenere uno stile accogliente, così l'utente non avrà l'impressione di aver fatto qualcosa di sbagliato.
  1. Avvia la tua app con `vercel dev ` nella finestra del terminale. Questo sostituisce i precedenti comandi ` npm run dev `, eseguendo sia il frontend che il backend insieme su un'unica porta.
  2. Clicca sul pulsante “Paga ora”. Dovresti essere reindirizzato a una pagina di pagamento ospitata da Stripe che mostra il nome e il prezzo del prodotto.
  3. Usa la carta di prova di Stripe per completare il pagamento:
    • Numero della carta: 4242 4242 4242 4242
    • Data di scadenza: qualsiasi data futura (ad es., 30/12)
    • CVC: 3 cifre qualsiasi (ad es. 123)
    • Nome: Qualsiasi nome
  4. Dopo aver inviato il pagamento, dovresti essere reindirizzato alla pagina di conferma che ti ringrazia per il pagamento.

  5. Per verificare che il pagamento sia andato a buon fine, vai sulla dashboard di Stripe e clicca su "Pagamenti". Dovresti vedere il tuo pagamento di prova elencato lì.

  6. Prova di nuovo. Stavolta, annulla il pagamento mentre sei sulla pagina di pagamento di Stripe. 
  7. Dovrebbe reindirizzarti alla pagina di annullamento, permettendoti di riprovare. 

Una volta che hai verificato che il processo di pagamento funzioni correttamente, salva le modifiche: è un buon punto di controllo prima di aggiungere i webhook.

Puoi eseguire il commit tramite il pannello " Controllo del codice sorgente " (clicca sull'icona "Controllo del codice sorgente" nella barra laterale sinistra, aggiungi un messaggio di commit e clicca sul segno di spunta) oppure tramite il terminale:

Webhook

Il tuo processo di pagamento funziona e la pagina di conferma mostra i dettagli del pagamento. Ma ecco il problema: la pagina di conferma non è un modo affidabile per confermare il pagamento.

Pensa a cosa potrebbe andare storto:

  • L'utente chiude il browser prima che si carichi la pagina di conferma
  • La connessione si interrompe durante il reindirizzamento
  • L'utente esce dalla pagina di conferma

In tutti questi casi, Stripe ha elaborato il pagamento, ma la tua app non ne ha mai saputo nulla. L'utente ha pagato, ma il tuo server non ne ha traccia.

Webhook risolvono il problema. Un webhook è un messaggio che Stripe invia direttamente al tuo server quando succede qualcosa, ad esempio quando viene completato un pagamento. Il tuo server non deve chiedere a Stripe “hanno pagato?”: è Stripe a comunicarlo al tuo server in modo proattivo.

Capire il funzionamento dei webhook

È fondamentale prevenire le false conferme di pagamento. Senza la verifica tramite webhook, qualcuno potrebbe inviare una richiesta al tuo server sostenendo che è stato effettuato un pagamento quando in realtà non è così.

Ecco come funzionano i webhook nel tuo flusso di pagamento:

L'utente clicca su “Paga” → Stripe Checkout → L'utente paga → Succedono due cose:

  1. L'utente vede la pagina di conferma (non affidabile)
  2. Stripe invia un webhook al tuo server (affidabile)

Il tuo server riceve il webhook indipendentemente da ciò che fa l'utente con il proprio browser. Ecco perché i webhook sono la fonte affidabile di riferimento per la conferma del pagamento.

ATTIVITÀ 3

Crea un gestore di webhook

Tempo stimato: 30 minuti

Creerai un endpoint sul tuo server Express che riceve le conferme di pagamento da Stripe, ne verificherai l'autenticità e testerai sia gli scenari di pagamento andati a buon fine che quelli falliti.

Devi aggiungere l'endpoint del webhook al tuo server backend. L'endpoint del webhook ha un requisito specifico: ha bisogno del corpo della richiesta in formato grezzo per la verifica della firma. Se Express analizza prima il corpo come JSON, la verifica della firma fallirà. 

Importante: se nel tuo server è presente app.use(express.json()), la route del webhook deve essere definita prima di quella riga. Altrimenti, Express analizzerà il corpo della richiesta come JSON prima che il gestore del webhook lo veda, e la verifica della firma fallirà.

  1. Apri il pannello della chat di Copilot e chiedi:

Aggiungi un endpoint webhook di Stripe al mio server Express nel file api/index.js.

Requisiti:

– Endpoint POST all'indirizzo /webhook

– Usa il middleware express.raw() solo per questa route (non express.json())

– Verifica la firma del webhook usando STRIPE_WEBHOOK_SECRET

– Gestisci gli eventi checkout.session.completed e checkout.session.expired

– Registra i dettagli del pagamento nel log della console (ID sessione, importo, email del cliente)

– Rispondi con 200 per confermare la ricezione

– Importante: questo percorso deve utilizzare l'analisi del corpo del messaggio in formato grezzo, non l'analisi JSON

  1. Controlla il tuo file index.js e, se necessario, riorganizza l'ordine. La tua route per il webhook dovrebbe essere la prima.
				
					// 1. Webhook route FIRST (needs raw body)
app.post('/webhook', express.raw({ type: 'application/json' }), webhookHandler);

// 2. JSON parsing for all other routes AFTER
app.use(express.json());

// 3. Your other routes
app.post('/create-checkout-session', ...);
app.get('/success', ...);
				
			

Eseguire un webhook da Codespaces non è proprio una passeggiata. Devi rendere pubblica la porta del tuo server e, anche così, a volte possono comunque verificarsi degli errori. Per semplificare le cose durante i test, useremo la CLI (interfaccia a riga di comando) di Stripe. Questa crea un tunnel tra la tua app e Stripe per consentire una comunicazione diretta.

  1. Installa Stripe in un nuovo terminale di Codespaces
    brew install stripe/stripe-cli/stripe
    o

    Se non funziona (metodo manuale):

    • Aggiungi la chiave: curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg –dearmor | sudo tee /usr/share/keyrings/stripe.gpg > /dev/null

    • Aggiungi il repository: echo “deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main” | sudo tee /etc/apt/sources.list.d/stripe.list

    • Installazione: sudo apt update && sudo apt install stripe

  2. Accedi. Digita
    stripe login

  3. Ti fornirà un codice di accoppiamento e un link. Clicca sul link, accedi a Stripe e autorizza la connessione

  4. Controlla su quale porta è in esecuzione l'applicazione. Vai alla scheda "Porte" e controlla quella indicata per la tua app. Probabilmente è la 3000, ma potrebbe essere una porta diversa. 
  5. Inizia ad ascoltare. Digita:
    stripe listen –forward-to localhost:[YOUR_PORT]/api/webhook

  6. Una volta che inizia ad ascoltare, vedrai un messaggio: "
    , pronto! Il tuo segreto di firma per il webhook è whsec_xxxxxxxx"

  7. Copia il segreto di firma e incollalo subito nel tuo file .env , al posto di STRIPE_WEBHOOK_SECRET.

  8. Riavvia l'app affinché le modifiche abbiano effetto.
  1. Prova a cliccare sul pulsante “Paga ora” nella tua app.
  2. Effettua il pagamento con la carta di prova 4242 4242 4242 4242.
  3. Controlla il log della console nel tuo terminale Codespace: dovresti vedere:
    Pagamento confermato!
    ID sessione: cs_test_…
    Importo: 1500
    Email del cliente: [email protected]
  4. Se non compare, controlla se ci sono errori e chiedi a Copilot di verificare che il codice console.log ci sia e funzioni.

A volte capita che le carte degli utenti reali vengano rifiutate. Testare questo scenario ti aiuta a capire cosa succede quando un pagamento non va a buon fine.

  1. Ripeti la procedura di pagamento, ma questa volta usa la carta di prova non valida:
    • Numero della carta: 4000 0000 0000 0002
    • Altri valori: gli stessi previsti per i pagamenti validi

Stripe mostrerà all'utente il messaggio "La tua carta è stata rifiutata" nella pagina di pagamento. L'utente rimane sulla pagina di pagamento di Stripe e può provare con un'altra carta.

Nota cosa succede sul tuo server: non viene attivato nessun webhook. L'evento `checkout.session.completed ` si attiva solo quando il pagamento va a buon fine. Questo è il comportamento corretto: il tuo server deve intervenire solo in caso di pagamenti andati a buon fine

Esegui il commit tramite il pannello di controllo del codice sorgente o dal terminale. In questo modo ogni fase dell'implementazione del pagamento viene salvata in commit separati.

IMPORTANTE: Per i test su Codespaces abbiamo usato la CLI di Stripe. Quando passerai all'app di produzione, dovrai configurare il webhook nella dashboard di Stripe. Ti verrà assegnata una chiave di firma segreta diversa, che dovrai aggiungere ai segreti della tua piattaforma di produzione, e reindirizzare il webhook al tuo URL di produzione. Di seguito trovi le istruzioni.

  1. Vai alla tua dashboard di Stripe.
  2. Cerca " Webhooks" nella barra di ricerca in alto. Clicca su "Webhooks". Dovresti essere reindirizzato al Workbench.
  3. All'interno di Workbench, vai alla scheda " Webhooks ".
  4. Clicca sul + Aggiungi destinazione pulsante.
    • Eventi da: Cerca " Checkout " nel menu a tendina
    • Seleziona gli eventi: scegli checkout.session.completed e checkout.session.expired
    • Tipo di destinazione: Seleziona " Endpoint Webhook".
    • URL dell'endpoint: incolla il tuo URL di produzione (ad es., https://your-vercel.app/api/webhook).
  5. Dopo aver creato l'endpoint, clicca su "Reveal " sotto " Signing secret " per ottenere il tuo segreto del webhook (che inizia con whsec_)
  6. Aggiungi la chiave ai tuoi "Secrets" di produzione (la procedura è la stessa che hai seguito per le chiavi API di Stripe):
    • Nome: STRIPE_WEBHOOK_SECRET
    • Valore: incolla la tua chiave segreta whsec_…

Non devi modificare nulla del tuo codice.

Fatturazione degli abbonamenti

Non tutte le aziende addebitano un unico importo. Se la tua strategia di prezzo prevede entrate ricorrenti — abbonamenti mensili, piani annuali o quote associative — allora hai bisogno della fatturazione a abbonamento.

La buona notizia è che passare dai pagamenti una tantum agli abbonamenti su Stripe richiede solo qualche piccola modifica al codice che hai già scritto. L'esperienza di pagamento per l'utente rimane praticamente identica. In questa parte vedrai anche come l'app di esempio sugli abbonamenti di Stripe presenta le opzioni di prezzo e gestisce gli abbonamenti.

Nella prossima attività imparerai come aggiungere un'opzione di pagamento in abbonamento nella tua app. Manterremo l'opzione di pagamento singolo, nel nostro caso per una lezione di ripetizioni, ma aggiungeremo un secondo tipo di pagamento per un abbonamento annuale alle funzionalità dell'app.

ATTIVITÀ 4

Aggiungi pagamento dell'abbonamento

Tempo stimato: 45 minuti

Aggiungerai un secondo tipo di pagamento, un abbonamento ricorrente, nella tua app. Una volta completata l'operazione, aggiornerai il tuo database Supabase per contrassegnare quell'utente come pagante.

  • Apri questo repository nel tuo browser: github.com/stripe-samples/checkout-single-subscription

    Il codice del server (circa 120 righe) è molto simile a quello che hai creato nell'Attività 1. Le differenze principali sono:
    • la modalità è "abbonamento" anziché " pagamento"
    • L'esempio include anche un endpoint del Portale Clienti: questo permette agli abbonati di gestire il proprio abbonamento (annullare, cambiare piano) senza doverti contattare.

Aggiungi un endpoint webhook di Stripe al mio server Express.

Requisiti:

– Endpoint POST all'indirizzo /webhook

– Usa il middleware express.raw() solo per questa route (non express.json())

– Verifica la firma del webhook usando STRIPE_WEBHOOK_SECRET

– Gestisci gli eventi checkout.session.completed e checkout.session.expired

– Registra i dettagli del pagamento nel log della console (ID sessione, importo, email del cliente)

– Rispondi con 200 per confermare la ricezione

– Importante: questo percorso deve utilizzare l'analisi del corpo del messaggio in formato grezzo, non l'analisi JSON

  1. Controlla il tuo file server.js e, se necessario, modifica l'ordine. La tua route per il webhook dovrebbe essere la prima.
				
					// 1. Webhook route FIRST (needs raw body)
app.post('/webhook', express.raw({ type: 'application/json' }), webhookHandler);

// 2. JSON parsing for all other routes AFTER
app.use(express.json());

// 3. Your other routes
app.post('/create-checkout-session', ...);
app.get('/success', ...);
				
			
  1. Se non l'hai ancora fatto, aggiungi un prodotto in abbonamento al tuo account Stripe.
    1. Vai al Catalogo prodotti dal menu principale.

    2. Clicca su "Crea un prodotto"
      • Dai un nome al tuo prodotto 
      • Aggiungi una descrizione
      • Scegli " ricorrente"
      • Inserisci un prezzo e scegli la valuta
    3. Aggiungi il prodotto.
    4. Quando il prodotto compare nell'elenco, cliccaci sopra per visualizzarlo.
    5. Sulla riga del prezzo, clicca sui 3 puntini e seleziona “Copia ID prezzo”.
  1. Torna al tuo account GitHub e aggiungi un nuovo segreto per Codespace.
    1. Dal tuo profilo, clicca su Impostazioni → Codespaces.
    2. Aggiungi un nuovo segreto e chiamalo STRIPE_PRICE_ID_SUBSCRIPTION o con un altro nome appropriato.
    3. Inserisci l'ID del prezzo che hai copiato da Stripe.
    4. Vai anche sul tuo Codespace e aggiungilo al file .env . Questo backup ti evita di dover ricompilare il Codespace per integrare il nuovo segreto del Codespace. Vedi la nota qui sotto.

NOTA: Potrebbe essere necessario ricompilare il container di Codespace affinché riconosca il nuovo segreto. Puoi farlo da Codespace, premendo F1 per aprire la Command Palette e cercando "Codespaces: Rebuild Container". Puoi anche aggiungerlo al file server/.env come misura di sicurezza, così non dovrai ricompilare il container per questa sessione.

Se vogliamo che gli utenti paghino per usare la nostra app, vogliamo tenerne traccia su Supabase. Al momento, gli utenti possono registrarsi tramite l'autenticazione di Supabase e l'accesso con Google.

Non possiamo modificare il processo di autenticazione degli utenti di Supabase, quindi aggiungeremo una nuova tabella "profiles" per conservare i dati di pagamento.

  1. Vai al tuo progetto Supabase → Autenticazione → Utenti.
    Vedrai tutti gli utenti che si sono registrati alla tua app. 
  2. Vai all 'Editor SQL dal menu principale.
  3. Incolla questo comando SQL per creare la tabella dei profili. Si collega alla tabella di autenticazione degli utenti tramite l'ID utente presente in quella tabella.
				
					create table profiles (
  id uuid references auth.users not null primary key,
  email text,
  is_paid boolean default false
);
				
			

Bisogna creare la tabella dei profili.

  1. Vai all'Editor delle tabelle: dovresti vedere una tabella dei profili vuota.
    Se hai già degli utenti, dobbiamo eseguire un comando per aggiungere quegli utenti esistenti alla tabella dei profili.
  2. Torna all 'editor SQL ed esegui questo comando.
				
					INSERT INTO public.profiles (id, email)
SELECT id, email 
FROM auth.users
ON CONFLICT (id) DO NOTHING;
				
			
  1. Torna all 'Editor delle tabelle.
  2. Controlla che tutti gli utenti siano stati aggiunti alla tabella dei profili.
  3. Cerca in alto sullo schermo un pulsante con la scritta Politica RLS o RLS disattivato.
    • Se c'è scritto " RLS disabilitato", clicca sul pulsante e seleziona "Abilita RLS per questa tabella". 
    • Se c'è scritto " Criterio RLS", cliccaci sopra. Dovrebbe comparire la scritta: "Gli utenti possono aggiornare il proprio profilo".
supabase screen with RLS disabled

Supabase deve sapere che, quando si registra un nuovo utente, deve creare una nuova riga nella tabella dei profili. Puoi farlo impostando un trigger in Supabase.

  1. Vai all'editor SQL in Supabase e incolla il codice seguente.
				
					create or replace function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id, email)
  values (new.id, new.email);
  return new;
end;
$$ language plpgsql security definer;

create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();
				
			

Bisogna creare il trigger.

  1. Per verificare che ci sia, vai su Database → Funzioni. Dovresti vedere handle_new_user nell'elenco .

L'autenticazione utente con Supabase è stata eseguita dal frontend e ha utilizzato la chiave pubblicabile. L'aggiornamento della tabella dei profili avverrà dal backend e per farlo useremo la chiave segreta di Supabase. Questo significa che dobbiamo aggiungerla alla nostra app come variabile d'ambiente.

  1. Vai al tuo progetto Supabase e clicca su " Impostazioni progetto".
  2. Clicca su " Chiavi API".
  3. Scorri verso il basso e trova la chiave segreta. Copiala.
  4. Nella tua dashboard di GitHub, vai su Profilo → Codespaces. 
  5. Aggiungi un nuovo segreto, chiamaloSUPABASE_SERVICE_ROLE_KEY e incolla la chiave come valore.
  6. Ora aggiungi il SUPABASE_URL come un altro segreto di Codespace. Potresti averlo già nella tua directory principale .env file, ma puoi ritrovarlo nella dashboard di Supabase.
    1. Vai su Impostazioni progetto → API dati. 
    2. Copia l'URL dell'API e incollalo come valore segreto nel tuo Codespace.
  7. Per evitare di dover ricreare il tuo container Codespace (vedi nota sotto), aggiungi anche queste due variabili al tuo file root .env in Codespace.

NOTA: Potrebbe essere necessario ricompilare il container di Codespace affinché riconosca eventuali nuovi segreti. Puoi farlo da Codespace, premendo F1 per aprire la Paletta dei comandi e cercando “Codespaces: Rebuild Container”. Puoi anche aggiungerlo al file server/.env come misura di sicurezza, così non dovrai ricompilare il container per questa sessione.

Procederemo passo dopo passo per far sì che Copilot aggiunga il codice necessario.

  1. Torna al tuo ambiente Codespace. Assicurati che gli aggiornamenti precedenti siano stati salvati, in modo da partire da un commit nuovo.
  2. Chiedi a Copilot di aggiornare il server backend. Ecco un esempio di richiesta:

Mantieni l'endpoint di pagamento Stripe già esistente per le lezioni private e aggiungine uno nuovo per l'abbonamento nel file api/index.js.

La modalità è in abbonamento e il prezzo è STRIPE_PRICE_ID_SUBSCRIPTION

Copilot dovrebbe generare un nuovo file app/post in api/index.js. Clicca qui sotto per vedere il codice che ha generato per Tutormatch.

				
					app.post('/api/create-subscription-session', async (req, res) => {
  try {
    const priceId = process.env.STRIPE_PRICE_ID_SUBSCRIPTION;
    if (!priceId) {
      return res.status(400).json({ error: 'Subscription Price ID not configured' });
    }
    if (!process.env.FRONTEND_URL) {
      return res.status(500).json({ error: 'FRONTEND_URL not configured' });
    }
    const session = await stripe.checkout.sessions.create({
      line_items: [
        {
          price: priceId,
          quantity: 1,
        },
      ],
      mode: 'subscription',
      success_url: process.env.FRONTEND_URL + '/success',
      cancel_url: process.env.FRONTEND_URL,
    });
    res.json({ url: session.url });
  } catch (error) {
    console.error('Stripe subscription error:', error);
    res.status(500).json({ error: error.message });
  }
});
				
			
  1. Chiedi a Copilot di aggiungere l'opzione di abbonamento al gestore del webhook, in modo che aggiorni Supabase per un abbonamento a pagamento.

    Ecco un esempio di prompt:

Aggiungilo al mio gestore di webhook in modo che gestisca la modalità di pagamento come fa adesso.

Se sei in modalità abbonamento, aggiorna la tabella dei profili in Supabase per quell'utente. Imposta is_paid su true. SUPABASE_URL è l'URL del progetto e la chiave segreta è SUPABASE_SERVICE_ROLE_KEY

Gestisci anche l'evento `subscription.deleted` e aggiorna i profili di quell'utente in modo che `is_paid` sia falso.

Clicca qui sotto per vedere il codice del gestore del webhook di Tutormatch. Tieni presente che c'è un bel po' di codice `console.log`, per assicurarci che il webhook funzioni e che i pagamenti vengano ricevuti.

				
					app.post('/api/webhook', express.raw({ type: 'application/json' }), (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;

    try {
        // This verifies the message actually came from Stripe
        event = stripe.webhooks.constructEvent(
            req.body, 
            sig, 
            process.env.STRIPE_WEBHOOK_SECRET // This is your whsec_ key
        );
    } catch (err) {
        console.error(`Webhook Error: ${err.message}`);
        return res.status(400).send(`Webhook Error: ${err.message}`);
    }

    // 2. HANDLE THE EVENT
      if (event.type === 'checkout.session.completed') {
          const session = event.data.object;
          const mode = session.mode;
          const email = session.customer_details?.email;

          console.log('Session mode:', mode);
          console.log('Session email:', email);
          console.log('Session details:', session);

          if (mode === 'payment') {
              console.log('Payment Successful!');
              console.log('Customer Email:', email);
              console.log('Amount Paid:', session.amount_total / 100); // Stripe uses cents
              // This is where you would update your database (e.g., User.paid = true)
          } else if (mode === 'subscription') {
              console.log('Subscription Successful!');
              console.log('Customer Email:', email);
              // Update Supabase profiles table: set is_paid true for this user
              if (email) {
                  const supabaseUrl = `${process.env.SUPABASE_URL}/rest/v1/profiles?email=eq.${encodeURIComponent(email)}`;
                  const supabaseHeaders = {
                      'Content-Type': 'application/json',
                      'apikey': process.env.SUPABASE_SERVICE_ROLE_KEY,
                      'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`,
                  };
                  const supabaseBody = JSON.stringify({ is_paid: true });
                  console.log('Supabase PATCH URL:', supabaseUrl);
                  console.log('Supabase PATCH Headers:', supabaseHeaders);
                  console.log('Supabase PATCH Body:', supabaseBody);
                  fetch(supabaseUrl, {
                      method: 'PATCH',
                      headers: supabaseHeaders,
                      body: supabaseBody
                  })
                  .then(res => {
                      console.log('Supabase PATCH response status:', res.status);
                      return res.json();
                  })
                  .then(data => {
                      console.log('Supabase update (is_paid true):', data);
                  })
                  .catch(err => {
                      console.error('Supabase update error:', err);
                  });
              } else {
                  console.log('No email found for subscription session. Supabase not updated.');
              }
          }
      } else if (event.type === 'checkout.session.expired') {
          const session = event.data.object;
          console.log('Session expired. Releasing the tutor slot for:', session.id);
      } else if (event.type === 'subscription.deleted') {
          const subscription = event.data.object;
          const email = subscription.customer_email || subscription.customer?.email;
          console.log('Subscription deleted. Email:', email);
          console.log('Subscription details:', subscription);
          // Update Supabase profiles table: set is_paid false for this user
          if (email) {
              const supabaseUrl = `${process.env.SUPABASE_URL}/rest/v1/profiles?email=eq.${encodeURIComponent(email)}`;
              const supabaseHeaders = {
                  'Content-Type': 'application/json',
                  'apikey': process.env.SUPABASE_SERVICE_ROLE_KEY,
                  'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`,
              };
              const supabaseBody = JSON.stringify({ is_paid: false });
              console.log('Supabase PATCH URL:', supabaseUrl);
              console.log('Supabase PATCH Headers:', supabaseHeaders);
              console.log('Supabase PATCH Body:', supabaseBody);
              fetch(supabaseUrl, {
                  method: 'PATCH',
                  headers: supabaseHeaders,
                  body: supabaseBody
              })
              .then(res => {
                  console.log('Supabase PATCH response status:', res.status);
                  return res.json();
              })
              .then(data => {
                  console.log('Supabase update (is_paid false):', data);
              })
              .catch(err => {
                  console.error('Supabase update error:', err);
              });
          } else {
              console.log('No email found for deleted subscription. Supabase not updated.');
          }
      }

    // Return a 200 response to acknowledge receipt
    res.json({ received: true });
});
				
			

Passiamo ora al frontend. Abbiamo diviso la schermata in due parti. Prima aggiungiamo il pulsante per il pagamento dell’abbonamento, poi aggiungiamo la logica per attivare/disattivare le funzionalità a seconda che l’abbonamento sia stato pagato o meno.

  1. Chiedi a Copilot di aggiungere un nuovo pulsante per il pagamento dell'abbonamento. Ecco un esempio di richiesta.

Nel frontend, nel file App.jsx, aggiungi un nuovo pulsante: “Abbonati a 9,99 $ all’anno”.
Cliccando sul pulsante dovrebbe essere inviato il POST create_subscription_session.

  1. Chiedi a Copilot di aggiungere la logica per disattivare le funzionalità se l'utente non è in regola con i pagamenti e di attivarle se invece lo è.

I pulsanti "Chiedi consiglio" e "Trova un tutor" devono essere disabilitati se la variabile `is_paid` dell'utente è `false`, e abilitati se è `true`.
Aggiungi un'etichetta informativa – "Sblocca queste funzionalità abbonandoti" – e posizionala sopra i due pulsanti delle funzionalità.

Clicca qui sotto per vedere il codice frontend di Tutormatch.

				
					function Home({ user, setUser }) {
  const [screen, setScreen] = useState('advice');
  const [isPaid, setIsPaid] = useState(false);
  const navigate = useNavigate();

  useEffect(() => {
    async function fetchProfile() {
      if (user && user.email) {
        try {
          const res = await fetch(`${import.meta.env.VITE_SUPABASE_URL}/rest/v1/profiles?email=eq.${encodeURIComponent(user.email)}`, {
            headers: {
              'apikey': import.meta.env.VITE_SUPABASE_KEY,
              'Authorization': `Bearer ${import.meta.env.VITE_SUPABASE_KEY}`,
            },
          });
          if (res.ok) {
            const data = await res.json();
            // Supabase may return booleans or truthy strings; normalize to boolean
            setIsPaid(Boolean(data[0]?.is_paid));
          } else {
            setIsPaid(false);
          }
        } catch {
          setIsPaid(false);
        }
      } else {
        setIsPaid(false);
      }
    }
    fetchProfile();
  }, [user]);

  const handlePay = async () => {
    try {
      const res = await fetch('/api/create-checkout-session', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
      });
      if (!res.ok) {
        const text = await res.text();
        alert('Payment error: ' + text);
        return;
      }
      const data = await res.json();
      if (data.url) {
        window.location.href = data.url;
      } else {
        alert('Could not start payment session.');
      }
    } catch (err) {
      alert('Payment error: ' + err.message);
    }
  };

    const handleSubscribe = async () => {
      try {
        const res = await fetch('/api/create-subscription-session', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
        });
        if (!res.ok) {
          const text = await res.text();
          alert('Subscription error: ' + text);
          return;
        }
        const data = await res.json();
        if (data.url) {
          window.location.href = data.url;
        } else {
          alert('Could not start subscription session.');
        }
      } catch (err) {
        alert('Subscription error: ' + err.message);
      }
    };

  const handleLogout = async () => {
    await supabase.auth.signOut();
    setUser(null);
  };

  return (
    <div className="app">
      <header className="header">
        <h1 className="title">TutorMatch</h1>
        <p className="subtitle">Get Personalized Tutor Advice</p>
      </header>
      {!user ? (
        <AuthForm onLogin={() => {
          supabase.auth.getSession().then(({ data }) => {
            setUser(data?.session?.user || null);
          });
        }} />
      ) : (
        <>
          <button onClick={handlePay} style={{ margin: '1rem', padding: '0.75rem 1.5rem', fontSize: '1.1rem', background: '#635bff', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>
            Book a Session ($60)
          </button>
          <button onClick={handleSubscribe} style={{ margin: '1rem', padding: '0.75rem 1.5rem', fontSize: '1.1rem', background: '#00b386', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>
            Subscribe for $9.99/year
          </button>
          {!isPaid && (
            <div style={{ margin: '1.5rem 0 0.5rem 0', color: '#b00', fontWeight: 500, fontSize: '1.05rem', textAlign: 'center' }}>
              Unlock these features by subscribing
            </div>
          )}
          <NavTabs
            screen={screen}
            setScreen={setScreen}
            onLogout={handleLogout}
            adviceDisabled={!isPaid}
            findTutorDisabled={!isPaid}
          />
          <main className="container">
            {screen === 'advice' ? <GetAdvice disabled={!isPaid} /> : <FindTutor disabled={!isPaid} />}
          </main>
        </>
      )}
    </div>
  );
}


function App() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const { subscription } = supabase.auth.onAuthStateChange((event, session) => {
      setUser(session?.user || null);
    });
    supabase.auth.getSession().then(({ data }) => {
      setUser(data?.session?.user || null);
    });
    return () => {
      subscription?.unsubscribe && subscription.unsubscribe();
    };
  }, []);

  return (
    <Routes>
      <Route path="/" element={<Home user={user} setUser={setUser} />} />
      <Route path="/success" element={<Success />} />
      <Route path="/cancel" element={<Cancel />} />
    </Routes>
  );
}

export default App

				
			
  1. Prova del pagamento una tantum: clicca sull'opzione "una tantum", usa la carta di prova 4242 4242 4242 4242, completa il pagamento
  2. Abbonamento di prova: clicca sull'opzione di abbonamento, usa la stessa carta di prova, completa il pagamento
  3. Controlla il tuo terminale: il webhook dovrebbe registrare messaggi diversi per ogni modalità
  4. Nella dashboard di Stripe, vai su:
    • Pagamenti per visualizzare il tuo pagamento una tantum
    • Abbonamenti: per visualizzare il tuo nuovo abbonamento (questa sezione compare dopo aver creato un abbonamento)
  5. Vai su Supabase e controlla la tabella dei profili. Per l'utente che ha effettuato il pagamento, il campo `is_paid` dovrebbe essere impostato su `true`.
  6. Verifica nella tua app che un utente a pagamento abbia accesso alle funzionalità sbloccate.
  7. Esci e accedi di nuovo. Controlla se hanno ancora accesso a tutte le funzionalità.
  8. Esci e accedi come utente non a pagamento. Controlla se le funzionalità sono bloccate.

Quando sei sicuro che il tuo codice funzioni correttamente per entrambi i tipi di pagamento, salva le modifiche nel ramo del tuo repository.

A questo punto puoi tornare su GitHub e unire il tuo codice per l'elaborazione dei pagamenti al ramo principale. In questo modo le modifiche dovrebbero essere immediatamente pubblicate su Vercel.

Hai aggiunto diverse nuove variabili d'ambiente nei segreti di Codespace. Devi aggiungerle anche su Vercel, così continueranno a funzionare in produzione.

  1. Vai alla dashboard del tuo progetto Vercel.
  2. Clicca su Impostazioni → Variabili d'ambiente.
  3. Aggiungi le seguenti variabili d'ambiente con i valori corrispondenti:
    • STRIPE_SECRET_KEY
    • STRIPE_PRICE_ID_TUTOR (o come hai chiamato il tuo ID prezzo)
    • STRIPE_PRICE_ID_SUBSCRIPTION (o il nome del tuo ID prezzo)
    • STRIPE_WEBHOOK_SECRET
    • SUPABASE_URL
    • SUPABASE_SERVICE_ROLE_KEY

Non devi aggiungere FRONTEND_URL su Vercel: la variabile d'ambiente VERCEL_URL viene impostata automaticamente da Vercel e l'abbiamo già inserita nel codice delle nostre pagine "Success" e "Cancel" (Attività 2).

Se non l'hai già fatto nella lezione sull'implementazione, assicurati che il tuo URL Vercel sia stato aggiunto agli URL di reindirizzamento consentiti da Supabase — dai un'occhiata alla lezione sull'implementazione per le istruzioni.

Riflessione

La gestione dei pagamenti è un elemento fondamentale per il successo della tua attività, quindi è importante assicurarti che funzioni correttamente. Rifletti su queste domande:

Sunset and reflection over lake
01 Strategia di prezzo di

In che misura la tua strategia di prezzi originariamente prevista si è adattata all’implementazione su Stripe? Hai dovuto modificare qualcosa — prezzo, modello o struttura del piano — quando hai visto come funziona dal punto di vista tecnico?
02 Codice di esempio per "

"
Il codice di riferimento di Stripe ti è stato d'aiuto per capire e implementare il tutto? Useresti questo approccio "prima studia, poi realizza" anche per altre integrazioni?
03 Il modello di business di "

"
Ripensando al tuo Simple Budget, per la tua attività ha più senso un pagamento una tantum, un abbonamento o una combinazione dei due?

Termini chiave

  • Hosted Checkout — Una pagina di pagamento fornita da Stripe a cui la tua app reindirizza gli utenti, dove Stripe raccoglie i dettagli di pagamento in modo che la tua app non entri mai in contatto con i dati della carta.

  • Sessione di checkout — Un oggetto lato server che crei su Stripe e che definisce cosa addebitare, quanto e dove reindirizzare l'utente dopo il pagamento.

  • Webhook — Una richiesta HTTP che Stripe invia direttamente al tuo server quando si verifica un evento di pagamento, fornendo una conferma affidabile del pagamento indipendentemente da ciò che l'utente fa nel proprio browser.

  • express.raw() — Middleware Express che mantiene inalterato il corpo della richiesta, necessario affinché la verifica della firma del webhook funzioni correttamente.

  • Business Sandbox— Un ambiente Stripe in cui i pagamenti vengono simulati utilizzando numeri di carte di prova, in modo che non venga addebitato denaro reale durante lo sviluppo.