enricodias / paghiper
A PHP client for the PagHiper API (Boleto, PIX, withdrawals and nota fiscal) with typed request builders, response models and proper exception handling.
Requires
- php: ^7.3 || ^8.0
- ext-json: *
- php-http/discovery: ^1.20
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
- psr/log: ^1.1 || ^2.0 || ^3.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.12
- guzzlehttp/psr7: ^2.0
- phpunit/phpunit: ^9.6
Suggests
- guzzlehttp/guzzle: Required when no PSR-18 HTTP client is provided (auto-discovered at runtime).
- monolog/monolog: A PSR-3 logger implementation to enable request/response logging.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-08-23 22:57:29 UTC
README
A complete PHP client for the PagHiper API with typed request builders, response models, and proper exception handling.
Supports: boleto, PIX, transaction listing, bank account withdrawals, and nota fiscal.
Requirements
Installation
composer require enricodias/paghiper
This package does not include an HTTP client. Install one alongside it. Guzzle is the most common choice:
composer require guzzlehttp/guzzle
Setup
use PagHiper\Client; $client = new Client('your-api-key', 'your-token');
Your apiKey and token are available in your PagHiper panel.
HTTP client
Any PSR-18 compatible client works. The client is auto-discovered at runtime via php-http/discovery, so no additional configuration is needed as long as a supported client is installed.
If you already use a PSR-18 client in your project, you can pass it directly to avoid auto-discovery:
$client = new Client('your-api-key', 'your-token', $yourHttpClient);
The PSR-17 request and stream factories are also auto-discovered. If you want to provide your own, you can also pass them directly:
$client = new Client('your-api-key', 'your-token', $yourHttpClient, null, $requestFactory, $streamFactory);
Boleto
Create
use PagHiper\Type\BankSlipType; $transaction = $client->boleto()->create() ->orderId('ORD-001') ->payer('João Silva', 'joao@example.com', '00000000191') ->daysDueDate(5) ->bankSlipType(BankSlipType::a4()) ->addItem('1', 'Produto A', 2, 1500) ->addItem('2', 'Produto B', 1, 3000) ->send(); echo $transaction->transactionId(); // HF97T5SH2ZKVKR3K echo $transaction->bankSlip()->digitableLine(); echo $transaction->bankSlip()->urlSlip();
Optional fields:
$client->boleto()->create() ->orderId('ORD-001') ->payer('João Silva', 'joao@example.com', '00000000191') ->daysDueDate(5) ->bankSlipType(BankSlipType::a4()) ->addItem('1', 'Produto A', 1, 9900) ->payerPhone('11987654321') ->payerStreet('Rua das Flores')->payerNumber('42')->payerDistrict('Centro') ->payerCity('São Paulo')->payerState('SP')->payerZipCode('01310100') ->notificationUrl('https://mysite.com/webhooks/paghiper') ->discountCents(500) ->shippingPriceCents(1000) ->latePaymentFine(2) ->perDayInterest() ->earlyPaymentDiscountsDays(3)->earlyPaymentDiscountsCents(200) ->openAfterDayDue(10) ->send();
Status
$transaction = $client->boleto()->status('HF97T5SH2ZKVKR3K'); echo $transaction->status()->value(); // "paid" $transaction->status()->isPaid(); // true $transaction->status()->isFinal(); // true echo $transaction->valueCents()->toReais(); // "R$ 60,00"
Cancel
$result = $client->boleto()->cancel('HF97T5SH2ZKVKR3K'); echo $result->responseMessage();
Webhook notifications
PagHiper POSTs to your notification_url when a transaction's status changes. Your endpoint must respond with HTTP 200. Use the responder to parse the payload, confirm receipt to PagHiper, and get the full transaction details in one call:
// In your webhook endpoint: $transaction = $client->boleto()->notifications()->confirm($_POST); echo $transaction->status()->value(); echo $transaction->payerName(); echo $transaction->valueCentsPaid()?->cents();
If you want to inspect or validate the notification before confirming (e.g. to check the apiKey matches your own):
use PagHiper\Module\Boleto\Notification\BoletoNotificationParser; $parser = new BoletoNotificationParser(); $notification = $parser->parse($_POST); if ($notification->apiKey() !== 'apk_your-api-key') { http_response_code(403); exit; } $transaction = $client->boleto()->notifications()->confirmNotification($notification);
PIX
Create
$transaction = $client->pix()->create() ->orderId('ORD-002') ->payer('Maria Souza', 'maria@example.com', '00000000272') ->daysDueDate(1) ->addItem('1', 'Produto A', 1, 9900) ->send(); echo $transaction->transactionId(); echo $transaction->pixCode()->qrCodeBase64(); // base64 PNG for display echo $transaction->pixCode()->emv(); // EMV code for copy & paste $transaction->pixCode()->toBinary(); // raw PNG bytes to save to disk
Use minutesDueDate() instead of daysDueDate() for short-lived charges:
$client->pix()->create() ->orderId('ORD-002') ->payer('Maria Souza', 'maria@example.com', '00000000272') ->minutesDueDate(30) ->addItem('1', 'Produto A', 1, 9900) ->send();
Status
Some fields are not present in the response after a Pix is paid.
$transaction = $client->pix()->status('BPV661O7AVLORCN5'); $transaction->status()->isPaid(); // true $transaction->pixCode()->emv(); // null
Cancel
$result = $client->pix()->cancel('BPV661O7AVLORCN5');
Refund
PIX refunds are subject to PagHiper's business rules: the transaction must be paid, the refund must be requested within 60 days of the payment date, and your account must have sufficient balance. If any condition is not met, a RequestRejectedException is thrown with the reason.
$result = $client->pix()->refund('BPV661O7AVLORCN5'); echo $result->responseMessage();
Webhook notifications
// In your webhook endpoint: $transaction = $client->pix()->notifications()->confirm($_POST); echo $transaction->status()->value(); echo $transaction->payerName();
Transaction listing
Lists both boleto and PIX transactions. All filters are optional.
use PagHiper\Type\DateFilterFieldType; use PagHiper\Type\TransactionStatusType; use PagHiper\Type\ValueCentsFilterOperatorType; $page = $client->transactions()->list() ->status(TransactionStatusType::paid()) ->initialDate('2024-01-01') ->finalDate('2024-01-31') ->filterDate(DateFilterFieldType::paid()) ->limit(50) ->send(); echo $page->currentPage(); // 1 echo $page->totalPages(); // 4 $page->hasMorePages(); // true foreach ($page->items() as $item) { echo $item->transactionId(); echo $item->orderId(); echo $item->status()->value(); echo $item->valueCents()->toReais(); if ($item->isBoleto()) { echo $item->bankSlip()->digitableLine(); } if ($item->isPix()) { echo $item->pixCode()->qrCodeBase64(); } }
To page through all results:
$page = 1; do { $result = $client->transactions()->list() ->status(TransactionStatusType::paid()) ->limit(100) ->page($page) ->send(); foreach ($result->items() as $item) { // process $item } $page++; } while ($result->hasMorePages());
When a query matches no transactions, an empty page is returned rather than throwing an exception. $page->isEmpty() will be true and $page->items() will be [].
Available filters: status(), initialDate(), finalDate(), filterDate(), dueDate(), payerCpfCnpj(), orderId(), valueCents(), limit() (1–100), page() (1–999).
filterDate() is required by PagHiper whenever initialDate() or finalDate() is set. Valid values: DateFilterFieldType::created(), DateFilterFieldType::paid(), DateFilterFieldType::due().
Bank accounts and withdrawals
Withdrawals always transfer your entire available balance to the selected account. There is no partial withdrawal option.
// List registered bank accounts eligible for withdrawal: $accounts = $client->bankAccounts()->list(); foreach ($accounts as $account) { echo $account->bankAccountId(); // integer, use this to withdraw echo $account->bankName(); // "Itaú" echo $account->bankCode(); // "341" echo $account->accountType(); // "Corrente" } // Withdraw full available balance to a specific account: $result = $client->bankAccounts()->withdraw($accounts[0]->bankAccountId()); echo $result->responseMessage();
Nota fiscal
Lists the last 24 fee invoices PagHiper has issued to your account. These are PagHiper's billing documents for the fees they charge you, not customer-facing invoices for your own orders.
$invoices = $client->notaFiscal()->list(); foreach ($invoices as $invoice) { echo $invoice->invoiceId(); echo $invoice->invoiceDate(); // "Y-m-d" echo $invoice->invoiceValueCents()->toReais(); // "R$ 1,23" echo $invoice->invoiceUrl(); // link to the NF-e document echo $invoice->invoiceType(); // "tarifa" echo $invoice->invoiceDescription(); // fee calculation breakdown }
For invoices older than the last 24, contact PagHiper support.
Exception handling
All exceptions extend PagHiper\Exception\PagHiperException, so you can catch broadly or granularly:
use PagHiper\Exception\HttpException; use PagHiper\Exception\PagHiperException; use PagHiper\Exception\RateLimitedException; use PagHiper\Exception\RequestRejectedException; use PagHiper\Exception\ValidationException; try { $transaction = $client->boleto()->create() ->orderId('ORD-001') ->payer('João Silva', 'joao@example.com', '00000000191') ->daysDueDate(5) ->bankSlipType(BankSlipType::a4()) ->addItem('1', 'Produto A', 1, 9900) ->send(); } catch (ValidationException $e) { // A required field was missing or invalid before any HTTP call was made. echo $e->getMessage(); } catch (RateLimitedException $e) { // HTTP 429, back off and retry after a delay. } catch (RequestRejectedException $e) { // PagHiper accepted the request but rejected it for a business reason. echo $e->getResponseMessage(); // e.g. "token não informado ou invalido" // Check for a specific known reason: if ($e->messageContains('prazo de reembolso')) { // handle expired refund window } } catch (HttpException $e) { // Network error, non-2xx HTTP status, or malformed response body. echo $e->getStatusCode(); echo $e->getResponseBody(); } catch (PagHiperException $e) { // Catch-all for anything else from this package. }
Exception reference
| Exception | When thrown |
|---|---|
ValidationException |
Required field missing or invalid, before any HTTP call |
RequestRejectedException |
PagHiper returns "result": "reject" (HTTP 200) |
RateLimitedException |
HTTP 429 too many requests |
HttpException |
Network error, non-2xx status, or malformed/empty response body |
UnexpectedResponseException |
HTTP 2xx + "result": "success" but a required field is missing from the response |
JSON serialization
All transaction response classes implement JsonSerializable, so they can be passed directly to json_encode(). All monetary values are serialized as integer cents.
$transaction = $client->boleto()->status('HF97T5SH2ZKVKR3K'); $json = \json_encode($transaction); // or, to get the array directly: $data = $transaction->jsonSerialize();
The same applies to PixTransaction and TransactionListItem. This is useful for storing transaction snapshots for audit or logging purposes.
Money values
All monetary values are represented as Money objects, which wrap the raw cents integer PagHiper returns:
$money = $transaction->valueCents(); $money->cents(); // 9900 (int) $money->toFloat(); // 99.0 (float) $money->toReais(); // "R$ 99,00" (formatted string)
Logging
Pass any PSR-3 compatible logger as the fourth argument. Monolog is a common choice:
composer require monolog/monolog
use Monolog\Logger; use Monolog\Handler\StreamHandler; $logger = new Logger('paghiper'); $logger->pushHandler(new StreamHandler('/var/log/paghiper.log')); $client = new Client('apk_your-api-key', 'your-token', null, $logger);
When a logger is provided, the following events are recorded:
| Event | Level | When |
|---|---|---|
PagHiper request |
debug |
Before each outgoing request (credentials redacted) |
PagHiper response |
debug |
After each successful response |
PagHiper rejected request |
warning |
PagHiper returns "result": "reject" |
PagHiper rate limit exceeded |
warning |
HTTP 429 |
PagHiper unexpected HTTP status |
error |
Non-2xx HTTP status |
PagHiper network error |
error |
Connection failure |
PagHiper transport error |
error |
Other HTTP client error |