ZeroToShip

Docs/Componenti

// componente

Button Checkout

Il bottone che porta l'utente su Stripe Checkout. Crea la sessione lato server e poi fa un redirect completo del browser. È il bottone che <Pricing /> renderizza dentro ogni card di prezzo. Client component.

File

components/ButtonCheckout.tsx

Uso base

components/Pricing.tsx
tsx
import ButtonCheckout from "@/components/ButtonCheckout";

// pagamento una tantum
<ButtonCheckout priceId={plan.priceId} />

// abbonamento ricorrente, sul piano evidenziato
<ButtonCheckout
  priceId={plan.priceId}
  mode="subscription"
  featured={plan.isFeatured}
/>

Props

  • priceId: string — obbligatoria. È il price_... di Stripe. Nel boilerplate arriva da config.stripe.plans[].priceId.
  • mode?: "payment" | "subscription" — default "payment". Deve combaciare con il tipo di price che hai creato su Stripe: un price ricorrente con mode: "payment" fa fallire la sessione.
  • featured?: boolean — default false. Cambia solo lo stile: btn-brutal-mustard se true, btn-brutal-primary se false.

L'etichetta è “Ottieni {config.appName}”: prende il nome da config.ts, non c'è una prop per il testo.

Il giro completo

Al click il componente manda:

components/ButtonCheckout.tsx
ts
const { url }: { url: string } = await apiClient.post(
  "/stripe/create-checkout",
  {
    priceId,
    successUrl: window.location.origin + "/checkout-success",
    cancelUrl: window.location.href,
    mode,
  }
);

window.location.href = url;

Nota che successUrl e cancelUrl sono calcolati nel browser: il successo va sempre su /checkout-success, l'annullamento riporta esattamente sulla pagina da cui hai cliccato.

app/api/stripe/create-checkout/route.ts valida priceId, le due URL e mode (400 se manca qualcosa), legge l'utente Supabase dalla sessione, recupera la sua riga profiles e passa a createCheckout di libs/stripe.ts:

  • clientReferenceId: user?.id — così il webhook sa a chi assegnare l'acquisto;
  • user.email e user.customerId — per precompilare il Checkout e riusare la carta già salvata.

La route risponde { url } e il componente fa il redirect. Se qualcosa va storto, l'interceptor di libs/api.ts mostra il toast con il messaggio di errore; su 401 spedisce l'utente su config.auth.loginUrl.

Personalizzare

  • Pagina di successo: cambia "/checkout-success" nel componente se vuoi un'altra landing post-pagamento.
  • Coupon: la route ha già il punto pronto — decommenta couponId: body.couponId e passalo dal componente nel body della richiesta.
  • Login obbligatorio: di default un utente anonimo può comunque aprire il Checkout (senza clientReferenceId). Se vuoi forzare il login, aggiungi un 401 nella route quando user è nullo.
  • Etichetta e icona: il testo e il fulmine SVG sono inline nel componente.