Error Handling
This section covers comprehensive error handling with the Cardinity Python SDK, including exception types, retry strategies, and robust error management patterns.
Overview
Effective error handling includes:
Exception Types: Understanding different Cardinity exceptions
Validation Errors: Handling input validation failures
API Errors: Managing network and API communication issues
Payment Errors: Handling payment-specific failures
Retry Strategies: Implementing intelligent retry logic
Logging: Proper error logging and monitoring
Exception Types
The Cardinity SDK provides several exception types:
"""
Cardinity Exception Types
Understanding and handling different exception types.
"""
import os
from cardinity import (
Cardinity,
CardinityError, # Base exception
ValidationError, # Input validation failures
APIError, # API communication issues
AuthenticationError, # Authentication failures
NotFoundError, # Resource not found
RateLimitError # Rate limiting
)
# 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 demonstrate_exception_types():
"""Demonstrate different exception types and how to handle them."""
# 1. Validation Error Example
try:
payment = cardinity.create_payment(
amount="invalid_amount", # Invalid amount format
currency="EUR",
description="Test payment",
country="LT",
payment_instrument={
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Test User"
}
)
except ValidationError as e:
print(f"❌ Validation Error: {e}")
print(" Check your input data format and required fields")
# 2. Authentication Error Example
try:
invalid_client = Cardinity(
consumer_key="invalid_key",
consumer_secret="invalid_secret"
)
payment = invalid_client.create_payment(
amount="10.00",
currency="EUR",
description="Test",
country="LT",
payment_instrument={
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Test User"
}
)
except AuthenticationError as e:
print(f"❌ Authentication Error: {e}")
print(" Check your API credentials")
# 3. Not Found Error Example
try:
payment = cardinity.get_payment("non-existent-payment-id")
except NotFoundError as e:
print(f"❌ Not Found Error: {e}")
print(" The requested resource does not exist")
# 4. General API Error Example
try:
# This might cause an API error depending on server state
payment = cardinity.create_payment(
amount="10.00",
currency="INVALID_CURRENCY", # Invalid currency
description="Test",
country="LT",
payment_instrument={
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Test User"
}
)
except APIError as e:
print(f"❌ API Error: {e}")
print(" Server-side error or invalid request")
# 5. Rate Limit Error Example (rare in normal usage)
try:
# Simulate rapid requests that might trigger rate limiting
for i in range(100):
payment = cardinity.create_payment(
amount="1.00",
currency="EUR",
description=f"Rate limit test {i}",
country="LT",
payment_instrument={
"pan": "4111111111111111",
"exp_month": 12,
"exp_year": 2025,
"cvc": "123",
"holder": "Test User"
}
)
except RateLimitError as e:
print(f"❌ Rate Limit Error: {e}")
print(" Too many requests - implement backoff strategy")
Comprehensive Error Handling
Robust error handling for payment operations:
import time
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_payment_with_robust_error_handling(payment_data, max_retries=3):
"""Create payment with comprehensive error handling and retry logic."""
for attempt in range(max_retries):
try:
logger.info(f"Creating payment - attempt {attempt + 1}/{max_retries}")
payment = cardinity.create_payment(**payment_data)
# Success - log and return
logger.info(f"✅ Payment created successfully: {payment['id']}")
return {
'success': True,
'payment': payment,
'attempts': attempt + 1
}
except ValidationError as e:
# Validation errors are not retryable
logger.error(f"❌ Validation error: {str(e)}")
return {
'success': False,
'error_type': 'validation',
'error_message': str(e),
'retryable': False,
'attempts': attempt + 1
}
except AuthenticationError as e:
# Authentication errors are not retryable
logger.error(f"❌ Authentication error: {str(e)}")
return {
'success': False,
'error_type': 'authentication',
'error_message': str(e),
'retryable': False,
'attempts': attempt + 1
}
except RateLimitError as e:
# Rate limit errors are retryable with backoff
wait_time = 2 ** attempt # Exponential backoff
logger.warning(f"⚠️ Rate limit hit - waiting {wait_time}s before retry")
if attempt < max_retries - 1:
time.sleep(wait_time)
continue
else:
logger.error(f"❌ Rate limit exceeded after {max_retries} attempts")
return {
'success': False,
'error_type': 'rate_limit',
'error_message': str(e),
'retryable': True,
'attempts': attempt + 1
}
except APIError as e:
# API errors might be retryable
logger.warning(f"⚠️ API error on attempt {attempt + 1}: {str(e)}")
if attempt < max_retries - 1:
wait_time = 1 + attempt # Linear backoff for API errors
logger.info(f" Retrying in {wait_time} seconds...")
time.sleep(wait_time)
continue
else:
logger.error(f"❌ API error after {max_retries} attempts")
return {
'success': False,
'error_type': 'api',
'error_message': str(e),
'retryable': True,
'attempts': attempt + 1
}
except CardinityError as e:
# Generic Cardinity errors
logger.error(f"❌ Cardinity error: {str(e)}")
return {
'success': False,
'error_type': 'cardinity',
'error_message': str(e),
'retryable': False,
'attempts': attempt + 1
}
except Exception as e:
# Unexpected errors
logger.error(f"❌ Unexpected error: {str(e)}")
return {
'success': False,
'error_type': 'unexpected',
'error_message': str(e),
'retryable': False,
'attempts': attempt + 1
}
# Should not reach here, but just in case
return {
'success': False,
'error_type': 'unknown',
'error_message': 'Unknown error after all retry attempts',
'retryable': False,
'attempts': max_retries
}
Payment-Specific Error Handling
Handle payment-specific error scenarios:
def handle_payment_errors(payment_result):
"""Handle specific payment error scenarios."""
if payment_result.get('success'):
payment = payment_result['payment']
if payment['status'] == 'approved':
return {
'outcome': 'success',
'message': 'Payment approved successfully',
'payment_id': payment['id']
}
elif payment['status'] == 'declined':
# Handle declined payments
decline_reason = payment.get('error', 'Unknown reason')
# Map decline reasons to user-friendly messages
decline_messages = {
'insufficient_funds': 'Insufficient funds in account',
'expired_card': 'Card has expired',
'invalid_card': 'Invalid card number',
'blocked_card': 'Card is blocked',
'do_not_honor': 'Transaction declined by bank',
'generic_decline': 'Transaction declined'
}
user_message = decline_messages.get(
decline_reason,
f'Payment declined: {decline_reason}'
)
return {
'outcome': 'declined',
'message': user_message,
'decline_reason': decline_reason,
'payment_id': payment['id']
}
elif payment['status'] == 'pending':
# Handle pending payments (likely 3DS)
if 'threeds2_data' in payment:
return {
'outcome': 'requires_3ds',
'message': '3D Secure authentication required',
'payment_id': payment['id'],
'acs_url': payment['threeds2_data']['acs_url'],
'creq': payment['threeds2_data']['creq']
}
else:
return {
'outcome': 'pending',
'message': 'Payment is being processed',
'payment_id': payment['id']
}
else:
# Handle API/system errors
error_type = payment_result.get('error_type')
error_message = payment_result.get('error_message')
if error_type == 'validation':
return {
'outcome': 'validation_error',
'message': 'Please check your payment information',
'details': error_message
}
elif error_type == 'authentication':
return {
'outcome': 'system_error',
'message': 'Payment system temporarily unavailable',
'details': 'Authentication failed'
}
elif error_type == 'rate_limit':
return {
'outcome': 'temporary_error',
'message': 'Too many requests. Please try again later.',
'retry_after': 60 # seconds
}
else:
return {
'outcome': 'system_error',
'message': 'Payment system temporarily unavailable',
'details': error_message
}
Circuit Breaker Pattern
Implement circuit breaker pattern for API resilience:
class PaymentCircuitBreaker:
"""Circuit breaker for payment operations."""
def __init__(self, failure_threshold=5, timeout=60):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.failure_count = 0
self.last_failure_time = None
self.state = 'closed' # closed, open, half_open
def can_execute(self):
"""Check if operation can be executed."""
if self.state == 'closed':
return True
elif self.state == 'open':
# Check if timeout has passed
if (time.time() - self.last_failure_time) > self.timeout:
self.state = 'half_open'
return True
return False
elif self.state == 'half_open':
return True
return False
def record_success(self):
"""Record successful operation."""
self.failure_count = 0
self.state = 'closed'
def record_failure(self):
"""Record failed operation."""
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'open'
# Global circuit breaker instance
payment_circuit_breaker = PaymentCircuitBreaker()
def create_payment_with_circuit_breaker(payment_data):
"""Create payment with circuit breaker protection."""
if not payment_circuit_breaker.can_execute():
return {
'success': False,
'error_type': 'circuit_breaker',
'error_message': 'Payment service temporarily unavailable'
}
try:
result = create_payment_with_robust_error_handling(payment_data)
if result['success']:
payment_circuit_breaker.record_success()
else:
# Only record failure for certain error types
if result.get('error_type') in ['api', 'rate_limit']:
payment_circuit_breaker.record_failure()
return result
except Exception as e:
payment_circuit_breaker.record_failure()
raise
Error Logging and Monitoring
Implement comprehensive error logging:
import json
from datetime import datetime
class PaymentErrorLogger:
"""Enhanced error logging for payment operations."""
def __init__(self):
self.logger = logging.getLogger('payment_errors')
def log_payment_error(self, operation, error_details, context=None):
"""Log payment error with context."""
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'operation': operation,
'error_type': error_details.get('error_type'),
'error_message': error_details.get('error_message'),
'attempts': error_details.get('attempts', 1),
'retryable': error_details.get('retryable', False),
'context': context or {}
}
# Add sensitive data filtering
if 'payment_data' in log_entry['context']:
log_entry['context']['payment_data'] = self._filter_sensitive_data(
log_entry['context']['payment_data']
)
self.logger.error(json.dumps(log_entry))
def log_payment_success(self, operation, payment_id, attempts=1, context=None):
"""Log successful payment operation."""
log_entry = {
'timestamp': datetime.utcnow().isoformat(),
'operation': operation,
'status': 'success',
'payment_id': payment_id,
'attempts': attempts,
'context': context or {}
}
self.logger.info(json.dumps(log_entry))
def _filter_sensitive_data(self, data):
"""Remove sensitive data from logs."""
filtered_data = data.copy()
# Remove sensitive payment instrument data
if 'payment_instrument' in filtered_data:
instrument = filtered_data['payment_instrument'].copy()
# Mask PAN (keep first 6 and last 4 digits)
if 'pan' in instrument:
pan = instrument['pan']
if len(pan) >= 10:
instrument['pan'] = pan[:6] + '*' * (len(pan) - 10) + pan[-4:]
# Remove CVC
if 'cvc' in instrument:
instrument['cvc'] = '***'
filtered_data['payment_instrument'] = instrument
return filtered_data
# Global error logger
error_logger = PaymentErrorLogger()
def create_payment_with_logging(payment_data):
"""Create payment with comprehensive logging."""
context = {
'amount': payment_data.get('amount'),
'currency': payment_data.get('currency'),
'country': payment_data.get('country'),
'payment_data': payment_data
}
result = create_payment_with_circuit_breaker(payment_data)
if result['success']:
error_logger.log_payment_success(
'create_payment',
result['payment']['id'],
result.get('attempts', 1),
context
)
else:
error_logger.log_payment_error(
'create_payment',
result,
context
)
return result
Graceful Degradation
Implement graceful degradation for service failures:
class PaymentServiceManager:
"""Manage payment service with graceful degradation."""
def __init__(self):
self.service_health = {
'payment_creation': True,
'payment_retrieval': True,
'refunds': True,
'settlements': True
}
self.maintenance_mode = False
def check_service_health(self):
"""Check health of payment services."""
# In production, this would check actual service health
# For now, simulate based on recent error rates
if payment_circuit_breaker.state == 'open':
self.service_health['payment_creation'] = False
else:
self.service_health['payment_creation'] = True
def create_payment_with_degradation(self, payment_data):
"""Create payment with graceful degradation."""
self.check_service_health()
if self.maintenance_mode:
return {
'success': False,
'error_type': 'maintenance',
'error_message': 'Payment system is under maintenance',
'retry_after': 1800 # 30 minutes
}
if not self.service_health['payment_creation']:
# Offer alternative or queue the payment
return {
'success': False,
'error_type': 'service_unavailable',
'error_message': 'Payment service temporarily unavailable',
'alternatives': [
'Try again in a few minutes',
'Use alternative payment method',
'Contact support for assistance'
]
}
return create_payment_with_logging(payment_data)
# Global service manager
payment_service = PaymentServiceManager()
Webhook Error Handling
Handle webhook-related errors:
def handle_webhook_errors(webhook_data):
"""Handle errors in webhook processing."""
try:
# Validate webhook signature (implement based on your webhook setup)
if not validate_webhook_signature(webhook_data):
logger.error("Invalid webhook signature")
return {'status': 'error', 'message': 'Invalid signature'}
# Process webhook data
event_type = webhook_data.get('event_type')
payment_id = webhook_data.get('payment_id')
if event_type == 'payment.approved':
handle_payment_approved(payment_id)
elif event_type == 'payment.declined':
handle_payment_declined(payment_id)
elif event_type == 'refund.approved':
handle_refund_approved(webhook_data.get('refund_id'))
else:
logger.warning(f"Unknown webhook event type: {event_type}")
return {'status': 'success'}
except Exception as e:
logger.error(f"Webhook processing error: {str(e)}")
# Return error response to trigger webhook retry
return {
'status': 'error',
'message': 'Webhook processing failed',
'retry': True
}
def validate_webhook_signature(webhook_data):
"""Validate webhook signature for security."""
# Implement signature validation based on your webhook configuration
return True # Placeholder
Testing Error Scenarios
Test error handling thoroughly:
def test_error_handling():
"""Test various error handling scenarios."""
test_cases = [
{
'name': 'Invalid Amount Format',
'payment_data': {
'amount': 'invalid',
'currency': 'EUR',
'description': 'Test',
'country': 'LT',
'payment_instrument': {
'pan': '4111111111111111',
'exp_month': 12,
'exp_year': 2025,
'cvc': '123',
'holder': 'Test User'
}
},
'expected_error': 'validation'
},
{
'name': 'Invalid Currency',
'payment_data': {
'amount': '10.00',
'currency': 'INVALID',
'description': 'Test',
'country': 'LT',
'payment_instrument': {
'pan': '4111111111111111',
'exp_month': 12,
'exp_year': 2025,
'cvc': '123',
'holder': 'Test User'
}
},
'expected_error': 'api'
},
{
'name': 'Missing Required Field',
'payment_data': {
'amount': '10.00',
'currency': 'EUR',
'description': 'Test',
# Missing country field
'payment_instrument': {
'pan': '4111111111111111',
'exp_month': 12,
'exp_year': 2025,
'cvc': '123',
'holder': 'Test User'
}
},
'expected_error': 'validation'
}
]
for test_case in test_cases:
print(f"\n🧪 Testing: {test_case['name']}")
result = create_payment_with_logging(test_case['payment_data'])
if not result['success']:
if result.get('error_type') == test_case['expected_error']:
print(f" ✅ Expected error type: {test_case['expected_error']}")
else:
print(f" ❌ Unexpected error type: {result.get('error_type')}")
else:
print(f" ❌ Expected failure but got success")
Best Practices Summary
"""
Error Handling Best Practices Summary
"""
def error_handling_best_practices():
"""Summary of error handling best practices."""
best_practices = [
{
"category": "Exception Handling",
"practices": [
"Catch specific exception types, not generic exceptions",
"Handle validation errors differently from API errors",
"Implement proper retry logic for transient errors",
"Use circuit breaker pattern for service resilience"
]
},
{
"category": "User Experience",
"practices": [
"Provide user-friendly error messages",
"Map technical errors to business-friendly language",
"Offer alternative actions when possible",
"Show progress indicators during retry attempts"
]
},
{
"category": "Logging and Monitoring",
"practices": [
"Log all errors with context but filter sensitive data",
"Use structured logging (JSON) for better parsing",
"Monitor error rates and patterns",
"Set up alerts for critical error thresholds"
]
},
{
"category": "Security",
"practices": [
"Never log sensitive payment data",
"Validate all webhook signatures",
"Implement rate limiting to prevent abuse",
"Use secure error messages that don't leak information"
]
},
{
"category": "Resilience",
"practices": [
"Implement exponential backoff for retries",
"Use circuit breaker pattern for failing services",
"Provide graceful degradation options",
"Queue operations when services are unavailable"
]
}
]
for category in best_practices:
print(f"\n{category['category']}:")
for practice in category['practices']:
print(f" • {practice}")
Production Error Monitoring
Set up production error monitoring:
"""
Production Error Monitoring Setup
"""
# 1. Error rate monitoring
def monitor_error_rates():
"""Monitor payment error rates and alert when thresholds exceeded."""
error_thresholds = {
'validation_errors': 0.05, # 5% of requests
'api_errors': 0.01, # 1% of requests
'authentication_errors': 0.001 # 0.1% of requests
}
# Implementation would connect to your monitoring system
pass
# 2. Service health checks
def health_check():
"""Health check endpoint for payment service."""
try:
# Test basic payment creation
test_result = create_payment_with_robust_error_handling({
'amount': '1.00',
'currency': 'EUR',
'description': 'Health check',
'country': 'LT',
'payment_instrument': {
'pan': '4111111111111111',
'exp_month': 12,
'exp_year': 2025,
'cvc': '123',
'holder': 'Health Check'
}
})
return {
'status': 'healthy' if test_result['success'] else 'unhealthy',
'timestamp': datetime.utcnow().isoformat(),
'details': test_result
}
except Exception as e:
return {
'status': 'unhealthy',
'timestamp': datetime.utcnow().isoformat(),
'error': str(e)
}
# 3. Error aggregation and reporting
def generate_error_report(start_date, end_date):
"""Generate error report for analysis."""
# Implementation would query your logging system
report = {
'period': {'start': start_date, 'end': end_date},
'total_requests': 0,
'total_errors': 0,
'error_rate': 0.0,
'error_breakdown': {
'validation_errors': 0,
'api_errors': 0,
'authentication_errors': 0,
'rate_limit_errors': 0,
'other_errors': 0
},
'top_error_messages': [],
'recommendations': []
}
return report
Next Steps
After implementing comprehensive error handling:
Monitoring Setup: Implement proper error monitoring and alerting
Error Analytics: Analyze error patterns and trends
User Experience: Optimize error messages and recovery flows
Service Resilience: Enhance circuit breaker and retry strategies
Documentation: Document error codes and troubleshooting guides