concept24 / omnipay-payu-rest
PayU REST API (OpenPayU v2.1, PayU GPO) driver for the Omnipay payment processing library
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.8
- omnipay/common: ^3.2
- php-http/guzzle7-adapter: ^1.0
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
PayU REST API (OpenPayU v2.1, PayU GPO — including PayU GPO Romania) driver for the Omnipay payment processing library.
For the older PayU Romania LiveUpdate / IPN integration (MERCHANT + secret key) use a LiveUpdate driver instead; this package talks to secure.payu.com/api/v2_1 with OAuth.
Requirements
- PHP 8.2+ (Laravel 12 and 13 compatible — dependencies:
omnipay/common3.x, Guzzle 7) - PayU POS data: POS id, OAuth
client_id/client_secret, second key (MD5)
Installation
composer require concept24/omnipay-payu-rest
Usage
use Omnipay\Omnipay;
$gateway = Omnipay::create('PayURest'); // or new \Omnipay\PayURest\Gateway
$gateway->initialize([
'posId' => '…',
'clientId' => '…', // usually the POS id
'clientSecret' => '…',
'secondKey' => '…', // signs the notifications
'testMode' => true, // secure.snd.payu.com; false → secure.payu.com ('endpoint' overrides both)
]);
// 1. create the order and send the client to PayU
$response = $gateway->purchase([
'amount' => '122.89',
'currency' => 'RON', // default RON
'description' => 'Order C24-77',
'transactionId' => 'C24-77-'.bin2hex(random_bytes(3)), // extOrderId: unique per POS, so unique per attempt
'returnUrl' => 'https://shop.ro/pay/77', // continueUrl
'notifyUrl' => 'https://shop.ro/payu/notify',
'clientIp' => $request->ip(),
'language' => 'ro',
'buyer' => ['email' => 'client@example.com', 'firstName' => 'Ana', 'lastName' => 'Pop', 'phone' => '0722…'],
// 'products' => [['name' => '…', 'unitPrice' => '12289', 'quantity' => '1']], // default: one line with the total
])->send();
if ($response->isRedirect()) {
$orderId = $response->getTransactionReference(); // keep it with the payment: continueUrl does not carry it
$response->redirect();
}
// 2. PayU POSTs every status change to notifyUrl — answer HTTP 200 (duplicates are possible)
$notification = $gateway->acceptNotification();
if ($notification->isValid()) { // OpenPayu-Signature checked with the second key
$notification->getOrderStatus(); // COMPLETED, CANCELED, PENDING, WAITING_FOR_CONFIRMATION
$notification->getTransactionReference(); // PayU orderId
$notification->getTransactionId(); // your extOrderId
$notification->getAmountInteger(); // minor units
}
// 3. when the client comes back (or any time later)
$status = $gateway->fetchTransaction(['transactionReference' => $orderId])->send();
$status->isSuccessful(); // COMPLETED
$status->isPending(); // NEW, PENDING, WAITING_FOR_CONFIRMATION
$status->isCancelled(); // CANCELED
The OAuth token is reused until shortly before it expires (cached per endpoint + client id, for the life of the PHP process). The default HTTP client does not follow redirects: PayU answers the order creation with HTTP 302 and the JSON body.
Refund and cancel
$gateway->refund(['transactionReference' => $orderId])->send(); // full
$gateway->refund(['transactionReference' => $orderId, 'amount' => '10.00', 'description' => 'Return'])->send(); // partial
$gateway->void(['transactionReference' => $orderId])->send(); // cancel an order still in progress
Tests
composer install
vendor/bin/phpunit # unit tests, no network
PAYU_POS_ID=… PAYU_CLIENT_ID=… PAYU_CLIENT_SECRET=… vendor/bin/phpunit --group sandbox # against PayU's sandbox
Sandbox cards (expiry 12/29, CVV 123): 4444333322221111 approved · 5000105018126595 declined · 4245757666349685 3-D Secure challenge (the sandbox sends the challenge to an internal PayU simulator that is not publicly reachable). When PayU recognises the buyer's e-mail in Click to Pay, choose "Card" to enter a new card.
Changelog
- 1.0.0 — first release.
License
MIT — see LICENSE.