# Non-3DS payments

Process an online card transaction without 3DS.

## Overview

By implementing a non-3DS payment flow, you benefit from:

* **Faster checkout:** Streamlined payment process with fewer steps and redirects.
* **Better conversion:** Reduced friction leads to higher completion rates.
* **Simplified integration:** Fewer callbacks and less complex implementation.
* **Lower latency:** Direct payment processing without authentication steps.


However, non-3DS payments typically have higher risk and may not qualify for liability shift protection. They're best suited for low-risk transactions, trusted customers, or when 3DS authentication isn't required.

## Payment flow

The non-3DS payment flow is a streamlined process consisting of five key steps.

### Step 1: Submission

The customer clicks the submit button or the payment is triggered programmatically. The `submitAsync()` method is invoked and 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 not required based on factors such as:

* The transaction intent being `Payout`.
* Your configuration not requiring 3DS authentication (i.e., `onPreInitiateAuthentication` callback not provided).
* The transaction falling below risk thresholds.
* Regulatory exemptions being applicable.


Since 3DS isn't required, the flow skips all authentication steps and proceeds directly to authorisation.

### Step 4: Authorisation

This is the final processing step where the SDK sends the authorisation request directly to the payment gateway without any 3DS authentication data. The transaction data is processed using standard card payment protocols.

The authorisation step has two associated callbacks:

* `onPreAuthorisation`: Provides transaction data for final review. This is your last chance to modify the transaction before authorisation. Note that in non-3DS flows, no authentication results are included.
* `onPostAuthorisation`: Receives the final transaction result from the payment gateway. The transaction is either approved or declined with standard payment processing details.


### Step 5: 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. Since this is a non-3DS flow, no authentication confirmation data is included in the response.

## Implementation

### Before you start

To use non-3DS payments in your application:

1. Ensure your merchant account supports non-3DS transactions.
2. Configure your risk settings appropriately in the Unity Portal.
3. Consider implementing additional fraud prevention measures.


### Step 1: Configure your SDK

Set up your `sdkConfig` with basic transaction information.


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

### Step 2: Implement callbacks

Implement the required callbacks for non-3DS payments.


```typescript
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  // REQUIRED: Final transaction approval
  onPreAuthorisation: async (data) => {
    console.log('Processing non-3DS transaction for token:', data.gatewayTokenId);
    
    // Merchant can use gatewayTokenId to retrieve token details and update transaction decision
    const transactionDecision = await getAuthorisationDecision(data.gatewayTokenId);
    
    if (!transactionDecision) {
      // Not proceeding
      return null;
    }
    
    // Add any additional transaction data
    return {
      addressVerification: await getBillingAddress(),
      riskScreeningData: await getRiskData(),
      // Add custom metadata if needed
      metadata: {
        processingType: 'non-3ds',
        timestamp: new Date().toISOString()
      }
    };
  },

  // REQUIRED: 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 merchant backend
    const result = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
    
    if (result.state === 'Authorised') {
      console.log('Payment successful!');
      // Redirect to success page
      window.location.href = `/payment-success?txn=${data.merchantTransactionId}`;
    } else {
      console.log('Payment failed:', result.state);
      // Handle failure
      showError('Payment failed. Please try again.');
    }
  },

  // OPTIONAL: Handle errors during processing
  onSubmitError: (error) => {
    console.error('Payment error:', error);
    showError('Payment failed. Please try again.');
  }
});
```

### Step 3: Handle common scenarios

#### Amount-based processing

Use different processing based on transaction amounts.


```typescript
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  onPreAuthorisation: async (data) => {
    const amount = sdkConfig.transactionData.amount;
    
    // Get authorisation decision from backend
    const decision = await getAuthorisationDecision(data.gatewayTokenId);
    
    if (!decision) {
      return null;
    }
    
    // Add risk indicators for higher amounts
    if (amount > 100) {
      return {
        addressVerification: await getBillingAddress(),
        riskScreeningData: {
          deviceSessionId: await getKountSessionId(),
          performRiskScreening: true,
          customData: {
            level: 'medium',
            reason: 'high-value-transaction'
          }
        }
      };
    }
    
    return {
      riskScreeningData: await getRiskData()
    };
  }
});
```

#### Customer type handling

Handle different customer types with varying risk profiles.


```typescript
const getCustomerRiskProfile = (customerType) => {
  switch (customerType) {
    case 'new':
      return { level: 'high', verification: 'enhanced' };
    case 'returning':
      return { level: 'medium', verification: 'standard' };
    case 'vip':
      return { level: 'low', verification: 'minimal' };
    default:
      return { level: 'medium', verification: 'standard' };
  }
};

const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  onPreAuthorisation: async (data) => {
    const decision = await getAuthorisationDecision(data.gatewayTokenId);
    
    if (!decision) {
      return null;
    }
    
    const riskProfile = getCustomerRiskProfile('returning');
    
    return {
      addressVerification: await getBillingAddress(),
      riskScreeningData: {
        deviceSessionId: await getKountSessionId(),
        performRiskScreening: true,
        customData: {
          customerRisk: riskProfile
        }
      }
    };
  }
});
```

### Step 4: Handle errors

Implement comprehensive error handling for non-3DS payments.


```typescript
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  onSubmitError: (error) => {
    console.error('Payment error:', error);
    
    // Handle specific error types
    if (error.code === 'INSUFFICIENT_FUNDS') {
      showError('Insufficient funds. Please try a different payment method.');
    } else if (error.code === 'CARD_DECLINED') {
      showError('Your card was declined. Please try a different payment method.');
    } else if (error.code === 'INVALID_CARD') {
      showError('Invalid card details. Please check and try again.');
    } else if (error.code === 'NETWORK_ERROR') {
      showError('Connection failed. Please check your internet and try again.');
    } else {
      showError('Payment failed. Please try again.');
    }
  },

  onPostAuthorisation: async (data) => {
    // Get full result from backend
    const result = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
    
    if (result.state !== 'Authorised' && result.state !== 'Captured') {
      // Handle different decline reasons
      const declineCode = result.stateData?.code;
      
      switch (declineCode) {
        case '05': // Do not honor
          showError('Payment declined by your bank. Please try a different card.');
          break;
        case '14': // Invalid card number
          showError('Invalid card number. Please check your details.');
          break;
        case '54': // Expired card
          showError('Your card has expired. Please use a different card.');
          break;
        case '61': // Exceeds withdrawal limit
          showError('Transaction exceeds your card limit. Try a smaller amount.');
          break;
        default:
          showError('Payment was declined. Please try a different payment method.');
      }
    }
  }
});
```

### Example

The following example shows a simple non-3DS implementation using the card submit component.


```typescript
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  // Step 1: Process the transaction
  onPreAuthorisation: async (data) => {
    console.log('Processing card payment for token:', data.gatewayTokenId);
    
    // Get authorisation decision from backend
    const decision = await getAuthorisationDecision(data.gatewayTokenId);
    
    if (!decision) {
      return null; // Skip authorisation
    }
    
    // Log transaction details
    console.log(`Amount: ${sdkConfig.transactionData.amount}`);
    console.log(`Currency: ${sdkConfig.transactionData.currency}`);
    
    // Add any additional data
    return {
      addressVerification: await getBillingAddress(),
      riskScreeningData: await getRiskData()
    };
  },

  // Step 2: Handle success/failure
  onPostAuthorisation: async (data) => {
    console.log('Authorisation completed');
    
    // Get full result from backend
    const result = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
    
    if (result.state === 'Authorised' || result.state === 'Captured') {
      // Success - redirect to confirmation
      console.log('Payment completed successfully');
      sessionStorage.setItem('paymentSuccess', 'true');
      window.location.href = `/payment-success?txn=${data.merchantTransactionId}`;
    } else {
      // Failure - show error and allow retry
      console.error('Payment failed:', result.state);
      showError('Payment failed: ' + (result.stateData?.message || 'Please try again'));
      enableRetryButton();
    }
  },

  // Step 3: Error handling
  onSubmitError: (error) => {
    console.error('Payment error:', error);
    hideLoadingSpinner();
    showError('Payment failed. Please try again.');
  }
});
```

## Callback data

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

### 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 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('Non-3DS 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
    console.log('Not proceeding with authorisation');
    return null;
  }
  
  // Add fraud prevention data
  const enhancedData = {
    // Add browser fingerprinting
    deviceData: {
      userAgent: navigator.userAgent,
      language: navigator.language,
      screenResolution: `${screen.width}x${screen.height}`,
      timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
    },
    
    // Add risk screening data
    riskScreeningData: {
      deviceSessionId: await getKountSessionId(),
      performRiskScreening: true,
      customData: {
        customerType: 'returning',
        paymentHistory: 'good',
        velocityCheck: 'passed'
      }
    },
    
    // Add address verification if available
    addressVerification: await getBillingAddress()
  };
  
  console.log('Sending enhanced card transaction');
  return enhancedData;
}
```

**Important**: 3DS external data should no longer be provided via the callback. For non-3DS flows, no 3DS data is applicable.

#### 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 With transaction initiation data
{
  initiateIntegratedSuccessAuthenticationResult: null,
  transactionInitiationData: {
    threeDSecureData: null,
    psd2Data: {
      scaExemption: "LowValue"
    },
    identityVerification: {
      nameVerification: true
    },
    addressVerification: {
      countryCode: "US",
      houseNumberOrName: "123",
      postalCode: "10001"
    }
  },
  cardTokenData: null
}
```


```typescript With card token data
{
  initiateIntegratedSuccessAuthenticationResult: null,
  transactionInitiationData: null,
  cardTokenData: {
    gatewayTokenId: "gw_token_abc123def456789",
    schemeTokenId: null,
    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 | This is always`null` for non-3DS transactions. |
| `transactionInitiationData`object | Details about the transaction, if associated with a new card, `null` otherwise. |
| `transactionInitiationData.threeDSecureData`object or null | This is always`null` for non-3DS transactions. |
| `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.identityVerification`object | Details about the identity verification. |
| `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. |


### onPostAuthorisation

The `onPostAuthorisation` callback receives only the transaction identifiers. Use these IDs to retrieve full transaction details from your backend.

#### 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 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
onPostAuthorisation: async (data) => {
  console.log('Non-3DS payment result');
  console.log('Merchant Transaction ID:', data.merchantTransactionId);
  console.log('System Transaction ID:', data.systemTransactionId);
  
  // Get full result from backend
  const result = await getAuthorisationResultFromGateway(data.merchantTransactionId, data.systemTransactionId);
  
  if (result.state === 'Authorised' || result.state === 'Captured') {
    console.log('Payment successful!');
    
    // Verify no 3DS data (should be null for non-3DS)
    if (result.threeDSAuthenticationData) {
      console.warn('Unexpected 3DS data in non-3DS transaction');
    }
    
    // Check verification results
    const fundingData = result.fundingData;
    if (fundingData.cardVerificationCodeResult === 'Matched') {
      console.log('CVC verification passed');
    }
    
    if (fundingData.addressVerificationServiceResult === 'Y') {
      console.log('Address verification passed');
    }
    
    // Store transaction details
    storeTransactionRecord({
      merchantTransactionId: data.merchantTransactionId,
      systemTransactionId: data.systemTransactionId,
      amount: result.amounts.transaction,
      currency: result.amounts.currency,
      cardType: fundingData.scheme,
      processingType: 'non-3ds',
      timestamp: new Date().toISOString()
    });
    
    // Redirect to success page
    window.location.href = `/payment-success?txn=${merchantTransactionId}`;
    
  } else {
    handlePaymentFailure(result);
  }
}
```

#### 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 honour"
  },
  providerResponse: {
    code: "05",
    message: "Do not honour",
    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 failures:


```typescript
function handlePaymentFailure(result) {
  console.error('Non-3DS payment failed:', result);
  
  if (result instanceof RefusedSubmitResult) {
    // Bank/issuer declined
    const declineCode = result.stateData?.code;
    const merchantAdvice = result.providerResponse?.merchantAdvice;
    
    switch (declineCode) {
      case '05': // Do not honor
        showError('Payment declined by your bank. Please try a different card.');
        break;
      case '14': // Invalid card
        showError('Invalid card details. Please check and try again.');
        break;
      case '51': // Insufficient funds
        showError('Insufficient funds. Please try a different payment method.');
        break;
      case '54': // Expired card
        showError('Your card has expired. Please use a different card.');
        break;
      default:
        showError(merchantAdvice?.message || 'Payment declined. Please try again.');
    }
    
    // Log decline for analysis
    logPaymentDecline({
      transactionId: result.transactionId,
      declineCode: declineCode,
      declineReason: result.stateData?.message,
      merchantAdvice: merchantAdvice?.message
    });
    
  } else {
    // System error
    showError('Payment failed due to a system error. Please try again.');
    logSystemError('Non-3DS payment system failure', result);
  }
  
  // Re-enable payment form
  enablePaymentForm();
  hideLoadingSpinner();
}
```

### onSubmitError

If an error occurs during the payment processing, you'll receive error details.


```typescript
{
  errorCode: "VALIDATION_FAILED",
  errorReason: "Card number validation failed",
  correlationId: "corr_12345678",
  httpStatusCode: 400,
  details: ["Invalid card number format"]
}
```

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


```typescript
onSubmitError: (error) => {
  console.error('Non-3DS payment error:', error);
  
  // Handle specific error types
  switch (error.code) {
    // Validation Errors
    case 'VALIDATION_FAILED':
      showError('Please check your payment details and try again.');
      highlightInvalidFields();
      break;
      
    case 'INVALID_CARD_NUMBER':
      showError('Invalid card number. Please check and try again.');
      focusField('cardNumber');
      break;
      
    case 'INVALID_EXPIRY_DATE':
      showError('Invalid expiry date. Please check and try again.');
      focusField('expiryDate');
      break;
      
    case 'INVALID_CVC':
      showError('Invalid security code. Please check and try again.');
      focusField('cvc');
      break;
      
    // Processing Errors
    case 'TOKENIZATION_FAILED':
      showError('Unable to process card details. Please try again.');
      logError('Tokenisation failed for non-3DS payment', error);
      break;
      
    case 'NETWORK_ERROR':
      showError('Connection failed. Please check your internet and try again.');
      break;
      
    case 'GATEWAY_TIMEOUT':
      showError('Payment system is busy. Please try again in a moment.');
      break;
      
    case 'SERVICE_UNAVAILABLE':
      showError('Payment service temporarily unavailable. Please try again later.');
      break;
      
    // Configuration Errors
    case 'MERCHANT_NOT_CONFIGURED':
      showError('Payment setup error. Please contact support.');
      logError('Merchant configuration error', error);
      break;
      
    default:
      showError('An unexpected error occurred. Please try again.');
      logError('Unknown non-3DS payment error', error);
  }
  
  // Re-enable the payment form
  hideLoadingSpinner();
  enablePaymentForm();
  
  // Log error for monitoring
  logPaymentError({
    errorCode: error.code,
    errorReason: error.message,
    correlationId: error.correlationId,
    timestamp: new Date().toISOString(),
    paymentType: 'non-3ds'
  });
}
```