Laravel Stripe Payments: Step-by-Step Guide

·

·

Stripe powers payments for Laravel SaaS products, marketplaces, and e-commerce. Laravel Cashier abstracts subscriptions while the Stripe SDK handles one-time Checkout sessions and Connect payouts.

Stripe Account and SDK Setup

Create a Stripe account and obtain test API keys from the Dashboard. Never expose secret keys in frontend code.

Install Laravel Cashier for subscriptions or stripe/stripe-php for custom payment flows.

Step 1: Install Laravel Cashier

Run composer require laravel/cashier then php artisan vendor:publish –tag=cashier-migrations && php artisan migrate.

Cashier adds billable columns to users table and provides subscription management APIs.

composer require laravel/cashier
php artisan vendor:publish --tag=cashier-migrations
php artisan migrate

Step 2: Configure Stripe keys in .env

Set STRIPE_KEY (publishable) and STRIPE_SECRET in .env. Use test keys prefixed pk_test_ and sk_test_ during development.

Cashier reads these automatically. For raw SDK use Stripe::setApiKey(config('services.stripe.secret')).

STRIPE_KEY=pk_test_...
STRIPE_SECRET=sk_test_...

Step 3: Add Billable trait to User

Include LaravelCashierBillable on your User model for createOrGetStripeCustomer() and subscription helpers.

Stripe customer IDs persist on the user record for repeat charges without re-entering cards.

use LaravelCashierBillable;

class User extends Authenticatable
{
    use Billable;
}

Step 4: Verify SDK connectivity

In tinker run StripeStripe::setApiKey(env('STRIPE_SECRET')); StripeBalance::retrieve(); to confirm API access.

Network errors often mean firewall blocks or wrong key mode (test vs live).

StripeBalance::retrieve();

Checkout and Subscriptions

Stripe Checkout hosts PCI-compliant payment forms. Cashier newSubscription() manages recurring billing.

Always create Checkout Sessions server-side and redirect users to session.url.

Step 1: Create a Checkout Session

Use StripeCheckoutSession::create with line_items, mode=payment or subscription, and success/cancel URLs.

Pass customer_email or Stripe customer ID to prefill billing details.

$session = StripeCheckoutSession::create([
    'mode' => 'subscription',
    'line_items' => [['price' => 'price_123', 'quantity' => 1]],
    'success_url' => route('billing.success'),
    'cancel_url' => route('billing.cancel'),
]);

Step 2: Redirect to Stripe hosted page

return redirect($session->url) sends users to Stripe. Never collect raw card numbers in your Laravel forms unless PCI certified.

Mobile apps use the same session URL in an in-app browser or Stripe mobile SDK.

return redirect($session->url);

Step 3: Start subscription with Cashier

Call $user->newSubscription('default', 'price_monthly')->create($paymentMethodId) after SetupIntent collects payment method.

Check $user->subscribed('default') in middleware to gate premium features.

$user->newSubscription('default', 'price_monthly')->create($paymentMethod);

Step 4: Handle proration and plan swaps

Swap plans with $user->subscription('default')->swap('price_yearly'). Cashier handles Stripe proration invoices.

Communicate billing changes clearly in UI before swap to reduce support tickets.

$user->subscription('default')->swap('price_yearly');

Webhooks and Production Hardening

Stripe webhooks notify your app about successful payments, failed charges, and subscription cancellations.

Verify webhook signatures to reject forged requests.

Step 1: Create webhook endpoint

Route::post('/stripe/webhook', StripeWebhookController::class) must exclude CSRF verification for this URI.

Register the URL in Stripe Dashboard with events like checkout.session.completed and invoice.payment_failed.

Route::post('/stripe/webhook', [StripeWebhookController::class, 'handle']);

Step 2: Verify signatures with Cashier

Cashier provides WebhookController. Set STRIPE_WEBHOOK_SECRET from the Dashboard signing secret.

Invalid signatures return 400 so Stripe retries with exponential backoff.

STRIPE_WEBHOOK_SECRET=whsec_...

Step 3: Fulfill orders on checkout.session.completed

In the webhook handler, retrieve session ID, load the order, and mark paid. Idempotency keys prevent duplicate fulfillment.

Queue webhook processing so the endpoint returns 200 quickly before heavy work.

if ($event->type === 'checkout.session.completed') {
    FulfillOrder::dispatch($session->id);
}

Step 4: Go live with production keys

Swap to pk_live_ and sk_live_ keys, update webhook endpoint to HTTPS production URL, and run test transactions in live mode with small amounts.

Monitor Stripe Dashboard logs and set up billing alerts for failed payment spikes.

php artisan config:cache

Aadyanex builds production-ready Laravel applications with clean architecture, secure APIs, and long-term support.