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

485 lines
16 KiB
Markdown

# 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](#1-critical-issues)
2. [Account Status & Activation Flow](#2-account-status--activation-flow)
3. [Credit System Analysis](#3-credit-system-analysis)
4. [Invoice Management](#4-invoice-management)
5. [Payment Method Flows](#5-payment-method-flows)
6. [Subscription Renewal Flow](#6-subscription-renewal-flow)
7. [Credit Package Purchases](#7-credit-package-purchases)
8. [Data Model Issues](#8-data-model-issues)
9. [Missing Features](#9-missing-features)
10. [Recommended Fixes](#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:
```python
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
### Recommended Invoice Statuses
| 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
---
## 10. Recommended Fixes
### 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