Laravel Billing (Cashier)
Agent Workflow (MANDATORY)
Before ANY implementation, use TeamCreate to spawn 3 agents:
- •fuse-ai-pilot:explore-codebase - Check existing billing setup, User model
- •fuse-ai-pilot:research-expert - Verify latest Cashier docs via Context7
- •mcp__context7__query-docs - Query specific patterns (Stripe/Paddle)
After implementation, run fuse-ai-pilot:sniper for validation.
Overview
Laravel Cashier provides subscription billing with Stripe or Paddle. Choose based on your needs:
| Provider | Package | Best For |
|---|---|---|
| Stripe | laravel/cashier | Full control, high volume, complex billing |
| Paddle | laravel/cashier-paddle | Tax handling, compliance, global sales |
Key Difference: MoR vs Payment Processor
| Aspect | Stripe | Paddle |
|---|---|---|
| Type | Payment Processor | Merchant of Record |
| Taxes | You manage (or Stripe Tax) | Paddle manages automatically |
| Invoices | Your company name | Paddle + your name |
| Compliance | Your responsibility | Paddle handles |
| Fees | ~2.9% + $0.30 | ~5% + $0.50 (all-inclusive) |
Critical Rules
- •Use webhooks - Never rely on client-side confirmations
- •Handle grace periods - Allow access until subscription ends
- •Never store card details - Use payment tokens/methods
- •Test with test keys - Always before production
- •Verify webhook signatures - Prevent spoofing attacks
- •Handle incomplete payments - 3D Secure requires user action
Architecture
code
app/
├── Http/
│ ├── Controllers/
│ │ └── Billing/ ← Billing controllers
│ │ ├── SubscriptionController.php
│ │ ├── CheckoutController.php
│ │ └── InvoiceController.php
│ └── Middleware/
│ └── EnsureSubscribed.php ← Subscription check
├── Models/
│ └── User.php ← Billable trait
├── Listeners/
│ └── StripeEventListener.php ← Webhook handling
└── Services/
└── BillingService.php ← Business logic
config/
├── cashier.php ← Stripe/Paddle config
└── services.php ← API keys
routes/
└── web.php ← Webhook routes (excluded from CSRF)
FuseCore Integration
When working in a FuseCore project, billing follows the modular structure:
code
FuseCore/ ├── Core/ # Infrastructure (priority 0) │ └── App/Contracts/ │ └── BillingServiceInterface.php ← Billing contract │ ├── User/ # Auth module (existing) │ └── App/Models/User.php ← Add Billable trait here │ ├── Billing/ # Billing module (new) │ ├── App/ │ │ ├── Http/ │ │ │ ├── Controllers/ │ │ │ │ ├── SubscriptionController.php │ │ │ │ ├── CheckoutController.php │ │ │ │ └── WebhookController.php │ │ │ └── Middleware/ │ │ │ └── EnsureSubscribed.php │ │ ├── Listeners/ │ │ │ └── HandleWebhookEvents.php │ │ └── Services/ │ │ └── BillingService.php │ ├── Config/ │ │ └── cashier.php ← Module-level config │ ├── Database/Migrations/ │ ├── Routes/ │ │ ├── web.php ← Webhooks (no CSRF) │ │ └── api.php ← Subscription management │ └── module.json # dependencies: ["User"]
FuseCore Billing Checklist
- • Billing code in
/FuseCore/Billing/module - • Billable trait on User model in
/FuseCore/User/ - • Webhook routes in
/FuseCore/Billing/Routes/web.php - • Exclude webhook from CSRF in
VerifyCsrfToken - • Declare
"User"dependency inmodule.json
→ See fusecore skill for complete module patterns.
Decision Guide
Stripe vs Paddle
code
Selling to businesses (B2B)? → Stripe
├── Need OAuth for third-party apps? → Stripe Connect
└── Selling to consumers (B2C) globally?
├── Want to handle taxes yourself? → Stripe + Stripe Tax
└── Want tax compliance handled? → Paddle
Subscription vs One-Time
code
Recurring revenue? → Subscription ├── Fixed plans? → Single-price subscription └── Usage-based? → Metered billing (Stripe) or quantity-based Single purchase? → One-time charge ├── Digital product? → Checkout session └── Service fee? → Direct charge
Key Concepts
| Concept | Description | Reference |
|---|---|---|
| Billable | Trait that enables billing on a model | stripe.md |
| Subscription | Recurring billing cycle | subscriptions.md |
| Price ID | Stripe/Paddle price identifier | stripe.md |
| Grace Period | Time after cancellation with access | subscriptions.md |
| Webhook | Server-to-server payment notifications | webhooks.md |
| Customer Portal | Self-service billing management | checkout.md |
Reference Guide
Concepts (WHY & Architecture)
| Topic | Reference | When to Consult |
|---|---|---|
| Stripe Cashier | stripe.md | Stripe setup, configuration |
| Paddle Cashier | paddle.md | Paddle setup, differences |
| Subscriptions | subscriptions.md | Create, cancel, swap, pause |
| Webhooks | webhooks.md | Webhook security, handling |
| Invoices | invoices.md | PDF generation, receipts |
| Payment Methods | payment-methods.md | Cards, wallets, updates |
| Checkout | checkout.md | Hosted checkout, portal |
| Testing | testing.md | Test cards, webhook testing |
Advanced SaaS Features
| Topic | Reference | When to Consult |
|---|---|---|
| Metered Billing | metered-billing.md | Usage-based pricing (API, storage) |
| Team Billing | team-billing.md | Organization billing, per-seat |
| Dunning | dunning.md | Failed payment recovery |
| Feature Flags | feature-flags.md | Plan-based feature access |
Templates (Complete Code)
| Template | When to Use |
|---|---|
| UserBillable.php.md | User model with Billable trait |
| SubscriptionController.php.md | CRUD subscription operations |
| WebhookController.php.md | Custom webhook handling |
| CheckoutController.php.md | Stripe Checkout + Portal |
| InvoiceController.php.md | Invoice download |
| BillingRoutes.php.md | Complete route definitions |
| SubscriptionTest.php.md | Pest tests for billing |
| MeteredBillingController.php.md | Usage tracking and reporting |
| TeamBillable.php.md | Team model with seat management |
| DunningService.php.md | Payment recovery automation |
| FeatureFlags.php.md | Laravel Pennant per-plan features |
Quick Reference
Check Subscription Status
php
// Has active subscription?
$user->subscribed('default');
// Subscribed to specific price?
$user->subscribedToPrice('price_premium', 'default');
// On trial?
$user->onTrial('default');
// Cancelled but still active?
$user->subscription('default')->onGracePeriod();
Create Subscription
php
// Simple subscription
$user->newSubscription('default', 'price_monthly')
->create($paymentMethodId);
// With trial
$user->newSubscription('default', 'price_monthly')
->trialDays(14)
->create($paymentMethodId);
Manage Subscription
php
$subscription = $user->subscription('default');
// Change plan
$subscription->swap('price_yearly');
// Cancel at period end
$subscription->cancel();
// Cancel immediately
$subscription->cancelNow();
// Resume cancelled subscription
$subscription->resume();
Billing Portal
php
// Redirect to customer portal (Stripe)
return $user->redirectToBillingPortal(route('dashboard'));
// Get portal URL
$url = $user->billingPortalUrl(route('dashboard'));
Best Practices
DO
- •Use webhooks for payment confirmation
- •Implement grace periods for cancelled subscriptions
- •Set up webhook signature verification
- •Handle
IncompletePaymentexceptions - •Test with Stripe CLI locally
- •Prune old data regularly
DON'T
- •Trust client-side payment confirmations
- •Store card numbers (PCI compliance)
- •Skip webhook verification
- •Ignore failed payment webhooks
- •Forget to handle 3D Secure
- •Hardcode prices (use env or config)