PHP code example of myckhel / laravel-paystack

1. Go to this page and download the library: Download myckhel/laravel-paystack library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

myckhel / laravel-paystack example snippets


return [
    "public_key" => env("PAYSTACK_PUBLIC_KEY"),
    "secret_key" => env("PAYSTACK_SECRET_KEY"),
    "url" => env("PAYSTACK_URL", "https://api.paystack.co"),
    "merchant_email" => env("PAYSTACK_MERCHANT_EMAIL"),
    "route" => [
        "middleware" => ["paystack_route_disabled", "api"],
        "prefix" => "api",
        "hook_middleware" => ["validate_paystack_hook", "api"],
    ],
];

use Binkode\Paystack\Support\Transaction;
use Binkode\Paystack\Support\Customer;

$init = Transaction::initialize([
    "email" => "[email protected]",
    "amount" => 500000, // amount in kobo
]);

$verify = Transaction::verify("reference_here");

$customer = Customer::create([
    "email" => "[email protected]",
    "first_name" => "Jane",
    "last_name" => "Doe",
]);

namespace App\Http\Controllers;

use App\Models\Order;
use Binkode\Paystack\Support\Transaction;
use Illuminate\Http\Request;

class PaymentController extends Controller
{
    /**
     * Step 1: Initialize checkout and redirect to Paystack
     */
    public function checkout(Order $order)
    {
        // Paystack amount is in kobo (e.g. 5,000 NGN = 500000 kobo)
        $amountInKobo = $order->total_amount * 100;

        $response = Transaction::initialize([
            'email' => auth()->user()->email,
            'amount' => $amountInKobo,
            'reference' => 'ORD-' . $order->id . '-' . time(),
            'callback_url' => route('payment.callback'),
            'metadata' => [
                'order_id' => $order->id,
            ],
        ]);

        if (isset($response['status']) && $response['status'] === true) {
            // Save reference to the order
            $order->update([
                'payment_reference' => $response['data']['reference'],
                'status' => 'pending',
            ]);

            // Redirect user to the Paystack checkout page
            return redirect($response['data']['authorization_url']);
        }

        return back()->with('error', 'Unable to initialize transaction with Paystack.');
    }

    /**
     * Step 2: Handle user redirection back from Paystack (Callback)
     */
    public function callback(Request $request)
    {
        $reference = $request->query('reference');

        if (!$reference) {
            return redirect()->route('dashboard')->with('error', 'No reference returned.');
        }

        $response = Transaction::verify($reference);

        if (isset($response['data']['status']) && $response['data']['status'] === 'success') {
            $order = Order::where('payment_reference', $reference)->firstOrFail();
            
            // Avoid double processing (idempotency check)
            if ($order->status !== 'completed') {
                $order->update(['status' => 'completed']);
                // Trigger any order success events / mailers here
            }

            return redirect()->route('orders.show', $order)->with('success', 'Payment successful!');
        }

        return redirect()->route('dashboard')->with('error', 'Payment verification failed.');
    }
}

namespace App\Services;

use App\Models\User;
use Binkode\Paystack\Support\Customer;
use Binkode\Paystack\Support\Subscription;

class BillingService
{
    /**
     * Ensure a user has a Paystack customer account, then subscribe them to a plan.
     */
    public function subscribeUserToPlan(User $user, string $planCode): array
    {
        // 1. Ensure user has a Paystack customer code
        if (!$user->paystack_customer_code) {
            $customerRes = Customer::create([
                'email' => $user->email,
                'first_name' => $user->first_name,
                'last_name' => $user->last_name,
                'phone' => $user->phone,
            ]);

            if (isset($customerRes['data']['customer_code'])) {
                $user->update([
                    'paystack_customer_code' => $customerRes['data']['customer_code'],
                ]);
            }
        }

        // 2. Create the subscription on Paystack
        $subscriptionRes = Subscription::create([
            'customer' => $user->paystack_customer_code,
            'plan' => $planCode,
        ]);

        if (isset($subscriptionRes['status']) && $subscriptionRes['status'] === true) {
            $user->update([
                'subscription_code' => $subscriptionRes['data']['subscription_code'],
                'subscription_status' => 'active',
                'subscribed_at' => now(),
            ]);
        }

        return $subscriptionRes;
    }
}

namespace App\Jobs;

use App\Models\TransferRequest;
use Binkode\Paystack\Support\Transfer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\HttpException;

class ProcessPayoutJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * The number of times the job may be attempted.
     */
    public int $tries = 3;

    /**
     * The number of seconds to wait before retrying the job.
     */
    public int $backoff = 60;

    protected TransferRequest $payout;

    public function __construct(TransferRequest $payout)
    {
        $this->payout = $payout;
    }

    public function handle(): void
    {
        // Don't re-process completed payouts
        if ($this->payout->status === 'processed') {
            return;
        }

        try {
            $response = Transfer::initiate([
                'source' => 'balance',
                'amount' => $this->payout->amount * 100, // in kobo
                'recipient' => $this->payout->recipient_code,
                'reason' => "Payout for Request #{$this->payout->id}",
                'reference' => 'PAY-' . $this->payout->id . '-' . time(),
            ]);

            if (isset($response['status']) && $response['status'] === true) {
                $this->payout->update([
                    'transfer_code' => $response['data']['transfer_code'],
                    'status' => 'processing',
                ]);
            }
        } catch (HttpException $e) {
            // Log the API failure
            Log::error("Paystack API Payout Failure: " . $e->getMessage(), [
                'payout_id' => $this->payout->id,
                'status_code' => $e->getStatusCode()
            ]);

            // If it's a server error (5xx) or rate limit (429), retry the job
            if ($e->getStatusCode() >= 500 || $e->getStatusCode() === 429) {
                $this->release($this->backoff);
                return;
            }

            // For client errors (400, 401, 403, 404), fail the job as retries won't help
            $this->payout->update(['status' => 'failed', 'error_log' => $e->getMessage()]);
            $this->fail($e);
        }
    }
}

use Binkode\Paystack\Support\Transaction;
use Symfony\Component\HttpKernel\Exception\HttpException;

try {
    $verify = Transaction::verify("non_existent_ref");
} catch (HttpException $e) {
    $statusCode = $e->getStatusCode(); // e.g. 404
    $errorMessage = $e->getMessage(); // Message returned from Paystack
    
    // Handle error accordingly
}

use Binkode\Paystack\Events\Hook;
use App\Models\Order;
use App\Models\TransferRequest;
use Illuminate\Support\Facades\Log;

class PaystackWebhookListener
{
    public function handle(Hook $event): void
    {
        $payload = $event->event;
        $eventType = $payload['event'] ?? null;
        $data = $payload['data'] ?? [];

        Log::info("Paystack webhook received: {$eventType}");

        switch ($eventType) {
            case 'charge.success':
                $reference = $data['reference'] ?? null;
                if ($reference) {
                    $order = Order::where('payment_reference', $reference)->first();
                    if ($order && $order->status !== 'completed') {
                        $order->update(['status' => 'completed']);
                    }
                }
                break;

            case 'transfer.success':
                $transferCode = $data['transfer_code'] ?? null;
                if ($transferCode) {
                    $payout = TransferRequest::where('transfer_code', $transferCode)->first();
                    if ($payout) {
                        $payout->update(['status' => 'processed']);
                    }
                }
                break;

            case 'transfer.failed':
            case 'transfer.reversed':
                $transferCode = $data['transfer_code'] ?? null;
                if ($transferCode) {
                    $payout = TransferRequest::where('transfer_code', $transferCode)->first();
                    if ($payout) {
                        $payout->update([
                            'status' => 'failed',
                            'error_log' => $data['reason'] ?? 'Transfer failed or was reversed.',
                        ]);
                    }
                }
                break;

            default:
                Log::warning("Unhandled Paystack event: {$eventType}");
                break;
        }
    }
}
bash
php artisan vendor:publish --provider="Binkode\Paystack\PaystackServiceProvider"
bash
php artisan make:listener PaystackWebhookListener --event=Binkode\\Paystack\\Events\\Hook