> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel Cashier (Stripe)

> Implémentez abonnements, paiements uniques, factures et webhooks Stripe avec Laravel Cashier.

## Vue d'ensemble

Laravel Cashier (Stripe) est le package officiel qui interface Stripe avec Laravel. Il fournit une API unifiée pour créer des Subscriptions, vérifier leur état, les annuler, effectuer des paiements uniques, télécharger des factures et traiter les webhooks.

## Installation et configuration

Installez Cashier et créez les tables nécessaires.

```shell theme={null}
composer require laravel/cashier
php artisan vendor:publish --tag="cashier-migrations"
php artisan migrate
```

Ajoutez le trait `Billable` au modèle facturable (par ex. `App\Models\User`).

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}
```

Configurez les clés Stripe dans `.env`.

```ini theme={null}
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
```

## Gestion des clients

Si un Customer Stripe peut ne pas exister encore : `createOrGetStripeCustomer()`.

```php theme={null}
$stripeCustomer = $user->createOrGetStripeCustomer();
```

Pour créer explicitement : `createAsStripeCustomer()`.

```php theme={null}
$stripeCustomer = $user->createAsStripeCustomer();
```

## Subscriptions

### Création

`newSubscription()` + `create()`.
`$paymentMethodId` : ID de méthode de paiement obtenu via Stripe.js, etc.

```php theme={null}
$user->newSubscription('default', 'price_monthly')
    ->create($paymentMethodId);
```

`price_monthly` est un exemple : utilisez le vrai Price ID créé dans Stripe.

### État

`subscribed()` indique si un abonnement est actif.

```php theme={null}
if ($user->subscribed('default')) {
    // Abonnement actif...
}
```

### Annulation et reprise

```php theme={null}
$user->subscription('default')->cancel();

if ($user->subscription('default')->onGracePeriod()) {
    // Période de grâce...
}

$user->subscription('default')->resume();
```

```mermaid theme={null}
stateDiagram-v2
    [*] --> Active
    Active --> GracePeriod: cancel()
    GracePeriod --> Active: resume()
    GracePeriod --> Canceled: fin de la période de grâce
    Active --> Canceled: cancelNow()
```

## Paiement unique (Charge)

`charge()` facture une fois. Le montant est en plus petite unité (ex : `100` = 1,00 USD).
Utilisez à nouveau `$paymentMethodId`.

```php theme={null}
$payment = $user->charge(100, $paymentMethodId);
```

En cas d'échec, `charge()` lance une exception.

## Payment Element

Le [Payment Element](https://stripe.com/docs/payments/payment-element) Stripe unifie carte, Apple Pay, Google Pay, iDEAL, etc.

### Pour un abonnement

Créez un Setup Intent et passez-le à la vue.

```php theme={null}
return view('subscribe', [
    'intent' => $user->createSetupIntent()
]);
```

Montez le Payment Element avec le `client_secret`.

```html theme={null}
<div id="payment-element"></div>
<button id="submit">Subscribe</button>

<script src="https://js.stripe.com/v3/"></script>
<script>
    const stripe = Stripe('stripe-public-key');

    const elements = stripe.elements({
        clientSecret: '{{ $intent->client_secret }}'
    });

    const paymentElement = elements.create('payment');

    paymentElement.mount('#payment-element');

    document.getElementById('submit').addEventListener('click', async () => {
        const { error } = await stripe.confirmSetup({
            elements,
            confirmParams: {
                return_url: '{{ route("subscription.complete") }}',
            },
        });

        if (error) {
            // Afficher l'erreur à l'utilisateur...
        }
    });
</script>
```

L'URL de redirection Stripe reçoit `setup_intent`. Récupérez la méthode de paiement et créez l'abonnement.

```php theme={null}
use Illuminate\Http\Request;

Route::get('/subscription/complete', function (Request $request) {
    $setupIntent = $request->user()->findSetupIntent(
        $request->setup_intent
    );

    $paymentMethod = $setupIntent->payment_method;

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

    return redirect('/dashboard');
})->name('subscription.complete');
```

### Pour un paiement unique

Utilisez `pay()` pour créer un Payment Intent. Votre modèle Order doit avoir `user_id`, `amount`, `status`, `stripe_payment_intent_id`.

```php theme={null}
use App\Models\Order;
use Illuminate\Http\Request;

Route::post('/pay', function (Request $request) {
    $amount = 1000;

    $payment = $request->user()->pay($amount);

    $order = Order::create([
        'user_id' => $request->user()->id,
        'amount' => $amount,
        'status' => 'pending',
        'stripe_payment_intent_id' => $payment->id,
    ]);

    return view('checkout', [
        'clientSecret' => $payment->client_secret,
        'order' => $order,
    ]);
});
```

Montez le Payment Element et confirmez.

```html theme={null}
<div id="payment-element"></div>
<button id="submit">Pay Now</button>

<script src="https://js.stripe.com/v3/"></script>
<script>
    const stripe = Stripe('stripe-public-key');

    const elements = stripe.elements({
        clientSecret: '{{ $clientSecret }}'
    });

    const paymentElement = elements.create('payment');

    paymentElement.mount('#payment-element');

    document.getElementById('submit').addEventListener('click', async () => {
        const { error } = await stripe.confirmPayment({
            elements,
            confirmParams: {
                return_url: '{{ route("payment.complete") }}',
            },
        });

        if (error) {
            // Afficher l'erreur à l'utilisateur...
        }
    });
</script>
```

Après redirection, retrouvez la commande via `payment_intent`, vérifiez qu'elle appartient à l'utilisateur et est en statut `succeeded`.

```php theme={null}
use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/payment/complete', function (Request $request) {
    $order = Order::where('user_id', $request->user()->id)
        ->where('stripe_payment_intent_id', $request->payment_intent)
        ->firstOrFail();

    $paymentIntent = $request->user()
        ->stripe()
        ->paymentIntents
        ->retrieve($request->payment_intent);

    if ($paymentIntent->customer === $request->user()->stripe_id &&
        $paymentIntent->status === 'succeeded') {
        $order->update(['status' => 'paid']);

        // Finaliser la commande...
    }

    return redirect('/dashboard');
})->name('payment.complete');
```

```mermaid theme={null}
sequenceDiagram
    participant User as Utilisateur
    participant App as Application<br>Laravel
    participant Stripe

    User->>App: POST /pay
    App->>Stripe: pay() → création PaymentIntent
    Stripe-->>App: client_secret
    App-->>User: Affichage de la vue checkout
    User->>Stripe: confirmPayment()
    Stripe-->>User: Redirection vers return_url
    User->>App: GET /payment/complete
    App->>Stripe: Récupération et vérification du PaymentIntent
    App->>App: order.status = paid
    App-->>User: Redirection vers le dashboard
```

## Factures

Liste des factures : `invoices()`.

```php theme={null}
$invoices = $user->invoices();
```

Pour le téléchargement PDF, installez `dompdf/dompdf` puis utilisez `downloadInvoice()`.

```shell theme={null}
composer require dompdf/dompdf
```

```php theme={null}
return $user->downloadInvoice($invoiceId);
```

## Configuration des webhooks

Cashier enregistre une route webhook sur `/stripe/webhook` par défaut. Configurez cette URL dans Stripe.

Créer le webhook :

```shell theme={null}
php artisan cashier:webhook
```

Excluez `stripe/*` de la protection CSRF.

```php theme={null}
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'stripe/*',
    ]);
})
```

Définissez `STRIPE_WEBHOOK_SECRET` dans `.env` pour que le middleware de vérification de signature Cashier valide les requêtes.


## Related topics

- [Mise à niveau de Laravel 10 à 11](/fr/blog/upgrade-10-to-11.md)
- [Mises à jour Laravel — juin 2026](/fr/blog/changelog/202606.md)
- [Protection CSRF](/fr/csrf.md)
- [Structure d'application Laravel 11+](/fr/advanced/app-structure.md)
- [Conteneur de services](/fr/service-container.md)
