Skip to main content

Events and tokenization

The SDK delivers events through SecureFieldsDelegate as the user interacts with the card form. Events contain field metadata — never raw card values. When the form is complete, call submit() to tokenize and receive a vault_form_token.

Implement the delegate

Adopt SecureFieldsDelegate in your view controller. Four methods are required — secureFieldsDidTokenize, secureFieldsDidFail, secureFieldsBrandsDetected, and secureFieldsFormValidityChanged. The rest (secureFieldsBrandSelected, secureFieldsContentChanged, secureFieldsFocusChanged, secureFieldsScreenshotDetected) have default no-op implementations — override only what you need.

extension CheckoutViewController: SecureFieldsDelegate {

func secureFieldsFormValidityChanged(_ isValid: Bool) {
payButton.isEnabled = isValid
}

func secureFieldsDidTokenize(_ result: TokenizationResult) {
// success path — see Handle the tokenization result below
}

func secureFieldsDidFail(_ error: SecureFieldsError) {
// error path — see Error reference below
}
}

Track form validity

secureFieldsFormValidityChanged(_:) fires whenever the aggregate form validity changes. Use it to enable or disable your submit button.

func secureFieldsFormValidityChanged(_ isValid: Bool) {
payButton.isEnabled = isValid

// Query individual field state without reading values:
let panValid = secureFields.isFieldValid(.pan)
let cvvValid = secureFields.isFieldValid(.cvv)
let expValid = secureFields.isFieldValid(.expDate)

// Character count for PAN (useful for progress indicators):
let digitCount = secureFields.panDigitCount
}

isFieldValid, isFieldFocused, hasFieldContent, and panDigitCount never expose raw card data — only derived metadata.

Track individual field state

secureFieldsFocusChanged(field:isFocused:) fires on focus and blur. secureFieldsContentChanged() fires on any keystroke in any field.

func secureFieldsFocusChanged(field: SecureField, isFocused: Bool) {
let borderView = containerView(for: field)
borderView.layer.borderColor = isFocused
? UIColor.systemBlue.cgColor
: UIColor.separator.cgColor
}

func secureFieldsContentChanged() {
// Fires on any keystroke in any field.
// Useful to update auxiliary UI without querying individual fields.
}

Handle brand detection

secureFieldsBrandsDetected(_:) fires when the BIN lookup returns results (≥8 digits typed) or when the card number drops below 8 digits (empty array).

func secureFieldsBrandsDetected(_ brands: [CardBrand]) {
// brands — e.g. [.visa] or [.visa, .carteBancaire] for co-branded cards
brandImageView.image = brands.first.map { brandImage($0) }
brandImageView.isHidden = brands.isEmpty
}

Co-branded card selector

panContainer shows a brand picker automatically when two or more brands are detected for the same PAN. secureFieldsBrandSelected(_:) fires when the user makes a choice.

func secureFieldsBrandSelected(_ brand: CardBrand) {
// Fires when the user picks a brand from the in-PAN brand selector.
cvvLabel.text = brand == .oney ? "Date of birth" : "CVV"
}

If a BIN lookup returns brands not in your configured brands list, they are filtered out. If no allowed brand is detected, the selector is not shown and secureFieldsBrandsDetected([]) fires.

Handle errors

secureFieldsDidFail(_:) fires when submit() encounters a problem:

func secureFieldsDidFail(_ error: SecureFieldsError) {
payButton.isEnabled = secureFields.isFieldValid(.pan)
&& secureFields.isFieldValid(.cvv)
&& secureFields.isFieldValid(.expDate)

switch error {
case .fieldsIncomplete:
showAlert("Please complete all card fields.")
case .networkError(let underlying):
showAlert("Network error: \(underlying.localizedDescription)")
case .apiError(let message, let statusCode):
showAlert("Payment error (\(statusCode)): \(message)")
case .invalidResponse:
showAlert("Unexpected response from the server.")
}
}

Handle screenshot detection

secureFieldsScreenshotDetected() fires immediately after the system saves a screenshot. The OS does not allow apps to prevent screenshots, but you can limit the exposure window:

func secureFieldsScreenshotDetected() {
secureFields.clearFields()
showAlert("Screenshot detected. For your security, please re-enter your card details.")
}

Submit the form

Call submit() when the user taps Pay. The SDK validates all fields internally and fires secureFieldsDidTokenize or secureFieldsDidFail on your delegate.

@objc func payTapped() {
payButton.isEnabled = false
loadingIndicator.startAnimating()
secureFields.submit()
}

submit() is a no-op and fires secureFieldsDidFail(.fieldsIncomplete) if any required field is invalid — it does not make a network request in that case. Disabling the Pay button on invalid state is belt-and-suspenders, not strictly required.

Submit with saveToken

Pass saveToken: true to persist the card for future payments:

secureFields.submit(saveToken: true)

The vault stores the card and returns the same vaultFormToken. Your backend can use the token for subsequent charges without re-entering the card.

Handle the tokenization result

secureFieldsDidTokenize(_:) delivers a TokenizationResult:

func secureFieldsDidTokenize(_ result: TokenizationResult) {
loadingIndicator.stopAnimating()

print("Token: \(result.vaultFormToken)")
print("BIN: \(result.bin)") // first 8 digits — never the full PAN
print("Last four: \(result.lastFourDigits)")
print("Brands: \(result.detectedBrands)")

// Send vaultFormToken to your backend — never send raw card data
sendTokenToBackend(result.vaultFormToken)
}
FieldDescription
vaultFormTokenOpaque server-side token — send to your backend
binFirst 8 digits of the PAN (safe to display)
lastFourDigitsLast 4 digits of the PAN (safe to display)
detectedBrandsCard networks detected by the BIN lookup

Clear fields

Call clearFields() to wipe all card data from memory and reset the form:

@objc func clearTapped() {
secureFields.clearFields()
resultLabel.text = nil
payButton.isEnabled = false
}

clearFields() zeroes all internal field buffers, cancels any pending BIN lookup, resets the brand selector, and fires secureFieldsBrandsDetected([]) and secureFieldsFormValidityChanged(false) on your delegate.

Delegate reference

MethodRequiredWhen it fires
secureFieldsDidTokenize(_:)Yessubmit() succeeds
secureFieldsDidFail(_:)Yessubmit() fails
secureFieldsFormValidityChanged(_:)YesAggregate form validity changes
secureFieldsBrandsDetected(_:)YesBIN lookup returns or clears results
secureFieldsBrandSelected(_:)NoUser picks a brand from the co-branded selector
secureFieldsContentChanged()NoAny keystroke in any field
secureFieldsFocusChanged(field:isFocused:)NoA field gains or loses focus
secureFieldsScreenshotDetected()NoThe system saves a screenshot while the form is visible

Error reference

CaseTrigger
.fieldsIncompletesubmit() called while one or more required fields are invalid
.networkError(Error)URLSession transport failure (no network, timeout, cancelled)
.apiError(message:statusCode:)Non-2xx HTTP response from the vault API
.invalidResponseResponse could not be decoded