Namespace: ESolution\BriPayments
License: Apache-2.0
This package provides a pragmatic Laravel integration for Bank BRI payments:
- QRIS MPM Dynamic (SNAP): B2B access token (RSA-SHA256), generate QR, inquiry, and a ready-to-wire webhook controller.
- BRIVA (Virtual Account, Non‑SNAP): OAuth token, create/get/update/delete VA, get payment status, reports, and a push-notification verifier.
It is designed to be production-friendly, with clear signatures, headers, and timestamps handled for you.
⚠️ Always verify the latest BRI docs for any contract changes. This package follows BRI’s official docs for the endpoints and headers.
- Requirements
- Installation
- Configuration
- Environment Variables
- QRIS (SNAP) Usage
- BRIVA (VA, Non-SNAP) Usage
- Webhooks
- Examples
- Testing Tips (Postman/cURL)
- Production Notes & Security
- Versioning
- Support & Hiring
- Donations
- License
- PHP 8.2+
- Laravel 10.x or 11.x
ext-openssl
- Network access to BRI sandbox/production endpoints
composer require elgibor-solution/laravel-payment-bri
php artisan vendor:publish --provider="ESolution\BriPayments\BriPaymentsServiceProvider" --tag=bri-payments-config
This publishes a config file at config/bri.php
.
config/bri.php
return [
'base_url' => env('BRI_BASE_URL', 'https://sandbox.partner.api.bri.co.id'),
'common' => [
'client_id' => env('BRI_CLIENT_ID'),
'client_secret' => env('BRI_CLIENT_SECRET'),
],
'qris' => [
'partner_id' => env('BRI_SNAP_PARTNER_ID'),
'channel_id' => env('BRI_SNAP_CHANNEL_ID', '95221'),
'merchant_id' => env('BRI_SNAP_MERCHANT_ID'),
'terminal_id' => env('BRI_SNAP_TERMINAL_ID'),
'private_key_path' => env('BRI_SNAP_PRIVATE_KEY_PATH'),
'public_key_path' => env('BRI_SNAP_PUBLIC_KEY_PATH'), // optional: verify incoming signatures
'timeout' => env('BRI_SNAP_TIMEOUT', 30),
'notify' => [
'enabled' => true,
'uri' => 'bri/qris/notify',
'middleware' => ['api'],
],
],
'briva' => [
'institution_code' => env('BRI_BRIVA_INSTITUTION_CODE'),
'briva_no' => env('BRI_BRIVA_NUMBER'),
'timeout' => env('BRI_BRIVA_TIMEOUT', 30),
'notify' => [
'enabled' => true,
'uri' => 'bri/briva/notify',
'middleware' => ['api'],
],
],
];
# Common
BRI_BASE_URL=https://sandbox.partner.api.bri.co.id
BRI_CLIENT_ID=your_client_id
BRI_CLIENT_SECRET=your_client_secret
# SNAP (QRIS)
BRI_SNAP_PARTNER_ID=your_partner_id
BRI_SNAP_CHANNEL_ID=95221
BRI_SNAP_MERCHANT_ID=00007100010926
BRI_SNAP_TERMINAL_ID=213141251124
BRI_SNAP_PRIVATE_KEY_PATH=storage/keys/bri-snap-private.pem
BRI_SNAP_PUBLIC_KEY_PATH=storage/keys/bri-snap-public.pem
BRI_SNAP_TIMEOUT=30
# BRIVA (Non-SNAP)
BRI_BRIVA_INSTITUTION_CODE=J104408
BRI_BRIVA_NUMBER=77777
BRI_BRIVA_TIMEOUT=30
Keep keys outside your repository; do not commit secrets. Consider using a secret manager or encrypted storage.
Namespaces
ESolution\BriPayments\Qris\QrisClient
ESolution\BriPayments\Support\SnapSignature
(internal)
use ESolution\BriPayments\Qris\QrisClient;
/** @var QrisClient $qris */
$qris = app(QrisClient::class);
$token = $qris->getToken();
$qr = $qris->generateQr(
partnerReferenceNo: 'INV-2025-0001',
amount: '10000.00',
currency: 'IDR'
);
// $qr->qrContent, $qr->referenceNo
$status = $qris->inquiryPayment(
originalReferenceNo: $qr->referenceNo,
terminalId: config('bri.qris.terminal_id')
);
// Use latestTransactionStatus (e.g., "00" for success) according to BRI docs
SNAP business requests sign with HMAC-SHA512 over canonical string; headers include
Authorization: Bearer <accessToken>
,X-TIMESTAMP
,X-SIGNATURE
,X-PARTNER-ID
,CHANNEL-ID
, andX-EXTERNAL-ID
(package also sendsX-EXTRENAL-ID
for compatibility).
Namespace
ESolution\BriPayments\Briva\BrivaClient
use ESolution\BriPayments\Briva\BrivaClient;
/** @var BrivaClient $briva */
$briva = app(BrivaClient::class);
$token = $briva->getToken();
$res = $briva->createVa([
'institutionCode' => config('bri.briva.institution_code'),
'brivaNo' => config('bri.briva.briva_no'),
'custCode' => 'CUST001',
'nama' => 'John Doe',
'amount' => '25000', // string, numbers only
'keterangan' => 'Invoice INV-001',
'expiredDate' => '2025-12-31 23:59:59', // YYYY-MM-DD HH:mm:ss
]);
$va = $briva->getVa(config('bri.briva.institution_code'), config('bri.briva.briva_no'), 'CUST001');
$status = $briva->getStatus(config('bri.briva.institution_code'), config('bri.briva.briva_no'), 'CUST001');
$update = $briva->updateVa([/* ...payload per BRI docs... */]);
$mark = $briva->updateStatus(
config('bri.briva.institution_code'),
config('bri.briva.briva_no'),
'CUST001',
'Y' // Y = paid, N = unpaid (check docs)
);
$del = $briva->deleteVa(
config('bri.briva.institution_code'),
config('bri.briva.briva_no'),
'CUST001'
);
$report = $briva->getReport(
config('bri.briva.institution_code'),
config('bri.briva.briva_no'),
'2025-01-01', '2025-01-31'
);
$reportTime = $briva->getReportTime(
config('bri.briva.institution_code'),
config('bri.briva.briva_no'),
'2025-01-01', '00:00',
'2025-01-02', '23:59'
);
Non‑SNAP requests use
BRI-Signature
=base64(HMAC_SHA256(payload, client_secret))
andBRI-Timestamp
(UTC). ForDELETE /v1/briva
, BRI expectsContent-Type: text/plain
withinstitutionCode=&brivaNo=&custCode=
body — the package handles this and signs exactly that body.
- Route:
POST /bri/qris/notify
(can be changed inconfig/bri.php
) - Controller:
ESolution\BriPayments\Http\Controllers\QrisNotificationController@handle
- Event:
ESolution\BriPayments\Events\QrisPaymentNotified
Usage:
use ESolution\BriPayments\Events\QrisPaymentNotified;
use Illuminate\Support\Facades\Event;
Event::listen(QrisPaymentNotified::class, function ($event) {
// $event->payload, $event->headers, $event->validSignature
// Update your order/payment here
});
- Route:
POST /bri/briva/notify
(can be changed inconfig/bri.php
) - Controller:
ESolution\BriPayments\Http\Controllers\BrivaNotificationController@handle
- Event:
ESolution\BriPayments\Events\BrivaPaymentNotified
The controller verifies BRI-Signature
. BRI’s push docs often sign using the absolute partner URL as path
. Some integrations sign only the path (without scheme/host). The package verifies both for compatibility.
Usage:
use ESolution\BriPayments\Events\BrivaPaymentNotified;
use Illuminate\Support\Facades\Event;
Event::listen(BrivaPaymentNotified::class, function ($event) {
// $event->payload, $event->headers, $event->validSignature
// Update your VA/payment here
});
use ESolution\BriPayments\Qris\QrisClient;
class PaymentController
{
public function createQris(QrisClient $qris)
{
$qr = $qris->generateQr('INV-2025-0001', '10000.00');
return response()->json([
'referenceNo' => $qr->referenceNo ?? null,
'qrContent' => $qr->qrContent ?? null,
]);
}
}
use ESolution\BriPayments\Briva\BrivaClient;
class VaController
{
public function create(BrivaClient $briva)
{
$res = $briva->createVa([
'institutionCode' => config('bri.briva.institution_code'),
'brivaNo' => config('bri.briva.briva_no'),
'custCode' => 'CUST-ABC-001',
'nama' => 'Jane Doe',
'amount' => '150000',
'keterangan' => 'Order #12345',
'expiredDate' => now()->addDay()->format('Y-m-d H:i:s'),
]);
return response()->json($res);
}
}
- Use BRI Sandbox credentials.
- For SNAP token, ensure your RSA private key matches the client key.
- For BRIVA, verify
BRI-Timestamp
is UTC (ISO 8601). - To test BRIVA DELETE, send
text/plain
body exactly as BRI expects. - For webhooks, expose your local URL using
ngrok
and register it with BRI.
- Rotate secrets regularly and do not log sensitive headers or bodies.
- Constrain webhook routes with IP allowlist or additional shared secrets if possible.
- Implement idempotency for webhook processing to avoid double credits.
- Add retries/backoff for intermittent gateway errors.
- Monitor and alert on non-
00
statuses and signature mismatches.
Semantic versioning (MAJOR.MINOR.PATCH). Breaking changes will bump MAJOR.
Need professional help or want to move faster? Hire the E-Solution / Elgibor team for integration, audits, or custom features.
📧 [email protected]
If this package saves you time, consider supporting development ❤️
Apache-2.0