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/ */
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;
}
}
}