payemnt billing and credits refactoring

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-20 07:39:51 +00:00
parent a97c72640a
commit bc50b022f1
34 changed files with 3028 additions and 307 deletions

View File

@@ -0,0 +1,105 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from igny8_core.business.billing.models import Invoice, Payment, CreditTransaction, CreditPackage
from igny8_core.auth.models import Account
class Command(BaseCommand):
help = "Audit invoice/payment/credits for a purchase"
def add_arguments(self, parser):
parser.add_argument("--invoice-number", dest="invoice_number", help="Invoice number (e.g., INV-26010008)")
parser.add_argument("--invoice-id", dest="invoice_id", type=int, help="Invoice ID")
parser.add_argument("--payment-id", dest="payment_id", type=int, help="Payment ID")
parser.add_argument("--account-id", dest="account_id", type=int, help="Account ID (optional)")
def handle(self, *args, **options):
invoice_number = options.get("invoice_number")
invoice_id = options.get("invoice_id")
payment_id = options.get("payment_id")
account_id = options.get("account_id")
if not any([invoice_number, invoice_id, payment_id, account_id]):
self.stderr.write("Provide at least one filter: --invoice-number, --invoice-id, --payment-id, --account-id")
return
invoice_qs = Invoice.objects.all().select_related("account", "subscription", "subscription__plan")
payment_qs = Payment.objects.all().select_related("account", "invoice")
invoice = None
if invoice_number:
invoice = invoice_qs.filter(invoice_number=invoice_number).first()
elif invoice_id:
invoice = invoice_qs.filter(id=invoice_id).first()
elif payment_id:
payment = payment_qs.filter(id=payment_id).first()
invoice = payment.invoice if payment else None
elif account_id:
invoice = invoice_qs.filter(account_id=account_id).order_by("-created_at").first()
if not invoice:
self.stderr.write("No invoice found for the provided filter.")
return
account = invoice.account
invoice_type = invoice.invoice_type
credit_package_id = (invoice.metadata or {}).get("credit_package_id")
credit_package = None
if credit_package_id:
credit_package = CreditPackage.objects.filter(id=credit_package_id).first()
self.stdout.write("=== INVOICE ===")
self.stdout.write(f"Invoice: {invoice.invoice_number} (ID={invoice.id})")
self.stdout.write(f"Type: {invoice_type}")
self.stdout.write(f"Status: {invoice.status}")
self.stdout.write(f"Total: {invoice.total} {invoice.currency}")
self.stdout.write(f"Paid at: {invoice.paid_at}")
self.stdout.write(f"Expires at: {invoice.expires_at}")
self.stdout.write(f"Void reason: {invoice.void_reason}")
self.stdout.write(f"Account: {account.id} - {account.name}")
self.stdout.write(f"Account credits: {account.credits}")
if invoice.subscription:
plan = invoice.subscription.plan
self.stdout.write(f"Subscription: {invoice.subscription.id} (status={invoice.subscription.status})")
self.stdout.write(f"Plan: {plan.id if plan else None} - {plan.name if plan else None}")
if credit_package:
self.stdout.write(f"Credit Package: {credit_package.id} - {credit_package.name} ({credit_package.credits} credits)")
payments = payment_qs.filter(invoice_id=invoice.id).order_by("created_at")
self.stdout.write("\n=== PAYMENTS ===")
if not payments.exists():
self.stdout.write("No payments found for this invoice.")
else:
for pay in payments:
self.stdout.write(
f"Payment {pay.id}: status={pay.status}, method={pay.payment_method}, amount={pay.amount} {pay.currency}, processed_at={pay.processed_at}"
)
credit_transactions = CreditTransaction.objects.filter(account=account).order_by("-created_at")[:50]
related_transactions = CreditTransaction.objects.filter(
account=account
).filter(
metadata__invoice_id=invoice.id
).order_by("created_at")
self.stdout.write("\n=== CREDIT TRANSACTIONS (RELATED) ===")
if not related_transactions.exists():
self.stdout.write("No credit transactions linked to this invoice.")
else:
for tx in related_transactions:
self.stdout.write(
f"{tx.created_at}: {tx.transaction_type} amount={tx.amount} balance_after={tx.balance_after} desc={tx.description}"
)
if not related_transactions.exists() and invoice_type == "credit_package" and invoice.status == "paid":
self.stdout.write("\n!!! WARNING: Paid credit invoice with no linked credit transaction.")
self.stdout.write("This indicates credits were not applied.")
self.stdout.write("\n=== RECENT CREDIT TRANSACTIONS (LAST 50) ===")
for tx in credit_transactions:
self.stdout.write(
f"{tx.created_at}: {tx.transaction_type} amount={tx.amount} balance_after={tx.balance_after} desc={tx.description}"
)
self.stdout.write("\nAudit completed at: " + timezone.now().isoformat())