Overview
Stripe is a full-featured payment gateway that supports subscriptions, one-time payments, refunds, and more. Eufaturo Billing provides seamless Stripe integration through the eufaturo/billing-stripe package.
Stripe is the recommended gateway for most applications due to its comprehensive feature set, excellent documentation, and global reach.
Features
The Stripe integration supports:
✅ Subscription Management - Recurring billing with automatic charges
✅ One-Time Payments - Single purchases and orders
✅ Hosted Checkout - Stripe Checkout for payment collection
✅ Customer Portal - Self-service subscription management
✅ Refunds - Full and partial refund support
✅ Payment Methods - Cards, digital wallets (Apple Pay, Google Pay)
✅ Webhooks - Real-time event notifications
✅ Test Mode - Sandbox environment for development
✅ Proration - Automatic proration on plan changes
✅ Tax Calculation - Stripe Tax integration
Installation
Step 1: Install the Package
composer require eufaturo/billing-stripe
The package will auto-register via Laravel package discovery.
Step 2: Publish Configuration
php artisan vendor:publish --tag=billing-stripe-config
This creates config/billing-stripe.php:
return [
// Stripe API keys
'public_key' => env('STRIPE_PUBLIC_KEY'),
'secret_key' => env('STRIPE_SECRET_KEY'),
// Webhook configuration
'webhook' => [
'secret' => env('STRIPE_WEBHOOK_SECRET'),
'tolerance' => 300, // seconds
],
// Checkout configuration
'checkout' => [
'success_url' => env('STRIPE_SUCCESS_URL', '/checkout/success?session_id={CHECKOUT_SESSION_ID}'),
'cancel_url' => env('STRIPE_CANCEL_URL', '/checkout/cancel'),
],
// Customer portal
'customer_portal' => [
'enabled' => env('STRIPE_CUSTOMER_PORTAL_ENABLED', true),
],
];
Add your Stripe credentials to .env:
# Get these from https://dashboard.stripe.com/apikeys
STRIPE_PUBLIC_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
# Webhook signing secret (we'll configure this later)
STRIPE_WEBHOOK_SECRET=whsec_...
# Optional: Customize URLs
STRIPE_SUCCESS_URL=/billing/success?session_id={CHECKOUT_SESSION_ID}
STRIPE_CANCEL_URL=/billing/cancel
Never commit your secret key to version control! Use environment variables only.
Basic Usage
Getting the Stripe Gateway
use Eufaturo\Billing\Billing;
$stripe = Billing::gateways()->get('stripe');
Creating a Subscription
use Eufaturo\Billing\Billing;
use Eufaturo\Billing\Subscriptions\Enums\SubscriptionType;
// Get the plan
$plan = Billing::plans()->find('pro-monthly');
// Get Stripe gateway
$stripe = Billing::gateways()->get('stripe');
// Create subscription (gateway-managed)
$subscription = Billing::subscriptions()->create(
billable: $user,
plan: $plan,
type: SubscriptionType::PaymentGatewayManaged,
paymentGateway: $stripe,
);
// Redirect to Stripe Checkout
return redirect($subscription->checkout_url);
One-Time Payment
// Create order
$order = Billing::orders()->create(
billable: $user,
productPlan: $plan,
quantity: 1,
);
// Create checkout session
$checkout = $stripe->createPaymentCheckout(
billable: $user,
order: $order,
successUrl: route('payment.success'),
cancelUrl: route('payment.cancel'),
);
// Redirect to Stripe Checkout
return redirect($checkout->url);
Stripe Checkout Flow
1. Create Checkout Session
When a customer subscribes or makes a payment, create a Stripe Checkout session:
public function subscribe(Request $request)
{
$user = auth()->user();
$plan = Billing::plans()->find($request->input('plan'));
$stripe = Billing::gateways()->get('stripe');
// Create subscription
$subscription = Billing::subscriptions()->create(
billable: $user,
plan: $plan,
type: SubscriptionType::PaymentGatewayManaged,
paymentGateway: $stripe,
);
// Redirect to Stripe Checkout
return redirect($subscription->checkout_url);
}
2. Handle Success
After successful payment, Stripe redirects to your success URL:
Route::get('/checkout/success', function (Request $request) {
$sessionId = $request->get('session_id');
if (!$sessionId) {
return redirect()->route('pricing')->with('error', 'Invalid session');
}
$stripe = Billing::gateways()->get('stripe');
// Retrieve checkout session
$session = $stripe->retrieveSubscriptionCheckout($sessionId);
// Find subscription by metadata
$subscription = Subscription::where('id', $session->metadata['subscription_id'])->firstOrFail();
// Update subscription with Stripe data
$subscription->update([
'status' => SubscriptionStatus::Active,
]);
// Store gateway record
$subscription->gatewayRecords()->updateOrCreate(
['payment_gateway_id' => $stripe->id],
['external_id' => $session->id],
);
return redirect()->route('dashboard')->with('success', 'Welcome! Your subscription is now active.');
});
3. Handle Cancellation
If the customer cancels the checkout:
Route::get('/checkout/cancel', function () {
return redirect()->route('pricing')->with('info', 'Checkout cancelled. You can try again anytime.');
});
Subscription Management
Cancel Subscription
use Eufaturo\Billing\Billing;
$subscription = $user->subscription();
$stripe = Billing::gateways()->get('stripe');
// Get Stripe subscription ID
$externalId = $subscription->getExternalIdForGateway($stripe);
// Cancel immediately
$stripe->cancelSubscription($externalId, immediately: true);
// Or cancel at period end
$stripe->cancelSubscription($externalId, immediately: false);
// Update local subscription
$subscription->update([
'status' => SubscriptionStatus::Cancelled,
'ends_at' => now(),
]);
Resume Subscription
// Resume a subscription cancelled at period end
if ($subscription->status === SubscriptionStatus::Cancelled && $subscription->ends_at->isFuture()) {
$externalId = $subscription->getExternalIdForGateway($stripe);
$stripe->resumeSubscription($externalId);
$subscription->update([
'status' => SubscriptionStatus::Active,
'ends_at' => null,
]);
}
Change Plan
$newPlan = Billing::plans()->find('enterprise-monthly');
$externalId = $subscription->getExternalIdForGateway($stripe);
// Stripe handles proration automatically
$stripe->updatePlan($externalId, $newPlan, prorate: true);
// Update local subscription
$subscription->update([
'product_plan_id' => $newPlan->id,
'price' => $newPlan->price,
]);
Update Payment Method
// Get new payment method ID from Stripe.js
$paymentMethodId = $request->input('payment_method_id');
$externalId = $subscription->getExternalIdForGateway($stripe);
$stripe->updatePaymentMethod($externalId, $paymentMethodId);
Webhooks
Webhooks allow Stripe to notify your application of events in real-time.
The Stripe package automatically registers a webhook route:
Step 2: Create Webhook in Stripe Dashboard
-
Go to Stripe Dashboard → Developers → Webhooks
-
Click Add endpoint
-
Enter your webhook URL:
https://your domain.com/webhooks/stripe
-
Select events to listen for:
checkout.session.completed
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
invoice.paid
invoice.payment_failed
payment_intent.succeeded
payment_intent.payment_failed
-
Click Add endpoint
-
Copy the Signing secret (starts with
whsec_)
Step 3: Add Signing Secret to .env
STRIPE_WEBHOOK_SECRET=whsec_...
Step 4: Webhook Events
The Stripe integration automatically handles these events:
checkout.session.completed
Fired when a customer completes checkout:
// Automatically handled by StripeWebhookHandler
// - Activates subscription
// - Creates gateway record
// - Records transaction
customer.subscription.updated
Fired when subscription changes:
// Automatically handled
// - Updates subscription status
// - Updates plan if changed
// - Syncs trial dates
customer.subscription.deleted
Fired when subscription is cancelled:
// Automatically handled
// - Marks subscription as cancelled
// - Sets ends_at date
invoice.paid
Fired when invoice is successfully paid:
// Automatically handled
// - Records transaction
// - Updates subscription status
invoice.payment_failed
Fired when payment fails:
// Automatically handled
// - Marks subscription as past_due
// - Records failed transaction
// - Triggers notification
Custom Webhook Handling
You can listen to Stripe events in your own code:
use Eufaturo\BillingStripe\Events\StripeWebhookReceived;
Event::listen(StripeWebhookReceived::class, function ($event) {
if ($event->type === 'customer.subscription.updated') {
$subscription = $event->data['object'];
// Your custom logic here
Log::info('Subscription updated', ['id' => $subscription['id']]);
}
});
Testing
Test Mode
Stripe provides a test environment with test API keys:
# Use test keys for development
STRIPE_PUBLIC_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
Test Card Numbers
Use these test cards in Stripe Checkout:
| Card Number | Description |
4242424242424242 | Successful payment |
4000000000000002 | Card declined |
4000002500003155 | Requires authentication (3D Secure) |
4000000000009995 | Insufficient funds |
Expiry: Any future date
CVC: Any 3 digits
Postal Code: Any 5 digits
Full list: https://stripe.com/docs/testing
Testing Webhooks Locally
Use Stripe CLI to forward webhooks to your local environment:
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks
stripe listen --forward-to localhost:8000/webhooks/stripe
The CLI will output a webhook signing secret. Add it to .env:
STRIPE_WEBHOOK_SECRET=whsec_...
Triggering Test Events
# Trigger a successful payment
stripe trigger checkout.session.completed
# Trigger a failed payment
stripe trigger invoice.payment_failed
# Trigger subscription cancellation
stripe trigger customer.subscription.deleted
Customer Portal
Stripe Customer Portal allows customers to manage their subscriptions self-service.
Enable Customer Portal
- Go to Stripe Dashboard → Settings → Billing → Customer portal
- Configure portal settings:
- Allow customers to update payment methods
- Allow customers to cancel subscriptions
- Configure cancellation behavior (immediately vs. end of period)
Generate Portal Link
use Stripe\StripeClient;
public function customerPortal()
{
$user = auth()->user();
$stripe = new StripeClient(config('billing-stripe.secret_key'));
// Get Stripe customer ID
$stripeGateway = Billing::gateways()->get('stripe');
$customerRecord = $user->getGatewayRecordForGateway($stripeGateway);
// Create portal session
$session = $stripe->billingPortal->sessions->create([
'customer' => $customerRecord->external_id,
'return_url' => route('billing.settings'),
]);
return redirect($session->url);
}
{{-- In your billing settings view --}}
<a href="{{ route('customer-portal') }}" class="btn">
Manage Subscription
</a>
Advanced Features
Attach custom data to Stripe objects:
$checkout = $stripe->createSubscriptionCheckout(
billable: $user,
plan: $plan,
successUrl: route('checkout.success'),
cancelUrl: route('checkout.cancel'),
metadata: [
'user_id' => $user->id,
'plan_id' => $plan->id,
'source' => 'marketing_campaign_2024',
],
);
Metadata is returned in webhooks and can be used for tracking.
Trial Periods
$subscription = Billing::subscriptions()->create(
billable: $user,
plan: $plan,
type: SubscriptionType::PaymentGatewayManaged,
paymentGateway: $stripe,
trialEnds: now()->addDays(14), // 14-day trial
);
Stripe won’t charge until the trial ends.
Coupons and Discounts
// Create Stripe coupon
$stripe = new StripeClient(config('billing-stripe.secret_key'));
$coupon = $stripe->coupons->create([
'percent_off' => 20,
'duration' => 'forever',
'id' => 'SAVE20',
]);
// Apply during checkout
$checkout = $stripe->createSubscriptionCheckout(
billable: $user,
plan: $plan,
successUrl: route('checkout.success'),
cancelUrl: route('checkout.cancel'),
metadata: ['coupon' => 'SAVE20'],
);
Stripe Tax
Enable automatic tax calculation:
- Go to Stripe Dashboard → Settings → Tax
- Enable Stripe Tax
- Tax will be calculated automatically during checkout
Troubleshooting
”No such customer” Error
Problem: Stripe customer doesn’t exist.
Solution:
// Ensure customer is created before creating subscription
$customer = $stripe->createOrRetrieveCustomer($user);
Webhook Signature Verification Failed
Problem: Webhook signature doesn’t match.
Solutions:
- Verify
STRIPE_WEBHOOK_SECRET is correct
- Check webhook endpoint URL is correct in Stripe Dashboard
- Ensure webhook route is not behind CSRF protection:
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'webhooks/*',
];
Test Cards Not Working
Problem: Test cards declined in test mode.
Solutions:
- Verify you’re using test API keys (
pk_test_ and sk_test_)
- Use correct test card numbers from Stripe documentation
- Check Stripe Dashboard for error messages
Subscription Not Activating
Problem: Subscription stays in “new” status after payment.
Solutions:
- Verify webhook is configured and receiving events
- Check webhook signing secret is correct
- Look for errors in webhook logs (Stripe Dashboard → Developers → Webhooks → Your endpoint)
Best Practices
✅ Do
-
Use test mode during development
STRIPE_PUBLIC_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
-
Verify webhooks before processing
// Automatically handled by StripeWebhookHandler
-
Handle failed payments gracefully
Event::listen(StripeInvoicePaymentFailed::class, function ($event) {
// Notify user, retry payment, etc.
});
-
Store customer IDs for future reference
$user->gatewayRecords()->create([
'payment_gateway_id' => $stripe->id,
'external_id' => $customer->id,
]);
❌ Don’t
- Don’t skip webhook verification - always verify signatures
- Don’t commit API keys to version control
- Don’t rely on redirects for critical updates - use webhooks
- Don’t create duplicate customers - check for existing first
Next Steps
See Also