jey/zibal
Laravel gateway integration for Zibal payments. Provides simple API to create payment requests, redirect users to Zibal, and verify transactions after callback. Includes configuration, service classes, and helpers to streamline checkout and order payment flows.
Installation
composer require jey/zibal
Publish the config file (if needed):
php artisan vendor:publish --provider="Jey\Zibal\ZibalServiceProvider"
Configuration
Edit .env with your Zibal API credentials:
ZIBAL_API_KEY=your_api_key_here
ZIBAL_API_URL=https://api.zibal.ir/v1
First Use Case Generate a payment request in a controller:
use Jey\Zibal\Facades\Zibal;
public function createPayment()
{
$payment = Zibal::createPayment([
'amount' => 10000, // 10,000 Rials
'callback_url' => route('zibal.callback'),
'description' => 'Order #12345',
'metadata' => ['order_id' => 12345],
]);
return redirect()->away($payment->getPaymentUrl());
}
Callback Handling Define a route for Zibal’s callback:
Route::post('/zibal/callback', [PaymentController::class, 'handleCallback']);
Verify the callback in your controller:
public function handleCallback(Request $request)
{
$payment = Zibal::verifyCallback($request->all());
if ($payment->isSuccessful()) {
// Process successful payment
}
}
Zibal::createPayment() to generate a payment request.$payment->getPaymentUrl()).tracking_code in your database for later verification.$payment = Zibal::createPayment([
'amount' => 50000,
'callback_url' => route('zibal.callback'),
'metadata' => ['user_id' => auth()->id(), 'product_id' => 42],
]);
session()->put('zibal_tracking_code', $payment->tracking_code);
return redirect()->away($payment->getPaymentUrl());
Zibal::verifyCallback() in your callback route to validate the payment.$payment->isSuccessful() to confirm success.public function handleCallback(Request $request)
{
$payment = Zibal::verifyCallback($request->all());
if ($payment->isSuccessful()) {
// Update order status, send confirmation, etc.
Order::find($payment->metadata->order_id)
->update(['status' => 'paid']);
}
return response()->json(['status' => 'success']);
}
tracking_code:$payment = Zibal::verifyPayment($tracking_code);
if ($payment->isSuccessful()) {
// Process payment
}
Zibal::refundPayment():$refund = Zibal::refundPayment($tracking_code, [
'amount' => 50000, // Full or partial refund
'description' => 'Customer refund request',
]);
tracking_code, amount, metadata, and status in your orders or payments table.Schema::create('payments', function (Blueprint $table) {
$table->id();
$table->string('tracking_code')->unique();
$table->integer('amount');
$table->json('metadata');
$table->string('status')->default('pending');
$table->timestamps();
});
public function handleCallback(Request $request)
{
try {
$payment = Zibal::verifyCallback($request->all());
// Process payment
} catch (\Exception $e) {
// Log and retry later
ProcessFailedCallback::dispatch($request->all());
}
}
$this->mock(Zibal::class)->shouldReceive('createPayment')
->once()
->andReturn(new Payment(['tracking_code' => '12345', 'payment_url' => 'https://sandbox.zibal.ir/12345']));
status before updating).tracking_code and status in your database to avoid duplicate processing.if (!$payment->isSuccessful() && $this->paymentExists($payment->tracking_code)) {
return response()->json(['status' => 'already_processed']);
}
amount in the callback may differ from the original request due to fees or rounding.amount in the callback with your stored value (allowing for minor differences).$storedAmount = $this->getStoredAmount($payment->tracking_code);
if (abs($payment->amount - $storedAmount) > 100) { // Allow 100 Rial tolerance
throw new \Exception('Amount mismatch detected');
}
metadata is passed as a string in callbacks. Use json_decode() to access it.$metadata = json_decode($payment->metadata);
$orderId = $metadata->order_id;
\Log::info('Zibal payment created', [
'tracking_code' => $payment->tracking_code,
'amount' => $payment->amount,
]);
config(['zibal.api_url' => 'https://sandbox.zibal.ir/v1']);
try {
$payment = Zibal::verifyPayment($tracking_code);
} catch (\GuzzleHttp\Exception\RequestException $e) {
\Log::error('Zibal API error', [
'response' => $e->getResponse()->getBody(),
'tracking_code' => $tracking_code,
]);
}
Payment model to add custom logic:namespace App\Models;
use Jey\Zibal\Models\Payment as BasePayment;
class Payment extends BasePayment
{
public function order()
{
return $this->belongsTo(Order::class);
}
}
Zibal::extend(function ($app) {
$app->bind(\Jey\Zibal\Contracts\CallbackHandler::class, function () {
return new CustomCallbackHandler();
});
});
// In your callback handler
event(new PaymentProcessed($payment));
// config/zibal.php
'locale' => 'fa', // Force Persian responses
class RetryFailedPayment implements ShouldQueue
{
use Dispatchable, InteractsWithQueue;
public function handle()
{
$payment = Zibal::verifyPayment($this->trackingCode);
if (!$payment->isSuccessful()) {
$this->release(60); // Retry in 60 minutes
}
}
}
How can I help you explore Laravel packages today?