beziworld / laravel-payu
PayU REST API integration for Laravel: orders, signed notifications, capture, refunds and payment methods.
Requires
- php: ^8.3
- ext-json: *
- guzzlehttp/guzzle: ^7.8
- illuminate/contracts: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/routing: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^3.7|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- phpstan/phpstan-strict-rules: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-04 16:36:38 UTC
README
PayU REST API v2.1 for Laravel: orders, BLIK, signed notifications, capture, cancel, refunds and payment methods, with a cached OAuth token. Verified against the PayU sandbox.
Installation
composer require beziworld/laravel-payu
php artisan vendor:publish --tag=payu-config # optional
PAYU_POS_ID=300746 PAYU_SECOND_KEY=b6ca15b0d1020e8094d9b5f8d163db54 # "Second key (MD5)" PAYU_CLIENT_ID=300746 # defaults to the POS id PAYU_CLIENT_SECRET=2ee86a66e5d97e3fadc400c9f19b065d PAYU_SANDBOX=true PAYU_CONTINUE_URL=https://shop.example.com/payment/return
The values above are the public PayU sandbox POS. The notification URL defaults to the package route
/payu/notification.
Payment flow
use Beziworld\PayU\Data\OrderRequest; use Beziworld\PayU\Data\Product; use Beziworld\PayU\Facades\PayU; public function pay(Request $request, Order $order): RedirectResponse { $created = PayU::createOrder(new OrderRequest( totalAmount: $order->total_in_grosze, description: "Order {$order->number}", customerIp: $request->ip(), products: $order->items->map(fn ($item) => new Product($item->name, $item->price_in_grosze, $item->quantity))->all(), extOrderId: $order->number, buyer: ['email' => $order->email, 'firstName' => $order->first_name, 'lastName' => $order->last_name, 'language' => 'pl'], )); $order->update(['payu_order_id' => $created->orderId]); return redirect()->away($created->redirectUri); }
Notifications
use Beziworld\PayU\Events\OrderCompleted; Event::listen(function (OrderCompleted $event) { $order = Order::where('number', $event->order->extOrderId)->firstOrFail(); if (! $order->isPaid() && $event->order->totalAmount === $order->total_in_grosze) { $order->markAsPaid(payuPaymentId: $event->paymentId); } });
Other events: OrderStatusChanged (every order notification) and RefundStatusChanged. The OpenPayu-Signature
header (MD5 or SHA-2) is verified over the raw body with the second key. PayU repeats notifications until it
receives HTTP 200, so listeners must be idempotent. Optionally restrict the sender with
payu.notifications.allowed_ips.
BLIK level 0
PayU::createOrder(new OrderRequest( // ... payMethod: ['type' => 'BLIK_AUTHORIZATION_CODE', 'value' => 'blik', 'authorizationCode' => $request->input('blik_code')], ));
Capture, cancel, refunds
PayU::order($orderId)->status; // OrderStatus::Completed PayU::capture($orderId); // for POS without automatic capture (WAITING_FOR_CONFIRMATION) PayU::cancel($orderId); // release the funds of an uncompleted order PayU::refund($orderId, 'Return', 2500, "refund-{$return->id}"); // amount omitted = full refund PayU::paymentMethods('pl'); // list<PaymentMethod> with logos and amount limits
extRefundId makes refunds idempotent: repeating the call with the same id does not refund twice.
Exceptions
ApiException (statusCode(), codeLiteral(), httpStatus(), payload()), InvalidNotification and
InvalidConfiguration, all extending PayUException.
Development
composer install
composer ci # pint --test, phpstan (level 8), pest
Author
Created and maintained by Kamil Ćwiertnia, beziworld.eu.
- E-mail: biuro@beziworld.eu
- Issues: GitHub issues
- Security vulnerabilities: see SECURITY.md
Need help integrating payments in your shop? Get in touch through beziworld.eu.
Other packages by the author
laravel-przelewy24 · laravel-tpay · laravel-paynow · laravel-autopay · laravel-inpost · laravel-company-data
License
MIT. See LICENSE.md. This package is not affiliated with PayU S.A.