generated from vignesh/searchengine
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 34s
157 lines
6.9 KiB
Python
157 lines
6.9 KiB
Python
from django.db import models
|
|
|
|
|
|
class PaymentGatewayConfig(models.Model):
|
|
"""Configuration and health tracking for each integrated payment gateway."""
|
|
HEALTH_CHOICES = [
|
|
('HEALTHY', 'Healthy'),
|
|
('DEGRADED', 'Degraded'),
|
|
('DOWN', 'Down'),
|
|
]
|
|
ENV_CHOICES = [
|
|
('sandbox', 'Sandbox'),
|
|
('production', 'Production'),
|
|
]
|
|
|
|
name = models.CharField(max_length=50, unique=True, help_text="Gateway identifier e.g. CASHFREE, RAZORPAY")
|
|
display_name = models.CharField(max_length=100)
|
|
is_active = models.BooleanField(default=True)
|
|
priority = models.PositiveIntegerField(default=1, help_text="Routing priority: 1 is primary, 2 is secondary")
|
|
health_status = models.CharField(max_length=20, choices=HEALTH_CHOICES, default='HEALTHY')
|
|
consecutive_failures = models.PositiveIntegerField(default=0)
|
|
failure_threshold = models.PositiveIntegerField(default=3, help_text="Number of failures before circuit trips to DOWN")
|
|
app_id = models.CharField(max_length=255, blank=True, default='')
|
|
secret_key = models.CharField(max_length=255, blank=True, default='')
|
|
environment = models.CharField(max_length=20, choices=ENV_CHOICES, default='sandbox')
|
|
api_version = models.CharField(max_length=20, default='2023-08-01')
|
|
last_health_check = models.DateTimeField(blank=True, null=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ['priority', 'name']
|
|
|
|
def __str__(self):
|
|
return f"{self.display_name} ({self.name}) - Priority: {self.priority} [{self.health_status}]"
|
|
|
|
def record_success(self):
|
|
"""Reset failures and set health to HEALTHY."""
|
|
self.consecutive_failures = 0
|
|
self.health_status = 'HEALTHY'
|
|
self.save(update_fields=['consecutive_failures', 'health_status', 'updated_at'])
|
|
|
|
def record_failure(self):
|
|
"""Increment failure counter and degrade/trip circuit if threshold exceeded."""
|
|
self.consecutive_failures += 1
|
|
if self.consecutive_failures >= self.failure_threshold:
|
|
self.health_status = 'DOWN'
|
|
else:
|
|
self.health_status = 'DEGRADED'
|
|
self.save(update_fields=['consecutive_failures', 'health_status', 'updated_at'])
|
|
|
|
|
|
class PaymentOrder(models.Model):
|
|
"""Unified Order model across all gateways."""
|
|
STATUS_CHOICES = [
|
|
('INITIATED', 'Initiated'),
|
|
('ACTIVE', 'Active'),
|
|
('PAID', 'Paid'),
|
|
('FAILED', 'Failed'),
|
|
('EXPIRED', 'Expired'),
|
|
('TERMINATED', 'Terminated'),
|
|
('REFUNDED', 'Refunded'),
|
|
]
|
|
|
|
order_id = models.CharField(max_length=100, unique=True, primary_key=True)
|
|
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
|
currency = models.CharField(max_length=3, default='INR')
|
|
customer_id = models.CharField(max_length=100)
|
|
customer_name = models.CharField(max_length=200, blank=True, default='')
|
|
customer_email = models.EmailField(blank=True, null=True)
|
|
customer_phone = models.CharField(max_length=20, blank=True, default='')
|
|
status = models.CharField(max_length=30, choices=STATUS_CHOICES, default='INITIATED')
|
|
active_gateway = models.CharField(max_length=50, default='CASHFREE')
|
|
return_url = models.TextField(blank=True, null=True)
|
|
notify_url = models.TextField(blank=True, null=True)
|
|
order_note = models.TextField(blank=True, null=True)
|
|
metadata = models.JSONField(blank=True, default=dict)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
def __str__(self):
|
|
return f"{self.order_id} - {self.amount} {self.currency} [{self.status}]"
|
|
|
|
|
|
class PaymentAttempt(models.Model):
|
|
"""Tracks each payment attempt with a specific gateway."""
|
|
STATUS_CHOICES = [
|
|
('INITIATED', 'Initiated'),
|
|
('SUCCESS', 'Success'),
|
|
('FAILED', 'Failed'),
|
|
('PENDING', 'Pending'),
|
|
('USER_DROPPED', 'User Dropped'),
|
|
('CANCELLED', 'Cancelled'),
|
|
]
|
|
|
|
order = models.ForeignKey(PaymentOrder, on_delete=models.CASCADE, related_name='attempts')
|
|
gateway_name = models.CharField(max_length=50)
|
|
gateway_order_id = models.CharField(max_length=100, blank=True, null=True)
|
|
payment_session_id = models.TextField(blank=True, null=True)
|
|
gateway_payment_id = models.CharField(max_length=100, blank=True, null=True)
|
|
status = models.CharField(max_length=30, choices=STATUS_CHOICES, default='INITIATED')
|
|
payment_method = models.CharField(max_length=50, blank=True, null=True)
|
|
bank_reference = models.CharField(max_length=100, blank=True, null=True)
|
|
error_code = models.CharField(max_length=100, blank=True, null=True)
|
|
error_message = models.TextField(blank=True, null=True)
|
|
raw_response = models.JSONField(blank=True, default=dict)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
|
|
def __str__(self):
|
|
return f"Attempt {self.id} for {self.order_id} via {self.gateway_name}: {self.status}"
|
|
|
|
|
|
class PaymentRefund(models.Model):
|
|
"""Refunds issued against orders."""
|
|
STATUS_CHOICES = [
|
|
('PENDING', 'Pending'),
|
|
('SUCCESS', 'Success'),
|
|
('FAILED', 'Failed'),
|
|
('CANCELLED', 'Cancelled'),
|
|
]
|
|
|
|
refund_id = models.CharField(max_length=100, unique=True, primary_key=True)
|
|
order = models.ForeignKey(PaymentOrder, on_delete=models.CASCADE, related_name='refunds')
|
|
gateway_name = models.CharField(max_length=50, default='CASHFREE')
|
|
gateway_refund_id = models.CharField(max_length=100, blank=True, null=True)
|
|
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
|
status = models.CharField(max_length=30, choices=STATUS_CHOICES, default='PENDING')
|
|
speed = models.CharField(max_length=20, default='STANDARD')
|
|
refund_arn = models.CharField(max_length=100, blank=True, null=True)
|
|
reason = models.TextField(blank=True, null=True)
|
|
raw_response = models.JSONField(blank=True, default=dict)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
def __str__(self):
|
|
return f"Refund {self.refund_id} for {self.order_id}: {self.amount} [{self.status}]"
|
|
|
|
|
|
class WebhookLog(models.Model):
|
|
"""Audit log of incoming webhooks from all gateways."""
|
|
gateway_name = models.CharField(max_length=50)
|
|
event_type = models.CharField(max_length=100, blank=True, default='')
|
|
signature_valid = models.BooleanField(default=False)
|
|
payload = models.JSONField(default=dict)
|
|
processed = models.BooleanField(default=False)
|
|
error_message = models.TextField(blank=True, null=True)
|
|
received_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['-received_at']
|
|
|
|
def __str__(self):
|
|
return f"{self.gateway_name} Webhook {self.event_type} at {self.received_at} [Valid: {self.signature_valid}]"
|