elgibor-solution/laravel-payment-bri

Laravel package for Bank BRI payments: QRIS MPM Dynamic (SNAP) + BRIVA Virtual Account (Non-SNAP) with push notification handlers.

1.0.0 2025-09-17 08:25 UTC

This package is auto-updated.

Last update: 2025-09-17 08:52:07 UTC


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

  • 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, and X-EXTERNAL-ID (package also sends X-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)) and BRI-Timestamp (UTC). For DELETE /v1/briva, BRI expects Content-Type: text/plain with institutionCode=&brivaNo=&custCode= body — the package handles this and signs exactly that body.

Webhooks

QRIS Notification

  • Route: POST /bri/qris/notify (can be changed in config/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 in config/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 ❤️

  • Ko‑fi: ko-fi

License

Apache-2.0