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

Paysera Wallet Php Client Laravel Package

evp/paysera-wallet-php-client

A PHP client for the Paysera Wallet API that simplifies authentication, payment initiation, and callback handling. Integrates cleanly into Laravel and plain PHP projects, giving developers a straightforward, object-oriented wrapper for Paysera e-wallet transactions.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require evp/paysera-wallet-php-client guzzlehttp/guzzle

There is no service provider, no config file to publish, and no facade. You wire it yourself in config/services.php:

// config/services.php
'paysera_wallet' => [
    'client_id'     => env('PAYSERA_WALLET_CLIENT_ID'),
    'client_secret' => env('PAYSERA_WALLET_CLIENT_SECRET'),
    'redirect_uri'  => env('PAYSERA_WALLET_REDIRECT_URI'),
    'host'          => env('PAYSERA_WALLET_HOST', 'https://api.paysera.com/wallet/rest/v1'),
],

Where to look first. This is an OpenAPI-generated client, so the layout is predictable:

  • composer.json → autoload.psr-4 gives you the real root namespace (it is not always what you guess — confirm before writing use statements).
  • src/Configuration.php → host, auth, user-agent, debug toggle. This is your main integration point.
  • src/Api/*Api.php → one class per REST tag (WalletApi, TransferApi, PaymentApi, …). Each public method is one endpoint and its docblock tells you the exact request/response models.
  • src/Model/ → DTOs. Read the model for the endpoint you need; do not guess field names from the Paysera docs, they drift.

First use case: a read-only smoke test. Get a token, call the cheapest "list wallets" or "get balance" endpoint, and dump the response. This validates credentials, host, and PSR-18 wiring in one shot before you touch money movement.

// app/Providers/AppServiceProvider.php
use Paysera\WalletApi\Configuration;
use Paysera\WalletApi\Api\WalletApi;

$this->app->bind(WalletApi::class, function () {
    $config = (new Configuration())
        ->setHost(config('services.paysera_wallet.host'))
        ->setAccessToken(Cache::get('paysera.wallet.token'));

    return new WalletApi(new \GuzzleHttp\Client(['timeout' => 15]), $config);
});
// tinker / a throwaway route
$wallets = app(WalletApi::class)->getWallets();
dump($wallets); // or ObjectSerializer::sanitizeForSerialization($wallets)

Implementation Patterns

OAuth token handling is your job. The generated client accepts an access token via Configuration::setAccessToken(); it does not run the authorization-code flow for you. For user-scoped wallets, build the consent URL yourself (/oauth/v1/authorize on the wallet host, with client_id, redirect_uri, scope, state), exchange the code at /oauth/v1/token, and persist the token pair against your User model with an encrypted cast. For server-to-server jobs use client credentials and cache the token with a TTL slightly under expires_in.

Never bind a token-bearing client as a singleton. Tokens are per-user, so bind a factory and resolve per request/job:

final class PayseraWalletFactory
{
    public function forUser(User $user): WalletApi
    {
        $token = $this->tokenService->validToken($user); // refreshes if <60s left

        return new WalletApi(
            app(\GuzzleHttp\ClientInterface::class),
            (new Configuration())
                ->setHost(config('services.paysera_wallet.host'))
                ->setAccessToken($token->access_token),
        );
    }
}

Wrap the generated Api classes. They throw generic ApiException and return raw DTOs. Put them behind a PayseraWallet service that maps DTOs to your own value objects/exceptions. This is a 21-star, vendor-generated library — the adapter boundary is what makes it replaceable later.

Outbox pattern for money movement. Never call a transfer endpoint directly from a controller. Write your transfers row and a SendPayseraTransfer job inside one DB::transaction, then let the queue do the HTTP call. On retry, reuse the same idempotency key / client-side transaction ID you stored on the row, otherwise retries can double-send.

final class SendPayseraTransfer implements ShouldQueue
{
    use Queueable;

    public int $tries = 5;
    public array $backoff = [5, 15, 60, 300];

    public function __construct(public Transfer $transfer) {}

    public function handle(PayseraWallet $wallet): void
    {
        $wallet->transfer(
            $this->transfer->user,
            amount: $this->transfer->amount_minor,   // integer minor units
            currency: $this->transfer->currency,
            idempotencyKey: $this->transfer->uuid,   // stable across retries
        );
    }

    public function failed(Throwable $e): void
    {
        $this->transfer->update(['status' => 'failed', 'error' => $e->getMessage()]);
    }
}

Webhooks. Register a route outside web middleware (or withoutMiddleware(VerifyCsrfToken::class)), return 200 immediately, and dispatch a job. Verify the signature header before trusting anything, and dedupe on the event ID the payload carries — Paysera will redeliver.

Testing. Because the client is PSR-18, Http::fake() does nothing. Use Guzzle's MockHandler and inject the handler, or bind a fake PayseraWallet interface in the container — the latter is why the adapter from above matters:

$mock = new \GuzzleHttp\Handler\MockHandler([
    new \GuzzleHttp\Psr7\Response(200, [], json_encode(['items' => []])),
]);
$client = new \GuzzleHttp\Client(['handler' => \GuzzleHttp\HandlerStack::create($mock)]);

Gotchas and Tips

  • Vendor mismatch. The Composer package is evp/… but the upstream repo is paysera/lib-wallet-php-client. Confirm the Packagist package actually tracks upstream releases; if it lags, add the upstream as a VCS repository instead of forking.
  • PSR-18 discovery failures. "No HTTP client found" means php-http/discovery has nothing to pick. Install guzzlehttp/guzzle and always pass the client explicitly rather than relying on discovery.
  • ApiException is unhelpful by default. Log $e->getCode() (HTTP status) and $e->getResponseBody() (the JSON error body) — the message alone is usually just [422]. Map 401 → refresh token, 429 → backoff job, 4xx → permanent failure (mark the row failed, don't retry), 5xx → retry.
  • Token refresh races. Several queued jobs will refresh the same user's token simultaneously. Guard the refresh with Cache::lock("paysera.token.{$user->id}") and re-check expiry inside the lock.
  • Money is integers/minor units. The models may accept strings or ints depending on the field; never let a float pass through. Store minor units in your DB and convert only at the DTO boundary.
  • DTO debugging is painful. Dumping a model recurses badly. Use ObjectSerializer::sanitizeForSerialization($model) then json_encode to see what you're actually sending.
  • Empty vs null. Serialization drops null fields; endpoints that require an explicit null or false (e.g. clearing a field) will silently no-op. Check the generated model's getXAllowNull() / setX() signatures.
  • Dates are \DateTime objects, not strings, in both directions. Format at the edge; don't hand a Carbon instance to a strict \DateTime typed setter without converting.
  • Retries live in your HTTP client, not the API class. Add a Guzzle middleware/HandlerStack with RetryMiddleware for 429/5xx and a logging middleware — that's your only observability hook into the generated code.
  • Extension points are limited to Configuration (setHost, setUserAgent, setDebug, setAccessToken, setTempFolderPath) and injecting a custom HeaderSelector. Anything else requires subclassing a generated file, which you'll lose on the next update — prefer wrapping.
  • Environment/host confusion. Sandbox and production differ only by base URL and credentials. Make PAYSERA_WALLET_HOST explicit per environment and fail fast on boot if it's missing in production.
  • Pin the version ("evp/paysera-wallet-php-client": "1.*") and treat upgrades as a review task — generated clients change method signatures between minor releases more often than hand-written ones.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package