Build Paystack Subscriptions in Laravel
To build Paystack subscriptions in Laravel, create a plan via the API, initialize a transaction with the plan code, handle subscription webhooks with Jobs, track status in an Eloquent model, and use middleware to gate premium features behind active subscription checks.
Subscription Model
// database/migrations/create_subscriptions_table.php
Schema::create('subscriptions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('subscription_code')->unique();
$table->string('plan_code');
$table->string('email_token');
$table->string('status')->default('active');
$table->timestamp('next_payment_date')->nullable();
$table->timestamps();
});
Subscribe a Customer
public function subscribe(Request $request)
{
$response = Http::withToken(config('services.paystack.secret_key'))
->post('https://api.paystack.co/transaction/initialize', [
'email' => auth()->user()->email,
'plan' => $request->plan_code,
'callback_url' => route('subscription.callback'),
]);
$data = $response->json();
if ($data['status'] ?? false) {
return redirect($data['data']['authorization_url']);
}
return back()->with('error', $data['message'] ?? 'Failed');
}
Webhook Handlers
// In ProcessPaystackWebhook Job
case 'subscription.create':
$user = \App\Models\User::where('email', $data['customer']['email'])->first();
if ($user) {
\App\Models\Subscription::updateOrCreate(
['user_id' => $user->id],
[
'subscription_code' => $data['subscription_code'],
'plan_code' => $data['plan']['plan_code'],
'email_token' => $data['email_token'] ?? '',
'status' => 'active',
'next_payment_date' => $data['next_payment_date'] ?? null,
]
);
}
break;
case 'invoice.payment_failed':
$subCode = $data['subscription']['subscription_code'] ?? null;
if ($subCode) {
\App\Models\Subscription::where('subscription_code', $subCode)
->update(['status' => 'past_due']);
}
break;
case 'subscription.disable':
$subCode = $data['subscription_code'] ?? null;
if ($subCode) {
\App\Models\Subscription::where('subscription_code', $subCode)
->update(['status' => 'cancelled']);
}
break;
Subscription Middleware
// app/Http/Middleware/EnsureActiveSubscription.php
namespace App\Http\Middleware;
use Closure;
class EnsureActiveSubscription
{
public function handle($request, Closure $next)
{
$user = $request->user();
if (!$user || !$user->subscription || $user->subscription->status !== 'active') {
return redirect('/subscribe');
}
return $next($request);
}
}
// In routes:
Route::middleware(['auth', 'subscription'])->group(function () {
Route::get('/premium', [PremiumController::class, 'index']);
});
Cancel Subscription
public function cancel()
{
$sub = auth()->user()->subscription;
if (!$sub) {
return back()->with('error', 'No subscription found');
}
$response = Http::withToken(config('services.paystack.secret_key'))
->post('https://api.paystack.co/subscription/disable', [
'code' => $sub->subscription_code,
'token' => $sub->email_token,
]);
if ($response->json()['status'] ?? false) {
$sub->update(['status' => 'cancelled']);
return redirect('/dashboard')->with('success', 'Subscription cancelled');
}
return back()->with('error', 'Cancellation failed');
}
Key Takeaways
- ✓Paystack manages recurring billing. You create plans, subscribe customers, and Paystack charges automatically.
- ✓Use Eloquent for subscription tracking. A Subscription model with status, subscription_code, and email_token.
- ✓Webhook events are the source of truth. Handle subscription.create, charge.success, and invoice.payment_failed.
- ✓Laravel middleware gates premium routes behind active subscription checks.
- ✓Store email_token from the subscription.create webhook for cancellation.
- ✓Dispatch Jobs from your webhook handler for reliable async processing.
Frequently Asked Questions
- Can I use Laravel Cashier with Paystack?
- No. Laravel Cashier is designed for Stripe and Paddle. For Paystack subscriptions, build the integration directly using the HTTP client and Paystack API as shown in this guide.
- How do I handle plan upgrades?
- Cancel the current subscription and create a new one on the new plan. Handle prorating in your application logic.
- What happens when a renewal fails?
- Paystack retries the charge. You receive invoice.payment_failed webhooks. Update the status to past_due and notify the customer.
- How do I register the middleware?
- Add it to your Kernel.php routeMiddleware array with a key like "subscription". Then use it in your route groups with middleware("subscription").
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon