Laravel Cashier

Com gestionar subscripcions i pagaments amb Laravel Cashier: Stripe, plans, facturació, webhooks, portal de client i pagaments únics.

Que es Laravel Cashier?#

Laravel Cashier proporciona una interficie expressiva i fluida per gestionar la facturacio de subscripcions amb serveis de pagament. Abstrau tota la complexitat d'interactuar amb les APIs de pagament i ofereix metodes intuitus per crear subscripcions, gestionar periodes de prova, canviar de pla, aplicar cupons, generar factures i molt mes.

Cashier s'encarrega de totes les operacions repetitives que necessita qualsevol aplicacio SaaS: la creacio de clients al servei de pagament, la gestio de metodes de pagament, el control de l'estat de les subscripcions, la gestio de webhooks i la generacio de factures en PDF. Sense Cashier, hauriess d'implementar tota aquesta logica manualment, interactuant directament amb l'API de Stripe o Paddle.

Cashier Stripe vs Cashier Paddle#

Laravel ofereix dues variants de Cashier:

  • Cashier Stripe (laravel/cashier): La mes popular i completa. Stripe es el processador de pagaments mes utilitzat per a aplicacions SaaS, amb suport per a mes de 135 monedes i metodes de pagament de tot el mon. Es la opcio recomanada per a la majoria d'aplicacions.

  • Cashier Paddle (laravel/cashier-paddle): Paddle actua com a "Merchant of Record", cosa que significa que gestiona els impostos (IVA, GST, sales tax) per tu. Ideal si no vols gestionar la complexitat fiscal de vendre a multiples paisos.

En aquesta guia ens centrem en Cashier Stripe, ja que es la variant mes utilitzada i completa.

Installacio i configuracio#

Instal·lar el paquet#

composer require laravel/cashier

Despres de la installacio, publica i executa les migracions de Cashier. Aquestes creen les columnes necessaries a la taula users (com stripe_id, pm_type, pm_last_four i trial_ends_at) i la taula subscriptions amb la taula auxiliar subscription_items:

php artisan migrate

El trait Billable#

Afegeix el trait Billable al model que representara els clients (normalment User). Aquest trait proporciona tots els metodes necessaris per gestionar subscripcions, pagaments i factures:

use Laravel\Cashier\Billable;
 
class User extends Authenticatable
{
    use Billable;
}

Si tens clients que no son usuaris (per exemple, equips o organitzacions), pots afegir el trait Billable a qualsevol model Eloquent.

Configuracio de Stripe#

Configura les claus de Stripe al fitxer .env. Necessites la clau publica (per al frontend), la clau secreta (per al backend) i el secret del webhook:

STRIPE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx

Utilitzeu les claus de test (pk_test_ i sk_test_) durant el desenvolupament. Les claus de produccio (pk_live_ i sk_live_) nomes s'han d'utilitzar a l'entorn de produccio. Mai poseu claus de produccio al codi font.

Moneda i configuracio regional#

Per defecte, Cashier utilitza dolars americans (USD). Pots canviar la moneda per defecte al fitxer config/cashier.php o publicant-lo primer:

// config/cashier.php
 
'currency' => env('CASHIER_CURRENCY', 'eur'),
'currency_locale' => env('CASHIER_CURRENCY_LOCALE', 'ca_ES'),

O simplement al .env:

CASHIER_CURRENCY=eur
CASHIER_CURRENCY_LOCALE=ca_ES

Amb el locale ca_ES, els imports es formataran automaticament en format catala (per exemple, 49,99 EUR).

Gestio de clients#

Crear clients a Stripe#

Abans de crear subscripcions o cobrar pagaments, l'usuari necessita un registre de client a Stripe. Cashier ho fa automaticament quan crees una subscripcio, pero tambe pots crear el client manualment:

// Crear el client a Stripe immediatament
$stripeCustomer = $user->createAsStripeCustomer();
 
// Crear amb opcions addicionals
$stripeCustomer = $user->createAsStripeCustomer([
    'preferred_locales' => ['ca', 'es'],
    'metadata' => [
        'plan' => 'professional',
        'source' => 'website',
    ],
]);

Un cop creat, l'ID del client de Stripe s'emmagatzema automaticament a la columna stripe_id de la taula users.

Actualitzar informacio del client#

Pots actualitzar les dades del client a Stripe en qualsevol moment:

// Actualitzar dades del client a Stripe
$user->updateStripeCustomer([
    'name' => $user->name,
    'email' => $user->email,
    'phone' => $user->phone,
    'address' => [
        'city' => 'Andorra la Vella',
        'country' => 'AD',
        'line1' => 'Avinguda Meritxell, 1',
        'postal_code' => 'AD500',
    ],
]);

Metodes utils del trait Billable#

El trait Billable proporciona metodes per interactuar amb el client de Stripe:

// Obtenir l'objecte client de Stripe
$stripeCustomer = $user->asStripeCustomer();
 
// Comprovar si l'usuari te un registre a Stripe
if ($user->hasStripeId()) {
    // L'usuari ja existeix a Stripe
}
 
// Obtenir l'ID de Stripe
$stripeId = $user->stripeId();
 
// Obtenir el balanc del client (en centims)
$balance = $user->balance();
 
// Aplicar un credit al client
$user->applyBalance(-500, 'Credit de benvinguda'); // -5.00 EUR

Subscripcions#

Les subscripcions son el nucli de Cashier. La majoria d'aplicacions SaaS es basen en plans de subscripcio recurrents que Cashier gestiona de manera elegant.

Crear subscripcions#

Per crear una subscripcio, primer necessites l'ID del preu (price) de Stripe. Els preus es defineixen al Dashboard de Stripe i tenen el format price_xxxxx. El metode newSubscription inicia el proces:

use Illuminate\Http\Request;
 
Route::post('/subscribe', function (Request $request) {
    $request->validate([
        'payment_method' => 'required|string',
        'plan' => 'required|in:price_basic,price_pro,price_enterprise',
    ]);
 
    $request->user()
        ->newSubscription('default', $request->plan)
        ->create($request->payment_method);
 
    return redirect()->route('dashboard')
        ->with('success', 'Subscripcio creada correctament!');
})->middleware('auth');

El primer argument de newSubscription es el nom de la subscripcio. Si la teva aplicacio nomes te un tipus de subscripcio, pots utilitzar 'default'. El segon argument es l'ID del preu de Stripe.

Tambe pots afegir opcions adicionals durant la creacio:

$user->newSubscription('default', 'price_pro')
    ->withCoupon('BENVINGUDA20')           // Aplicar un cupo de descompte
    ->withPromotionCode('promo_xxxxxx')    // O un codi de promocio
    ->withMetadata(['source' => 'landing']) // Metadades addicionals
    ->create($paymentMethod);

Comprovar l'estat de la subscripcio#

Cashier proporciona metodes expressius per comprovar l'estat de les subscripcions. Aquests metodes retornen booleans i son ideals per a middleware, gates i logica condicional:

// Comprovar si l'usuari esta subscrit (qualsevol subscripcio activa)
if ($user->subscribed('default')) {
    // L'usuari te accés al contingut premium
}
 
// Comprovar si esta subscrit a un preu concret
if ($user->subscribedToPrice('price_pro', 'default')) {
    // L'usuari esta al pla Pro
}
 
// Comprovar si esta en periode de prova
if ($user->onTrial('default')) {
    // L'usuari esta en el trial
}
 
// Comprovar si esta en periode de gracia (despres de cancel·lar)
if ($user->subscription('default')->onGracePeriod()) {
    // Encara te acces fins al final del periode facturat
}
 
// Comprovar si esta cancel·lat
if ($user->subscription('default')->cancelled()) {
    // La subscripcio esta cancel·lada (pot estar en gracia)
}
 
// Comprovar si ha acabat completament
if ($user->subscription('default')->ended()) {
    // La subscripcio ha acabat, ja no te acces
}
 
// Comprovar si te un pagament incomplet (requereix accio)
if ($user->subscription('default')->hasIncompletePayment()) {
    // Redirigir a la pagina de pagament
}

Un patro comu es crear un middleware per protegir les rutes que requereixen subscripcio:

<?php
 
namespace App\Http\Middleware;
 
use Closure;
use Illuminate\Http\Request;
 
class EnsureSubscribed
{
    public function handle(Request $request, Closure $next, string $subscription = 'default'): mixed
    {
        if (! $request->user()?->subscribed($subscription)) {
            return redirect()->route('pricing')
                ->with('error', 'Necessites una subscripcio activa per accedir.');
        }
 
        return $next($request);
    }
}

Multiples subscripcions#

Una aplicacio pot oferir multiples tipus de subscripcio independents. Per exemple, un servei de fitness podria tenir una subscripcio per a sessions en linia i una altra per a sessions presencials:

// Crear subscripcions independents
$user->newSubscription('online', 'price_online_monthly')->create($paymentMethod);
$user->newSubscription('presencial', 'price_presencial_monthly')->create($paymentMethod);
 
// Comprovar cadascuna
if ($user->subscribed('online')) {
    // Acces a sessions en linia
}
 
if ($user->subscribed('presencial')) {
    // Acces a sessions presencials
}

Plans i preus: canviar de pla amb swap#

Quan un usuari vol canviar de pla (per exemple, de Basic a Pro), utilitza el metode swap(). Stripe calcula automaticament la diferencia prorrategada:

// Canviar al pla Pro (prorrateig automatic)
$user->subscription('default')->swap('price_pro');
 
// Canviar immediatament sense esperar al final del cicle
$user->subscription('default')->swapAndInvoice('price_pro');
 
// Comprovar el pla actual
$currentPriceId = $user->subscription('default')->stripe_price;

Si l'usuari canvia a un pla mes car, Stripe cobra la diferencia prorrategada immediatament. Si canvia a un pla mes barat, l'import es reflecteix com a credit a la proxima factura.

Quantitat de subscripcio (seients)#

Per a models de preus basats en seients o quantitat (per exemple, cobrar per cada membre de l'equip), Cashier gestiona la quantitat de la subscripcio:

// Crear subscripcio amb 5 seients
$user->newSubscription('default', 'price_per_seat')
    ->quantity(5)
    ->create($paymentMethod);
 
// Canviar la quantitat
$user->subscription('default')->updateQuantity(10);
 
// Incrementar / decrementar
$user->subscription('default')->incrementQuantity();      // +1
$user->subscription('default')->incrementQuantity(3);     // +3
$user->subscription('default')->decrementQuantity();      // -1
$user->subscription('default')->decrementQuantity(2);     // -2
 
// Obtenir la quantitat actual
$quantity = $user->subscription('default')->quantity;

Periodes de prova (trials)#

Cashier suporta dues formes de periodes de prova: trials per subscripcio i trials generics.

Trials per subscripcio: L'usuari proporciona el metode de pagament des del principi, pero no se li cobra fins que acabi el trial:

// Trial de 14 dies amb metode de pagament
$user->newSubscription('default', 'price_pro')
    ->trialDays(14)
    ->create($paymentMethod);
 
// Trial fins a una data concreta
$user->newSubscription('default', 'price_pro')
    ->trialUntil(now()->addMonth())
    ->create($paymentMethod);
 
// Comprovar si esta en trial
if ($user->onTrial('default')) {
    $trialEndsAt = $user->subscription('default')->trial_ends_at;
    // "El teu trial acaba el {$trialEndsAt->format('d/m/Y')}"
}

Trials generics: L'usuari no necessita proporcionar metode de pagament durant el trial. Es configura directament al model:

// Al model User (o al registre)
$user->createAsStripeCustomer();
 
$user->update([
    'trial_ends_at' => now()->addDays(14),
]);
 
// Comprovar trial generic (sense nom de subscripcio)
if ($user->onTrial()) {
    // L'usuari esta en trial generic
}
 
if ($user->onGenericTrial()) {
    // Especificament en trial generic (no de subscripcio)
}

Els trials generics son utils per oferir un periode de prova sense demanar targeta de credit. Quan el trial acabi, l'usuari haura de crear una subscripcio real amb metode de pagament per continuar.

Cancel·lar subscripcions#

Cashier ofereix diverses maneres de cancel·lar una subscripcio:

// Cancel·lar al final del periode facturat (gracia)
$user->subscription('default')->cancel();
// L'usuari manté l'acces fins al final del cicle actual
 
// Cancel·lar immediatament
$user->subscription('default')->cancelNow();
// L'acces s'elimina immediatament
 
// Cancel·lar immediatament i generar factura final
$user->subscription('default')->cancelNowAndInvoice();
// Factura la part prorratejada i cancel·la
 
// Cancel·lar en una data concreta
$user->subscription('default')->cancelAt(now()->addDays(5));

Despres de cancel·lar amb cancel(), la subscripcio entra en periode de gracia. L'usuari manté l'acces fins al final del cicle de facturacio actual:

if ($user->subscription('default')->onGracePeriod()) {
    // Mostrar missatge: "La subscripcio s'acabara el X"
    $endsAt = $user->subscription('default')->ends_at;
}

Reprendre subscripcions#

Un usuari que ha cancel·lat la seva subscripcio pot reprendre-la durant el periode de gracia:

if ($user->subscription('default')->onGracePeriod()) {
    $user->subscription('default')->resume();
    // La subscripcio torna a estar activa
}

El metode resume() nomes funciona durant el periode de gracia. Un cop la subscripcio ha acabat completament (ended()), l'usuari haura de crear una nova subscripcio.

Pagaments unics#

A mes de les subscripcions, Cashier permet realitzar cobros unics:

// Cobrament unic (en centims)
$user->charge(4999, $paymentMethod); // 49.99 EUR
 
// Cobrament amb descripció a la factura
$user->charge(4999, $paymentMethod, [
    'description' => 'Curs Laravel Avancat',
]);
 
// Crear un cobrament amb factura automatica
$user->invoiceFor('Curs Laravel Avancat', 4999);
 
// Cobrament amb mes opcions
$user->invoiceFor('Curs Laravel Avancat', 4999, [
    'description' => 'Curs en video de 20 hores',
], [
    'collection_method' => 'send_invoice',
    'due_date' => now()->addDays(30)->timestamp,
]);

Els imports a Stripe sempre s'expressen en centims (o la unitat mes petita de la moneda). Per tant, 4999 son 49,99 EUR. Cashier gestiona automaticament la conversio per mostrar els imports correctament.

Reemborsaments#

Per reemborsar un cobrament:

$payment = $user->charge(4999, $paymentMethod);
 
// Reemborsament total
$user->refund($payment->id);
 
// Reemborsament parcial
$user->refund($payment->id, 2500); // Reemborsar 25.00 EUR

Stripe Checkout Sessions#

Stripe Checkout es una pagina de pagament allotjada per Stripe que gestiona tota la interficie de pagament, incloent la recollida de dades de la targeta, la validacio i la seguretat 3D Secure. Cashier facilita la creacio de sessions de checkout:

use Illuminate\Http\Request;
 
Route::get('/checkout', function (Request $request) {
    return $request->user()->checkout('price_pro', [
        'success_url' => route('checkout.success'),
        'cancel_url' => route('checkout.cancel'),
    ]);
})->middleware('auth');
 
// Checkout per a subscripcions
Route::get('/subscribe/checkout', function (Request $request) {
    return $request->user()
        ->newSubscription('default', 'price_pro')
        ->checkout([
            'success_url' => route('dashboard') . '?session_id={CHECKOUT_SESSION_ID}',
            'cancel_url' => route('pricing'),
        ]);
})->middleware('auth');
 
// Checkout amb multiples productes
Route::get('/cart/checkout', function (Request $request) {
    return $request->user()->checkout([
        'price_curs_laravel' => 1,
        'price_curs_vue' => 1,
    ], [
        'success_url' => route('orders.success'),
        'cancel_url' => route('cart'),
    ]);
})->middleware('auth');

Stripe Checkout es la forma mes senzilla i segura de recollir pagaments, ja que Stripe gestiona tota la interficie de pagament i la conformitat PCI.

Metodes de pagament#

Cashier proporciona metodes per gestionar els metodes de pagament dels clients:

// Obtenir tots els metodes de pagament
$paymentMethods = $user->paymentMethods();
 
// Obtenir el metode de pagament per defecte
$defaultPaymentMethod = $user->defaultPaymentMethod();
 
// Afegir un nou metode de pagament
$user->addPaymentMethod($paymentMethodId);
 
// Establir un metode de pagament com a per defecte
$user->updateDefaultPaymentMethod($paymentMethodId);
 
// Eliminar un metode de pagament
$user->deletePaymentMethod($paymentMethodId);
 
// Eliminar tots els metodes de pagament
$user->deletePaymentMethods();
 
// Comprovar si te algun metode de pagament
if ($user->hasPaymentMethod()) {
    // Te almenys un metode de pagament
}
 
// Comprovar si te un metode de pagament per defecte
if ($user->hasDefaultPaymentMethod()) {
    // Te un metode de pagament per defecte
}

Factures#

Cashier facilita la gestio de factures, incloent la generacio de PDFs:

// Obtenir totes les factures
$invoices = $user->invoices();
 
// Obtenir factures incloent les pendents
$invoices = $user->invoicesIncludingPending();
 
// Obtenir una factura concreta
$invoice = $user->findInvoice($invoiceId);
 
// Iterar sobre les factures
foreach ($user->invoices() as $invoice) {
    echo $invoice->date()->toFormattedDateString();    // Data
    echo $invoice->total();                            // Total formatat
    echo $invoice->tax();                              // Impostos
    echo $invoice->subtotal();                         // Subtotal
    echo $invoice->hasDiscount();                      // Te descompte?
    echo $invoice->discount();                         // Import descompte
}

Descarregar factures en PDF#

Cashier pot generar factures en PDF utilitzant la biblioteca DomPDF. Primer, instal·la la dependencia:

composer require dompdf/dompdf

Despres, pots servir factures per descarregar:

Route::get('/invoices/{invoice}', function (Request $request, string $invoice) {
    return $request->user()->downloadInvoice($invoice, [
        'vendor' => 'La Meva Empresa SL',
        'product' => 'Subscripcio Professional',
        'street' => 'Avinguda Meritxell, 1',
        'location' => 'AD500, Andorra la Vella',
        'phone' => '+376 123 456',
        'email' => 'info@empresa.ad',
        'url' => 'https://empresa.ad',
    ]);
})->middleware('auth');

Tambe pots personalitzar la vista de la factura publicant les vistes de Cashier:

php artisan vendor:publish --tag="cashier-views"

Aixo publicara les vistes a resources/views/vendor/cashier/, on pots personalitzar el disseny del PDF.

Portal de facturacio#

Stripe ofereix un portal de facturacio allotjat on els clients poden gestionar les seves subscripcions, metodes de pagament i factures sense que hagis de construir tu la interficie. Cashier proporciona un metode directe per redirigir l'usuari al portal:

Route::get('/billing', function (Request $request) {
    return $request->user()->redirectToBillingPortal(route('dashboard'));
})->middleware('auth')->name('billing');

L'argument de redirectToBillingPortal es la URL on l'usuari sera redirigit quan surti del portal. Al portal, l'usuari pot:

  • Veure i descarregar factures
  • Actualitzar el metode de pagament
  • Canviar de pla
  • Cancel·lar la subscripcio
  • Actualitzar les dades de facturacio

El portal de facturacio de Stripe es configura al Dashboard de Stripe (Settings > Billing > Customer portal). Alla pots definir quines accions permet el portal, quins plans es poden canviar i l'aspecte visual.

Webhooks#

Els webhooks son essencials per mantenir la teva base de dades sincronitzada amb Stripe. Quan passa alguna cosa a Stripe (un pagament es completa, una subscripcio es cancel·la, una targeta falla), Stripe envia una notificacio HTTP al teu servidor.

Configuracio dels webhooks#

Primer, configura la URL del webhook a Stripe Dashboard (o amb la CLI). La URL per defecte de Cashier es /stripe/webhook:

// A routes/web.php (Cashier registra la ruta automaticament)
// La ruta /stripe/webhook ja esta disponible

Al .env, afegeix el secret del webhook:

STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxx

Cashier gestiona automaticament els events mes importants:

  • customer.subscription.created - Subscripcio creada
  • customer.subscription.updated - Subscripcio actualitzada
  • customer.subscription.deleted - Subscripcio eliminada
  • invoice.payment_action_required - Pagament requereix accio (3D Secure)
  • invoice.payment_succeeded - Pagament completat

Gestors d'events personalitzats#

Pots respondre a events addicionals de Stripe creant listeners:

<?php
 
namespace App\Listeners;
 
use Laravel\Cashier\Events\WebhookReceived;
 
class StripeEventListener
{
    public function handle(WebhookReceived $event): void
    {
        if ($event->payload['type'] === 'invoice.payment_succeeded') {
            $stripeCustomerId = $event->payload['data']['object']['customer'];
 
            // Enviar email de confirmacio, actualitzar estadistiques, etc.
        }
 
        if ($event->payload['type'] === 'customer.subscription.deleted') {
            // Revocar acces, enviar email, netejar dades...
        }
 
        if ($event->payload['type'] === 'charge.refunded') {
            // Gestionar reemborsament
        }
    }
}

Registra el listener al EventServiceProvider o amb l'atribut #[Listener]:

// app/Providers/AppServiceProvider.php
 
use App\Listeners\StripeEventListener;
use Laravel\Cashier\Events\WebhookReceived;
 
public function boot(): void
{
    Event::listen(WebhookReceived::class, StripeEventListener::class);
}

Testejar webhooks localment#

Durant el desenvolupament, pots utilitzar la CLI de Stripe per reenviar events al teu servidor local:

# Instal·lar la CLI de Stripe
brew install stripe/stripe-cli/stripe
 
# Iniciar sessio
stripe login
 
# Reenviar events al servidor local
stripe listen --forward-to http://localhost:8000/stripe/webhook

La CLI mostrara el webhook secret temporal que has d'utilitzar al .env durant el desenvolupament. Tambe pots activar events de prova per verificar que tot funciona:

# Activar un event de prova
stripe trigger payment_intent.succeeded
stripe trigger customer.subscription.created
stripe trigger invoice.payment_failed

Assegureu-vos que la URL del webhook (/stripe/webhook) esta exclosa de la verificacio CSRF. Cashier ho fa automaticament, pero si teniu middleware CSRF personalitzat, cal excloure-la explicitament.

Testing#

Mode de test de Stripe#

Stripe proporciona un entorn de test complet amb targetes de prova:

  • 4242 4242 4242 4242 - Pagament exitós
  • 4000 0000 0000 3220 - Requereix 3D Secure
  • 4000 0000 0000 0002 - Targeta denegada

Tests automatitzats#

Pots testejar la logica de facturacio als teus tests funcionals. Cashier funciona amb les claus de test de Stripe:

use App\Models\User;
 
test('un usuari pot crear una subscripcio', function () {
    $user = User::factory()->create();
 
    // Crear el client a Stripe
    $user->createAsStripeCustomer();
 
    // Crear un metode de pagament de prova
    $paymentMethod = $user->updateDefaultPaymentMethod('pm_card_visa');
 
    // Crear la subscripcio
    $user->newSubscription('default', 'price_test_basic')
        ->create('pm_card_visa');
 
    expect($user->subscribed('default'))->toBeTrue();
    expect($user->subscription('default')->stripe_price)->toBe('price_test_basic');
});
 
test('un usuari pot cancel·lar la subscripcio', function () {
    $user = User::factory()->create();
    $user->createAsStripeCustomer();
 
    $user->newSubscription('default', 'price_test_basic')
        ->create('pm_card_visa');
 
    $user->subscription('default')->cancel();
 
    expect($user->subscription('default')->cancelled())->toBeTrue();
    expect($user->subscription('default')->onGracePeriod())->toBeTrue();
});
 
test('un usuari pot canviar de pla', function () {
    $user = User::factory()->create();
    $user->createAsStripeCustomer();
 
    $user->newSubscription('default', 'price_test_basic')
        ->create('pm_card_visa');
 
    $user->subscription('default')->swap('price_test_pro');
 
    expect($user->subscription('default')->stripe_price)->toBe('price_test_pro');
});

Els tests que interactuen amb l'API de Stripe son mes lents que els tests unitaris. Considereu agrupar-los en un grup de tests separats i executar-los amb menys frequencia, o utilitzeu mocking per als tests mes rapids.

Exemple practic: flux de subscripcio SaaS#

A continuacio, un exemple complet d'un flux de subscripcio per a una aplicacio SaaS. Inclou la pagina de preus, la creacio de subscripcio amb Stripe Checkout i la gestio des del dashboard:

Controlador de subscripcions#

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
 
class SubscriptionController extends Controller
{
    /**
     * Mostra la pagina de preus.
     */
    public function pricing()
    {
        return view('pricing', [
            'plans' => [
                [
                    'name' => 'Basic',
                    'price_id' => config('stripe.prices.basic'),
                    'price' => '9,99',
                    'features' => ['5 projectes', '1 GB emmagatzematge', 'Suport email'],
                ],
                [
                    'name' => 'Professional',
                    'price_id' => config('stripe.prices.pro'),
                    'price' => '29,99',
                    'features' => ['Projectes il·limitats', '50 GB emmagatzematge', 'Suport prioritari'],
                ],
                [
                    'name' => 'Enterprise',
                    'price_id' => config('stripe.prices.enterprise'),
                    'price' => '99,99',
                    'features' => ['Tot del Pro', 'API acces', 'Suport dedicat', 'SLA 99.9%'],
                ],
            ],
        ]);
    }
 
    /**
     * Crea una sessio de checkout per a la subscripcio.
     */
    public function checkout(Request $request)
    {
        $request->validate([
            'plan' => 'required|string',
        ]);
 
        return $request->user()
            ->newSubscription('default', $request->plan)
            ->allowPromotionCodes()
            ->checkout([
                'success_url' => route('dashboard') . '?subscribed=1',
                'cancel_url' => route('pricing'),
            ]);
    }
 
    /**
     * Mostra el dashboard de la subscripcio.
     */
    public function dashboard(Request $request)
    {
        $user = $request->user();
        $subscription = $user->subscription('default');
 
        return view('subscription.dashboard', [
            'subscription' => $subscription,
            'onTrial' => $user->onTrial('default'),
            'onGracePeriod' => $subscription?->onGracePeriod(),
            'currentPlan' => $subscription?->stripe_price,
            'invoices' => $user->invoices()->take(5),
        ]);
    }
 
    /**
     * Cancel·la la subscripcio.
     */
    public function cancel(Request $request)
    {
        $request->user()->subscription('default')->cancel();
 
        return redirect()->route('subscription.dashboard')
            ->with('success', 'Subscripcio cancel·lada. Mantindras l\'acces fins al final del periode actual.');
    }
 
    /**
     * Repren la subscripcio cancel·lada.
     */
    public function resume(Request $request)
    {
        $request->user()->subscription('default')->resume();
 
        return redirect()->route('subscription.dashboard')
            ->with('success', 'Subscripcio represa correctament!');
    }
 
    /**
     * Redirigeix al portal de facturacio de Stripe.
     */
    public function billingPortal(Request $request)
    {
        return $request->user()
            ->redirectToBillingPortal(route('subscription.dashboard'));
    }
}

Rutes#

use App\Http\Controllers\SubscriptionController;
 
Route::get('/pricing', [SubscriptionController::class, 'pricing'])
    ->name('pricing');
 
Route::middleware('auth')->group(function () {
    Route::post('/subscribe', [SubscriptionController::class, 'checkout'])
        ->name('subscribe');
 
    Route::get('/subscription', [SubscriptionController::class, 'dashboard'])
        ->name('subscription.dashboard');
 
    Route::post('/subscription/cancel', [SubscriptionController::class, 'cancel'])
        ->name('subscription.cancel');
 
    Route::post('/subscription/resume', [SubscriptionController::class, 'resume'])
        ->name('subscription.resume');
 
    Route::get('/billing-portal', [SubscriptionController::class, 'billingPortal'])
        ->name('billing.portal');
});

Aquest exemple proporciona un flux complet que cobreix els escenaris mes comuns: veure preus, crear subscripcio via Stripe Checkout, gestionar la subscripcio des del dashboard i accedir al portal de facturacio per a operacions avancades.