Przyjmij płatność kartą w hosted fields
Na tej stronie osadzisz pole karty na swojej stronie, zareagujesz na jego stan i zamienisz dane karty na token, który przekażesz do backendu.
Wymagania
- Wczytane i zainicjalizowane SDK: Instalacja i inicjalizacja.
- Strona serwowana po HTTPS (
localhostliczy się jako bezpieczny).
Krok 1. Zamontuj element karty
<div id="card"></div>
const elements = paymentic.elements(); // rzuca INSECURE_CONTEXT na http://
const card = elements.create('card', { lang: 'pl' });
card.mount('#card'); // selektor albo HTMLElement
Element renderuje jedno hostowane pole z numerem karty, datą ważności i CVC. Startuje z zerową
wysokością i rośnie do zawartości po wczytaniu. Jeśli chcesz uniknąć przesunięcia layoutu, zarezerwuj
miejsce szkieletem przez opcję iframeStyle (niżej).
Krok 2. Reaguj na stan pola
card.on('change', (event) => {
// event.empty boolean — wszystkie pola są puste
// event.complete boolean — wszystkie pola poprawne; można wywołać tokenize()
// event.brand 'VISA' | 'MASTERCARD' | null — wykryty z numeru karty
// event.length number — liczba cyfr wpisanych w numerze karty
// event.error { field, code, message } | null — pierwsze niepoprawne pole
payButton.disabled = !event.complete;
});
Zdarzenie change przychodzi, gdy opisany stan faktycznie się zmienia: podczas pisania, po
opuszczeniu pola i uruchomieniu walidacji (wtedy pojawia się error) oraz po zmianie języka, żeby
zlokalizowany error.message był aktualny. Identyczne kolejne stany nie są emitowane ponownie.
Pełna lista zdarzeń jest w sekcji Zdarzenia.
Krok 3. Tokenizuj kartę
const result = await paymentic.tokenize(card);
if (result.error) {
// result.error.code stabilny kod błędu
// result.error.message komunikat dla człowieka
showError(result.error.message);
return;
}
// result.token token; dla płatności kartą to ten sam JWT
// result.jwt podpisany JWT — TO wysyłasz do backendu
// result.maskedCard np. "450875******1019"
// result.cardBrand 'VISA' | 'MASTERCARD' | null
// result.expiresIn liczba sekund, przez które token można wykorzystać (60)
await fetch('/api/checkout/pay', { method: 'POST', body: JSON.stringify({ token: result.jwt }) });
tokenize() nigdy nie odrzuca promise'a: zawsze zwraca token albo error. Najpierw waliduje
pole, więc wywołanie przy complete === false kończy się kodem INVALID_CARD, a pole pokazuje,
czego brakuje. W danym momencie trwa tylko jedna tokenizacja; drugie wywołanie w trakcie kończy
się kodem IN_PROGRESS. Kody błędów: Błędy i rozwiązywanie problemów.
Token jest ważny 60 sekund i można go użyć raz. Wyślij go do backendu natychmiast i tam
utwórz płatność. Nie przechowuj go. Dla płatności kartą token i jwt niosą ten sam JWT (portfele
zwracają w token nieprzezroczystą referencję, a JWT w jwt). Używaj wszędzie jwt, a różnica
nie będzie miała znaczenia.
Krok 4. Przekaż token do backendu
Wyślij jwt do swojego backendu po HTTPS w ciągu 60 sekund od tokenizacji, razem z imieniem
i nazwiskiem posiadacza karty oraz danymi przeglądarki. Backend realizuje płatność dla utworzonej
wcześniej transakcji przez POST /v1_2/payment/points/{pointId}/transactions/{transactionId}/cards
z tokenType: SDK_CARD, a ewentualny challenge 3-D Secure pokazujesz płatnikowi w checkoucie.
Cały ten krok opisuje strona Zrealizuj płatność tokenem.
Nigdy nie umieszczaj tokenu w URL-u ani w logach.
Opcje elementu
elements.create('card', options) przyjmuje:
| Opcja | Typ | Domyślnie | Opis |
|---|---|---|---|
lang | CardLang | 'pl' | Język interfejsu: pl, en, uk, de, fr, es, it, pt, nl, cs, sk, ro, hu, el, sv, da, nb, fi, bg, hr. |
style | CardStyleConfig | — | Branding wewnątrz pola. Zob. Stylowanie. |
floatingLabels | boolean | true | Etykiety unoszą się nad wartością podczas pisania; false pokazuje nazwę pola jako placeholder. W obu trybach w spoczynku klient widzi nazwę pola, a podpowiedź formatu (1234 1234 1234 1234, MM / YY, CVC) pojawia się tylko w aktywnym polu. |
disabled | boolean | false | Renderuje pola jako wyłączone (np. w trakcie finalizacji zamówienia). |
showErrors | boolean | true | Pokazuje wbudowany komunikat walidacji wewnątrz pola. Ustaw false, żeby renderować błędy samodzielnie na podstawie zdarzenia change; czerwona ramka nadal się pokazuje. |
cvcHelp | 'modal' | 'event' | 'off' | 'modal' | Zachowanie ikony „?" obok CVC: otwórz wbudowane wyjaśnienie SDK, tylko wyemituj zdarzenie cvcHelp (otwierasz własne), albo ukryj ikonę. |
layout | 'compact' | 'stacked' | 'auto' | 'form' | 'compact' | compact: jedno pudełko, data i CVC obok siebie. stacked: jedno pudełko, wiersze na pełną szerokość. auto: compact, gdy etykiety się mieszczą, inaczej stacked (przeliczane przy zmianie rozmiaru i języka). form: trzy osobne pola, każde z własnym komunikatem walidacji. Zob. Układ form. |
iframeStyle | Partial<CSSStyleDeclaration> | — | Style nakładane na element iframe na Twojej stronie (np. { minHeight: '48px' } jako szkielet). |
Każdą opcję możesz zmienić po zamontowaniu przez card.update({ … }).
Stylowanie
Branding jest stosowany wewnątrz iframe przez zwalidowany zestaw właściwości, więc Twoja strona nie może wstrzyknąć dowolnego CSS do bezpiecznego pola (ani odwrotnie).
Wszystkie właściwości stylu, układy i języki możesz przestawiać na żywo w konfiguratorze.
elements.create('card', {
style: {
basic: {
fontFamily: 'Inter, sans-serif',
fontSize: '16px',
fontColor: '#1f2937',
fontWeight: '400',
letterSpacing: '0.2px',
lineHeight: '1.5',
},
placeholder: { fontColor: '#9ca3af' },
focus: { fontColor: '#111827', fontWeight: '500' },
invalid: { fontColor: '#b91c1c' },
disabled: { fontColor: '#9ca3af' },
box: {
background: '#ffffff',
borderColor: '#d1d5db',
borderColorFocus: '#2563eb',
borderColorInvalid: '#dc2626',
borderWidth: '1px',
radius: '8px',
},
},
});
| Grupa | Właściwości | Uwagi |
|---|---|---|
basic | fontColor, fontSize, fontFamily, fontWeight, letterSpacing, lineHeight | Bazowy wygląd tekstu w polach. |
placeholder, focus, invalid, disabled | fontColor, fontWeight | Stany pola. |
label | fontColor, fontSize, fontWeight, letterSpacing, textTransform | Unosząca się etykieta (compact/stacked) lub etykieta nad każdym polem (form). textTransform: none, uppercase, capitalize. |
error | fontColor, fontSize, fontWeight | Komunikat walidacji pod pudełkiem lub pod każdym polem w układzie form. |
box | background, borderColor, borderColorFocus, borderColorInvalid, borderWidth, radius, gap | gap ustawia odstęp między polami w układzie form. |
Zasady wartości (wszystko inne jest po cichu ignorowane i zostaje wbudowany domyślny styl):
| Rodzaj | Zasada | Przykłady |
|---|---|---|
| Kolory | 6-cyfrowy hex | #1f2937 |
| Rozmiary | liczba z px, em lub % | 16px, 1.1em |
fontFamily | litery, cyfry, spacje, przecinki, myślniki i cudzysłowy, do 100 znaków | 'Inter, "Helvetica Neue", sans-serif' |
fontWeight | 100–900, normal, bold, lighter, bolder, inherit, initial, unset | '500' |
letterSpacing | normal albo rozmiar ze znakiem | '0.5px', '-0.02em' |
lineHeight | normal, liczba bez jednostki albo rozmiar | '1.4', '24px' |
Fonty webowe nie są ładowane wewnątrz iframe. fontFamily powinno wskazywać fonty dostępne
w systemie klienta albo generyczny stos.
Układ form
elements.create('card', {
layout: 'form',
style: {
label: { fontColor: '#374151', fontSize: '13px', fontWeight: '600', textTransform: 'uppercase' },
error: { fontColor: '#b91c1c', fontSize: '12px' },
box: { radius: '8px', gap: '16px' },
},
});
Renderuje trzy osobne pola (numer karty, data ważności, CVC), każde z prawdziwą etykietą label
i własnym komunikatem walidacji pod spodem, wszystko wewnątrz iframe. Z domyślnym
floatingLabels: true etykieta leży wewnątrz pola i unosi się nad wartością po aktywacji (pola
w stylu Material). Z floatingLabels: false etykieta leży nad polem, a podpowiedzi formatu
pokazują się jako placeholdery. Data i CVC leżą obok siebie, a na kontenerach węższych niż około
340 px układają się jeden pod drugim. Zdarzenia, tokenize(), showErrors i język działają
dokładnie tak samo jak w pozostałych układach.
Zdarzenia
card.on('ready', (event) => { /* { pointId } — iframe wczytany i skonfigurowany */ });
card.on('change', (event) => { /* opis w kroku 2 */ });
card.on('focus', (event) => { /* { field: 'number' | 'expiry' | 'cvc' } */ });
card.on('blur', (event) => { /* { field: 'number' | 'expiry' | 'cvc' } */ });
card.on('cvcHelp', (event) => { /* { title, body } — zlokalizowany tekst pomocy */ });
on() zwraca element, więc wywołania można łączyć w łańcuch; off(event, handler) usuwa handler.
focus i blur mówią, w którym polu jest klient (np. żeby podświetlić własną etykietę albo
mierzyć, gdzie klienci rezygnują). Nie niosą żadnych wartości.
Żeby renderować błędy samodzielnie:
const card = elements.create('card', { showErrors: false });
card.on('change', ({ error }) => {
errorBox.textContent = error ? error.message : '';
});
Kody błędów pól (event.error.code):
| Kod | Pole | Znaczenie |
|---|---|---|
INVALID_NUMBER | number | Numer karty nie przechodzi kontroli długości lub sumy Luhna. |
INVALID_EXPIRY | expiry | Data nie ma formatu MM/YY albo miesiąc jest niepoprawny. |
EXPIRED_CARD | expiry | Data ważności jest w przeszłości. |
INVALID_CVC | cvc | Kod bezpieczeństwa nie ma 3–4 cyfr. |
Aktualizacja, odmontowanie, zniszczenie
card.update({ lang: 'en', disabled: true }); // dowolna opcja elementu
card.unmount(); // usuwa iframe i listenery; można zamontować ponownie
paymentic.destroy(); // odmontowuje wszystkie elementy, usuwa przyciski portfeli i handlery