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

Qiwi Api Laravel Package

jeka/qiwi-api

Lightweight PHP/Laravel wrapper for the QIWI Wallet API: check balances, send payments, and manage invoices with a simple, readable client and straightforward configuration. A convenient way to add QIWI support to small projects and prototypes.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require jeka/qiwi-api

This is a plain PHP SDK, not a Laravel package — there is no service provider, facade, or publishable config. You wire it up yourself, which takes about ten minutes and is genuinely the easy part.

Where to look first:

  1. README.md on the GitHub repo for the client constructor signature and the list of available methods. That file is your only documentation; there is no hosted docs site.
  2. Skim src/ end to end. The package is small enough to read in 10–15 minutes, and you should, because you will be maintaining this code.
  3. Check composer.json for the PHP and illuminate/* constraints before committing to anything. Low-activity packages routinely lag behind current PHP 8.x and Laravel 11/12 releases.

Credentials. Get a QIWI API token from https://qiwi.com/api (Settings → API Access) and whitelist your server's outbound IPs — QIWI rejects requests from unrecognized addresses with an unhelpful error. The partner/billing API uses a site_id + secret pair instead. Either way, put it in .env and never in the repo.

First use case: a balance check or recent-payments listing. It is read-only, moves no money, and validates your token and IP whitelist before you attempt anything transactional. Once that returns, you know auth works and can start on invoices.

// app/Providers/QiwiServiceProvider.php
$this->app->singleton(\Jeka\QiwiAPI\Client::class, fn () => new \Jeka\QiwiAPI\Client(
    config('services.qiwi.token'),
));

Implementation Patterns

Bind the client as a singleton. Each instantiation usually carries auth headers and its own HTTP client. Let the container own it so you get connection reuse and one place to inject Guzzle options, timeouts, and middleware for logging.

Wrap it in a domain service. Never call the SDK from a controller. Create App\Services\QiwiService that exposes intent-shaped methods (createInvoice, getPaymentStatus, refund) and returns your own DTOs. This is the single most important pattern here — the SDK is thin and likely to be abandoned, so you want exactly one seam to swap when you move to raw Guzzle, a maintained fork, or another provider.

class QiwiService
{
    public function __construct(private \Jeka\QiwiAPI\Client $client) {}

    public function createInvoice(string $billId, int $amountRub, string $comment): Invoice
    {
        // map to the SDK call, validate the response, return your own DTO
    }
}

Invoice creation is keyed by a caller-supplied bill_id. Generate it at the business layer as a UUID (or order-{id}-{uuid}), persist it on your invoices table with status pending, then call the API. That gives you idempotency for free: a retry after a network timeout resends the same bill_id rather than creating a second bill.

Reconciliation is poll-based. QIWI does not push reliable webhooks for every account tier. The realistic pattern is a scheduled job that pulls open invoices and refreshes their status:

// bootstrap/app.php or routes/console.php
Schedule::job(new ReconcileQiwiInvoices)->everyFiveMinutes();

Have the job select invoices in pending younger than N hours, call the status endpoint, and update local state. Drive every transition through a single markPaid() / markFailed() method so domain events (PaymentSucceeded, PaymentFailed) dispatch from one place.

If your account does support webhooks, treat the handler as a trigger, not a source of truth. Always re-fetch the bill's authoritative status from the API before flipping local state, and make the handler idempotent — QIWI retries. Verify whatever signature or shared secret your merchant tier provides; do not accept the payload body on trust.

Queue every write. Anything that creates a bill, refunds, or pays out goes on a queued job with a timeout and bounded retries. Keep the HTTP call out of the request path so a slow or failing QIWI response doesn't stall checkout. Inside the job, catch transport exceptions, log with the bill_id, and let the reconciler recover anything that timed out.

Log with redaction. The SDK may serialize request payloads into exceptions. Wrap calls in a try/catch that logs method, endpoint, HTTP status, and bill_id — and explicitly strips the token and any full request body before it hits your log aggregator.

Gotchas and Tips

  • The package is effectively unmaintained. 16 stars, zero dependents, a single maintainer. Vendor or fork it early, pin the commit, and record provenance. Do not assume an upstream fix exists for anything you find.
  • Verify the endpoint version against current QIWI documentation. A stale API version is the most likely silent failure — calls return plausible-looking responses that no longer mean what the wrapper thinks they mean. Confirm the paths and field names before your first real transaction.
  • Compatibility is the fastest kill switch. If composer.json doesn't accept your PHP/Laravel version, stop and decide between a fork and raw Guzzle. Don't force it with --ignore-platform-reqs.
  • No built-in retry, timeout, or idempotency. You supply all three. Set a connect timeout (~5s) and a total timeout (~15s) on the underlying Guzzle client via the container binding, and back off on 429 and 5xx rather than hammering.
  • Status semantics are non-obvious. QIWI statuses (waiting, paid, rejected, expired) don't map cleanly onto a typical order lifecycle. Write the mapping down in one place, cover it with tests, and don't let controllers interpret raw status strings.
  • Token expiry and rotation are yours to handle. The wrapper won't refresh credentials or tell you a token has lapsed until a call fails. Alert on a spike in 401s.
  • Partial failures leave inconsistent state. A refund or payout can succeed on QIWI's side while your DB write fails, or vice versa. Treat the API as the source of truth and let the reconciliation job correct local state rather than trying to make the two atomic.
  • Extension point: because the client is a thin HTTP wrapper, the cleanest extension is not subclassing it — it's replacing it behind your QiwiService interface with Laravel's Http client when you need streaming, retry middleware, or a better testing story. Http::fake() is far nicer than mocking a third-party SDK.
  • Test against fakes, not the sandbox, in CI. Point QiwiService at a fake in tests so the suite doesn't depend on QIWI's sandbox availability, and keep a small opt-in integration test suite for pre-release checks.
  • Ramp-up is a day or two of code, weeks of domain knowledge. The tricky part isn't the SDK, it's QIWI's settlement timing and reconciliation quirks. Capture that in the reconciler's tests and a short runbook, not in someone's head.
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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings