pgpay / pakailink-sdk
PakaiLink Payment Gateway SDK for Laravel - SNAP compliant payment integration
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/pgpay/pakailink-sdk
Requires
- php: ^8.3
- illuminate/cache: ^12.0
- illuminate/http: ^12.0
- illuminate/log: ^12.0
- illuminate/support: ^12.0
Requires (Dev)
- pestphp/pest: ^3.0
- phpunit/phpunit: ^11.0
This package is not auto-updated.
Last update: 2025-12-08 14:35:18 UTC
README
A comprehensive Laravel package for integrating with PakaiLink Payment Gateway. This SDK provides SNAP-compliant payment processing for Indonesian payment methods including Virtual Accounts, QRIS, E-Wallets, and Bank Transfers.
Features
- ✅ SNAP Compliant - Follows Standard National API Payment specification
- ✅ Multiple Payment Methods - VA, QRIS, E-Wallet, Retail, Bank Transfer
- ✅ Dual Signature - RSA-SHA256 (asymmetric) and HMAC-SHA512 (symmetric)
- ✅ Token Management - Automatic B2B token generation with caching
- ✅ Retry Logic - Automatic retry for network failures
- ✅ Comprehensive Logging - Detailed logging for debugging
- ✅ Type Safe - Full PHP 8.4 type hints and enums
- ✅ Exception Handling - Custom exceptions for different error types
Requirements
- PHP 8.4+
- Laravel 12.0+
- ext-openssl (for RSA signatures)
- ext-json
Installation
Step 1: Add Repository to composer.json
Add the local repository to your main project's composer.json:
{
"repositories": [
{
"type": "path",
"url": "packages/pgpay/pakailink-sdk"
}
]
}
Step 2: Install Package
composer require pgpay/pakailink-sdk
Step 3: Publish Configuration
php artisan vendor:publish --tag=pakailink-config
Step 4: Configure Environment
Add to your .env file:
PAKAILINK_ENV=sandbox PAKAILINK_BASE_URL=https://rising-dev.pakailink.id PAKAILINK_CLIENT_ID=your_client_id PAKAILINK_CLIENT_SECRET=your_client_secret PAKAILINK_PARTNER_ID=your_partner_id PAKAILINK_MERCHANT_ID=your_merchant_id PAKAILINK_CHANNEL_ID=your_channel_id PAKAILINK_PRIVATE_KEY_PATH=storage/keys/pakailink_private.pem PAKAILINK_PUBLIC_KEY_PATH=storage/keys/pakailink_public.pem
Step 5: Generate RSA Keys
# Generate private key openssl genrsa -out storage/keys/pakailink_private.pem 2048 # Generate public key openssl rsa -in storage/keys/pakailink_private.pem -pubout -out storage/keys/pakailink_public.pem # Set permissions chmod 400 storage/keys/pakailink_private.pem chmod 444 storage/keys/pakailink_public.pem
Usage
Basic Service Access
use Pgpay\PakaiLink\Services\PakaiLinkService; // Get balance $service = app(PakaiLinkService::class); $balance = $service->getBalance(); // Get balance history $history = $service->getBalanceHistory( startDate: '2025-01-01', endDate: '2025-01-31', page: 1, limit: 100 );
Virtual Account Service
use Pgpay\PakaiLink\Services\PakaiLinkVirtualAccountService; use Pgpay\PakaiLink\Data\CreateVirtualAccountData; $service = app(PakaiLinkVirtualAccountService::class); // Create Virtual Account $data = CreateVirtualAccountData::from([ 'amount' => 100000, 'customer_name' => 'John Doe', 'bank_code' => '002', // BRI ]); $response = $service->create($data); // Returns: ['virtualAccountData' => ['virtualAccountNo' => '8808...', ...]] // Inquiry VA Status $status = $service->inquiryStatus($vaNumber, $inquiryRequestId); // Update VA $service->update($vaNumber, $trxId, 'U', 150000); // Delete VA $service->delete($vaNumber, $trxId);
QRIS Service
use Pgpay\PakaiLink\Services\PakaiLinkQrisService; use Pgpay\PakaiLink\Data\GenerateQrisData; $service = app(PakaiLinkQrisService::class); // Generate QRIS QR Code $data = GenerateQrisData::from([ 'amount' => 50000, ]); $response = $service->generateQris($data); // Returns: ['qrContent' => '...', 'nmid' => '...', ...] // Inquiry QRIS Status $status = $service->inquiryStatus($partnerReferenceNo); // Refund QRIS Transaction $refund = $service->refund($partnerReferenceNo, 50000, 'Customer request');
Bank Transfer Service
use Pgpay\PakaiLink\Services\PakaiLinkTransferService; use Pgpay\PakaiLink\Data\TransferToBankData; $service = app(PakaiLinkTransferService::class); // Inquiry bank account (verify before transfer) $data = TransferToBankData::from([ 'beneficiary_bank_code' => '008', // Mandiri 'beneficiary_account_no' => '1234567890', 'beneficiary_account_name' => 'Jane Smith', 'amount' => 100000, ]); $inquiry = $service->inquiryTransfer($data); // Execute transfer $transfer = $service->transferToBank($data); // Check transfer status $status = $service->inquiryStatus($partnerReferenceNo); // Transfer to VA $vaTransfer = $service->transferToVA( beneficiaryVirtualAccountNo: '8808123456789012', beneficiaryBankCode: '002', amount: 100000 );
E-money/E-wallet Service
use Pgpay\PakaiLink\Services\PakaiLinkEmoneyService; use Pgpay\PakaiLink\Data\CreateEmoneyPaymentData; $service = app(PakaiLinkEmoneyService::class); // Create E-wallet Payment $data = CreateEmoneyPaymentData::from([ 'channel_id' => 'PAYGOPAY', // GoPay 'amount' => 75000, 'customer_name' => 'Bob Wilson', 'customer_phone' => '081234567890', ]); $response = $service->createPayment($data); // Returns: ['webRedirectUrl' => '...', 'referenceNo' => '...'] // Inquiry E-money Status $status = $service->inquiryStatus($partnerReferenceNo); // Refund E-money Transaction $refund = $service->refund($partnerReferenceNo, 75000);
HTTP Client
use Pgpay\PakaiLink\Services\PakaiLinkHttpClient; $client = app(PakaiLinkHttpClient::class); // Make authenticated POST request $response = $client->post('/api/v1.0/endpoint', [ 'key' => 'value', ]); // Make GET request $response = $client->get('/api/v1.0/endpoint', [ 'param' => 'value', ]);
Signature Service
use Pgpay\PakaiLink\Services\PakaiLinkSignatureService; $service = app(PakaiLinkSignatureService::class); // Generate asymmetric signature (for B2B token) $signature = $service->generateAsymmetricSignature( clientId: 'your_client_id', timestamp: now()->toIso8601String() ); // Generate symmetric signature (for API requests) $signature = $service->generateSymmetricSignature( httpMethod: 'POST', endpointUrl: '/api/v1.0/endpoint', accessToken: 'your_token', requestBody: '{"key":"value"}', timestamp: now()->toIso8601String() ); // Validate callback signature $isValid = $service->validateCallbackSignature( receivedSignature: $request->header('X-SIGNATURE'), requestBody: $request->getContent(), timestamp: $request->header('X-TIMESTAMP') );
Authentication Service
use Pgpay\PakaiLink\Services\PakaiLinkAuthService; $service = app(PakaiLinkAuthService::class); // Get B2B access token (cached) $token = $service->getB2BAccessToken(); // Refresh token $newToken = $service->refreshToken(); // Check if token is expired if ($service->isTokenExpired()) { // Token needs refresh } // Clear cached token $service->clearToken();
Payment Methods Supported
| Method | Code | Description |
|---|---|---|
| Virtual Account | 002, 008, 009, etc. |
13 banks supported |
| QRIS | QRIS |
Dynamic & Static QR payments |
| E-Wallet | PAYGOPAY, PAYOVO, etc. |
5 providers (GoPay, OVO, DANA, ShopeePay, LinkAja) |
| Bank Transfer | 002-950 |
117 banks supported |
| Retail Payment | ALFAMART, INDOMARET |
2 stores |
Enums
Transaction Types
use Pgpay\PakaiLink\Enums\PakaiLinkTransactionType; PakaiLinkTransactionType::VIRTUAL_ACCOUNT PakaiLinkTransactionType::QRIS PakaiLinkTransactionType::EWALLET PakaiLinkTransactionType::RETAIL PakaiLinkTransactionType::TRANSFER_BANK PakaiLinkTransactionType::TRANSFER_VA PakaiLinkTransactionType::TOP_UP PakaiLinkTransactionType::BALANCE_INQUIRY
Transaction Status
use Pgpay\PakaiLink\Enums\PakaiLinkTransactionStatus; PakaiLinkTransactionStatus::PENDING PakaiLinkTransactionStatus::PROCESSING PakaiLinkTransactionStatus::SUCCESS PakaiLinkTransactionStatus::FAILED PakaiLinkTransactionStatus::EXPIRED PakaiLinkTransactionStatus::CANCELLED
Bank Codes
use Pgpay\PakaiLink\Enums\PakaiLinkBankCode; PakaiLinkBankCode::BRI->value // '002' PakaiLinkBankCode::MANDIRI->value // '008' PakaiLinkBankCode::BNI->value // '009' PakaiLinkBankCode::BCA->value // '014' // Get label PakaiLinkBankCode::BRI->getLabel() // 'Bank BRI' // Check type PakaiLinkBankCode::BSI->isSyariah() // true PakaiLinkBankCode::JAGO->isDigital() // true
Exceptions
All exceptions extend Pgpay\PakaiLink\Exceptions\PakaiLinkException:
use Pgpay\PakaiLink\Exceptions\PakaiLinkException; use Pgpay\PakaiLink\Exceptions\PakaiLinkAuthenticationException; use Pgpay\PakaiLink\Exceptions\PakaiLinkSignatureException; use Pgpay\PakaiLink\Exceptions\PakaiLinkValidationException; use Pgpay\PakaiLink\Exceptions\PakaiLinkTransactionException; try { $service->getBalance(); } catch (PakaiLinkAuthenticationException $e) { // Handle auth errors } catch (PakaiLinkException $e) { // Handle other PakaiLink errors $context = $e->getContext(); }
Configuration
The package uses config/pakailink.php for configuration. Key settings:
return [ 'env' => env('PAKAILINK_ENV', 'sandbox'), 'base_url' => env('PAKAILINK_BASE_URL'), 'credentials' => [ 'client_id' => env('PAKAILINK_CLIENT_ID'), 'client_secret' => env('PAKAILINK_CLIENT_SECRET'), 'partner_id' => env('PAKAILINK_PARTNER_ID'), 'merchant_id' => env('PAKAILINK_MERCHANT_ID'), 'channel_id' => env('PAKAILINK_CHANNEL_ID'), ], 'keys' => [ 'private_key_path' => base_path(env('PAKAILINK_PRIVATE_KEY_PATH')), 'public_key_path' => base_path(env('PAKAILINK_PUBLIC_KEY_PATH')), ], 'timeout' => env('PAKAILINK_TIMEOUT', 30), 'retry_times' => env('PAKAILINK_RETRY_TIMES', 3), 'retry_delay' => env('PAKAILINK_RETRY_DELAY', 1000), 'cache' => [ 'token_ttl' => 840, // 14 minutes 'token_key' => 'pakailink:access_token', ], ];
Logging
The package logs all activities to the pakailink channel:
// config/logging.php 'pakailink' => [ 'driver' => 'daily', 'path' => storage_path('logs/payments/pakailink.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 90, ],
Architecture
Pgpay\PakaiLink\Services\PakaiLinkService (Main)
├── PakaiLinkHttpClient (Authenticated requests)
│ ├── PakaiLinkAuthService (Token management)
│ │ └── PakaiLinkSignatureService (Asymmetric RSA)
│ └── PakaiLinkSignatureService (Symmetric HMAC)
└── Balance Operations
Security
RSA Keys
- Private key used for B2B token request signatures (RSA-SHA256)
- Public key uploaded to PakaiLink dashboard
- Keys stored in
storage/keys/with restrictive permissions - Keys excluded from version control
Signatures
- Asymmetric (RSA-SHA256): For B2B access token requests
- Symmetric (HMAC-SHA512): For all other API requests
- Callback Validation: HMAC-SHA512 with timing-safe comparison
Token Caching
- Access tokens cached for 14 minutes (expire in 15 minutes)
- Automatic refresh on expiry
- Cache key:
pakailink:access_token
Testing
# Test service resolution php artisan tinker >>> $service = app(\Pgpay\PakaiLink\Services\PakaiLinkService::class); >>> $balance = $service->getBalance(); # Test signature generation >>> $sig = app(\Pgpay\PakaiLink\Services\PakaiLinkSignatureService::class); >>> $signature = $sig->generateSymmetricSignature(...);
Error Handling
use Pgpay\PakaiLink\Exceptions\PakaiLinkException; use Illuminate\Support\Facades\Log; try { $result = $client->post('/api/v1.0/endpoint', $data); } catch (PakaiLinkException $e) { Log::error('PakaiLink error', [ 'message' => $e->getMessage(), 'code' => $e->getCode(), 'context' => $e->getContext(), ]); // Handle error }
Package Structure
packages/pgpay/pakailink-sdk/
├── src/
│ ├── Services/
│ │ ├── PakaiLinkSignatureService.php
│ │ ├── PakaiLinkAuthService.php
│ │ ├── PakaiLinkHttpClient.php
│ │ └── PakaiLinkService.php
│ ├── Exceptions/
│ │ ├── PakaiLinkException.php
│ │ ├── PakaiLinkAuthenticationException.php
│ │ ├── PakaiLinkSignatureException.php
│ │ ├── PakaiLinkValidationException.php
│ │ └── PakaiLinkTransactionException.php
│ ├── Enums/
│ │ ├── PakaiLinkTransactionType.php
│ │ ├── PakaiLinkTransactionStatus.php
│ │ └── PakaiLinkBankCode.php
│ ├── Contracts/
│ ├── Facades/
│ └── PakaiLinkServiceProvider.php
├── config/
│ └── pakailink.php
├── composer.json
└── README.md
Extending the Package
Add Payment Method Service
Create a new service in your application:
namespace App\Services\PakaiLink; use Pgpay\PakaiLink\Services\PakaiLinkHttpClient; class CustomPaymentService { public function __construct( protected PakaiLinkHttpClient $client ) {} public function processPayment(array $data): array { return $this->client->post('/api/v1.0/custom-endpoint', $data); } }
Register Custom Service
In your application's service provider:
$this->app->singleton(CustomPaymentService::class, function ($app) { return new CustomPaymentService( client: $app->make(\Pgpay\PakaiLink\Services\PakaiLinkHttpClient::class) ); });
Support
- PakaiLink Documentation: https://pakaidonk.id/en/dokumentasi-api/
- Issues: Report issues in your project repository
- Questions: Contact your development team
License
MIT License
Credits
Developed by BigPG Team for Laravel applications integrating with PakaiLink Payment Gateway.
Version: 1.0.0
Status: Production Ready
Last Updated: 2025-11-18