paymentservice/payments/models.py
2026-10-18 21:27:39 +05:30

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}]"