Version 1.6.0

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-08 00:36:32 +00:00
parent 7ad1f6bdff
commit 3028db5197
7 changed files with 855 additions and 4450 deletions

View File

@@ -1,7 +1,7 @@
# IGNY8 Change Log # IGNY8 Change Log
**Current Version:** 1.4.0 **Current Version:** 1.6.0
**Last Updated:** January 6, 2026 **Last Updated:** January 8, 2026
--- ---
@@ -9,6 +9,7 @@
| Version | Date | Summary | | Version | Date | Summary |
|---------|------|---------| |---------|------|---------|
| 1.6.0 | Jan 8, 2026 | **Major** - Payment System Refactor Complete: Stripe, PayPal, Bank Transfer flows finalized; Simplified signup (no payment redirect); Country-based payment rules; Webhook security; PDF invoices |
| 1.5.0 | *Planned* | Image Generation System Overhaul, Quality Tier Refinements, Credit Service Enhancements | | 1.5.0 | *Planned* | Image Generation System Overhaul, Quality Tier Refinements, Credit Service Enhancements |
| 1.4.0 | Jan 5, 2026 | **Major** - AI Model Architecture Overhaul, IntegrationProvider Model, AIModelConfig with Credit System, SystemAISettings, Django Admin Reorganization | | 1.4.0 | Jan 5, 2026 | **Major** - AI Model Architecture Overhaul, IntegrationProvider Model, AIModelConfig with Credit System, SystemAISettings, Django Admin Reorganization |
| 1.3.2 | Jan 3, 2026 | **Major** - Publishing Scheduler, Onboarding Wizard, Content Calendar, Design System Consolidation, Site Dashboard redesign | | 1.3.2 | Jan 3, 2026 | **Major** - Publishing Scheduler, Onboarding Wizard, Content Calendar, Design System Consolidation, Site Dashboard redesign |
@@ -36,6 +37,173 @@
--- ---
## v1.6.0 - January 8, 2026
### Major Release: Payment System Refactor Complete
This release completes the comprehensive refactoring of the payment system, including all three payment gateways (Stripe, PayPal, Bank Transfer), simplified signup flow, and enhanced security.
---
### 💳 Payment Gateway Integration
**Stripe Integration:**
- Subscription checkout sessions
- Credit package checkout
- Billing portal for subscription management
- Webhook processing with idempotency
- Signature verification enforced
**PayPal Integration:**
- One-time orders for credit packages
- Subscription orders for plans
- Order capture flow
- Webhook signature verification enabled
- Amount validation on capture
**Bank Transfer (Pakistan):**
- Manual payment submission
- File upload for payment proof
- Admin approval workflow
- Email notifications on approval/rejection
---
### 🔄 Simplified Signup Flow
**Before:** Payment gateway redirect from signup page
**After:** Signup creates account only, payment on /account/plans
**Changes:**
- Removed Stripe/PayPal checkout creation from registration
- Account created with `status='pending_payment'` for paid plans
- User redirected to Plans & Billing page to complete payment
- Single `/signup` route (removed `/signup/pk` variant)
---
### 🌍 Country-Based Payment Rules
**Global Users (non-PK):**
- Stripe (Credit/Debit Card) ✅
- PayPal ✅
- Bank Transfer ❌
**Pakistan Users (PK):**
- Stripe (Credit/Debit Card) ✅
- PayPal ❌ (not available in Pakistan)
- Bank Transfer ✅
---
### 🎨 Frontend Components
**New Components:**
- `PendingPaymentView` - Full-page payment interface for new users
- `BankTransferForm` - Bank transfer submission with proof upload
- `PaymentGatewaySelector` - Gateway selection UI
**Updated Components:**
- `PlansAndBillingPage` - Conditional rendering based on user state
- `PendingPaymentBanner` - Alert for pending payments
- `PayInvoiceModal` - Invoice payment modal
---
### 🔒 Security Enhancements
- **Webhook Idempotency:** `WebhookEvent` model tracks processed events
- **PayPal Signature Verification:** Enabled and enforced
- **Stripe Signature Verification:** Already enforced
- **Amount Validation:** PayPal capture validates amount matches expected
- **Manual Reference Uniqueness:** Database constraint prevents duplicates
---
### 📄 PDF Invoice Generation
- Added `reportlab` for professional PDF generation
- Logo integration from frontend assets
- Proper formatting (no HTML tags in output)
- Clean layout with company info, line items, totals
- Payment information section for paid invoices
---
### 🗄️ Database Changes
**New Migration:** `0029_add_webhook_event_and_manual_reference_constraint`
**New Model:** `WebhookEvent`
```python
class WebhookEvent(models.Model):
provider = models.CharField(choices=['stripe', 'paypal'])
event_id = models.CharField(max_length=255)
event_type = models.CharField(max_length=100)
payload = models.JSONField()
status = models.CharField() # pending, processed, failed
processed_at = models.DateTimeField(null=True)
```
**Updated Model:** `Payment`
- Added unique constraint on `manual_reference`
---
### 📚 Documentation
**Consolidated:** Three payment documentation files merged into one:
- `PAYMENT-SYSTEM-ARCHITECTURE.md` → Removed
- `PAYMENT-SYSTEM-AUDIT-REPORT.md` → Removed
- `PAYMENT-SYSTEM-REFACTOR-PLAN.md` → Removed
**New:** `90-REFERENCE/PAYMENT-SYSTEM.md`
- Complete payment system documentation
- All flows, endpoints, models in one file
- Current state only (no planning artifacts)
---
### 📦 Backend Files Changed
| File | Changes |
|------|---------|
| `billing/views/stripe_views.py` | Idempotency, enhanced logging |
| `billing/views/paypal_views.py` | Signature verification, amount validation, idempotency |
| `billing/views/refund_views.py` | Fixed imports |
| `billing/services/stripe_service.py` | Full service implementation |
| `billing/services/paypal_service.py` | Full service implementation |
| `billing/services/pdf_service.py` | Professional PDF generation with logo |
| `billing/services/email_service.py` | Payment notification emails |
| `billing/models.py` | WebhookEvent model, payment method choices |
| `auth/serializers.py` | Removed checkout creation from registration |
| `auth/urls.py` | Added country list endpoint |
---
### 📱 Frontend Files Changed
| File | Changes |
|------|---------|
| `SignUpFormUnified.tsx` | Simplified, removed payment selection |
| `PlansAndBillingPage.tsx` | Conditional views, state handling |
| `PendingPaymentView.tsx` | New component for new user payments |
| `BankTransferForm.tsx` | New component for bank transfers |
| `PaymentGatewaySelector.tsx` | New component for gateway selection |
| `billing.api.ts` | Country-based gateway availability |
| `authStore.ts` | Removed checkout URL handling |
---
### 🗑️ Removed
- `/signup/pk` route (consolidated into `/signup`)
- `SignUpPK.tsx` page component
- Payment gateway redirect from signup
- Three separate payment documentation files
---
## v1.5.0 - Planned ## v1.5.0 - Planned
### Upcoming Release: Image Generation System Overhaul ### Upcoming Release: Image Generation System Overhaul

View File

@@ -1,10 +1,12 @@
# Billing Module # Billing Module
**Last Verified:** January 6, 2026 **Last Verified:** January 8, 2026
**Status:** ✅ Active (Simplified January 2026) **Status:** ✅ Active (Payment System Refactored January 2026)
**Backend Path:** `backend/igny8_core/modules/billing/` + `backend/igny8_core/business/billing/` **Backend Path:** `backend/igny8_core/modules/billing/` + `backend/igny8_core/business/billing/`
**Frontend Path:** `frontend/src/pages/Billing/` + `frontend/src/pages/Account/` **Frontend Path:** `frontend/src/pages/Billing/` + `frontend/src/pages/Account/`
> **Payment System Reference:** For comprehensive payment gateway documentation (Stripe, PayPal, Bank Transfer), see [PAYMENT-SYSTEM.md](../90-REFERENCE/PAYMENT-SYSTEM.md)
--- ---
## Quick Reference ## Quick Reference

File diff suppressed because it is too large Load Diff

View File

@@ -1,959 +0,0 @@
# Payment System Audit Report
> **Audit Date:** January 7, 2026
> **Status:** Complete (Updated with Frontend UX Audit)
> **Severity Levels:** CRITICAL | HIGH | MEDIUM | LOW
---
## Executive Summary
The IGNY8 payment system documentation is **accurate and matches the implementation**. However, the deep audit revealed **45+ issues** across security, reliability, UX, and functionality areas. The most critical concerns involve:
1. **PayPal webhook signature verification disabled** (security risk)
2. **Missing idempotency in payment processing** (double-charge risk)
3. **No admin dashboard for manual payment approval** (operational gap)
4. **Plan shows "Active" even with unpaid invoice** (misleading UX)
5. **Payment options not properly restricted by state** (UX confusion)
6. **Hardcoded currency exchange rates** (financial accuracy)
7. **Refund functions reference non-existent modules** (broken feature)
---
## Documentation Verification
### All Documented Files: VERIFIED
| Category | File | Status |
|----------|------|--------|
| Frontend Entry Points | SignUpFormUnified.tsx | EXISTS |
| | PlansAndBillingPage.tsx | EXISTS |
| | PayInvoiceModal.tsx | EXISTS |
| | PendingPaymentBanner.tsx | EXISTS |
| Frontend Services | billing.api.ts | EXISTS |
| | authStore.ts | EXISTS |
| Backend Views | stripe_views.py | EXISTS |
| | paypal_views.py | EXISTS |
| Backend Services | stripe_service.py | EXISTS |
| | paypal_service.py | EXISTS |
| | payment_service.py | EXISTS |
| | invoice_service.py | EXISTS |
| Models | billing/models.py | EXISTS |
| | auth/models.py | EXISTS |
### Country-Based Payment Logic: CORRECT
- **Pakistan (PK):** Stripe + Bank Transfer (NO PayPal)
- **Global:** Stripe + PayPal
Logic correctly implemented in:
- `SignUpFormUnified.tsx:160-186`
- `PayInvoiceModal.tsx:69-97`
- `payment_service.py:260-263`
---
## PlansAndBillingPage UX Audit (NEW)
### Overview
The `/account/plans` page (`PlansAndBillingPage.tsx`) is the central hub for subscription management. This audit identifies **critical UX issues** related to state handling, invoice lifecycle, and payment option restrictions.
---
### CRITICAL UX Issue #1: Plan Shows "Active" Even With Unpaid Invoice
**Location:** `PlansAndBillingPage.tsx:459-461`
**Current Code:**
```tsx
<Badge variant="solid" tone={hasActivePlan ? 'success' : 'warning'}>
{hasActivePlan ? 'Active' : 'Inactive'}
</Badge>
```
**Problem:**
- `hasActivePlan` is `true` if user has ANY plan assigned (Line 384)
- User who signed up for paid plan but never completed payment sees **"Active"**
- This is misleading - their account is actually `pending_payment`
**Correct Logic Should Check:**
1. `account.status === 'active'` (not just plan existence)
2. No pending invoices
3. Subscription status is `active` (not `pending_payment`)
**Fix Required:**
```tsx
const accountStatus = user?.account?.status;
const subscriptionStatus = currentSubscription?.status;
const isFullyActive = accountStatus === 'active' &&
subscriptionStatus === 'active' &&
!hasPendingInvoice;
<Badge variant="solid" tone={isFullyActive ? 'success' : accountStatus === 'pending_payment' ? 'warning' : 'error'}>
{isFullyActive ? 'Active' : accountStatus === 'pending_payment' ? 'Pending Payment' : 'Inactive'}
</Badge>
```
---
### CRITICAL UX Issue #2: Subscription States Not Properly Reflected
**Location:** `PlansAndBillingPage.tsx:379-386`
**Current Code:**
```tsx
const currentSubscription = subscriptions.find((sub) => sub.status === 'active') || subscriptions[0];
// ...
const hasActivePlan = Boolean(effectivePlanId);
```
**Problem:**
The page doesn't distinguish between subscription statuses:
- `active` - Paid and working
- `pending_payment` - Waiting for first payment
- `past_due` - Renewal payment failed
- `canceled` - User cancelled
**Missing Status Handling:**
| Subscription Status | What User Sees | What They SHOULD See |
|---------------------|----------------|----------------------|
| `pending_payment` | "Active" badge | "Payment Required" with prominent CTA |
| `past_due` | No indication | "Payment Overdue" warning |
| `canceled` | May still show "Active" | "Cancels on [date]" |
| `trialing` | "Active" | "Trial (X days left)" |
**Fix Required:** Add comprehensive status display:
```tsx
const getSubscriptionDisplay = () => {
if (!currentSubscription) return { label: 'No Plan', tone: 'error' };
switch (currentSubscription.status) {
case 'active':
return hasPendingInvoice
? { label: 'Payment Due', tone: 'warning' }
: { label: 'Active', tone: 'success' };
case 'pending_payment':
return { label: 'Awaiting Payment', tone: 'warning' };
case 'past_due':
return { label: 'Payment Overdue', tone: 'error' };
case 'canceled':
return { label: `Cancels ${formatDate(currentSubscription.cancel_at)}`, tone: 'warning' };
case 'trialing':
return { label: `Trial`, tone: 'info' };
default:
return { label: currentSubscription.status, tone: 'neutral' };
}
};
```
---
### HIGH UX Issue #3: Upgrade Button Available When Payment Pending
**Location:** `PlansAndBillingPage.tsx:478-486`
**Current Code:**
```tsx
<Button
variant="primary"
tone="brand"
onClick={() => setShowUpgradeModal(true)}
startIcon={<ArrowUpIcon className="w-4 h-4" />}
>
Upgrade
</Button>
```
**Problem:** User with pending invoice can click "Upgrade" and attempt to subscribe to another plan, creating confusion and potentially duplicate subscriptions.
**Fix Required:**
```tsx
<Button
variant="primary"
tone="brand"
onClick={() => setShowUpgradeModal(true)}
disabled={hasPendingInvoice || accountStatus === 'pending_payment'}
startIcon={<ArrowUpIcon className="w-4 h-4" />}
>
{hasPendingInvoice ? 'Pay Invoice First' : 'Upgrade'}
</Button>
```
---
### HIGH UX Issue #4: Cancel Subscription Available When Account Already Pending
**Location:** `PlansAndBillingPage.tsx:609-616`
**Current Code:**
```tsx
{hasActivePlan && (
<button onClick={() => setShowCancelConfirm(true)} ...>
Cancel Subscription
</button>
)}
```
**Problem:** User with `pending_payment` status can "cancel" a subscription they never paid for. This is confusing.
**Fix Required:**
```tsx
{hasActivePlan && accountStatus === 'active' && !hasPendingInvoice && (
<button onClick={() => setShowCancelConfirm(true)} ...>
Cancel Subscription
</button>
)}
```
---
### HIGH UX Issue #5: "Manage Billing" Button Shown to Non-Stripe Users
**Location:** `PlansAndBillingPage.tsx:468-477`
**Current Code:**
```tsx
{availableGateways.stripe && hasActivePlan && (
<Button ... onClick={handleManageSubscription}>
Manage Billing
</Button>
)}
```
**Problem:**
- Shows "Manage Billing" if Stripe is available, even if user pays via Bank Transfer
- Bank Transfer users clicking this get error "No Stripe customer ID"
**Fix Required:**
```tsx
{availableGateways.stripe &&
hasActivePlan &&
user?.account?.stripe_customer_id &&
selectedPaymentMethod === 'stripe' && (
<Button ... onClick={handleManageSubscription}>
Manage Billing
</Button>
)}
```
---
### HIGH UX Issue #6: Credits Section Doesn't Show Pending Credit Purchases
**Location:** `PlansAndBillingPage.tsx:689-713`
**Problem:** If user purchased credits via bank transfer and it's `pending_approval`, they don't see this anywhere clearly. They might try to purchase again.
**Fix Required:** Add pending credits indicator:
```tsx
{pendingCreditPayments.length > 0 && (
<div className="mb-4 p-3 bg-info-50 border border-info-200 rounded-lg">
<p className="text-sm text-info-700">
You have {pendingCreditPayments.length} credit purchase(s) pending approval
</p>
</div>
)}
```
---
### MEDIUM UX Issue #7: Invoice Status Badge Colors Inconsistent
**Location:** `PlansAndBillingPage.tsx:817-819`
**Current Code:**
```tsx
<Badge variant="soft" tone={invoice.status === 'paid' ? 'success' : 'warning'}>
{invoice.status}
</Badge>
```
**Problem:** Only handles `paid` and everything else is `warning`. Missing:
- `draft` - Should be gray/neutral
- `void` - Should be gray
- `uncollectible` - Should be error/red
- `pending` - Warning (correct)
**Fix Required:**
```tsx
const getInvoiceStatusTone = (status: string) => {
switch (status) {
case 'paid': return 'success';
case 'pending': return 'warning';
case 'void':
case 'draft': return 'neutral';
case 'uncollectible': return 'error';
default: return 'neutral';
}
};
```
---
### MEDIUM UX Issue #8: No Clear Indication of Payment Method per Invoice
**Location:** `PlansAndBillingPage.tsx:809-849` (Invoice table)
**Problem:** Invoice table doesn't show which payment method was used/expected. User can't tell if they need to do bank transfer or card payment.
**Fix Required:** Add payment method column:
```tsx
<td className="px-6 py-3 text-center">
{invoice.payment_method === 'bank_transfer' ? (
<span className="flex items-center gap-1"><Building2Icon className="w-4 h-4" /> Bank</span>
) : invoice.payment_method === 'paypal' ? (
<span className="flex items-center gap-1"><WalletIcon className="w-4 h-4" /> PayPal</span>
) : (
<span className="flex items-center gap-1"><CreditCardIcon className="w-4 h-4" /> Card</span>
)}
</td>
```
---
### MEDIUM UX Issue #9: Renewal Date Shows Even When No Active Subscription
**Location:** `PlansAndBillingPage.tsx:514-522`
**Current Code:**
```tsx
<div className="text-2xl font-bold text-gray-900 dark:text-white">
{currentSubscription?.current_period_end
? new Date(currentSubscription.current_period_end).toLocaleDateString(...)
: '—'}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">Next billing</div>
```
**Problem:** Shows "—" and "Next billing" even when:
- Account is `pending_payment` (never billed yet)
- Subscription is `canceled` (won't renew)
**Fix Required:**
```tsx
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
{currentSubscription?.status === 'canceled' ? 'Ends on' :
currentSubscription?.status === 'pending_payment' ? 'Starts after payment' :
'Next billing'}
</div>
```
---
### MEDIUM UX Issue #10: Payment Gateway Selection Not Synced With Account
**Location:** `PlansAndBillingPage.tsx:655-686`
**Problem:** Payment method selector in "Buy Credits" section can show options the user hasn't verified. If user signed up with bank transfer, they shouldn't see PayPal as an option until they've added it.
**Current Logic:** Shows all `availableGateways` regardless of user's `AccountPaymentMethod` records.
**Fix Required:**
```tsx
// Only show gateways user has verified OR is willing to add
const userCanUseGateway = (gateway: PaymentGateway) => {
const userHasMethod = userPaymentMethods.some(m =>
(gateway === 'stripe' && m.type === 'stripe') ||
(gateway === 'paypal' && m.type === 'paypal') ||
(gateway === 'manual' && ['bank_transfer', 'local_wallet'].includes(m.type))
);
return availableGateways[gateway] && (userHasMethod || gateway === selectedGateway);
};
```
---
### LOW UX Issue #11: Annual Billing Toggle Does Nothing
**Location:** `PlansAndBillingPage.tsx:959-983`
**Current Code:**
```tsx
const displayPrice = selectedBillingCycle === 'annual' ? (annualPrice / 12).toFixed(0) : planPrice;
```
**Problem:**
- Shows "Save 20%" badge for annual
- Calculates display price
- But **no annual plans exist** in database
- Clicking "Choose Plan" subscribes to monthly regardless
**Fix Required:** Either:
1. Remove annual toggle until annual plans implemented
2. Implement annual plan variants in backend
3. Pass `billing_cycle` to `subscribeToPlan()` and handle in backend
---
### LOW UX Issue #12: PayInvoiceModal Hardcodes Bank Details
**Location:** `PayInvoiceModal.tsx:437-443`
**Current Code:**
```tsx
<p><span className="font-medium">Bank:</span> Standard Chartered Bank Pakistan</p>
<p><span className="font-medium">Account Title:</span> IGNY8 Technologies</p>
<p><span className="font-medium">Account #:</span> 01-2345678-01</p>
```
**Problem:** Bank details hardcoded in frontend. Should come from `PaymentMethodConfig` in backend.
**Fix Required:** Fetch bank details from `/v1/billing/payment-configs/payment-methods/?country_code=PK&payment_method=bank_transfer` and display dynamically.
---
### Account Lifecycle State Machine (Missing)
The page doesn't follow a clear state machine. Here's what it SHOULD be:
```
┌─────────────────────────────────────────────────────────────────────┐
│ ACCOUNT STATES │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ Payment ┌──────────┐ Payment ┌──────────┐ │
│ │ trial │ ────────────▶ │ pending_ │ ────────────▶ │ active │ │
│ │ │ Required │ payment │ Success │ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ │ │ Payment │ │
│ │ │ Failed │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ expired │ │suspended │ ◀─────────── │ past_due │ │
│ │ │ │ │ Auto │ │ │
│ └──────────┘ └──────────┘ Suspend └──────────┘ │
│ │ │ │
│ │ Admin │ │
│ │ Action │ │
│ ▼ │ │
│ ┌──────────┐ │ │
│ │cancelled │ ◀──────────────────┘ │
│ │ │ User Cancel │
│ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
**Each State Should Show:**
| State | Plan Badge | Actions Available | Warnings |
|-------|------------|-------------------|----------|
| `trial` | "Trial (X days)" | Upgrade, Buy Credits | "Trial ends [date]" |
| `pending_payment` | "Awaiting Payment" | Pay Invoice, Change Method | "Complete payment to activate" |
| `active` | "Active" | Upgrade, Buy Credits, Cancel, Manage | None |
| `past_due` | "Payment Overdue" | Update Payment, Pay Now | "Update payment to avoid suspension" |
| `suspended` | "Suspended" | Pay to Reactivate | "Account suspended - pay to restore" |
| `cancelled` | "Cancels [date]" | Resubscribe | "Access ends [date]" |
---
### Payment Options By State (Missing Restrictions)
**Current:** All payment options shown regardless of account state.
**Required Restrictions:**
| Account State | Can Upgrade? | Can Buy Credits? | Can Change Plan? | Can Cancel? |
|---------------|--------------|------------------|------------------|-------------|
| `trial` | Yes | Yes | N/A | No |
| `pending_payment` | No (Pay first) | No (Pay first) | No | No |
| `active` | Yes | Yes | Yes | Yes |
| `past_due` | No (Pay first) | No | No | Yes |
| `suspended` | No | No | No | No |
| `cancelled` | Yes (Resubscribe) | No | No | No |
---
### Summary of PlansAndBillingPage Fixes Needed
| # | Issue | Severity | Effort |
|---|-------|----------|--------|
| 1 | Plan shows "Active" with unpaid invoice | CRITICAL | 30 min |
| 2 | Subscription states not reflected | CRITICAL | 1 hr |
| 3 | Upgrade available when payment pending | HIGH | 15 min |
| 4 | Cancel available for unpaid subscriptions | HIGH | 15 min |
| 5 | Manage Billing shown to non-Stripe users | HIGH | 15 min |
| 6 | No pending credit purchase indicator | HIGH | 30 min |
| 7 | Invoice status colors inconsistent | MEDIUM | 15 min |
| 8 | No payment method shown per invoice | MEDIUM | 30 min |
| 9 | Renewal date context wrong | MEDIUM | 15 min |
| 10 | Gateway selection not synced | MEDIUM | 30 min |
| 11 | Annual billing does nothing | LOW | 2 hrs |
| 12 | Bank details hardcoded | LOW | 1 hr |
---
## Critical Issues (Immediate Action Required)
### 1. PayPal Webhook Signature Not Enforced
**Location:** `paypal_views.py:498-511`
**Problem:**
```python
if not is_valid:
logger.warning("PayPal webhook signature verification failed")
# Optionally reject invalid signatures
# return Response({'error': 'Invalid signature'}, status=400) # COMMENTED OUT!
```
**Risk:** Malicious actors can craft fake webhook events to:
- Approve payments that never happened
- Cancel legitimate subscriptions
- Add credits without payment
**Fix Required:**
```python
if not is_valid:
logger.error("PayPal webhook signature verification failed")
return Response({'error': 'Invalid signature'}, status=400) # UNCOMMENT
```
---
### 2. Stripe Webhook Not Idempotent (Double-Charge Risk)
**Location:** `stripe_views.py:380-391` (_handle_checkout_completed)
**Problem:** Webhook can be called multiple times for same event. No check prevents duplicate invoice/payment creation.
**Scenario:**
1. Stripe sends webhook
2. Invoice and payment created
3. Stripe retries webhook (network timeout)
4. **Duplicate invoice and payment created**
**Fix Required:**
```python
# At start of _handle_checkout_completed:
session_id = session.get('id')
if Payment.objects.filter(stripe_checkout_session_id=session_id).exists():
logger.info(f"Webhook already processed for session {session_id}")
return # Already processed
```
---
### 3. PayPal Capture Order Not Idempotent
**Location:** `paypal_views.py:261-365` (PayPalCaptureOrderView)
**Problem:** If frontend calls `/capture-order/` twice (network timeout), payment captured twice.
**Fix Required:**
```python
# Check if order already captured
existing = Payment.objects.filter(paypal_order_id=order_id, status='succeeded').first()
if existing:
return Response({'status': 'already_captured', 'payment_id': existing.id})
```
---
### 4. Refund Functions Call Non-Existent Modules
**Location:** `refund_views.py:160-208`
**Problem:**
```python
from igny8_core.business.billing.utils.payment_gateways import get_stripe_client # DOESN'T EXIST
from igny8_core.business.billing.utils.payment_gateways import get_paypal_client # DOESN'T EXIST
```
**Risk:** Refunds marked as processed but **never actually charged back** to customer.
**Fix Required:** Create the missing modules or use existing service classes:
```python
from igny8_core.business.billing.services.stripe_service import StripeService
from igny8_core.business.billing.services.paypal_service import PayPalService
```
---
### 5. Amount Validation Missing for PayPal
**Location:** `paypal_views.py:569`
**Problem:**
```python
amount = float(capture_result.get('amount', package.price)) # Trusts PayPal amount
```
**Risk:** If PayPal returns wrong amount, system processes it as correct.
**Fix Required:**
```python
captured_amount = float(capture_result.get('amount', 0))
expected_amount = float(package.price)
if abs(captured_amount - expected_amount) > 0.01: # Allow 1 cent tolerance
logger.error(f"Amount mismatch: captured={captured_amount}, expected={expected_amount}")
return Response({'error': 'Amount mismatch'}, status=400)
```
---
## High Priority Issues
### 6. No Admin Dashboard for Pending Payments
**Problem:** Admins must use Django admin to approve manual payments.
**Missing Endpoint:**
```
GET /v1/admin/billing/pending-payments/ - List pending approvals
POST /v1/admin/billing/payments/{id}/approve/ - Approve payment
POST /v1/admin/billing/payments/{id}/reject/ - Reject payment
```
**Required Implementation:**
```python
class AdminPaymentViewSet(viewsets.ModelViewSet):
permission_classes = [IsAdminUser]
@action(detail=False, methods=['get'])
def pending(self, request):
payments = Payment.objects.filter(status='pending_approval')
return Response(PaymentSerializer(payments, many=True).data)
@action(detail=True, methods=['post'])
def approve(self, request, pk=None):
payment = self.get_object()
PaymentService.approve_manual_payment(payment, request.user.id, request.data.get('notes'))
return Response({'status': 'approved'})
```
---
### 7. Invoice Number Race Condition
**Location:** `invoice_service.py:52-78`
**Problem:**
```python
count = Invoice.objects.select_for_update().filter(...).count()
invoice_number = f"{prefix}-{count + 1:04d}"
while Invoice.objects.filter(invoice_number=invoice_number).exists(): # NOT LOCKED!
count += 1
```
**Fix Required:**
```python
# Use database unique constraint + retry logic
@transaction.atomic
def generate_invoice_number(account_id, invoice_type='SUB'):
prefix = f"INV-{account_id}-{invoice_type}-{timezone.now().strftime('%Y%m')}"
for attempt in range(5):
count = Invoice.objects.filter(invoice_number__startswith=prefix).count()
invoice_number = f"{prefix}-{count + 1:04d}"
try:
# Use get_or_create with unique constraint
return invoice_number
except IntegrityError:
continue
raise ValueError("Unable to generate unique invoice number")
```
---
### 8. Browser Redirect Lost After Payment
**Problem:** If user closes browser after Stripe payment but before redirect:
- Payment succeeds (webhook processes it)
- User doesn't know payment succeeded
- May attempt to pay again
**Fix Required:** Add payment status check endpoint:
```python
# New endpoint
GET /v1/billing/payment-status/{session_id}/
# Frontend should check this on /account/plans load
const checkPaymentStatus = async (sessionId) => {
const response = await fetch(`/v1/billing/payment-status/${sessionId}/`);
if (response.data.status === 'completed') {
toast.success('Your previous payment was successful!');
refreshUser();
}
};
```
---
### 9. Subscription Renewal Gets Stuck
**Location:** `subscription_renewal.py:78-131`
**Problem:** Status set to `pending_renewal` with no expiry or retry mechanism.
**Fix Required:**
```python
# Add Celery task
@app.task
def check_stuck_renewals():
"""Run daily to check for stuck renewals"""
stuck = Subscription.objects.filter(
status='pending_renewal',
metadata__renewal_required_at__lt=timezone.now() - timedelta(days=7)
)
for sub in stuck:
# Send reminder email
send_renewal_reminder(sub)
# After 14 days, suspend
if sub.metadata.get('renewal_required_at') < timezone.now() - timedelta(days=14):
sub.status = 'past_due'
sub.account.status = 'suspended'
sub.save()
```
---
### 10. Currency Exchange Rates Hardcoded
**Location:** `currency.py:137-157`
**Problem:**
```python
CURRENCY_MULTIPLIERS = {
'PKR': 278.0, # STATIC - real rate changes daily!
'INR': 83.0,
# ...
}
```
**Risk:** Users charged incorrect amounts over time.
**Fix Required:**
```python
class ExchangeRateService:
CACHE_KEY = 'exchange_rates'
CACHE_TTL = 86400 # 24 hours
@classmethod
def get_rate(cls, currency):
rates = cache.get(cls.CACHE_KEY)
if not rates:
rates = cls._fetch_from_api()
cache.set(cls.CACHE_KEY, rates, cls.CACHE_TTL)
return rates.get(currency, 1.0)
@classmethod
def _fetch_from_api(cls):
# Use OpenExchangeRates, Fixer.io, or similar
response = requests.get('https://api.exchangerate-api.com/v4/latest/USD')
return response.json()['rates']
```
---
## Medium Priority Issues
### 11. No Promo Code/Discount Support
**Missing Models:**
```python
class PromoCode(models.Model):
code = models.CharField(max_length=50, unique=True)
discount_type = models.CharField(choices=[('percent', '%'), ('fixed', '$')])
discount_value = models.DecimalField(max_digits=10, decimal_places=2)
valid_from = models.DateTimeField()
valid_until = models.DateTimeField(null=True)
max_uses = models.IntegerField(null=True)
current_uses = models.IntegerField(default=0)
applicable_plans = models.ManyToManyField('Plan', blank=True)
```
---
### 12. No Partial Payment Support
**Current:** User must pay full invoice amount.
**Needed:**
- Split invoice into multiple payments
- Track partial payment progress
- Handle remaining balance
---
### 13. No Dunning Management
**Missing:** When payment fails:
- Day 1: Payment failed notification
- Day 3: Retry attempt + reminder
- Day 7: Second retry + warning
- Day 14: Account suspension warning
- Day 21: Account suspended
---
### 14. No Manual Payment Reference Uniqueness
**Location:** `models.py:487-490`
**Fix:**
```python
manual_reference = models.CharField(
max_length=255,
blank=True,
unique=True, # ADD THIS
null=True # Allow null for non-manual payments
)
```
---
### 15. Refund Credit Deduction Race Condition
**Location:** `refund_views.py:108-129`
**Fix:** Use `select_for_update()`:
```python
with transaction.atomic():
account = Account.objects.select_for_update().get(id=payment.account_id)
if account.credit_balance >= credits_to_deduct:
account.credit_balance -= credits_to_deduct
account.save()
```
---
### 16. Invoice Total Calculation Silent Failure
**Location:** `models.py:439-448`
**Problem:**
```python
except Exception:
pass # SILENT FAILURE!
```
**Fix:**
```python
except Exception as e:
logger.error(f"Invalid line item in invoice {self.id}: {item}, error: {e}")
raise ValueError(f"Invalid invoice line item: {item}")
```
---
### 17. No Webhook Event Storage
**Missing:** All incoming webhooks should be stored for:
- Audit trail
- Replay on failure
- Debugging
**Add Model:**
```python
class WebhookEvent(models.Model):
event_id = models.CharField(max_length=255, unique=True)
provider = models.CharField(max_length=20) # stripe, paypal
event_type = models.CharField(max_length=100)
payload = models.JSONField()
processed = models.BooleanField(default=False)
processed_at = models.DateTimeField(null=True)
error_message = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
```
---
### 18. No Payment Audit Trail for Rejections
**Missing Fields in Payment model:**
```python
rejected_by = models.ForeignKey('User', null=True, related_name='rejected_payments')
rejected_at = models.DateTimeField(null=True)
rejection_reason = models.TextField(blank=True)
```
---
## Recommendations Summary
### Immediate (This Week)
| # | Issue | Effort | Impact |
|---|-------|--------|--------|
| 1 | Enable PayPal webhook signature verification | 5 min | CRITICAL |
| 2 | Add Stripe webhook idempotency check | 30 min | CRITICAL |
| 3 | Add PayPal capture idempotency check | 30 min | CRITICAL |
| 4 | Fix refund module imports | 1 hr | CRITICAL |
| 5 | Add PayPal amount validation | 30 min | CRITICAL |
### Short-Term (This Month)
| # | Issue | Effort | Impact |
|---|-------|--------|--------|
| 6 | Build admin pending payments dashboard | 1 day | HIGH |
| 7 | Fix invoice number race condition | 2 hrs | HIGH |
| 8 | Add payment status check endpoint | 2 hrs | HIGH |
| 9 | Fix stuck renewal subscriptions | 1 day | HIGH |
| 10 | Implement dynamic currency rates | 1 day | HIGH |
### Medium-Term (This Quarter)
| # | Issue | Effort | Impact |
|---|-------|--------|--------|
| 11 | Promo code system | 3 days | MEDIUM |
| 12 | Partial payment support | 2 days | MEDIUM |
| 13 | Dunning management | 2 days | MEDIUM |
| 14 | Webhook event storage | 1 day | MEDIUM |
| 15 | Payment audit trail | 1 day | MEDIUM |
---
## Architecture Assessment
### What's Good
1. **Clean separation** between frontend entry points, services, and backend
2. **Country-based logic** correctly isolates Pakistan users from PayPal
3. **Multi-gateway support** (Stripe, PayPal, Manual) well architected
4. **Service layer abstraction** (`StripeService`, `PayPalService`, `PaymentService`)
5. **Invoice and payment tracking** comprehensive
6. **Webhook handlers** exist for both gateways
### What Needs Improvement
1. **Idempotency** - Critical for payment processing
2. **Transaction safety** - Need more `@transaction.atomic()` and `select_for_update()`
3. **Observability** - No webhook event storage, limited metrics
4. **Admin tooling** - Manual payments need proper dashboard
5. **Error handling** - Too many silent failures
6. **Feature gaps** - No promo codes, partial payments, dunning
---
## Final Assessment
| Area | Rating | Notes |
|------|--------|-------|
| Documentation Accuracy | A | Matches codebase |
| Security | C | Webhook verification gaps |
| Reliability | C | Idempotency issues |
| Completeness | B | Core features present |
| Admin Experience | D | No proper dashboard |
| User Experience | B | Good flows, missing status checks |
| Code Quality | B | Good structure, some silent failures |
**Overall Grade: C+**
The payment system is functional but has critical security and reliability gaps that must be addressed before scaling. The architecture is sound, but implementation details need hardening.
---
## Quick Wins (Can Do Today)
1. Uncomment PayPal webhook signature rejection (5 min)
2. Add `@transaction.atomic()` to all payment handlers (30 min)
3. Add duplicate check before creating payments (30 min)
4. Add unique constraint to `manual_reference` (migration)
5. Remove silent `except: pass` blocks (30 min)
---
*Report generated by deep audit of IGNY8 payment system codebase.*

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,677 @@
# Payment System Documentation
> **Version:** 1.6.0
> **Last Updated:** January 8, 2026
> **Status:** Production Ready
This document provides comprehensive documentation of the IGNY8 payment system architecture, implementation, and flows.
---
## Table of Contents
1. [System Overview](#system-overview)
2. [Payment Entry Points](#payment-entry-points)
3. [Backend Architecture](#backend-architecture)
4. [Frontend Architecture](#frontend-architecture)
5. [Payment Flows](#payment-flows)
6. [Country-Based Payment Rules](#country-based-payment-rules)
7. [Webhook Processing](#webhook-processing)
8. [Models Reference](#models-reference)
9. [Security Features](#security-features)
---
## System Overview
### Supported Payment Methods
| Method | Type | Regions | Use Cases |
|--------|------|---------|-----------|
| **Stripe** | Credit/Debit Card | Global | Subscriptions, Credit packages |
| **PayPal** | PayPal account | Global (except PK) | Subscriptions, Credit packages |
| **Bank Transfer** | Manual | Pakistan (PK) | Subscriptions, Credit packages |
### Payment Method Selection Logic
```
┌────────────────────────────────────────────────────────┐
│ Country-Based Payment Rules │
├────────────────────────────────────────────────────────┤
│ │
│ Global Users (non-PK): │
│ ✅ Stripe (Credit/Debit Card) │
│ ✅ PayPal │
│ ❌ Bank Transfer (not available) │
│ │
│ Pakistan Users (PK): │
│ ✅ Stripe (Credit/Debit Card) │
│ ❌ PayPal (not available in PK) │
│ ✅ Bank Transfer (manual) │
│ │
└────────────────────────────────────────────────────────┘
```
### Architecture Overview
```
┌─────────────────────────────────────────────────────────────────┐
│ PAYMENT SYSTEM FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Signup │───────▶ │ /account/ │ │
│ │ (no pay) │ │ plans │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ │ │ │
│ New User? Existing User? │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ PendingPay- │ │ Plans/Billing│ │
│ │ mentView │ │ Dashboard │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ┌─────────┼─────────┐ ┌────────┼────────┐ │
│ │ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ │
│ Stripe PayPal Bank Upgrade Credits Manage │
│ Transfer │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## Payment Entry Points
### 1. Signup Flow
**File:** `frontend/src/components/auth/SignUpFormUnified.tsx`
**Simplified Signup (No Payment on Signup):**
- User selects plan and provides details
- Account created with `status='pending_payment'` for paid plans
- User redirected to `/account/plans` to complete payment
- No payment gateway redirect from signup page
```typescript
// Signup flow creates account only, no checkout
const handleSignup = async (data) => {
const result = await register({
email, password, plan_slug, billing_country
});
// Redirect to plans page for payment
navigate('/account/plans');
};
```
### 2. Plans & Billing Page
**File:** `frontend/src/pages/account/PlansAndBillingPage.tsx`
Central hub for all payment-related actions:
**For New Users (pending_payment):**
- Shows `PendingPaymentView` component
- Full-page payment interface
- Invoice details and payment method selection
**For Existing Users:**
- Current plan and subscription status
- Credit balance and purchase
- Invoice history and downloads
- Subscription management
### 3. PendingPaymentView Component
**File:** `frontend/src/components/billing/PendingPaymentView.tsx`
Full-page payment interface for new users:
- Displays invoice details and plan info
- Payment method selection based on country
- Stripe/PayPal redirect or Bank Transfer form
- Status checking for bank transfer submissions
### 4. Bank Transfer Form
**File:** `frontend/src/components/billing/BankTransferForm.tsx`
Manual payment submission for Pakistan users:
- Bank account details display
- Transaction reference input
- File upload for payment proof
- Submission and status tracking
---
## Backend Architecture
### Service Layer
#### StripeService
**File:** `backend/igny8_core/business/billing/services/stripe_service.py`
| Method | Description |
|--------|-------------|
| `create_checkout_session()` | Create subscription checkout |
| `create_credit_checkout_session()` | Create credit package checkout |
| `create_billing_portal_session()` | Customer billing portal |
| `get_or_create_customer()` | Stripe customer management |
| `construct_webhook_event()` | Verify webhook signatures |
#### PayPalService
**File:** `backend/igny8_core/business/billing/services/paypal_service.py`
| Method | Description |
|--------|-------------|
| `create_order()` | Create one-time payment order |
| `create_subscription_order()` | Create subscription order |
| `capture_order()` | Capture approved payment |
| `verify_webhook_signature()` | Webhook verification |
#### InvoiceService
**File:** `backend/igny8_core/business/billing/services/invoice_service.py`
| Method | Description |
|--------|-------------|
| `create_subscription_invoice()` | Invoice for plan subscription |
| `create_credit_package_invoice()` | Invoice for credit purchase |
| `mark_paid()` | Mark invoice as paid |
| `generate_pdf()` | Generate PDF invoice |
#### PaymentService
**File:** `backend/igny8_core/business/billing/services/payment_service.py`
| Method | Description |
|--------|-------------|
| `create_stripe_payment()` | Record Stripe payment |
| `create_paypal_payment()` | Record PayPal payment |
| `create_manual_payment()` | Record bank transfer |
| `approve_manual_payment()` | Admin approval |
### API Endpoints
#### Stripe Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/v1/billing/stripe/config/` | GET | Publishable key |
| `/v1/billing/stripe/checkout/` | POST | Create checkout session |
| `/v1/billing/stripe/credit-checkout/` | POST | Credit package checkout |
| `/v1/billing/stripe/billing-portal/` | POST | Billing portal |
| `/v1/billing/webhooks/stripe/` | POST | Webhook handler |
#### PayPal Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/v1/billing/paypal/config/` | GET | Client ID |
| `/v1/billing/paypal/create-order/` | POST | Credit package order |
| `/v1/billing/paypal/create-subscription-order/` | POST | Subscription order |
| `/v1/billing/paypal/capture-order/` | POST | Capture payment |
| `/v1/billing/webhooks/paypal/` | POST | Webhook handler |
#### Invoice Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/v1/billing/invoices/` | GET | List invoices |
| `/v1/billing/invoices/{id}/` | GET | Invoice detail |
| `/v1/billing/invoices/{id}/download_pdf/` | GET | Download PDF |
#### Payment Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/v1/billing/payments/` | GET | List payments |
| `/v1/billing/payments/manual/` | POST | Submit bank transfer |
| `/v1/billing/admin/payments/confirm/` | POST | Admin approve/reject |
---
## Frontend Architecture
### Services
**File:** `frontend/src/services/billing.api.ts`
Key functions:
```typescript
// Gateway availability (country-based)
getAvailablePaymentGateways(userCountry?: string)
// Subscription helpers
subscribeToPlan(planId, gateway, options)
purchaseCredits(packageId, gateway, options)
// Stripe functions
createStripeCheckout(planId, options)
createStripeCreditCheckout(packageId, options)
openStripeBillingPortal(returnUrl)
// PayPal functions
createPayPalSubscriptionOrder(planId, options)
createPayPalCreditOrder(packageId, options)
capturePayPalOrder(orderId, metadata)
// Manual payment
submitManualPayment(invoiceId, data)
```
### Components
| Component | File | Purpose |
|-----------|------|---------|
| `PendingPaymentView` | `/components/billing/PendingPaymentView.tsx` | New user payment |
| `BankTransferForm` | `/components/billing/BankTransferForm.tsx` | Bank transfer submission |
| `PendingPaymentBanner` | `/components/billing/PendingPaymentBanner.tsx` | Alert for pending payments |
| `PaymentGatewaySelector` | `/components/billing/PaymentGatewaySelector.tsx` | Gateway selection UI |
| `PayInvoiceModal` | `/components/billing/PayInvoiceModal.tsx` | Pay invoice modal |
---
## Payment Flows
### Flow 1: New User Signup with Stripe
```
1. User submits signup form with plan
2. Backend creates:
- User account
- Account (status='pending_payment')
- Subscription (status='pending_payment')
- Invoice (status='pending')
3. User redirected to /account/plans
4. PendingPaymentView displays
5. User selects Stripe, clicks Pay
6. Redirect to Stripe Checkout
7. User completes payment
8. Stripe webhook received:
- Payment recorded
- Invoice marked paid
- Account activated
- Credits added
9. User redirected back to /account/plans
10. Success message, dashboard displays
```
### Flow 2: New User with PayPal (Non-PK)
```
1. Same as Stripe steps 1-4
5. User selects PayPal, clicks Pay
6. PayPal order created
7. Redirect to PayPal approval
8. User approves on PayPal
9. Redirect back with order_id
10. Frontend calls capture-order
11. Backend processes:
- Payment captured
- Payment recorded
- Invoice marked paid
- Account activated
- Credits added
12. Success displayed
```
### Flow 3: Pakistan User with Bank Transfer
```
1. Same as signup steps 1-4
5. User sees Stripe + Bank Transfer options
6. User selects Bank Transfer
7. BankTransferForm displays:
- Bank details (SCB Pakistan)
- Reference input
- Proof upload option
8. User makes transfer, submits form
9. Backend creates:
- Payment (status='pending_approval')
10. User sees "Awaiting Approval" status
11. Admin reviews in Django Admin
12. Admin approves:
- Payment marked succeeded
- Invoice marked paid
- Account activated
- Credits added
13. User receives email confirmation
```
### Flow 4: Existing User Buys Credits
```
1. User on /account/plans clicks "Buy Credits"
2. Credit package selection modal
3. User selects package and gateway
4. For Stripe/PayPal: redirect flow
5. For Bank Transfer: form submission
6. On success: credits added to account
```
---
## Country-Based Payment Rules
### Implementation in Frontend
```typescript
// billing.api.ts
export async function getAvailablePaymentGateways(userCountry?: string) {
const [stripeAvailable, paypalAvailable] = await Promise.all([
isStripeConfigured(),
isPayPalConfigured(),
]);
const isPakistan = userCountry?.toUpperCase() === 'PK';
return {
stripe: stripeAvailable,
// PayPal: NOT available for Pakistan
paypal: !isPakistan && paypalAvailable,
// Bank Transfer: ONLY for Pakistan
manual: isPakistan,
};
}
```
### Usage in Components
```typescript
// PendingPaymentView.tsx
const isPakistan = userCountry === 'PK';
// Load gateways with country filter
const gateways = await getAvailablePaymentGateways(userCountry);
// Show appropriate options
const paymentOptions = [
{ type: 'stripe', ... }, // Always shown if configured
isPakistan ? { type: 'manual', ... } : { type: 'paypal', ... },
];
```
---
## Webhook Processing
### Stripe Webhooks
**Endpoint:** `POST /v1/billing/webhooks/stripe/`
| Event | Handler Action |
|-------|----------------|
| `checkout.session.completed` | Activate subscription, add credits |
| `invoice.paid` | Add renewal credits |
| `invoice.payment_failed` | Send notification |
| `customer.subscription.updated` | Sync changes |
| `customer.subscription.deleted` | Cancel subscription |
**Idempotency:** Checks `WebhookEvent` model before processing:
```python
# Check if already processed
if WebhookEvent.objects.filter(
provider='stripe',
event_id=event_id,
status='processed'
).exists():
return # Already handled
```
### PayPal Webhooks
**Endpoint:** `POST /v1/billing/webhooks/paypal/`
| Event | Handler Action |
|-------|----------------|
| `CHECKOUT.ORDER.APPROVED` | Auto-capture if configured |
| `PAYMENT.CAPTURE.COMPLETED` | Mark succeeded, add credits |
| `PAYMENT.CAPTURE.DENIED` | Mark failed |
| `BILLING.SUBSCRIPTION.ACTIVATED` | Activate subscription |
**Signature Verification:** Enabled and enforced:
```python
is_valid = service.verify_webhook_signature(...)
if not is_valid:
return Response({'error': 'Invalid signature'}, status=400)
```
---
## Models Reference
### Invoice Model
```python
class Invoice(AccountBaseModel):
STATUS_CHOICES = [
('draft', 'Draft'),
('pending', 'Pending'),
('paid', 'Paid'),
('void', 'Void'),
('uncollectible', 'Uncollectible'),
]
invoice_number = models.CharField(max_length=50, unique=True)
subscription = models.ForeignKey('auth.Subscription', ...)
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
subtotal = models.DecimalField(...)
tax = models.DecimalField(...)
total = models.DecimalField(...)
currency = models.CharField(max_length=3, default='USD')
due_date = models.DateField()
line_items = models.JSONField(default=list)
```
### Payment Model
```python
class Payment(AccountBaseModel):
STATUS_CHOICES = [
('pending', 'Pending'),
('pending_approval', 'Pending Approval'),
('succeeded', 'Succeeded'),
('failed', 'Failed'),
('refunded', 'Refunded'),
]
PAYMENT_METHOD_CHOICES = [
('stripe', 'Stripe'),
('paypal', 'PayPal'),
('bank_transfer', 'Bank Transfer'),
('manual', 'Manual'),
]
invoice = models.ForeignKey('Invoice', ...)
amount = models.DecimalField(...)
currency = models.CharField(max_length=3)
payment_method = models.CharField(choices=PAYMENT_METHOD_CHOICES)
status = models.CharField(choices=STATUS_CHOICES)
stripe_payment_intent_id = models.CharField(...)
paypal_order_id = models.CharField(...)
manual_reference = models.CharField(..., unique=True)
```
### WebhookEvent Model
```python
class WebhookEvent(models.Model):
"""Audit trail for webhook processing"""
PROVIDER_CHOICES = [
('stripe', 'Stripe'),
('paypal', 'PayPal'),
]
provider = models.CharField(choices=PROVIDER_CHOICES)
event_id = models.CharField(max_length=255)
event_type = models.CharField(max_length=100)
payload = models.JSONField()
status = models.CharField() # 'pending', 'processed', 'failed'
processed_at = models.DateTimeField(null=True)
error_message = models.TextField(blank=True)
```
---
## Security Features
### Implemented Security Measures
1. **Webhook Signature Verification**
- Stripe: `stripe.Webhook.construct_event()` with signing secret
- PayPal: `verify_webhook_signature()` API call
2. **Idempotency**
- `WebhookEvent` model tracks processed events
- Duplicate detection before processing
3. **Amount Validation**
- PayPal capture validates amount matches expected
- Prevents manipulation attacks
4. **Manual Reference Uniqueness**
- Database constraint prevents duplicate bank transfer references
- Prevents double submission
5. **CSRF Protection**
- Webhook endpoints exempt (external callers)
- All other endpoints protected
6. **Authentication**
- Payment endpoints require `IsAuthenticatedAndActive`
- Config endpoints allow `AllowAny` (public keys only)
---
## Admin Operations
### Django Admin Features
**Location:** Django Admin > Billing
- **Invoices:** View, filter, download PDF
- **Payments:** View, approve/reject manual payments
- **Credit Transactions:** Audit trail
- **Credit Packages:** Manage packages
### Manual Payment Approval
```
1. Admin navigates to Payments in Django Admin
2. Filter by status='pending_approval'
3. Review payment details and proof
4. Click "Approve" or "Reject" action
5. System automatically:
- Updates payment status
- Marks invoice paid (if approved)
- Activates account (if approved)
- Adds credits (if approved)
- Sends email notification
```
---
## Configuration
### Environment Variables
```bash
# Stripe
STRIPE_SECRET_KEY=sk_...
STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_WEBHOOK_SECRET=whsec_...
# PayPal
PAYPAL_CLIENT_ID=...
PAYPAL_CLIENT_SECRET=...
PAYPAL_WEBHOOK_ID=...
PAYPAL_MODE=sandbox|live
# General
DEFAULT_CURRENCY=USD
```
### IntegrationProvider Setup
Payment gateways configured via `IntegrationProvider` model in Django Admin:
1. **Stripe Provider:**
- Name: "Stripe"
- Provider Type: "stripe"
- Credentials: `{"secret_key": "...", "publishable_key": "...", "webhook_secret": "..."}`
2. **PayPal Provider:**
- Name: "PayPal"
- Provider Type: "paypal"
- Credentials: `{"client_id": "...", "client_secret": "...", "webhook_id": "..."}`
---
## Troubleshooting
### Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| "Stripe not configured" | Missing IntegrationProvider | Add Stripe provider in admin |
| "PayPal not configured" | Missing IntegrationProvider | Add PayPal provider in admin |
| PayPal shown for PK users | Country not passed correctly | Ensure `billing_country` saved on account |
| Duplicate payments | Webhook retry without idempotency | Check `WebhookEvent` for duplicates |
| PDF download fails | Missing `reportlab` | Run `pip install reportlab` |
### Debug Logging
Enable billing debug logs:
```python
# settings.py
LOGGING = {
'loggers': {
'igny8_core.business.billing': {
'level': 'DEBUG',
},
},
}
```
---
## File Reference
### Backend Files
| File | Description |
|------|-------------|
| `billing/views/stripe_views.py` | Stripe API endpoints |
| `billing/views/paypal_views.py` | PayPal API endpoints |
| `billing/views/refund_views.py` | Refund processing |
| `billing/services/stripe_service.py` | Stripe service layer |
| `billing/services/paypal_service.py` | PayPal service layer |
| `billing/services/invoice_service.py` | Invoice operations |
| `billing/services/payment_service.py` | Payment operations |
| `billing/services/pdf_service.py` | PDF generation |
| `billing/services/email_service.py` | Email notifications |
| `billing/models.py` | Billing models |
| `billing/urls.py` | URL routing |
### Frontend Files
| File | Description |
|------|-------------|
| `services/billing.api.ts` | API client functions |
| `pages/account/PlansAndBillingPage.tsx` | Main billing page |
| `components/billing/PendingPaymentView.tsx` | New user payment |
| `components/billing/BankTransferForm.tsx` | Bank transfer form |
| `components/billing/PayInvoiceModal.tsx` | Invoice payment modal |
| `components/billing/PaymentGatewaySelector.tsx` | Gateway selection |
| `components/billing/PendingPaymentBanner.tsx` | Payment alert banner |
---
*Document generated from production codebase - January 8, 2026*

View File

@@ -1,7 +1,7 @@
# IGNY8 Technical Documentation # IGNY8 Technical Documentation
**Version:** 1.4.0 **Version:** 1.6.0
**Last Updated:** January 6, 2026 **Last Updated:** January 8, 2026
**Purpose:** Complete technical reference for the IGNY8 AI content platform **Purpose:** Complete technical reference for the IGNY8 AI content platform
--- ---
@@ -20,6 +20,7 @@
| Understand frontend structure | [30-FRONTEND/PAGES.md](30-FRONTEND/PAGES.md) | | Understand frontend structure | [30-FRONTEND/PAGES.md](30-FRONTEND/PAGES.md) |
| Trace a workflow end-to-end | [40-WORKFLOWS/](#workflows) | | Trace a workflow end-to-end | [40-WORKFLOWS/](#workflows) |
| Look up model fields | [90-REFERENCE/MODELS.md](90-REFERENCE/MODELS.md) | | Look up model fields | [90-REFERENCE/MODELS.md](90-REFERENCE/MODELS.md) |
| **Payment system (Stripe/PayPal/Bank)** | [90-REFERENCE/PAYMENT-SYSTEM.md](90-REFERENCE/PAYMENT-SYSTEM.md) |
| See prelaunch checklist | [plans/FINAL-PRELAUNCH.md](plans/FINAL-PRELAUNCH.md) | | See prelaunch checklist | [plans/FINAL-PRELAUNCH.md](plans/FINAL-PRELAUNCH.md) |
| **Understand publishing flow** | [50-DEPLOYMENT/WORDPRESS-INTEGRATION-FLOW.md](50-DEPLOYMENT/WORDPRESS-INTEGRATION-FLOW.md) | | **Understand publishing flow** | [50-DEPLOYMENT/WORDPRESS-INTEGRATION-FLOW.md](50-DEPLOYMENT/WORDPRESS-INTEGRATION-FLOW.md) |
| **AI model architecture (v1.4.0)** | [plans/4th-jan-refactor/final-model-schemas.md](plans/4th-jan-refactor/final-model-schemas.md) | | **AI model architecture (v1.4.0)** | [plans/4th-jan-refactor/final-model-schemas.md](plans/4th-jan-refactor/final-model-schemas.md) |
@@ -138,6 +139,7 @@
|----------|---------| |----------|---------|
| [MODELS.md](90-REFERENCE/MODELS.md) | All database models and fields | | [MODELS.md](90-REFERENCE/MODELS.md) | All database models and fields |
| [AI-FUNCTIONS.md](90-REFERENCE/AI-FUNCTIONS.md) | AI engine capabilities and costs | | [AI-FUNCTIONS.md](90-REFERENCE/AI-FUNCTIONS.md) | AI engine capabilities and costs |
| [PAYMENT-SYSTEM.md](90-REFERENCE/PAYMENT-SYSTEM.md) | Payment gateways (Stripe, PayPal, Bank Transfer) |
| [TROUBLESHOOTING.md](90-REFERENCE/TROUBLESHOOTING.md) | Common issues and fixes | | [TROUBLESHOOTING.md](90-REFERENCE/TROUBLESHOOTING.md) | Common issues and fixes |
--- ---