Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Atol Client Bundle Laravel Package

lamoda/atol-client-bundle

Symfony bundle that integrates the ATOL Online client, providing service configuration for fiscal receipt registration with ATOL cash registers. Simplifies adding ATOL fiscalization to PHP/Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin Laravel wrapper around atol-client — the transport layer for ATOL Online (fiscal receipts / cloud kassa). It handles config, container binding, and token/session plumbing; the actual request building and response DTOs still come from atol-client, so read that package's README alongside this one.

  1. composer require <vendor>/laravel-atol — with this release you'll pull atol-client ^1.6.3 and guzzlehttp/guzzle ^6.2 || ^7.3.
  2. Publish config: php artisan vendor:publish --tag=atol-config (check the tag in the README; some releases ship a config/atol.php you can copy manually).
  3. Fill .env with the four credentials you get from the ATOL personal cabinet: group code, login, password, and the endpoint (sandbox vs. production are different hosts — pick deliberately).
  4. Verify the binding resolves: php artisan tinker → app(\Atol\Client::class) or the facade, and confirm the config values land where you expect.
  5. First use case: fiscalise a single test sale in the sandbox before writing any business logic. Build the smallest possible receipt (one item, one payment method), send it, capture the returned UUID, then poll the status until it settles. If that round-trip works, everything else is configuration.

Don't wire it into checkout until you've done that sandbox run end to end — receipt payload errors come back as opaque validation failures and are much easier to read in isolation.

Implementation Patterns

Synchronous send, asynchronous confirmation. ATOL is not a synchronous fiscaliser. The standard shape is: send sell, persist the returned UUID against your order, and treat "queued" as the success signal. A queued job then polls getStatus and reconciles the order when the receipt reaches done. Don't block a user-facing HTTP request waiting for done — it can take seconds to minutes.

Wrap every call in a job. Both the send and the poll belong in queue jobs with retry. This gives you backoff for free and keeps timeouts out of your request cycle. Give the send job a low $tries (2–3) and make polling its own scheduled job so a stuck receipt doesn't hold a worker.

Idempotency lives on your side, not the API's. ATOL will happily create duplicate receipts if you retry a send. Generate your own key (order ID + attempt counter + a stable UUID for the receipt's external_id/uuid field — naming varies by atol-client version), store it before dispatch, and short-circuit on replay. This is the single most important pattern in the package; fiscal double-charges are painful to unwind.

Use the DTOs from atol-client, not raw arrays. Passing arrays works until a required nested field moves. The client's item/payment/receipt objects give you IDE support and catch structural mistakes before they become API errors. Keep a thin mapping layer in your own code (Order → Receipt) so an upstream DTO change is a one-file fix.

Multi-store / multi-tenant. The group code is per-merchant. If you operate several legal entities, don't rely on the singleton binding — resolve a fresh client per tenant with overridden config (app()->make(...) with a config array, or Config::set before resolution, depending on what the package exposes). Confirm whether the token cache is keyed by group code; if it isn't, tenants will bleed sessions into each other.

Testing. Http::fake() against the ATOL endpoint is the pragmatic route: stub a queued response with a UUID, then a done response, and assert your order state machine. Build one fake fixture per status (queued, done, error, cancel) — your reconciliation logic needs all four, and the error shape is the one people forget.

Integration ordering for checkout: create order → dispatch fiscalise job (transactional outbox or dispatchAfterResponse) → poll → mark order fiscally complete → notify customer. Keep "fiscally complete" a separate flag from "paid"; they diverge during ATOL outages and you want to be able to answer "which orders are unm fiscalised?" with a single query.

Gotchas and Tips

This release is a dependency bump, not a feature release. Two constraints moved: atol-client ^1.6.3 and guzzlehttp/guzzle ^6.2 || ^7.3. Treat it as low-risk but run composer why guzzlehttp/guzzle afterwards — if another package pins Guzzle 6, Composer may resolve you onto the ^6.2 branch silently. Guzzle 6 is long EOL; you want 7.3+ in production.

^1.6.3 is a hard floor. If you were on an older atol-client, check its changelog for DTO shape changes before upgrading — this wrapper won't insulate you from those. Pin the wrapper version in composer.json and read the diff when you bump.

Credential leakage in logs. Receipt payloads contain customer names, emails, phone numbers, and line-item detail. Guzzle's default debug output and Laravel's HTTP client logging will both happily write these to storage/logs. Configure the client's logger/handler stack deliberately, and never enable debug in production.

Token/session caching. The client authenticates and caches a token. If your cache driver is array (e.g. under Octane or in a queue worker with no shared store), you'll re-authenticate on every call and eventually hit rate limits. Use Redis or the database cache for the token store, and verify the TTL is shorter than the real token lifetime.

VAT and rounding. Use strings or integers (kopecks), never floats. atol-client expects specific VAT rate identifiers (none / 0 / 10 / 20 / calculated) as strings — passing 20 instead of "20" or "vat20" (naming varies) fails validation at the API, not locally. Test with a discounted line and an odd kopeck amount; that's where rounding drift appears.

Fiscal receipts are immutable. Once fiscalised you cannot edit or delete — only refund (full/partial) or issue a correction. Model this in your schema: a receipt row with status and a separate refunds relation, not a mutable order field. Design your admin UI around "issue refund", never "change this receipt".

Retries can duplicate. Default job $tries plus Guzzle retry middleware can compound. Pick one retry strategy — prefer the queue's — and make the send job check for an existing receipt by your idempotency key before dispatching to ATOL.

Sandbox behaves differently from production. Sandbox is more permissive on some field validation and returns status transitions faster. Don't tune timeouts or backoff against sandbox timings.

Extension points: if you need custom headers, a proxy, or a mock transport for tests, look for the container binding for the underlying Guzzle client in the service provider — rebinding or decorating it there is cleaner than subclassing the wrapper. Same for the token store: if it's bound, swap it; if it's constructed inline, that's your cue to open an issue rather than fight it.

Timestamps and timezone. Receipt timestamps are Moscow time. Convert at the boundary and store UTC in your DB, or you'll get off-by-hours mismatches when reconciling against your own order times.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core