McTaba Labs logo
Bonaventure OgetoBy Bonaventure Ogeto|

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');
}

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

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');
}

Ship Payments Faster

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").

Learn Paystack Integration

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

Not ready? Create Free Account