Billing & Subscriptions Guide
Tenanto uses Stripe for subscription billing. This guide covers setting up Stripe and managing subscriptions.
Stripe Setup
Step 1: Create Stripe Account
- Go to stripe.com and create an account
- Complete business verification
- Enable test mode for development
Step 2: Get API Keys
- Go to Developers → API keys
- Copy your keys:
- Publishable key (pk_test_xxx or pk_live_xxx)
- Secret key (sk_test_xxx or sk_live_xxx)
Step 3: Configure Environment
Add to your .env file:
# Stripe API Keys
STRIPE_KEY=pk_test_your_publishable_key
STRIPE_SECRET=sk_test_your_secret_key
# Webhook Secret (see Step 5)
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
Step 4: Create Products and Prices
In Stripe Dashboard:
- Go to Products → Add product
- Create products for each plan:
Basic Plan:
- Name: Basic
- Price: $29/month (or your pricing)
- Copy the Price ID (price_xxx)
Pro Plan:
- Name: Pro
- Price: $79/month
- Copy the Price ID
Enterprise Plan:
- Name: Enterprise
- Price: $199/month
- Copy the Price ID
Step 5: Configure Price IDs
Add to .env:
STRIPE_PRICE_BASIC=price_xxx_basic
STRIPE_PRICE_PRO=price_xxx_pro
STRIPE_PRICE_ENTERPRISE=price_xxx_enterprise
Step 6: Set Up Webhooks
- Go to Developers → Webhooks
- Click Add endpoint
- Enter your webhook URL:
https://yourdomain.com/stripe/webhook - Select events to listen for:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedcustomer.subscription.trial_will_endinvoice.payment_failedinvoice.paid
- Copy the Signing secret to
STRIPE_WEBHOOK_SECRET
Plan Configuration
Plans are configured in config/billing.php:
'plans' => [
'basic' => [
'name' => 'Basic',
'stripe_price_id' => env('STRIPE_PRICE_BASIC'),
'features' => [
'users' => 5,
'teams' => 1,
'projects' => 10,
'api_access' => false,
'custom_domain' => false,
'priority_support' => false,
],
],
'pro' => [
'name' => 'Pro',
'stripe_price_id' => env('STRIPE_PRICE_PRO'),
'features' => [
'users' => 25,
'teams' => 5,
'projects' => 50,
'api_access' => true,
'custom_domain' => true,
'priority_support' => true,
],
],
'enterprise' => [
'name' => 'Enterprise',
'stripe_price_id' => env('STRIPE_PRICE_ENTERPRISE'),
'features' => [
'users' => -1, // -1 = unlimited
'teams' => -1,
'projects' => -1,
'api_access' => true,
'custom_domain' => true,
'priority_support' => true,
],
],
],
Adding a Custom Plan
- Create product/price in Stripe
- Add environment variable:
STRIPE_PRICE_CUSTOM=price_xxx - Add plan to
config/billing.php - Add enum case to
app/Domain/Billing/Enums/SubscriptionPlan.php
Trial Periods
Configure trial settings in config/billing.php:
'trial' => [
'enabled' => true,
'days' => 14,
],
How Trials Work
- New tenants automatically get a trial period
- Trial ends at
trial_ends_atdate - 3 days before expiry, users receive a notification
- After trial, tenant must subscribe or loses premium features
Subscription Lifecycle
Creating a Subscription
- User visits
/billing/plans - Clicks "Subscribe" on a plan
- Redirected to Stripe Checkout
- After payment, redirected back
- Webhook confirms subscription
- Tenant's plan is updated
Upgrading/Downgrading
- User visits
/billing - Clicks "Change Plan"
- Selects new plan
- Stripe prorates the charge
- Plan changes immediately
Canceling
- User visits
/billing - Clicks "Cancel Subscription"
- Subscription marked for cancellation
- Access continues until period end
- After period, reverts to free tier
Grace Period
Configure grace period in config/billing.php:
'grace_days' => 3,
After subscription ends, tenants have grace days before losing premium features.
Checking Plan Features
In your code, check if a tenant has a feature:
use App\Domain\Billing\Services\PlanFeatureService;
// Check if tenant can add more users
$featureService = app(PlanFeatureService::class);
$canAddUser = $featureService->canUseFeature($tenant, 'users');
// Get feature limit
$maxUsers = $featureService->getFeatureLimit($tenant, 'users');
// Returns -1 for unlimited
Available Feature Checks
users- Number of users allowedteams- Number of teams allowedprojects- Number of projects allowedapi_access- Whether API access is enabledcustom_domain- Whether custom domains are allowedpriority_support- Whether priority support is included
Testing with Stripe
Test Card Numbers
| Card | Number | Use Case |
|---|---|---|
| Visa | 4242 4242 4242 4242 | Successful payment |
| Visa | 4000 0000 0000 0002 | Declined |
| Visa | 4000 0000 0000 3220 | 3D Secure required |
Use any future expiry date and any 3-digit CVC.
Testing Webhooks Locally
Use Stripe CLI:
# Install Stripe CLI
# https://stripe.com/docs/stripe-cli
# Forward webhooks to local
stripe listen --forward-to localhost/stripe/webhook
# Use the provided webhook secret
Customer Portal
Stripe Customer Portal allows users to:
- Update payment methods
- View invoices
- Cancel subscriptions
Access via /billing → "Manage Subscription" button.
Configuring Customer Portal
- Go to Stripe Dashboard → Settings → Customer Portal
- Enable features:
- Invoice history
- Update payment methods
- Cancel subscriptions
- Customize branding
Notifications
Tenanto sends automatic notifications for:
| Event | Notification |
|---|---|
| Subscription created | SubscriptionCreated email |
| Trial ending (3 days) | TrialEnding email |
| Payment failed | PaymentFailed email |
| Subscription cancelled | SubscriptionCancelled email |
Customizing Notifications
Notification templates are in:
resources/views/vendor/notifications/
Or modify notification classes in:
app/Domain/Billing/Notifications/
Troubleshooting
"Webhook signature verification failed"
- Check
STRIPE_WEBHOOK_SECRETmatches Stripe Dashboard - Ensure raw request body is used (not parsed JSON)
- Verify no middleware is modifying the request
"No such price" error
- Verify price ID exists in Stripe
- Check you're using correct mode (test vs live)
- Ensure price is active
Subscription not updating after payment
- Check webhook endpoint is reachable
- Verify webhook events are selected
- Check Laravel logs for errors:
storage/logs/laravel.log
Going Live
Before going live:
- Replace test keys with live keys in
.env - Update webhook endpoint to live
- Test a real transaction with a small amount
- Enable live mode in Stripe Dashboard
Next Steps
- API Quick Start - Programmatic billing management
- Customization - Modify billing UI