← Back to Laravel SaaS foundation team

Skill · Version 1.0.0

Stripe integration

Connects Stripe checkout and subscription events to verified local payment, invoice and entitlement states.

stripe-integration

Team members using this skill

See how it helps

When is it useful?

Use when implementing payments or changing subscription plans.

What does it help you do?

  • Chooses hosted Checkout or PaymentIntents for the actual flow.
  • Processes signed subscription and invoice events with duplicate protection.
  • Makes plan changes, prorations and API versions explicit.

Try asking this

Implement a subscription upgrade in test mode, showing pending payment and access changes after verified invoice events. Approve an upgrade whose invoice is still pending, then deliver confirmation twice. Activate the new entitlement once from verified payment state, preserving the agreed proration.

Detailed documentation

Read the skill’s original instructions, examples and checks in their authored language.

Details…Show less

Stripe Integration

When integrating Stripe, pin the API version explicitly, rely on Stripe's server-side objects as the source of truth for payment and subscription state, and handle asynchronous 3D Secure (SCA) and webhook lifecycles cleanly.

1. Checkout Sessions vs. PaymentIntents & Elements

  • Stripe Checkout (checkout.sessions.create):
    • Preferred for standard one-time purchases (mode: 'payment'), recurring subscriptions (mode: 'subscription') and saving payment methods (mode: 'setup').
    • Always pass client_reference_id and metadata: { userId, workspaceId, planId }, plus subscription_data.metadata for subscriptions so subsequent customer.subscription.* and invoice.* webhooks carry your tenant identifiers!
  • PaymentIntents + Payment Element:
    • Use when building a custom embedded checkout flow.
    • Create the PaymentIntent on the server with the server-calculated amount and currency, enabling automatic_payment_methods: { enabled: true }.
    • Handle requires_action (3DS/SCA redirects or modals) and processing (delayed bank debits like SEPA, ACH, Sofort) without assuming synchronous completion.

2. Subscription & Invoice Webhook Lifecycle

Fulfill and update recurring access from the canonical webhook sequence:

  • checkout.session.completed: links the new stripe_customer_id and stripe_subscription_id to your local tenant. (For delayed payment methods, also watch checkout.session.async_payment_succeeded / async_payment_failed.)
  • invoice.paid: confirms initial and recurring billing period payment; update current_period_start, current_period_end and reset/grant period quotas.
  • invoice.payment_failed: renewal attempt failed; transition local state to past_due, record grace-period deadline, and notify the customer to update their payment method via the Billing Portal (billingPortal.sessions.create).
  • customer.subscription.updated: handles plan upgrades/downgrades, quantity/seat changes, cancel_at_period_end toggles, trial endings and status transitions (trialing, active, past_due, unpaid, paused).
  • customer.subscription.deleted: subscription has terminated at period end or immediately; revoke paid subscription entitlements.

3. Upgrades, Downgrades & Proration Behavior

  • Immediate upgrade with payment guarantee: update the subscription item's price with proration_behavior: 'always_invoice' and payment_behavior: 'pending_if_incomplete' so the plan change only commits once the prorated invoice is paid.
  • End-of-period downgrade: use Subscription Schedules (subscriptionSchedules.create({ from_subscription })) to schedule the lower-tier price starting at current_period_end without immediate proration credits.
  • Previewing charges: use invoices.createPreview (or retrieveUpcoming depending on SDK version) so the customer sees the exact prorated amount due today and on the next renewal before confirming.

4. Stripe Security & Version Hygiene

  • Webhook verification: call stripe.webhooks.constructEvent(rawBodyBuffer, sigHeader, webhookSecret) using the raw unparsed request body.
  • API Version pinning: inspect the installed stripe SDK version in package.json and the pinned apiVersion before assuming newer fields (such as flexible billing or newer invoice preview methods).
  • Currency exponents: remember that Stripe treats HUF, ISK and UGX as two-decimal currencies for charges (multiply whole HUF by 100, e.g., 5000 HUF -> 500000), while JPY and KRW are zero-decimal.

Included files 1

  • SKILL.md