Płatności kartowe Server-to-Server (S2S)
Integracja Server-to-Server pozwala na przetwarzanie płatności kartowych (Visa, Mastercard) bezpośrednio na Twojej stronie, bez przekierowania do zewnętrznej bramki. Dane karty są szyfrowane kluczem publicznym RSA, co zapewnia bezpieczeństwo transakcji.
Integracja S2S wymaga posiadania certyfikacji PCI DSS, ponieważ dane kart płatniczych są przetwarzane po stronie Twojej infrastruktury. Uruchomienie tego trybu integracji wymaga wcześniejszej zgody dpay - skontaktuj się z nami przed rozpoczęciem implementacji.
Dowiedz się więcej o wymaganiach na stronie PCI DSS - informacje dla merchantów.
Schemat działania
Krok 1: Rejestracja płatności
Zarejestruj transakcję tak samo jak w standardowej integracji:
curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "transfers",
"service": "abc123",
"value": "99.99",
"url_success": "https://mojsklep.pl/sukces",
"url_fail": "https://mojsklep.pl/blad",
"url_ipn": "https://mojsklep.pl/api/ipn",
"checksum": "..."
}'
Zapisz transactionId z odpowiedzi - będzie potrzebny w następnych krokach.
Krok 2: Pobranie klucza publicznego RSA
Pobierz klucz publiczny używany do szyfrowania danych karty:
GET https://api-payments.dpay.pl/api/v1_0/cards/public-key
Odpowiedź
{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----"
}
Klucz publiczny nie zmienia się często. Możesz go cache'ować po stronie serwera, ale sprawdzaj aktualizacje co 24 godziny.
Krok 3: Przygotowanie i szyfrowanie danych karty
Struktura JSON z danymi karty
Przygotuj obiekt JSON z danymi karty w następującym formacie:
{
"PN": "4111111111111111",
"SC": "123",
"DT": "12/25",
"ID": "abc-def-123-456",
"TX": 1700000000
}
| Pole | Opis | Format |
|---|---|---|
PN | Numer karty (PAN) | Ciąg cyfr bez spacji |
SC | Kod CVV/CVC | 3 cyfry (4 dla Amex) |
DT | Data ważności | MM/YY |
ID | Identyfikator transakcji | transactionId z Kroku 1 |
TX | Timestamp Unix | Aktualny czas w sekundach |
Szyfrowanie RSA/PKCS#1
Dane karty muszą być zaszyfrowane algorytmem RSA z paddingiem PKCS#1 v1.5, a następnie zakodowane w Base64.
TypeScript (z użyciem JSEncrypt)
import JSEncrypt from 'jsencrypt';
function encryptCardData(
cardNumber: string,
cvv: string,
expiryDate: string,
transactionId: string,
publicKey: string
): string {
const cardData = JSON.stringify({
PN: cardNumber,
SC: cvv,
DT: expiryDate,
ID: transactionId,
TX: Math.floor(Date.now() / 1000),
});
const encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
const encrypted = encrypt.encrypt(cardData);
if (!encrypted) {
throw new Error('Szyfrowanie danych karty nie powiodło się');
}
return encrypted; // Już w formacie Base64
}
PHP (OpenSSL)
function encryptCardData(
string $cardNumber,
string $cvv,
string $expiryDate,
string $transactionId,
string $publicKeyPem
): string {
$cardData = json_encode([
'PN' => $cardNumber,
'SC' => $cvv,
'DT' => $expiryDate,
'ID' => $transactionId,
'TX' => time(),
]);
$publicKey = openssl_pkey_get_public($publicKeyPem);
if (!$publicKey) {
throw new Exception('Nieprawidłowy klucz publiczny');
}
$encrypted = '';
$result = openssl_public_encrypt($cardData, $encrypted, $publicKey, OPENSSL_PKCS1_PADDING);
if (!$result) {
throw new Exception('Szyfrowanie nie powiodło się');
}
return base64_encode($encrypted);
}
Python
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import json
import base64
import time
def encrypt_card_data(card_number, cvv, expiry_date, transaction_id, public_key_pem):
card_data = json.dumps({
'PN': card_number,
'SC': cvv,
'DT': expiry_date,
'ID': transaction_id,
'TX': int(time.time()),
})
key = RSA.import_key(public_key_pem)
cipher = PKCS1_v1_5.new(key)
encrypted = cipher.encrypt(card_data.encode('utf-8'))
return base64.b64encode(encrypted).decode('utf-8')
Krok 4: Wysłanie płatności kartowej
Wyślij zaszyfrowane dane karty na endpoint płatności kartowej:
POST https://api-payments.dpay.pl/api/v1_0/cards/payment/{transactionId}/pay/card-otp
Content-Type: application/json
Parametry zapytania
{
"encryptedCardData": "Base64-encoded-encrypted-data...",
"deviceInfo": {
"browserJavaEnabled": false,
"browserLanguage": "pl-PL",
"browserColorDepth": "24",
"browserScreenHeight": "1080",
"browserScreenWidth": "1920",
"browserTZ": "-60",
"browserUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"browserAcceptHeader": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"browserJavascriptEnabled": true
}
}
Obiekt DeviceInfo
DeviceInfo jest wymagany do procesu weryfikacji 3D Secure. Wszystkie pola są obowiązkowe:
| Pole | Typ | Opis |
|---|---|---|
browserJavaEnabled | boolean | Czy Java jest włączona w przeglądarce |
browserLanguage | string | Język przeglądarki (np. "pl-PL") |
browserColorDepth | string | Głębia kolorów ekranu |
browserScreenHeight | string | Wysokość ekranu w pikselach |
browserScreenWidth | string | Szerokość ekranu w pikselach |
browserTZ | string | Strefa czasowa w minutach (np. "-60" dla CET) |
browserUserAgent | string | Pełny User-Agent przeglądarki |
browserAcceptHeader | string | Nagłówek Accept przeglądarki |
browserJavascriptEnabled | boolean | Czy JavaScript jest włączony |
Zbieranie DeviceInfo (JavaScript)
function getDeviceInfo() {
return {
browserJavaEnabled: navigator.javaEnabled ? navigator.javaEnabled() : false,
browserLanguage: navigator.language || 'pl-PL',
browserColorDepth: String(screen.colorDepth),
browserScreenHeight: String(screen.height),
browserScreenWidth: String(screen.width),
browserTZ: String(new Date().getTimezoneOffset()),
browserUserAgent: navigator.userAgent,
browserAcceptHeader: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
browserJavascriptEnabled: true,
};
}
Krok 5: Obsługa 3D Secure
Jeśli bank wymaga autoryzacji 3D Secure, odpowiedź będzie zawierać dane do przekierowania:
Odpowiedź z 3DS
{
"error": false,
"status": "3DS_REQUIRED",
"redirectType": "FORM",
"redirectUrl": "https://acs-bank.example.com/3ds",
"redirectParams": {
"PaReq": "eJzVWFmTo...",
"TermUrl": "https://api-payments.dpay.pl/api/v1_0/cards/3ds/callback/abc-def",
"MD": "abc-def-123-456"
}
}
Obsługa przekierowania FORM
Gdy redirectType to FORM, musisz dynamicznie utworzyć formularz HTML i automatycznie go wysłać:
function handle3DS(response) {
if (response.redirectType === 'FORM') {
const form = document.createElement('form');
form.method = 'POST';
form.action = response.redirectUrl;
Object.entries(response.redirectParams).forEach(([key, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = value;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
}
Po zakończeniu procesu 3DS klient zostanie przekierowany na url_success lub url_fail, a dpay.pl wyśle powiadomienie IPN.
Odpowiedź sukcesu (bez 3DS)
Jeśli 3DS nie jest wymagane, płatność zostaje przetworzona natychmiast:
{
"error": false,
"status": "paid",
"transactionId": "abc-def-123-456"
}
Pełny przykład - Node.js
const crypto = require('crypto');
const axios = require('axios');
const JSEncrypt = require('jsencrypt');
async function processCardPayment(orderData, cardData, deviceInfo) {
const service = process.env.DPAY_SERVICE;
const secretHash = process.env.DPAY_SECRET_HASH;
// Krok 1: Rejestracja płatności
const checksum = crypto
.createHash('sha256')
.update(`${service}|${secretHash}|${orderData.value}|${orderData.urlSuccess}|${orderData.urlFail}|${orderData.urlIpn}`)
.digest('hex');
const registerResponse = await axios.post(
'https://api-payments.dpay.pl/api/v1_0/payments/register',
{
transactionType: 'transfers',
service,
value: orderData.value,
url_success: orderData.urlSuccess,
url_fail: orderData.urlFail,
url_ipn: orderData.urlIpn,
checksum,
}
);
const { transactionId } = registerResponse.data;
// Krok 2: Pobranie klucza publicznego
const keyResponse = await axios.get(
'https://api-payments.dpay.pl/api/v1_0/cards/public-key'
);
// Krok 3: Szyfrowanie danych karty
const encrypt = new JSEncrypt();
encrypt.setPublicKey(keyResponse.data.publicKey);
const encryptedCardData = encrypt.encrypt(JSON.stringify({
PN: cardData.number,
SC: cardData.cvv,
DT: cardData.expiry,
ID: transactionId,
TX: Math.floor(Date.now() / 1000),
}));
// Krok 4: Wysłanie płatności
const payResponse = await axios.post(
`https://api-payments.dpay.pl/api/v1_0/cards/payment/${transactionId}/pay/card-otp`,
{
encryptedCardData,
deviceInfo,
}
);
return payResponse.data;
}
Bezpieczeństwo
- Nigdy nie przechowuj danych kart (numerów, CVV, dat ważności) na swoim serwerze
- Nigdy nie loguj danych kart w plikach dziennika
- Szyfrowanie RSA musi odbywać się po stronie klienta (w przeglądarce), aby surowe dane karty nigdy nie trafiły na Twój serwer
- Użyj HTTPS na całej stronie