bankapi-laravel maintained by codetay
codetay/bankapi-laravel
Laravel bridge for codetay/bankapi-php: auto-registered
service provider, a BankApi facade, and a webhook-verifying middleware.
Install
composer require codetay/bankapi-laravel
The service provider (BankApi\Laravel\BankApiServiceProvider) is
auto-discovered. Publish the config file:
php artisan vendor:publish --tag=bankapi-config
This creates config/bankapi.php. Set these environment variables:
BANKAPI_KEY=bk_live_...
BANKAPI_BASE_URL=https://<your-org-slug>.bankapi.vn
BANKAPI_WEBHOOK_SECRET=whsec_...
Set BANKAPI_BASE_URL to your organization host (https://<your-org-slug>.bankapi.vn):
org-scoped operations (banking, webhook endpoints) are rejected with a 403 on
a non-organization host.
BANKAPI_WEBHOOK_SECRET is the secret shown once when you create a webhook
endpoint (see the core SDK README) — required only if you use the webhook
middleware.
The base URL must be https (plain http only for loopback hosts in local
development) — the API key travels on every request, so anything else is
refused at boot.
When Guzzle is installed, the package builds its HTTP client with bounded
timeouts and redirect following turned off (the API key rides in the
X-API-Key header, which Guzzle does not strip when a redirect changes host).
Tune the timeouts if your network needs it:
BANKAPI_HTTP_TIMEOUT=10
BANKAPI_HTTP_CONNECT_TIMEOUT=5
Facade usage
use BankApi\Laravel\Facades\BankApi;
$page = BankApi::banking()->transactions(limit: 50);
foreach ($page->autoPaging() as $transaction) {
// ...
}
$created = BankApi::webhookEndpoints()->create(
url: 'https://myapp.test/hooks/bankapi',
eventTypes: ['bank.credit'],
);
// $created->secret (whsec_...) is shown ONLY here — store it now.
The facade resolves the same singleton BankApi\BankApi instance the
container builds from config/bankapi.php; you can also type-hint
BankApi\BankApi for constructor injection instead of the facade.
Webhook route
Register a route and attach the bankapi.webhook middleware alias. On
success it verifies the signature, puts the decoded event on the request as
bankapi_event, and dispatches a BankApi\Laravel\Events\BankApiWebhookReceived
event; on failure it aborts the request with 400 (bad signature) or 500
(webhook secret not configured).
use BankApi\Webhook\Event;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::post('/webhooks/bankapi', function (Request $request) {
/** @var Event $event */
$event = $request->attributes->get('bankapi_event');
// handled asynchronously via the BankApiWebhookReceived listener below;
// just acknowledge quickly here.
return response()->noContent();
})->middleware('bankapi.webhook');
CSRF exclusion
Webhook requests come from BankAPI's servers, not a browser session, so the route must be excluded from CSRF verification:
Laravel 11+ (in bootstrap/app.php):
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'webhooks/*',
]);
})
Laravel 10 (in app/Http/Middleware/VerifyCsrfToken.php):
protected $except = [
'webhooks/*',
];
Handling the event
Prefer a queued listener over handling the webhook inline in the route, so
BankAPI gets a fast 2xx and retries don't pile up. Dedupe on
$event->event->deliveryId — BankAPI retries deliveries on 408/429/5xx
responses, so the same delivery can arrive more than once.
namespace App\Listeners;
use BankApi\Laravel\Events\BankApiWebhookReceived;
use Illuminate\Contracts\Queue\ShouldQueue;
final class HandleBankApiWebhook implements ShouldQueue
{
public function handle(BankApiWebhookReceived $listenerEvent): void
{
$event = $listenerEvent->event; // BankApi\Webhook\Event
if (WebhookDelivery::query()->where('delivery_id', $event->deliveryId)->exists()) {
return; // already processed
}
match ($event->type) {
'bank.credit' => ProcessCredit::dispatch($event->data),
default => null,
};
WebhookDelivery::create(['delivery_id' => $event->deliveryId]);
}
}
Register it in EventServiceProvider (or via Event::listen()):
protected $listen = [
\BankApi\Laravel\Events\BankApiWebhookReceived::class => [
\App\Listeners\HandleBankApiWebhook::class,
],
];
See the core SDK README for the exception hierarchy, pagination, and the raw signature-verification contract.