Skip to content

Instantly share code, notes, and snippets.

@MuhammadQuran17
Last active January 9, 2026 05:48
Show Gist options
  • Select an option

  • Save MuhammadQuran17/aadd1c12cbfdc2f91247ce541a9694e1 to your computer and use it in GitHub Desktop.

Select an option

Save MuhammadQuran17/aadd1c12cbfdc2f91247ce541a9694e1 to your computer and use it in GitHub Desktop.
cashier_stripe_laravel.md

Stripe

Cashier v15.7

  1. you should not seed/fill any migrations. Cashier migrations will be populated automatically after getting response from stripe.
  2. You need to duplicate Stripe's product name, price , description to show it on a pricing table in UI. Duplicate it in config file or in some db table
  3. $user->subscribed() checks wheter user is subscribed to any product.
  4. $user->subscribedToProduct('STRIPE_PRODUCT_ID') the parameter will be the stripe's product id.
  5. You should not use SAP when redirecting $request->user()>newSubscription()->checkout(), not use axios, or Inerti, just use window.location.href = route('subscribe', { plan: planId });
  6. pm_type, pm_last_four in users table will be filled only if user will select save his payment_card. when a user has a default payment method set on their Stripe customer.
  7. The ends_at in subscriptions table field is only populated when a subscription is canceled. For active subscriptions, this field should be null, which indicates the subscription is ongoing.
  8. if Subscription or invoice fails then Chashier will throw an Laravel\Cashier\Exceptions\ IncompletePayment exception. Stripe will send request to your webhook app when it will be failed.
  9. If something was changed in STRIPE then you will get it thortugh webhook
  10. Turn on Customer Portal in Stripe. It is useful to users to manage their subscription by themself. But in testing you can test only email verified user, so use your Stripe's admin email for this.
  11. There is a test cards for success and failure (for example not sufficient money)
  12. If you want to use flat fee + overage usage based model, then you should include 2 prices for subscription, like
    ->newSubscription('default', config("subscription-plans.plans.$planKey.stripe_price_id"))
    ->meteredPrice(config("subscription-plans.plans.$planKey.stripe_metered_price_id").
    if you have configured Meterd as in Stripe doc: https://docs.stripe.com/products-prices/pricing-models#fixed-fee-overage .
    Report metered events as : $user->reportMeterEvent('prompts'); no meter Id is required
  13. What if Webhook fails????

Webhook

  1. You can create listener and listen for (no need to register listener in Laravel auto binding is by default):
  • Looking at the webhook events you're receiving, for a one-time purchase system where you need to credit purchased prompts, you should use charge.succeeded as the primary event to listen to, as it's the most reliable indicator that payment has actually been successfully processed.

In production create a webhook using command: php artisan cashier:webhook ref . But it is not enough. Go to Stripe dashboard, on bottom click to 'Open Workbench' in Developers section, go to Webhooks, press back in breadcrumb to Event destinations, click 3 dots and Edit. Make sure that this events are turned on:

Here's a list of key webhook events you should enable for subscription, metered usage, and one-time product purchase scenarios:

Subscription Events

customer.subscription.created - When a new subscription is created customer.subscription.updated - When a subscription is updated (e.g., plan change) customer.subscription.deleted - When a subscription is canceled customer.subscription.trial_will_end - (Not NEEDED, Optional) Three days before a subscription trial ends invoice.payment_succeeded - When a subscription invoice is paid successfully invoice.payment_failed - When a subscription payment attempt fails invoice.upcoming - (Not NEEDED, Optional) Before a subscription invoice is created (customizable timing)

Metered Usage Events

invoice.created - When an invoice is created containing metered usage invoice.finalized - When a draft invoice with metered usage is finalized invoice.payment_succeeded - When a metered usage invoice is paid

One-Time Purchase Events

checkout.session.completed - When a one-time purchase Checkout flow completes payment_intent.succeeded - When a direct one-time payment succeeds payment_intent.payment_failed - When a one-time payment fails charge.succeeded - When a charge is successful charge.failed - When a charge fails

Other Important Events (Not NEEDED, Optional)

customer.created - When a new customer is created customer.updated - When customer details change payment_method.attached - When a new payment method is added to a customer payment_method.detached - When a payment method is removed

  1. If webhook failed then read here. And also here. Short: Stripe will auto send webhook up for 3 days, trying to reach your endpoint in a logoriphmic time progression .
  2. It's hard to test failure webhooks locally, so I skipped it.

Stripe specific

  1. While creating a product, if you SaaS B2C then select for Product Tax code SaaS personal use. if B2B then SaaS business use.
  2. Include tax in price: No - will add tax on top of your price . Customer will pay for tax, not you.
  3. In production, go to Tax settings from search on Stripe, select No for Include Tax, and go to next section and enable tax for subscription.

MoR's - LemonSqueezy, Paddle, Creem.io

Stripe is a payment processor that requires businesses to handle their own tax and compliance for international sales, while Lemon Squeezy acts as a Merchant of Record (MoR) that automatically manages taxes, compliance, and other administrative burdens. This makes Lemon Squeezy simpler for businesses, particularly smaller SaaS companies selling globally, while Stripe offers more flexibility and customization for those with technical resources and the desire to manage compliance themselves.

Tax

  1. If you use Stripe (No MoR) you can outsource Tax filling to https://trykintsugi.com/pricing .
  2. I use Polar and am happy with them taking on that burden, even if the fee is higher than stripe. Starting to hear a lot of buzz about Numeral too
  3. Kintsugi
  • Here more details from LLM's when to register and how to register

  • European Union (EU): For digital services like SaaS, the EU requires non-EU sellers to register for VAT from the very first sale to a consumer (B2C). You can use the EU's One-Stop Shop (OSS) to register in one EU country and file a single return for all EU sales.

  • For Non-UK Businesses to Sell in UK. For businesses based outside the UK, the rules are stricter: there is a zero-value threshold for the supply of digital services to UK consumers (B2C). This means a non-UK SaaS company must register for VAT with HM Revenue & Customs (HMRC) from its very first sale to a UK private consumer.

Stripe Tax

  1. If you started global business, then please enable Stripe Tax from first transaction, because some EU countries can charge VAT from first transaction. UK definetly.
  2. For US states it differs from state to state. Example Maine : Nexus/Treshold is : 100,000$ or 200 transactions

Other Payment methods

  1. Paddle support Uzb. It pays 12% VAT in Uzb. Payout through Wire (SWIFT), Payoneer, PayPal.
  2. Laravel Spark is not needed if u are using Stripe. It is equal to Stripe Checkout page.

How to create a webhook in Stripe

For local development with Stripe CLI:
You don't need to register a webhook endpoint in the Dashboard
It is required only in a production.

If you are using Cashier you can create webhoock via: php artisan cashier:webhook --url "https://example.com/stripe/webhook" Because it will use appropriate version of Stipr API for Cashier

  1. Navigate to the Webhooks section:

Go to the Webhooks tab in Workbench

  1. Create a new webhook endpoint:

Click Create an event destination or Add endpoint button Configure the webhook source:

  1. Select Your account to listen to events from your own Stripe account Choose your preferred API version (usually the latest version) Select the webhook event:

  2. In the event selection screen, find and select customer.subscription.created and others

  3. Set up the destination:

Click Continue
Select Webhook endpoint as the destination type
Click Continue again

  1. Add your endpoint details:

Enter your publicly accessible HTTPS URL where Stripe should send events
The URL format should be: https://your-domain.com/your-webhook-path in cashier it is stripe/webhook
Optionally add a description for this webhook endpoint

  1. Create and secure the endpoint:

Click Create destination
Copy the generated webhook signing secret (it starts with whsec_) to validate incoming requests
Implement webhook handling in your application code:

  1. Verify the signature of incoming webhook events using the secret Process the customer.subscription.created event data
    Test your webhook:

Use the Stripe CLI to forward events to your local environment during development
Run stripe listen --forward-to localhost:4242/stripe/webhook (adjust the port/path as needed)
Test by creating a subscription through Checkout

9/14/25

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment