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.,
onPreInitiateAuthenticationcallback 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.
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: {
card: 'Authorisation'
},
merchantTransactionId: 'order-123',
merchantTransactionDate: () => new Date().toISOString()
},
// Your other SDK configuration
};Implement the required callbacks for non-3DS payments.
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.');
}
});Use different processing based on transaction amounts.
const cardSubmitComponent = new CardSubmitComponent(sdkConfig, {
onPreAuthorisation: async (data) => {
const amount = sdkConfig.transactionData.amount;
// Get authorization 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()
};
}
});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 (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
}
}
};
}
});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: 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.');
}
}
}
});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 (data) => {
console.log('Processing card payment for token:', data.gatewayTokenId);
// Get authorization decision from backend
const decision = await getAuthorisationDecision(data.gatewayTokenId);
if (!decision) {
return null; // Skip authorization
}
// 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.');
}
});This section describes the data received by the different callbacks as part of the non-3DS flow.
The onPreAuthorisation callback receives only the gateway token ID. Use this ID to retrieve token details and make authorization decisions on your backend.
| Parameter | Description |
|---|---|
dataobject | Object containing token information. |
data.gatewayTokenIdstring | 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 API to retrieve full token details including card scheme, funding source (credit/debit), masked PAN, and expiry date.
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 authorization');
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.
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).
{
initiateIntegratedSuccessAuthenticationResult: null,
transactionInitiationData: {
threeDSecureData: null,
psd2Data: {
scaExemption: "LowValue"
},
identityVerification: {
nameVerification: true
},
addressVerification: {
countryCode: "US",
houseNumberOrName: "123",
postalCode: "10001"
}
},
cardTokenData: null
}| Parameter | Description |
|---|---|
initiateIntegratedSuccessAuthenticationResultobject | This is alwaysnull for non-3DS transactions. |
transactionInitiationDataobject | Details about the transaction, if associated with a new card, null otherwise. |
transactionInitiationData.threeDSecureDataobject or null | This is alwaysnull for non-3DS transactions. |
transactionInitiationData.psd2Dataobject or null | Details about PSD2. This is required for non-3DS transactions. |
transactionInitiationData.psd2Data.scaExemptionstring or null | The type of SCA exemption that applies to this transaction. Possible values:
|
transactionInitiationData.identityVerificationobject | Details about the identity verification. |
transactionInitiationData.identityVerification.nameVerificationboolean | Whether the cardholder's name matches the name associated with the registered address on file. |
transactionInitiationData.addressVerificationobject | Details about the address verification. |
transactionInitiationData.identityVerification.countryCodestring | The country code associated with the cardholder's address, in ISO-3166-1 alpha-2 format. |
transactionInitiationData.identityVerification.houseNumberOrNamestring | The cardholder's street address. |
transactionInitiationData.identityVerification.postalCodestring | The postal or ZIP code associated with the cardholder's address. |
cardTokenDataobject | Details about the card token if associated with a saved card, null otherwise. |
cardToken.gatewayTokenIdstring or null | The gateway token ID. |
cardToken.schemeTokenIdstring or null | The scheme token ID. |
cardToken.maskedPrimaryAccountNumberstring or null | The masked Primary Account Number (PAN). |
cardToken.cardExpiryMonthstring or null | The expiry month (MM) of the card. |
cardToken.cardExpiryYearstring or null | The expiry year (YYYY) of the card. |
cardToken.schemestring or null | The card scheme. |
cardToken.fundingSourcestring or null | The funding source. |
cardToken.ownerTypestring or null | The owner type. |
cardToken.issuerNamestring or null | The issuer name. |
cardToken.issuerCountryCodestring or null | The country code of the issuer. |
cardToken.lastSuccessfulPurchaseDatestring or null | The date of the last successful purchase. |
cardToken.lastSuccessfulPayoutDatestring or null | The date of the last successful payout. |
The onPostAuthorisation callback receives only the transaction identifiers. Use these IDs to retrieve full transaction details from your backend.
| Parameter | Description |
|---|---|
dataobject | Object containing transaction identifiers. |
data.merchantTransactionIdstring | The merchant's unique identifier for the transaction. Use this with systemTransactionId to retrieve full authorisation details from Unity backend. |
data.systemTransactionIdstring | The system's unique identifier for the transaction. Use this with merchantTransactionId to retrieve full authorisation details from Unity backend. |
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);
}
}When you retrieve the full transaction result from your backend, you'll receive detailed information:
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 |
|---|---|
statestring | The final state of the transaction. Possible values:
|
providerResponseobject | Details about the provider's response. |
providerResponse.codestring | The raw result code returned by the provider that processed the transaction. |
providerResponse.messagestring | The raw message associated with the result code from the provider that processed the transaction. |
providerResponse.cardVerificationCodeResultstring | 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.addressVerificationServiceResultstring | 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. |
fundingDataobject | Details about the payment method. |
fundingData.cardVerificationCodeResultstring | The Card Verification Code (CVC) result in human-readable format. |
fundingData.addressVerificationServiceResultstring | The Address Verification Service (AVS) result in human-readable format. |
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 |
|---|---|
statestring | The final state of the transaction. Possible values:
|
stateDataobject | Additional details about the state. |
stateData.codestring | The state code. |
stateData.messagestring | The state message. |
providerResponseobject | Details about the provider's response. |
providerResponse.codestring | The raw result code returned by the provider that processed the transaction. |
providerResponse.messagestring | The raw message associated with the result code from the provider that processed the transaction. |
providerResponse.cardVerificationCodeResultstring | 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.addressVerificationServiceResultstring | 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. |
fundingDataobject | Details about the payment method. |
fundingData.cardVerificationCodeResultstring | The Card Verification Code (CVC) result in human-readable format. |
fundingData.addressVerificationServiceResultstring | 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'
});
}