Non-3DS transactions

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 is not 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.

const sdkConfig = {
  transactionData: {
    amount: 99.99,
    currency: 'USD',
    entryType: 'Ecom',
    intent: 'Authorisation',
    merchantTransactionId: 'order-123',
    merchantTransactionDate: () => new Date().toISOString(),
    // Optional shopper data for enhanced processing
    shopper: {
      email: '[email protected]',
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  // Your other SDK configuration
};

Step 2: Implement callbacks

Implement the required callbacks for non-3DS payments.

const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  // REQUIRED: Final transaction approval
  onPreAuthorisation: async (preAuthData, threeDSData) => {
    console.log('Processing non-3DS transaction:', preAuthData);
    
    // Add any additional transaction data
    return {
      ...preAuthData.transactionInitiationData,
      // Add custom metadata if needed
      metadata: {
        processingType: 'non-3ds',
        timestamp: new Date().toISOString()
      }
    };
  },

  // REQUIRED: Handle the final result
  onPostAuthorisation: (result) => {
    if (result.success) {
      console.log('Payment successful!');
      console.log('Transaction ID:', result.transactionId);
      // Redirect to success page
      window.location.href = '/payment-success';
    } else {
      console.log('Payment failed:', result.errorMessage);
      // 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.

const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  onPreAuthorisation: async (preAuthData, threeDSData) => {
    const amount = sdkConfig.transactionData.amount;
    
    // Add risk indicators for higher amounts
    if (amount > 100) {
      return {
        ...preAuthData.transactionInitiationData,
        riskData: {
          level: 'medium',
          reason: 'high-value-transaction'
        }
      };
    }
    
    return preAuthData.transactionInitiationData;
  }
});

Customer type handling

Handle different customer types with varying risk profiles.

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 (preAuthData, threeDSData) => {
    const riskProfile = getCustomerRiskProfile('returning');
    
    return {
      ...preAuthData.transactionInitiationData,
      customerRisk: riskProfile
    };
  }
});

Step 4: Handle errors

Implement comprehensive error handling for non-3DS payments.

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: (result) => {
    if (!result.success) {
      // 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.

const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
  // Step 1: Process the transaction
  onPreAuthorisation: async (preAuthData, threeDSData) => {
    console.log('Processing card payment');
    
    // Log transaction details
    console.log(`Amount: ${preAuthData.transactionInitiationData.amounts.transaction}`);
    console.log(`Currency: ${preAuthData.transactionInitiationData.amounts.currencyCode}`);
    
    // Add any additional data
    return {
      ...preAuthData.transactionInitiationData,
      processingNotes: 'Non-3DS direct payment'
    };
  },

  // Step 2: Handle success/failure
  onPostAuthorisation: (result) => {
    if (result.success) {
      // Success - redirect to confirmation
      console.log('Payment completed successfully');
      sessionStorage.setItem('paymentSuccess', 'true');
      window.location.href = `/payment-success?tx=${result.transactionId}`;
    } else {
      // Failure - show error and allow retry
      console.error('Payment failed:', result.errorMessage);
      showError('Payment failed: ' + (result.errorMessage || '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:

  • Pre-authorisation data (preAuthData): Transaction data ready for authorisation.
  • 3DS data (threeDSData): This will be null or undefined for non-3DS flows.

Pre-authorisation data

The pre-authorisation data includes transaction initiation data (for new cards) or card token data (for saved cards).

{
  initiateIntegratedSuccessAuthenticationResult: null,
  transactionInitiationData: {
    threeDSecureData: null,
    psd2Data: {
      scaExemption: "LowValue"
    },
    identityVerification: {
      nameVerification: true
    },
    addressVerification: {
      countryCode: "US",
      houseNumberOrName: "123",
      postalCode: "10001"
    }
  },
  cardTokenData: null
}
{
  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 alwaysnull 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 alwaysnull 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.

Here's an example of what to do with this data:

onPreAuthorisation: async (preAuthData, threeDSData) => {
  console.log('Card transaction data:', preAuthData);
  
  if (threeDSData) {
    console.warn('Unexpected 3DS data in non-3DS flow');
  }
  
  const transactionData = preAuthData.transactionInitiationData;
  
  // Add fraud prevention data
  const enhancedData = {
    ...transactionData,
    
    // Add browser fingerprinting
    deviceData: {
      userAgent: navigator.userAgent,
      language: navigator.language,
      screenResolution: `${screen.width}x${screen.height}`,
      timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
    },
    
    // Add session information
    sessionData: {
      sessionId: generateSessionId(),
      timestamp: new Date().toISOString(),
      ipAddress: await getClientIP() // You'd implement this
    },
    
    // Add custom risk indicators
    riskIndicators: {
      customerType: 'returning',
      paymentHistory: 'good',
      velocityCheck: 'passed'
    }
  };
  
  console.log('Sending enhanced card transaction');
  return enhancedData;
}

onPostAuthorisation

The onPostAuthorisation callback receives the final transaction result (BaseSubmitResult).

Success

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

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

Here's an example of what to do with this data:

onPostAuthorisation: (result) => {
  console.log('Non-3DS payment result:', result);
  
  if (result.success) {
    console.log('Payment successful!');
    console.log(`Transaction ID: ${result.transactionId}`);
    
    // Verify no 3DS data (should be null)
    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({
      transactionId: result.transactionId,
      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?tx=${result.transactionId}`;
    
  } else {
    handlePaymentFailure(result);
  }
}

Failure (declined)

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

{
  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
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:

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.

{
  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:

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('Tokenization 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'
  });
}