larapardakht/larapardakht

A modern, extensible payment gateway integration package for Laravel supporting Iranian payment providers.

Maintainers

Package info

github.com/TheXERC/LaraPardakht

pkg:composer/larapardakht/larapardakht

Statistics

Installs: 19

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

V2.0 2026-04-19 11:20 UTC

This package is auto-updated.

Last update: 2026-04-19 11:26:10 UTC


README

English | فارسی

Badge Wall

Packagist Version Total Downloads PHP Version Laravel Tests License

A modern, extensible payment gateway integration package for Laravel 11, 12, and 13, supporting Iranian payment providers.

Features

  • Driver-based architecture — easily add new gateways without modifying core code
  • Fluent API — clean, chainable interface for purchases, payments and verifications
  • Sandbox/test support — every driver supports sandbox mode out of the box
  • Events — fires events after purchase and first-time successful verification for easy integration
  • Runtime configuration — switch drivers and override settings on the fly
  • Typed exceptions — distinct exception classes for different failure scenarios

Supported Gateways

Gateway Normal Mode Sandbox Mode
Zarinpal
Zibal
IDPay

More gateways coming soon! You can also create custom drivers.

Requirements

  • PHP 8.2+
  • Laravel 11, 12, or 13

Installation

composer require larapardakht/larapardakht

Publish Configuration

php artisan vendor:publish --tag=larapardakht-config

This will create config/larapardakht.php in your application.

Configuration

Set your gateway credentials in .env:

PAYMENT_GATEWAY=zarinpal

# Zarinpal
ZARINPAL_MERCHANT_ID=your-merchant-id-here
ZARINPAL_SANDBOX=false

# Zibal
ZIBAL_MERCHANT=your-zibal-merchant
ZIBAL_SANDBOX=false

# IDPay
IDPAY_API_KEY=your-idpay-api-key
IDPAY_SANDBOX=false

# Shared
PAYMENT_CALLBACK_URL=https://yoursite.com/payment/callback

Usage

Purchase & Redirect

use LaraPardakht\Facades\Payment;
use LaraPardakht\DTOs\Invoice;

$invoice = new Invoice();
$invoice->amount(50000)
    ->description('Order #123')
    ->detail('mobile', '09121234567')
    ->detail('email', 'customer@example.com');

return Payment::purchase($invoice, function ($driver, $transactionId) {
    // Persist $transactionId in your own storage (for example, your order record)
})->pay()->render();

For idpay, order_id is required by the API. Set it on invoice details before purchase/verify:

$invoice->detail('order_id', 'ORD-123');

Verify Payment

use LaraPardakht\Facades\Payment;
use LaraPardakht\Exceptions\InvalidPaymentException;

try {
    $receipt = Payment::amount(50000)
        ->transactionId($transactionId)
        ->verify();

    // Payment was successful
    echo $receipt->getReferenceId();
    echo $receipt->getDriver();

} catch (InvalidPaymentException $e) {
    // Payment verification failed
    echo $e->getMessage();
}

Switch Driver at Runtime

Payment::via('zibal')->purchase($invoice, function ($driver, $transactionId) {
    // ...
});

Override Config at Runtime

Payment::config('merchant_id', 'another-merchant-id')->purchase($invoice);

// Or multiple values:
Payment::config([
    'merchant_id' => 'another-merchant-id',
    'sandbox' => true,
])->purchase($invoice);

Override Callback URL

Payment::callbackUrl('https://yoursite.com/custom-callback')
    ->purchase($invoice);

Get JSON Redirect Data

$redirect = Payment::purchase($invoice)->pay();
return $redirect->toJson();

Security and Reliability Notes

  • PaymentManager is now container-scoped (request/job lifecycle) to avoid cross-request state leakage in long-lived workers.
  • Facade root caching is disabled for Payment, so each call resolves from the current container scope.
  • pay() now validates that a transaction identifier exists and throws InvalidPaymentException when missing.
  • verify() now validates that a reference identifier exists in successful gateway responses and throws InvalidPaymentException when missing.
  • verify() now requires a positive invoice amount (amount > 0) before contacting the gateway.
  • Zibal verify performs consistency checks against local invoice data:
    • Gateway amount must match local invoice amount when returned by gateway.
    • If invoice order_id detail is set and gateway returns orderId, values must match.
  • Already-verified responses (code=101 for Zarinpal, result=201 for Zibal, status=101 for IDPay) are still accepted as valid verify results, but now include already_verified=true in receipt raw data.
  • PaymentVerified event is dispatched only for first-time successful verification, not for already-verified gateway responses.
  • Malformed/non-JSON gateway responses are handled safely and converted to typed exceptions (PurchaseFailedException / InvalidPaymentException).

Compatibility Impact

No public method signatures changed and no config changes are required.

If your integration previously called pay() before a successful purchase(), or relied on low-level runtime errors for malformed gateway responses, update your error handling to catch typed gateway exceptions.

verify() now requires invoice amount to be set to a positive value before calling it. If you were verifying only by transaction identifier, update your flow to include the original invoice amount.

PaymentVerified is no longer dispatched for gateway responses that indicate the transaction was already verified (code=101 / result=201 / status=101). If you had listeners relying on repeated verify calls, make sure they depend on your own idempotent persistence flow instead of repeated event dispatches.

For Zibal: verify may throw InvalidPaymentException when gateway-reported amount / orderId conflicts with your local invoice data. This is a security hardening change. No API update is required on your side, but ensure your verify flow handles this exception and keeps local invoice values authoritative.

Gateway Codes (English Translations)

These translations are used by the package when possible so exception messages are predictable.

Zarinpal - Common Codes

Code Meaning
-9 Validation error
-10 Terminal is not valid (check merchant_id or IP)
-11 Terminal is not active
-12 Too many attempts
-13 Terminal limit reached
-14 Callback domain does not match registered terminal domain
-15 Terminal user is suspended
-16 Terminal user level is not valid
-17 Terminal user level is not valid
-18 Referrer address does not match registered domain
-19 Terminal transactions are banned
100 Success
101 Already verified

Zarinpal - Purchase Codes

Code Meaning
-30 Terminal does not allow floating wages
-31 Terminal does not allow wages (default bank account missing)
-32 Invalid wages (floating total exceeds max amount)
-33 Invalid floating wages
-34 Invalid wages (fixed total exceeds max amount)
-35 Floating wages reached max parts limit
-36 Minimum floating wage amount is 10,000 Rials
-37 One or more wage IBAN values are inactive
-38 Wage IBAN setup is invalid
-39 Generic wages error
-40 Invalid expire_in
-41 Maximum amount is 100,000,000 Tomans

Zarinpal - Verify Codes

Code Meaning
-50 Session invalid (amount mismatch)
-51 Session invalid (payment not successful / inactive session)
-52 Unexpected system error
-53 Session does not belong to this merchant_id
-54 Invalid authority
-55 Manual payment request not found

Zibal - Request Result Codes

Code Meaning
100 Success
102 Merchant not found
103 Merchant inactive / gateway contract not signed
104 Invalid merchant
105 Amount must be greater than 1,000 Rials
106 Invalid callbackUrl (must start with http/https)
107 Invalid percentMode (only 0 or 1)
108 One or more beneficiaries in multiplexingInfos are invalid
109 One or more beneficiaries in multiplexingInfos are inactive
110 id=self missing in multiplexingInfos
111 Amount is not equal to total shares in multiplexingInfos
112 Insufficient fee wallet balance
113 Amount exceeds transaction limit
114 Invalid national code
115 IP is not registered in panel

Zibal - Verify Result Codes

Code Meaning
100 Success
102 Merchant not found
103 Merchant inactive
104 Invalid merchant
201 Already verified
202 Order not paid or payment unsuccessful
203 Invalid trackId

Zibal - Payment Status Codes

Status Meaning
-1 Waiting for payment
-2 Internal error
1 Paid and verified
2 Paid and unverified
3 Cancelled by user
4 Invalid card number
5 Insufficient funds
6 Invalid password/PIN
7 Too many requests
8 Daily internet payment count limit exceeded
9 Daily internet payment amount limit exceeded
10 Invalid card issuer
11 Switch error
12 Card not accessible
15 Refunded
16 Refund in progress
18 Reversed
21 Invalid merchant

IDPay - Transaction Status Codes

Status Meaning
1 Payment not made
2 Payment failed
3 Error occurred
4 Blocked
5 Refunded to payer
6 System refund
7 Canceled by payer
8 Redirected to payment gateway
10 Waiting for verification
100 Verified
101 Already verified
200 Settled to payee

Creating Custom Drivers

1. Create a Gateway Class

Create a new directory under src/Drivers/YourGateway/ and implement GatewayInterface:

<?php

declare(strict_types=1);

namespace LaraPardakht\Drivers\MyGateway;

use LaraPardakht\Contracts\GatewayInterface;
use LaraPardakht\Contracts\InvoiceInterface;
use LaraPardakht\Contracts\ReceiptInterface;
use LaraPardakht\DTOs\Receipt;
use LaraPardakht\DTOs\RedirectResponse;

class MyGatewayGateway implements GatewayInterface
{
    protected InvoiceInterface $invoice;

    public function __construct(
        protected readonly array $settings,
    ) {}

    public function setInvoice(InvoiceInterface $invoice): static
    {
        $this->invoice = $invoice;
        return $this;
    }

    public function purchase(): string
    {
        // Send purchase request to gateway API
        // Return the transaction ID
    }

    public function pay(): RedirectResponse
    {
        // Return redirect URL to gateway payment page
        return new RedirectResponse(url: 'https://gateway.com/pay/' . $this->invoice->getTransactionId());
    }

    public function verify(): ReceiptInterface
    {
        // Verify the payment
        // Return a Receipt
        return new Receipt(
            referenceId: 'ref-123',
            driver: 'mygateway',
            date: new \DateTimeImmutable(),
            rawData: [],
        );
    }
}

2. Register in Config

Add your driver to config/larapardakht.php:

'drivers' => [
    // ... existing drivers
    'mygateway' => [
        'api_key' => env('MYGATEWAY_API_KEY', ''),
        'sandbox' => env('MYGATEWAY_SANDBOX', false),
        'callback_url' => env('PAYMENT_CALLBACK_URL', ''),
    ],
],

'map' => [
    // ... existing mappings
    'mygateway' => \LaraPardakht\Drivers\MyGateway\MyGatewayGateway::class,
],

3. Write Tests

Add tests under tests/Drivers/MyGateway/ using Http::fake() to mock API calls.

Events

Event When Fired
PaymentPurchased After a successful purchase (transaction ID obtained)
PaymentVerified After a first-time successful payment verification (already_verified responses do not dispatch this event)
use LaraPardakht\Events\PaymentPurchased;
use LaraPardakht\Events\PaymentVerified;

// In your EventServiceProvider or listener
Event::listen(PaymentPurchased::class, function ($event) {
    logger("Payment purchased: {$event->transactionId} via {$event->driver}");
});

Event::listen(PaymentVerified::class, function ($event) {
    logger("Payment verified: {$event->receipt->getReferenceId()} via {$event->driver}");
});

Testing

Run the test suite:

./vendor/bin/pest

License

The MIT License (MIT). See LICENSE for details.