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

# Stripe

> Accept payments using Stripe with Lunar.

Lunar's Stripe addon integrates with Stripe's Payment Intents API to handle card payments, including support for automatic and manual capture policies, webhooks, and address synchronization.

## Installation

### Require the Composer package

```sh theme={null}
composer require lunarphp/stripe
```

### Publish the configuration

This will publish the configuration under `config/lunar/stripe.php`.

```sh theme={null}
php artisan vendor:publish --tag=lunar.stripe.config
```

### Publish the views (optional)

The Stripe addon includes helper Blade and Livewire components for use during checkout. To customize these views, publish them.

```sh theme={null}
php artisan vendor:publish --tag=lunar.stripe.components
```

### Enable the driver

Set the driver in `config/lunar/payments.php`.

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

return [
    // ...
    'types' => [
        'card' => [
            // ...
            'driver' => 'stripe',
        ],
    ],
];
```

### Add Stripe credentials

Add the Stripe credentials to `config/services.php`.

```php theme={null}
'stripe' => [
    'key' => env('STRIPE_SECRET'),
    'public_key' => env('STRIPE_PK'),
    'webhooks' => [
        'lunar' => env('LUNAR_STRIPE_WEBHOOK_SECRET'),
    ],
],
```

<Tip>
  Keys can be found in the [Stripe Dashboard](https://dashboard.stripe.com/apikeys).
</Tip>

## Configuration

The following options are available in `config/lunar/stripe.php`.

| Key                     | Default               | Description                                                                                                           |
| ----------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `webhook_path`          | `stripe/webhook`      | The URI path for the Stripe webhook endpoint                                                                          |
| `policy`                | `automatic`           | Determines the capture policy. `automatic` captures payment immediately; `manual` authorizes first and captures later |
| `sync_addresses`        | `true`                | When enabled, billing and shipping addresses stored on the Stripe PaymentIntent are synced to the order               |
| `status_mapping`        | See below             | Maps Stripe PaymentIntent statuses to Lunar order statuses                                                            |
| `actions.store_charges` | `StoreCharges::class` | The action class responsible for storing charge data as transactions                                                  |

### Status mapping

The default status mapping translates Stripe PaymentIntent statuses to Lunar order statuses.

```php theme={null}
'status_mapping' => [
    'requires_capture' => 'requires-capture',
    'canceled' => 'cancelled',
    'processing' => 'processing',
    'requires_action' => 'awaiting-payment',
    'requires_confirmation' => 'auth-pending',
    'requires_payment_method' => 'failed',
    'succeeded' => 'payment-received',
],
```

## Backend Usage

### Create a PaymentIntent

```php theme={null}
use Lunar\Stripe\Facades\Stripe;
use Lunar\Models\Cart;

Stripe::createIntent(Cart $cart, $options = []);
```

This method creates a Stripe PaymentIntent from a cart and stores the resulting ID in the cart's meta data. If a PaymentIntent already exists for the cart, the existing one is returned instead.

The following parameters are sent by default:

```php theme={null}
[
    'amount' => 1099,
    'currency' => 'GBP',
    'automatic_payment_methods' => ['enabled' => true],
    'capture_method' => config('lunar.stripe.policy', 'automatic'),
]
```

If the cart has a shipping address, the addon also calls `updateShippingAddress()` to sync the address to the PaymentIntent.

Any additional parameters passed in the `$options` array are merged with the defaults.

### Fetch or create a PaymentIntent

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::fetchOrCreateIntent($cart, $createOptions = []);
```

This is a convenience method that fetches the existing PaymentIntent for a cart if one exists, or creates a new one.

### Retrieve the PaymentIntent ID from a cart

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

$intentId = Stripe::getCartIntentId($cart);
```

Alternatively, the PaymentIntent ID can be accessed directly from the cart's meta data.

```php theme={null}
$cart->meta['payment_intent'];
// or
$cart->meta->payment_intent;
```

### Fetch an existing PaymentIntent

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::fetchIntent($paymentIntentId);
```

### Sync an existing PaymentIntent

If a PaymentIntent has been created and the cart contents change, call `syncIntent` to update the intent with the correct totals.

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::syncIntent($cart);
```

### Update an existing PaymentIntent

To update specific properties on the PaymentIntent without recalculating the cart, use `updateIntent`.

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::updateIntent($cart, [
    'shipping' => [/* ... */],
]);
```

A PaymentIntent can also be updated directly by ID.

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::updateIntentById($intentId, [
    'description' => 'Updated description',
]);
```

### Cancel a PaymentIntent

To cancel a PaymentIntent, provide a valid cancellation reason. The addon includes a PHP enum for the available reasons.

```php theme={null}
use Lunar\Stripe\Facades\Stripe;
use Lunar\Stripe\Enums\CancellationReason;

Stripe::cancelIntent($cart, CancellationReason::ABANDONED);
```

Available cancellation reasons:

| Enum Case                                   | Value                   |
| ------------------------------------------- | ----------------------- |
| `CancellationReason::ABANDONED`             | `abandoned`             |
| `CancellationReason::DUPLICATE`             | `duplicate`             |
| `CancellationReason::REQUESTED_BY_CUSTOMER` | `requested_by_customer` |
| `CancellationReason::FRAUDULENT`            | `fraudulent`            |

### Update the shipping address

To sync the cart's shipping address to the PaymentIntent without manually specifying all fields, use the helper method.

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::updateShippingAddress($cart);
```

### Retrieve a payment method

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::getPaymentMethod($paymentMethodId);
```

## Charges

### Retrieve a specific charge

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::getCharge($chargeId);
```

### Get all charges for a PaymentIntent

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::getCharges($paymentIntentId);
```

## Capture and Refund

When using the `manual` capture policy, payments are authorized but not captured immediately. The Lunar payments system can then be used to capture or refund transactions.

### Capturing a payment

```php theme={null}
use Lunar\Facades\Payments;

Payments::driver('stripe')->capture($transaction, $amount);
```

### Refunding a payment

```php theme={null}
use Lunar\Facades\Payments;

Payments::driver('stripe')->refund($transaction, $amount, $notes);
```

## Webhooks

The addon provides a webhook endpoint that can be registered in the Stripe Dashboard. Follow the [Stripe webhook guide](https://stripe.com/docs/webhooks/quickstart) to set this up.

The webhook endpoint is available at:

```
https://yoursite.com/stripe/webhook
```

The path is configurable via the `webhook_path` option in `config/lunar/stripe.php`.

### Supported events

The webhook listens for the following Stripe events:

* `payment_intent.succeeded`
* `payment_intent.payment_failed`

### Webhook signing secret

Add the webhook signing secret to `config/services.php`.

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

return [
    // ...
    'stripe' => [
        // ...
        'webhooks' => [
            'lunar' => env('LUNAR_STRIPE_WEBHOOK_SECRET'),
        ],
    ],
];
```

### Manual order processing

If the webhook is not used, or if an order needs to be processed manually, the payment can be authorized directly.

```php theme={null}
use Lunar\Facades\CartSession;
use Lunar\Facades\Payments;

$cart = CartSession::current();

// With a draft order
$draftOrder = $cart->createOrder();
Payments::driver('stripe')->order($draftOrder)->withData([
    'payment_intent' => $draftOrder->meta['payment_intent'],
])->authorize();

// Using just the cart
Payments::driver('stripe')->cart($cart)->withData([
    'payment_intent' => $cart->meta['payment_intent'],
])->authorize();
```

## Livewire Component

The addon includes a Livewire payment form component that handles PaymentIntent creation and Stripe Elements rendering.

```blade theme={null}
<livewire:stripe.payment
    :cart="$cart"
    :returnUrl="route('checkout.complete')"
/>
```

To include the Stripe.js script, use the Blade directive in the page layout.

```blade theme={null}
@stripeScripts
```

## Storefront Examples

### API route for PaymentIntents

Set up a backend API route to fetch or create the PaymentIntent.

```php theme={null}
use Lunar\Facades\CartSession;
use Lunar\Stripe\Facades\Stripe;

Route::post('api/payment-intent', function () {
    $cart = CartSession::current();

    $intent = Stripe::fetchOrCreateIntent($cart);

    if ($intent->amount != $cart->total->value) {
        Stripe::syncIntent($cart);
    }

    return $intent;
})->middleware('web');
```

### Vue.js

This example uses Stripe's Payment Elements. For more information, see the [Stripe Elements guide](https://stripe.com/docs/payments/elements).

#### Payment component

```js theme={null}
<script setup>
const { VITE_STRIPE_PK } = import.meta.env

const stripe = Stripe(VITE_STRIPE_PK)
const stripeElements = ref({})

const buildForm = async () => {
    const { data } = await axios.post("api/payment-intent")

    stripeElements.value = stripe.elements({
        clientSecret: data.client_secret,
    })

    const paymentElement = stripeElements.value.create("payment", {
        layout: "tabs",
        defaultValues: {
            billingDetails: {
                name: `${billingAddress.value.first_name} ${billingAddress.value?.last_name}`,
                phone: billingAddress.value?.contact_phone,
            },
        },
        fields: {
            billingDetails: "never",
        },
    })

    paymentElement.mount("#payment-element")
}

onMounted(async () => {
    await buildForm()
})

const submit = async () => {
    try {
        const address = { /* ... */ }

        const { error } = await stripe.confirmPayment({
            elements: stripeElements.value,
            confirmParams: {
                return_url: "https://yoursite.com/checkout/complete",
                payment_method_data: {
                    billing_details: {
                        name: `${address.first_name} ${address.last_name}`,
                        email: address.contact_email,
                        phone: address.contact_phone,
                        address: {
                            city: address.city,
                            country: address.country.iso2,
                            line1: address.line_one,
                            line2: address.line_two,
                            postal_code: address.postcode,
                            state: address.state,
                        },
                    },
                },
            },
        })
    } catch (e) {
        // Handle error
    }
}
</script>
```

```html theme={null}
<template>
    <form @submit.prevent="submit">
        <div id="payment-element">
            <!--Stripe.js injects the Payment Element-->
        </div>
    </form>
</template>
```

## Extending

### Webhook event parameters

To process a PaymentIntent and link it to an order, the addon needs the PaymentIntent ID and an optional order ID. By default, the PaymentIntent ID is extracted from the Stripe event and the order ID is looked up from the PaymentIntent metadata.

This behavior can be customized by overriding the `ProcessesEventParameters` implementation.

```php theme={null}
use Lunar\Stripe\Concerns\ProcessesEventParameters;
use Lunar\Stripe\DataTransferObjects\EventParameters;

// In a service provider's boot method
$this->app->instance(ProcessesEventParameters::class, new class implements ProcessesEventParameters
{
    public function handle(\Stripe\Event $event): EventParameters
    {
        $paymentIntentId = $event->data->object->id;
        $orderId = null; // Setting to null creates a new order

        return new EventParameters($paymentIntentId, $orderId);
    }
});
```

## Database

The addon creates a `stripe_payment_intents` table to track PaymentIntents and their relationship to carts and orders.

| Field           | Type                   | Description                  |
| --------------- | ---------------------- | ---------------------------- |
| `id`            | `id`                   | Primary key                  |
| `cart_id`       | `foreignId`            | Associated cart              |
| `order_id`      | `foreignId` `nullable` | Associated order             |
| `intent_id`     | `string`               | Stripe PaymentIntent ID      |
| `status`        | `string` `nullable`    | Current PaymentIntent status |
| `event_id`      | `string` `nullable`    | Stripe event ID              |
| `processing_at` | `timestamp` `nullable` |                              |
| `processed_at`  | `timestamp` `nullable` |                              |
| `created_at`    | `timestamp`            |                              |
| `updated_at`    | `timestamp`            |                              |

## Events

### CartMissingForIntent

Dispatched when a webhook is received for a PaymentIntent, but no matching cart or order can be found.

```php theme={null}
use Lunar\Stripe\Events\Webhook\CartMissingForIntent;

public function handle(CartMissingForIntent $event)
{
    echo $event->paymentIntentId;
}
```

## Testing

The addon includes a mock client for testing without making real Stripe API calls.

```php theme={null}
use Lunar\Stripe\Facades\Stripe;

Stripe::fake();
```
