Skip to main content

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',
]);
OptionTypeDescription
servicestringPayment Point name from panel.dpay.pl (required)
secret_hashstringSecret Hash used for checksum generation (required)
timeoutintHTTP request timeout in seconds (default 30)
http_clientHttpClientInterfaceCustom HTTP client (tests, proxy)
base_urlsarrayAPI host overrides (keys: api_payments, panel, gateway)

The client exposes services as public properties:

ServiceScope
$dpay->paymentsPayment registration, transaction details
$dpay->refundsRefunds and refund availability checks
$dpay->banksPay-by-link bank list
$dpay->blikBLIK OneClick and Recurring aliases
$dpay->cardsServer-to-server card payments
$dpay->payouts1: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

MethodDescription
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';
}
The response body must be exactly OK

dpay.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.

info

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 codeRefusal reason
400Amount exceeds the available refund amount
401The payment channel does not support refunds
402The transaction has not been paid
406Insufficient balance to cover the refund
409A refund request has already been submitted
410The transaction has already been refunded
411Charge 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.

info

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:

ExceptionWhen
TransportExceptionNetwork error - payment status unknown, use payments->details()
AuthenticationException401 - invalid checksum or secret hash
InvalidRequestException400/422 - validation errors (getFieldErrors())
NotFoundException404 - resource does not exist
RateLimitException429 - rate limited (getRetryAfter())
PaymentRejectedExceptionRegistration rejected (e.g. wrong BLIK code; getTransactionId())
CardPaymentExceptionCard payment rejection (getErrorCode())
SignatureVerificationExceptionInvalid IPN signature
ApiServerException5xx 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()
}

More information