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.
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:
- Rejestracja mandatu - merchant wywołuje
/registerz obiektemregister_card_recurring. dpay.pl zwracacard_recurring_alias. - Aktywacja - klient wykonuje pierwszą płatność kartą (3DS). Po jej powodzeniu mandat zostaje aktywowany.
- Pobrania cykliczne (MIT) - merchant obciąża zapisaną kartę, podając
card_recurring_alias(server-to-server, bez udziału klienta).
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_aliaspo 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).
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
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
transactionType | string | Tak | "card_recurring" |
service | string | Tak | Nazwa serwisu z panelu |
value | string | Tak | Kwota pierwszej płatności w PLN (musi być > 0) |
creditcard | integer | Tak | 1 - płatność kartą |
url_success | string | Tak | URL po udanej płatności |
url_fail | string | Tak | URL po nieudanej płatności |
url_ipn | string | Tak | URL do powiadomień IPN |
checksum | string | Tak | Suma kontrolna SHA-256 |
email | string | Nie | E-mail klienta |
client_name | string | Nie | Imię klienta |
client_surname | string | Nie | Nazwisko klienta |
register_card_recurring | object | Tak | Dane mandatu do rejestracji |
Obiekt register_card_recurring
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
label | string | Tak | Etykieta mandatu (max 50 znaków) |
frequency | string | Nie | Opcjonalna częstotliwość cyklu - wartość enum: DAILY, WEEKLY, BIWEEKLY, MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL. |
limit_amt | integer | Nie | Limit pojedynczego pobrania w groszach |
tot_limit_amt | integer | Nie | Limit łączny wszystkich pobrań w groszach |
is_limit_amt_fixed | boolean | Nie | true - kwota pobrania musi być dokładnie równa limit_amt; false - limit_amt jest kwotą maksymalną |
expiration_date | string | Nie | Data wygaśnięcia mandatu (YYYY-MM-DD, po dacie pobrania są odrzucane) |
init_date | string | Nie | Data rozpoczęcia cyklu (YYYY-MM-DD) |
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ę.
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.
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
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
transactionType | string | Tak | "card_recurring" |
service | string | Tak | Nazwa serwisu z panelu |
value | string | Tak | Kwota pobrania w PLN (musi być > 0) |
card_recurring_alias | string | Tak | Alias mandatu zwrócony przy rejestracji (max 128 znaków) |
url_success | string | Tak | URL po udanej płatności |
url_fail | string | Tak | URL po nieudanej płatności |
url_ipn | string | Tak | URL do powiadomień IPN |
checksum | string | Tak | Suma kontrolna SHA-256 |
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.
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:
| Limit | Pole | Działanie |
|---|---|---|
| Pojedyncze pobranie (sztywne) | limit_amt + is_limit_amt_fixed: true | Kwota pobrania musi być dokładnie równa limit_amt |
| Pojedyncze pobranie (maksymalne) | limit_amt + is_limit_amt_fixed: false | Kwota pobrania nie może przekroczyć limit_amt |
| Łączny | tot_limit_amt | Suma wszystkich rozliczonych pobrań + bieżące nie może przekroczyć tot_limit_amt |
| Wygaśnięcie | expiration_date | Po 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ła | Opis |
|---|---|
transactionType | Musi być "card_recurring" |
| Wzajemne wykluczanie | register_card_recurring i card_recurring_alias nie mogą wystąpić jednocześnie |
register_card_recurring.label | Maksymalnie 50 znaków |
register_card_recurring.frequency | Opcjonalna; wartość enum: DAILY, WEEKLY, BIWEEKLY, MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL |
card_recurring_alias | Maksymalnie 128 znaków; przy pobraniu wymaga value > 0 |
| Alias mandatu | Zawsze generowany przez dpay.pl - nie można podać własnego |
| Limity | limit_amt, tot_limit_amt w groszach |
Obsługa błędów
| Komunikat | Przyczyna | Działanie |
|---|---|---|
Invalid checksum | Nieprawidłowa suma kontrolna | Sprawdź 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ści | Skontaktuj 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_date | Zarejestruj nowy mandat |
Card Recurring charge requires value > 0. | Pobranie z kwotą 0 | Podaj dodatnią value |
Card Recurring: payment amount ... exceeds single payment limit ... | Przekroczony limit_amt | Dostosuj kwotę do limitu |
Card Recurring: total spent ... exceeds total limit ... | Przekroczony tot_limit_amt | Limit łą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 aliasu | Nie ponawiaj w ciemno - sprawdź status poprzedniej próby |
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ę.