Przejdź do głównej zawartości

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.

Wymagana certyfikacja PCI DSS

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-----"
}
Cache klucza publicznego

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
}
PoleOpisFormat
PNNumer karty (PAN)Ciąg cyfr bez spacji
SCKod CVV/CVC3 cyfry (4 dla Amex)
DTData ważnościMM/YY
IDIdentyfikator transakcjitransactionId z Kroku 1
TXTimestamp UnixAktualny 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:

PoleTypOpis
browserJavaEnabledbooleanCzy Java jest włączona w przeglądarce
browserLanguagestringJęzyk przeglądarki (np. "pl-PL")
browserColorDepthstringGłębia kolorów ekranu
browserScreenHeightstringWysokość ekranu w pikselach
browserScreenWidthstringSzerokość ekranu w pikselach
browserTZstringStrefa czasowa w minutach (np. "-60" dla CET)
browserUserAgentstringPełny User-Agent przeglądarki
browserAcceptHeaderstringNagłówek Accept przeglądarki
browserJavascriptEnabledbooleanCzy 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

ostrzeżenie
  • 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