# 3DS transactions

Integrate 3D Secure (3DS) into your checkout.

## Overview

By implementing 3DS authentication into your payment flow, you benefit from:

* **Additional security:** 3DS adds multiple layers of authentication and risk assessment.
* **Liability shift:** Successful 3DS authentication typically shifts fraud liability from merchant to card issuer.
* **Higher success rate:** Banks are more likely to approve 3DS-authenticated transactions.


However, the 3DS payment flow is longer than the non-3DS one due to the additional authentication steps. It may also require active customer participation if a challenge is presented.

## Payment flow

The 3D Secure flow is made up of nine key steps.

### Step 1: Submission

The customer clicks the submit button or the payment is triggered programmatically. The `submitAsync()` method in `CardSubmitComponent` is invoked and validation occurs inside itand validation occurs inside it. If validation passes, the method continues with the payment processing. If it fails, the method exits early and doesn't proceed with the transaction.

### Step 2: Card tokenisation

If it's a new card, the SDK sends the card details to the tokenisation service. If it's a saved card, the SDK retrieves the existing token.

### Step 3: Evaluation

The SDK evaluates whether 3DS authentication is required. In this flow, 3DS is required based on factors like transaction amount, risk assessment, or regulatory requirements.

### Step 4: Pre-initiation

This is the initial step in the 3DS authentication flow. It establishes the authentication session by sending transaction and card details to the payment processor.

This step has two associated callbacks:

* `onPreInitiateAuthentication`: Returns configuration for the authentication setup.
* `onPostInitiateAuthentication`: Receives the result of the pre-initiation call.


### Step 5: Fingerprinting

During this step, device information and browser characteristics are collected by the fingerprinting component. It creates a hidden iframe that submits transaction details to the issuer's fingerprint URL, enabling risk assessment based on the user's device profile.

### Step 6: Authentication

The 3DS server evaluates the transaction risk and determines the authentication path:

* **Frictionless flow:** If the transaction is low-risk, authentication completes automatically without customer interaction.
* **Challenge flow:** If additional verification is needed, the customer completes the 3DS authentication challenge (PIN entry, SMS code, biometric verification, etc.)


The authentication step has two associated callbacks:

* `onPreAuthentication`: Configures the main authentication parameters.
* `onPostAuthentication`: Receives authentication results and challenge data.


### Step 7: Authentication result

The SDK receives the 3DS authentication result indicating whether authentication was successful, failed, or requires additional action.

### Step 8: Authorisation

This is the final step of the 3DS authentication flow. You receive the transaction data along with the 3DS authentication results and decide whether to proceed. At this point, you can still add additional data or cancel the transaction entirely. The SDK then sends the authorisation request to the payment gateway, including the 3DS authentication data.

The authorisation step has two associated callbacks:

* `onPreAuthorisation`: Provides final transaction data, including 3DS authentication results. This is your last chance to modify the transaction before authorisation.
* `onPostAuthorisation`: Receives the transaction identifiers (`merchantTransactionId` and `systemTransactionId`). Use these identifiers to call the Unity Backend and retrieve the full transaction outcome.


The `onPostAuthorisation` callback only returns transaction identifiers, not the full transaction result. To obtain the complete transaction outcome (`Authorised`, `Captured`, `Refused`, etc.), you must call the Unity Backend using the [Get transaction details](/apis/transaction/other/get-transaction-details) API with the `systemTransactionId`.

### Step 9: Authorisation result

You receive the final authorisation response from the payment gateway. The transaction is either approved or declined and final transaction details are available, along with 3DS authentication confirmation.

## Implementation

### Before you start

To use 3D Secure in your application, you first need to enable it in the Unity Portal:

1. In the [Unity Portal](https://portal.pxp.io), go to **Merchant setup > Merchant groups**.
2. Select a merchant group.
3. Click the **Services** tab.
4. Click **Edit** in the *Card service* row.
5. Click **Configure modules** in the top right.
6. Click the toggle next to *ThreeD secure service*.


You'll also need to get the following from your payment processor:

* `acquirerProfileId`: Your acquirer profile identifier.
* `providerId`: Your 3DS provider identifier.
* Test credentials for the sandbox environment.


### Step 1: Configure your SDK

To start, set up your `sdkConfig` to include the `shopper` object.


```typescript
const sdkConfig = {
  transactionData: {
    amount: 99.99,
    currency: 'USD',
    entryType: 'Ecom',
    intent: {
      card: 'Authorisation',
      paypal: 'Authorisation'
    },
    merchantTransactionId: 'order-123',
    merchantTransactionDate: () => new Date().toISOString()
  },
  // Your other SDK configuration
};
```

### Step 2: Implement callbacks

Next, implement your chosen callbacks. Note that some are required.


```typescript
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  // OPTIONAL: Use Unity authentication strategy
  useUnityAuthenticationStrategy: true,
  
  // REQUIRED: Provide 3DS configuration
  onPreInitiateAuthentication: () => {
    return {
      providerId: "your_3ds_provider_id", // optional
      requestorAuthenticationIndicator: '01',
      timeout: 120
    };
  },

  // OPTIONAL: Handle the pre-initiation result
  onPostInitiateAuthentication: async (data) => {
    console.log('3DS pre-initiation completed. Authentication ID:', data.authenticationId);
    
    // Your backend uses authenticationId to retrieve initiate authentication result
    const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
    
    // Your backend evaluates the PreInitiateAuthentication result to update authentication/authorisation decision
    const decision = await evaluateAuthenticationAndUpdateSession(authResult);
  },
  
  // REQUIRED: Configure main authentication
  onPreAuthentication: async () => {
    // Get authentication decision evaluated after onPostInitiateAuthentication
    const decision = await getAuthenticationDecision();
    
    if (!decision) {
      // Not proceeding with authentication
      return null;
    }
    
    return {
      merchantCountryNumericCode: "840",
      merchantLegalName: "Your company name",
      challengeWindowSize: decision.challengeWindowSize || 4,
      requestorChallengeIndicator: decision.challengeIndicator || "01",
      timeout: decision.timeout || 300
    };
  },

  // OPTIONAL: Handle the authentication result
  onPostAuthentication: async (data) => {
    console.log('3DS authentication completed. Authentication ID:', data.authenticationId);
    
    // Send authenticationId to your backend to retrieve authentication result
    const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
    
    // Your backend evaluates authentication result to update authorisation decision
    const authorisationDecision = await evaluateAuthenticationAndUpdateAuthorization(authResult);
  },
  
  // REQUIRED: Final transaction approval
  onPreAuthorisation: async (data) => {
    console.log('Pre-authorisation for token:', data.gatewayTokenId);
    
    // You can use gatewayTokenId to retrieve token details and update transaction decision
    const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);
    
    if (!transactionDecision) {
      // Not proceeding
      return null;
    }
    
    // Add risk screening data for fraud detection
    return {
      riskScreeningData: {
        performRiskScreening: true,
        userIp: "192.168.1.100",
        account: {
          id: "user_12345678",
          creationDateTime: "2024-01-15T10:30:00.000Z"
        },
        items: [{
          price: 89.99,
          quantity: 1,
          category: "Electronics"
        }],
        fulfillments: [{
          type: "Shipped",
          shipping: {
            shippingMethod: "Express"
          },
          recipientPerson: {
            phoneNumber: "+1234567890",
            email: "customer@example.com"
          }
        }]
      }
    };
  },

  // OPTIONAL: Handle the final result
  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 your backend
    const result = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
    
    if (result.state === 'Authorised') {
      console.log('Payment successful with 3DS!');
      // Redirect to a success page
      window.location.href = `/payment-success?txn=${data.merchantTransactionId}`;
    } else {
      console.log('Payment failed:', result.state);
      // Handle failure
    }
  }
});
```

### Step 3: Handle common scenarios

#### Conditional 3DS

Use the following snippet to only trigger 3DS transactions above a certain amount.


```typescript
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  onPreInitiateAuthentication: () => {
    if (sdkConfig.transactionData.amount > 100) {
      return {
        providerId: "your_provider", // optional
        requestorAuthenticationIndicator: '01',
        timeout: 120
      };
    }
    
    // Return null to skip 3DS for low-value transactions
    return null;
  }
});
```

### Step 4: Handle errors

Lastly, make sure to implement proper error handling.


```typescript
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  onSubmitError: (error: BaseSdkException) => {
    console.error('Payment error:', error);
    
    // Handle specific 3DS errors
    if (error instanceof TokenVaultException) {
        showError("Token vault exception.");
    } else if (error instanceof ValidationException) {
        showError("Validation failed.");
    } else if (error instanceof TransactionAuthenticationRejectedException) {
        showError('Payment was rejected by your bank.');
    } else if (error instanceof PreInitiateAuthenticationFailedException) {
        showError("Pre-initiate authentication failed.");
    } else if (error instanceof AuthenticationFailedException) {
        showError('Payment authentication failed. Please try again.');
    } else if (error instanceof TransactionAuthenticationRequireScaExemptionException) {
        showError("Transaction authentication requires SCA exemption.");
    } else if (error instanceof TransactionAuthenticationInvalidException) {
        showError("Transaction authentication is invalid.");
    } else if (error instanceof NetworkSdkException) {
        showError("Network error occurred. Please try again.");
    } else if (error instanceof UnexpectedSdkException) {
      showError('Payment failed. Please try again.');
    }
  },

  onPostAuthentication: async (data) => {
    // Retrieve authentication result from backend
    const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
    
    // Handle authentication failures
    if (authResult.state === 'AuthenticationFailed') {
      console.log('Authentication failed:', authResult.errorReason);
      showError('Card authentication failed');
      // Don't proceed to authorisation - update session accordingly
      await updateSessionDecision(data.authenticationId, { proceed: false });
      return;
    }

    console.log('Authentication successful, proceeding to payment');
    // Update authorisation decision
    await evaluateAuthenticationAndUpdateAuthorization(authResult);
  },

  onSubmitError: (error) => {
    console.error('Payment error:', error);
    
    // Handle specific 3DS errors
    if (error.code === 'AUTHENTICATION_FAILED') {
      showError('Payment authentication failed. Please try again.');
    } else if (error.code === 'CHALLENGE_TIMEOUT') {
      showError('Authentication timed out. Please try again.');
    } else if (error.code === 'AUTHENTICATION_REJECTED') {
      showError('Payment was rejected by your bank.');
    } else {
      showError('Payment failed. Please try again.');
    }
  }
});
```

### Example

The following example shows a simple 3DS implementation using the new card component.


```typescript
// Create and customise components
const newCardComponent = pxpCheckoutSdk.create('new-card', {
  // OPTIONAL: Use Unity authentication strategy
  useUnityAuthenticationStrategy: true,
  
  // Step 1: Set up 3DS
  onPreInitiateAuthentication: () => ({
    providerId: process.env.PROVIDER_ID, // optional
    requestorAuthenticationIndicator: '01',
    timeout: 120
  }),

  // Step 2: Configure authentication
  onPreAuthentication: async () => {
    // Get authentication decision from backend
    const decision = await getAuthenticationDecision();
    
    if (!decision) {
      return null; // Skip authentication
    }
    
    return {
      merchantCountryNumericCode: "840",
      merchantLegalName: "Your Company Ltd",
      challengeWindowSize: 4,
      requestorChallengeIndicator: "01",
      timeout: 300
    };
  },

  // Step 3: Handle final authorisation
  onPreAuthorisation: async (data) => {
    // Get authorisation decision from backend
    const decision = await getAuthorisationDecision(data.gatewayTokenId);
    
    if (!decision) {
      return null; // Skip authorisation
    }
    
    return {
      riskScreeningData: {
        performRiskScreening: true,
        userIp: "192.168.1.100",
        account: {
          id: "user_12345678",
          creationDateTime: "2024-01-15T10:30:00.000Z"
        },
        fulfillments: [{
          type: "Shipped",
          recipientPerson: {
            phoneNumber: "+1234567890"
          }
        }]
      }
    };
  },

  // Step 4: Handle success/failure
  onPostAuthorisation: async (data) => {
    // Get full result from backend
    const result = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
    
    if (result.state === 'Authorised' || result.state === 'Captured') {
      window.location.href = '/payment-success';
    } else {
      showError('Payment failed: ' + result.state);
    }
  },

  // Step 5: Error handling
  onSubmitError: (error: BaseSdkException) => {
    console.error('3DS Error:', error);
    showError('Payment authentication failed');
  }
}) as NewCardComponent;
```

## Callback data

This section describes the data received by the different callbacks as part of the 3DS flow.

Note that the `onPreInitiateAuthentication` callback doesn't receive anything so isn't included. Instead, it returns your 3DS configuration in the `PreInitiateIntegratedAuthenticationData` object.

### onPostInitiateAuthentication

The `onPostInitiateAuthentication` callback receives only the authentication identifier. Use this ID to retrieve full authentication details from your backend.

#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing the authentication initiation result. |
| `data.authenticationId`string | The unique identifier for this 3DS session. Use this ID to retrieve full PreInitiateAuthentication result from the Unity backend. |


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
onPostInitiateAuthentication: async (data) => {
  console.log('3DS pre-initiation completed. Authentication ID:', data.authenticationId);
  
  // Your backend uses authenticationId to retrieve initiate authentication result from Unity
  const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
  
  // Check if authentication setup was successful
  if (authResult.state === 'PendingClientData') {
    console.log('Waiting for client data collection');
  }
  
  // Check if SCA (Strong Customer Authentication) is mandated
  if (authResult.scaMandated) {
    console.log('SCA is required - 3DS must complete successfully');
    // Show user message: "Additional verification required"
  }
  
  // Check available exemptions
  if (authResult.applicableExemptions === 'LVP') {
    console.log('Low value exemption available');
  } else if (authResult.applicableExemptions === 'TRA') {
    console.log('Transaction risk analysis exemption available');
  }
  
  // Your backend evaluates the PreInitiateAuthentication result to update authentication/authorisation decision via Unity update session API
  const decision = await evaluateAuthenticationAndUpdateSession(authResult);
  
  // Your backend returns the decision to your frontend for use in onPreAuthentication callback
}
```

### onPreAuthentication

The `onPreAuthentication` callback receives no parameters. You should use the authentication decision evaluated after `onPostInitiateAuthentication` to determine whether to proceed.

#### Event data

This callback receives no parameters.

#### Example implementation


```typescript
onPreAuthentication: async () => {
  // Get authentication decision evaluated after onPostInitiateAuthentication
  const decision = await getAuthenticationDecision();
  
  if (!decision) {
    // Not proceeding with authentication
    console.log('Skipping authentication based on backend decision');
    return null;
  }
  
  console.log('Proceeding with authentication');
  
  // Check if SCA is mandated to adjust challenge preference
  let challengeIndicator: RequestorChallengeIndicatorType = "01"; // No preference
  
  if (decision.scaMandated) {
    challengeIndicator = "04";
    console.log('SCA mandated - requesting challenge');
  } else if (decision.applicableExemptions === 'LVP') {
    challengeIndicator = "10"; 
    console.log('Low value exemption - requesting no challenge');
  } else if (decision.applicableExemptions === 'TRA') {
    challengeIndicator = "05";
    console.log('TRA exemption - requesting no challenge');
  }
  
  return {
    merchantCountryNumericCode: "840",
    merchantLegalName: "Your company name",
    challengeWindowSize: 4,
    requestorChallengeIndicator: challengeIndicator,
    timeout: decision.scaMandated ? 600 : 300
  };
}
```

### onPostAuthentication

The `onPostAuthentication` callback receives only the authentication identifier. Use this ID to retrieve full authentication details from your backend.

#### Event data

| Parameter | Description |
|  --- | --- |
| `data`object | Object containing authentication result. |
| `data.authenticationId`string | The unique identifier for the authentication attempt. Use this ID to retrieve full authentication details from the Unity backend. |


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
onPostAuthentication: async (data) => {
  console.log('3DS authentication completed. Authentication ID:', data.authenticationId);
  
  // Send authenticationId to your backend to retrieve authentication result
  const authResult = await fetchAuthenticationResultFromBackend(data.authenticationId);
  
  console.log('Authentication result:', authResult);
  
  // Check transaction status
  switch (authResult.transactionStatus) {
    case 'Y':
      console.log('Authentication successful - no challenge needed');
      showMessage('Card verified successfully');
      break;
      
    case 'C':
      console.log('Challenge completed - checking result...');
      if (authResult.state === 'AuthenticationSuccessful') {
        console.log('Challenge completed successfully');
        showMessage('Verification completed');
      } else {
        console.log('Challenge failed');
        showError('Verification failed. Please try again.');
        return; // Stop payment
      }
      break;
      
    case 'N':
      console.log('Authentication failed');
      showError('Card verification failed');
      return; // Stop payment
      
    case 'R':
      console.log('Authentication rejected');
      showError('Payment was rejected by your bank');
      return; // Stop payment
  }
  
  // Your backend evaluates authentication result to update authorisation decision via Unity update decision API
  const authorisationDecision = await evaluateAuthenticationAndUpdateAuthorization(authResult);
  
  // Your backend returns the decision to your frontend and provides the decision to the SDK via onPreAuthorization
  console.log('Proceeding to final authorisation...');
}
```

#### Retrieved authentication data structure

When you retrieve the full authentication result from your backend, it will include detailed 3DS data:


```typescript
{
  uniqueId: "unique_12345",
  state: "completed",
  transactionStatus: "Y", // AuthenticationVerificationSuccessful
  electronicCommerceIndicator: "05", // 3DS authenticated
  exemptionGranted: false,
  exemptionGrantedByIssuer: "79", // NoExemptionApplied
  acsUrl: undefined, // No challenge needed
  challengeData: undefined, // No challenge needed
  stateData: { 
    code: "success", 
    reason: "Authentication completed successfully" 
  },
  cardholderInfo: undefined
}
```


```typescript
{
  uniqueId: "unique_67890",
  state: "challenge_required",
  transactionStatus: "C",
  electronicCommerceIndicator: "07",
  exemptionGranted: false,
  exemptionGrantedByIssuer: "79",
  acsUrl: "https://acs.bank.com/challenge",
  challengeData: "eyJhbGciOiJIUzI1NiJ9...",
  stateData: { 
    code: "pending", 
    reason: "Challenge required from cardholder" 
  },
  cardholderInfo: "Additional details about the cardholder"
}
```

| Parameter | Description |
|  --- | --- |
| `uniqueId`string | The unique identifier for this 3DS session. |
| `state`string | The state of the authentication. |
| `transactionStatus`string | The status of the transaction.Possible values:* `Y`: Authentication verification successful* `N`: Not authenticated / not verified* `U`: Authentication couldn't be performed* `A`: Attempts processing performed* `C`: Challenge required* `R`: Authentication rejected* `I`: Informational only |
| `electronicCommerceIndicator`string | The Electronic Commerce Indicator (ECI).Possible values:* `01`: 3DS not available (non-3DS transaction)* `02`: 3DS available but not used (you attempted authentication but it wasn't possible)* `05`: 3DS authentication successful (fully authenticated)* `06`: 3DS authentication attempted (issuer or cardholder not enrolled)* `07`: 3DS authentication failed but transaction allowed (soft decline) |
| `exemptionGranted`boolean | Whether an exemption was granted. |
| `exemptionGrantedByIssuer`string | The type of exemption granted by the issuer.Possible values:* `05`: Transaction risk analysis exemption* `08`: Trust list exemption* `10`: Low value exemption* `11`: Secure corporate payments exemption* `79`: No exemption applied |
| `acsUrl`string | The ACS URL. |
| `challengeData`string | Base64 encoded challenge data. |
| `stateData`object | Details about the state. |
| `stateData.code`string | The state code. |
| `stateData.reason`string | The state reason. |
| `cardholderInfo`string | Additional details about the cardholder. |


##### Failure


```typescript
{
  errorCode: "CHALLENGE_FAILED",
  errorReason: "Cardholder failed 3D Secure challenge",
  correlationId: "corr_challenge_failed_456",
  details: [
    "Incorrect SMS verification code entered",
    "Maximum authentication attempts exceeded"
  ],
  status: 401
}
```

| Parameter | Description |
|  --- | --- |
| `errorCode`string | The error code. |
| `errorReason`string | The reason for the error. |
| `correlationId`string | The correlation ID. |
| `details`array of strings | Additional details about the error. |
| `status`HttpStatusCode | The HTTP status code. |


### onPreAuthorisation

The `onPreAuthorisation` callback receives only the gateway token ID. Use this ID to retrieve token details and make authorisation decisions on your backend.

#### 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.

#### Example implementation


```typescript
onPreAuthorisation: async (data) => {
  console.log('Pre-authorisation for token:', data.gatewayTokenId);
  
  // You can use gatewayTokenId to retrieve token details and update transaction decision on your backend
  const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);
  
  if (!transactionDecision) {
    // To not proceed
    console.log('Not proceeding with authorisation');
    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,
      userIp: "192.168.1.100",
      account: {
        id: "user_12345678",
        creationDateTime: "2024-01-15T10:30:00.000Z"
      },
      items: [{
        price: 89.99,
        quantity: 1,
        category: "Electronics"
      }],
      fulfillments: [{
        type: "Shipped",
        shipping: {
          shippingMethod: "Express"
        },
        recipientPerson: {
          phoneNumber: "+1234567890",
          email: "customer@example.com"
        }
      }]
    }
  };
}
```

**Important**: 3DS external data (obtained from external authentication sources) should no longer be provided via the `threeDSecureData` return parameter. Instead, provide this data to the backend via the Unity session update endpoint.

#### Retrieved transaction data structure

When you retrieve the full token/transaction details from your backend, you'll get detailed information. The pre-authorisation data includes transaction initiation data (for new cards) or card token data (for saved cards).


```typescript
{
  initiateIntegratedSuccessAuthenticationResult: {
    status: 200,
    authenticationId: "auth_12345678-abcd-1234-efgh-123456789012",
    uniqueId: "unique_98765",
    state: "Completed",
    transactionStatus: "Y",
    electronicCommerceIndicator: "05",
    exemptionGranted: false,
    exemptionGrantedByIssuer: "79",
    acsUrl: null, 
    challengeData: null,
    stateData: {
     code: "SUCCESS",
     reason: "Authentication completed successfully"
    },
    cardholderInfo: "Additional cardholder verification completed"
  },
  transactionInitiationData: {
    psd2Data: {
      scaExemption: null,
    },
    threeDSecureData: {
      threeDSecureVersion: "2.2.0",
      electronicCommerceIndicator: "05",
      cardHolderAuthenticationVerificationValue: "jGvQIvG/5UhjAREALGYYemQLXPI=",
      directoryServerTransactionId: "ds_trans_12345",
      threeDSecureTransactionStatus: "Y"
    },
    identityVerification: {
      nameVerification: boolean,
    },
    addressVerification: AddressVerification;
    }
}
```


```typescript
{
  initiateIntegratedSuccessAuthenticationResult: null,
  transactionInitiationData: null,
  cardTokenData: {
    gatewayTokenId: "token_abc123def456789",
    schemeTokenId: "scheme_xyz987654321",
    maskedPrimaryAccountNumber: "****-****-****-4242",
    cardExpiryMonth: "12",
    cardExpiryYear: "2025", 
    scheme: "VISA",
    fundingSource: "CREDIT",
    ownerType: "PERSONAL",
    issuerName: "Chase Bank",
    issuerCountryCode: "US",
    lastSuccessfulPurchaseDate: "2024-01-15T10:30:00Z",
    lastSuccessfulPayoutDate: null
  }
}
```

| Parameter | Description |
|  --- | --- |
| `initiateIntegratedSuccessAuthenticationResult`object | Details about the successful authentication. |
| `initiateIntegratedSuccessAuthenticationResult.status`string | The HTTP status. |
| `initiateIntegratedSuccessAuthenticationResult.authenticationId`HttpStatusCode | The unique identifier for this 3DS session. |
| `initiateIntegratedSuccessAuthenticationResult.uniqueId`string | The unique identifier for this 3DS session. |
| `initiateIntegratedSuccessAuthenticationResult.state`string | The state of the authentication. |
| `initiateIntegratedSuccessAuthenticationResult.transactionStatus`string | The status of the transaction.Possible values:* `Y`: Authentication verification successful* `N`: Not authenticated / not verified* `U`: Authentication couldn't be performed* `A`: Attempts processing performed* `C`: Challenge required* `R`: Authentication rejected* `I`: Informational only |
| `initiateIntegratedSuccessAuthenticationResult.electronicCommerceIndicator`string | The Electronic Commerce Indicator (ECI).Possible values:* `01`: 3DS not available (non-3DS transaction)* `02`: 3DS available but not used (you attempted authentication but it wasn't possible)* `05`: 3DS authentication successful (fully authenticated)* `06`: 3DS authentication attempted (issuer or cardholder not enrolled)* `07`: 3DS authentication failed but transaction allowed (soft decline) |
| `initiateIntegratedSuccessAuthenticationResult.exemptionGranted`boolean | Whether an exemption was granted. |
| `initiateIntegratedSuccessAuthenticationResult.exemptionGrantedByIssuer`string | The type of exemption granted by the issuer.Possible values:* `05`: Transaction risk analysis exemption* `08`: Trust list exemption* `10`: Low value exemption* `11`: Secure corporate payments exemption* `79`: No exemption applied |
| `initiateIntegratedSuccessAuthenticationResult.acsUrl`string | The ACS URL. |
| `initiateIntegratedSuccessAuthenticationResult.challengeData`string | Base64 encoded challenge data. |
| `initiateIntegratedSuccessAuthenticationResult.stateData`object | Details about the state. |
| `initiateIntegratedSuccessAuthenticationResult.stateData.code`string | The state code. |
| `initiateIntegratedSuccessAuthenticationResult.stateData.reason`string | The state reason. |
| `initiateIntegratedSuccessAuthenticationResult.cardholderInfo`string | Additional details about the cardholder. |
| `transactionInitiationData`object | Details about the transaction, if associated with a new card, `null` otherwise. |
| `transactionInitiationData.psd2Data`object or null | Details about PSD2. This is required for non-3DS transactions. |
| `transactionInitiationData.psd2Data.scaExemption`string or null | The type of SCA exemption that applies to this transaction.Possible values:* `AnonymousCard`* `LowValue`* `SecureCorporate`* `TransactionRiskAnalysis`* `TrustedBeneficiary` |
| `transactionInitiationData.threeDSecureData`object or null | Details about 3DS. |
| `transactionInitiationData.threeDSecureData.threeDSecureVersion`string | The 3DS secure version. |
| `transactionInitiationData.threeDSecureData.electronicCommerceIndicator`string | The Electronic Commerce Indicator (ECI).Possible values:* `01`* `02`* `05`* `06`* `07` |
| `transactionInitiationData.threeDSecureData.directoryServerTransactionId`string | The transaction ID assigned by the Directory Server. |
| `transactionInitiationData.threeDSecureData.cardHolderAuthenticationVerificationValue`string | The cardholder authentication verification value (CAVV). |
| `transactionInitiationData.threeDSecureData.threeDSecureTransactionStatus`string | The status of the 3DS transaction.Possible values:* `Y`: Authentication verification successful* `N`: Not authenticated / not verified* `U`: Authentication couldn't be performed* `A`: Attempts processing performed* `C`: Challenge required* `R`: Authentication rejected* `I`: Informational only |
| `transactionInitiationData.identityVerification.nameVerification`boolean | Whether the cardholder's name matches the name associated with the registered address on file. |
| `transactionInitiationData.addressVerification`object | Details about the address verification. |
| `transactionInitiationData.identityVerification.countryCode`string | The country code associated with the cardholder's address, in ISO-3166-1 alpha-2 format. |
| `transactionInitiationData.identityVerification.houseNumberOrName`string | The cardholder's street address. |
| `transactionInitiationData.identityVerification.postalCode`string | The postal or ZIP code associated with the cardholder's address. |
| `cardTokenData`object | Details about the card token if associated with a saved card, `null` otherwise. |
| `cardToken.gatewayTokenId`string or null | The gateway token ID. |
| `cardToken.schemeTokenId`string or null | The scheme token ID. |
| `cardToken.maskedPrimaryAccountNumber`string or null | The masked Primary Account Number (PAN). |
| `cardToken.cardExpiryMonth`string or null | The expiry month (`MM`) of the card. |
| `cardToken.cardExpiryYear`string or null | The expiry year (`YYYY`) of the card. |
| `cardToken.scheme`string or null | The card scheme. |
| `cardToken.fundingSource`string or null | The funding source. |
| `cardToken.ownerType`string or null | The owner type. |
| `cardToken.issuerName`string or null | The issuer name. |
| `cardToken.issuerCountryCode`string or null | The country code of the issuer. |
| `cardToken.lastSuccessfulPurchaseDate`string or null | The date of the last successful purchase. |
| `cardToken.lastSuccessfulPayoutDate`string or null | The date of the last successful payout. |


#### Retrieved 3DS data structure

When you retrieve authentication details from your backend, the 3DS data includes details about the final result of the authentication.


```typescript
{
  authenticationId: "auth_12345678-abcd-1234-efgh-123456789012",
  state: "AuthenticationSuccessful",
  threeDSecureVersion: "2.2.0",
  directoryServerTransactionId: "ds_trans_12345678-1234-1234-1234-123456789012",
  cardholderAuthenticationVerificationValue: "jGvQIvG/5UhjAREALGYYemQLXPI=",
  electronicCommerceIndicator: "05"
}
```

| Parameter | Description |
|  --- | --- |
| `authenticationId`string | The unique identifier for this 3DS session. |
| `state`string | The state of the authentication.Possible values:* `AuthenticationSuccessful`* `AuthenticationFailed`* `AuthenticationRejected`* `AuthenticationError` |
| `threeDSecureVersion`string | The 3DS secure version. |
| `directoryServerTransactionId`string | The transaction ID assigned by the Directory Server. |
| `cardHolderAuthenticationVerificationValue`string | The cardholder authentication verification value (CAVV). |
| `electronicCommerceIndicator`string | The Electronic Commerce Indicator (ECI).Possible values:* `01`* `02`* `05`* `06`* `07` |


### onPostAuthorisation

The `onPostAuthorisation` callback receives only the transaction identifiers. Use these IDs to call the Unity Backend and retrieve the full transaction outcome.

The `onPostAuthorisation` callback doesn't return the transaction outcome (Authorised, Captured, Refused, etc.). You must call the Unity backend to obtain the full transaction result. Use the [Get transaction details](/apis/transaction/other/get-transaction-details) API with the `systemTransactionId` to retrieve the complete transaction outcome.

#### Event data

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


#### Example implementation


```typescript
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 your backend
  const result = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
  
  console.log('Final payment result:', result);
  
  if (result.state === 'Authorised' || result.state === 'Captured') {
    console.log('Payment successful with 3DS authentication!');
    
    // Check if 3DS was used
    if (result.threeDSAuthenticationData) {
      console.log('3DS authentication confirmed');
      console.log(`ECI: ${result.threeDSAuthenticationData.electronicCommerceIndicator}`);
      
      // Log liability shift information
      const eci = result.threeDSAuthenticationData.electronicCommerceIndicator;
      if (eci === '05' || eci === '06') {
        console.log('Liability shift achieved - protected from chargebacks');
      }
      
      // Store 3DS data for compliance/auditing
      storeTransactionAudit({
        merchantTransactionId: data.merchantTransactionId,
        systemTransactionId: data.systemTransactionId,
        threeDSVersion: result.threeDSAuthenticationData.threeDSecureVersion,
        authenticationId: result.threeDSAuthenticationData.authenticationId,
        eci: result.threeDSAuthenticationData.electronicCommerceIndicator
      });
    }
    
    // Redirect to success page
    window.location.href = `/payment-success?txn=${data.merchantTransactionId}`;
    
  } else {
    console.error('Payment failed:', result.state);
    
    // Handle different failure types
    if (result.stateData?.code === '51') {
      showError('Insufficient funds. Please try a different payment method.');
    } else if (result.state === 'Refused') {
      showError('Card verification failed. Please try again.');
    } else {
      showError('Payment failed. Please try again or contact support.');
    }
  }
}
```

#### Retrieved transaction result structure

When you retrieve the full transaction result from your backend, you'll receive detailed information:

##### Success

If the transaction was successful, you'll receive either an `AuthorisedSubmitResult` or a `CapturedSubmitResult`.


```typescript
{
  state: "Authorised",
  providerResponse: {
    code: "00",
    message: "Approved",
    cardVerificationCodeResult: "M",
    addressVerificationServiceResult: "Y"
  },
  fundingData: {
    cardVerificationCodeResult: "Matched", 
    addressVerificationServiceResult: "Y"
  }
}
```

| Parameter | Description |
|  --- | --- |
| `state`string | The final state of the transaction.Possible values:* `Authorised`* `Captured`* `Refused` |
| `providerResponse`object | Details about the provider's response. |
| `providerResponse.code`string | The raw result code returned by the provider that processed the transaction. |
| `providerResponse.message`string | The raw message associated with the result code from the provider that processed the transaction. |
| `providerResponse.cardVerificationCodeResult`string | The Card Verification Code (CVC) result returned by the provider. This is a raw data indicating the outcome of the CVC check performed during the transaction processing. |
| `providerResponse.addressVerificationServiceResult`string | The Address Verification Service (AVS) result returned by the provider. This is a raw data indicating the outcome of the AVS check performed during the transaction processing. |
| `fundingData`object | Details about the payment method. |
| `fundingData.cardVerificationCodeResult`string | The Card Verification Code (CVC) result in human-readable format. |
| `fundingData.addressVerificationServiceResult`string | The Address Verification Service (AVS) result in human-readable format. |


#### Failure (declined)

If the bank or issuer declines the transaction, you'll receive a `RefusedSubmitResult`.


```typescript
{
  state: "Refused",
  stateData: {
    code: "05",
    message: "Do not honor"
  },
  providerResponse: {
    code: "05",
    message: "Do not honor",
    merchantAdvice: {
      code: "01",
      message: "Try another payment method"
    },
    cardVerificationCodeResult: "M",
    addressVerificationServiceResult: "Y"
  },
  fundingData: {
    cardVerificationCodeResult: "Matched",
    addressVerificationServiceResult: "Y"
  }
}
```

| Parameter | Description |
|  --- | --- |
| `state`string | The final state of the transaction.Possible values:* `Authorised`* `Captured`* `Refused` |
| `stateData`object | Additional details about the state. |
| `stateData.code`string | The state code. |
| `stateData.message`string | The state message. |
| `providerResponse`object | Details about the provider's response. |
| `providerResponse.code`string | The raw result code returned by the provider that processed the transaction. |
| `providerResponse.message`string | The raw message associated with the result code from the provider that processed the transaction. |
| `providerResponse.cardVerificationCodeResult`string | The Card Verification Code (CVC) result returned by the provider. This is a raw data indicating the outcome of the CVC check performed during the transaction processing. |
| `providerResponse.addressVerificationServiceResult`string | The Address Verification Service (AVS) result returned by the provider. This is a raw data indicating the outcome of the AVS check performed during the transaction processing. |
| `fundingData`object | Details about the payment method. |
| `fundingData.cardVerificationCodeResult`string | The Card Verification Code (CVC) result in human-readable format. |
| `fundingData.addressVerificationServiceResult`string | The Address Verification Service (AVS) result in human-readable format. |


Here's an example of how to handle it.


```typescript
function handlePaymentResult(result: BaseSubmitResult) {
  console.log('Payment result received:', result);
  
  if (result instanceof AuthorisedSubmitResult) {
    // SUCCESS
    handlePaymentSuccess(result);
    
  } else {
    // FAILURE - Handle different failure types
    handlePaymentFailure(result);
  }
}

function handlePaymentSuccess(result: AuthorisedSubmitResult) {
  console.log('Payment successful!');
  console.log(`Transaction ID: ${result.transactionId}`);
  
  // Check 3DS authentication data
  if (result.threeDSAuthenticationData) {
    console.log('3DS authentication confirmed');
    console.log(`ECI: ${result.threeDSAuthenticationData.electronicCommerceIndicator}`);
  }
  
  // Redirect to success page
  window.location.href = `/payment-success?tx=${result.transactionId}`;
}

function handlePaymentFailure(result: BaseSubmitResult) {
  console.error('Payment failed:', result);
  
  if (result instanceof FailedSubmitResult) {
    // System/network failure (non-200 HTTP)
    handleSystemFailure(result);
    
  } else if (result instanceof RefusedSubmitResult) {
    // Bank/issuer declined (200 HTTP but refused)
    handleBankDecline(result);
    
  } else if (result instanceof ErrorSubmitResult) {
    // Processing error (200 HTTP but error state)
    handleProcessingFailure(result);
    
  } else {
    // Unknown failure type
    console.error('Unknown failure type:', result);
    showError('Payment failed. Please try again.');
    logError('Unknown payment failure type', result);
  }
  
  // Hide loading states
  hideLoadingSpinner();
  enablePaymentForm();
}
```

### onSubmitError

If an error occurs at any point in the 3DS flow (e.g., due to a network outage), you'll receive the following data.


```typescript
{
  errorCode: "GATEWAY_TIMEOUT",
  errorReason: "Payment gateway timeout",
  correlationId: "corr_12345678",
  httpStatusCode: 504,
  details: ["Connection timeout after 30 seconds"]
}
```

Here's an example of how to handle this data:


```typescript
function handleProcessingError(error: BaseSdkException) {
  console.error('Processing error occurred:', error);
  
  // Common processing errors
  switch (error.code) {
    // 3DS Authentication Errors
    case 'AUTHENTICATION_FAILED':
      showError('Card verification failed. Please try again.');
      logError('3DS authentication failed', error);
      break;
      
    case 'AUTHENTICATION_TIMEOUT':
      showError('Verification timed out. Please try again.');
      logError('3DS authentication timeout', error);
      break;
      
    case 'AUTHENTICATION_REJECTED':
      showError('Payment was rejected during verification.');
      logError('3DS authentication rejected', error);
      break;
      
    // Validation Errors
    case 'VALIDATION_FAILED':
      showError('Please check your payment details and try again.');
      logError('Form validation failed', error);
      break;
      
    case 'INVALID_CARD_NUMBER':
      showError('Invalid card number. Please check and try again.');
      break;
      
    case 'INVALID_EXPIRY_DATE':
      showError('Invalid expiry date. Please check and try again.');
      break;
      
    case 'INVALID_CVC':
      showError('Invalid security code. Please check and try again.');
      break;
      
    // Network/System Errors
    case 'NETWORK_ERROR':
      showError('Connection failed. Please check your internet and try again.');
      logError('Network error during payment', error);
      break;
      
    case 'GATEWAY_TIMEOUT':
      showError('Payment system is busy. Please try again in a moment.');
      logError('Gateway timeout', error);
      break;
      
    case 'SERVICE_UNAVAILABLE':
      showError('Payment service temporarily unavailable. Please try again later.');
      logError('Payment service unavailable', error);
      break;
      
    // Token/Configuration Errors
    case 'TOKEN_INVALID':
      showError('Payment method is no longer valid. Please try a different card.');
      logError('Invalid payment token', error);
      break;
      
    case 'CONFIGURATION_ERROR':
      showError('Payment setup error. Please contact support.');
      logError('SDK configuration error', error);
      break;
      
    default:
      showError('An unexpected error occurred. Please try again.');
      logError('Unknown processing error', error);
  }
  
  // Hide loading states
  hideLoadingSpinner();
  enablePaymentForm();
}
```