laravel-billing
SKILL.md
Laravel Billing (Cashier)
Agent Workflow (MANDATORY)
Before ANY implementation, launch in parallel:
- 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
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:
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
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
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
// 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
// Simple subscription
$user->newSubscription('default', 'price_monthly')
->create($paymentMethodId);
// With trial
$user->newSubscription('default', 'price_monthly')
->trialDays(14)
->create($paymentMethodId);
Manage Subscription
$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
// 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)