digipay-laravel maintained by eamirgh
Digipay UPG for Laravel 11, 12, 13+
Production-grade, scalable Laravel package for Digipay UPG (Unified Payment Gateway). Built with Test-Driven Development (TDD) for high-concurrency applications, multi-store/multi-tenant platforms, and enterprise Iranian financial workflows.
Features
- Laravel 11, 12, 13+ & PHP 8.2 - 8.4 compatibility.
- Full Digipay UPG API Coverage:
- OAuth2 Authentication with high-performance cached token reuse and auto-refresh.
- Ticket Creation (IPG, Wallet, Credit, BNPL, Credit Card).
- Web & App Payment Redirection.
- Callback Verification & Validation.
- Manual Payment Reversal (within 25 min window).
- Credit / BNPL Delivery Confirmation.
- Full & Partial Refunds + Refund Tracking.
- Enterprise Multi-Connection: Switch credentials per store or build dynamic connections on the fly.
- Decoupled Event-Driven Architecture: Emits
TicketCreated,PaymentVerified,PaymentReversed,PaymentRefunded. - Resilient Network Layer: Automatic retries, configurable connect & read timeouts, and request pooling.
- 100% Test-Driven: Complete test suite using Orchestra Testbench and PHPUnit 11.
Installation
Install via Composer:
composer require eamirgh/digipay-laravel
Publish configuration:
php artisan vendor:publish --tag=digipay-config
Environment Configuration
Add the following to your .env file:
DIGIPAY_SANDBOX=true # Set false for production
DIGIPAY_CLIENT_ID=your_client_id
DIGIPAY_CLIENT_SECRET=your_client_secret
DIGIPAY_USERNAME=your_username
DIGIPAY_PASSWORD=your_password
DIGIPAY_DEFAULT_TYPE=11 # 0: IPG, 5: CREDIT, 11: WALLET, 13: BNPL, 24: CREDIT_CARD
DIGIPAY_CALLBACK_URL=https://yourdomain.com/payment/callback
Eloquent Model Integration (Payable Interface)
Adapt your existing Eloquent models (Order, Invoice, Subscription, Bill) to work natively with Digipay:
namespace App\Models;
use Eamirgh\Digipay\Contracts\Payable;
use Eamirgh\Digipay\Traits\InteractsWithDigipay;
use Illuminate\Database\Eloquent\Model;
class Order extends Model implements Payable
{
use InteractsWithDigipay;
// Optional: override amount if column is not `amount` or `total`
public function getDigipayAmount(): int
{
return (int) $this->total_price; // in Rials
}
// Optional: provide customer phone number for gateway pre-fill
public function getDigipayCellNumber(): ?string
{
return $this->user?->phone_number;
}
}
Initiate Payment Directly from Model:
// Pay via Facade
$response = Digipay::pay($order);
// Or call model helper directly:
$response = $order->createDigipayTicket();
return $response->redirect();
Verify Payment Directly from Model:
$verification = $order->verifyDigipayPayment($callback->trackingCode);
if ($verification->isSuccessful()) {
$order->update(['paid_at' => now(), 'rrn' => $verification->rrn]);
}
Quick Start
1. Request Payment Ticket & Redirect
use Eamirgh\Digipay\Facades\Digipay;
use Eamirgh\Digipay\DTO\TicketRequest;
use Eamirgh\Digipay\Enums\TicketType;
// Simple array syntax
$response = Digipay::createTicket([
'amount' => 100000, // In Rials
'providerId' => 'ORDER_12345',
'cellNumber' => '09120000000',
]);
// Or using strongly-typed DTO
$request = (new TicketRequest(
amount: 100000,
providerId: 'ORDER_12345',
cellNumber: '09120000000',
callbackUrl: route('payment.callback')
))->withPreferredGateway(2); // 2: IPG, 0: WALLET
$response = Digipay::createTicket($request, TicketType::WALLET);
if ($response->isSuccessful()) {
// Store $response->ticket in database
return $response->redirect(); // Redirects customer to Digipay gateway
}
2. Handle Gateway Callback & Verify
Digipay posts the payment outcome to your callback_url:
use Eamirgh\Digipay\Facades\Digipay;
use Illuminate\Http\Request;
public function handleCallback(Request $request)
{
$callback = Digipay::parseCallback($request);
if (! $callback->isSuccessful()) {
// Customer canceled or gateway failed
return redirect()->route('checkout.failed')->with('error', 'Payment failed.');
}
// Always verify with Digipay prior to fulfilling orders!
try {
$verification = Digipay::verify(
trackingCodeOrRequest: $callback->trackingCode,
providerId: $callback->providerId
);
if ($verification->isSuccessful()) {
// Transaction verified
$rrn = $verification->rrn;
$cardPan = $verification->maskedPan;
return redirect()->route('checkout.success', [
'trackingCode' => $verification->trackingCode,
'rrn' => $rrn,
]);
}
} catch (\Eamirgh\Digipay\Exceptions\DigipayApiException $e) {
// Digipay error (status code, result message)
logger()->error('Digipay verification failed', ['error' => $e->getMessage()]);
}
return redirect()->route('checkout.failed');
}
Advanced Operations
Manual Reversal (IPG / DPG only, within 25 mins)
$reversal = Digipay::reverse(
purchaseTrackingCodeOrRequest: '10357963991735727353649',
providerId: 'ORDER_12345'
);
if ($reversal->isSuccessful()) {
// Payment reversed
}
Credit / BNPL Delivery Confirmation
Mandatory for CREDIT (type = 5) and BNPL (type = 13):
$delivery = Digipay::deliver(
trackingCodeOrRequest: '10357963991735727353649',
deliveryDate: now()->getTimestampMs(),
invoiceNumber: 'INV-9900',
products: ['sku-laptop-1', 'sku-case-2'],
type: \Eamirgh\Digipay\Enums\TicketType::BNPL
);
Refund & Refund Tracking
// Initiate refund
$refund = Digipay::refund(
saleTrackingCodeOrRequest: '10357963991735727353649',
amount: 100000,
providerId: 'REFUND_REQ_001'
);
// Check refund status later
$track = Digipay::trackRefund('REFUND_REQ_001');
if ($track->isSuccessful()) {
// Refund completed
} elseif ($track->isPending()) {
// Waiting for settlement
}
Multi-Store & Multi-Tenant Support
Configure multiple credentials in config/digipay.php:
'connections' => [
'second_store' => [
'sandbox' => false,
'client_id' => 'store_2_id',
'client_secret' => 'store_2_secret',
'username' => 'store_2_user',
'password' => 'store_2_pass',
],
],
Use specific connections:
$ticket = Digipay::connection('second_store')->createTicket([
'amount' => 250000,
'providerId' => 'STORE2_ORDER_1',
]);
Or build a dynamic client on the fly:
$client = Digipay::build([
'client_id' => $tenant->digipay_client_id,
'client_secret' => $tenant->digipay_client_secret,
'username' => $tenant->digipay_username,
'password' => $tenant->digipay_password,
]);
$response = $client->createTicket(...);
Event Listeners
Listen to dispatched domain events in your EventServiceProvider:
Eamirgh\Digipay\Events\TicketCreatedEamirgh\Digipay\Events\PaymentVerifiedEamirgh\Digipay\Events\PaymentReversedEamirgh\Digipay\Events\PaymentRefunded
use Eamirgh\Digipay\Events\PaymentVerified;
Event::listen(PaymentVerified::class, function (PaymentVerified $event) {
$trackingCode = $event->response->trackingCode;
$amount = $event->response->amount;
// Update order status, credit user wallet, etc.
});
Testing
Run unit & feature test suite:
composer test
# or
vendor/bin/phpunit
License
The MIT License (MIT). Please see License File for more information.