Files
igny8/docs/audits/BILLING-SYSTEM-AUDIT-20260120.md
IGNY8 VPS (Salman) a97c72640a credis pacakges ifxes
2026-01-20 04:32:12 +00:00

16 KiB

IGNY8 Billing System Comprehensive Audit

Date: January 20, 2026
Status: CRITICAL - Multiple issues identified
Reviewer: System Audit


Executive Summary

The billing system has fundamental architectural flaws that cause incorrect behavior across multiple payment methods, credit management, and account activation flows. This audit identifies all issues and provides a detailed analysis of the current implementation.


Table of Contents

  1. Critical Issues
  2. Account Status & Activation Flow
  3. Credit System Analysis
  4. Invoice Management
  5. Payment Method Flows
  6. Subscription Renewal Flow
  7. Credit Package Purchases
  8. Data Model Issues
  9. Missing Features
  10. Recommended Fixes

1. Critical Issues

Issue #1: Credit Package Approval Adds WRONG Credits (CRITICAL)

Location: AdminBillingViewSet.approve_payment() and BillingViewSet.approve_payment()

Current Behavior:

  • When admin approves a credit package payment, the code adds subscription.plan.included_credits (e.g., 200 credits from the plan)
  • It should add the credit_package.credits from the purchased package (e.g., 500 credits)

Evidence:

User bought 500-credit package → Admin approved → System added 200 credits (plan credits)

Root Cause: Both approval methods look for subscription/plan credits instead of checking if the invoice is for a credit package purchase via invoice.metadata.credit_package_id.


Issue #2: Invoice Types Not Differentiated (CRITICAL)

Problem: No clear distinction between:

  • Subscription invoices (for plan activation/renewal)
  • Credit package invoices (for additional credit purchases)

Impact:

  • Frontend checks hasPendingInvoice which includes BOTH types
  • Creating a credit package invoice should NOT affect account status
  • Creating a credit package invoice should NOT block other purchases

Issue #3: Credits Have No Validity/Expiration (CRITICAL)

Current State: Credits are a simple integer counter on Account model:

credits = models.IntegerField(default=0)

Missing:

  • No expiration date for credits
  • No validity period tracking (1 month as business rule)
  • No automatic reset mechanism
  • Purchased credits vs subscription credits not tracked separately

Impact: Users can accumulate credits indefinitely, no monthly reset logic.


Issue #4: Subscription Renewal Does NOT Reset Credits Correctly (CRITICAL)

Current Behavior:

  • Stripe webhook _handle_invoice_paid() calls CreditService.reset_credits_for_renewal()
  • Manual payment approval uses CreditService.add_credits() instead of reset ✗

Problem with add_credits:

  • If user had 50 credits remaining
  • Plan gives 200 credits monthly
  • add_credits(200) → User now has 250 credits
  • Should be: Reset to 200 credits

Issue #5: No Invoice Expiration/Cancellation Mechanism

Current State:

  • Pending invoices remain pending indefinitely
  • No automatic void/cancellation after timeout (e.g., 24-48 hours)
  • User cannot cancel/void their own pending credit package invoices
  • No cleanup task for stale invoices

2. Account Status & Activation Flow

Status Values

Status Description When Set
active Account fully operational After successful payment or trial activation
trial Free trial period On signup with free plan
pending_payment Awaiting first payment After signup with paid plan, before payment
suspended Manually suspended Admin action
cancelled Subscription cancelled After cancellation

Current Flow Problems

New User with Paid Plan (Bank Transfer):

  1. User signs up selecting paid plan with bank transfer → status = pending_payment
  2. Invoice created → Invoice status = pending
  3. User submits bank transfer confirmation → Payment status = pending_approval
  4. Admin approves payment → Account status = active
  5. BUG: Credits added = plan credits (not credit package credits if that's what was purchased)

Existing Active User Buying Credits (Bank Transfer):

  1. User has status = active
  2. User creates credit package invoice → Invoice created with status = pending
  3. BUG: Frontend incorrectly interprets this as "account has pending invoice"
  4. BUG: Some UI elements get disabled unnecessarily
  5. Admin approves → BUG: Wrong credits added (plan credits instead of package credits)

3. Credit System Analysis

Current Data Model

Account.credits (integer)

  • Single counter for all credits
  • No distinction between:
    • Plan/subscription credits
    • Purchased credit packages
    • Bonus/promotional credits
  • No expiration tracking

CreditTransaction (history log)

  • Records additions and deductions
  • Transaction types: purchase, subscription, refund, deduction, adjustment
  • Has balance_after field for audit trail

CreditUsageLog (detailed usage)

  • Per-operation usage tracking
  • Links to site, operation type, model used
  • Used for analytics and billing reports

Credit Flow Issues

Scenario Expected Behavior Current Behavior Status
New subscription Reset credits to plan amount Add credits to existing BUG
Subscription renewal (Stripe) Reset credits to plan amount Reset credits ✓ OK
Subscription renewal (Manual) Reset credits to plan amount Add credits BUG
Credit package purchase Add package credits Add plan credits BUG
Monthly reset Reset subscription credits, keep purchased No reset logic MISSING

Credit Validity Rules (Business Requirement - NOT IMPLEMENTED)

Per business rules, credits should have a 1-month validity period:

  • Subscription credits reset monthly on billing cycle
  • Purchased credits have 1-month validity from purchase date
  • Expired credits should be voided

Current Implementation: None - credits never expire or reset.


4. Invoice Management

Invoice Types (Implicit, Not Enforced)

Type Identifier Purpose
Subscription invoice.subscription IS NOT NULL Plan activation/renewal
Credit Package invoice.metadata.credit_package_id EXISTS Additional credit purchase
Custom Neither Admin-created invoices

Invoice Status Flow

draft → pending → paid/void/uncollectible

Issues

  1. No Expiration: Invoices stay pending forever
  2. No User Cancellation: Users cannot cancel their own credit package invoices
  3. No Automatic Cleanup: No task to void stale invoices
  4. No Type Enforcement: Invoice type determined by checking multiple fields
Status Description Auto-Transition
draft Created, not sent → pending when finalized
pending Awaiting payment → void after 48h (credit packages)
paid Payment received Terminal
void Cancelled/expired Terminal
uncollectible Cannot be collected Terminal

5. Payment Method Flows

Supported Payment Methods

Method Countries Auto-Approval Account Activation
Stripe (Card) All except PK Yes Immediate
PayPal All except PK Yes Immediate
Bank Transfer PK only No (Manual) After admin approval

Stripe Flow (Working ✓)

  1. User initiates checkout → Stripe session created
  2. User completes payment on Stripe
  3. Webhook checkout.session.completed → Account activated, credits added
  4. Webhook invoice.paid (renewal) → Credits reset

PayPal Flow (Working ✓)

  1. User initiates checkout → PayPal order created
  2. User approves on PayPal
  3. Payment captured → Account activated, credits added

Bank Transfer Flow (BUGGY )

  1. User initiates → Invoice created, account status may change incorrectly
  2. User submits confirmation → Payment created with pending_approval
  3. Admin approves → WRONG CREDITS ADDED
  4. User rejection flow exists but no automatic expiration

6. Subscription Renewal Flow

Automatic (Stripe/PayPal)

  • Stripe handles automatic billing
  • Webhook invoice.paid triggers renewal
  • reset_credits_for_renewal() correctly resets credits ✓

Manual (Bank Transfer)

  1. Task process_subscription_renewals creates renewal invoice
  2. Subscription status → pending_renewal
  3. Grace period (7 days) starts
  4. User pays manually
  5. BUG: Admin approval uses add_credits() instead of reset_credits_for_renewal()
  6. Task check_expired_renewals expires after grace period ✓

7. Credit Package Purchases

Current Flow

  1. User selects package
  2. For Stripe/PayPal: Redirect to checkout
  3. For Bank Transfer: Create invoice → Show payment form

Issues

Step Issue Severity
Invoice Creation Uses InvoiceService.create_credit_package_invoice() OK
Invoice stores credit_package_id in metadata OK OK
Approval reads subscription.plan.included_credits WRONG CRITICAL
Should read invoice.metadata.credit_package_id → get package → add package.credits Missing CRITICAL

Stripe Credit Purchase (Partially Working)

  • Webhook handler _handle_credit_purchase() correctly reads metadata
  • Uses CreditService.add_credits() with correct amount ✓

Manual Credit Purchase (BROKEN)

  • approve_payment() does NOT check if invoice is credit package
  • Adds plan credits instead of package credits
  • Completely wrong behavior

8. Data Model Issues

Missing Fields

Invoice Model:

  • invoice_type enum: subscription, credit_package, custom
  • expires_at datetime for auto-void
  • credit_package_id FK (instead of in metadata)

Account Model:

  • subscription_credits (monthly reset)
  • purchased_credits (expire after 1 month)
  • credits_valid_until datetime

CreditTransaction:

  • expires_at datetime
  • source_type: subscription, purchase, bonus

Proposed Schema Changes

Account:
  + subscription_credits: int (resets monthly)
  + purchased_credits: int (separate pool)
  + credits_last_reset: datetime

Invoice:
  + invoice_type: enum('subscription', 'credit_package', 'custom')
  + expires_at: datetime (nullable)

CreditPurchase: (NEW MODEL)
  - account: FK
  - credit_package: FK
  - credits: int
  - purchase_date: datetime
  - expires_at: datetime
  - credits_remaining: int
  - status: enum('active', 'expired', 'used')

9. Missing Features

Invoice Management

  • User can cancel own pending credit package invoices
  • Auto-void invoices after 24-48 hours
  • Invoice expiration emails
  • Invoice reminder emails (before expiration)

Credit Management

  • Credit expiration tracking
  • Monthly credit reset task
  • Separate subscription vs purchased credits
  • Credit validity period configuration
  • Low credit warnings before operations

Payment Management

  • Payment retry for failed payments
  • Automatic payment reminder emails
  • Payment receipt PDFs
  • Refund processing

Admin Features

  • Bulk invoice void
  • Credit adjustment with reason
  • Payment history export
  • Revenue reports

Priority 1: Critical (Immediate Fix Required)

Fix 1.1: Credit Package Approval Logic

File: billing_views.py - approve_payment() method

Change: Before adding credits, check if invoice has credit_package_id in metadata:

IF invoice.metadata.credit_package_id EXISTS:
    package = CreditPackage.get(invoice.metadata.credit_package_id)
    credits_to_add = package.credits
ELSE:
    credits_to_add = subscription.plan.included_credits

Fix 1.2: Manual Renewal Should Reset Credits

File: billing_views.py - approve_payment() method

Change: For subscription renewals, use reset_credits_for_renewal() instead of add_credits().

Fix 1.3: Separate Invoice Type Checking in Frontend

File: PlansAndBillingPage.tsx

Already Fixed: hasPendingSubscriptionInvoice and hasPendingCreditInvoice now separate.

Priority 2: High (Fix Within 1 Week)

Fix 2.1: Add Invoice Expiration

  • Add expires_at field to Invoice model
  • Set expiration on creation (48 hours for credit packages)
  • Create Celery task to void expired invoices
  • Send reminder email 24 hours before expiration

Fix 2.2: User Invoice Cancellation

  • Add API endpoint for user to cancel own pending credit package invoices
  • Add "Cancel" button in frontend invoice list
  • Set invoice status to void on cancellation

Priority 3: Medium (Fix Within 1 Month)

Fix 3.1: Credit Expiration System

  • Add credit validity tracking
  • Create monthly reset task for subscription credits
  • Track purchased credits separately with expiration

Fix 3.2: Invoice Type Field

  • Add invoice_type enum field
  • Migrate existing invoices based on metadata
  • Update all queries to use type field

Priority 4: Low (Future Enhancement)

  • Payment retry automation
  • Revenue reporting
  • Credit pool separation
  • Advanced analytics

Appendix A: Current Code Locations

Component File Path
Account Model auth/models.py
Plan Model auth/models.py
Subscription Model auth/models.py
Invoice Model business/billing/models.py
Payment Model business/billing/models.py
CreditPackage Model business/billing/models.py
CreditTransaction Model business/billing/models.py
CreditService business/billing/services/credit_service.py
InvoiceService business/billing/services/invoice_service.py
PaymentService business/billing/services/payment_service.py
Admin Approval (v1) business/billing/billing_views.py
Admin Approval (v2) modules/billing/views.py
Stripe Webhooks business/billing/views/stripe_views.py
PayPal Webhooks business/billing/views/paypal_views.py
Renewal Tasks business/billing/tasks/subscription_renewal.py

Appendix B: Test Scenarios Required

Scenario Current Result Expected Result
PK user buys 500 credit package via bank transfer Gets 200 credits (plan) Gets 500 credits
Active user creates credit invoice Account blocked Account stays active
Subscription renewal (manual) Credits added Credits reset
Invoice unpaid after 48 hours Stays pending Auto-void
User wants to cancel own invoice No option Can cancel
Credits unused for 1 month Stay forever Should expire

Appendix C: Affected User Flows

Flow 1: New PK User Subscription

Signup → Select Plan → Bank Transfer → Submit Confirmation → Wait for Approval → Account Active

Issues: None if subscription only

Flow 2: Active PK User Buys Credits

Go to Credits → Select Package → Bank Transfer → Invoice Created → Submit Confirmation → Wait → WRONG CREDITS ADDED

Issues:

  • Invoice creation may show incorrect UI states
  • Wrong credits added on approval

Flow 3: Active Non-PK User Buys Credits

Go to Credits → Select Package → Stripe Checkout → Payment → Credits Added

Issues: None - Stripe handler works correctly

Flow 4: Subscription Renewal (Manual)

Renewal Date → Invoice Created → User Pays → Admin Approves → CREDITS ADDED (not reset)

Issues: Should reset credits, not add


End of Audit Report

Next Steps:

  1. Review this audit with team
  2. Prioritize fixes based on business impact
  3. Implement Priority 1 fixes immediately
  4. Create automated tests for all scenarios
  5. Schedule remaining fixes