Przejdź do głównej zawartości

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

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:

OpcjaTypDomyślnieOpis
langCardLang'pl'Język interfejsu: pl, en, uk, de, fr, es, it, pt, nl, cs, sk, ro, hu, el, sv, da, nb, fi, bg, hr.
styleCardStyleConfigBranding wewnątrz pola. Zob. Stylowanie.
floatingLabelsbooleantrueEtykiety 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.
disabledbooleanfalseRenderuje pola jako wyłączone (np. w trakcie finalizacji zamówienia).
showErrorsbooleantruePokazuje 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.
iframeStylePartial<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',
},
},
});
GrupaWłaściwościUwagi
basicfontColor, fontSize, fontFamily, fontWeight, letterSpacing, lineHeightBazowy wygląd tekstu w polach.
placeholder, focus, invalid, disabledfontColor, fontWeightStany pola.
labelfontColor, fontSize, fontWeight, letterSpacing, textTransformUnosząca się etykieta (compact/stacked) lub etykieta nad każdym polem (form). textTransform: none, uppercase, capitalize.
errorfontColor, fontSize, fontWeightKomunikat walidacji pod pudełkiem lub pod każdym polem w układzie form.
boxbackground, borderColor, borderColorFocus, borderColorInvalid, borderWidth, radius, gapgap 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):

RodzajZasadaPrzykłady
Kolory6-cyfrowy hex#1f2937
Rozmiaryliczba z px, em lub %16px, 1.1em
fontFamilylitery, cyfry, spacje, przecinki, myślniki i cudzysłowy, do 100 znaków'Inter, "Helvetica Neue", sans-serif'
fontWeight100900, normal, bold, lighter, bolder, inherit, initial, unset'500'
letterSpacingnormal albo rozmiar ze znakiem'0.5px', '-0.02em'
lineHeightnormal, 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):

KodPoleZnaczenie
INVALID_NUMBERnumberNumer karty nie przechodzi kontroli długości lub sumy Luhna.
INVALID_EXPIRYexpiryData nie ma formatu MM/YY albo miesiąc jest niepoprawny.
EXPIRED_CARDexpiryData ważności jest w przeszłości.
INVALID_CVCcvcKod 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

Co dalej