3D Secure Authentication
This section covers 3D Secure (3DS) authentication with the Cardinity Python SDK, including both 3DS1 and 3DS2 implementations for enhanced payment security.
Overview
3D Secure authentication provides an additional layer of security for online payments by:
Strong Customer Authentication (SCA): Meets PSD2 requirements in Europe
Fraud Prevention: Reduces chargebacks and fraudulent transactions
Liability Shift: Moves liability from merchant to issuer for authenticated transactions
Customer Trust: Increases customer confidence in payment security
3D Secure Workflow
The 3DS authentication process follows these steps:
Payment Creation: Create payment with 3DS parameters
Challenge Required: Payment status becomes
pending
if 3DS is requiredCustomer Authentication: Redirect customer to bank’s authentication page
Authentication Response: Customer completes authentication
Payment Finalization: Finalize payment with authentication result
Basic 3D Secure Example
Here’s a complete example of 3D Secure authentication:
"""
3D Secure Authentication Example
This example demonstrates the complete 3DS authentication workflow.
"""
import os
from cardinity import Cardinity, CardinityError
# Initialize client
cardinity = Cardinity(
consumer_key=os.getenv("CARDINITY_CONSUMER_KEY", "your_consumer_key_here"),
consumer_secret=os.getenv("CARDINITY_CONSUMER_SECRET", "your_consumer_secret_here")
)
def create_3ds_payment():
"""Create a payment that may require 3D Secure authentication."""
try:
payment = cardinity.create_payment(
amount="25.00",
currency="EUR",
description="3ds2-pass", # Triggers 3DS in test environment
country="LT",
payment_instrument={
"pan": "4111111111111111", # Test Visa card
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "John Doe"
},
# 3DS2 specific data
threeds2_data={
"notification_url": "https://your-site.com/3ds-callback",
"browser_info": {
"accept_header": "text/html",
"browser_language": "en-US",
"screen_width": 1920,
"screen_height": 1040,
"challenge_window_size": "500x600",
"user_agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0)",
"color_depth": 24,
"time_zone": -60,
"javascript_enabled": True,
"java_enabled": False
}
}
)
print(f"Payment created: {payment['id']}")
print(f"Status: {payment['status']}")
return payment
except CardinityError as e:
print(f"❌ Payment creation failed: {e}")
return None
def handle_3ds_authentication(payment):
"""Handle the 3D Secure authentication process."""
if not payment:
return None
if payment['status'] == 'approved':
print("✅ Payment approved without 3DS challenge")
return payment
elif payment['status'] == 'pending':
print("🔒 3D Secure authentication required")
if 'threeds2_data' in payment:
# 3DS2 flow
acs_url = payment['threeds2_data']['acs_url']
creq = payment['threeds2_data']['creq']
print(f"Redirect customer to ACS URL: {acs_url}")
print(f"POST creq parameter: {creq}")
# In a real application, you would:
# 1. Redirect customer to acs_url with creq
# 2. Customer completes authentication
# 3. Receive callback with authentication result
# 4. Finalize payment with the result
# For testing, simulate successful authentication
return finalize_3ds_payment(payment['id'], "3ds2-pass")
elif 'authorization_information' in payment:
# 3DS1 flow (legacy)
acs_url = payment['authorization_information']['url']
pareq = payment['authorization_information']['data']
print(f"3DS1 - Redirect to: {acs_url}")
print(f"3DS1 - PAReq: {pareq}")
# Similar process for 3DS1
return finalize_3ds_payment(payment['id'], "3ds1-pass")
elif payment['status'] == 'declined':
print("❌ Payment declined")
return payment
return payment
def finalize_3ds_payment(payment_id, auth_result):
"""Finalize the payment after 3DS authentication."""
try:
finalized_payment = cardinity.finalize_payment(
payment_id,
authorize_data=auth_result
)
print(f"✅ Payment finalized: {finalized_payment['status']}")
return finalized_payment
except CardinityError as e:
print(f"❌ Payment finalization failed: {e}")
return None
3DS2 Browser Information
Comprehensive browser information for 3DS2:
def get_browser_info_from_request(request):
"""Extract browser information from HTTP request (Flask/Django example)."""
browser_info = {
"accept_header": request.headers.get('Accept', 'text/html'),
"browser_language": request.headers.get('Accept-Language', 'en-US').split(',')[0],
"user_agent": request.headers.get('User-Agent', ''),
"color_depth": 24, # Default value
"screen_width": 1920, # Default value
"screen_height": 1080, # Default value
"time_zone": -60, # UTC offset in minutes
"javascript_enabled": True, # Assume enabled
"java_enabled": False, # Usually disabled
"challenge_window_size": "500x600" # Modal size
}
return browser_info
def create_3ds2_payment_with_browser_info(browser_info):
"""Create 3DS2 payment with proper browser information."""
payment = cardinity.create_payment(
amount="50.00",
currency="EUR",
description="3DS2 payment with browser info",
country="LT",
payment_instrument={
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Test User"
},
threeds2_data={
"notification_url": "https://your-site.com/webhooks/3ds-callback",
"browser_info": browser_info
}
)
return payment
Handling Different 3DS Scenarios
Different 3DS outcomes and how to handle them:
def handle_all_3ds_scenarios():
"""Handle all possible 3DS authentication scenarios."""
scenarios = [
{
"name": "Frictionless - No Challenge",
"description": "3ds2-pass",
"expected": "approved"
},
{
"name": "Challenge Required",
"description": "3ds2-challenge",
"expected": "pending"
},
{
"name": "Authentication Failed",
"description": "3ds2-fail",
"expected": "declined"
},
{
"name": "Technical Error",
"description": "3ds2-error",
"expected": "declined"
}
]
for scenario in scenarios:
print(f"\n🧪 Testing: {scenario['name']}")
payment = cardinity.create_payment(
amount="30.00",
currency="EUR",
description=scenario['description'],
country="LT",
payment_instrument={
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Test User"
},
threeds2_data={
"notification_url": "https://your-site.com/3ds-callback",
"browser_info": {
"accept_header": "text/html",
"browser_language": "en-US",
"screen_width": 1920,
"screen_height": 1040,
"user_agent": "Mozilla/5.0 Test Browser",
"color_depth": 24,
"time_zone": -60,
"javascript_enabled": True,
"java_enabled": False
}
}
)
if payment:
print(f" Status: {payment['status']}")
if payment['status'] == 'pending':
# Handle challenge flow
finalized = finalize_3ds_payment(payment['id'], scenario['description'])
if finalized:
print(f" Final Status: {finalized['status']}")
Web Integration Example
Complete web application integration:
"""
Flask Web Application Example for 3DS Integration
This example shows how to integrate 3DS in a web application.
"""
from flask import Flask, request, render_template, redirect, session
import json
app = Flask(__name__)
app.secret_key = 'your-secret-key'
@app.route('/checkout', methods=['POST'])
def process_checkout():
"""Process checkout with 3DS support."""
# Get form data
amount = request.form.get('amount')
card_number = request.form.get('card_number')
exp_month = request.form.get('exp_month')
exp_year = request.form.get('exp_year')
cvc = request.form.get('cvc')
holder = request.form.get('holder')
# Extract browser information
browser_info = {
"accept_header": request.headers.get('Accept', 'text/html'),
"browser_language": request.headers.get('Accept-Language', 'en-US').split(',')[0],
"user_agent": request.headers.get('User-Agent', ''),
"color_depth": 24,
"screen_width": 1920,
"screen_height": 1080,
"time_zone": -60,
"javascript_enabled": True,
"java_enabled": False,
"challenge_window_size": "500x600"
}
try:
# Create payment with 3DS data
payment = cardinity.create_payment(
amount=amount,
currency="EUR",
description="Online purchase",
country="LT",
payment_instrument={
"pan": card_number,
"exp_month": int(exp_month),
"exp_year": int(exp_year),
"cvc": cvc,
"holder": holder
},
threeds2_data={
"notification_url": request.url_root + "webhooks/3ds-callback",
"browser_info": browser_info
}
)
# Store payment ID in session
session['payment_id'] = payment['id']
if payment['status'] == 'approved':
# Payment successful without challenge
return redirect('/success')
elif payment['status'] == 'pending':
# 3DS challenge required
if 'threeds2_data' in payment:
acs_url = payment['threeds2_data']['acs_url']
creq = payment['threeds2_data']['creq']
# Render 3DS challenge page
return render_template('3ds_challenge.html',
acs_url=acs_url,
creq=creq,
payment_id=payment['id'])
else:
# Payment declined or error
return redirect('/failure')
except CardinityError as e:
return render_template('error.html', error=str(e))
@app.route('/3ds-callback', methods=['POST'])
def handle_3ds_callback():
"""Handle 3DS authentication callback."""
payment_id = session.get('payment_id')
cres = request.form.get('cres') # Challenge response
if not payment_id or not cres:
return redirect('/failure')
try:
# Finalize payment with 3DS response
finalized_payment = cardinity.finalize_payment(
payment_id,
authorize_data=cres
)
if finalized_payment['status'] == 'approved':
return redirect('/success')
else:
return redirect('/failure')
except CardinityError as e:
return render_template('error.html', error=str(e))
@app.route('/webhooks/3ds-callback', methods=['POST'])
def webhook_3ds_callback():
"""Handle 3DS webhook notifications."""
# Process webhook data
webhook_data = request.get_json()
# Update payment status in your system
# This is called when 3DS authentication completes
return '', 200
Mobile App Integration
3DS integration for mobile applications:
def mobile_3ds_integration():
"""Example of 3DS integration for mobile apps."""
# Mobile-specific browser info
mobile_browser_info = {
"accept_header": "application/json",
"browser_language": "en-US",
"user_agent": "YourApp/1.0 (iOS 15.0; iPhone13,2)",
"color_depth": 32,
"screen_width": 390, # iPhone screen width
"screen_height": 844, # iPhone screen height
"time_zone": -300, # EST timezone
"javascript_enabled": False, # Native app
"java_enabled": False,
"challenge_window_size": "390x600" # Mobile modal size
}
payment = cardinity.create_payment(
amount="99.99",
currency="EUR",
description="Mobile app purchase",
country="LT",
payment_instrument={
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Mobile User"
},
threeds2_data={
"notification_url": "https://your-api.com/webhooks/3ds-mobile",
"browser_info": mobile_browser_info
}
)
if payment['status'] == 'pending' and 'threeds2_data' in payment:
# Return 3DS data to mobile app
return {
"requires_3ds": True,
"acs_url": payment['threeds2_data']['acs_url'],
"creq": payment['threeds2_data']['creq'],
"payment_id": payment['id']
}
return payment
Advanced 3DS Configuration
Advanced configuration options for 3DS:
def advanced_3ds_configuration():
"""Advanced 3DS configuration with additional parameters."""
# Comprehensive 3DS2 data
advanced_3ds_data = {
"notification_url": "https://your-site.com/webhooks/3ds",
"browser_info": {
"accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"browser_language": "en-US,en;q=0.5",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"color_depth": 24,
"screen_width": 1920,
"screen_height": 1080,
"time_zone": -300,
"javascript_enabled": True,
"java_enabled": False,
"challenge_window_size": "500x600"
},
# Additional merchant data
"merchant_data": {
"merchant_name": "Your Store Name",
"merchant_url": "https://your-store.com"
},
# Transaction-specific data
"transaction_data": {
"shipping_address_usage": "current_transaction",
"delivery_timeframe": "electronic_delivery",
"pre_order_purchase": False,
"reorder_items": False
}
}
payment = cardinity.create_payment(
amount="150.00",
currency="EUR",
description="High-value transaction requiring 3DS",
country="LT",
payment_instrument={
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Premium Customer"
},
threeds2_data=advanced_3ds_data
)
return payment
Error Handling for 3DS
Comprehensive error handling for 3DS flows:
def robust_3ds_handling(payment_data):
"""Robust 3DS handling with comprehensive error management."""
try:
# Create payment
payment = cardinity.create_payment(**payment_data)
if not payment:
return {"error": "Payment creation failed"}
# Handle different statuses
if payment['status'] == 'approved':
return {
"success": True,
"payment_id": payment['id'],
"requires_3ds": False
}
elif payment['status'] == 'pending':
# Check for 3DS data
if 'threeds2_data' in payment:
return {
"success": True,
"payment_id": payment['id'],
"requires_3ds": True,
"acs_url": payment['threeds2_data']['acs_url'],
"creq": payment['threeds2_data']['creq']
}
else:
return {"error": "Payment pending but no 3DS data available"}
elif payment['status'] == 'declined':
return {
"error": "Payment declined",
"reason": payment.get('error', 'Unknown reason')
}
else:
return {"error": f"Unknown payment status: {payment['status']}"}
except ValidationError as e:
return {"error": f"Validation error: {str(e)}"}
except APIError as e:
return {"error": f"API error: {str(e)}"}
except CardinityError as e:
return {"error": f"Cardinity error: {str(e)}"}
Testing 3DS Flows
Test different 3DS scenarios:
def test_3ds_flows():
"""Test various 3DS authentication flows."""
test_cases = [
{
"name": "Frictionless Success",
"description": "3ds2-pass",
"expected_initial": "approved",
"expected_final": "approved"
},
{
"name": "Challenge Success",
"description": "3ds2-challenge",
"expected_initial": "pending",
"expected_final": "approved"
},
{
"name": "Authentication Failed",
"description": "3ds2-fail",
"expected_initial": "pending",
"expected_final": "declined"
},
{
"name": "Bypass 3DS (Low Amount)",
"description": "Low amount transaction",
"amount": "5.00",
"expected_initial": "approved",
"expected_final": "approved"
}
]
for test_case in test_cases:
print(f"\n🧪 Testing: {test_case['name']}")
payment_data = {
"amount": test_case.get("amount", "100.00"),
"currency": "EUR",
"description": test_case["description"],
"country": "LT",
"payment_instrument": {
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Test User"
},
"threeds2_data": {
"notification_url": "https://test.com/3ds-callback",
"browser_info": {
"accept_header": "text/html",
"browser_language": "en-US",
"user_agent": "Test Browser",
"color_depth": 24,
"screen_width": 1920,
"screen_height": 1080,
"time_zone": -60,
"javascript_enabled": True,
"java_enabled": False
}
}
}
result = robust_3ds_handling(payment_data)
if result.get("success"):
print(f" ✅ Initial status matches expected: {test_case['expected_initial']}")
if result.get("requires_3ds"):
# Simulate 3DS completion
finalized = finalize_3ds_payment(
result["payment_id"],
test_case["description"]
)
if finalized:
print(f" ✅ Final status: {finalized['status']}")
else:
print(f" ❌ Test failed: {result.get('error')}")
Best Practices for 3DS
"""
3D Secure Best Practices
"""
def threeds_best_practices():
"""Examples of 3DS best practices."""
# 1. Always provide accurate browser information
def collect_accurate_browser_info():
"""Collect accurate browser information on frontend."""
# JavaScript example (to be included in your frontend)
js_browser_collection = """
// Collect browser info on frontend
const browserInfo = {
accept_header: document.querySelector('meta[http-equiv="accept"]')?.content || 'text/html',
browser_language: navigator.language || 'en-US',
user_agent: navigator.userAgent,
color_depth: screen.colorDepth || 24,
screen_width: screen.width,
screen_height: screen.height,
time_zone: -new Date().getTimezoneOffset(),
javascript_enabled: true,
java_enabled: navigator.javaEnabled(),
challenge_window_size: '500x600'
};
// Send this data to your backend
"""
return js_browser_collection
# 2. Handle all possible 3DS outcomes
def handle_all_outcomes(payment):
outcomes = {
'approved': lambda p: handle_approved_payment(p),
'declined': lambda p: handle_declined_payment(p),
'pending': lambda p: handle_3ds_challenge(p),
'error': lambda p: handle_payment_error(p)
}
handler = outcomes.get(payment['status'], lambda p: handle_unknown_status(p))
return handler(payment)
# 3. Implement proper callback handling
def secure_callback_handling():
"""Secure 3DS callback implementation."""
# Validate callback authenticity
# Store payment state securely
# Handle both success and failure cases
# Provide clear user feedback
pass
# 4. User experience considerations
def ux_considerations():
"""UX best practices for 3DS."""
ux_tips = [
"Show loading indicators during 3DS process",
"Provide clear instructions to customers",
"Handle browser back button gracefully",
"Implement timeout handling for 3DS challenge",
"Provide fallback for failed 3DS authentication",
"Test on different devices and browsers"
]
return ux_tips
Production Considerations
Important considerations for production 3DS implementation:
Webhook Security: Validate webhook signatures
Session Management: Securely store payment state during 3DS flow
Browser Compatibility: Test across different browsers and devices
Mobile Optimization: Optimize 3DS challenge for mobile devices
Timeout Handling: Handle cases where customers don’t complete 3DS
Fallback Mechanisms: Provide alternatives when 3DS fails
Monitoring: Track 3DS success/failure rates
Compliance: Ensure PSD2 and SCA compliance
Next Steps
After implementing 3DS authentication:
Webhook Integration: Set up secure webhook handling
User Experience Optimization: Improve 3DS challenge UX
Analytics: Track authentication success rates
Fallback Strategies: Handle 3DS failures gracefully
Mobile Optimization: Optimize for mobile 3DS flows
6. Testing: Comprehensive testing across browsers and devices </rewritten_file>