elgibor-solution / laravel-payment-bri
Laravel package for Bank BRI payments: QRIS MPM Dynamic (SNAP) + BRIVA Virtual Account (Non-SNAP) with push notification handlers.
Requires
- php: >=8.2
- guzzlehttp/guzzle: ^7.9
- illuminate/contracts: ^10|^11|^12
- illuminate/http: ^10|^11|^12
- illuminate/support: ^10|^11|^12
README
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.
Contents
- 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
Requirements
- PHP 8.2+
- Laravel 10.x or 11.x
ext-openssl
- Network access to BRI sandbox/production endpoints
Installation
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
.
Configuration
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'], ], ], ];
Environment Variables
# 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.
QRIS (SNAP) Usage
Namespaces
ESolution\BriPayments\Qris\QrisClient
ESolution\BriPayments\Support\SnapSignature
(internal)
1) Get Token (B2B, RSA-SHA256)
use ESolution\BriPayments\Qris\QrisClient; /** @var QrisClient $qris */ $qris = app(QrisClient::class); $token = $qris->getToken();
2) Generate Dynamic QR
$qr = $qris->generateQr( partnerReferenceNo: 'INV-2025-0001', amount: '10000.00', currency: 'IDR' ); // $qr->qrContent, $qr->referenceNo
3) Inquiry Payment
$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).
BRIVA (VA, Non-SNAP) Usage
Namespace
ESolution\BriPayments\Briva\BrivaClient
1) Get OAuth Token (Non‑SNAP)
use ESolution\BriPayments\Briva\BrivaClient; /** @var BrivaClient $briva */ $briva = app(BrivaClient::class); $token = $briva->getToken();
2) Create a VA
$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 ]);
3) Get VA / Get Payment Status
$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');
4) Update VA or Mark as Paid/Unpaid
$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) );
5) Delete VA
$del = $briva->deleteVa( config('bri.briva.institution_code'), config('bri.briva.briva_no'), 'CUST001' );
6) Reports
$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.
Webhooks
QRIS Notification
- 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 });
BRIVA Push Notification
- 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 });
Examples
Minimal Controller to Create QR (SNAP)
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, ]); } }
Minimal Controller to Create BRIVA
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); } }
Testing Tips (Postman/cURL)
- 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.
Production Notes & Security
- 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.
Versioning
Semantic versioning (MAJOR.MINOR.PATCH). Breaking changes will bump MAJOR.
Support & Hiring
Need professional help or want to move faster? Hire the E-Solution / Elgibor team for integration, audits, or custom features.
📧 info@elgibor-solution.com
Donations
If this package saves you time, consider supporting development ❤️
License
Apache-2.0