docs and billing adn acaoutn 40%
This commit is contained in:
@@ -158,3 +158,249 @@ class CreditCostConfig(models.Model):
|
||||
except CreditCostConfig.DoesNotExist:
|
||||
pass
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Invoice(AccountBaseModel):
|
||||
"""
|
||||
Invoice for subscription or credit purchases
|
||||
Tracks billing invoices with line items and payment status
|
||||
"""
|
||||
STATUS_CHOICES = [
|
||||
('draft', 'Draft'),
|
||||
('pending', 'Pending'),
|
||||
('paid', 'Paid'),
|
||||
('void', 'Void'),
|
||||
('uncollectible', 'Uncollectible'),
|
||||
]
|
||||
|
||||
invoice_number = models.CharField(max_length=50, unique=True, db_index=True)
|
||||
subscription = models.ForeignKey(
|
||||
'igny8_core_auth.Subscription',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='invoices'
|
||||
)
|
||||
|
||||
# Amounts
|
||||
subtotal = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
tax = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||
total = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
|
||||
# Status
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', db_index=True)
|
||||
|
||||
# Dates
|
||||
invoice_date = models.DateField(db_index=True)
|
||||
due_date = models.DateField()
|
||||
paid_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
# Line items
|
||||
line_items = models.JSONField(default=list, help_text="Invoice line items: [{description, amount, quantity}]")
|
||||
|
||||
# Payment integration
|
||||
stripe_invoice_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
payment_method = models.CharField(max_length=50, null=True, blank=True)
|
||||
|
||||
# Metadata
|
||||
notes = models.TextField(blank=True)
|
||||
metadata = models.JSONField(default=dict)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'billing'
|
||||
db_table = 'igny8_invoices'
|
||||
ordering = ['-invoice_date', '-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['account', 'status']),
|
||||
models.Index(fields=['account', 'invoice_date']),
|
||||
models.Index(fields=['invoice_number']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"Invoice {self.invoice_number} - {self.account.name if self.account else 'No Account'}"
|
||||
|
||||
|
||||
class Payment(AccountBaseModel):
|
||||
"""
|
||||
Payment record for invoices
|
||||
Supports: Stripe, PayPal, Manual (Bank Transfer, Local Wallet)
|
||||
"""
|
||||
STATUS_CHOICES = [
|
||||
('pending', 'Pending'),
|
||||
('processing', 'Processing'),
|
||||
('succeeded', 'Succeeded'),
|
||||
('failed', 'Failed'),
|
||||
('refunded', 'Refunded'),
|
||||
('cancelled', 'Cancelled'),
|
||||
]
|
||||
|
||||
PAYMENT_METHOD_CHOICES = [
|
||||
('stripe', 'Stripe (Credit/Debit Card)'),
|
||||
('paypal', 'PayPal'),
|
||||
('bank_transfer', 'Bank Transfer (Manual)'),
|
||||
('local_wallet', 'Local Wallet (Manual)'),
|
||||
('manual', 'Manual Payment'),
|
||||
]
|
||||
|
||||
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='payments')
|
||||
|
||||
# Amount
|
||||
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
currency = models.CharField(max_length=3, default='USD')
|
||||
|
||||
# Status
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending', db_index=True)
|
||||
|
||||
# Payment method
|
||||
payment_method = models.CharField(max_length=50, choices=PAYMENT_METHOD_CHOICES, db_index=True)
|
||||
|
||||
# Stripe integration
|
||||
stripe_payment_intent_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
stripe_charge_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
# PayPal integration
|
||||
paypal_order_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
paypal_capture_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
# Manual payment details
|
||||
manual_reference = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
help_text="Bank transfer reference, wallet transaction ID, etc."
|
||||
)
|
||||
manual_notes = models.TextField(blank=True, help_text="Admin notes for manual payments")
|
||||
approved_by = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='approved_payments'
|
||||
)
|
||||
approved_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
# Timestamps
|
||||
processed_at = models.DateTimeField(null=True, blank=True)
|
||||
failed_at = models.DateTimeField(null=True, blank=True)
|
||||
refunded_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
# Error tracking
|
||||
failure_reason = models.TextField(blank=True)
|
||||
|
||||
# Metadata
|
||||
metadata = models.JSONField(default=dict)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'billing'
|
||||
db_table = 'igny8_payments'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['account', 'status']),
|
||||
models.Index(fields=['account', 'payment_method']),
|
||||
models.Index(fields=['invoice', 'status']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"Payment {self.id} - {self.get_payment_method_display()} - {self.amount} {self.currency}"
|
||||
|
||||
|
||||
class CreditPackage(models.Model):
|
||||
"""
|
||||
One-time credit purchase packages
|
||||
Defines available credit bundles for purchase
|
||||
"""
|
||||
name = models.CharField(max_length=100)
|
||||
slug = models.SlugField(unique=True, db_index=True)
|
||||
|
||||
# Credits
|
||||
credits = models.IntegerField(validators=[MinValueValidator(1)])
|
||||
|
||||
# Pricing
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
discount_percentage = models.IntegerField(default=0, help_text="Discount percentage (0-100)")
|
||||
|
||||
# Stripe
|
||||
stripe_product_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
stripe_price_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
# PayPal
|
||||
paypal_plan_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
# Status
|
||||
is_active = models.BooleanField(default=True, db_index=True)
|
||||
is_featured = models.BooleanField(default=False, help_text="Show as featured package")
|
||||
|
||||
# Display
|
||||
description = models.TextField(blank=True)
|
||||
features = models.JSONField(default=list, help_text="Bonus features or highlights")
|
||||
|
||||
# Sort order
|
||||
sort_order = models.IntegerField(default=0, help_text="Display order (lower = first)")
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'billing'
|
||||
db_table = 'igny8_credit_packages'
|
||||
ordering = ['sort_order', 'price']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} - {self.credits} credits - ${self.price}"
|
||||
|
||||
|
||||
class PaymentMethodConfig(models.Model):
|
||||
"""
|
||||
Configure payment methods availability per country
|
||||
Allows enabling/disabling manual payments by region
|
||||
"""
|
||||
PAYMENT_METHOD_CHOICES = [
|
||||
('stripe', 'Stripe'),
|
||||
('paypal', 'PayPal'),
|
||||
('bank_transfer', 'Bank Transfer'),
|
||||
('local_wallet', 'Local Wallet'),
|
||||
]
|
||||
|
||||
country_code = models.CharField(
|
||||
max_length=2,
|
||||
db_index=True,
|
||||
help_text="ISO 2-letter country code (e.g., US, GB, IN)"
|
||||
)
|
||||
payment_method = models.CharField(max_length=50, choices=PAYMENT_METHOD_CHOICES)
|
||||
is_enabled = models.BooleanField(default=True)
|
||||
|
||||
# Display info
|
||||
display_name = models.CharField(max_length=100, blank=True)
|
||||
instructions = models.TextField(blank=True, help_text="Payment instructions for users")
|
||||
|
||||
# Manual payment details (for bank_transfer/local_wallet)
|
||||
bank_name = models.CharField(max_length=255, blank=True)
|
||||
account_number = models.CharField(max_length=255, blank=True)
|
||||
routing_number = models.CharField(max_length=255, blank=True)
|
||||
swift_code = models.CharField(max_length=255, blank=True)
|
||||
|
||||
# Additional fields for local wallets
|
||||
wallet_type = models.CharField(max_length=100, blank=True, help_text="E.g., PayTM, PhonePe, etc.")
|
||||
wallet_id = models.CharField(max_length=255, blank=True)
|
||||
|
||||
# Order/priority
|
||||
sort_order = models.IntegerField(default=0)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'billing'
|
||||
db_table = 'igny8_payment_method_config'
|
||||
unique_together = [['country_code', 'payment_method']]
|
||||
ordering = ['country_code', 'sort_order']
|
||||
verbose_name = 'Payment Method Configuration'
|
||||
verbose_name_plural = 'Payment Method Configurations'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.country_code} - {self.get_payment_method_display()}"
|
||||
|
||||
Reference in New Issue
Block a user