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 ""

Billing Configuration.

Configure manual or Stripe-based billing for partner subscriptions.

May 1, 2026

v4 Status: The billing foundation is implemented and functional. Manual billing works out of the box. Stripe mode processes subscription lifecycle events automatically. Partners can view their plan, features, and usage from the Partner Billing Page. For the full end-to-end SaaS setup, see the SaaS Setup Guide.

Overview

Reward Loyalty v4 supports two billing modes:

Mode How It Works Best For
Manual (default) Admin assigns plans. You invoice externally. Agencies, network operators, white-label deployments
Stripe Stripe manages subscriptions via Laravel Cashier. Webhook events sync plan state automatically. Operators wanting automated subscription billing

Both modes use the same plan tier system (config/plans.php). The billing provider controls how subscription state is resolved. It does not change plan definitions, feature gates, or resource limits.


Manual Mode (Default)

No configuration needed. This is the default when BILLING_PROVIDER is not set in .env.

# .env - manual billing (or simply omit this line)
BILLING_PROVIDER=

How it works:

  • Admins assign plans via the partner management CRUD
  • The EntitlementService treats all partners as manual status (full plan access with no billing restrictions)
  • You handle invoicing through your accounting software (QuickBooks, Xero, bank transfers, etc.)

Health Center status: Billing Provider: Manual / Offline, always OK.


Stripe Mode

Stripe mode uses Laravel Cashier to manage subscriptions. When a partner's subscription state changes in Stripe, the webhook automatically syncs that state to the local database.

Required Environment Variables

Add these to your .env file:

# Billing provider
BILLING_PROVIDER=stripe

# Stripe API keys (from https://dashboard.stripe.com/apikeys)
STRIPE_KEY=pk_live_...
STRIPE_SECRET=sk_live_...

# Webhook signing secret (REQUIRED - see Webhook Setup below)
STRIPE_WEBHOOK_SECRET=whsec_...

⚠️ All three Stripe values are required. The billing provider is not considered configured until STRIPE_KEY, STRIPE_SECRET, and STRIPE_WEBHOOK_SECRET are all set. Without the webhook secret, the webhook endpoint actively rejects all requests with 403 Forbidden.

Stripe Price IDs

Each plan tier can have monthly and yearly Stripe Price IDs. Create your products and prices in the Stripe Dashboard, then map them:

# Tier 1 is typically the free tier - no price ID needed
# STRIPE_PRICE_TIER1_MONTHLY=
# STRIPE_PRICE_TIER1_YEARLY=

# Paid tiers
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...

These values are loaded through config/default.php (not env() at runtime), so they are safe with php artisan config:cache in production.

Webhook Setup

The webhook endpoint is:

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

Configure in Stripe Dashboard:

  1. Go to Stripe Dashboard → Webhooks
  2. Click Add endpoint
  3. Enter your webhook URL: https://your-domain.com/api/stripe/webhook
  4. Select events to listen for:
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • customer.updated
    • customer.deleted
  5. Click Add endpoint
  6. Copy the Signing secret (starts with whsec_)
  7. Add it to .env as STRIPE_WEBHOOK_SECRET

Security: When STRIPE_WEBHOOK_SECRET is not set, the webhook controller returns 403 Forbidden for all requests. It does not silently accept unsigned payloads. When the secret IS set, Laravel Cashier verifies the Stripe-Signature header on every request.

Subscription State Resolution

The billing provider resolves subscription state for the EntitlementService:

State Meaning
manual Billing disabled or null provider. Full plan access.
legacy Partner has a plan but no Stripe customer ID. Treated as pre-existing, with full access.
active Active Stripe subscription. Full plan access.
trialing Active trial period. Full plan access.
past_due Payment failed. Access continues during grace period.
canceled Subscription cancelled. Access continues until period end.
incomplete Initial payment pending. Limited access.
incomplete_expired Initial payment failed and expired. Subscription deleted.
unpaid Repeated payment failures. Access restricted.
paused Subscription paused. Access restricted.

Note: legacy and manual statuses never restrict access. Partners assigned plans before Stripe was enabled continue working without any billing configuration.


Health Center

The Health Center reports billing status in the Services category:

Status Meaning Action
Manual / Offline Manual billing mode. No Stripe needed. None
Stripe Stripe fully configured with API keys and webhook secret. None
⚠️ Stripe (not configured) BILLING_PROVIDER=stripe but API keys are missing. Add STRIPE_KEY and STRIPE_SECRET to .env
🔴 Stripe (webhook insecure) API keys present but STRIPE_WEBHOOK_SECRET is missing. Webhook rejects all requests. Add STRIPE_WEBHOOK_SECRET to .env

Background Processing

Stripe Webhooks

Stripe webhook processing is synchronous. No cron job or queue worker is required. When Stripe sends an event to /api/stripe/webhook, Cashier processes it in the HTTP request cycle and returns a response immediately. This means billing state updates happen in real time with no additional infrastructure.

Laravel Scheduler (Cron)

The Laravel scheduler handles background tasks like cache cleanup, scheduled notifications, and the Health Center heartbeat. All core features work without cron. It is recommended but not required. Without it, the Health Center reports a Scheduler Heartbeat warning, but billing and loyalty features continue working normally.

If you want to enable it, add this to your server's crontab:

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

See the Health Center documentation for verifying scheduler status.

Queue Workers

By default, Reward Loyalty uses the sync queue driver. All jobs run inline during the HTTP request. This works fine for small installations and manual billing mode.

For SaaS/high-volume deployments with Stripe billing, consider switching to database or redis queue drivers and running a persistent queue worker:

# .env
QUEUE_CONNECTION=database
php artisan queue:work --sleep=3 --tries=3

This offloads email notifications, campaign processing, and other background work from the web request cycle. The billing webhook itself remains synchronous regardless of your queue driver.

📖 Verify your setup: The Health Center shows scheduler heartbeat and queue driver status. Use it to confirm both are operational after deployment.


Partner Billing Page

Partners can access My Plan from the sidebar or user menu. The page displays:

  • Plan card: Current plan name, description, and subscription status badge (Stripe mode)
  • Feature gates: Visual grid of enabled/disabled features for the current plan
  • Resource usage: Progress bars showing usage vs. limits for each resource type
  • Status banners: Contextual alerts for trial, past due, cancelled, or suspended states
  • Plan comparison: Available plans with pricing (Stripe mode only)
  • Billing portal: "Manage Billing" button linking to Stripe's hosted portal for payment methods and invoices

Manual Mode

When billing is disabled or set to manual, partners see their plan details, features, and limits with a note to contact their administrator for plan changes.

Stripe Mode

When Stripe is configured, all partners see:

  • Subscription status badge (Active, Trial, Past Due, etc.), displayed for every partner when billing is enabled
  • Available plans comparison with pricing, displayed whenever Stripe is configured, regardless of the partner's Stripe status

Self-registered partners (those who signed up through the registration page) can self-service their billing:

  • Partners without an active subscription see Upgrade buttons on higher-tier plan cards. Clicking a button initiates a Stripe Checkout session. Cashier creates the Stripe customer automatically during the process.
  • Partners with an active subscription see Change Plan buttons and are redirected to the Stripe billing portal, which handles both upgrades and downgrades.

Admin-created and legacy partners (those created by an admin, imported from v3, or otherwise assigned a created_by value) see plan cards but no self-service action buttons. These partners must contact their administrator for plan changes.

How it works: The system distinguishes self-registered partners (created_by is null) from admin-created partners (created_by is set). This ensures that newly registered partners can subscribe immediately, while legacy accounts that predate Stripe integration remain under admin control until explicitly migrated.


Quick Reference

# === Billing Configuration (.env) ===

# Provider: leave empty for manual, set to "stripe" for Stripe
BILLING_PROVIDER=stripe

# Stripe credentials
STRIPE_KEY=pk_live_...
STRIPE_SECRET=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Price IDs (create in Stripe Dashboard → Products → Prices)
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_...