Search by

binderbyte / binderpay-php

Official BinderPay SNAP API SDK for PHP (Virtual Account & QRIS)

Maintainers

Package info

github.com/binderdigitalindonesia/binderpay-php

Homepage

pkg:composer/binderbyte/binderpay-php

Transparency log

Statistics

Installs: 9

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-08-15 09:57 UTC

This package is auto-updated.

Last update: 2026-09-02 03:00:17 UTC


README

Packagist Version Stable Version Total Downloads License

Official BinderPay SNAP API SDK for PHP — Virtual Account & QRIS.

Implements RSA-SHA256 (SNAP Bank Indonesia) signature automatically on every request and provides callback verification helpers.

Installation

composer require binderbyte/binderpay-php

Requires PHP >= 8.1.0, ext-json, ext-openssl, ext-curl (for HTTP).

Configuration

use BinderPay\BinderPay;

$client = new BinderPay([
    'partnerId' => '170041',                        // X-PARTNER-ID
    'privateKey' => file_get_contents('private.pem'), // RSA private key (PEM)
    'channelId' => 'BCA',                           // CHANNEL-ID
    'isProduction' => false,                        // default sandbox; true = production
]);
Option Type Default Description
partnerId string Registered partner ID (required)
privateKey string RSA private key PEM (required)
channelId string Bank channel code, e.g. BCA (required)
isProduction bool false false = sandbox, true = production
baseUrl string Override base URL

Default base URLs: Sandbox https://api-sandbox.binderpay.id, Production https://api.binderpay.id.

Virtual Account

// Create VA (Service 27)
$client->virtualAccount->create([
    'customerNo' => '000003212',
    'virtualAccountName' => 'Chus Pandi',
    'trxId' => 'INV-000000023212',
    'totalAmount' => ['value' => '25000.00', 'currency' => 'IDR'],
    'virtualAccountTrxType' => 'C',                 // C | O | R
    'expiredDate' => '2023-09-05T19:30:14+07:00',
    'additionalInfo' => ['channel' => 'CIMB'],
]);

// Inquiry active VA (Service 30)
$client->virtualAccount->inquiry([
    'trxId' => 'INV-000000023212',
    'additionalInfo' => ['contractId' => 'ci302a21c9'],
]);

// VA payment status (Service 26)
$client->virtualAccount->status([
    'virtualAccountNo' => '2269141693898987',
    'trxId' => 'INV-000000023212',
    'additionalInfo' => ['contractId' => 'ci302a21c9', 'channel' => 'BCA'],
]);

// Delete VA (Service 31)
$client->virtualAccount->delete([
    'trxId' => 'INV-000000023212',
    'virtualAccountNo' => '2269141693898987',
    'additionalInfo' => ['channel' => 'BCA', 'contractId' => 'ci302a21c9'],
]);

VA types: C (one-off), O (open recurring), R (close recurring).

Channels: BRI, BNI, MANDIRI, MANDIRIPC, PERMATA, BSI, MUAMALAT, BCA, CIMB, SINARMAS, BNC, MAYBANK.

QRIS

// Generate QRIS (Service 47)
$client->qris->generate([
    'partnerReferenceNo' => 'INV-000000023212',
    'amount' => ['value' => '45000.00', 'currency' => 'IDR'],
    'validityPeriod' => '2024-01-11T17:00:00+07:00', // required if isStatic = false
    'additionalInfo' => ['isStatic' => false],
]);

// Query status (Service 51)
$client->qris->query([
    'originalPartnerReferenceNo' => 'INV-000000023212',
    'serviceCode' => '47',
    'additionalInfo' => ['contractId' => 'ci302a21c9'],
]);

// Cancel (Service 77)
$client->qris->cancel([
    'originalPartnerReferenceNo' => 'INV-000000023212',
    'reason' => 'cancel order',
    'additionalInfo' => ['contractId' => 'ci302a21c9'],
]);

Webhook / Callback Validation

Callbacks from BinderPay are sent with the X-TIMESTAMP, X-SIGNATURE, and X-PARTNER-ID headers. Verify the signature with the BinderPay public key (download from https://binderpay.id/docs/binderpay-public.pem, not your private key).

There are three separate path concepts:

  • Merchant callback route: your application-owned route, for example /api/binderpay/callback.
  • Signed callback path: the exact path BinderPay includes in the callback string-to-sign: /v1.0/transfer-va/payment for VA or /v1.0/qr/qr-mpm-notify for QRIS.
  • Outbound API endpoint: an SDK request path such as /v1.0/transfer-va/create-va.
use BinderPay\Webhook;

// Laravel example
Route::post('/api/binderpay/callback', function (Request $request) {
    $rawBody = $request->getContent(); // exactly as received; do not re-serialize

    // Load BinderPay public key (PEM format) from file or environment variable
    $binderpayPublicKey = file_get_contents(storage_path('keys/binderpay-public.pem'));

    $valid = Webhook::verifyCallbackSignature(
        $request->headers->all(),
        $rawBody,
        $binderpayPublicKey,
    );
    if (! $valid) {
        return response()->json(['message' => 'Cannot verify signature'], 401);
    }

    $callback = Webhook::parseCallback($request->all()); // auto-detects VA or QRIS
    // ... process payment idempotently ...

    // Return the matching SNAP success code for the detected callback type.
    return response()->json(Webhook::successResponse($callback['type']));
});

parseCallback() inspects the payload and validates the required fields of the detected type — a VA callback (has trxId) or a QRIS callback (has originalReferenceNo). It throws ValidationException with the detected type in the message when a required field is missing, and rejects payloads that match neither type. Use Webhook::successResponse($callback['type']) to acknowledge — it returns 2002500 for va and 2005200 for qris.

For a standard VA or QRIS callback, use the same Webhook::verifyCallbackSignature(...) method. It automatically checks /v1.0/transfer-va/payment and /v1.0/qr/qr-mpm-notify, and accepts Laravel/Symfony header arrays. For non-standard integrations, use Webhook::verifyCallbackSignatureForPath(...) with an explicit path. You can also use Webhook::validatePublicKey($publicKey) to validate public key presence and RSA format upfront.

Important:

  • $rawBody must be exactly as received by the server; do not decode and re-serialize it before verification.
  • $binderpayPublicKey is strictly validated; passing an empty/missing key or invalid PEM throws a ValidationException.
  • The merchant route remains application-owned; the unified helper selects the BinderPay signed callback path automatically.
  • Replay prevention and idempotent transaction handling remain the merchant application's responsibility.
  • Return HTTP 200 with the appropriate BinderPay response code after successful processing.

Error Handling

use BinderPay\Exceptions\BinderPayException;
use BinderPay\Exceptions\ValidationException;

try {
    $client->virtualAccount->create([...]);
} catch (ValidationException $e) {
    // invalid input on the SDK side
} catch (BinderPayException $e) {
    if ($e->responseCode === '4002701') {
        // field format mismatch
    }
    // $e->status (HTTP status), $e->responseBody (raw body)
}

Testing

composer test   # Pest

License

This project is licensed under the MIT License - see the LICENSE file for details.