Przejdź do głównej zawartości

Generowanie checksum (sum kontrolnych)

Checksum (suma kontrolna) to mechanizm zabezpieczający integralność danych przesyłanych między Twoim serwerem a dpay.pl. Kazde zapytanie do API wymaga prawidlowego checksum wygenerowanego z użyciem Twojego Secret Hash.

Zasada działania

Checksum to skrot SHA-256 wygenerowany z połączenia określonych pól rozdzielonych separatorem. dpay.pl weryfikuje checksum po swójej stronie - jeśli nie zgadza się z oczekiwanym, zapytanie zostaje odrzućone.

uwaga

Secret Hash to klucz prywatny. Nigdy nie ujawniaj go publicznie, nie umieszczaj w kodzie frontendowym, nie commituj do repozytorium kodu. Przechowuj go wyłącznie w zmiennych środowiskowych serwera.

Formaty checksum dla roznych endpointow

Rejestracja płatności (payments/register)

sha256({service}|{SecretHash}|{value}|{url_success}|{url_fail}|{url_ipn})
ElementOpis
serviceNazwa serwisu z panelu
SecretHashTwoj klucz prywatny
valueKwota płatności (np. "29.99")
url_successURL powodzenia
url_failURL niepowodzenia
url_ipnURL powiadomień IPN

Separator: | (pipe)

Zwrot pełny (pbl/refund)

sha256({service}|{transaction_id}|{secret_hash})
ElementOpis
serviceNazwa serwisu
transaction_idIdentyfikator transakcji do zwrotu
secret_hashTwoj klucz prywatny

Separator: | (pipe)

Zwrot częściowy (pbl/refund z parametrem value)

sha256({service}|{transaction_id}|{value}|{secret_hash})
ElementOpis
serviceNazwa serwisu
transaction_idIdentyfikator transakcji do zwrotu
valueKwota zwrotu częściowego (np. "15.00")
secret_hashTwoj klucz prywatny

Separator: | (pipe)

ostrzeżenie

Gdy przekazujesz parametr value w zapytaniu o zwrot, musisz uwzględnić go w checksum. Pominięcie value w checksum spowoduje błąd Invalid checksum.

Direct Carrier Billing (DCB)

sha256({GUID}|{SecretHash}|{value}|{url_success}|{url_fail}|{url_ipn})
ElementOpis
GUIDIdentyfikator GUID Punktu Płatności
SecretHashTwoj klucz prywatny
valueKwota w groszach (np. "1023")
url_successURL powodzenia
url_failURL niepowodzenia
url_ipnURL powiadomień IPN

Separator: | (pipe)

Weryfikacja podpisu IPN

sha256({id}|{SecretHash}|{amount}|{email}|{type}|{attempt}|{version}|{custom})
ElementOpis
idIdentyfikator transakcji
SecretHashTwoj klucz prywatny
amountKwota transakcji
emailE-mail klienta
typeTyp powiadomienia
attemptNumer próby dostarczenia
versionWersja protokolu
customDane wlasne

Separator: | (pipe)

Przykłady implementacji

PHP

<?php
/**
* Generowanie checksum dla rejestracji płatności
*/
function generatePaymentChecksum(
string $service,
string $secretHash,
string $value,
string $urlSuccess,
string $urlFail,
string $urlIpn
): string {
$data = implode('|', [
$service,
$secretHash,
$value,
$urlSuccess,
$urlFail,
$urlIpn,
]);

return hash('sha256', $data);
}

/**
* Generowanie checksum dla zwrotu
*/
function generateRefundChecksum(
string $service,
string $transactionId,
string $secretHash,
?string $value = null
): string {
if ($value !== null) {
$data = implode('|', [$service, $transactionId, $value, $secretHash]);
} else {
$data = implode('|', [$service, $transactionId, $secretHash]);
}
return hash('sha256', $data);
}

/**
* Generowanie checksum dla DCB
*/
function generateDcbChecksum(
string $guid,
string $secretHash,
string $valueInGrosze,
string $urlSuccess,
string $urlFail,
string $urlIpn
): string {
$data = implode('|', [
$guid,
$secretHash,
$valueInGrosze,
$urlSuccess,
$urlFail,
$urlIpn,
]);

return hash('sha256', $data);
}

/**
* Weryfikacja podpisu IPN
*/
function verifyIpnSignature(array $data, string $secretHash): bool
{
$expected = hash('sha256',
$data['id'] . '|' . $secretHash . '|' . $data['amount'] . '|' . $data['email']
. '|' . $data['type'] . '|' . $data['attempt'] . '|' . $data['version'] . '|' . $data['custom']
);

return hash_equals($expected, $data['signature']);
}

// === Przykład użycia ===

$service = getenv('DPAY_SERVICE');
$secretHash = getenv('DPAY_SECRET_HASH');

// Checksum płatności
$checksum = generatePaymentChecksum(
$service,
$secretHash,
'29.99',
'https://mojsklep.pl/sukces',
'https://mojsklep.pl/błąd',
'https://mojsklep.pl/api/ipn'
);

echo $checksum; // np. "a1b2c3d4e5f6..."

JavaScript (Node.js)

const crypto = require('crypto');

/**
* Generowanie checksum dla rejestracji płatności
*/
function generatePaymentChecksum(service, secretHash, value, urlSuccess, urlFail, urlIpn) {
const data = [service, secretHash, value, urlSuccess, urlFail, urlIpn].join('|');
return crypto.createHash('sha256').update(data).digest('hex');
}

/**
* Generowanie checksum dla zwrotu
*/
function generateRefundChecksum(service, transactionId, secretHash, value = null) {
const parts = value !== null
? [service, transactionId, value, secretHash]
: [service, transactionId, secretHash];
return crypto.createHash('sha256').update(parts.join('|')).digest('hex');
}

/**
* Generowanie checksum dla DCB
*/
function generateDcbChecksum(guid, secretHash, valueInGrosze, urlSuccess, urlFail, urlIpn) {
const data = [guid, secretHash, valueInGrosze, urlSuccess, urlFail, urlIpn].join('|');
return crypto.createHash('sha256').update(data).digest('hex');
}

/**
* Weryfikacja podpisu IPN
*/
function verifyIpnSignature(data, secretHash) {
const raw = [
data.id, secretHash, data.amount, data.email,
data.type, data.attempt, data.version, data.custom,
].join('|');

const expected = crypto.createHash('sha256').update(raw).digest('hex');

return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(data.signature, 'hex')
);
}

// === Przykład użycia ===
const service = process.env.DPAY_SERVICE;
const secretHash = process.env.DPAY_SECRET_HASH;

const checksum = generatePaymentChecksum(
service,
secretHash,
'29.99',
'https://mojsklep.pl/sukces',
'https://mojsklep.pl/błąd',
'https://mojsklep.pl/api/ipn'
);

console.log(checksum);

Python

import hashlib
import hmac
import os

def generate_payment_checksum(
service: str,
secret_hash: str,
value: str,
url_success: str,
url_fail: str,
url_ipn: str,
) -> str:
"""Generowanie checksum dla rejestracji płatności."""
data = '|'.join([service, secret_hash, value, url_success, url_fail, url_ipn])
return hashlib.sha256(data.encode('utf-8')).hexdigest()


def generate_refund_checksum(
service: str,
transaction_id: str,
secret_hash: str,
value: str | None = None,
) -> str:
"""Generowanie checksum dla zwrotu (pełnego lub częściowego)."""
if value is not None:
data = '|'.join([service, transaction_id, value, secret_hash])
else:
data = '|'.join([service, transaction_id, secret_hash])
return hashlib.sha256(data.encode('utf-8')).hexdigest()


def generate_dcb_checksum(
guid: str,
secret_hash: str,
value_in_grosze: str,
url_success: str,
url_fail: str,
url_ipn: str,
) -> str:
"""Generowanie checksum dla DCB."""
data = '|'.join([guid, secret_hash, value_in_grosze, url_success, url_fail, url_ipn])
return hashlib.sha256(data.encode('utf-8')).hexdigest()


def verify_ipn_signature(data: dict, secret_hash: str) -> bool:
"""Weryfikacja podpisu IPN."""
raw = '|'.join([
data['id'], secret_hash, data['amount'], data['email'],
data['type'], str(data['attempt']), data['version'], data['custom'],
])
expected = hashlib.sha256(raw.encode('utf-8')).hexdigest()
return hmac.compare_digest(expected, data['signature'])


# === Przykład użycia ===
service = os.environ['DPAY_SERVICE']
secret_hash = os.environ['DPAY_SECRET_HASH']

checksum = generate_payment_checksum(
service,
secret_hash,
'29.99',
'https://mojsklep.pl/sukces',
'https://mojsklep.pl/błąd',
'https://mojsklep.pl/api/ipn',
)

print(checksum)

Najczęstsze błędy

1. Nieprawidłowa kolejność pol

Pola muszą byc w dokładnie takiej kolejności, jak podano w dokumentacji. Zamiana kolejności spowoduje wygenerowanie innego checksum.

2. Dodatkowe białoznaki

Upewnij się, ze wartości pól nie zawieraja dodatkowych spacji, znaków nowej linii ani innych białoznaków:

// ZLE - dodatkowa spacja
$value = '29.99 ';

// DOBRZE
$value = trim('29.99');

3. Kodowanie znaków

Użyj kodowania UTF-8. Znaki specjalne w URL-ach (np. polskie znaki) mogą powodowac rozne wyniki w zaleznosci od kodowania.

4. Porownywanie w sposób odporny na timing attacks

Przy weryfikacji podpisu IPN użyj porownywania odpornego na ataki czasowe:

// ZLE - podatne na timing attack
if ($expected === $data['signature']) { ... }

// DOBRZE - odporne na timing attack
if (hash_equals($expected, $data['signature'])) { ... }
// DOBRZE - Node.js
crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
);
# DOBRZE - Python
import hmac
hmac.compare_digest(expected, signature)

Debugowanie checksum

Jeśli otrzymujesz błąd Invalid checksum, wykonaj nastepujace kroki:

  1. Wypisz dane wejsciowe - sprawdz dokładna wartość każdego pola
  2. Sprawdz separator - wszędzie używaj separatora | (pipe)
  3. Zweryfikuj Secret Hash - upewnij się, ze używasz aktualnego klucza z panelu
  4. Porownaj z narzedziem online - użyj dowolnego generatora SHA-256 i porównaj wynik z tym, co generuje Twoj kod
// Debugowanie - wypisz dane przed hashowaniem
$raw = $service . '|' . $secretHash . '|' . $value . '|' .
$urlSuccess . '|' . $urlFail . '|' . $urlIpn;
error_log('Checksum input: ' . $raw);
error_log('Checksum output: ' . hash('sha256', $raw));