atanunu / laravel-xpresswallet
Laravel integration for Providus Xpress Wallet API with token storage, caching, logging and audit.
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.8|^8.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- infection/infection: ^0.29 || ^0.31
- larastan/larastan: ^2.9
- laravel/pint: ^1.14
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/phpstan: ^1.11
- roave/security-advisories: dev-latest
- vimeo/psalm: ^5.26 || ^6.0 || dev-master
README
This is a Laravel SDK for the Providus Bank XpressWallet API. It handles:
- Login and token refresh (X-Access-Token / X-Refresh-Token)
- Secure token storage (DB) + caching
- Request/response logging & auditing (masking, truncation, correlation IDs)
- Automatic retries with exponential (full-jitter) backoff & 429 handling
- Automatic 401 refresh
- Circuit breaker (configurable failure threshold & cool-down)
- Rate limiting detection & events
- Optional GET response caching
- Idempotency-Key automatic header for unsafe methods
- Pagination helper (
paginate()
) - Webhook verification (multi-secret rotation & optional async queue)
- OpenTelemetry tracing (optional)
- Example controllers & routes
- Pruning command for old logs & limiting token history
- Publishable config & migrations
- Testbench-powered tests, GitHub Actions CI, PHPStan level 8, Psalm (complementary), mutation testing config
- Public endpoint coverage dashboard: https://atanunu.github.io/laravel-xpresswallet/
- HTML API Guide (optional proxy routes): https://atanunu.github.io/laravel-xpresswallet/api-guide.html
- Automatic Packagist sync on pushes & tags (set secrets
PACKAGIST_USERNAME
&PACKAGIST_TOKEN
).
Installation
composer require atanunu/laravel-xpresswallet php artisan vendor:publish --provider="Atanunu\XpressWallet\XpressWalletServiceProvider" --tag=xpresswallet-config php artisan vendor:publish --provider="Atanunu\XpressWallet\XpressWalletServiceProvider" --tag=xpresswallet-migrations php artisan migrate
Configuration Overview
Once published, open config/xpresswallet.php
. Key sections & env flags:
Core Credentials:
XPRESSWALLET_BASE_URL
– API base (e.g. sandbox URL)XPRESSWALLET_EMAIL
,XPRESSWALLET_PASSWORD
– raw credentials (auto base64 on login)
Retry & Backoff:
XPRESSWALLET_RETRY_ATTEMPTS
(default 2 total attempts)XPRESSWALLET_RETRY_DELAY
/XPRESSWALLET_RETRY_MAX_DELAY
XPRESSWALLET_RETRY_FULL_JITTER
(bool) – full jitter algorithmXPRESSWALLET_RATE_LIMIT_ATTEMPTS
– separate cap for 429 retries
Authentication & Tokens:
XPRESSWALLET_AUTO_REFRESH
– auto refresh on first 401- Cache TTL & keys under
cache
array
Logging:
XPRESSWALLET_LOG_BODIES
– log raw bodies (careful in prod)XPRESSWALLET_MAX_BODY
– truncate lengthXPRESSWALLET_MASK_TOKENS
– mask auth headers
Retention & Pruning:
XPRESSWALLET_RETENTION_DAYS
– older logs/webhooks removed by prune commandXPRESSWALLET_MAX_TOKENS
– keep only latest N token rows
Webhooks:
XPRESSWALLET_WEBHOOK_SECRET
– primary secret (legacy)XPRESSWALLET_WEBHOOK_SECRETS
– comma-separated list for rotationXPRESSWALLET_WEBHOOK_SIGNATURE_HEADER
XPRESSWALLET_WEBHOOK_TOLERANCE
XPRESSWALLET_WEBHOOK_ASYNC
– queue processing (provide queue config)
Response Caching:
XPRESSWALLET_CACHE_GET_TTL
– seconds to cache successful GETs (0 disables)
Correlation IDs:
XPRESSWALLET_CORRELATION_HEADER
– header injected; logged & can propagate downstream
Circuit Breaker:
XPRESSWALLET_CB_ENABLED
XPRESSWALLET_CB_FAILURES
– consecutive failures to openXPRESSWALLET_CB_COOLDOWN
– cool-down in seconds
Idempotency:
XPRESSWALLET_IDEMPOTENCY_AUTO
(bool)XPRESSWALLET_IDEMPOTENCY_HEADER
(defaultIdempotency-Key
)
OpenTelemetry:
XPRESSWALLET_OTEL_ENABLED
XPRESSWALLET_OTEL_SERVICE
All settings may be overridden per environment. Keep secrets in .env
and do not commit them.
Quick start
use Atanunu\XpressWallet\Facades\XpressWallet; $response = XpressWallet::customers()->all();
Example routes
After installing in a Laravel app, you can load example routes:
Route::prefix('xpress-demo')->middleware('web')->group(function() { \Atanunu\XpressWallet\Routes\routes(); });
Built-in API Routes (Optional)
The package can automatically expose a comprehensive set of REST-style proxy endpoints (customers, wallets, transfers, cards, merchant, team, transactions). They are DISABLED by default to avoid accidentally exposing sensitive actions.
Enable in .env
(after publishing config):
XPRESSWALLET_ROUTES_ENABLED=true XPRESSWALLET_ROUTES_PREFIX=xpresswallet XPRESSWALLET_ROUTES_MIDDLEWARE=api,auth:sanctum
Notes:
- Use strong auth / rate limiting. These proxy real money-moving operations.
- Adjust prefix & middleware in
config/xpresswallet.php
under theroutes
key. - Routes register only when
enabled
and routes cache is not already built (standard Laravel behavior). - Best suited for internal tools or rapid prototyping; for production domains build thin wrappers enforcing business rules.
Examples (with default prefix):
GET /xpresswallet/customers
– list customersPOST /xpresswallet/wallets
– create walletPOST /xpresswallet/transfers/bank
– bank transferPOST /xpresswallet/cards/setup
– initiate card setup
See src/Routes/routes.php
for the full route map and names (all names start with xpresswallet.
).
Testing & Coverage
Basic test run:
composer test
To generate coverage locally (requires Xdebug or PCOV):
# Enable Xdebug in php.ini (add: zend_extension=xdebug) # Then run composer coverage
If you prefer PCOV (often faster):
pecl install pcov echo "extension=pcov" >> $(php --ini | grep ".ini" | head -1 | awk '{print $NF}') php -d pcov.enabled=1 -d pcov.directory=src vendor/bin/pest --coverage
CI runs a separate coverage job (PHP 8.3) – you can later enforce a minimum threshold by raising the --min
value in the workflow once baseline is established.
CI
GitHub Actions workflows:
ci.yml
: matrix quality (PHP 8.2/8.3) + dedicated coverage job (8.3 with Xdebug).release-draft.yml
: auto-drafts release notes from merged PR labels on tag push (v*.*.*
).
Planned enhancements you can enable:
- Upload coverage to Codecov (add a step with
codecov/codecov-action
). - Add a minimum coverage gate by increasing
--min=0
to your baseline (e.g. 70).
Maintenance / Pruning
Run periodically (e.g. daily):
php artisan xpress:prune
Use --dry-run
to preview and --days=30
to override retention for that run.
Webhooks
Add the middleware to your webhook route:
Route::post('/xpress/webhook', XpressWebhookController::class) ->middleware(\Atanunu\XpressWallet\Http\Middleware\VerifyXpressWebhook::class);
Set XPRESSWALLET_WEBHOOK_SECRET
and (optionally) XPRESSWALLET_WEBHOOK_SIGNATURE_HEADER
.
Events
Dispatched:
Atanunu\XpressWallet\Events\LoginSucceeded
Atanunu\XpressWallet\Events\TokensRefreshed
Atanunu\XpressWallet\Events\RateLimited
(each 429 attempt with method/url/attempt/retryAfter)Atanunu\XpressWallet\Events\CircuitBreakerOpened
(breaker transition)
Use listeners to instrument metrics, alerts or custom logging.
Example listener registration:
Event::listen(\Atanunu\XpressWallet\Events\RateLimited::class, function($e) { logger()->warning('Xpress rate limited', ['url' => $e->url, 'attempt' => $e->attempt, 'retry_after' => $e->retryAfterSeconds]); });
Pagination Helper
Use paginate()
when endpoint accepts page
& per_page
:
$page1 = XpressWallet::client()->paginate('customers', [], 1, 50); while ($next = $page1['meta']['next_page']) { $page1 = XpressWallet::client()->paginate('customers', [], $next, 50); }
Rate Limiting & Circuit Breaker
On 429, the client retries with full-jitter until rate_limit_max_attempts
reached then throws RateLimitException
(contains retryAfterSeconds
).
Consecutive failures trigger a breaker; once open, calls throw CircuitBreakerOpenException
until cool-down passes.
GET Response Caching
Enable by setting XPRESSWALLET_CACHE_GET_TTL>0
. Subsequent identical GET calls within TTL return cached payload (per URI+query). Use prudent TTLs for data freshness.
Idempotency
Unsafe methods automatically include an Idempotency-Key
header (UUID) unless you override or disable via config. Set your own key by passing it in headers: XpressWallet::client()->post('endpoint', [...]);
(add custom header via method overload / PR for header injection if needed).
Tracing (OpenTelemetry)
If OpenTelemetry SDK is installed, spans are created per request (attributes: http.method
, http.url
, http.status_code
, errors flagged). Configure exporter in your host app; this package only emits spans when the global tracer provider is available.
Static Analysis
Run PHPStan (Larastan) locally: Run Psalm (complementary):
composer psalm
composer analyse
Raised to level 8 (strict). Keep code green by adding types / phpdoc when extending.
Dependency Updates
Consider enabling Dependabot (.github/dependabot.yml
):
version: 2 updates: - package-ecosystem: "composer" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly"
Security
Recommendations:
- Add
roave/security-advisories
(conflict package) inrequire-dev
for vulnerable dependency prevention. - Run
composer audit
in CI (Composer 2.4+). - CodeQL workflow is currently disabled pending general availability for PHP; rely on PHPStan + Infection.
Release Process
- Update
CHANGELOG.md
(optional – or rely on auto draft). - Bump version in your tag:
git tag v0.1.0 && git push origin v0.1.0
. - GitHub Action drafts release notes (edit if needed, then publish).
Support / Issues
Open issues or PRs with clear reproduction steps. Use labels (bug
, feature
, docs
, test
) to improve autogenerated changelog quality.
Roadmap / Future
Planned and potential enhancements (open to contribution):
Short Term
- Mutation score badge automation (parse latest Infection report and update Shields endpoint / static JSON).
- Psalm baseline generation & gradual tightening of errorLevel.
- Additional feature tests for edge-case error mappings (422 variants, timeout simulation).
- Webhook middleware queueable job example & configurable model table names.
Medium Term
- Pluggable cache store strategy (allow per-endpoint TTL overrides).
- Configurable idempotency key provider interface.
- Extended OpenTelemetry attributes (retry count, cache hit, circuit state).
- Optional Redis-backed circuit breaker (distributed state) fallback.
- Pagination auto-iterator helper (Generator yielding pages transparently).
Long Term / Ideas
- Laravel Horizon metrics integration example (events -> metrics).
- Publish a companion CLI to inspect stored tokens, breaker state, recent API logs.
- Automatic generation of typed response DTOs via schema inference (if API specs become available).
- Multi-tenant token segregation strategy helpers.
- Add a lightweight HTTP fakes/replay tool for offline testing of recorded interactions.
Have another idea? Open a discussion or PR—align proposals with principles: stability, observability, least surprise.
License
MIT