Search by

beziworld / laravel-paynow

kamcw

Paynow (mBank) payment gateway for Laravel: payments, BLIK, refunds and HMAC-signed notifications.

Package info

github.com/Beziworld/laravel-paynow

Homepage

Issues

pkg:composer/beziworld/laravel-paynow

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

v1.0.0 2026-11-04 14:25 UTC

This package is auto-updated.

Last update: 2026-11-04 16:36:36 UTC


README

tests Latest Version License

Paynow (mBank) payment gateway for Laravel, API v3: payments, BLIK level 0, refunds, payment methods and HMAC-signed notifications. Verified against the Paynow sandbox.

Polska wersja dokumentacji

Installation

composer require beziworld/laravel-paynow
php artisan vendor:publish --tag=paynow-config   # optional
PAYNOW_API_KEY=your-api-key
PAYNOW_SIGNATURE_KEY=your-signature-key
PAYNOW_SANDBOX=true
PAYNOW_CONTINUE_URL=https://shop.example.com/payment/return

Set the notification URL of the shop in the Paynow panel to https://your-app.com/paynow/notification.

Payment flow

use Beziworld\Paynow\Data\PaymentRequest;
use Beziworld\Paynow\Exceptions\ApiException;
use Beziworld\Paynow\Facades\Paynow;

public function pay(Order $order): RedirectResponse
{
    try {
        $payment = Paynow::createPayment(new PaymentRequest(
            amount: $order->total_in_grosze,          // integer minor units, at least 100
            externalId: $order->number,
            description: "Order {$order->number}",
            buyerEmail: $order->email,
            buyer: ['firstName' => $order->first_name, 'lastName' => $order->last_name, 'locale' => 'pl-PL'],
        ));
    } catch (ApiException $e) {
        report($e);

        return back()->withErrors('The payment could not be started.');
    }

    $order->update(['paynow_payment_id' => $payment->paymentId]);

    return redirect()->away($payment->redirectUrl);
}

Every request carries an idempotency key. For payments it is derived from the external id and the amount, so a repeated call for the same order (e.g. after a timeout) returns the original payment instead of charging twice.

Notifications

use Beziworld\Paynow\Events\PaymentConfirmed;
use Beziworld\Paynow\Events\PaymentStatusChanged;

Event::listen(function (PaymentConfirmed $event) {
    $order = Order::where('number', $event->notification->externalId)->firstOrFail();

    if (! $order->isPaid()) {
        $order->markAsPaid(paynowPaymentId: $event->notification->paymentId);
    }
});

Event::listen(function (PaymentStatusChanged $event) {
    // NEW, PENDING, CONFIRMED, REJECTED, ERROR, EXPIRED, ABANDONED
});

The signature is verified over the raw request body. Paynow may send notifications more than once and out of order, so keep listeners idempotent and never move an order back from a final status.

BLIK level 0 (code entered in your shop)

$payment = Paynow::createPayment(new PaymentRequest(
    amount: 4999,
    externalId: $order->number,
    description: 'Subscription',
    buyerEmail: $user->email,
    paymentMethodId: Paynow::blikMethodId(4999),   // requires white label to be enabled
    authorizationCode: $request->input('blik_code'),
));
// No redirect: the buyer confirms in the banking app, the result arrives as a notification.

Errors such as AUTHORIZATION_CODE_INVALID or AUTHORIZATION_CODE_EXPIRED are available through ApiException::hasErrorType().

Refunds, status and payment methods

use Beziworld\Paynow\Enums\RefundReason;

$refund = Paynow::refund($order->paynow_payment_id, 2500, RefundReason::ReturnOfGoods, "refund-{$return->id}");
Paynow::refundStatus($refund->refundId)->status;   // RefundStatus::Successful

Paynow::paymentStatus($paymentId);                  // PaymentStatus::Confirmed
Paynow::paymentMethods(amount: 4999);               // list<PaymentMethod> with logos for your own picker

Pass a stable idempotency key to refund() (e.g. the id of the return) to make retries safe.

Notes

  • continueUrl must be a valid public ASCII URL; Paynow rejects addresses with national characters or test domains.
  • Amounts are integers in minor units (grosze).

Development

composer install
composer ci   # pint --test, phpstan (level 8), pest

Author

Created and maintained by Kamil Ćwiertnia, beziworld.eu.

Need help integrating payments in your shop? Get in touch through beziworld.eu.

Other packages by the author

laravel-przelewy24 · laravel-tpay · laravel-payu · laravel-autopay · laravel-inpost · laravel-company-data · laravel-smsapi

License

MIT. See LICENSE.md. This package is not affiliated with mBank S.A. (Paynow).