Skip to content
ESC

Searching...

Quick Links

Type to search • Press to navigate • Enter to select

Keep typing to search...

No results found

No documentation matches ""

Stripe Billing.

Set up Stripe subscription billing for partners, step by step.

Jun 22, 2026

This is the full setup guide for Stripe. For the billing overview, the plan tiers, and the other modes, see Billing Configuration.

What this mode does

Stripe runs your partner subscriptions through Laravel Cashier. A partner subscribes with Stripe Checkout, and Stripe calls a webhook on every change. The app reads that change and sets the partner's plan. Existing subscribers manage their card, invoices, upgrades, and downgrades in Stripe's hosted billing portal.

When to use it

Use Stripe when you want automated card billing with self-service upgrades. Partners pay by card, Stripe handles the checkout and the hosted portal, and the app keeps each partner's plan in step through the webhook.

Before you start

⚠️ Keep test and live separate. Stripe keeps test mode and live mode apart, including the API keys, the price IDs, and the webhook signing secret. Build and confirm everything in test mode first, then repeat the same steps in live mode. Never mix a test value with a live value.

Step 1: Get your API keys

  1. Sign in to the Stripe Dashboard.
  2. Set the dashboard mode switch to Test mode while you set up. Turn it off later for live.
  3. Open Developers → API keys (direct link: dashboard.stripe.com/apikeys).
  4. Copy the Publishable key (pk_test_… in test, pk_live_… in live).
  5. In the Secret key row, click Reveal and copy it (sk_test_… or sk_live_…).

Keep the secret key private. Test keys and live keys are different values, so copy the pair that matches the mode you are configuring.

Step 2: Create a product and its prices

  1. Open the Product catalog (direct link: dashboard.stripe.com/products) and click Add product.
  2. Name the product after your plan tier, for example "Gold".
  3. Under Pricing, add a Recurring price for the monthly cycle. Add a second recurring price for the yearly cycle. Save.
  4. Open each price and copy its API ID. A price ID starts with price_…. Copy the price ID, not the product ID (prod_…).

Create one product per paid tier, each with a monthly and a yearly price. Tier 1 is free in the stock plans, so it needs no price. See Stripe's manage prices for more.

Step 3: Add the keys and price IDs to .env

# Billing provider
BILLING_PROVIDER=stripe

# Stripe API keys (Developers → API keys)
STRIPE_KEY=pk_live_...
STRIPE_SECRET=sk_live_...

# Webhook signing secret (you get this in Step 4)
STRIPE_WEBHOOK_SECRET=whsec_...

# Price IDs: one monthly and one yearly per paid tier.
# Tier 1 is the free tier, so it needs no price ID.
STRIPE_PRICE_TIER2_MONTHLY=price_1Abc...
STRIPE_PRICE_TIER2_YEARLY=price_1Def...
STRIPE_PRICE_TIER3_MONTHLY=price_1Ghi...
STRIPE_PRICE_TIER3_YEARLY=price_1Jkl...
STRIPE_PRICE_TIER4_MONTHLY=price_1Mno...
STRIPE_PRICE_TIER4_YEARLY=price_1Pqr...

⚠️ All three credentials are required. The provider stays unconfigured until STRIPE_KEY, STRIPE_SECRET, and STRIPE_WEBHOOK_SECRET are all set. Without the webhook secret, the webhook endpoint rejects every request with 403 Forbidden.

Price IDs load through config/default.php, not env() at runtime, so they keep working with php artisan config:cache in production.

Step 4: Create the webhook

Your webhook endpoint is:

https://your-domain.com/api/stripe/webhook

New Stripe accounts use Workbench. Open Workbench from the dashboard and select the Webhooks tab:

  1. Click Create new destination.
  2. Keep the default API version, choose Events on your account, and click Continue.
  3. Select these event types, then click Continue:
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • customer.updated
    • customer.deleted
  4. Choose Webhook as the destination type.
  5. In Endpoint URL, enter https://your-domain.com/api/stripe/webhook, then click Create destination.

Older accounts use the Developers Dashboard: open Developers → Webhooks, click Create an event destination (labelled Add endpoint on some accounts), enter the Endpoint URL, pick the same events under Select events, and click Add endpoint.

Open the new endpoint, find Signing secret, click Click to reveal, and copy the value. It starts with whsec_. Put it in .env as STRIPE_WEBHOOK_SECRET. The signing secret is separate from your API keys, and it differs between test and live, so create the webhook in the same mode as your keys. See Stripe's webhooks guide for more.

Step 5: Confirm

Open the Health Center. The Billing Provider check reads Stripe in green once the keys and the webhook secret are all set. To confirm delivery, send a test event from the webhook's page in Stripe and check that it returns 200.

Test before you go live

  1. With test-mode keys, the test price IDs, and a test-mode webhook in place, register a test partner and subscribe through Stripe Checkout with a Stripe test card.
  2. Confirm the partner's My Plan page shows the new tier and an Active badge.
  3. When the flow works end to end, repeat Steps 1 to 4 in live mode: copy the live keys, create live prices, and create a live-mode webhook. Put the live values in .env.

What partners see on My Plan

Every partner sees a subscription status badge and the available-plans comparison whenever Stripe is configured, with a monthly and yearly toggle.

Self-registered partners (signed up through the registration page) manage their own billing:

  • Without an active subscription, they see Upgrade on higher tiers. The button opens a Stripe Checkout session, and Cashier creates the Stripe customer during checkout.
  • With an active subscription, they see Change Plan and go to the Stripe hosted portal, which handles upgrades and downgrades.

Admin-created and legacy partners (those with a created_by value, including v3 imports) see the plan cards but no self-service buttons. They contact their administrator for changes.

Subscription states

The provider maps Stripe state onto the shared entitlement vocabulary (see the state reference):

Stripe status Entitlement state Access
(no Stripe customer) legacy Full plan access (pre-billing)
active active Full plan access
trialing trialing Full plan access
past_due past_due Access during the grace period
canceled cancelled Access until the period ends
incomplete incomplete Limited until the first payment clears
incomplete_expired cancelled Restricted; subscription deleted
unpaid suspended Restricted
paused suspended Restricted

legacy never restricts access. Partners assigned a plan before you enabled Stripe keep working with no billing setup.

Webhook synchronization safety

The Stripe webhook handler is hardened so a partner can never reach a paid tier they did not pay for:

  • Reads the database, not the payload. After Cashier processes the event, StripeWebhookController reads the local Cashier subscription row, rather than trusting raw values in the incoming payload.
  • Active-subscription guard. It moves the plan only for active or trialing subscriptions. It blocks past_due, incomplete, canceled, and incomplete_expired from granting a paid tier.
  • Strict price-ID mapping. It reverse-maps the active Stripe Price ID against config/default.php to find the tier. It ignores an unknown price ID.
  • Permission sync. When a plan change passes the guards, it derives and saves the partner's feature gates, resource limits, and metadata so entitlements match the new tier.

Troubleshooting

Symptom Likely cause Resolution
Webhook deliveries return 403 STRIPE_WEBHOOK_SECRET is missing Reveal the signing secret on the webhook endpoint and set it in .env. The Health Center shows Stripe (webhook insecure) when it is missing.
Signature verification fails The signing secret is from the other mode The secret differs between test and live. Use the secret from the same mode as your keys.
Webhook returns 200 but the plan does not change The subscription is not active or trialing, or the price ID is not mapped Confirm the STRIPE_PRICE_* mapping holds the active price ID, and that the subscription is active.
Checkout fails with "No such price" The price ID is from the other mode, or it is the product ID Use a price_… ID, not a prod_… ID, from the same mode as your keys.
Partner sees no Upgrade buttons The partner is admin-created (created_by is set) Admin-created and legacy partners are admin-managed. Change their plan from the admin screens.
Keys or webhook rejected after go-live A test value is mixed with live values Use live keys, live price IDs, and a live-mode webhook together.

Quick reference

# === Stripe (.env) ===

BILLING_PROVIDER=stripe

# Credentials: Developers → API keys
STRIPE_KEY=pk_live_...
STRIPE_SECRET=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...   # Step 4, reveal on the webhook endpoint

# Price IDs: Product catalog → your product → each price's API ID (price_...)
STRIPE_PRICE_TIER2_MONTHLY=price_...
STRIPE_PRICE_TIER2_YEARLY=price_...
STRIPE_PRICE_TIER3_MONTHLY=price_...
STRIPE_PRICE_TIER3_YEARLY=price_...
STRIPE_PRICE_TIER4_MONTHLY=price_...
STRIPE_PRICE_TIER4_YEARLY=price_...