lunarphp/stripe integrates Lunar with Stripe’s Payment Intents API. It supports automatic and manual capture policies, webhooks, address synchronization, refunds, and payment checks (AVS, postal code, CVC). Stripe is the reference implementation for a first-party Lunar payment driver: every other gateway except PayPal is community-maintained.
Installation
Require the Composer package
Publish the configuration
config/lunar/stripe.php.
Publish the views (optional)
The addon ships a Blade/Livewire payment form. To customize it, publish the views.Run migrations
The addon adds astripe_payment_intents table.
Enable the driver
Registerstripe as a driver for a payment type in config/lunar/payments.php.
Add Stripe credentials
The API key and webhook secret are read fromconfig/services.php, not config/lunar/stripe.php.
Configuration
The following options are available inconfig/lunar/stripe.php.
Backend usage
Create a PaymentIntent
Lunar\Stripe\Models\StripePaymentIntent row linking it to the cart. If an intent already exists for the cart, the existing one is returned instead.
The following parameters are sent by default, merged with anything passed in $options:
Amount conversion
Lunar stores prices as integers scaled by the currency’s configureddecimal_places, which does not have to match the sub-unit Stripe expects for that currency code. Lunar\Stripe\Managers\StripeManager::toStripeAmount() converts the stored value back to the major unit using decimal_places, then re-scales it to whatever sub-unit Stripe requires. StripeManager::fromStripeAmount() performs the inverse conversion for amounts read back from Stripe, such as a charge or a refund.
The sub-unit is chosen from the currency code, following Stripe’s currency reference:
Conversion is done with integer arithmetic throughout, because float division misrounds at half-unit boundaries.
Fetch or create a PaymentIntent
Retrieve the PaymentIntent ID from a cart
$cart->meta['payment_intent'] first, falling back to the cart’s active StripePaymentIntent record.
Fetch an existing PaymentIntent
Sync an existing PaymentIntent
If a PaymentIntent has been created and the cart contents change, callsyncIntent to recalculate the cart and push the new total to Stripe.
Update an existing PaymentIntent
To update specific properties on the PaymentIntent without recalculating the cart, useupdateIntent.
Update the shipping address
Syncs the cart’s shipping address onto the PaymentIntent without manually specifying every field.Cancel a PaymentIntent
Retrieve a payment method
Charges
Retrieve a specific charge
Get all charges for a PaymentIntent
Authorizing a payment
Lunar\Stripe\StripePaymentType::authorize() is the driver’s entry point, resolved through the Lunar\Core\Facades\Payments facade.
authorize():
- Rejects the attempt if a
StripePaymentIntentrecord already exists for the intent and is no longer active (canceled or succeeded), or if the order is already placed. - Retrieves the intent from Stripe and verifies its amount and currency match the order or cart total (see below).
- Captures the intent immediately when its status is
requires_captureand thepolicyisautomatic. - Creates the order from the cart if one does not already exist.
- Stores the intent’s charges as transactions and places the order once the intent has succeeded.
Lunar\Core\DataObjects\PaymentAuthorize with success, message, orderId, and paymentType.
Amount and currency verification
Before capturing anything,authorize() checks the retrieved PaymentIntent against the expected total — the order’s total when authorizing against a placed/draft order, or the calculated cart’s total otherwise. The intent’s amount is compared at Stripe’s sub-unit scale via StripeManager::toStripeAmount(), and the currency is compared case-insensitively.
A mismatch on either amount or currency fails the authorization before anything is captured, unless allow_partial_payment is enabled. This guard exists specifically so a client-supplied PaymentIntent ID cannot be used to place a more expensive order against a cheaper payment.
Orphaned intents
If order creation fails after Stripe has already reported the PaymentIntent assucceeded (for example, the cart cannot create a second order), the driver dispatches Lunar\Stripe\Events\OrphanedPaymentIntentDetected with the intent ID, cart ID, and failure reason, so the captured payment can be reconciled manually.
Capture and refund
Under themanual capture policy, a PaymentIntent is authorized but not captured until later.
Capturing a payment
$amount is optional and in the order currency’s minor unit; when omitted, the full authorized amount is captured. Internally this calls Stripe’s capture endpoint, re-fetches the intent, and stores its charges as transactions via Lunar\Stripe\Actions\UpdateOrderFromIntent.
The same call is available as a verb on the transaction itself:
Refunding a payment
$amount is required and in the order currency’s minor unit. The driver creates a Stripe refund against the charge’s PaymentIntent and records a refund transaction, populating Lunar\Core\DataObjects\PaymentRefund::$transaction with the row it created.
Equivalently:
For a refund that also allocates against specific order lines — rather than a flat amount against a transaction — use
Lunar\Core\Models\Order::refund() with a Lunar\Core\DataObjects\RefundRequest. It resolves the capture transaction, computes the amount from the requested lines, shipping, and adjustment, and dispatches to the driver’s refund() under the hood, so the amount-matching guard above still applies to what Stripe actually receives.Payment checks
The driver surfaces the AVS line 1, AVS postal code, and CVC check results Stripe returns on a charge.Webhooks
Register a webhook endpoint in the Stripe Dashboard. Follow the Stripe webhook guide to set this up.webhook_path.
Supported events
StripeWebhookMiddleware only lets the following event types through to the controller; every other event returns a 200 without further processing:
payment_intent.succeededpayment_intent.payment_failed
Webhook signing secret
How a webhook is processed
The controller extracts the PaymentIntent ID and, if present, an order ID from the event’s metadata, then dispatchesLunar\Stripe\Jobs\ProcessStripeWebhook on a five-second delay (so it lands after a same-request authorize() call would have already processed the intent). The job re-authorizes against the order if one is known, or against the cart otherwise. If neither can be found, it dispatches Lunar\Stripe\Events\Webhook\CartMissingForIntent.
Extending event parameter resolution
The PaymentIntent ID and order ID extraction can be customized by overriding theProcessesEventParameters binding.
CartMissingForIntent
Dispatched when a webhook is received for a PaymentIntent, but no matching cart or order can be found. It broadcasts on a privatestripe-webhooks channel.
Manual order processing
If webhooks are disabled, or an order needs to be processed manually, authorize directly.Livewire component
The addon includes a Livewire payment form component that handles PaymentIntent creation and Stripe Elements rendering.Storefront example
API route for PaymentIntents
Stripe Elements
This example uses Stripe’s Payment Element. For more information, see the Stripe Elements guide.Database
The addon adds astripe_payment_intents table (Lunar\Stripe\Models\StripePaymentIntent) tracking PaymentIntents and their relationship to carts and orders.