PHP SDK
✅ Available - version 0.1.1
The official PHP library for integrating with the dpay.pl API. It automates checksum generation, IPN signature verification and API calls: payment registration, transaction details, refunds, bank list, BLIK aliases, server-to-server card payments and 1:1 payouts. Amounts are represented by the Money object (minor units as int) and API responses are mapped to typed objects.
Requirements
- PHP 7.4 or newer (tested up to PHP 8.5)
- Extensions:
curl,json,openssl - Composer
The library has no runtime dependencies beyond PHP extensions.
Installation
composer require dpayglobal/dpay-php-sdk
The package is published on Packagist and the source code is available on GitHub.
Configuration
Create a DPay\DPayClient instance using credentials from the dpay.pl Panel:
use DPay\DPayClient;
$dpay = new DPayClient([
'service' => 'service_name',
'secret_hash' => 'your_secret_hash',
]);
| Option | Type | Description |
|---|---|---|
service | string | Payment Point name from panel.dpay.pl (required) |
secret_hash | string | Secret Hash used for checksum generation (required) |
timeout | int | HTTP request timeout in seconds (default 30) |
http_client | HttpClientInterface | Custom HTTP client (tests, proxy) |
base_urls | array | API host overrides (keys: api_payments, panel, gateway) |
The client exposes services as public properties:
| Service | Scope |
|---|---|
$dpay->payments | Payment registration, transaction details |
$dpay->refunds | Refunds and refund availability checks |
$dpay->banks | Pay-by-link bank list |
$dpay->blik | BLIK OneClick and Recurring aliases |
$dpay->cards | Server-to-server card payments |
$dpay->payouts | 1:1 payout details |
Amounts - the Money object
Every amount in the SDK is a DPay\Money object that internally stores minor units (grosze) as int. The SDK takes care of the correct amount format for each endpoint (decimal amount vs minor units), so you never have to remember which endpoint expects which format.
use DPay\Money;
Money::pln(1050); // 10.50 PLN
Money::of(500, 'EUR'); // 5.00 EUR
Money::fromDecimal('10.50', 'PLN');
$money->getMinor(); // 1050 (minor units)
$money->toDecimal(); // "10.50"
Payment registration
Build the request with RegisterPaymentRequest - required fields go into create(), optional ones via with*() setters. The checksum and the transactionType field are added automatically:
use DPay\DPayClient;
use DPay\Money;
use DPay\Payment\Payer;
use DPay\Payment\RegisterPaymentRequest;
use DPay\Payment\ReturnUrls;
use DPay\Payment\TransactionType;
$dpay = new DPayClient([
'service' => 'service_name',
'secret_hash' => 'your_secret_hash',
]);
$payment = $dpay->payments->register(
RegisterPaymentRequest::create(
Money::pln(1050),
TransactionType::TRANSFERS,
new ReturnUrls(
'https://yourshop.com/success',
'https://yourshop.com/fail',
'https://yourshop.com/ipn'
)
)
->withDescription('Order #1234')
->withCustom('order-1234')
->withPayer(Payer::create()->withEmail('client@example.com'))
);
if ($payment->getRedirectUrl() !== null) {
header('Location: ' . $payment->getRedirectUrl());
exit;
}
if ($payment->isPaid()) {
// payment settled inline (e.g. BLIK Level 0)
}
Transaction types (TransactionType): TRANSFERS, DCB_GATEWAY, CARD_AUTH, MB_WAY_DIRECT, BIZUM_DIRECT, BLIK_RECURRING, CARD_RECURRING.
Key RegisterPaymentRequest setters
| Method | Description |
|---|---|
withDescription(string) | Transaction description visible to the customer |
withCustom(string) | Your own order identifier (returned in IPN) |
withPayer(Payer) | Customer e-mail and name |
withChannel(string) | Direct payment via a specific bank channel |
withCreditCard(bool) / withPaypal(bool) / withPaysafecard(bool) / withInstallment(bool) / withBlik(bool) | Enable or disable methods on the gateway |
withNoBanks(bool) | Hide the bank list |
withBlikCode(string $code, string $userAgent, string $userIp) | BLIK Level 0 payment (6-digit code) |
withBlikAlias(string $alias, string $userAgent, string $userIp) | Pay with a BLIK OneClick alias |
withRegisterBlikAlias(BlikAliasRegistration) | Register a OneClick alias during payment |
withCardRecurring(CardRecurringRegistration) | Register a card recurring mandate |
withCardRecurringAlias(string) | Charge a stored card (MIT) |
withPayout(PayoutInstruction) | 1:1 payout instruction |
withEfaktura(?InvoiceDetails) | eInvoice (KSeF) payment |
withPhoneNumber(string $phone, string $currency) | MB WAY |
Handling IPN
Verify IPN notifications with IpnVerifier::constructEvent() - an invalid signature throws SignatureVerificationException:
use DPay\Exception\SignatureVerificationException;
use DPay\Ipn\IpnEvent;
use DPay\Ipn\IpnVerifier;
try {
$event = IpnVerifier::constructEvent(
(string) file_get_contents('php://input'),
'your_secret_hash'
);
if ($event->isTransfer()) {
markOrderAsPaid($event->getId(), $event->getAmount());
}
if ($event->isCapture()) {
markOrderAsCaptured($event->getId(), $event->getAmount(), $event->getCapturePaymentId());
}
http_response_code(200);
echo IpnEvent::ACK;
} catch (SignatureVerificationException $e) {
http_response_code(400);
echo 'Invalid signature';
}
OKdpay.pl considers an IPN delivered only when the response body is exactly OK (the IpnEvent::ACK constant). The HTTP status code is ignored. Make sure your framework does not append anything to the body.
Always compare $event->getAmount() (string, e.g. "10.00") with the order amount in your database and process each transaction only once (an IPN may be delivered multiple times).
Transaction details
$transaction = $dpay->payments->details('transaction-id');
$transaction->getStatus(); // 'paid', 'created', 'processing', 'expired', 'captured'
$transaction->isPaid(); // true for paid and captured
$transaction->getValue()->toDecimal(); // "29.99"
$transaction->getRefundedAmount(); // Money
$transaction->getAvailableRefundAmount(); // Money
$transaction->isFullyRefunded(); // bool
$transaction->getRefunds(); // TransactionRefund[]
Refunds
use DPay\Money;
// full refund
$refund = $dpay->refunds->create('transaction-id');
// partial refund with a reason
$refund = $dpay->refunds->create('transaction-id', Money::pln(500), 'complaint');
$refund->isAccepted();
Before refunding you can check availability - the method returns a business result (it does not throw) even when the refund is not possible:
$availability = $dpay->refunds->checkAvailability('transaction-id', Money::pln(500));
if (!$availability->isAvailable()) {
$availability->getMessage(); // e.g. "Transaction has not been paid"
$availability->getHttpStatus(); // stable reason code, see the table
}
| HTTP code | Refusal reason |
|---|---|
400 | Amount exceeds the available refund amount |
401 | The payment channel does not support refunds |
402 | The transaction has not been paid |
406 | Insufficient balance to cover the refund |
409 | A refund request has already been submitted |
410 | The transaction has already been refunded |
411 | Charge transactions cannot be refunded |
Banks
$banks = $dpay->banks->all(); // all dpay.pl banks
$banks = $dpay->banks->forService(); // banks available for your service
$banks[0]->getId();
$banks[0]->getName();
BLIK - OneClick and Recurring aliases
$alias = $dpay->blik->alias('DPAY.UID.123456.abc12345');
$alias->isActive();
$alias->getApps();
$dpay->blik->unregisterAlias('DPAY.UID.123456.abc12345');
$status = $dpay->blik->recurringStatus('PAYID-...');
$status->getRegistration();
Alias registration and alias payments happen through payment registration (withBlikCode + withRegisterBlikAlias, payment via withBlikAlias).
Server-to-server cards
Card data is encrypted with an RSA key fetched before every payment attempt:
use DPay\Card\CardData;
use DPay\Card\CardEncryptor;
use DPay\Card\CardPaymentRequest;
use DPay\Card\DccDecision;
use DPay\Payment\DeviceInfo;
$publicKey = $dpay->cards->publicKey();
$encrypted = (new CardEncryptor())->encrypt(
new CardData('4111111111111111', '123', '12/30'),
$transactionId,
$publicKey
);
$deviceInfo = DeviceInfo::create(/* payer browser data */);
$result = $dpay->cards->payOtp($transactionId, CardPaymentRequest::create($deviceInfo)
->withEncryptedCardData($encrypted)
->withCardHolder('John', 'Smith')
->withChannelId(31));
if ($result->isSuccess()) {
// payment captured
} elseif ($result->requiresThreeDsForm()) {
$html = $result->getThreeDsFormHtml(); // render in the payer's browser
} elseif ($result->hasDccOffer()) {
$offer = $result->getDccOffer(); // present the DCC offer to the payer
// send the decision by calling payOtp again with ->withDccDecision(DccDecision::ACCEPT)
}
Also available: preAuth() (pre-authorization), capture() and cancel(), googlePay() and applePay(). A payment rejection at HTTP 200 throws CardPaymentException with an error code.
S2S cards require PCI-DSS compliance on the merchant side. See the S2S cards documentation for the full flow.
1:1 payouts
$details = $dpay->payouts->details(12345);
$details->isProcessed();
$details->getNet()->toDecimal();
$details->getReceiver();
Error handling
All SDK exceptions implement DPay\Exception\ExceptionInterface:
| Exception | When |
|---|---|
TransportException | Network error - payment status unknown, use payments->details() |
AuthenticationException | 401 - invalid checksum or secret hash |
InvalidRequestException | 400/422 - validation errors (getFieldErrors()) |
NotFoundException | 404 - resource does not exist |
RateLimitException | 429 - rate limited (getRetryAfter()) |
PaymentRejectedException | Registration rejected (e.g. wrong BLIK code; getTransactionId()) |
CardPaymentException | Card payment rejection (getErrorCode()) |
SignatureVerificationException | Invalid IPN signature |
ApiServerException | 5xx or a malformed API response |
use DPay\Exception\ApiErrorException;
use DPay\Exception\InvalidRequestException;
use DPay\Exception\TransportException;
try {
$payment = $dpay->payments->register($request);
} catch (InvalidRequestException $e) {
$e->getFieldErrors(); // array<string, string[]>
} catch (ApiErrorException $e) {
$e->getHttpStatus();
$e->getErrorCode();
} catch (TransportException $e) {
// unknown whether the request arrived - verify via payments->details()
}