Skip to main content

Údaje karty

Kartové platby prebiehajú prostredníctvom zabezpečených vstupných komponentov — Secure Fields. Tieto komponenty zabezpečujú bezpečné zadávanie, validáciu a ochranu údajov karty. Hostiteľská aplikácia nikdy nemá prístup k citlivým údajom v nechránenej podobe.

Secure Fields

Knižnica poskytuje štyri komponenty na zadávanie údajov v platobnom formulári:

SecurePanField

Pole na zadanie čísla karty (PAN — Primary Account Number).

  • Prijíma iba číslice
  • Automaticky deteguje kartovú sieť (Visa, Mastercard)
  • Formátuje číslo karty s medzerami podľa kartovej siete (napr. 4111 1111 1111 1111)
  • Obmedzuje dĺžku vstupu podľa detegovanej kartovej siete
  • Validuje číslo karty pomocou Luhnovho algoritmu
  • Sprístupňuje vlastnosť last4 — posledné 4 číslice karty (bezpečné na zobrazenie v UI)
import SwiftUI
import ComgateSDK

@StateObject private var panState = SecurePanFieldState()

var body: some View {
SecurePanField(state: panState)
}

SecureExpiryField

Pole na zadanie dátumu exspirácie karty.

  • Prijíma iba číslice
  • Automaticky formátuje vstup v tvare MM/RR
  • Validuje mesiac (1–12) a kontroluje, či karta nie je po platnosti
@StateObject private var expiryState = SecureExpiryFieldState()

SecureExpiryField(state: expiryState)

SecureCvvField

Pole na zadanie bezpečnostného kódu karty (CVV/CVC).

  • Prijíma iba číslice
  • Dĺžka je 3 číslice
  • Validuje minimálnu požadovanú dĺžku
@StateObject private var cvvState = SecureCvvFieldState()

SecureCvvField(state: cvvState)

SecureFullNameField

Pole na zadanie mena a priezviska platiteľa.

  • Prijíma textový vstup (vrátane medzier)
  • Je platné pri neprázdnej hodnote
  • Po pripojení k ComgateSecureSession sa jeho hodnota automaticky použije ako fullName
@StateObject private var fullNameState = SecureFullNameFieldState()

SecureFullNameField(state: fullNameState)
.onAppear { fullNameState.attachTo(session) }
.onDisappear { fullNameState.detachFrom(session) }
Informácie

PaymentParams.fullName má vždy prednosť pred hodnotou z SecureFullNameField — ak je fullName v PaymentParams vyplnený, pole SecureFullNameField nie je vyžadované. Ak je fullName prázdny, použije sa hodnota z pripojeného poľa. Ak je prázdne oboje, platba zlyhá s chybou MISSING_CARDHOLDER_NAME.

Spoločné vlastnosti

Všetky stavy Secure Fields (SecurePanFieldState, SecureExpiryFieldState, SecureCvvFieldState, SecureFullNameFieldState) zdieľajú tieto vlastnosti a metódy:

Vlastnosť / MetódaTypPopis
isValidBoolAktuálny stav validácie poľa (read-only, @Published).
errorTextString?Aktuálna chybová správa — lokalizovaná podľa translation.
translationTranslationPreklady použité pre placeholder a chybové správy. Nastavte na session.translation pre zhodu so session.
onFieldError((String?) -> Void)?Callback volaný pri zmene chybového stavu.
clear()Vymaže obsah poľa a resetuje validáciu.
requestFocus()@discardableResult BoolProgramovo zaostrí pole.

Označenia vstupných polí

Každé pole má označenie nad vstupom. Predvolený text označenia sa berie z translation. Pre vlastné označenie nastavte zodpovedajúci kľúč v Translation:

panState.translation = Translation(panLabel: "Číslo platobnej karty")

SecureCardDataCollector

SecureCardDataCollector prepája tri samostatné Secure Fields a sleduje ich celkový validačný stav. Je vyžadovaný na spracovanie platby. Vytvorte ho raz pomocou factory funkcie secureCardDataCollector(pan:expiry:cvv:) a uchovajte v @StateObject ObservableObject obale, aby prežil rerendery view:

@MainActor
final class CollectorHolder: ObservableObject {
let collector: SecureCardDataCollector
init(pan: SecurePanFieldState, expiry: SecureExpiryFieldState, cvv: SecureCvvFieldState) {
self.collector = secureCardDataCollector(pan: pan, expiry: expiry, cvv: cvv)
}
}

struct PaymentForm: View {
@StateObject private var panState: SecurePanFieldState
@StateObject private var expiryState: SecureExpiryFieldState
@StateObject private var cvvState: SecureCvvFieldState
@StateObject private var holder: CollectorHolder

init() {
let pan = SecurePanFieldState()
let expiry = SecureExpiryFieldState()
let cvv = SecureCvvFieldState()
_panState = StateObject(wrappedValue: pan)
_expiryState = StateObject(wrappedValue: expiry)
_cvvState = StateObject(wrappedValue: cvv)
_holder = StateObject(wrappedValue: CollectorHolder(pan: pan, expiry: expiry, cvv: cvv))
}

private var collector: SecureCardDataCollector { holder.collector }
}
Upozornenie

Nepoužívajte var collector: SecureCardDataCollector { secureCardDataCollector(...) } ako computed property — vytváral by nový collector pri každom renderi, čím by sa stratil focus advance aj validačný stav.

Vlastnosť / MetódaTypPopis
isValidBooltrue ak sú všetky tri polia platné.
autoAdvanceFocusBoolZapne/vypne automatický presun fokusu medzi poľami (PAN → Exspirácia → CVV). Predvolene: true.
onValidationChanged((Bool) -> Void)?Callback volaný pri zmene validačného stavu ktoréhokoľvek poľa.

Posun fokusu medzi poľami

Kolektor v predvolenom stave automaticky presúva fokus medzi poľami v poradí:

PAN → Exspirácia → CVV

K presunu dôjde vo chvíli, keď je aktuálne pole kompletne vyplnené a platné. Ak chcete fokus riadiť sami, vypnite automatiku:

let collector = secureCardDataCollector(pan: panState, expiry: expiryState, cvv: cvvState)
collector.autoAdvanceFocus = false

SecurePayButton

SecurePayButton je predpripravené platobné tlačidlo, ktoré sa automaticky integruje so session a kolektorom:

  • Automaticky sa aktivuje/deaktivuje podľa validačného stavu polí karty
  • Tlačidlo zostane deaktivované, kým nie je session úspešne inicializovaná (session.state == .ready)
  • Po kliknutí spustí spracovanie platby prostredníctvom ComgateSecureSession
  • Počas spracovania zobrazuje shimmer animáciu (možno vypnúť v PayButtonStyle)
  • Vracia výsledok platby cez onResult callback

Nastavenie tlačidla

SecurePayButton(
session: session,
collector: collector,
paymentParams: {
try! PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "EUR",
country: "SK",
label: "Názov platby",
refId: "ref-123",
fullName: "Ján Novák",
billingAddrCity: "Bratislava",
billingAddrStreet: "Hlavná 10",
billingAddrPostalCode: "81101",
billingAddrCountry: "SK"
)
},
onResult: { result in
// Spracovanie výsledku platby
}
)

Parameter paymentParams je closure, ktorá je zavolaná v okamihu kliknutia na tlačidlo. Vďaka tomu možno dynamicky čítať aktuálne hodnoty z UI (napr. sumu z textového poľa).

PaymentParams

ParameterTypPovinnýPopis
emailStringÁnoE-mailová adresa platiteľa.
priceIntÁnoSuma platby v centoch (napr. 10000 = 100,00 EUR). Musí byť kladné celé číslo.
currStringÁnoKód meny — ISO 4217 (napr. "EUR", "CZK"). Pozri PaymentParams.supportedCurrencies pre úplný zoznam.
labelStringÁnoKrátky popis produktu (1–16 znakov).
refIdStringÁnoVariabilný symbol alebo číslo objednávky (vaše interné ID).
fullNameStringPodmienečneMeno a priezvisko platiteľa. Ak je neprázdny, má prednosť pred hodnotou z SecureFullNameField.
countryStringNieKód krajiny podľa ISO 3166-1 alpha-2 (napr. "SK", "CZ"). Predvolene: "CZ".
accountString?NieIdentifikátor bankového účtu klienta v systéme Comgate.
nameString?NieIdentifikátor produktu (zobrazí sa v dennom CSV ako „Produkt").
preauthBool?NieOznačí platbu ako predautorizáciu. Nemožno kombinovať s initRecurring = true.
initRecurringBool?NieOznačí platbu ako prvú v sérii opakovaných platieb. Nemožno kombinovať s preauth = true.
billingAddrCityString?NieFakturačná adresa — mesto.
billingAddrStreetString?NieFakturačná adresa — ulica.
billingAddrPostalCodeString?NieFakturačná adresa — PSČ.
billingAddrCountryString?NieFakturačná adresa — kód krajiny (ISO 3166-1 alpha-2).
deliveryString?NieSpôsob doručenia ("HOME_DELIVERY", "PICKUP", "ELECTRONIC_DELIVERY").
homeDeliveryCityString?NieDoručovacia adresa — mesto (iba pri delivery = "HOME_DELIVERY").
homeDeliveryStreetString?NieDoručovacia adresa — ulica (iba pri delivery = "HOME_DELIVERY").
homeDeliveryPostalCodeString?NieDoručovacia adresa — PSČ (iba pri delivery = "HOME_DELIVERY").
homeDeliveryCountryString?NieDoručovacia adresa — kód krajiny (iba pri delivery = "HOME_DELIVERY").
categoryString?NieKategória produktu ("PHYSICAL_GOODS_ONLY", "OTHER").
require3dsBool?NieIba v dev režime (devMode = true). Vynucuje typ 3DS priebehu. V produkcii ignorované.
errorReasonErrorReason?NieIba v dev režime. Simuluje konkrétny dôvod zamietnutia. V produkcii ignorované. Pozri ErrorReason.
Informácie

Konštruktor PaymentParams vyhodí ComgateError.invalidPrice, ak nie je price kladné celé číslo, alebo ComgateError.conflictingPaymentOptions pri kombinácii initRecurring = truepreauth = true.

Podporované meny a krajiny

Podporované meny (PaymentParams.supportedCurrencies)

PaymentParams.supportedCurrencies vracia zoznam ISO 4217 kódov mien prijímaných platobnou bránou Comgate.

BGN, CHF, CZK, DKK, EUR, GBP, HUF, NOK, PLN, RON, SEK, USD

Podporované krajiny (PaymentParams.supportedCountries)

PaymentParams.supportedCountries vracia zoznam ISO 3166-1 alpha-2 kódov krajín prijímaných pre parameter country. Hodnota "ALL" predstavuje žiadne obmedzenie na konkrétnu krajinu.

ALL, AT, BE, CY, CZ, DE, EE, EL, ES, FI, FR, GB, HR, HU, IE, IT, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, US

Príklad naplnenia výberu meny a krajiny

let currencies = PaymentParams.supportedCurrencies
let countries = PaymentParams.supportedCountries

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 10000,
curr: selectedCurrency,
country: selectedCountry,
label: "Objednávka",
refId: "order-123",
fullName: "Ján Novák"
)

Opakované platby a predautorizácia

initRecurring

Parameter initRecurring = true označí platbu ako prvú (iniciačnú) transakciu v sérii opakovaných platieb. Banka tým dostane signál, že v budúcnosti budú prebiehať ďalšie automatické platby (napr. predplatné, pravidelné poplatky).

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 9900,
curr: "EUR",
label: "Predplatné",
refId: "sub-001",
fullName: "Ján Novák",
initRecurring: true
)
Informácie

Parameter initRecurring nemožno kombinovať s preauth = true.

preauth

Parameter preauth = true označí platbu ako predautorizáciu — banka dočasne rezervuje požadovanú sumu na karte platiteľa, ale prostriedky nie sú ihneď stiahnuté.

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 50000,
curr: "EUR",
label: "Rezervácia",
refId: "reservation-42",
fullName: "Ján Novák",
preauth: true
)
Informácie

Parameter preauth nemožno kombinovať s initRecurring = true.

Vlastné tlačidlo a priame volanie processPayment

Ak vám predpripravené SecurePayButton nevyhovuje, môžete platbu spustiť priamo volaním metódy session.processPayment().

Tip

Metóda processPayment šifruje údaje karty, odosiela ich na platobnú bránu a v prípade potreby automaticky vykoná 3D Secure autentifikáciu. Citlivé údaje nikdy neopustia knižnicu v nechránenej podobe.

Signatúra metódy

public func processPayment(
collector: SecureCardDataCollector,
params: PaymentParams,
presenter: UIViewController? = nil
) async -> PaymentResult
ParameterTypPopis
collectorSecureCardDataCollectorKolektor prepájajúci Secure Fields (PAN, exspirácia, CVV).
paramsPaymentParamsParametre platby.
presenterUIViewController?Voliteľný presenter pre 3DS challenge UI. Ak nil, knižnica použije aktuálny top view controller.

Príklad implementácie

struct CustomPaymentScreen: View {
@ObservedObject var session: ComgateSecureSession

@StateObject private var panState: SecurePanFieldState
@StateObject private var expiryState: SecureExpiryFieldState
@StateObject private var cvvState: SecureCvvFieldState
@StateObject private var holder: CollectorHolder
@State private var isProcessing = false
@State private var resultText = ""

init(session: ComgateSecureSession) {
self._session = ObservedObject(wrappedValue: session)
let pan = SecurePanFieldState()
let expiry = SecureExpiryFieldState()
let cvv = SecureCvvFieldState()
_panState = StateObject(wrappedValue: pan)
_expiryState = StateObject(wrappedValue: expiry)
_cvvState = StateObject(wrappedValue: cvv)
_holder = StateObject(wrappedValue: CollectorHolder(pan: pan, expiry: expiry, cvv: cvv))
}

private var collector: SecureCardDataCollector { holder.collector }

var body: some View {
VStack(spacing: 12) {
SecurePanField(state: panState)
SecureExpiryField(state: expiryState)
SecureCvvField(state: cvvState)

Button(isProcessing ? "Spracovávam…" : "Zaplatiť") {
Task {
isProcessing = true
let params = try! PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "EUR",
country: "SK",
label: "Objednávka #123",
refId: "order-123",
fullName: "Ján Novák"
)
let result = await session.processPayment(collector: collector, params: params)
isProcessing = false

switch result {
case .paid(let t): resultText = "Zaplatené (\(t))"
case .authorized(let t): resultText = "Autorizované (\(t))"
case .pending(let t): resultText = "Čaká (\(t))"
case .cancelled(let reason, _): resultText = "Zrušené: \(reason ?? "-")"
case .failed(let err): resultText = "Chyba: \(err.message)"
}
}
}
.disabled(!collector.isValid || isProcessing || session.state != .ready)

Text(resultText)
}
.padding()
}
}
Upozornenie

Pred volaním processPayment sa uistite, že:

  1. Session je úspešne inicializovaná (session.state == .ready).
  2. Údaje karty sú platné (collector.isValid == true).

Ak tieto podmienky nie sú splnené, metóda okamžite vráti .failed(.sessionNotInitialized) alebo .failed(.invalidCardData).

3D Secure

Knižnica poskytuje kompletnú podporu 3D Secure autentifikácie. Ak je 3DS nakonfigurované, knižnica automaticky:

  1. Pripraví autentifikačné parametre pri spracovaní platby
  2. Vyhodnotí odpoveď servera (frictionless / challenge)
  3. V prípade potreby zobrazí challenge obrazovku
  4. Vráti výsledok autentifikácie prostredníctvom PaymentResult

Konfigurácia

3DS sa konfiguruje prostredníctvom štruktúry ThreeDSConfig, ktorá sa odovzdáva do konštruktora ComgateSecureSession:

let threeDSConfig = ThreeDSConfig(
uiCustomization: threeDSUi,
defaultMessageVersion: "2.2.0",
challengeTimeoutMinutes: 5,
challengeWindowCornerRadiusDp: 16
)

let session = ComgateSecureSession(
checkoutId: "váš-checkout-id",
threeDSConfig: threeDSConfig
)

Parametre ThreeDSConfig

ParameterTypPredvolenePopis
uiCustomizationThreeDSUiCustomization?nilPrispôsobenie vzhľadu challenge obrazovky. Ak je nil, použijú sa predvolené štýly.
defaultMessageVersionString"2.2.0"Verzia 3DS protokolu. Podporované hodnoty: pozri ThreeDSConfig.supportedMessageVersions.
challengeTimeoutMinutesInt5Maximálny čas čakania na dokončenie challenge v minútach (1–30).
challengeWindowCornerRadiusDpInt?nilZaoblenie rohov challenge okna (0–64). Ak je nil, použije sa predvolená hodnota.

Prispôsobenie vzhľadu challenge obrazovky

Štruktúra ThreeDSUiCustomization umožňuje detailné prispôsobenie vzhľadu 3DS challenge obrazovky:

let threeDSUi = ThreeDSUiCustomization(
buttons: [
.submit: ThreeDSButtonStyle(
backgroundColor: "#4287F5",
textColor: "#FFFFFF",
textFontSize: 16,
cornerRadius: 48
),
.continue: ThreeDSButtonStyle(
backgroundColor: "#4287F5",
textColor: "#FFFFFF",
textFontSize: 16,
cornerRadius: 48
),
.resend: ThreeDSButtonStyle(
backgroundColor: "#F0F0F0",
textColor: "#4287F5",
textFontSize: 16,
cornerRadius: 48
)
],
labelStyle: ThreeDSLabelStyle(
headingTextColor: "#4287F5",
textColor: "#333333"
),
textBoxStyle: ThreeDSTextBoxStyle(
borderColor: "#4287F5",
borderWidth: 4,
cornerRadius: 16,
textColor: "#333333"
),
toolbarStyle: ThreeDSToolbarStyle(
headerText: "Overenie platby",
buttonText: "Zrušiť",
backgroundColor: "#F0F0F0",
textColor: "#333333"
)
)

ThreeDSButtonStyle

Prispôsobenie tlačidiel na challenge obrazovke.

ParameterTypPopis
backgroundColorString?Farba pozadia vo formáte #RRGGBB alebo #AARRGGBB. Predvolene "#4287F5".
textColorString?Farba textu. Predvolene "#FFFFFF".
textFontSizeInt?Veľkosť písma (8–48).
cornerRadiusInt?Zaoblenie rohov tlačidla (0–64).

Tlačidlá sa konfigurujú v dictionary kľúčovanom typom:

Typ tlačidlaPopis
.submitTlačidlo na odoslanie (potvrdenie).
.continueTlačidlo na pokračovanie.
.nextTlačidlo na ďalší krok.
.cancelTlačidlo na zrušenie.
.resendTlačidlo na opätovné odoslanie kódu.

ThreeDSLabelStyle

Prispôsobenie textových popiskov.

ParameterTypPopis
headingTextColorString?Farba nadpisu.
headingTextFontSizeInt?Veľkosť nadpisu (8–48).
textColorString?Farba bežného textu.
textFontSizeInt?Veľkosť bežného textu (8–48).

ThreeDSTextBoxStyle

Prispôsobenie vstupných polí na challenge obrazovke.

ParameterTypPopis
borderColorString?Farba ohraničenia.
borderWidthInt?Šírka ohraničenia (0–16).
cornerRadiusInt?Zaoblenie rohov (0–64).
textColorString?Farba textu.
textFontSizeInt?Veľkosť textu (8–48).

ThreeDSToolbarStyle

Prispôsobenie toolbaru challenge obrazovky.

ParameterTypPopis
headerTextString?Text v hlavičke toolbaru.
buttonTextString?Text tlačidla v toolbare (typicky „Zrušiť").
backgroundColorString?Farba pozadia toolbaru.
textColorString?Farba textu.
textFontSizeInt?Veľkosť textu (8–48).
Informácie

Farby sa zadávajú ako reťazce vo formáte #RRGGBB alebo #AARRGGBB. Neplatné formáty spôsobia runtime chybu validácie ThreeDSConfigurationError.invalidHexColor.

Testovanie 3DS platieb

Na uľahčenie vývoja a testovania ponúka knižnica v dev režime (devMode = true) možnosť simulovať rôzne priebehy 3DS autentifikácie bez nutnosti použiť skutočnú bankovú kartu.

Testovacie správanie sa riadi parametrom require3dsPaymentParams:

Hodnota require3dsPriebeh platby
trueServer vráti challenge — zobrazí sa 3DS challenge obrazovka, používateľ musí zadať OTP alebo vykonať overenie.
falseServer vráti frictionless výsledok — platba prebehne bez zobrazenia challenge obrazovky.
nil (predvolene)Server rozhodne sám; v produkcii štandardné správanie.
Upozornenie

Parameter require3ds je funkčný výhradne v dev režime (devMode = true). V produkcii (devMode = false) je hodnota tohto parametra ignorovaná a platba prebehne štandardným spôsobom.

Simulácia 3DS challenge

Nastavte require3ds = true. Knižnica zobrazí 3DS challenge obrazovku. Výsledok platby závisí od akcie používateľa:

  • Dokončenie overenia → .paid(transId:)
  • Zrušenie challenge → .failed(.threeDSChallengeCancelled)
  • Vypršanie časového limitu → .failed(.threeDSChallengeTimeout)
let params = try PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "EUR",
label: "Testovacia platba",
refId: "test-001",
fullName: "Ján Novák",
require3ds: true
)

Simulácia frictionless priebehu

Nastavte require3ds = false. Platba prebehne bez zobrazenia challenge obrazovky.

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "EUR",
label: "Testovacia platba",
refId: "test-001",
fullName: "Ján Novák",
require3ds: false
)

Simulácia chybového dôvodu — ErrorReason

dev režime (devMode = true) možno pomocou parametra errorReasonPaymentParams simulovať konkrétny dôvod zamietnutia alebo zlyhania platby.

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "EUR",
label: "Testovacia platba",
refId: "test-001",
fullName: "Ján Novák",
errorReason: .noFunds
)
Upozornenie

Parameter errorReason je funkčný výhradne v dev režime (devMode = true). V produkcii je tento parameter úplne ignorovaný.

Dostupné hodnoty výčtu ErrorReason:

HodnotaRaw valuePopis
.customerClickCUSTOMER_CLICKZrušené platiteľom.
.fraudSuspectedFRAUD_SUSPECTEDPodozrenie na podvod.
.eshopCancelledESHOP_CANCELLEDZrušené obchodníkom.
.providerReportPROVIDER_REPORTZrušené poskytovateľom.
.providerTimeoutPROVIDER_TIMEOUTVypršal časový limit poskytovateľa.
.customerTimeoutCUSTOMER_TIMEOUTVypršal časový limit platby.
.acsTimeoutACS_TIMEOUTVypršal časový limit overenia.
.invalidCardnoExpiryINVALID_CARDNO_EXPIRYChybne zadané číslo karty alebo dátum platnosti karty.
.invalidCvcINVALID_CVCChybne zadaný CVC / CVV kód.
.limitExceededLIMIT_EXCEEDEDLimit karty bol prekročený.
.noFundsNO_FUNDSNa účte nie je dostatočný zostatok.
.rejectedByBankREJECTED_BY_BANKPlatba bola zamietnutá bankou.
.threeDSAuthFail3DS_AUTH_FAILOverenie 3DS nebolo úspešné.
.notSpecifiedNOT_SPECIFIEDNešpecifikované.

Kompletný príklad

Nasledujúci príklad ukazuje kompletnú implementáciu kartovej platby s 3D Secure od inicializácie po spracovanie výsledku:

import SwiftUI
import ComgateSDK

@main
struct CardPaymentApp: App {
@StateObject private var session = ComgateSecureSession(
checkoutId: "váš-checkout-id",
threeDSConfig: ThreeDSConfig(
uiCustomization: ThreeDSUiCustomization(
toolbarStyle: ThreeDSToolbarStyle(
headerText: "Overenie platby",
buttonText: "Zrušiť"
)
),
challengeTimeoutMinutes: 5
)
)

var body: some Scene {
WindowGroup {
PaymentScreen(session: session)
.task {
if case .notInitialized = session.state {
try? await session.initialize()
}
}
}
}
}

struct PaymentScreen: View {
@ObservedObject var session: ComgateSecureSession

@StateObject private var panState: SecurePanFieldState
@StateObject private var expiryState: SecureExpiryFieldState
@StateObject private var cvvState: SecureCvvFieldState
@StateObject private var nameState: SecureFullNameFieldState
@StateObject private var statusState = PaymentStatusState()
@StateObject private var holder: CollectorHolder
@State private var cardInfo = "Vyplňte údaje karty"

init(session: ComgateSecureSession) {
self._session = ObservedObject(wrappedValue: session)
let pan = SecurePanFieldState()
let expiry = SecureExpiryFieldState()
let cvv = SecureCvvFieldState()
let name = SecureFullNameFieldState()
_panState = StateObject(wrappedValue: pan)
_expiryState = StateObject(wrappedValue: expiry)
_cvvState = StateObject(wrappedValue: cvv)
_nameState = StateObject(wrappedValue: name)
_holder = StateObject(wrappedValue: CollectorHolder(pan: pan, expiry: expiry, cvv: cvv))
}

private var collector: SecureCardDataCollector { holder.collector }

var body: some View {
ScrollView {
VStack(spacing: 12) {
Text(cardInfo)

SecureFullNameField(state: nameState)
SecurePanField(state: panState)
SecureExpiryField(state: expiryState)
SecureCvvField(state: cvvState)

SecurePayButton(
session: session,
collector: collector,
paymentParams: {
try! PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "EUR",
country: "SK",
label: "Objednávka #123",
refId: "order-123",
fullName: "Ján Novák",
billingAddrCity: "Bratislava",
billingAddrStreet: "Hlavná 10",
billingAddrPostalCode: "81101",
billingAddrCountry: "SK"
)
},
onResult: { result in
statusState.translation = session.translation
statusState.show(result: result)
}
)

SecurePaymentStatusView(state: statusState)
}
.padding()
}
.onChange(of: panState.isValid) { isValid in
cardInfo = isValid ? "✓ Karta končiaca na \(panState.last4)" : "Vyplňte údaje karty"
}
.onAppear {
panState.translation = session.translation
expiryState.translation = session.translation
cvvState.translation = session.translation
nameState.translation = session.translation
nameState.attachTo(session)
}
.onDisappear { nameState.detachFrom(session) }
.secureLoadingOverlay(session: session)
}
}