Přeskočit na hlavní obsah

Karetní data

Karetní platby probíhají prostřednictvím zabezpečených vstupních komponent — Secure Fields. Tyto komponenty zajišťují bezpečné zadávání, validaci a ochranu karetních údajů. Hostitelská aplikace nikdy nemá přístup k citlivým údajům v nechráněné podobě.

Secure Fields

Knihovna poskytuje čtyři komponenty pro zadávání údajů v platebním formuláři:

SecurePanField

Pole pro zadání čísla karty (PAN — Primary Account Number).

  • Přijímá pouze číslice
  • Automaticky detekuje karetní síť (Visa, Mastercard)
  • Formátuje číslo karty s mezerami dle karetní sítě (např. 4111 1111 1111 1111)
  • Omezuje délku vstupu podle detekované karetní sítě
  • Validuje číslo karty pomocí Luhnova algoritmu
  • Zpřístupňuje vlastnost last4 — poslední 4 číslice karty (bezpečné pro zobrazení v UI)
import SwiftUI
import ComgateSDK

@StateObject private var panState = SecurePanFieldState()

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

SecureExpiryField

Pole pro zadání data expirace karty.

  • Přijímá pouze číslice
  • Automaticky formátuje vstup ve tvaru MM/YY
  • Validuje měsíc (1–12) a kontroluje, zda karta není expirovaná
@StateObject private var expiryState = SecureExpiryFieldState()

SecureExpiryField(state: expiryState)

SecureCvvField

Pole pro zadání bezpečnostního kódu karty (CVV/CVC).

  • Přijímá pouze číslice
  • Délka je 3 číslice
  • Validuje minimální požadovanou délku
@StateObject private var cvvState = SecureCvvFieldState()

SecureCvvField(state: cvvState)

SecureFullNameField

Pole pro zadání jména a příjmení plátce.

  • Přijímá textový vstup (včetně mezer)
  • Je validní při neprázdné hodnotě
  • Po připojení k ComgateSecureSession se jeho hodnota automaticky použije jako fullName
@StateObject private var fullNameState = SecureFullNameFieldState()

SecureFullNameField(state: fullNameState)
.onAppear { fullNameState.attachTo(session) }
.onDisappear { fullNameState.detachFrom(session) }
Informace

PaymentParams.fullName má vždy přednost před hodnotou z SecureFullNameField — pokud je fullName v PaymentParams vyplněný, pole SecureFullNameField není vyžadováno. Pokud je fullName prázdný, použije se hodnota z připojeného pole. Je-li prázdné obojí, platba selže s chybou MISSING_CARDHOLDER_NAME.

Společné vlastnosti

Všechny stavy Secure Fields (SecurePanFieldState, SecureExpiryFieldState, SecureCvvFieldState, SecureFullNameFieldState) sdílejí tyto vlastnosti a metody:

Vlastnost / MetodaTypPopis
isValidBoolAktuální stav validace pole (read-only, @Published).
errorTextString?Aktuální chybová zpráva — lokalizovaná dle translation.
translationTranslationPřeklady použité pro placeholder a chybové zprávy. Nastavte na session.translation pro shodu se session.
onFieldError((String?) -> Void)?Callback volaný při změně chybového stavu.
clear()Vymaže obsah pole a resetuje validaci.
requestFocus()@discardableResult BoolProgramaticky zaměří pole.

Labely vstupních polí

Každé pole má label nad vstupem. Výchozí text labelu se bere z translation. Pro vlastní label nastavte odpovídající klíč v Translation:

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

SecureCardDataCollector

SecureCardDataCollector propojuje tři samostatná Secure Fields a sleduje jejich celkový validační stav. Je vyžadován pro zpracování platby. Vytvořte ho jednou pomocí factory funkce secureCardDataCollector(pan:expiry:cvv:) a uchovejte v @StateObject ObservableObject obalu, aby přež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 }
}
Upozornění

Nepoužívejte var collector: SecureCardDataCollector { secureCardDataCollector(...) } jako computed property — vytvářel by nový collector při každém renderu, čímž by se ztratil focus advance i validační stav.

Vlastnost / MetodaTypPopis
isValidBooltrue pokud jsou všechna tři pole validní.
autoAdvanceFocusBoolZapne/vypne automatický přesun focusu mezi poli (PAN → Expirace → CVV). Výchozí: true.
onValidationChanged((Bool) -> Void)?Callback volaný při změně validačního stavu kteréhokoliv pole.

Předávání focusu mezi poli

Kolektor ve výchozím stavu automaticky předává focus mezi poli v pořadí:

PAN → Expirace → CVV

K přesunu dojde ve chvíli, kdy je aktuální pole kompletně vyplněné a validní. Pokud chcete focus řídit sami, vypněte automatiku:

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

SecurePayButton

SecurePayButton je předpřipravené platební tlačítko, které se automaticky integruje se session a kolektorem:

  • Automaticky se aktivuje/deaktivuje podle validačního stavu karetních polí
  • Tlačítko zůstane deaktivované, dokud není session úspěšně inicializována (session.state == .ready)
  • Po kliknutí zahájí zpracování platby prostřednictvím ComgateSecureSession
  • Během zpracování zobrazuje shimmer animaci (lze vypnout v PayButtonStyle)
  • Vrací výsledek platby přes onResult callback

Nastavení tlačítka

SecurePayButton(
session: session,
collector: collector,
paymentParams: {
try! PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "CZK",
country: "CZ",
label: "Název platby",
refId: "ref-123",
fullName: "Jan Novák",
billingAddrCity: "Hradec Králové",
billingAddrStreet: "Jiráskova 115",
billingAddrPostalCode: "50304",
billingAddrCountry: "CZ"
)
},
onResult: { result in
// Zpracování výsledku platby
}
)

Parametr paymentParams je closure, která je zavolána v okamžiku kliknutí na tlačítko. Díky tomu lze dynamicky číst aktuální hodnoty z UI (např. částku z textového pole).

PaymentParams

ParametrTypPovinnýPopis
emailStringAnoE-mailová adresa plátce.
priceIntAnoČástka platby v haléřích/centech (např. 10000 = 100,00 CZK). Musí být kladné celé číslo.
currStringAnoKód měny — ISO 4217 (např. "CZK", "EUR"). Viz PaymentParams.supportedCurrencies pro úplný seznam.
labelStringAnoKrátký popis produktu (1–16 znaků).
refIdStringAnoVariabilní symbol nebo číslo objednávky (vaše interní ID).
fullNameStringPodmíněněJméno a příjmení plátce. Pokud je neprázdný, má přednost před hodnotou z SecureFullNameField. Pokud je prázdný, použije se hodnota z připojeného SecureFullNameField (tehdy povinného).
countryStringNeKód země dle ISO 3166-1 alpha-2 (např. "CZ", "SK"). Výchozí: "CZ".
accountString?NeIdentifikátor bankovního účtu klienta v systému Comgate.
nameString?NeIdentifikátor produktu (zobrazí se v denním CSV jako „Produkt").
preauthBool?NeOznačí platbu jako předautorizaci. Nelze kombinovat s initRecurring = true.
initRecurringBool?NeOznačí platbu jako první v sérii opakovaných plateb. Nelze kombinovat s preauth = true.
billingAddrCityString?NeFakturační adresa — město.
billingAddrStreetString?NeFakturační adresa — ulice.
billingAddrPostalCodeString?NeFakturační adresa — PSČ.
billingAddrCountryString?NeFakturační adresa — kód země (ISO 3166-1 alpha-2).
deliveryString?NeZpůsob doručení ("HOME_DELIVERY", "PICKUP", "ELECTRONIC_DELIVERY").
homeDeliveryCityString?NeDoručovací adresa — město (jen při delivery = "HOME_DELIVERY").
homeDeliveryStreetString?NeDoručovací adresa — ulice (jen při delivery = "HOME_DELIVERY").
homeDeliveryPostalCodeString?NeDoručovací adresa — PSČ (jen při delivery = "HOME_DELIVERY").
homeDeliveryCountryString?NeDoručovací adresa — kód země (jen při delivery = "HOME_DELIVERY").
categoryString?NeKategorie produktu ("PHYSICAL_GOODS_ONLY", "OTHER").
require3dsBool?NePouze v dev režimu (devMode = true). Vynucuje typ 3DS průběhu. V produkci ignorováno.
errorReasonErrorReason?NePouze v dev režimu. Simuluje konkrétní důvod zamítnutí. V produkci ignorováno. Viz ErrorReason.
Informace

Konstruktor PaymentParams vyhodí ComgateError.invalidPrice, pokud není price kladné celé číslo, nebo ComgateError.conflictingPaymentOptions při kombinaci initRecurring = truepreauth = true.

Podporované měny a země

Podporované měny (PaymentParams.supportedCurrencies)

PaymentParams.supportedCurrencies vrací seznam ISO 4217 kódů měn přijímaných platební bránou Comgate. Tento seznam lze využít například k naplnění výběru měny v UI.

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

Podporované země (PaymentParams.supportedCountries)

PaymentParams.supportedCountries vrací seznam ISO 3166-1 alpha-2 kódů zemí přijímaných pro parametr country. Hodnota "ALL" reprezentuje žádné omezení na konkrétní zemi.

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

Příklad použití pro naplnění výběru měny a země

// Dostupné měny pro výběr v UI
let currencies = PaymentParams.supportedCurrencies // ["BGN", "CHF", "CZK", ...]

// Dostupné země pro výběr v UI
let countries = PaymentParams.supportedCountries // ["ALL", "AT", "BE", "CY", "CZ", ...]

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

Opakované platby a předautorizace

initRecurring

Parametr initRecurring = true označí platbu jako první (iniciační) transakci v sérii opakovaných plateb. Banka tím dostane signál, že v budoucnu budou probíhat další automatické platby (např. předplatné, pravidelné poplatky).

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 9900,
curr: "CZK",
label: "Předplatné",
refId: "sub-001",
fullName: "Jan Novák",
initRecurring: true
)
Informace

Parametr initRecurring nelze kombinovat s preauth = true.

preauth

Parametr preauth = true označí platbu jako předautorizaci — banka dočasně rezervuje požadovanou částku na kartě plátce, ale prostředky nejsou ihned strženy. K zachycení (stržení) dochází až při potvrzení transakce na straně backendu.

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 50000,
curr: "CZK",
label: "Rezervace",
refId: "reservation-42",
fullName: "Jan Novák",
preauth: true
)
Informace

Parametr preauth nelze kombinovat s initRecurring = true.

Vlastní tlačítko a přímé volání processPayment

Pokud vám předpřipravené SecurePayButton nevyhovuje, můžete platbu spustit přímo voláním metody session.processPayment().

Tip

Metoda processPayment šifruje karetní data, odesílá je na platební bránu a v případě potřeby automaticky provede 3D Secure autentizaci. Citlivé údaje nikdy neopustí knihovnu v nechráněné podobě.

Signatura metody

public func processPayment(
collector: SecureCardDataCollector,
params: PaymentParams,
presenter: UIViewController? = nil
) async -> PaymentResult
ParametrTypPopis
collectorSecureCardDataCollectorKolektor propojující Secure Fields (PAN, expirace, CVV).
paramsPaymentParamsParametry platby (cena, měna, popis, refId, jméno plátce aj.).
presenterUIViewController?Volitelný presenter pro 3DS challenge UI. Pokud nil, knihovna použije aktuální top view controller.

Příklad implementace

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 ? "Zpracování…" : "Zaplatit") {
Task {
isProcessing = true
let params = try! PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "CZK",
country: "CZ",
label: "Objednávka #123",
refId: "order-123",
fullName: "Jan Novák"
)
let result = await session.processPayment(collector: collector, params: params)
isProcessing = false

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

Text(resultText)
}
.padding()
}
}
Upozornění

Před voláním processPayment se ujistěte, že:

  1. Session je úspěšně inicializovaná (session.state == .ready).
  2. Karetní data jsou validní (collector.isValid == true).

Pokud tyto podmínky nejsou splněny, metoda okamžitě vrátí .failed(.sessionNotInitialized) nebo .failed(.invalidCardData).

3D Secure

Knihovna poskytuje kompletní podporu 3D Secure autentizace. Pokud je 3DS nakonfigurováno, knihovna automaticky:

  1. Připraví autentizační parametry při zpracování platby
  2. Vyhodnotí odpověď serveru (frictionless / challenge)
  3. V případě potřeby zobrazí challenge obrazovku
  4. Vrátí výsledek autentizace prostřednictvím PaymentResult

Konfigurace

3DS se konfiguruje prostřednictvím struktury ThreeDSConfig, která se předává do konstruktoru ComgateSecureSession:

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

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

Parametry ThreeDSConfig

ParametrTypVýchozíPopis
uiCustomizationThreeDSUiCustomization?nilPřizpůsobení vzhledu challenge obrazovky. Pokud je nil, použijí se výchozí styly.
defaultMessageVersionString"2.2.0"Verze 3DS protokolu. Podporované hodnoty: viz ThreeDSConfig.supportedMessageVersions.
challengeTimeoutMinutesInt5Maximální doba čekání na dokončení challenge v minutách (1–30).
challengeWindowCornerRadiusDpInt?nilZaoblení rohů challenge okna (0–64). Pokud je nil, použije se výchozí hodnota.

Přizpůsobení vzhledu challenge obrazovky

Struktura ThreeDSUiCustomization umožňuje detailní přizpůsobení vzhledu 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: "Ověření platby",
buttonText: "Zrušit",
backgroundColor: "#F0F0F0",
textColor: "#333333"
)
)

ThreeDSButtonStyle

Přizpůsobení tlačítek na challenge obrazovce.

ParametrTypPopis
backgroundColorString?Barva pozadí ve formátu #RRGGBB nebo #AARRGGBB. Výchozí "#4287F5".
textColorString?Barva textu ve formátu #RRGGBB nebo #AARRGGBB. Výchozí "#FFFFFF".
textFontSizeInt?Velikost písma (8–48).
cornerRadiusInt?Zaoblení rohů tlačítka (0–64).

Tlačítka se konfigurují v dictionary klíčovaném typem:

Typ tlačítkaPopis
.submitTlačítko pro odeslání (potvrzení).
.continueTlačítko pro pokračování.
.nextTlačítko pro další krok.
.cancelTlačítko pro zrušení.
.resendTlačítko pro opětovné odeslání kódu.

ThreeDSLabelStyle

Přizpůsobení textových popisků.

ParametrTypPopis
headingTextColorString?Barva nadpisu.
headingTextFontSizeInt?Velikost nadpisu (8–48).
textColorString?Barva běžného textu.
textFontSizeInt?Velikost běžného textu (8–48).

ThreeDSTextBoxStyle

Přizpůsobení vstupních polí na challenge obrazovce.

ParametrTypPopis
borderColorString?Barva ohraničení.
borderWidthInt?Šířka ohraničení (0–16).
cornerRadiusInt?Zaoblení rohů (0–64).
textColorString?Barva textu.
textFontSizeInt?Velikost textu (8–48).

ThreeDSToolbarStyle

Přizpůsobení toolbaru challenge obrazovky.

ParametrTypPopis
headerTextString?Text v hlavičce toolbaru.
buttonTextString?Text tlačítka v toolbaru (typicky „Zrušit").
backgroundColorString?Barva pozadí toolbaru.
textColorString?Barva textu.
textFontSizeInt?Velikost textu (8–48).
Informace

Barvy se zadávají jako řetězce ve formátu #RRGGBB nebo #AARRGGBB. Neplatné formáty způsobí runtime chybu validace ThreeDSConfigurationError.invalidHexColor.

Testování 3DS plateb

Pro usnadnění vývoje a testování nabízí knihovna v dev režimu (devMode = true) možnost simulovat různé průběhy 3DS autentizace bez nutnosti použít skutečnou bankovní kartu.

Testovací chování se řídí parametrem require3dsPaymentParams:

Hodnota require3dsPrůběh platby
trueServer vrátí challenge — zobrazí se 3DS challenge obrazovka, uživatel musí zadat OTP nebo provést ověření.
falseServer vrátí frictionless výsledek — platba proběhne bez zobrazení challenge obrazovky.
nil (výchozí)Server rozhodne sám; v produkci standardní chování.
Upozornění

Parametr require3ds je funkční výhradně v dev režimu (devMode = true). V produkci (devMode = false) je hodnota tohoto parametru ignorována a platba proběhne standardním způsobem.

Simulace 3DS challenge

Nastavte require3ds = true. Knihovna zobrazí 3DS challenge obrazovku, kde uživatel provede ověření. Výsledek platby bude záviset na akci uživatele:

  • Dokončení ověření → .paid(transId:)
  • Zrušení challenge → .failed(.threeDSChallengeCancelled)
  • Vypršení časového limitu → .failed(.threeDSChallengeTimeout)
let params = try PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "CZK",
label: "Testovací platba",
refId: "test-001",
fullName: "Jan Novák",
require3ds: true
)

Simulace frictionless průběhu

Nastavte require3ds = false. Platba proběhne bez zobrazení challenge obrazovky.

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "CZK",
label: "Testovací platba",
refId: "test-001",
fullName: "Jan Novák",
require3ds: false
)

Simulace chybového důvodu — ErrorReason

dev režimu (devMode = true) lze pomocí parametru errorReasonPaymentParams simulovat konkrétní důvod zamítnutí nebo selhání platby.

let params = try PaymentParams(
email: "zakaznik@example.com",
price: 100,
curr: "CZK",
label: "Testovací platba",
refId: "test-001",
fullName: "Jan Novák",
errorReason: .noFunds
)
Upozornění

Parametr errorReason je funkční výhradně v dev režimu (devMode = true). V produkci je tento parametr zcela ignorován.

Dostupné hodnoty výčtu ErrorReason:

HodnotaRaw valuePopis
.customerClickCUSTOMER_CLICKZrušeno plátcem.
.fraudSuspectedFRAUD_SUSPECTEDPodezření na podvod.
.eshopCancelledESHOP_CANCELLEDZrušeno obchodníkem.
.providerReportPROVIDER_REPORTZrušeno providerem.
.providerTimeoutPROVIDER_TIMEOUTVypršel časový limit poskytovatele.
.customerTimeoutCUSTOMER_TIMEOUTVypršel časový limit platby.
.acsTimeoutACS_TIMEOUTVypršel časový limit pro ověření.
.invalidCardnoExpiryINVALID_CARDNO_EXPIRYChybně zadané číslo karty nebo datum platnosti karty.
.invalidCvcINVALID_CVCChybně zadaný CVC / CVV kód.
.limitExceededLIMIT_EXCEEDEDLimit karty byl překročen.
.noFundsNO_FUNDSNa účtu není dostatečný zůstatek.
.rejectedByBankREJECTED_BY_BANKPlatba byla zamítnuta bankou.
.threeDSAuthFail3DS_AUTH_FAILOvěření 3DS nebylo úspěšné.
.notSpecifiedNOT_SPECIFIEDNespecifikováno.

Kompletní příklad

Následující příklad ukazuje kompletní implementaci karetní platby s 3D Secure od inicializace po zpracování 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: "Ověření platby",
buttonText: "Zrušit"
)
),
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: "CZK",
country: "CZ",
label: "Objednávka #123",
refId: "order-123",
fullName: "Jan Novák",
billingAddrCity: "Hradec Králové",
billingAddrStreet: "Jiráskova 115",
billingAddrPostalCode: "50304",
billingAddrCountry: "CZ"
)
},
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čící 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)
}
}