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.
// 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();
});
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');
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
// 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;
// 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']);
});
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');
}
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course