Dynamic Currency Conversion (DCC)
DCC to mechanizm pozwalający posiadaczowi karty zagranicznej zapłacić w walucie własnej karty zamiast w walucie sklepu. Bramka oblicza ofertę z aktualnym kursem (zawierającym marżę sieci kartowej, np. 6% Mastercard) i przekazuje ją do prezentacji klientowi przed zatwierdzeniem płatności.
DCC jest dostępne wyłącznie w integracji Server-to-Server (S2S). Integracja przez iframe SDK obsługuje DCC automatycznie wewnątrz ramki - merchant nie musi nic dodatkowo implementować.
DCC wymaga osobnej aktywacji per serwis płatniczy. Jeśli chcesz włączyć ten mechanizm na swoim koncie, skontaktuj się z dpay.
Kiedy klient zobaczy ofertę DCC?
Oferta DCC pojawia się dopiero po spełnieniu wszystkich poniższych warunków:
- Serwis płatniczy ma aktywne DCC w panelu dpay.
- Karta klienta jest wydana w walucie innej niż waluta sklepu.
- Bramka stwierdziła, że transakcja kwalifikuje się do konwersji.
Jeśli któryś z warunków nie jest spełniony, transakcja przebiega standardowo - frontend dostaje od razu SUCCESS lub FORM (3DS), tak jak dotychczas. Nie musisz nic zmieniać w obsłudze "klasycznego" flow kart.
Schemat działania (state machine)
1. POST /pay/card-otp (encryptedCardData)
│
├── SUCCESS ──────────────────────────────────► koniec (transakcja zaksięgowana)
│
├── FORM (3DS) ──► render HTML ──► powrót z ACS
│ POST /pay/card-otp (threeDsConfirmed: true) ──► SUCCESS
│
└── DCC_OFFER ──► UI wyboru waluty ──► decyzja klienta
POST /pay/card-otp (dccDecision: "accept" | "reject")
│
├── SUCCESS ───────────────────────────────► koniec
│
└── FORM (3DS po DCC) ──► render HTML ──► powrót z ACS
POST /pay/card-otp (threeDsConfirmed: true) ──► SUCCESS
Pierwsze wywołanie - rozpoznanie odpowiedzi
Pierwsze wywołanie POST /api/v1_0/cards/payment/{transactionId}/pay/card-otp jest takie samo jak dotychczas. W odpowiedzi może pojawić się nowy redirectType:
{
"success": true,
"status": "success",
"message": {
"redirectType": "DCC_OFFER",
"redirectText": null,
"dccOffer": {
"currencyConversionId": "00509166251006151007",
"originalAmount": 3.00,
"originalCurrency": "EUR",
"convertedAmount": 13.52,
"convertedCurrency": "PLN",
"exchangeRate": 4.507968,
"validUntil": "2026-05-03T17:40:07+00:00",
"declarationText": "Make sure you understand the costs of currency conversions...",
"markup": [
{ "rate": 6.0, "additionalInfo": "Mastercard" }
],
"europeanEconomicArea": true
}
}
}
Pola dccOffer
| Pole | Typ | Opis |
|---|---|---|
currencyConversionId | string | Identyfikator oferty (przyda się przy korelacji ze wsparciem dpay) |
originalAmount | float | Kwota w walucie sklepu |
originalCurrency | string (ISO 4217) | Waluta sklepu |
convertedAmount | float | Kwota w walucie karty (z wbudowaną marżą) |
convertedCurrency | string (ISO 4217) | Waluta karty |
exchangeRate | float | Kurs konwersji (zawiera marżę) |
validUntil | string (ISO 8601) | Moment wygaśnięcia oferty |
declarationText | string | Tekst regulacyjny PSD2 - pokaż klientowi dosłownie, nie tłumacz |
markup | array | Tablica obiektów { rate, additionalInfo } - marża sieci kartowej |
europeanEconomicArea | bool | Czy karta wydana w EEA |
Wymagania UI - co musisz pokazać klientowi
Prezentacja oferty DCC jest regulowana przez PSD2 i CBPR2. Niezbędne elementy:
- Dwie kwoty side-by-side -
originalAmount+originalCurrencyvsconvertedAmount+convertedCurrency. declarationTextw pełnej formie - tekst regulacyjny przekazany przez bramkę. Nie skracaj i nie tłumacz.- Marża sieci kartowej (
markup) - np. "Konwersja zawiera marżę 6% (Mastercard)". Pole jest tablicą - obsłuż wiele wpisów. - Countdown do
validUntil- po wygaśnięciu zablokuj przyciski lub wymuś powrót na ekran metody płatności. - Dwa przyciski wyboru:
- "Zapłać w {originalCurrency}" → wyśle
dccDecision: "reject" - "Zapłać w {convertedCurrency}" → wyśle
dccDecision: "accept"
- "Zapłać w {originalCurrency}" → wyśle
Decyzję musi świadomie wybrać posiadacz karty - regulator wymaga, aby klient widział obie kwoty i jawnie wybrał walutę przed dokończeniem płatności.
Drugie wywołanie - wysłanie decyzji
Po kliknięciu wybranej waluty wykonaj ponownie ten sam endpoint z dotychczasowym payloadem + nowe pole dccDecision:
POST /api/v1_0/cards/payment/{transactionId}/pay/card-otp
Content-Type: application/json
{
"channelId": 31,
"encryptedCardData": "<ten sam payload co przy 1. wywołaniu>",
"deviceInfo": { ... },
"email": "...",
"dccDecision": "accept"
}
| Pole | Typ | Wartości | Opis |
|---|---|---|---|
dccDecision | string | "accept" / "reject" | Decyzja posiadacza karty |
encryptedCardData | string | - | Ten sam zaszyfrowany payload co w pierwszym wywołaniu |
Frontend trzyma encryptedCardData w pamięci między 1. a 2. wywołaniem - dokładnie tak samo jak przy challenge 3DS, gdzie po powrocie z ACS wysyłasz threeDsConfirmed: true z tym samym zaszyfrowanym payloadem.
Możliwe odpowiedzi po decyzji
SUCCESS- transakcja zaksięgowana (najczęstszy przypadek)FORM- bramka wymaga jeszcze 3DS challenge po DCC. Wyrenderuj HTML, po powrocie z ACS wywołaj endpoint zthreeDsConfirmed: true+ ten samencryptedCardData
Błędy biznesowe DCC
Komunikat (message) | HTTP | Przyczyna | Akcja frontu |
|---|---|---|---|
DCC_OFFER_EXPIRED | 400 | Decyzja wysłana po validUntil | Pokaż "Oferta wygasła, spróbuj ponownie", wróć do ekranu metody płatności (nowy flow od początku) |
INVALID_FLOW_STATE | 400 | dccDecision wysłane bez wcześniejszej oferty (np. na zfinalizowanej transakcji) | Błąd techniczny - log + redirect na error screen |
DCC_PROVIDER_ERROR | 502 | Błąd procesora kart przy aktualizacji decyzji DCC | Błąd techniczny - error screen |
Pełny przykład - obsługa wszystkich gałęzi (Node.js)
async function handleCardPaymentResponse(response, retryWith) {
const { redirectType, redirectText, dccOffer } = response.message;
switch (redirectType) {
case 'SUCCESS':
window.location.href = '/sukces';
return;
case 'FORM':
renderHtmlAndAutoSubmit(atob(redirectText));
return;
case 'DCC_OFFER':
const decision = await showDccOfferUI(dccOffer);
const next = await retryWith({ dccDecision: decision });
return handleCardPaymentResponse(next, retryWith);
case 'URL':
window.location.href = redirectText;
return;
default:
throw new Error(`Unknown redirectType: ${redirectType}`);
}
}
Testowanie
Pełna lista testowych PAN-ów do scenariuszy DCC dostępna na stronie Środowisko testowe. W skrócie:
| PAN | Scenariusz |
|---|---|
5346930000008110 | Standardowa oferta DCC (EUR→PLN, ważność 30 minut) |
5346930000008128 | Karta nie kwalifikuje się do DCC - frontend dostaje od razu SUCCESS |
5346930000008136 | Oferta DCC z ważnością 60 sekund - test wygaśnięcia |
5346930000008144 | Oferta DCC z dodatkowym 3DS po decyzji - kaskada DCC_OFFER → FORM → SUCCESS |
Co się NIE zmienia
- Standardowy success path (karta lokalna, bez DCC, bez 3DS) - bez zmian, działa jak dotychczas
- 3DS challenge bez DCC - bez zmian, ten sam flow, ten sam shape
FORM encryptedCardData,deviceInfo,email,channelId- bez zmian- Endpoint, autoryzacja, format kluczy RSA - bez zmian