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:

Event Action
payment_intent.succeeded Marks payment as succeeded
payment_intent.payment_failed Marks payment as failed
payment_intent.requires_action Flags payment for 3DS
payment_intent.canceled Marks payment as canceled
checkout.session.completed Creates 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 + redsys active, adyen 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. The ES market has an extra Redsys + card link set as primary (display_order=1), pushing Stripe card to display_order=2 — added by the 2026_03_30_110155_activate_redsys_for_es_market migration.

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 Number Scenario
4242 4242 4242 4242 Successful payment
4000 0025 0000 3155 Requires 3DS authentication
4000 0000 0000 9995 Declined (insufficient funds)
4000 0000 0000 0002 Declined (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 Key Env Variable Default Description
default_currency PAYMENT_DEFAULT_CURRENCY EUR Default payment currency
deposit.balance_days_before PAYMENT_BALANCE_DAYS_BEFORE 7 Days before departure for balance due
gateways.stripe.key STRIPE_KEY Stripe publishable key
gateways.stripe.secret STRIPE_SECRET Stripe secret key
gateways.stripe.webhook_secret STRIPE_WEBHOOK_SECRET Stripe webhook signing secret
gateways.stripe.currency CASHIER_CURRENCY eur Stripe charge currency
gateways.stripe.currency_locale CASHIER_CURRENCY_LOCALE es_ES Currency formatting locale