elgibor-solution / laravel-payment-bca
Laravel package for BCA OpenAPI Virtual Account (Inquiry, Payment Flag, Status) with caching and signatures.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/elgibor-solution/laravel-payment-bca
Requires
- php: ^8.3
- illuminate/cache: ^11 || ^12
- illuminate/contracts: ^11 || ^12
- illuminate/http: ^11 || ^12
- illuminate/support: ^11 || ^12
Requires (Dev)
- orchestra/testbench: ^9 || ^10 || ^11
- phpunit/phpunit: ^11 || ^12
README
Production-ready Laravel 11 / PHP 8.3 package for BCA OpenAPI Virtual Account (bill presentment + payment flag + optional payment status). Namespace: ESolution\BCAPayment.
Installation
composer require elgibor-solution/laravel-payment-bca
php artisan vendor:publish --provider="ESolution\\BCAPayment\\BcaServiceProvider" --tag=bca-config
Configuration
Config lives at config/bca.php (aliased to config/bca-openapi.php). Keys:
base_url(prod) /base_url_staging(sandbox) — defaults:https://api.klikbca.com/https://devapi.klikbca.comclient_id,client_secret,private_key(PEM)channel_id,partner_id, optionalorigintimeout,connect_timeout,retry_times,retry_sleep_mstoken_cache_ttl_seconds(default 900),retry_on_codes(default5002600for payment status retry)debugtoggles staging base URL
Environment example:
BCA_BASE_URL=https://api.klikbca.com BCA_BASE_URL_STAGING=https://devapi.klikbca.com BCA_CLIENT_ID=your_client_id BCA_CLIENT_SECRET=your_symmetric_secret BCA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----..." BCA_CHANNEL_ID=95231 BCA_PARTNER_ID=12345 BCA_ORIGIN=https://merchant.example BCA_DEBUG=false
Runtime config
- Override defaults globally:
Bca::config([...])orBca::setDefaultConfig([...]). - Per-call overrides:
Bca::withConfig([...])orBca::withBaseUrl($url)returning a client instance. - Debug staging per call:
Bca::withConfig(['debug' => true]).
Usage
use ESolution\BCAPayment\Facades\Bca; // Access token (cached per client_id + base URL) $token = Bca::token(); // Inquiry (Bill Presentment) $inquiry = Bca::withConfig(['debug' => true])->inquiry([ 'partnerServiceId' => '123', 'customerNo' => '987654', 'virtualAccountNo' => '123987654', 'trxDateInit' => now()->toIso8601String(), 'channelCode' => '6011', 'amount' => ['value' => '10000', 'currency' => 'IDR'], 'inquiryRequestId' => 'REQ-1', ]); // Payment Flag $payment = Bca::payment([ 'partnerServiceId' => '123', 'customerNo' => '987654', 'virtualAccountNo' => '123987654', 'virtualAccountName' => 'John Doe', 'paymentRequestId' => 'PAY-1', // should match inquiryRequestId when retrying 'channelCode' => '6011', 'paidAmount' => ['value' => '10000', 'currency' => 'IDR'], 'totalAmount' => ['value' => '10000', 'currency' => 'IDR'], 'trxDateTime' => now()->toIso8601String(), 'flagAdvise' => 'N', // N=new, Y=retry ]); // Payment Status (optional) $status = Bca::paymentStatus([ 'partnerServiceId' => '123', 'customerNo' => '987654', 'virtualAccountNo' => '123987654', 'inquiryRequestId' => 'REQ-1', 'paymentRequestId' => 'PAY-1', ]);
All transaction calls accept an optional second argument to override headers (e.g. provide your own X-EXTERNAL-ID). The package auto-generates a numeric X-EXTERNAL-ID (<=36 chars), and always sends CHANNEL-ID, X-PARTNER-ID, and optional ORIGIN.
Endpoints (fixed)
- Token:
POST /openapi/v1.0/access-token/b2b - Inquiry:
POST /openapi/v1.0/transfer-va/inquiry - Payment:
POST /openapi/v1.0/transfer-va/payment - Payment Status:
POST /openapi/v1.0/transfer-va/status
Signatures (overview)
- Token (asymmetric RSA SHA256): String to sign =
client_id|X-TIMESTAMP. Headers:X-TIMESTAMP,X-CLIENT-KEY,X-SIGNATURE. - Transactions (HMAC SHA-512):
- String to sign =
HTTPMethod:RelativeUrl:AccessToken:lowercase(sha256(minified_body)):Timestamp RelativeUrlstarts with/and includes sorted query params.- Body is JSON-minified; empty body => empty string hashed.
- Header set includes
Authorization: Bearer {token},X-TIMESTAMP,X-SIGNATURE,X-EXTERNAL-ID,CHANNEL-ID,X-PARTNER-ID, optionalORIGIN.
- String to sign =
Notes
- Bill Presentment (Inquiry) and Payment Flag must be used together; Payment Status is optional and marks code
5002600as retryable by default. - Tokens are cached using
client_id+ base URL hash with a safety buffer to avoid near-expiry calls. - HTTP client uses Laravel
Httpwith configurable timeouts and retries; secrets are never logged.
Testing
Run the feature tests with Orchestra Testbench:
composer test