Process an online card transaction without 3DS.
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.
The non-3DS payment flow is a streamlined process consisting of five key steps.
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.
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.
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.
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.
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.
To use non-3DS payments in your application:
- Ensure your merchant account supports non-3DS transactions.
- Configure your risk settings appropriately in the Unity Portal.
- Consider implementing additional fraud prevention measures.
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: 'customer@example.com',
firstName: 'John',
lastName: 'Doe'
}
},
// Your other SDK configuration
};
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.');
}
});
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;
}
});
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
};
}
});
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.');
}
}
}
});
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.');
}
});
This section describes the data received by the different callbacks as part of the non-3DS flow.
The onPreAuthorisation
callback receives:
- Pre-authorisation data (
preAuthData
): Transaction data ready for authorisation. - 3DS data (
threeDSData
): This will benull
orundefined
for non-3DS flows.
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
}
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:
|
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;
}
The onPostAuthorisation
callback receives the final transaction result (BaseSubmitResult
).
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:
|
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);
}
}
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:
|
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:
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();
}
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'
});
}