professionalweb/payment-laravel-yandex
Laravel wrapper for Yandex Checkout (YooKassa) that streamlines payment creation, redirects, and callback handling. Configure credentials, initiate payments, and process notifications with framework-friendly APIs to add Yandex payments to your app quickly.
Before you install, check compatibility. This package's last release was October 2018, which predates Laravel 6. Expect composer.json to pin illuminate/support and guzzlehttp/guzzle to 5.x-era constraints. On a modern Laravel app, install in a scratch branch first:
composer require professionalweb/payment-laravel-yandex
If resolution fails, don't force it blindly — check composer why-not output. You generally have three options, in order of preference: (1) use it on a legacy Laravel 5.x service where it was designed to run, (2) fork it and bump the constraints yourself (the wrapper is thin — mostly a service provider, a config file, and a facade), or (3) skip the wrapper and depend on the underlying driver library directly.
Where to look first — in this order:
vendor/professionalweb/payment-laravel-yandex/composer.json — the real, authoritative statement of what it supports.vendor/professionalweb/payment-laravel-yandex/config/ — the published config is the actual API surface. Driver names, credential keys, and endpoint URLs all live here.vendor/professionalweb/payment-laravel-yandex/src/ — the service provider's register()/boot() tells you how drivers are bound into the container.github.com/SergioMadness/payonline — this package is a Laravel wrapper; all real gateway logic (signing, request shaping, response parsing) lives there. Read its source, not the wrapper's, when debugging anything protocol-related.Publish and configure:
php artisan vendor:publish --provider="ProfessionalWeb\Payment\PaymentServiceProvider"
Then populate .env with per-driver credentials (merchant id, terminal/key, secret, test-mode flag — exact names come from the published config). Keep one driver's credentials per environment; these gateways almost always have distinct test and production hosts and distinct signing secrets, and mixing them produces opaque auth errors rather than clear ones.
First use case: the highest-value thing to get working end-to-end is a single redirect flow — PayOnline or Tinkoff — on a staging server with test credentials: create an order row, generate a payment URL, redirect the user, receive the callback, verify it, mark the order paid. Do that before wiring a second driver. The drivers share an interface but not identical semantics, and it's much easier to learn the shape of the abstraction from one working example.
Resolve the driver through config, not through hardcoded class names. The whole point of this package is driver interchangeability. Structure your code so the driver is chosen by a config value or a per-order attribute:
$gateway = Payment::driver(config('payment.default')); // or $order->payment_driver
Then wrap that in your own application-level service — App\Payments\Gateway — exposing createPayment(Order $order): string and handleCallback(Request $request): PaymentResult. Do not call the facade from controllers or models. This wrapper is unmaintained; the day you need to patch, replace, or shim it, a single adapter class is the difference between a one-file change and a codebase-wide refactor. Treat the package as an implementation detail behind your own interface.
Model the flow as a state machine on your own order record. The package handles protocol; it does not handle your business state. Persist at minimum: your order id, the gateway's payment/transaction id, the driver name, the amount and currency as sent, raw request payload, raw callback payload, and a status enum (pending → awaiting_callback → paid | failed | refunded). Every gateway here is asynchronous — the redirect returning is not proof of payment.
Callbacks are a separate, hardened entry point. Put them on a dedicated route with:
withoutMiddleware(['web']) or an explicit exclusion from VerifyCsrfToken — gateways POST form-encoded, never with a CSRF token.Idempotency matters because all of these providers retry. Guard with updateOrCreate on the gateway transaction id, or a unique index plus a caught duplicate-key exception. If you process a duplicate callback twice you will double-credit an account.
Verify the callback against your stored order, not against itself. Check the signature/checksum, then compare the callback's amount and currency to the order's recorded amount. A valid signature on a tampered amount is still a tampered amount if you don't cross-check.
Multi-driver routing is a real pattern here. With PayOnline, Tinkoff, Yandex.Kassa, processing.kz, and upc.ua in one package, the natural use is region/currency-based selection: RUB → Yandex.Kassa or Tinkoff, KZT → processing.kz, UAH → upc.ua. Drive that from a config map rather than if chains, and make it overridable per order so support can force a gateway.
Integration tips:
This is effectively abandonware. Budget for a fork. Fifteen stars, zero dependents, and no releases since 2018 means no security patches, no Laravel 9/10/11 support, and no issue triage. Vendor it into your repo (composer require then commit vendor/ is ugly; better: fork to your org and require the VCS URL) so your build can't break upstream and you can apply fixes. Before committing to it, evaluate whether the underlying SergioMadness/payonline drivers are worth keeping versus calling the gateway's HTTP API directly — for a single gateway, a 200-line custom client is often less risk than an unmaintained abstraction layer.
Amounts will bite you. Every one of these providers has opinions about minor units: some expect kopecks as an integer, some expect a decimal string with two places. The library may or may not normalize this per driver. Verify by testing the smallest possible amount (1 RUB / 1 KZT) against the sandbox and confirm what actually arrives. Never pass a float amount through the abstraction.
Callback encoding is not JSON. Expect application/x-www-form-urlencoded. If you've become accustomed to $request->validate() on JSON APIs, note that $request->all() will work but nested structures usually arrive flat with bracket notation. Parse with the driver's helper if it provides one.
The callback response body is part of the protocol. Most of these gateways require a specific literal response (often a plain-text token or empty 200). If you return a view, a JSON envelope, or a redirect, the gateway will retry forever. Return the raw string with an explicit Content-Type: text/plain.
Fiscalization / 54-FZ receipt data. Yandex.Kassa (and Tinkoff) require receipt items with VAT rate, quantity, and price for Russian fiscalization. If the wrapper's request builder doesn't expose these fields, you'll need to extend or override the driver — check for a method that accepts an arbitrary payload array before assuming it's impossible.
Test and production are separate worlds. Separate hosts, separate secrets, and frequently different terminal ids. A stale PAYMENT_TEST_MODE=true in production is a classic incident. Assert the mode flag at boot in your payment service constructor.
Extension points, best to worst: (1) implement your own driver against the package's driver interface and bind it in config/container; (2) extend an existing driver class and override the request/response builder; (3) swap the facade binding in a service provider for your adapter. Avoid editing files in vendor/.
Debugging checklist when a payment silently fails: log the exact outbound request (URL, headers, body), confirm the signature algorithm and field ordering matches the driver's expected order (sorting bugs here are the single most common failure), confirm the amount format, confirm you're hitting the correct host, then test the same payload with curl outside Laravel. If curl succeeds and the app fails, it's a config or client-injection issue, not a protocol issue.
No documentation means source is truth. Don't trust the 2018 README's method names; read the interfaces in the base library and use php artisan tinker to introspect the bound driver instance before writing calling code.
How can I help you explore Laravel packages today?