Invoices & Revenue
Invoice entity lifecycle, PaymentTransaction tracking, revenue dashboard (MRR/ARR/Churn), multi-currency support, and export formats.
The invoicing system tracks every payment event as an immutable Invoice record with one or more InvoiceLineItems and a corresponding PaymentTransaction. Invoices are created automatically by the Stripe webhook handler on successful payments. The revenue dashboard aggregates invoice data into MRR, ARR, churn rate, and edition-level analytics.
Invoice Entity
An Invoice represents a billing event for a tenant. It is created by the Stripe webhook handler when a payment succeeds or a subscription renews.
| Field | Type | Description |
|---|---|---|
| Id | Guid | Primary key |
| TenantId | Guid | Tenant this invoice belongs to |
| SubscriptionId | Guid? | Linked TenantSubscription (nullable for manual/one-off invoices) |
| InvoiceNumber | string(50) | Human-readable auto-generated (e.g. INV-2026-00001) |
| SequenceNumber | int | Auto-incrementing DB sequence for atomic InvoiceNumber generation |
| Currency | Currency | ISO currency code (USD, EUR, SAR, etc.) |
| SubTotal | decimal(18,2) | Pre-discount amount (sum of line items) |
| DiscountAmount | decimal(18,2) | Promotion discount applied |
| TaxAmount | decimal(18,2) | Tax charged (if applicable) |
| Total | decimal(18,2) | Final billed: SubTotal − DiscountAmount + TaxAmount |
| Status | InvoiceStatus | Draft | Pending | Paid | Void | Refunded |
| DueDate | DateTime? | Payment due date (null = immediate) |
| PaidAt | DateTime? | When the invoice was paid |
| BillingCycle | BillingCycle | The billing cycle this invoice covers (Monthly, Yearly, etc.) |
| StripeInvoiceId | string? | Stripe invoice ID for reconciliation (in_xxx) |
| StripePaymentIntentId | string? | Stripe payment intent ID for tracking (pi_xxx) |
| PdfUrl | string? | URL/path to the generated PDF invoice |
| Notes | string? | Admin notes (internal only — never exposed to tenants) |
Invoice Line Items
Each Invoice contains one or more InvoiceLineItems. A line item represents a single billable unit — typically the subscription price for a billing cycle, a promo discount, or a manual adjustment.
| Field | Type | Description |
|---|---|---|
| Description | string | Human-readable line item label |
| Quantity | int | Number of units (usually 1) |
| UnitPrice | decimal(18,2) | Price per unit |
| Total | decimal(18,2) | Quantity × UnitPrice |
| Type | LineItemType | Subscription | Discount | Tax | Adjustment |
PaymentTransaction Entity
A PaymentTransaction records the raw payment event from the gateway. It is linked to an Invoice and captures the gateway-specific transaction ID, the exact amount charged, the currency, and the processing status.
| Field | Type | Description |
|---|---|---|
| InvoiceId | Guid | Linked invoice |
| TenantId | Guid | Tenant that was charged |
| Gateway | PaymentGateway | Stripe | PayPal | Paymob | Manual |
| GatewayTransactionId | string | Stripe Payment Intent ID or Charge ID |
| Amount | decimal(18,2) | Amount charged |
| Currency | Currency | Currency of the charge |
| Status | TransactionStatus | Pending | Succeeded | Failed | Refunded |
| FailureReason | string? | Gateway failure message |
| GatewayResponse | string? | Full JSON response (for debugging) |
| ProcessedAt | DateTime? | When the payment was processed |
| RefundedAt | DateTime? | When the refund was processed |
Invoice Status Lifecycle
Invoices move through a linear lifecycle from creation to settlement or cancellation.
Invoice Numbering
Invoice numbers follow the pattern INV-YYYY-NNNNN. The sequence is maintained per-tenant using IgnoreQueryFilters() to ensure soft-deleted invoices are included in the sequence count, preventing gaps in numbering for audit purposes.
Revenue Dashboard
The billing dashboard aggregates invoice data into 8 key performance indicators. It is accessible at GET /api/v1/billing/dashboard and requires the billing.view permission.
Dashboard KPIs
The dashboard provides the following metrics, all computed from the Invoice and TenantSubscription tables:
- MRR (Monthly Recurring Revenue) — Sum of all active monthly subscriptions
- ARR (Annual Recurring Revenue) — MRR × 12
- Total Revenue — All-time sum of paid invoices
- Churn Rate — Percentage of subscriptions cancelled in the last 30 days
- Active Subscriptions — Count of currently active tenants
- Trial Subscriptions — Count of tenants in trial phase
- Cancelled This Month — Count of cancellations in current month
- Platform Health Score — Composite score based on MRR trend, churn, and trial conversion
Multi-Currency Support
SCRIPE supports 28 Stripe-supported currencies. Each Invoice stores the Currency (ISO code), SubTotal, DiscountAmount, TaxAmount, and Total in the original currency. ExchangeRateToUsd and TotalAmountUsd fields normalize amounts for consistent USD-based reporting across all currencies.
| Currency Group | Examples | Stripe Handling |
|---|---|---|
| Standard (×100 to cents) | USD, EUR, GBP, SAR, AED, EGP | Amount multiplied by 100 |
| Zero-decimal | JPY, KWD, BHD | Amount passed as-is |
| 3-decimal | OMR, TND | Amount multiplied by 1000 |
Export Formats
The subscription export system (GET /api/v1/subscriptions/export) generates comprehensive reports in three formats.
- CSV — Lightweight, importable into any spreadsheet or BI tool
- XLSX — Professional Excel workbook with styled headers, filter metadata sheet, conditional formatting, and auto-sized columns (ClosedXML)
- PDF — Print-ready document with branded cover page, statistical summary, and paginated data tables (QuestPDF)
API Endpoints
Invoice endpoints are exposed through the InvoicesController and the BillingController:
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/invoices | List invoices with pagination and filters (status, tenant, date range) | 🔒 Required |
| GET | /api/v1/invoices/{id} | Get invoice details including line items | 🔒 Required |
| GET | /api/v1/invoices/{id}/pdf | Download invoice as PDF | 🔒 Required |
| GET | /api/v1/billing/dashboard | Get revenue dashboard (MRR, ARR, churn, trends) | 🔒 Required |
| GET | /api/v1/subscriptions/export | Export subscriptions as CSV, Excel, or PDF | 🔒 Required |