Skip to content

Stripe Local Setup

Step-by-step guide to get Stripe payments working in your local environment.

  • Backend containers running (./vendor/bin/sail up -d)
  • Database migrated (./vendor/bin/sail artisan migrate)
  • A Stripe account in test mode

The Stripe CLI forwards webhook events from Stripe to your local backend.

Terminal window
# macOS
brew install stripe/stripe-cli/stripe
# Linux (Debian/Ubuntu)
curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg
echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | sudo tee -a /etc/apt/sources.list.d/stripe.list
sudo apt update && sudo apt install stripe

After installing, authenticate:

Terminal window
stripe login

Get your API keys from the Stripe Dashboard:

Terminal window
# Stripe API keys (test mode)
STRIPE_KEY=pk_test_... # Publishable key
STRIPE_SECRET=sk_test_... # Secret key
STRIPE_WEBHOOK_SECRET=whsec_... # From `stripe listen` output (step 3)
# Laravel Cashier settings
CASHIER_CURRENCY=eur
CASHIER_CURRENCY_LOCALE=es_ES
Terminal window
STRIPE_PUBLISHABLE_KEY=pk_test_... # Same publishable key as STRIPE_KEY above

The frontend reads this server-side via Astro’s env schema and passes it as an SSR prop — it is never bundled into client JavaScript directly.

Start the Stripe CLI listener in a dedicated terminal:

Terminal window
stripe listen --forward-to localhost/api/webhooks/stripe

This outputs a webhook signing secret (whsec_...). Copy it to STRIPE_WEBHOOK_SECRET in your backend .env. The secret changes each time you restart stripe listen.

Keep stripe listen running while developing payment features. The backend webhook endpoint (/api/webhooks/stripe) handles these events:

EventAction
payment_intent.succeededMarks payment as succeeded
payment_intent.payment_failedMarks payment as failed
payment_intent.requires_actionFlags payment for 3DS
payment_intent.canceledMarks payment as canceled
checkout.session.completedCreates payment record from Checkout Session

Payment gateway and method configuration is seeded automatically via a data migration that runs during artisan migrate. After migrating, verify the data exists:

Terminal window
./vendor/bin/sail artisan tinker --execute="App\Models\PaymentGateway::count();"
./vendor/bin/sail artisan tinker --execute="App\Models\PaymentMethod::count();"
./vendor/bin/sail artisan tinker --execute="App\Models\MarketPaymentMethod::count();"

Expected output:

  • Gateways: 3 (stripe active, adyen/redsys inactive)
  • Methods: 5 (card/apple_pay/google_pay active, sepa_debit/bank_transfer inactive)
  • Market links: One per active market, each linked to Stripe with card/apple_pay/google_pay

If data is missing (e.g., you rolled back migrations), re-run:

Terminal window
./vendor/bin/sail artisan migrate

The migration (seed_payment_system_data) is idempotent — it uses upsert/updateOrInsert and won’t duplicate records.

Terminal window
stripe trigger payment_intent.succeeded

Check the backend log or Telescope for the incoming webhook event.

Card NumberScenario
4242 4242 4242 4242Successful payment
4000 0025 0000 3155Requires 3DS authentication
4000 0000 0000 9995Declined (insufficient funds)
4000 0000 0000 0002Declined (generic)

Use any future expiry date, any 3-digit CVC, and any postal code.

See the full list in Stripe’s testing docs.

”Payment method ‘card’ is not enabled for market ‘XX’”

Section titled “”Payment method ‘card’ is not enabled for market ‘XX’””

The market_payment_methods table is missing entries for that market. This usually means:

  1. The market was created after the payment data migration ran, or
  2. Migrations were rolled back and not re-run

Fix: Re-run the payment data migration or manually seed via Filament admin (Payment Gateways / Payment Methods resources).

  • Ensure STRIPE_WEBHOOK_SECRET matches the output of your current stripe listen session
  • The webhook tolerance is 300 seconds (5 minutes) — check that your system clock is accurate
  • Restart stripe listen and update the secret if it changed
  • Verify stripe listen is running and forwarding to the correct URL
  • Check Telescope for incoming webhook requests at /api/webhooks/stripe
  • Ensure your Stripe Dashboard is in test mode (toggle at the top of the dashboard)

All payment settings live in backend/config/payment.php:

Config KeyEnv VariableDefaultDescription
default_currencyPAYMENT_DEFAULT_CURRENCYEURDefault payment currency
deposit.balance_days_beforePAYMENT_BALANCE_DAYS_BEFORE7Days before departure for balance due
gateways.stripe.keySTRIPE_KEYStripe publishable key
gateways.stripe.secretSTRIPE_SECRETStripe secret key
gateways.stripe.webhook_secretSTRIPE_WEBHOOK_SECRETStripe webhook signing secret
gateways.stripe.currencyCASHIER_CURRENCYeurStripe charge currency
gateways.stripe.currency_localeCASHIER_CURRENCY_LOCALEes_ESCurrency formatting locale