Skip to main content

Karty - płatności cykliczne (MIT)

Płatności cykliczne kartą umożliwiają obciążanie zapisanej karty klienta bez jego obecności - w modelu MIT (Merchant Initiated Transactions). Po jednorazowej zgodzie klienta (ustanowienie mandatu) merchant inicjuje kolejne pobrania samodzielnie, np. przy odnowieniu subskrypcji.

Model pull

Kolejne pobrania inicjuje merchant (model pull) - wołając endpoint /register w wybranym momencie cyklu. dpay.pl nie planuje pobrań automatycznie; to merchant decyduje kiedy i o jaką kwotę obciążyć kartę (w granicach ustalonych limitów).

Korzyści

  • Subskrypcje i płatności abonamentowe - automatyczne odnowienia bez udziału klienta
  • Mniejsza rezygnacja - brak konieczności ponownego wpisywania danych karty
  • Kontrola limitów - sztywne lub maksymalne kwoty pobrań ustalane per mandat

Jak działa?

Proces składa się z trzech kroków:

  1. Rejestracja mandatu - merchant wywołuje /register z obiektem register_card_recurring. dpay.pl zwraca card_recurring_alias.
  2. Aktywacja - klient wykonuje pierwszą płatność kartą (3DS). Po jej powodzeniu mandat zostaje aktywowany.
  3. Pobrania cykliczne (MIT) - merchant obciąża zapisaną kartę, podając card_recurring_alias (server-to-server, bez udziału klienta).
Zachowaj alias

card_recurring_alias zwracany przy rejestracji jest jedynym identyfikatorem mandatu potrzebnym do kolejnych pobrań. Zapisz go po stronie serwera, powiązany z klientem/subskrypcją.

Wymagania

  • Aktywny Punkt Płatności z włączoną obsługą płatności cyklicznych kartą (skontaktuj się z dpay.pl, aby ją włączyć)
  • Wdrożona integracja kartowa server-to-server - patrz Karty S2S (krok aktywacji wykorzystuje ten sam przepływ płatności kartą)
  • Możliwość przechowywania card_recurring_alias po stronie serwera

Endpoint

POST https://api-payments.dpay.pl/api/v1_0/payments/register
Content-Type: application/json

Krok 1. Rejestracja mandatu

Rejestracja tworzy mandat i zwraca jego alias. Pierwsza płatność jednocześnie ustanawia mandat - dlatego kwota value musi być większa od zera. Mandat staje się aktywny dopiero po udanej pierwszej płatności kartą (krok 2).

Kwota rejestracji

Rejestracja wymaga value > 0 (pierwsza realna płatność). Rejestracja bez obciążenia (value=0, card-on-file) nie jest obsługiwana w płatnościach cyklicznych - to odrębny mechanizm tokenizacji, niedostępny w tej wersji.

Parametry zapytania

PoleTypWymaganeOpis
transactionTypestringTak"card_recurring"
servicestringTakNazwa serwisu z panelu
valuestringTakKwota pierwszej płatności w PLN (musi być > 0)
creditcardintegerTak1 - płatność kartą
url_successstringTakURL po udanej płatności
url_failstringTakURL po nieudanej płatności
url_ipnstringTakURL do powiadomień IPN
checksumstringTakSuma kontrolna SHA-256
emailstringNieE-mail klienta
client_namestringNieImię klienta
client_surnamestringNieNazwisko klienta
register_card_recurringobjectTakDane mandatu do rejestracji

Obiekt register_card_recurring

PoleTypWymaganeOpis
labelstringTakEtykieta mandatu (max 50 znaków)
frequencystringNieOpcjonalna częstotliwość cyklu - wartość enum: DAILY, WEEKLY, BIWEEKLY, MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL.
limit_amtintegerNieLimit pojedynczego pobrania w groszach
tot_limit_amtintegerNieLimit łączny wszystkich pobrań w groszach
is_limit_amt_fixedbooleanNietrue - kwota pobrania musi być dokładnie równa limit_amt; false - limit_amt jest kwotą maksymalną
expiration_datestringNieData wygaśnięcia mandatu (YYYY-MM-DD, po dacie pobrania są odrzucane)
init_datestringNieData rozpoczęcia cyklu (YYYY-MM-DD)
Jednostki: grosze vs PLN

Limity (limit_amt, tot_limit_amt) podawane są w groszach (np. 5999 = 59,99 PLN), natomiast value podawane jest w PLN (np. "59.99"). To częsta pułapka - zwróć na nią uwagę.

Alias generowany automatycznie

card_recurring_alias jest zawsze generowany przez dpay.pl (format DPAY.CARD.{id}.{losowy}). Nie podawaj własnego aliasu - zostanie odrzucony.

Generowanie checksum

Checksum generowany jest identycznie jak dla standardowej płatności:

sha256({service}|{SecretHash}|{value}|{url_success}|{url_fail}|{url_ipn})

Przykład zapytania

cURL

curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "card_recurring",
"service": "abc123",
"value": "19.99",
"creditcard": 1,
"url_success": "https://mojsklep.pl/sukces",
"url_fail": "https://mojsklep.pl/blad",
"url_ipn": "https://mojsklep.pl/api/ipn",
"checksum": "e3b0c44298fc1c149afb...",
"email": "klient@example.com",
"register_card_recurring": {
"label": "Subskrypcja Premium",
"frequency": "MONTHLY",
"limit_amt": 5999,
"tot_limit_amt": 71988,
"is_limit_amt_fixed": false,
"expiration_date": "2027-06-30"
}
}'

PHP

<?php
$service = getenv('DPAY_SERVICE');
$secretHash = getenv('DPAY_SECRET_HASH');

$value = '19.99'; // pierwsza płatność ustanawiająca mandat (musi być > 0)
$urlSuccess = 'https://mojsklep.pl/sukces';
$urlFail = 'https://mojsklep.pl/blad';
$urlIpn = 'https://mojsklep.pl/api/ipn';

$checksum = hash('sha256',
$service . '|' . $secretHash . '|' . $value . '|' .
$urlSuccess . '|' . $urlFail . '|' . $urlIpn
);

$payload = json_encode([
'transactionType' => 'card_recurring',
'service' => $service,
'value' => $value,
'creditcard' => 1,
'url_success' => $urlSuccess,
'url_fail' => $urlFail,
'url_ipn' => $urlIpn,
'checksum' => $checksum,
'email' => 'klient@example.com',
'register_card_recurring' => [
'label' => 'Subskrypcja Premium',
'frequency' => 'MONTHLY',
'limit_amt' => 5999, // grosze
'tot_limit_amt' => 71988, // grosze
'is_limit_amt_fixed' => false,
'expiration_date' => '2027-06-30',
],
]);

$ch = curl_init('https://api-payments.dpay.pl/api/v1_0/payments/register');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);
// Zapisz $result['additionalInfo']['card_recurring_alias'] po stronie serwera

JavaScript (Node.js / Express)

const crypto = require('crypto');
const axios = require('axios');

async function registerCardMandate() {
const service = process.env.DPAY_SERVICE;
const secretHash = process.env.DPAY_SECRET_HASH;

const value = '19.99'; // pierwsza płatność ustanawiająca mandat (musi być > 0)
const urlSuccess = 'https://mojsklep.pl/sukces';
const urlFail = 'https://mojsklep.pl/blad';
const urlIpn = 'https://mojsklep.pl/api/ipn';

const checksum = crypto
.createHash('sha256')
.update(`${service}|${secretHash}|${value}|${urlSuccess}|${urlFail}|${urlIpn}`)
.digest('hex');

const response = await axios.post(
'https://api-payments.dpay.pl/api/v1_0/payments/register',
{
transactionType: 'card_recurring',
service,
value,
creditcard: 1,
url_success: urlSuccess,
url_fail: urlFail,
url_ipn: urlIpn,
checksum,
email: 'klient@example.com',
register_card_recurring: {
label: 'Subskrypcja Premium',
frequency: 'MONTHLY',
limit_amt: 5999, // grosze
tot_limit_amt: 71988, // grosze
is_limit_amt_fixed: false,
expiration_date: '2027-06-30',
},
}
);

// Zapisz response.data.additionalInfo.card_recurring_alias
return response.data;
}

Odpowiedź API

{
"error": false,
"status": true,
"msg": "Card recurring mandate registered, awaiting initial customer payment",
"transactionId": "abc-def-123-456",
"additionalInfo": {
"card_recurring_alias": "DPAY.CARD.123.a1b2c3d4",
"operation": "mandate_registration"
}
}

Zapisz additionalInfo.card_recurring_alias - będzie potrzebny do kolejnych pobrań. transactionId to identyfikator transakcji rejestracyjnej, którą klient opłaca w kroku 2.

Krok 2. Aktywacja mandatu

Klient finalizuje pierwszą płatność kartą dla transakcji transactionId z kroku 1 - dokładnie tak jak w zwykłej płatności kartą server-to-server (patrz Karty S2S), wraz z uwierzytelnieniem 3DS.

Po udanej pierwszej płatności mandat zostaje automatycznie aktywowany. Dopiero aktywny mandat pozwala na pobrania cykliczne. Wynik pierwszej płatności otrzymasz przez IPN, tak jak dla każdej transakcji kartowej.

info

Dopóki klient nie sfinalizuje pierwszej płatności, mandat pozostaje nieaktywny i próby pobrania zwrócą błąd No active card recurring mandate found for this alias.

Krok 3. Pobranie cykliczne (MIT)

Aby obciążyć zapisaną kartę, wywołaj /register z card_recurring_alias, transactionType=card_recurring oraz dodatnią kwotą value. Wywołanie jest server-to-server - klient nie bierze w nim udziału i nie jest wymagane 3DS.

Parametry zapytania

PoleTypWymaganeOpis
transactionTypestringTak"card_recurring"
servicestringTakNazwa serwisu z panelu
valuestringTakKwota pobrania w PLN (musi być > 0)
card_recurring_aliasstringTakAlias mandatu zwrócony przy rejestracji (max 128 znaków)
url_successstringTakURL po udanej płatności
url_failstringTakURL po nieudanej płatności
url_ipnstringTakURL do powiadomień IPN
checksumstringTakSuma kontrolna SHA-256
Wzajemne wykluczanie

Pola register_card_recurring i card_recurring_alias wzajemnie się wykluczają. Przy pobraniu przekazuj wyłącznie card_recurring_alias.

Przykład zapytania

cURL

curl -X POST https://api-payments.dpay.pl/api/v1_0/payments/register \
-H "Content-Type: application/json" \
-d '{
"transactionType": "card_recurring",
"service": "abc123",
"value": "59.99",
"card_recurring_alias": "DPAY.CARD.123.a1b2c3d4",
"url_success": "https://mojsklep.pl/sukces",
"url_fail": "https://mojsklep.pl/blad",
"url_ipn": "https://mojsklep.pl/api/ipn",
"checksum": "e3b0c44298fc1c149afb..."
}'

PHP

<?php
$service = getenv('DPAY_SERVICE');
$secretHash = getenv('DPAY_SECRET_HASH');

$value = '59.99';
$urlSuccess = 'https://mojsklep.pl/sukces';
$urlFail = 'https://mojsklep.pl/blad';
$urlIpn = 'https://mojsklep.pl/api/ipn';

$checksum = hash('sha256',
$service . '|' . $secretHash . '|' . $value . '|' .
$urlSuccess . '|' . $urlFail . '|' . $urlIpn
);

// $aliasValue - zapisany wcześniej alias mandatu klienta
$payload = json_encode([
'transactionType' => 'card_recurring',
'service' => $service,
'value' => $value,
'card_recurring_alias' => $aliasValue,
'url_success' => $urlSuccess,
'url_fail' => $urlFail,
'url_ipn' => $urlIpn,
'checksum' => $checksum,
]);

$ch = curl_init('https://api-payments.dpay.pl/api/v1_0/payments/register');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);

Odpowiedź API

{
"error": false,
"status": true,
"msg": "Internal processing",
"transactionId": "ghi-jkl-789-012"
}

Odpowiedź potwierdza przyjęcie pobrania. Ostateczny wynik (rozliczenie) realizowany jest asynchronicznie - otrzymasz powiadomienie IPN dla transakcji transactionId, tak jak dla każdej płatności. Sprawdź też statusy transakcji.

Idempotentność i ponawianie

Endpoint pobrania nie jest idempotentny - każde wywołanie tworzy nową próbę obciążenia. Po timeoucie sieciowym nie ponawiaj wywołania w ciemno - najpierw sprawdź status poprzedniej transakcji (IPN lub odpytanie statusu). Równoległe wywołania dla tego samego aliasu są blokowane (Card recurring charge already in progress for this alias).

Limity

Limity walidowane są po stronie dpay.pl przed obciążeniem karty:

LimitPoleDziałanie
Pojedyncze pobranie (sztywne)limit_amt + is_limit_amt_fixed: trueKwota pobrania musi być dokładnie równa limit_amt
Pojedyncze pobranie (maksymalne)limit_amt + is_limit_amt_fixed: falseKwota pobrania nie może przekroczyć limit_amt
Łącznytot_limit_amtSuma wszystkich rozliczonych pobrań + bieżące nie może przekroczyć tot_limit_amt
Wygaśnięcieexpiration_datePo tej dacie pobrania są odrzucane

Limity podawane są w groszach. Przekroczenie limitu zwraca błąd jeszcze przed wywołaniem operatora kartowego.

Wyrejestrowanie mandatu

Aby zakończyć subskrypcję, po prostu przestań wywoływać endpoint pobrania - dpay.pl nie wykona żadnego obciążenia bez Twojego zapytania. Mandat wygasa automatycznie po expiration_date. Operator dpay.pl może również ręcznie dezaktywować mandat w panelu administracyjnym (status UNREGISTERED), po czym kolejne pobrania będą odrzucane.

Walidacja i ograniczenia

RegułaOpis
transactionTypeMusi być "card_recurring"
Wzajemne wykluczanieregister_card_recurring i card_recurring_alias nie mogą wystąpić jednocześnie
register_card_recurring.labelMaksymalnie 50 znaków
register_card_recurring.frequencyOpcjonalna; wartość enum: DAILY, WEEKLY, BIWEEKLY, MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL
card_recurring_aliasMaksymalnie 128 znaków; przy pobraniu wymaga value > 0
Alias mandatuZawsze generowany przez dpay.pl - nie można podać własnego
Limitylimit_amt, tot_limit_amt w groszach

Obsługa błędów

KomunikatPrzyczynaDziałanie
Invalid checksumNieprawidłowa suma kontrolnaSprawdź kolejność i wartości pól w checksum
Card Recurring is not enabled for this service.Brak włączonej obsługi na Punkcie PłatnościSkontaktuj się z dpay.pl
No active card recurring mandate found for this alias.Mandat nie istnieje lub nie został aktywowany (klient nie opłacił pierwszej płatności)Upewnij się, że krok 2 zakończył się sukcesem
Card recurring mandate has expired; please register a new mandate.Mandat po expiration_dateZarejestruj nowy mandat
Card Recurring charge requires value > 0.Pobranie z kwotą 0Podaj dodatnią value
Card Recurring: payment amount ... exceeds single payment limit ...Przekroczony limit_amtDostosuj kwotę do limitu
Card Recurring: total spent ... exceeds total limit ...Przekroczony tot_limit_amtLimit łączny mandatu wyczerpany
Card recurring charge declined by provider.Obciążenie odrzucone przez operatora (np. brak środków, karta wygasła)Poinformuj klienta; przy trwałym odrzuceniu zarejestruj nowy mandat
Card recurring charge already in progress for this alias...Trwa równoległe pobranie dla tego aliasuNie ponawiaj w ciemno - sprawdź status poprzedniej próby
tip

Jeśli pobrania zaczną być trwale odrzucane (np. karta klienta wygasła) lub mandat wygasł, zarejestruj nowy mandat - klient ponownie poda dane karty i wyrazi zgodę.