# Events

Implement callbacks to customise your card payment flow for web.

## Overview

Components emit events based on user interaction or validation. You can use these to implement callback functions, which allow you to inject your own business logic and user experience customisations into the payment flow at critical moments. They ensure that while the SDK handles the complex technical aspects of payment processing, you retain full control over the customer experience and can seamlessly integrate payments into your broader business workflows and systems.

Callbacks enable you to:

* Validate business rules before payments proceed.
* Display custom error, failure, or success messages.
* Tailor user interfaces to match your brand's look and feel.
* Integrate with your own systems for fraud detection or customer management.
* Control exactly how your customers experience both successful and failed transactions.
* Handle card-specific requirements like 3DS authentication and tokenisation.
* Manage saved payment methods and customer billing information.


## SDK data callbacks

The card components require SDK initialisation callbacks to gather shopper and shipping information during transaction processing:

* **`onGetShopper` (required):** Called to retrieve current shopper data (ID, email, name, etc.) for tokenisation, transaction submission, and token management.
* **`onGetShippingAddress` (required when shipping is needed):** Called to get shipping address information for the transaction.


These callbacks ensure the card components always use the latest customer data from your application state, forms, or APIs when processing payments.

## Card-specific events

All card-specific events are optional and can be mixed and matched based on your business needs.

The SDK initialisation callbacks (`onGetShopper`, `onGetShippingAddress`) work alongside card-specific callbacks to provide a complete payment experience. SDK callbacks handle backend transaction data, while card callbacks handle form interactions, validation, and payment processing events.

## Supported events

Pre-built components
The following tables lists all events supported by the different pre-built components.

| Event | Billing address | Card-on-file | Click-once | New card |
|  --- | --- | --- | --- | --- |
| `onBlur` |  |  |  |  |
| `onCardBrandCannotRecognised` |  |  |  |  |
| `onCardBrandDetected` |  |  |  |  |
| `onChange` |  |  |  |  |
| `onClick` |  |  |  |  |
| `onCollectEnd` |  |  |  |  |
| `onCollectStart` |  |  |  |  |
| `onCustomValidation` |  |  |  |  |
| `onCvcEntered` |  |  |  |  |
| `onDeleteTokenFailed` |  |  |  |  |
| `onDeleteTokenSuccess` |  |  |  |  |
| `onFocus` |  |  |  |  |
| `onGetFingerprintResult` |  |  |  |  |
| `onOnceCardClick` |  |  |  |  |
| `onPostAuthentication` |  |  |  |  |
| `onPostAuthorisation` |  |  |  |  |
| `onPostInitiateAuthentication` |  |  |  |  |
| `onPostTokenisation` |  |  |  |  |
| `onPreAuthentication` |  |  |  |  |
| `onPreAuthorisation` |  |  |  |  |
| `onPreDeleteToken` |  |  |  |  |
| `onPreInitiateAuthentication` |  |  |  |  |
| `onPreRenderTokens` |  |  |  |  |
| `onPreTokenisation` |  |  |  |  |
| `onRetrieveTokensFailed` |  |  |  |  |
| `onSelectToken` |  |  |  |  |
| `onSubmitError` |  |  |  |  |
| `onUpdateTokenFailed` |  |  |  |  |
| `onUpdateTokenSuccess` |  |  |  |  |
| `onValidation` |  |  |  |  |
| `onValidationPassed` |  |  |  |  |
| `onValidationFailed` |  |  |  |  |
| `tokenItemBuilder` |  |  |  |  |
| `tokenLabelBuilder` |  |  |  |  |


Standalone components
The following table lists all events supported by the different standalone components. Note that the country selection, dynamic card image, and pre-fill billing address checkbox components don't support any callbacks and are These display-only.

| Event | Billing address input fields | Card input fields | Card submit |
|  --- | --- | --- | --- |
| `onBlur` |  |  |  |
| `onCardBrandCannotRecognised` |  | Card number only. |  |
| `onCardBrandDetected` |  |  Card number only. |  |
| `onChange` |  |  |  |
| `onClick` |  |  |  |
| `onCollectEnd` |  |  |  |
| `onCollectStart` |  |  |  |
| `onCustomValidation` |  |  |  |
| `onCvcEntered` |  |  |  |
| `onDeleteTokenFailed` |  |  |  |
| `onDeleteTokenSuccess` |  |  |  |
| `onFocus` |  |  |  |
| `onGetFingerprintResult` |  |  |  |
| `onOnceCardClick` |  |  |  |
| `onPostAuthentication` |  |  |  |
| `onPostAuthorisation` |  |  |  |
| `onPostInitiateAuthentication` |  |  |  |
| `onPostTokenisation` |  |  |  |
| `onPreAuthentication` |  |  |  |
| `onPreAuthorisation` |  |  |  |
| `onPreDeleteToken` |  |  |  |
| `onPreInitiateAuthentication` |  |  |  |
| `onPreRenderTokens` |  |  |  |
| `onPreTokenisation` |  |  |  |
| `onRetrieveTokensFailed` |  |  |  |
| `onSubmitError` |  |  |  |
| `onUpdateTokenFailed` |  |  |  |
| `onUpdateTokenSuccess` |  |  |  |
| `onValidation` |  |  |  |
| `onValidationPassed` |  |  |  |
| `onValidationFailed` |  |  |  |
| `tokenItemBuilder` |  |  |  |
| `tokenLabelBuilder` |  |  |  |


## Callbacks

### onPostAuthorisation

This callback is triggered after the payment transaction has been processed and authorisation has been completed (successfully or unsuccessfully).

You can use it to:

* Retrieve the full authorisation result from your backend using the transaction identifiers.
* Redirect customers to a success page with order confirmation.
* Update stock levels for purchased items.
* Send order confirmation emails to customers.
* Record successful transactions for business intelligence.


#### Event data

| Event data | Description |
|  --- | --- |
| `data`object | Object containing transaction identifiers. |
| `data.merchantTransactionId`string | Your unique identifier for the transaction. Use this with systemTransactionId to retrieve full authorisation details from Unity backend. |
| `data.systemTransactionId`string | The system's unique identifier for the transaction. Use this with merchantTransactionId to retrieve full authorisation details from Unity backend. |


#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPostAuthorisation: async (data) => {
    console.log('Authorisation completed');
    console.log('Merchant Transaction ID:', data.merchantTransactionId);
    console.log('System Transaction ID:', data.systemTransactionId);
    
    // Get authorisation result from merchant backend
    const authorisationResult = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
    
    // Handle the authorisation result
    if (authorisationResult.state === 'Authorised') {
      // Payment successful
      console.log('Payment authorised successfully');
      
      // Update inventory
      updateInventory(data.merchantTransactionId);
      
      // Send confirmation email
      sendConfirmationEmail(customerEmail, data.merchantTransactionId);
      
      // Track analytics
      trackPurchase(data.merchantTransactionId, authorisationResult.correlationId);
      
      // Redirect to success page
      window.location.href = `/payment-success?txn=${data.merchantTransactionId}`;
    } else if (authorisationResult.state === 'Declined') {
      console.error('Payment declined');
      handleDeclinedPayment(authorisationResult);
    } else if (authorisationResult.state === 'Failed') {
      console.error('Payment failed');
      showErrorMessage('Payment failed. Please try again or use a different payment method.');
    } else if (authorisationResult.state === 'Pending') {
      console.log('Payment pending further processing');
      showMessage('Payment is being processed. You will receive confirmation shortly.', 'info');
    }
  }
});
```

### onPreAuthorisation

This callback is triggered before the transaction authorisation, allowing you to provide additional transaction data or control whether to proceed.

You can use it to:

* Retrieve token details from your backend using the `gatewayTokenId`.
* Update the transaction decision in your backend.
* Integrate with Kount or other fraud detection services.
* Perform AVS (Address Verification System) checks.
* Apply business rules based on transaction amount or customer history.
* Control whether to proceed with authorisation by returning `null`.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing token information. |
| `data.gatewayTokenId`string | The token ID from the payment gateway. Use this ID to retrieve full token details from the Unity backend and update transaction decision. |


Use the `gatewayTokenId` with the [Get masked card data related to gateway token](/apis/token-vault/other/get-masked-card-related-to-gateway-token) API to retrieve full token details including card scheme, funding source (credit/debit), masked PAN, and expiry date.

After evaluating token details and transaction data, use the [Modify session](/apis/session/other/modify-session) API to update the authorisation decision on your backend before returning from this callback. Set `"allowTransaction": true` in the session data to proceed with the transaction.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPreAuthorisation: async (data) => {
    console.log('Pre-authorisation for token:', data.gatewayTokenId);
    
    // Merchant can use gatewayTokenId to retrieve token details and update transaction decision on merchant BE
    const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);

    if (!transactionDecision) {
      // To not proceed
      return null;
    }
    
    // Perform pre-payment validation
    const deviceSessionId = await getKountSessionId();
    const isHighRisk = await checkCustomerRiskProfile();
    const customerTier = await getCustomerTier();
    
    // Get billing address if AVS is enabled
    const billingAddress = await getBillingAddress();
    
    return {
      addressVerification: billingAddress ? {
        countryCode: billingAddress.countryCode,
        houseNumberOrName: billingAddress.address,
        postalCode: billingAddress.postalCode,
        city: billingAddress.city,
        state: billingAddress.state
      } : undefined,
      riskScreeningData: {
        deviceSessionId: deviceSessionId,
        performRiskScreening: true,
        customData: {
          customerTier: customerTier,
          orderType: 'ecommerce',
          previousTransactionCount: await getPreviousTransactionCount(),
          riskScore: isHighRisk ? 'high' : 'low'
        }
      }
    };
  }
});
```

### onPreTokenisation

This callback is triggered before card tokenisation begins, allowing you to control whether tokenisation should proceed.

You can use it to:

* Validate card information before tokenisation.
* Check if the customer wants to save their payment method.
* Apply business rules for token storage.
* Implement custom security checks.


#### Event data

This callback receives no parameters.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPreTokenisation: () => {
    // Check if customer consented to save card
    const saveCardConsent = document.getElementById('save-card-checkbox')?.checked;
    
    if (!saveCardConsent) {
      console.log('Customer declined to save card - skipping tokenisation');
      return false;
    }
    
    // Additional validation
    const isValidForStorage = validateCardForStorage();
    if (!isValidForStorage) {
      console.log('Card not suitable for storage - skipping tokenisation');
      return false;
    }
    
    console.log('Proceeding with card tokenisation');
    return true;
  }
});
```

### onPostTokenisation

This callback is triggered after card tokenisation completes with the tokenisation result.

You can use it to:

* Retrieve full token details from your backend using the `gatewayTokenId`.
* Evaluate token details to decide whether to proceed with authentication/authorisation.
* Update the user interface to show saved payment methods.
* Link the token to the customer's account.
* Log tokenisation events for analytics.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing tokenisation result. |
| `data.gatewayTokenId`string | The unique identifier for the tokenised card. Use this ID to retrieve full token details from Unity backend. |


Use the `gatewayTokenId` with the [Get masked card data related to gateway token](/apis/token-vault/other/get-masked-card-related-to-gateway-token) API to retrieve full token details including card scheme, funding source (credit/debit), masked PAN, and expiry date.

After evaluating token details and transaction data, use the [Modify session](/apis/session/other/modify-session) API to update the authorisation decision on your backend before returning from this callback. Set `"allowTransaction": true` in the session data to proceed with the transaction.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPostTokenisation: async (data) => {
    console.log('Card tokenised successfully. Token ID:', data.gatewayTokenId);
    
    // If merchant wants to evaluate the token details to decide whether to proceed with authentication/authorisation
    // Merchant BE uses the returned gatewayTokenId to get Token Details from Unity
    const tokenDetails = await fetchTokenDetailsFromBackend(data.gatewayTokenId);
    
    // Merchant BE evaluates the token details to update authentication/authorisation decision via Unity Update session API
    const decision = await evaluateTokenAndUpdateSession(tokenDetails);
    
    // Store token for future use
    await saveTokenToCustomerAccount({
      tokenId: data.gatewayTokenId,
      customerId: getCurrentCustomerId()
    });
    
    // Update UI to show saved card
    updateSavedPaymentMethodsUI(data.gatewayTokenId);
    
    // Track tokenisation event
    trackEvent('card-tokenized', {
      tokenId: data.gatewayTokenId,
      timestamp: new Date().toISOString()
    });
    
    // Show success message
    showMessage('Payment method saved successfully', 'success');
  }
});
```

### onBlur

This callback is triggered when the input loses focus.

You can use it to:

* Validate field data when user moves to next field.
* Show field-specific help or validation messages.
* Track user interaction patterns for UX analytics.
* Auto-format field values on blur.


#### Event data

| Parameter | Description |
|  --- | --- |
| `event`Event | The blur event object containing target and field information. |
| `event.type`string | The event type (always `blur`). |
| `event.target`HTMLInputElement | The input element that lost focus. |
| `event.target.value`string | The current value of the field. |
| `event.target.id`string | The field identifier (e.g., `card-number`, `card-expiry-date`). |
| `event.target.name`string | The field name attribute. |
| `event.target.validity`ValidityState | The HTML5 validation state of the field. |
| `event.target.validity.valid`boolean | Whether the field passes HTML5 validation. |
| `event.target.validity.valueMissing`boolean | Whether a required field is empty. |
| `event.currentTarget`HTMLInputElement | The element that the event listener is attached to. |
| `event.timestamp`number | The date and time when the event occurred. |


#### Example implementation


```typescript
const cardNumberComponent = sdk.create('card-number', {
  onBlur: (event) => {
    console.log('Card number field blurred:', event.target.value);
    
    // Validate card number on blur
    if (event.target.value) {
      const isValid = validateCardNumber(event.target.value);
      if (!isValid) {
        showFieldError('card-number', 'Please enter a valid card number');
      } else {
        clearFieldError('card-number');
      }
    }
    
    // Track field interaction
    trackFieldInteraction('card-number', 'blur', {
      hasValue: !!event.target.value,
      timestamp: new Date().toISOString()
    });
  }
});
```

### onCardBrandCannotRecognised

This callback is triggered when the card number input changes, but the entered digits don't match any known card brand patterns.

You can use it to:

* Display appropriate error messages for unsupported card types.
* Guide users to use supported payment methods.
* Log attempts to use unsupported card brands for analytics.
* Redirect users to alternative payment options.


#### Event data

| Parameter | Description |
|  --- | --- |
| `event`CustomEvent | The card brand recognition failure event object. |
| `event.type`string | The event type (always `cardbrandcannotrecognised`). |
| `event.target`HTMLInputElement | The card number input field. |
| `event.target.value`string | The current card number value that couldn't be recognised. |
| `event.target.selectionStart`number | The current cursor/selection start position. |
| `event.target.selectionEnd`number | The current cursor/selection end position. |
| `event.detail`object | Additional details about the recognition failure. |
| `event.detail.inputLength`number | The length of the current input value. |
| `event.detail.attempts`number | The number of recognition attempts made. |
| `event.detail.lastKnownBrand`string | The last successfully detected brand (if any). |
| `event.detail.supportedBrands`array | Array of the card brands you support. |
| `event.detail.inputPattern`string | The current input pattern (first 6 digits or BIN). |


#### Example implementation


```typescript
const cardNumberComponent = sdk.create('card-number', {
  onCardBrandCannotRecognised: (event) => {
    console.log('Card brand could not be recognised for:', event.target.value);
    
    // Show error message for unsupported card
    showFieldError('card-number', 'This card type is not supported. Please use Visa, MasterCard, or American Express.');
    
    // Log unsupported card attempt
    trackEvent('unsupported-card-attempted', {
      cardNumberPrefix: event.target.value.substring(0, 6),
      timestamp: new Date().toISOString()
    });
    
    // Show alternative payment methods
    showAlternativePaymentMethods();
    
    // Disable submit button until valid card is entered
    disableSubmitButton();
  }
});
```

### onCardBrandDetected

This callback is triggered when the card number input changes and a recognised card brand (e.g., Visa, MasterCard) is detected from the input value.

You can use it to:

* Update the card brand logo display in real-time.
* Apply brand-specific validation rules or formatting.
* Show relevant rewards or benefits for the detected card.
* Enable or disable certain features based on the card brand.


#### Event data

| Parameter | Description |
|  --- | --- |
| `event`CustomEvent | The card brand detection event object. |
| `event.type`string | The event type (always `cardbranddetected`). |
| `event.target`HTMLInputElement | The card number input field. |
| `event.target.value`string | The current card number value. |
| `event.detail`object | Enhanced card brand detection details. |
| `event.detail.cardBrand`object | The detected card brand information. |
| `event.detail.cardBrand.brand`string | The card brand identifier (e.g., `visa`, `mastercard`). |
| `event.detail.cardBrand.displayName`string | The user-friendly brand name (e.g., `Visa`, `MasterCard`). |
| `event.detail.cardBrand.type`string | The card type (e.g., `credit`, `debit`, `prepaid`). |
| `event.detail.cardBrand.category`string | The card category (e.g., `consumer`, `business`, `corporate`). |
| `event.detail.cardBrand.logo`string | The URL to the card brand logo image. |
| `event.detail.cardBrand.colors`object | The brand colour scheme for UI theming. |
| `event.detail.cardBrand.colors.primary`string | The primary brand colour (hex code). |
| `event.detail.cardBrand.colors.secondary`string | The secondary brand colour (hex code). |
| `event.detail.cardBrand.cardNumberLength`number | The expected card number length for this brand. |
| `event.detail.cardBrand.cvcLength`number | The expected CVC length for this brand (3 or 4). |
| `event.detail.cardBrand.supportedCountries`array | Array of country codes where this brand is supported. |
| `event.detail.isCardBrandAccept`boolean | Whether this card brand is accepted by you. |
| `event.detail.confidence`number | The confidence level of the brand detection (`0`-`1`). |
| `event.detail.alternativeBrands`array | Array of possible alternative brand matches. |


#### Example implementation


```typescript
const cardNumberComponent = sdk.create('card-number', {
  onCardBrandDetected: (event) => {
    const cardBrand = event.detail.cardBrand;
    const isAccepted = event.detail.isCardBrandAccept;
    
    console.log(`Card brand detected: ${cardBrand.brand}`, isAccepted);
    
    // Update card brand logo
    updateCardBrandLogo(cardBrand.brand);
    
    // Check if card brand is accepted
    if (!isAccepted) {
      showFieldError('card-number', `${cardBrand.displayName} cards are not accepted. Please use a different card.`);
      disableSubmitButton();
    } else {
      clearFieldError('card-number');
      enableSubmitButton();
      
      // Show brand-specific benefits
      showCardBrandBenefits(cardBrand.brand);
    }
    
    // Apply brand-specific validation
    applyBrandSpecificValidation(cardBrand.brand);
    
    // Track card brand usage
    trackEvent('card-brand-detected', {
      brand: cardBrand.brand,
      accepted: isAccepted,
      timestamp: new Date().toISOString()
    });
  }
});
```

### onChange

This callback is triggered when the input value changes in one or more fields.

You can use it to:

* Update UI elements based on field values in real-time.
* Perform progressive validation as users type.
* Calculate and display dynamic totals or fees.
* Enable or disable form elements based on input values.


#### Event data

| Parameter | Description |
|  --- | --- |
| `event`CustomEvent | The input change event object with enhanced properties. |
| `event.type`string | The event type (always `change`). |
| `event.target`HTMLInputElement | The input element that changed. |
| `event.target.value`string | The current value of the field. |
| `event.target.name`string | The field name identifier (e.g., `cardNumber`, `expiryDate`). |
| `event.target.id`string | The field ID attribute. |
| `event.detail`object | Enhanced event details provided by the SDK. |
| `event.detail.isValid`boolean | Whether the current field value is valid (if validation is enabled). |
| `event.detail.isEmpty`boolean | Whether the field is empty. |
| `event.detail.isComplete`boolean | Whether the field is completely filled (i.e., meets the expected length). |
| `event.detail.cardBrand`object | Card brand information (for card number fields only). |
| `event.detail.cardBrand.brand`string | The detected card brand (e.g., `visa`, `mastercard`). |
| `event.detail.cardBrand.displayName`string | The user-friendly brand name (e.g., `Visa`, `MasterCard`). |
| `event.detail.cardBrand.logo`string | The URL to the card brand logo image. |
| `event.detail.cardBrand.cvcLength`number | The expected CVC length for this card brand (3 or 4). |
| `event.detail.formattedValue`string | The formatted display value (with spaces, dashes, etc.). |
| `event.detail.errors`array | Array of validation error objects (if validation failed). |
| `event.detail.errors[].code`string | The error code for programmatic handling. |
| `event.detail.errors[].message`string | A human-readable error message. |


#### Example implementation


```typescript
const cardNumberComponent = sdk.create('card-number', {
  onChange: (event) => {
    console.log('Card number changed:', event.target.value);
    
    // Update card brand display
    if (event.detail.cardBrand) {
      updateCardBrandDisplay(event.detail.cardBrand);
    }
    
    // Progressive validation
    if (event.target.value.length >= 4) {
      const isValid = event.detail.isValid;
      if (isValid) {
        clearFieldError('card-number');
        showFieldSuccess('card-number');
      } else {
        showFieldError('card-number', 'Please enter a valid card number');
      }
    }
    
    // Enable submit button when all fields are filled
    checkFormCompletion();
    
    // Track input progress
    trackFieldProgress('card-number', {
      length: event.target.value.length,
      isValid: event.detail.isValid,
      timestamp: new Date().toISOString()
    });
  }
});

const cardExpiryComponent = sdk.create('card-expiry-date', {
  onChange: (event) => {
    console.log('Expiry date changed:', event.target.value);
    
    // Check for near-expiry warnings
    if (event.detail.isValid) {
      const expiryDate = new Date(event.target.value);
      const now = new Date();
      const monthsUntilExpiry = (expiryDate.getFullYear() - now.getFullYear()) * 12 + 
                                (expiryDate.getMonth() - now.getMonth());
      
      if (monthsUntilExpiry <= 1) {
        showWarning('Your card expires soon. Please ensure it will be valid when your payment is processed.');
      }
    }
  }
});
```

### onClick

This callback is triggered when the submit button is clicked, before any payment processing begins.

You can use it to:

* Perform final form validation before payment processing.
* Show loading states or disable the button to prevent double-clicks.
* Track conversion funnel progression for analytics.
* Execute pre-payment business logic or confirmations.


#### Event data

| Parameter | Description |
|  --- | --- |
| `event`MouseEvent | The click event object from the submit button. |
| `event.type`string | The event type (always `click`). |
| `event.target`HTMLButtonElement | The submit button element that was clicked. |
| `event.target.disabled`boolean | Whether the button is currently disabled. |
| `event.target.textContent`string | The button's text content. |
| `event.target.form`HTMLFormElement | The form element containing the button (if any). |
| `event.currentTarget`HTMLButtonElement | The element the event listener is attached to. |
| `event.clientX`number | The X coordinate of the mouse pointer relative to the viewport. |
| `event.clientY`number | The Y coordinate of the mouse pointer relative to the viewport. |
| `event.button`number | Which mouse button was pressed (`0` = left, `1` = wheel, `2` = right). |
| `event.ctrlKey`boolean | Whether the Ctrl key was held during the click. |
| `event.shiftKey`boolean | Whether the Shift key was held during the click. |
| `event.altKey`boolean | Whether the Alt key was held during the click. |
| `event.metaKey`boolean | Whether the Meta key (Cmd on Mac) was held during the click. |
| `event.timeStamp`number | The time when the event occurred. |
| `event.preventDefault`function | Method to prevent the default submission behaviour. |
| `event.stopPropagation`function | Method to stop the event from bubbling up. |


#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
    onClick: (event) => {
        console.log('Submit button clicked');
    
    // Track the payment attempt
    trackEvent('payment-attempt-started', {
      paymentMethod: 'card',
      timestamp: new Date().toISOString(),
      amount: getCurrentAmount(),
      currency: getCurrentCurrency()
    });
    
    // Perform final validation
    const validationErrors = performFinalValidation();
    if (validationErrors.length > 0) {
      event.preventDefault();
      showValidationErrors(validationErrors);
      return;
    }
    
    // Check inventory one final time
    const inventoryCheck = checkInventoryAvailability();
    if (!inventoryCheck.available) {
      event.preventDefault();
      showError('Some items in your cart are no longer available. Please review your order.');
      return;
    }
    
    // Show loading state
    showSubmitLoading(true);
    event.target.disabled = true;
    
    // Optional: Show confirmation dialog for high-value transactions
    const amount = getCurrentAmount();
    if (amount > 1000) {
      const confirmed = confirm(`Please confirm your payment of ${formatCurrency(amount)}`);
      if (!confirmed) {
        event.preventDefault();
        showSubmitLoading(false);
        event.target.disabled = false;
        return;
      }
    }
    
    // Log that payment is proceeding
    console.log('Payment processing initiated');
    }
});
```

### onCollectEnd

This callback is triggered when device fingerprinting and data collection ends.

You can use it to:

* Hide loading indicators shown during data collection.
* Log data collection completion for debugging.
* Proceed to the next step in the payment flow.
* Track data collection performance metrics.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | The data collection result object. |
| `data.success`boolean | Whether data collection completed successfully. |
| `data.sessionId`string | The session ID used for data collection. |
| `data.fingerprintData`string | The collected device fingerprint data (if successful). |
| `data.provider`string | The fraud detection provider used (e.g., `kount`). |
| `data.duration`number | The time taken for data collection in milliseconds. |
| `data.startTime`number | The date and time when collection started. |
| `data.endTime`number | The date and time when collection completed. |
| `data.dataPoints`object | The summary of collected data points. |
| `data.dataPoints.deviceInfo`boolean | Whether device information was collected. |
| `data.dataPoints.browserInfo`boolean | Whether browser information was collected. |
| `data.dataPoints.networkInfo`boolean | Whether network information was collected. |
| `data.dataPoints.behavioralInfo`boolean | Whether behavioural patterns were collected. |
| `data.error`Error | The error object if data collection failed (optional). |
| `data.error.code`string | The error code for programmatic handling. |
| `data.error.message`string | A human-readable error description. |
| `data.error.provider`string | The provider that generated the error. |
| `data.retryAttempts`number | The number of retry attempts made during collection. |
| `data.fallbackUsed`boolean | Whether fallback collection methods were used. |


#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onCollectEnd: (data) => {
    console.log('Data collection completed:', data);
    
    // Hide loading indicator
    hideMessage();
    hideDataCollectionSpinner();
    
    // Track collection metrics
    trackEvent('device-data-collection-completed', {
      success: data.success,
      duration: data.duration,
      timestamp: new Date().toISOString()
    });
    
    if (data.success) {
      console.log('Device fingerprinting successful');
      
      // Store fingerprint data for fraud detection
      storeFingerprintData(data.fingerprintData);
      
      // Enable submit button now that data collection is complete
      enableSubmitButton();
      
      // Show completion indicator
      showSuccessMessage('Security check completed', 2000);
    } else {
      console.warn('Data collection failed:', data.error);
      
      // Handle collection failure - continue without fingerprinting
      logError('fingerprint-collection-failed', {
        error: data.error?.message,
        duration: data.duration
      });
      
      // Still allow payment to proceed (fingerprinting is optional)
      enableSubmitButton();
      showWarningMessage('Security check unavailable - payment will proceed normally');
    }
  }
});
```

### onCollectStart

This callback is triggered when device fingerprinting and data collection begins.

You can use it to:

* Show loading indicators to inform users about the security process.
* Track the start of data collection for performance monitoring.
* Display educational messages about fraud prevention.
* Initialise any UI elements related to the collection process.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | The data collection initialisation object. |
| `data.provider`string | The fraud detection provider (e.g., `kount`). |
| `data.sessionId`string | The session ID for the data collection process. |
| `data.timeout`number | The maximum time allowed for data collection, in milliseconds. |
| `data.startTime`number | The date and time when collection is starting. |
| `data.config`object | Provider-specific configuration settings. |
| `data.config.merchantId`string | Your merchant identifier for the fraud detection service. |
| `data.config.environment`string | The environment setting. |
| `data.config.version`string | The provider SDK version being used. |
| `data.dataTypes`array | The types of data to be collected (e.g., `['device', 'browser', 'network']`). |
| `data.retryConfig`object | The retry configuration for failed collection attempts. |
| `data.retryConfig.maxAttempts`number | The maximum number of retry attempts. |
| `data.retryConfig.backoffMs`number | The backoff time between retries in milliseconds. |
| `data.fallbackEnabled`boolean | Whether fallback collection methods are enabled. |


#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onCollectStart: (data) => {
    console.log('Data collection started:', data);
    
    // Show informative loading message
    showMessage('Performing security check...', 'info');
    showDataCollectionSpinner();
    
    // Track collection start
    trackEvent('device-data-collection-started', {
      provider: data.provider,
      sessionId: data.sessionId,
      timeout: data.timeout,
      timestamp: new Date().toISOString()
    });
    
    // Disable submit button during collection
    disableSubmitButton();
    
    // Show educational tooltip about fraud prevention
    showTooltip('security-info', 
      'We\'re performing a quick security check to protect against fraud. This helps keep your payment safe.');
    
    // Set up timeout warning
    const warningTime = data.timeout * 0.8; // Warn at 80% of timeout
    setTimeout(() => {
      if (!isDataCollectionComplete()) {
        showWarningMessage('Security check taking longer than expected...');
      }
    }, warningTime);
    
    // Log provider-specific information
    if (data.provider === 'kount') {
      console.log('Kount fraud detection initiated with session:', data.sessionId);
    } else if (data.provider === 'threatmetrix') {
      console.log('ThreatMetrix profiling started with session:', data.sessionId);
    }
  }
});
```

### onCvcEntered

This callback is triggered when a customer enters a CVC value in the click-once component's CVC field.

You can use it to:

* Enable the submit button when CVC is provided for saved cards.
* Perform real-time CVC validation.
* Track security code entry for analytics.
* Show completion indicators for the payment form.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | The CVC entry event data. |
| `data.isValid`boolean | Whether the entered CVC is valid for the card type. |
| `data.cvcValue`string | The masked CVC value (e.g., '***' or '****'). |
| `data.cvcLength`number | The length of the entered CVC. |
| `data.expectedLength`number | The expected CVC length for this card brand. |
| `data.cardType`string | The type of card the CVC was entered for (e.g., `visa`, `amex`). |
| `data.cardBrand`object | Detailed card brand information. |
| `data.cardBrand.displayName`string | The user-friendly brand name (e.g., `Visa`, `American Express`). |
| `data.cardBrand.requiresCvc`boolean | Whether this card brand typically requires CVC. |
| `data.maskedCardNumber`string | The masked card number associated with the CVC. |
| `data.tokenId`string | The token ID of the saved card (for click-once components). |
| `data.validationErrors`array | Array of validation error objects (if validation failed). |
| `data.validationErrors[].code`string | The error code for programmatic handling. |
| `data.validationErrors[].message`string | A human-readable error message. |
| `data.inputSource`string | How the CVC was entered (e.g., `keyboard`). |
| `data.timeTaken`number | The time taken to enter the CVC, in milliseconds. |


#### Example implementation


```typescript
const clickOnceComponent = sdk.create('click-once', {
  onCvcEntered: (data) => {
    console.log('CVC has been entered for card:', data.maskedCardNumber);
    
    // Validate CVC format
    if (data.isValid) {
      console.log('Valid CVC entered');
      
      // Clear any previous errors
      clearFieldError('cvc');
      
      // Show success indicator
      showFieldSuccess('cvc');
      
      // Enable submit button
      enableSubmitButton();
      
      // Track successful CVC entry
      trackEvent('cvc-entered-valid', {
        cardType: data.cardType,
        maskedCardNumber: data.maskedCardNumber,
        timestamp: new Date().toISOString()
      });
      
      // Show payment ready indicator
      showPaymentReadyMessage();
      
    } else {
      console.log('Invalid CVC entered');
      
      // Show validation error
      showFieldError('cvc', 'Please enter a valid security code');
      
      // Keep submit button disabled
      disableSubmitButton();
      
      // Track invalid CVC attempts
      trackEvent('cvc-entered-invalid', {
        cardType: data.cardType,
        timestamp: new Date().toISOString()
      });
    }
    
    // Update UI to reflect CVC completion
    updateFormCompletionStatus();
  }
});
```

### onDeleteTokenFailed

This callback is triggered when a card token deletion request fails, for example due to a network error or authorisation issues.

You can use it to:

* Display user-friendly error messages for deletion failures.
* Log deletion errors for debugging and monitoring.
* Offer retry options for failed deletions.
* Track failure rates for saved payment method management.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`DeleteCardTokenResponseFailed | The failed deletion response object. |
| `data.error`object | The error details for the failed deletion. |
| `data.error.message`string | A human-readable error message. |
| `data.error.code`string | The specific error code for programmatic handling. |
| `data.tokenId`string | The ID of the token that failed to be deleted. |
| `data.correlationId`string | The correlation ID for tracking the request. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onDeleteTokenFailed: (data) => {
    console.log('Token deletion failed:', data.error);
    
    // Log the error for debugging
    logError('token-deletion-failed', {
      tokenId: data.tokenId,
      errorCode: data.error.code,
      errorMessage: data.error.message,
      correlationId: data.correlationId,
      timestamp: new Date().toISOString()
    });
    
    // Handle different error types
    let userMessage = 'Failed to delete payment method. Please try again.';
    
    if (data.error.code === 'UNAUTHORIZED') {
      userMessage = 'You are not authorised to delete this payment method.';
    } else if (data.error.code === 'TOKEN_NOT_FOUND') {
      userMessage = 'This payment method no longer exists.';
      // Remove from UI since it doesn't exist
      removeTokenFromUI(data.tokenId);
    } else if (data.error.code === 'NETWORK_ERROR') {
      userMessage = 'Network error. Please check your connection and try again.';
      // Offer retry option
      showRetryButton(() => retryDeleteToken(data.tokenId));
    } else if (data.error.code === 'TOKEN_IN_USE') {
      userMessage = 'This payment method cannot be deleted as it is currently being used.';
    }
    
    // Show error message to user
    showErrorMessage(userMessage);
    
    // Track deletion failure analytics
    trackEvent('token-deletion-failed', {
      tokenId: data.tokenId,
      errorCode: data.error.code,
      errorType: data.error.message,
      timestamp: new Date().toISOString()
    });
    
    // Re-enable delete button if it was disabled
    enableDeleteButton(data.tokenId);
  }
});
```

### onDeleteTokenSuccess

This callback is triggered when a card token is successfully deleted from the vault.

You can use it to:

* Update the UI to remove the deleted payment method.
* Show success confirmation messages to users.
* Track successful payment method deletions for analytics.
* Update local storage or state management.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`DeleteCardTokenResponseSuccess | The successful deletion response object. |
| `data.tokenId`string | The ID of the successfully deleted token. |
| `data.maskedCardNumber`string | The masked card number of the deleted token. |
| `data.cardScheme`string | The card scheme of the deleted token. |
| `data.correlationId`string | The correlation ID for tracking the request. |
| `data.timestamp`string | When the deletion was completed, in ISO 8601 format. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onDeleteTokenSuccess: (data) => {
    console.log('Token deleted successfully:', data.tokenId);
    
    // Remove the token from the UI
    removeTokenFromUI(data.tokenId);
    
    // Show success message
    showSuccessMessage(`${data.cardScheme} ending in ${data.maskedCardNumber.slice(-4)} has been removed successfully.`);
    
    // Update local data
    removeTokenFromLocalStorage(data.tokenId);
    
    // Track successful deletion
    trackEvent('token-deletion-success', {
      tokenId: data.tokenId,
      cardScheme: data.cardScheme,
      maskedCardNumber: data.maskedCardNumber,
      correlationId: data.correlationId,
      timestamp: new Date().toISOString()
    });
    
    // Check if this was the last saved card
    const remainingTokens = getRemainingTokenCount();
    if (remainingTokens === 0) {
      showEmptyStateMessage('No saved payment methods. Add a new card to save time on future purchases.');
      hideCardOnFileSection();
    }
    
    // If this was the default card, update default selection
    if (isDefaultCard(data.tokenId)) {
      updateDefaultCard();
    }
    
    // Log successful deletion
    console.log(`Payment method deleted: ${data.cardScheme} ${data.maskedCardNumber} at ${data.timestamp}`);
    
    // Optional: Trigger email notification about deleted payment method
    if (shouldNotifyOfDeletion()) {
      sendPaymentMethodDeletedNotification(data);
    }
  }
});
```

### onFocus

This callback is triggered when an input field receives focus.

You can use it to:

* Show helpful tooltips or instructions when users focus on fields.
* Highlight field requirements or formatting examples.
* Track user interaction patterns and form navigation.
* Update UI elements to show the active field state.


#### Event data

| Parameter | Description |
|  --- | --- |
| `event`FocusEvent | The focus event object. |
| `event.type`string | The event type (always `focus`). |
| `event.target`HTMLInputElement | The input field that received focus. |
| `event.target.name`string | The field name identifier. |
| `event.target.value`string | The current value of the field. |
| `event.target.id`string | The field ID attribute. |
| `event.target.placeholder`string | The field's placeholder text. |
| `event.target.selectionStart`number | The starting position of text selection. |
| `event.target.selectionEnd`number | The ending position of text selection. |
| `event.target.validity`ValidityState | The HTML5 validation state of the field. |
| `event.currentTarget`HTMLInputElement | The element the event listener is attached to. |
| `event.relatedTarget`HTMLElement | The element that previously had focus (if any). |
| `event.timeStamp`number | The date and time when the event occurred. |


#### Example implementation


```typescript
const cardNumberComponent = sdk.create('card-number', {
  onFocus: (event) => {
    console.log('Card number field focused');
    
    // Clear any previous errors
    clearFieldError('card-number');
    
    // Show helpful formatting tooltip
    showTooltip('card-number-help', 'Enter your 16-digit card number without spaces or dashes');
    
    // Highlight the field container
    highlightField('card-number');
    
    // Track field interaction
    trackEvent('field-focused', {
      fieldName: 'card-number',
      hasExistingValue: !!event.target.value,
      timestamp: new Date().toISOString()
    });
    
    // Show accepted card brands
    showAcceptedCardBrands();
  }
});

const cardExpiryComponent = sdk.create('card-expiry-date', {
  onFocus: (event) => {
    console.log('Expiry date field focused');
    
    // Show format example
    showTooltip('expiry-help', 'MM/YY format (e.g., 12/25)');
    
    // Clear errors
    clearFieldError('card-expiry-date');
    
    // Highlight field
    highlightField('card-expiry-date');
    
    // Track focus
    trackEvent('field-focused', {
      fieldName: 'card-expiry-date',
      timestamp: new Date().toISOString()
    });
  }
});

const cardCvcComponent = sdk.create('card-cvc', {
  onFocus: (event) => {
    console.log('CVC field focused');
    
    // Show security code help
    showTooltip('cvc-help', 'Enter the 3-digit security code from the back of your card (4 digits for Amex)');
    
    // Show CVC location diagram
    showCvcLocationDiagram();
    
    // Clear errors
    clearFieldError('card-cvc');
    
    // Track focus
    trackEvent('field-focused', {
      fieldName: 'card-cvc',
      timestamp: new Date().toISOString()
    });
  }
});
```

### onGetFingerprintResult

This callback is used to retrieve device fingerprint data for fraud detection and authentication purposes.

You can use it to:

* Integrate with third-party fraud detection services (Kount, ThreatMetrix, etc.).
* Provide device fingerprint data for enhanced security.
* Implement custom device identification logic.
* Return cached fingerprint data if available.


#### Event data

This callback receives no parameters but should return fingerprint data.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onGetFingerprintResult: async () => {
    console.log('Fingerprint data requested');
    
    try {
      // Check if we have cached fingerprint data
      let fingerprintData = getCachedFingerprintData();
      
      if (!fingerprintData) {
        console.log('Generating new fingerprint data');
        
        // Generate new fingerprint using Kount
        if (window.ka && window.ka.collect) {
          fingerprintData = await new Promise((resolve, reject) => {
            window.ka.collect({
              sessionId: getKountSessionId(),
              onSuccess: (sessionId) => resolve(sessionId),
              onFailure: (error) => reject(error)
            });
          });
        } 
        // Generate fingerprint using ThreatMetrix
        else if (window.tmx && window.tmx.profiling) {
          fingerprintData = await window.tmx.profiling.generateSessionId();
        }
        // Fallback: Generate basic browser fingerprint
        else {
          fingerprintData = generateBasicFingerprint();
        }
        
        // Cache the fingerprint data for future use
        cacheFingerprintData(fingerprintData);
      }
      
      // Track successful fingerprint generation
      trackEvent('fingerprint-generated', {
        method: getFingerprintMethod(),
        sessionId: fingerprintData,
        cached: !!getCachedFingerprintData(),
        timestamp: new Date().toISOString()
      });
      
      console.log('Returning fingerprint data:', fingerprintData);
      return fingerprintData;
      
    } catch (error) {
      console.error('Fingerprint generation failed:', error);
      
      // Log the error
      logError('fingerprint-generation-failed', {
        error: error.message,
        stack: error.stack,
        timestamp: new Date().toISOString()
      });
      
      // Return a fallback fingerprint or empty string
      const fallbackFingerprint = generateFallbackFingerprint();
      
      trackEvent('fingerprint-fallback-used', {
        error: error.message,
        fallbackData: fallbackFingerprint,
        timestamp: new Date().toISOString()
      });
      
      return fallbackFingerprint;
    }
  }
});

// Helper functions for fingerprint generation
function generateBasicFingerprint() {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  ctx.textBaseline = 'top';
  ctx.font = '14px Arial';
  ctx.fillText('Basic fingerprint', 2, 2);
  
  const fingerprint = btoa(JSON.stringify({
    userAgent: navigator.userAgent,
    language: navigator.language,
    platform: navigator.platform,
    screenResolution: `${screen.width}x${screen.height}`,
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    canvas: canvas.toDataURL(),
    timestamp: Date.now()
  }));
  
  return fingerprint.substring(0, 32); // Truncate for consistency
}

function generateFallbackFingerprint() {
  return btoa(`fallback_${Date.now()}_${Math.random()}`).substring(0, 32);
}
```

### onOnceCardClick

This callback is triggered when a customer selects a saved card in the click-once component.

You can use it to:

* Update the UI to show the selected card.
* Enable or disable the CVC field based on card requirements.
* Track card selection patterns for analytics.
* Load card-specific settings or preferences.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | The card selection event data. |
| `data.tokenId`string | The ID of the selected card token. |
| `data.maskedCardNumber`string | The masked card number of the selected card. |
| `data.cardScheme`string | The card scheme identifier (e.g., `visa`, `mastercard`). |
| `data.cardSchemeName`string | User-friendly card scheme name (e.g., `Visa`, `MasterCard`). |
| `data.expiryDate`string | The card expiry date in MM/YY format. |
| `data.expiryMonth`string | The expiry month (MM). |
| `data.expiryYear`string | The expiry year (YY). |
| `data.isExpired`boolean | Whether the card is currently expired. |
| `data.expiresWithinMonths`number | The number of months until expiry (negative if expired). |
| `data.cardType`string | The card type (e.g., `credit`, `debit`, `prepaid`). |
| `data.fundingSource`string | The funding source type. |
| `data.issuerName`string | The name of the card issuing bank. |
| `data.issuerCountry`string | The country code of the card issuer. |
| `data.isCvcRequired`boolean | Whether CVC is required for this card. |
| `data.isDefault`boolean | Whether this is the customer's default payment method. |
| `data.lastUsedDate`string | The ISO date when this card was last used for payment. |
| `data.addedDate`string | The ISO date when this card was added to the vault. |
| `data.usageCount`number | The number of times this card has been used. |
| `data.metadata`object | Additional metadata associated with the token. |
| `data.metadata.customerNickname`string | The customer-provided nickname for the card (optional). |
| `data.metadata.verificationStatus`string | The verification status of the card. |


#### Example implementation


```typescript
const clickOnceComponent = sdk.create('click-once', {
  onOnceCardClick: (data) => {
    console.log('User clicked on saved card:', data.maskedCardNumber);
    
    // Update UI to show selected card
    updateSelectedCardDisplay(data);
    
    // Track card selection
    trackEvent('saved-card-selected', {
      tokenId: data.tokenId,
      cardScheme: data.cardScheme,
      maskedCardNumber: data.maskedCardNumber,
      timestamp: new Date().toISOString()
    });
    
    // Show/hide CVC field based on requirements
    if (data.isCvcRequired) {
      showCvcField();
      showMessage('Please enter your security code to continue');
      disableSubmitButton(); // Keep disabled until CVC is entered
    } else {
      hideCvcField();
      enableSubmitButton();
    }
    
    // Check if card is expired
    const now = new Date();
    const [month, year] = data.expiryDate.split('/');
    const expiryDate = new Date(2000 + parseInt(year), parseInt(month) - 1);
    
    if (expiryDate < now) {
      showWarning('This card has expired. Please update your payment method or select a different card.');
      disableSubmitButton();
      showUpdateCardOption(data.tokenId);
    }
    
    // Show card-specific benefits
    showCardBenefits(data.cardScheme);
    
    // Update payment summary
    updatePaymentSummary({
      paymentMethod: `${data.cardScheme} ending in ${data.maskedCardNumber.slice(-4)}`,
      requiresCvc: data.isCvcRequired
    });
  }
});
```

### onPostAuthentication

This callback is triggered after 3DS authentication is completed, providing the authentication identifier.

You can use it to:

* Retrieve full authentication result from your backend using the authenticationId.
* Evaluate authentication result to update authorisation decision via Unity update session API.
* Provide the decision to the SDK via `onPreAuthorization`.
* Log authentication results for fraud monitoring.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing authentication result. |
| `data.authenticationId`string | The unique identifier for the authentication attempt. |


Use the `authenticationId` with the [Get 3DS authentication details](/apis/three-d-secure-authentication/integrated-authentication/get-authentication-details) API to retrieve the full authentication results including transaction status, ECI values, and CAVV data.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPostAuthentication: async (data) => {
    console.log('3DS authentication completed. Authentication ID:', data.authenticationId);
    
    // Send authenticationId to merchant BE to retrieve authentication result
    const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
    
    // Merchant BE evaluates authentication result to update authorisation decision via Unity update decision API
    const authorisationDecision = await evaluateAuthenticationAndUpdateAuthorization(authResult);
    
    // Merchant BE returns the decision to merchant FE and provides the decision to the SDK via onPreAuthorization
    
    // Track authentication completion
    trackEvent('3ds-authentication-completed', {
      authenticationId: data.authenticationId,
          timestamp: new Date().toISOString()
        });
  }
});
```

### onPostInitiateAuthentication

This callback is triggered after 3DS authentication is initiated, providing the authentication identifier.

You can use it to:

* Retrieve the full `PreInitiateAuthentication` result from your backend using the `authenticationId`.
* Evaluate the `PreInitiateAuthentication` result to decide whether to proceed with authentication/authorisation.
* Update the authentication/authorisation decision via the [Modify session](/apis/session/other/modify-session) API.
* Track 3DS initiation success rates and performance.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing authentication initiation result. |
| `data.authenticationId`string | The unique identifier for the authentication session. |


Use the `authenticationId` with the [Get 3DS pre-initiate authentication details](/apis/three-d-secure-authentication/integrated-authentication/get-preinitiate-authentication-details) API to retrieve pre-authentication results including SCA mandates, exemptions, and 3DS support.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPostInitiateAuthentication: async (data) => {
    console.log('3DS authentication initiated. Authentication ID:', data.authenticationId);
    
    // Merchant evaluates the 3DS PreInitiateAuthentication Result to decide whether to proceed with Authentication/Authorisation
    // Merchant BE uses authenticationId to retrieve initiate authentication result
    const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
    
    // Merchant BE evaluates the PreInitiateAuthentication result to update authentication/authorisation decision via Unity update decision api if needed
    const decision = await evaluateAuthenticationAndUpdateSession(authResult);
    
    // Merchant BE returns the decision to merchant FE
    // Merchant FE answers to the onPreAuthentication callback whether to proceed with it
    
    // Track initiation result
    trackEvent('3ds-authentication-initiated', {
      authenticationId: data.authenticationId,
          timestamp: new Date().toISOString()
        });
  }
});
```

### onPreAuthentication

This callback is triggered before 3DS authentication starts, allowing you to provide authentication configuration or control whether to proceed.

You can use it to:

* Provide the authentication decision evaluated after onPostInitiateAuthentication.
* Configure 3DS authentication parameters if proceeding.
* Control whether to proceed with authentication by returning null.
* Set up merchant-specific authentication data.


#### Event data

This callback receives no parameters.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPreAuthentication: async () => {
    // Get authentication decision evaluated after onPostInitiateAuthentication 
    const decision = await getAuthenticationDecision();
    
    if (!decision) {
      // Not proceeded
      return null;
    }
    
    // Provide authentication configuration if proceeding
    return {
      challengeWindowSize: decision.challengeWindowSize,
      timeout: decision.timeout,
      // other configurations
    };
  }
});
```

### onPreDeleteToken

This callback is triggered before a token deletion attempt, allowing you to show custom confirmation dialogs or perform validation.

You can use it to:

* Show custom confirmation dialogs for token deletion.
* Validate whether the user has permission to delete the token.
* Check if the token is currently being used in active subscriptions.
* Log token deletion attempts for security auditing.


#### Event data

| Parameter | Description |
|  --- | --- |
| `token`BaseCardToken | The card token that will be deleted. |
| `token.gatewayTokenId`string | The unique identifier of the token. |
| `token.maskedPrimaryAccountNumber`string | The masked card number. |
| `token.scheme`string | The card scheme. |
| `token.expiryDate`string | The card expiry date. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onPreDeleteToken: async (token) => {
    console.log('Token deletion requested:', token.maskedPrimaryAccountNumber);
    
    // Track deletion attempt
    trackEvent('token-deletion-requested', {
      tokenId: token.gatewayTokenId,
      cardScheme: token.scheme,
      maskedCardNumber: token.maskedPrimaryAccountNumber,
      timestamp: new Date().toISOString()
    });
    
    try {
      // Check if token is used in active subscriptions
      const activeSubscriptions = await checkActiveSubscriptions(token.gatewayTokenId);
      
      if (activeSubscriptions.length > 0) {
        const subscriptionList = activeSubscriptions.map(sub => sub.name).join(', ');
        const confirmed = confirm(
          `This payment method is used for active subscriptions: ${subscriptionList}. ` +
          `Deleting it may affect these subscriptions. Are you sure you want to continue?`
        );
        
        if (!confirmed) {
          trackEvent('token-deletion-cancelled-subscriptions', {
            tokenId: token.gatewayTokenId,
            subscriptionCount: activeSubscriptions.length,
            timestamp: new Date().toISOString()
          });
          return false;
        }
      }
      
      // Show confirmation dialog
      const cardDescription = `${token.scheme} ending in ${token.maskedPrimaryAccountNumber.slice(-4)}`;
      const confirmed = confirm(`Are you sure you want to delete ${cardDescription}?`);
      
      if (confirmed) {
        // Log confirmation
        trackEvent('token-deletion-confirmed', {
          tokenId: token.gatewayTokenId,
          cardScheme: token.scheme,
          timestamp: new Date().toISOString()
        });
        
        // Show deletion in progress
        showMessage('Deleting payment method...', 'info');
        
        return true;
    } else {
        // Log cancellation
        trackEvent('token-deletion-cancelled', {
          tokenId: token.gatewayTokenId,
          timestamp: new Date().toISOString()
        });
        
        return false;
      }
      
    } catch (error) {
      console.error('Error during token deletion validation:', error);
      
      // Log validation error
      logError('token-deletion-validation-failed', {
        tokenId: token.gatewayTokenId,
        error: error.message,
        timestamp: new Date().toISOString()
      });
      
      // Show error and allow user to decide
      const confirmed = confirm(
        'Unable to check payment method usage. Do you still want to delete it?'
      );
      
      return confirmed;
    }
  }
});
```

### onPostInitiateAuthentication

This callback is triggered after 3DS authentication is initiated, providing the authentication identifier.

You can use it to:

* Retrieve the full `PreInitiateAuthentication` result from your backend using the `authenticationId`.
* Evaluate the `PreInitiateAuthentication` result to decide whether to proceed with authentication/authorisation.
* Update the authentication/authorisation decision via the [Modify session](/apis/session/other/modify-session) API.
* Track 3DS initiation success rates and performance.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing authentication initiation result. |
| `data.authenticationId`string | The unique identifier for the authentication session. |


Use the `authenticationId` with the [Get 3DS pre-initiate authentication details](/apis/three-d-secure-authentication/integrated-authentication/get-preinitiate-authentication-details) API to retrieve pre-authentication results including SCA mandates, exemptions, and 3DS support.


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPostInitiateAuthentication: async (data) => {
    console.log('3DS authentication initiated. Authentication ID:', data.authenticationId);
    
    // Merchant evaluates the 3DS PreInitiateAuthentication Result to decide whether to proceed with Authentication/Authorisation
    // Merchant BE uses authenticationId to retrieve initiate authentication result
    const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
    
    // Merchant BE evaluates the PreInitiateAuthentication result to update authentication/authorisation decision via Unity update decision api if needed
    const decision = await evaluateAuthenticationAndUpdateSession(authResult);
    
    // Merchant BE returns the decision to merchant FE
    // Merchant FE answers to the onPreAuthentication callback whether to proceed with it
    
    // Track initiation result
    trackEvent('3ds-authentication-initiated', {
      authenticationId: data.authenticationId,
      timestamp: new Date().toISOString()
    });
  }
});
```

### onPostTokenisation

This callback is triggered after card tokenisation completes with the tokenisation result.

You can use it to:

* Retrieve full token details from your backend using the gatewayTokenId.
* Evaluate token details to decide whether to proceed with authentication/authorisation.
* Update the user interface to show saved payment methods.
* Link the token to the customer's account.
* Log tokenisation events for analytics.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing tokenisation result. |
| `data.gatewayTokenId`string | The unique identifier for the tokenised card. |


Use the `gatewayTokenId` with the [Get masked card data related to gateway token](/apis/token-vault/other/get-masked-card-related-to-gateway-token) API to retrieve full token details including card scheme, funding source (credit/debit), masked PAN, and expiry date.

After evaluating token details and transaction data, use the [Modify session](/apis/session/other/modify-session) API to update the authorisation decision on your backend before returning from this callback. Set `"allowTransaction": true` in the session data to proceed with the transaction.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPostTokenisation: async (data) => {
    console.log('Card tokenised successfully. Token ID:', data.gatewayTokenId);
    
    // If merchant wants to evaluate the token details to decide whether to proceed with authentication/authorisation
    // Merchant BE uses the returned gatewayTokenId to get Token Details from Unity
    const tokenDetails = await fetchTokenDetailsFromBackend(data.gatewayTokenId);
    
    // Merchant BE evaluates the token details to update authentication/authorisation decision via Unity Update session API
    const decision = await evaluateTokenAndUpdateSession(tokenDetails);
    
    // Store token for future use
    await saveTokenToCustomerAccount({
      tokenId: data.gatewayTokenId,
      customerId: getCurrentCustomerId()
    });
    
    // Track tokenisation success for analytics
    trackEvent('card-tokenisation-success', {
      tokenId: data.gatewayTokenId,
      timestamp: new Date().toISOString()
    });
    
    // Update UI to show the card was saved
    showSuccessMessage('Your payment method has been saved securely!');
    
    // Add to saved cards list in UI
    updateSavedPaymentMethodsUI(data.gatewayTokenId);
  }
});
```

### onPreAuthentication

This callback is triggered before 3DS authentication starts, allowing you to provide authentication configuration or control whether to proceed.

You can use it to:

* Provide the authentication decision evaluated after onPostInitiateAuthentication.
* Configure 3DS authentication parameters if proceeding.
* Control whether to proceed with authentication by returning null.
* Set up merchant-specific authentication data.


#### Event data

This callback receives no parameters.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPreAuthentication: async () => {
    // Get authentication decision evaluated after onPostInitiateAuthentication 
    const decision = await getAuthenticationDecision();
    
    if (!decision) {
      // Not proceeded
      return null;
    }
    
    // Provide authentication configuration if proceeding
    return {
      challengeWindowSize: decision.challengeWindowSize,
      timeout: decision.timeout,
      // other configurations
    };
  }
});
```

### onPreAuthorisation

This callback is triggered before the transaction authorisation, allowing you to provide additional transaction data or control whether to proceed.

You can use it to:

* Retrieve token details from your backend using the `gatewayTokenId`.
* Update transaction decision on merchant backend.
* Integrate with Kount or other fraud detection services.
* Perform AVS (Address Verification System) checks.
* Apply business rules based on transaction amount or customer history.
* Control whether to proceed with authorisation by returning `null`.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing token information. |
| `data.gatewayTokenId`string | The token ID from the payment gateway. Use this ID to retrieve full token details from Unity backend and update transaction decision. |


Use the `gatewayTokenId` with the [Get masked card data related to gateway token](/apis/token-vault/other/get-masked-card-related-to-gateway-token) API to retrieve full token details including card scheme, funding source (credit/debit), masked PAN, and expiry date.

After evaluating token details and transaction data, use the [Modify session](/apis/session/other/modify-session) API to update the authorisation decision on your backend before returning from this callback. Set `"allowTransaction": true` in the session data to proceed with the transaction.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPreAuthorisation: async (data) => {
    console.log('Pre-authorisation for token:', data.gatewayTokenId);
    
    // Merchant can use gatewayTokenId to retrieve token details and update transaction decision on merchant BE
    const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);

    if (!transactionDecision) {
      // To not proceed
      return null;
    }

    // Perform pre-payment validation
    const deviceSessionId = await getKountSessionId();
    const isHighRisk = await checkCustomerRiskProfile();
    const customerTier = await getCustomerTier();
    
    // Get billing address if AVS is enabled
    const billingAddress = await getBillingAddress();
    
    return {
      addressVerification: billingAddress ? {
        countryCode: billingAddress.countryCode,
        houseNumberOrName: billingAddress.address,
        postalCode: billingAddress.postalCode,
        city: billingAddress.city,
        state: billingAddress.state
      } : undefined,
      riskScreeningData: {
        deviceSessionId: deviceSessionId,
        performRiskScreening: true,
        customData: {
          customerTier: customerTier,
          orderType: 'ecommerce',
          previousTransactionCount: await getPreviousTransactionCount(),
          riskScore: isHighRisk ? 'high' : 'low'
        }
      }
    };
  }
});
```

### onPreDeleteToken

This callback is triggered before a token deletion attempt, allowing you to show custom confirmation dialogs or perform validation.

You can use it to:

* Show custom confirmation dialogs for token deletion.
* Validate whether the user has permission to delete the token.
* Check if the token is currently being used in active subscriptions.
* Log token deletion attempts for security auditing.


#### Event data

| Parameter | Description |
|  --- | --- |
| `token`BaseCardToken | The card token that will be deleted. |
| `token.gatewayTokenId`string | The unique identifier of the token. |
| `token.maskedPrimaryAccountNumber`string | The masked card number. |
| `token.scheme`string | The card scheme. |
| `token.expiryDate`string | The card expiry date. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onPreDeleteToken: async (token) => {
    // Check if token is used in active subscriptions
    const hasActiveSubscriptions = await checkActiveSubscriptions(token.gatewayTokenId);
    
    if (hasActiveSubscriptions) {
      const confirmed = confirm(
        `This card is used for active subscriptions. Deleting it will cancel those subscriptions. Continue?`
      );
      if (!confirmed) return false;
    }
    
    // Show custom confirmation dialog
    const confirmed = confirm(
      `Delete ${token.scheme} card ending in ${token.maskedPrimaryAccountNumber.slice(-4)}?`
    );
    
    // Log deletion attempt for security auditing
    if (confirmed) {
      trackEvent('token-deletion-confirmed', {
        tokenId: token.gatewayTokenId,
        scheme: token.scheme,
        lastFour: token.maskedPrimaryAccountNumber.slice(-4)
      });
    }
    
    return confirmed;
  }
});
```

### onPreInitiateAuthentication

This callback is triggered before 3DS authentication initiation, allowing you to configure authentication parameters or control whether to proceed with authentication.

You can use it to:

* Configure 3DS authentication parameters and merchant data.
* Set up provider information for 3DS (acquirer profile is no longer required).
* Customise authentication timeout and retry behaviour.
* Control whether to proceed with 3DS authentication by returning null.


#### Event data

This callback receives no parameters.

#### Example implementation

**To proceed with PreInitiateAuthentication:**


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPreInitiateAuthentication: () => {
    // Configure 3DS authentication parameters
    return {
      providerId: 'your-3ds-provider-id', // optional
      requestorAuthenticationIndicator: '01', // Payment transaction
      timeout: 30 // 30 seconds timeout for fingerprinting
    };
  }
});
```

**If merchant doesn't want to proceed with PreInitiateAuthentication:**


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPreInitiateAuthentication: () => {
    // Return null to skip authentication
    return null;
  }
});
```

### onPreTokenisation

This callback is triggered before card tokenisation begins, allowing you to control whether tokenisation should proceed.

You can use it to:

* Validate card information before tokenisation.
* Check if the customer wants to save their payment method.
* Apply business rules for token storage.
* Implement custom security checks.


#### Event data

This callback receives no parameters.

#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onPreTokenisation: () => {
    console.log('About to tokenise card');
    
    // Check if user has opted in to save payment method
    const savePaymentMethod = document.getElementById('save-card-checkbox')?.checked;
    if (!savePaymentMethod) {
      console.log('User opted out of saving payment method');
      return false;
    }
    
    // Apply business rules
    const isValidForStorage = validateCardForStorage();
    if (!isValidForStorage) {
      console.log('Card not suitable for storage - skipping tokenisation');
      return false;
    }
    
    console.log('Proceeding with card tokenisation');
    return true;
  }
});
```

### onPreRenderTokens

This callback is triggered after tokens are retrieved but before they're rendered, allowing filtering and transformation. This callback is available for both the **card-on-file** and **click-once** components.

You can use it to:

* Filter tokens based on business rules or user preferences.
* Transform token display order and configuration.
* Set custom CVC requirements for specific tokens.
* Apply merchant-specific token visibility rules.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`RetrieveCardTokensReponseSuccess | The successful response from the token retrieval API. |
| `data.gatewayTokens`array | Array of gateway tokens retrieved from the vault. |
| `data.gatewayTokens[].gatewayTokenId`string | The unique identifier for the gateway token. |
| `data.gatewayTokens[].maskedPrimaryAccountNumber`string | The masked card number for display. |
| `data.gatewayTokens[].scheme`string | The card scheme. |
| `data.gatewayTokens[].expiryDate`string | The card expiry date. |
| `data.gatewayTokens[].issuerName`string | The name of the card issuing bank. |
| `data.gatewayTokens[].issuerCountryCode`string | The country code of the card issuer. |
| `data.schemeTokens`array | Array of scheme tokens retrieved from the vault (if applicable). |
| `data.correlationId`string | The correlation ID for tracking the request. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onPreRenderTokens: (data) => {
    // Filter and transform tokens based on business rules
    const filteredTokens = data.gatewayTokens
      .filter(token => {
        // Only show non-expired cards
        const currentDate = new Date();
        const expiryParts = token.expiryDate.split('/');
        const expiryDate = new Date(2000 + parseInt(expiryParts[1]), parseInt(expiryParts[0]) - 1);
        return expiryDate > currentDate;
      })
      .filter(token => {
        // Only show cards from supported countries
        const supportedCountries = ['US', 'GB', 'CA', 'AU'];
        return supportedCountries.includes(token.issuerCountryCode);
      })
      .sort((a, b) => {
        // Sort by scheme preference
        const schemeOrder = { 'Visa': 1, 'MasterCard': 2, 'Amex': 3 };
        return (schemeOrder[a.scheme] || 999) - (schemeOrder[b.scheme] || 999);
      });

    // Transform to required format with custom CVC requirements
    return filteredTokens.map(token => ({
        id: token.gatewayTokenId,
      isCvcRequired: token.scheme === 'Amex' ? false : true // Amex doesn't require CVC
      }));
  }
});
```

### onRetrieveTokensFailed

This callback is triggered when the initial request to fetch saved card tokens fails. This callback is available for both the **card-on-file** and **click-once** components.

You can use it to:

* Display error messages to users about token retrieval failures.
* Implement fallback UI when saved cards cannot be loaded.
* Track token retrieval failure rates for monitoring.
* Offer alternative payment methods when tokens are unavailable.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`RetrieveCardTokensReponseFailed | The failed response from the token retrieval API. |
| `data.error`object | The error details for the failed retrieval. |
| `data.error.message`string | A human-readable error message. |
| `data.error.code`string | The specific error code for programmatic handling. |
| `data.correlationId`string | The correlation ID for tracking the request. |
| `data.statusCode`number | The HTTP status code of the failed request. |
| `data.timestamp`string | When the error occurred, in ISO 8601 format. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onRetrieveTokensFailed: (data) => {
    console.error('Failed to retrieve saved cards:', data.error);
    
    // Track the error for analytics
    trackError('token-retrieval-failed', {
      errorCode: data.error.code,
      correlationId: data.correlationId,
      statusCode: data.statusCode
    });
    
    // Show user-friendly error message
    let errorMessage = 'Unable to load your saved payment methods.';
    if (data.error.code === 'NETWORK_ERROR') {
      errorMessage = 'Network connection issue. Please check your internet connection.';
    } else if (data.error.code === 'UNAUTHORIZED') {
      errorMessage = 'Please log in again to access your saved payment methods.';
    }
    
    showNotification(errorMessage, 'error');
    
    // Hide the saved cards section and show alternative options
    document.getElementById('saved-cards-section').style.display = 'none';
    document.getElementById('add-new-card-section').style.display = 'block';
  }
});
```

### onSelectToken

This callback is triggered when a user selects a saved card token from the card-on-file component.

You can use it to:

* Track which saved cards users prefer.
* Update your UI to reflect the selected payment method.
* Perform custom validation or checks when a card is selected.
* Display card-specific information or warnings.
* Pre-fill related form fields based on the selected card.


#### Event data

| Parameter | Description |
|  --- | --- |
| `token`BaseCardToken | The selected card token object. |
| `token.gatewayTokenId`string | The unique identifier for the token. |
| `token.maskedPrimaryAccountNumber`string | The masked card number (e.g., "•••• 4242"). |
| `token.expiryDate`string | The card expiry date in MM/YY format. |
| `token.scheme`string | The card scheme (e.g., "Visa", "Mastercard"). |
| `token.fundingSource`string | The funding source type ("Credit" or "Debit"). |
| `token.issuerName`string | The name of the card issuing bank. |
| `token.issuerCountryCode`string | The country code of the card issuer. |
| `token.lastSuccessfulPurchaseDate`string | The date of the last successful purchase (ISO 8601 format). |
| `token.lastSuccessfulPayoutDate`string | The date of the last successful payout (ISO 8601 format). |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onSelectToken: (token) => {
    console.log('Card selected:', token.maskedPrimaryAccountNumber);
    
    // Track which cards users prefer
    trackAnalyticsEvent('payment_method_selected', {
      scheme: token.scheme,
      fundingSource: token.fundingSource,
      issuerCountry: token.issuerCountryCode
    });
    
    // Update UI to show selected card details
    document.getElementById('selected-card-display').innerHTML = `
      <div class="selected-card">
        <span class="card-brand">${token.scheme}</span>
        <span class="card-number">${token.maskedPrimaryAccountNumber}</span>
        <span class="card-expiry">Expires ${token.expiryDate}</span>
      </div>
    `;
    
    // Show card-specific warnings or information
    if (token.fundingSource === 'Debit') {
      showNotification('Debit cards may have different processing times', 'info');
    }
    
    // Check if card is expiring soon
    const [month, year] = token.expiryDate.split('/');
    const expiryDate = new Date(2000 + parseInt(year), parseInt(month) - 1);
    const monthsUntilExpiry = (expiryDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24 * 30);
    
    if (monthsUntilExpiry < 2) {
      showNotification(
        'This card will expire soon. Consider updating your payment method.',
        'warning'
      );
    }
    
    // Pre-fill billing country based on issuer
    if (token.issuerCountryCode) {
      document.getElementById('billing-country').value = token.issuerCountryCode;
    }
    
    // Enable submit button
    document.getElementById('submit-button').disabled = false;
  }
});
```

### onSubmitError

This callback is triggered when submission fails due to PXP-specific errors or validation issues.

You can use it to:

* Display user-friendly error messages for submission failures.
* Implement retry logic for transient errors.
* Track submission error rates and patterns.
* Handle different types of submission errors appropriately.


#### Event data

| Parameter | Description |
|  --- | --- |
| `error`object | The error object containing submission failure details. |
| `error.message`string | A human-readable error message. |
| `error.errorCode`string | The specific error code for programmatic handling. |
| `error.correlationId`string | The correlation ID for tracking the request (if available). |
| `error.validationErrors`array | Array of field-specific validation errors (if applicable). |
| `error.timestamp`string | When the error occurred, in ISO 8601 format. |


#### Example implementation


```typescript
const cardSubmit = sdk.create('card-submit', {
  onSubmitError: (error) => {
    console.error('Card submission failed:', error);
    
    // Track the error for analytics
    trackError('card-submit-error', {
      errorCode: error.errorCode,
      correlationId: error.correlationId,
      timestamp: error.timestamp
    });
    
    // Handle different types of errors
    let userMessage = 'Payment submission failed. Please try again.';
    
    switch (error.errorCode) {
      case 'VALIDATION_FAILED':
        userMessage = 'Please check your card details and try again.';
        // Handle specific field validation errors
        if (error.validationErrors) {
          error.validationErrors.forEach(validationError => {
            highlightFieldError(validationError.field, validationError.message);
          });
        }
        break;
        
      case 'NETWORK_ERROR':
        userMessage = 'Network connection issue. Please check your internet connection and try again.';
        break;
        
      case 'CARD_DECLINED':
        userMessage = 'Your card was declined. Please try a different payment method.';
        break;
        
      case 'INSUFFICIENT_FUNDS':
        userMessage = 'Insufficient funds. Please try a different card or payment method.';
        break;
        
      case 'EXPIRED_CARD':
        userMessage = 'Your card has expired. Please update your card details.';
        break;
        
      default:
        userMessage = `Payment failed: ${error.message}`;
    }
    
    // Show error to user
    showErrorNotification(userMessage);
    
    // Re-enable submit button for retry
    document.getElementById('submit-button').disabled = false;
  }
});
```

### onUpdateTokenFailed

This callback is triggered when updating card token information (e.g., expiry date) fails.

You can use it to:

* Display error messages for failed token updates.
* Implement retry logic for token update failures.
* Track token update failure rates for monitoring.
* Handle different types of update errors appropriately.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`UpdateCardTokenResponseFailed | The failed response from the token update API. |
| `data.error`object | The error details for the failed update. |
| `data.error.message`string | A human-readable error message. |
| `data.error.code`string | The specific error code for programmatic handling. |
| `data.tokenId`string | The ID of the token that failed to be updated. |
| `data.correlationId`string | The correlation ID for tracking the request. |
| `data.statusCode`number | The HTTP status code of the failed request. |
| `data.timestamp`string | When the error occurred, in ISO 8601 format. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onUpdateTokenFailed: (data) => {
    console.error('Token update failed:', data.error);
    
    // Track the error for analytics
    trackError('token-update-failed', {
      errorCode: data.error.code,
      tokenId: data.tokenId,
      correlationId: data.correlationId
    });
    
    // Handle different types of errors
    let errorMessage = 'Failed to update card details. Please try again.';
    
    switch (data.error.code) {
      case 'INVALID_EXPIRY_DATE':
        errorMessage = 'Please enter a valid expiry date.';
        break;
      case 'TOKEN_NOT_FOUND':
        errorMessage = 'This payment method no longer exists.';
        break;
      case 'UNAUTHORIZED':
        errorMessage = 'You are not authorised to update this payment method.';
        break;
      case 'NETWORK_ERROR':
        errorMessage = 'Network connection issue. Please try again.';
        break;
    }
    
    // Show error notification
    showErrorNotification(errorMessage);
    
    // Revert UI changes if update failed
    document.getElementById(`token-${data.tokenId}`).classList.remove('updating');
    
    // Show retry button
    const retryButton = document.getElementById(`retry-update-${data.tokenId}`);
    if (retryButton) {
      retryButton.style.display = 'block';
    }
  }
});
```

### onUpdateTokenSuccess

This callback is triggered when card token information is successfully updated.

You can use it to:

* Display success messages for token updates.
* Update the UI to reflect the changes.
* Track successful token updates for analytics.
* Sync updated token information with local storage.


#### Event data

| Parameter | Description |
|  --- | --- |
| `data`UpdateCardTokenResponseSuccess | The successful response from the token update API. |
| `data.tokenId`string | The ID of the successfully updated token. |
| `data.maskedCardNumber`string | The masked card number of the updated token. |
| `data.cardScheme`string | The card scheme of the updated token. |
| `data.updatedFields`object | The fields that were updated. |
| `data.updatedFields.expiryMonth`string | The new expiry month (if updated). |
| `data.updatedFields.expiryYear`string | The new expiry year (if updated). |
| `data.correlationId`string | The correlation ID for tracking the request. |
| `data.timestamp`string | When the update was completed, in ISO 8601 format. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  onUpdateTokenSuccess: (data) => {
    console.log('Token updated successfully:', data);
    
    // Track the success for analytics
    trackEvent('token-update-success', {
      tokenId: data.tokenId,
      correlationId: data.correlationId,
      updatedFields: Object.keys(data.updatedFields)
    });
    
    // Show success message
    showSuccessNotification('Payment method updated successfully!');
    
    // Update the UI to reflect changes
    const tokenElement = document.getElementById(`token-${data.tokenId}`);
    if (tokenElement) {
      tokenElement.classList.add('updated');
      
      // Update expiry display if it was changed
      if (data.updatedFields.expiryMonth && data.updatedFields.expiryYear) {
        const expiryElement = tokenElement.querySelector('.expiry-date');
        if (expiryElement) {
          expiryElement.textContent = `${data.updatedFields.expiryMonth}/${data.updatedFields.expiryYear}`;
        }
      }
    }
    
    // Update local storage with new token data
    updateLocalTokenData(data.tokenId, data.updatedFields);
    
    // Remove any error states
    document.getElementById(`token-${data.tokenId}`).classList.remove('error', 'updating');
  }
});
```

### onValidation

This callback is triggered when form validation occurs.

You can use it to:

* Handle validation results for all form fields.
* Display custom error messages based on validation results.
* Track validation patterns and field-specific error rates.
* Implement custom validation logic or additional checks.


#### Event data

| Parameter | Description |
|  --- | --- |
| `validationResults`ValidationResult[] | Array of validation results for all validated fields. |
| `validationResults[].valid`boolean | Whether the field passed validation. |
| `validationResults[].isNotValidated`boolean | Whether the field was not validated (optional field that's empty). |
| `validationResults[].errors`object | Object containing field-specific errors (if validation failed). |
| `validationResults[].errors[fieldName]`object | Error details for a specific field. |
| `validationResults[].errors[fieldName].code`string | The error code for programmatic handling. |
| `validationResults[].errors[fieldName].message`string | A human-readable error message. |


#### Example implementation


```typescript
const newCard = sdk.create('new-card', {
  onValidation: (validationResults) => {
    console.log('Card form validation:', validationResults);
    
    let allValid = true;
    let errorCount = 0;
    
    validationResults.forEach(result => {
      if (!result.valid && !result.isNotValidated) {
        allValid = false;
        errorCount++;
        
        // Handle field-specific errors
        if (result.errors) {
        Object.keys(result.errors).forEach(fieldName => {
          const error = result.errors[fieldName];
            
            // Track validation errors for analytics
            trackEvent('validation-error', {
              field: fieldName,
              errorCode: error.code,
              errorMessage: error.message
            });
            
            // Show custom error message
            showFieldError(fieldName, error.message);
          console.log(`${fieldName}: ${error.message} (${error.code})`);
        });
        }
      }
    });
    
    // Update form validity state
    const submitButton = document.getElementById('submit-button');
    if (submitButton) {
      submitButton.disabled = !allValid;
      submitButton.classList.toggle('valid', allValid);
    }
    
    // Update overall form status
    const formStatus = document.getElementById('form-status');
    if (formStatus) {
      if (allValid) {
        formStatus.textContent = 'All fields are valid';
        formStatus.className = 'status-valid';
      } else {
        formStatus.textContent = `${errorCount} field(s) need attention`;
        formStatus.className = 'status-invalid';
      }
    }
  }
});
```

### onCustomValidation

This callback is triggered before transaction submission to allow merchants to validate their own fields alongside SDK component validation. It enables simultaneous validation of merchant-owned fields (shipping address, email, terms acceptance) and SDK components (billing address), eliminating the two-phase validation flow.

You can use it to:

* Validate merchant-owned form fields before payment submission.
* Display all validation errors (merchant + SDK) simultaneously on first submit attempt.
* Improve customer experience by eliminating multi-attempt validation.
* Validate terms and conditions acceptance.
* Perform custom business rule validation.
* Make async validation calls to external services.


Without `onCustomValidation`, validation happens sequentially: merchant fields are validated first, and only if they pass does SDK validation occur. This creates a poor user experience where customers discover SDK errors only after fixing merchant field errors. With `onCustomValidation`, all validation happens simultaneously.

#### Return value

| Return value | Description |
|  --- | --- |
| `Promise<boolean>` | Return `true` to proceed with submission, `false` to prevent submission. |


#### Use with different card modes

The `onCustomValidation` callback works with all card integration modes:

* **New card:** Validates merchant fields alongside card and billing address validation
* **Click-once (card on file with CVC re-entry):** Validates merchant fields with CVC and billing address validation
* **Card on file:** Validates merchant fields with saved card and billing address validation


#### Example implementation

##### Basic validation


```typescript
const cardSubmit = sdk.create('card-submit', {
  cardNumberComponent: cardNumber,
  cardExpiryDateComponent: cardExpiry,
  cardCvcComponent: cardCvc,
  billingAddressComponents: {
    billingAddressComponent: billingAddress
  },
  avsRequest: true,
  onCustomValidation: async () => {
    let allValid = true;
    
    // Clear previous error messages
    clearAllErrors();
    
    // Validate shipping address
    const shippingAddress = document.getElementById('shipping-address').value;
    if (!shippingAddress || shippingAddress.trim().length < 5) {
      showFieldError('shipping-address', 'Please enter a complete shipping address');
      allValid = false;
    }
    
    // Validate email
    const email = document.getElementById('email').value;
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!email || !emailRegex.test(email)) {
      showFieldError('email', 'Please enter a valid email address');
      allValid = false;
    }
    
    // Validate terms acceptance
    const termsAccepted = document.getElementById('terms-checkbox').checked;
    if (!termsAccepted) {
      showFieldError('terms', 'You must accept the terms and conditions');
      allValid = false;
    }
    
    // Return false to prevent submission if validation failed
    // SDK will still run its own validation and display billing address errors
    return allValid;
  },
  onPreAuthorisation: async (data) => {
    return { psd2Data: {} };
  },
  onPostAuthorisation: (result) => {
    console.log('Payment completed:', result.merchantTransactionId);
    window.location.href = '/success';
  }
});
```

##### Advanced validation with async checks


```typescript
const cardSubmit = sdk.create('card-submit', {
  cardNumberComponent: cardNumber,
  cardExpiryDateComponent: cardExpiry,
  cardCvcComponent: cardCvc,
  billingAddressComponents: {
    billingAddressComponent: billingAddress
  },
  avsRequest: true,
  onCustomValidation: async () => {
    // Clear previous errors
    clearAllErrors();
    
    // Track validation results
    const validationResults = {
      shipping: false,
      email: false,
      phone: false,
      terms: false,
      inventory: false
    };
    
    // Validate shipping address with external API
    const shippingAddress = getShippingAddress();
    try {
      const addressValid = await validateAddressWithAPI(shippingAddress);
      if (addressValid) {
        validationResults.shipping = true;
      } else {
        showFieldError('shipping-address', 'Invalid shipping address. Please verify the address.');
      }
    } catch (error) {
      console.error('Address validation failed:', error);
      showFieldError('shipping-address', 'Unable to validate address. Please check your entry.');
    }
    
    // Validate email format
    const email = getEmail();
    if (isValidEmail(email)) {
      validationResults.email = true;
    } else {
      showFieldError('email', 'Please enter a valid email address.');
    }
    
    // Validate phone number
    const phone = getPhone();
    const phoneRegex = /^\+?[\d\s\-()]{10,}$/;
    if (phone && phoneRegex.test(phone)) {
      validationResults.phone = true;
    } else {
      showFieldError('phone', 'Please enter a valid phone number.');
    }
    
    // Validate terms and conditions
    if (isTermsAccepted()) {
      validationResults.terms = true;
    } else {
      showFieldError('terms', 'You must accept the terms and conditions to proceed.');
    }
    
    // Check product inventory
    try {
      const inStock = await checkInventory();
      if (inStock) {
        validationResults.inventory = true;
      } else {
        showGeneralError('Some items in your cart are no longer available. Please update your order.');
      }
    } catch (error) {
      console.error('Inventory check failed:', error);
      showGeneralError('Unable to verify product availability. Please try again.');
    }
    
    // Return overall validation result
    return Object.values(validationResults).every(result => result === true);
  },
  onPreAuthorisation: async (data) => {
    return { psd2Data: {} };
  },
  onPostAuthorisation: (result) => {
    console.log('Payment completed:', result.merchantTransactionId);
    window.location.href = `/success?txn=${result.merchantTransactionId}`;
  },
  onSubmitError: (error) => {
    console.error('Payment error:', error.message);
    showGeneralError('Payment failed. Please check your details and try again.');
  }
});

// Helper functions
function clearAllErrors() {
  document.querySelectorAll('.error-message').forEach(el => {
    el.textContent = '';
    el.style.display = 'none';
  });
  document.querySelectorAll('.field-error').forEach(el => {
    el.classList.remove('field-error');
  });
}

function showFieldError(fieldId, message) {
  const errorElement = document.getElementById(`${fieldId}-error`);
  if (errorElement) {
    errorElement.textContent = message;
    errorElement.style.display = 'block';
  }
  const fieldElement = document.getElementById(fieldId);
  if (fieldElement) {
    fieldElement.classList.add('field-error');
  }
}

async function validateAddressWithAPI(address) {
  const response = await fetch('/api/validate-address', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(address)
  });
  const result = await response.json();
  return result.valid === true;
}

async function checkInventory() {
  const cartItems = getCartItems();
  const response = await fetch('/api/check-inventory', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ items: cartItems })
  });
  const result = await response.json();
  return result.allInStock === true;
}
```

##### Validation with click-once component


```typescript
const clickOnce = sdk.create('click-once', {
  isCvcRequired: true,
  cardSubmitComponentConfig: {
    billingAddressComponents: {
      billingAddressComponent: billingAddress
    },
    avsRequest: true,
    onCustomValidation: async () => {
      // Validate merchant fields for click-once flow
      let isValid = true;
      
      const email = document.getElementById('email').value;
      if (!email || !isValidEmail(email)) {
        showFieldError('email', 'Valid email required');
        isValid = false;
      }
      
      const terms = document.getElementById('terms-checkbox').checked;
      if (!terms) {
        showFieldError('terms', 'Please accept the terms');
        isValid = false;
      }
      
      return isValid;
    }
  }
});
```

##### Validation with card on file


```typescript
const cardOnFile = sdk.create('card-on-file', {
  isCvcRequired: true
});

const cardSubmit = sdk.create('card-submit', {
  useCardOnFile: true,
  cardOnFileComponent: cardOnFile,
  billingAddressComponents: {
    billingAddressComponent: billingAddress
  },
  avsRequest: true,
  onCustomValidation: async () => {
    // Validate merchant fields for card-on-file flow
    return await validateShippingAndContactInfo();
  }
});
```

### onValidationFailed

This callback is triggered when validation fails for a single field.

You can use it to:

* Display field-specific error messages when validation fails.
* Track validation failure patterns for analytics.
* Implement custom error handling for specific field types.
* Update UI to highlight invalid fields.


#### Event data

| Parameter | Description |
|  --- | --- |
| `validationResults`ValidationResult[] | Array of validation results for the failed field. |
| `validationResults[].valid`boolean | Always `false` for this event. |
| `validationResults[].errors`object | Object containing field-specific errors. |
| `validationResults[].errors[fieldName]`object | Error details for the specific field that failed. |
| `validationResults[].errors[fieldName].code`string | The error code for programmatic handling. |
| `validationResults[].errors[fieldName].message`string | A human-readable error message. |


#### Example implementation


```typescript
const component = sdk.create('card-number', {
  onValidationFailed: (validationResults) => {
    console.log('Validation failed:', validationResults);
    
    validationResults.forEach(result => {
      if (result.errors) {
        Object.keys(result.errors).forEach(fieldName => {
          const error = result.errors[fieldName];
          
          // Track validation failure for analytics
          trackEvent('field-validation-failed', {
            field: fieldName,
            errorCode: error.code,
            errorMessage: error.message
          });
          
          // Show field-specific error styling
          const fieldElement = document.getElementById(fieldName);
          if (fieldElement) {
            fieldElement.classList.add('error');
            fieldElement.setAttribute('aria-invalid', 'true');
          }
          
          // Display error message near the field
          showFieldErrorMessage(fieldName, error.message);
        });
      }
    });
  }
});
```

### onValidationPassed

This callback is triggered when validation is successful for a single field.

You can use it to:

* Display success indicators when field validation passes.
* Track validation success patterns for analytics.
* Clear previous error states and messages.
* Update UI to show valid field status.


#### Event data

| Parameter | Description |
|  --- | --- |
| `validationResults`ValidationResult[] | Array of validation results for the successful field. |
| `validationResults[].valid`boolean | Always `true` for this event. |
| `validationResults[].isNotValidated`boolean | Whether the field was actually validated or just not required. |
| `validationResults[].errors`object | The error object. Empty object since validation passed. |


#### Example implementation


```typescript
const component = sdk.create('card-number', {
  onValidationPassed: (validationResults) => {
    console.log('Validation passed:', validationResults);
    
    validationResults.forEach(result => {
      if (result.valid) {
        const fieldElement = document.querySelector('[data-field-type="card-number"]');
        
        if (fieldElement) {
          // Remove error states
          fieldElement.classList.remove('error');
          fieldElement.classList.add('valid');
          fieldElement.setAttribute('aria-invalid', 'false');
          
          // Clear any error messages
          const errorElement = fieldElement.parentElement?.querySelector('.error-message');
          if (errorElement) {
            errorElement.remove();
          }
          
          // Show success indicator
          const successIcon = fieldElement.parentElement?.querySelector('.success-icon');
          if (successIcon) {
            successIcon.style.display = 'block';
          }
        }
        
        // Track successful validation for analytics
        trackEvent('field-validation-passed', {
          field: 'card-number',
          wasNotValidated: result.isNotValidated || false
        });
      }
    });
  }
});
```

### tokenItemBuilder

This callback is called when each token item's user interface is being constructed, allowing you to customise the HTML layout.

You can use it to:

* Create custom layouts for saved card tokens.
* Implement responsive designs for different screen sizes.
* Add custom branding and styling to token displays.
* Include additional elements like badges or status indicators.


#### Event data

| Parameter | Description |
|  --- | --- |
| `elementIds`TokenBuilderElementIds | Object containing all the element IDs that need to be included in the HTML. |
| `elementIds.tokenImageId`string | The ID for the card brand image element. |
| `elementIds.tokenLabelId`string | The ID for the token label text element. |
| `elementIds.expiryDateId`string | The ID for the expiry date display element. |
| `elementIds.editButtonId`string | The ID for the edit button element. |
| `elementIds.deleteButtonId`string | The ID for the delete button element. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  tokenItemBuilder: (elementIds) => {
    return `
      <div class="custom-token-card" style="
        border: 1px solid #e1e5e9;
        border-radius: 8px;
        padding: 16px;
        margin-bottom: 12px;
        background: white;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        transition: all 0.2s ease;
      " onmouseover="this.style.boxShadow='0 4px 8px rgba(0,0,0,0.15)'" 
         onmouseout="this.style.boxShadow='0 2px 4px rgba(0,0,0,0.1)'">
        
        <div style="display: flex; align-items: center; justify-content: space-between;">
          <!-- Left side: Card info -->
          <div style="display: flex; align-items: center; flex: 1;">
            <div id="${elementIds.tokenImageId}" style="
              width: 48px;
              height: 32px;
              margin-right: 12px;
              display: flex;
              align-items: center;
              justify-content: center;
            "></div>
            
            <div style="flex: 1;">
              <div id="${elementIds.tokenLabelId}" style="
                font-weight: 600;
                font-size: 14px;
                color: #2c3e50;
                margin-bottom: 4px;
              "></div>
              <div id="${elementIds.expiryDateId}" style="
                font-size: 12px;
                color: #7f8c8d;
              "></div>
            </div>
          </div>
          
          <!-- Right side: Actions -->
          <div style="display: flex; gap: 8px;">
            <div id="${elementIds.editButtonId}" style="
              cursor: pointer;
              padding: 6px;
              border-radius: 4px;
              transition: background-color 0.2s;
            " onmouseover="this.style.backgroundColor='#f8f9fa'" 
               onmouseout="this.style.backgroundColor='transparent'"></div>
               
            <div id="${elementIds.deleteButtonId}" style="
              cursor: pointer;
              padding: 6px;
              border-radius: 4px;
              transition: background-color 0.2s;
            " onmouseover="this.style.backgroundColor='#fee'" 
               onmouseout="this.style.backgroundColor='transparent'"></div>
          </div>
        </div>
      </div>
    `;
  }
});
```

### tokenLabelBuilder

This callback is called to generate the display text for each card token. It replaces the default masked card number.

You can use it to:

* Customise how card information is displayed to users.
* Include additional card details like issuer name or card type.
* Implement localised or branded token labels.
* Add custom formatting or styling to card displays.


#### Event data

| Parameter | Description |
|  --- | --- |
| `token`BaseCardToken | The card token object containing all available card information. |
| `token.gatewayTokenId`string | The unique identifier for the gateway token (if gateway token). |
| `token.schemeTokenId`string | The unique identifier for the scheme token (if scheme token). |
| `token.maskedPrimaryAccountNumber`string | The masked card number for display. |
| `token.scheme`string | The card scheme. |
| `token.expiryDate`string | The card expiry date in MM/YY format. |
| `token.issuerName`string | The name of the card issuing bank. |
| `token.issuerCountryCode`string | The country code of the card issuer. |
| `token.fundingSource`string | The funding source type. |


#### Example implementation


```typescript
const cardOnFileComponent = sdk.create('card-on-file', {
  tokenLabelBuilder: (token) => {
    // Create a comprehensive, user-friendly label
    const cardEnding = token.maskedPrimaryAccountNumber.slice(-4);
    const brandName = token.scheme || 'Card';
    const issuer = token.issuerName ? ` - ${token.issuerName}` : '';
    const fundingType = token.fundingSource ? ` (${token.fundingSource})` : '';
    
    // Check if card is expiring soon
    const currentDate = new Date();
    const [month, year] = token.expiryDate.split('/');
    const expiryDate = new Date(2000 + parseInt(year), parseInt(month) - 1);
    const monthsUntilExpiry = (expiryDate.getFullYear() - currentDate.getFullYear()) * 12 + 
                             (expiryDate.getMonth() - currentDate.getMonth());
    
    let expiryWarning = '';
    if (monthsUntilExpiry <= 2 && monthsUntilExpiry > 0) {
      expiryWarning = ' ⚠️ Expires Soon';
    } else if (monthsUntilExpiry <= 0) {
      expiryWarning = ' ❌ Expired';
    }
    
    return `${brandName} •••• ${cardEnding}${issuer}${fundingType}${expiryWarning}`;
  }
});
```

## Common scenarios

### Handle a declined transaction

The following snippet is an example of how you might handle a declined transaction when using the card submit component.


```typescript
const cardSubmitComponent = sdk.create('card-submit', {
  onPostAuthorisation: async (data) => {
    console.log('Authorisation completed');
    console.log('Merchant Transaction ID:', data.merchantTransactionId);
    console.log('System Transaction ID:', data.systemTransactionId);
    
    // Get authorisation result from merchant backend
    const submitResult = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
    
    console.log('Transaction result:', submitResult);
    
    if (submitResult.status === 'Refused') {
      // Transaction was declined by the issuer or payment provider
      const refusedResult = submitResult as RefusedSubmitResult;
      
      // Handle the declined transaction
      handleDeclinedTransaction(refusedResult);
      
    } else if (submitResult.status === 'Authorised') {
      // Transaction was successful
      handleSuccessfulTransaction(submitResult);
      
    } else if (submitResult.status === 'Error') {
      // System error occurred
      handleSystemError(submitResult);
      
    } else if (submitResult.status === 'Pending') {
      // Transaction is pending further processing
      handlePendingTransaction(submitResult);
    }
  }
});

function handleDeclinedTransaction(refusedResult) {
  // Extract decline information
  const stateData = refusedResult.stateData;
  const providerResponse = refusedResult.providerResponse;
  const fundingData = refusedResult.fundingData;
  
  // Log decline details
  console.log('Transaction declined:', {
    stateCode: stateData?.code,
    stateMessage: stateData?.message,
    providerCode: providerResponse?.code,
    providerMessage: providerResponse?.message,
    merchantAdvice: providerResponse?.merchantAdvice
  });
  
  // Show user-friendly error message based on decline reason
  const declineCode = providerResponse?.code;
  let userMessage = 'Your payment was declined. Please try again or use a different payment method.';
  
  switch (declineCode) {
    case 'INSUFFICIENT_FUNDS':
      userMessage = 'Your card has insufficient funds. Please try a different payment method.';
      break;
    case 'EXPIRED_CARD':
      userMessage = 'Your card has expired. Please check your card details or use a different card.';
      break;
    case 'INVALID_CVV':
      userMessage = 'The security code (CVV) is incorrect. Please check and try again.';
      break;
    case 'CARD_BLOCKED':
      userMessage = 'Your card is blocked. Please contact your bank or use a different payment method.';
      break;
    case 'LIMIT_EXCEEDED':
      userMessage = 'Transaction exceeds your card limit. Please try a smaller amount or different card.';
      break;
    default:
      // Use merchant advice if available
      if (providerResponse?.merchantAdvice?.message) {
        userMessage = providerResponse.merchantAdvice.message;
      }
  }
  
  // Display error to user
  showErrorMessage(userMessage);
  
  // Check if retry is recommended
  if (providerResponse?.merchantAdvice?.code === 'RETRY') {
    enableRetryOption();
  } else if (providerResponse?.merchantAdvice?.code === 'DO_NOT_RETRY') {
    disableRetryOption();
    suggestAlternativePayment();
  }
  
  // Log analytics event for declined transaction
  logDeclineAnalytics(declineCode, stateData?.code);
}
```

### Handle an authentication failure

The following snippet is an example of how you might handle authentication problems when using the card submit component.


```typescript
const cardSubmitComponent = sdk.create('card-submit', {
  onSubmitError: (error) => {
    console.log('Submit error occurred:', error);
    
    // Handle different types of authentication and processing errors
    switch (error.ErrorCode) {
      case 'SDK0503': // Transaction authentication rejected
        showErrorMessage('Authentication was rejected by your bank. Please try again or contact your bank.');
        enableRetryOption();
        break;
        
      case 'SDK0505': // Transaction authentication failed
        showErrorMessage('Authentication failed. Please try again or contact your bank for assistance.');
        enableRetryOption();
        break;
        
      case 'SDK0504': // SCA exemption required
        showErrorMessage('Additional authentication is required. Please complete the verification process.');
        // Handle SCA exemption flow
        break;
        
      default:
        // Generic error handling
        showErrorMessage('Payment processing failed. Please try again.');
        logError(error);
    }
  }
});
```