Billing and Invoicing
Overview
Waldur's billing system creates invoice items for marketplace resources based on their offering component's billing type. The central orchestrator is MarketplaceBillingService (src/waldur_mastermind/marketplace/billing.py), which dispatches to specialized processors depending on the billing type.
Billing Types
Defined in BillingTypes (src/waldur_mastermind/marketplace/enums.py):
| Type | Value | Trigger | Recurrence | Handler |
|---|---|---|---|---|
| FIXED | "fixed" |
Resource activation | Monthly (prorated) | MarketplaceBillingService |
| USAGE | "usage" |
Usage report submission | Per report | BillingUsageProcessor |
| ONE_TIME | "one" |
Resource creation | Once | MarketplaceBillingService |
| ON_PLAN_SWITCH | "few" |
Plan change | Once per switch | MarketplaceBillingService |
| LIMIT | "limit" |
Resource creation / limit change | Varies by limit_period |
LimitPeriodProcessor |
Billing Type Dispatch
graph TD
A[Resource event] --> B{Billing type?}
B -->|FIXED| C[Create prorated monthly item]
B -->|ONE_TIME| D{Order type = CREATE?}
D -->|Yes| E[Create single charge]
D -->|No| F[Skip]
B -->|ON_PLAN_SWITCH| G{Order type = UPDATE?}
G -->|Yes| H[Create single charge]
G -->|No| I[Skip]
B -->|USAGE| J[Skip - handled by BillingUsageProcessor]
B -->|LIMIT| K[LimitPeriodProcessor]
K --> L{limit_period?}
L -->|MONTH| M[Monthly invoice item]
L -->|QUARTERLY| N[Quarterly invoice item]
L -->|ANNUAL| O[Annual invoice item]
L -->|TOTAL| P[One-time quantity item]
Limit Periods
For components with billing_type=LIMIT, the limit_period field on OfferingComponent controls when and how invoice items are created.
Defined in LimitPeriods (src/waldur_mastermind/marketplace/enums.py):
| Period | Value | Invoice creation | Billing window | Unit |
|---|---|---|---|---|
| MONTH | "month" |
Every month | 1st to end of month | Plan unit |
| QUARTERLY | "quarterly" |
Months 1, 4, 7, 10 only | Quarter start to quarter end (e.g., Jan 1 - Mar 31) | Plan unit |
| ANNUAL | "annual" |
Resource's creation anniversary month | 12 months from delivery date | Plan unit |
| TOTAL | "total" |
Once on creation; incremental on changes | Full resource lifetime | QUANTITY |
Quarterly Billing Timeline
sequenceDiagram
participant Jan as January
participant Feb as February
participant Mar as March
participant Apr as April
Note over Jan: Q1 billing month
Jan->>Jan: Create invoice item (Jan 1 - Mar 31)
Note over Feb: Not a billing month for quarterly
Feb->>Feb: Skip (no new item)
Note over Feb: Limit changes update Jan invoice item
Feb-->>Jan: Update existing Q1 item with split periods
Note over Mar: Not a billing month for quarterly
Mar->>Mar: Skip (no new item)
Note over Apr: Q2 billing month
Apr->>Apr: Create invoice item (Apr 1 - Jun 30)
Invoice Lifecycle
Invoice States
| State | Description |
|---|---|
| PENDING | Active invoice for current billing period. Items can be added/modified. |
| PENDING_FINALIZATION | Transitional state used when a grace period is configured. Items can still be added/modified. |
| CREATED | Finalized invoice. Items are frozen. |
| PAID | Invoice has been paid. |
| CANCELED | Invoice has been canceled. |
Both PENDING and PENDING_FINALIZATION are considered mutable states — invoice items can be added or updated while the invoice is in either state.
Monthly Invoice Creation
The create_monthly_invoices task (src/waldur_mastermind/invoices/tasks.py) runs at midnight on the 1st of each month:
- Previous month PENDING invoices are finalized (see Finalization below)
- For each customer,
MarketplaceBillingService.get_or_create_invoiceis called - If the invoice is newly created, all active billable resources are processed via
_process_resource
When a resource is activated mid-month, _register calls get_or_create_invoice. If the invoice already exists, it adds items for just that resource with prorated start/end dates.
Invoice Finalization
Finalization transitions invoices from mutable to immutable (CREATED) state. The behavior depends on the INVOICE_FINALIZATION_GRACE_PERIOD_HOURS setting:
Without grace period (default, grace_hours = 0):
- On the 1st at midnight,
create_monthly_invoicesfinalizes previous month invoices immediately - Overdue credits are zeroed, compensations are applied, invoices transition PENDING → CREATED
- Reports and notifications are sent
With grace period (e.g., grace_hours = 24):
- On the 1st at midnight,
create_monthly_invoicestransitions previous month invoices to PENDING_FINALIZATION - The
finalize_previous_invoicestask runs hourly on the 1st–3rd of each month - Once the configured grace period has elapsed (measured from midnight on the 1st), it finalizes: PENDING_FINALIZATION → CREATED
- Reports and notifications are sent only after all invoices are finalized
The grace period allows late usage data (e.g., from external billing systems) to be captured before invoices are frozen.
graph TD
A[1st of month, midnight] --> B{Grace period configured?}
B -->|No| C[PENDING → CREATED immediately]
C --> D[Send reports & notifications]
B -->|Yes| E[PENDING → PENDING_FINALIZATION]
E --> F[Hourly check: grace period elapsed?]
F -->|No| G[Skip, retry next hour]
F -->|Yes| H[PENDING_FINALIZATION → CREATED]
H --> I{All invoices finalized?}
I -->|No| J[Wait for next hourly run]
I -->|Yes| D
Credits and Compensations
Customer credits can be configured with an optional end_date (must be the 1st of a month). During invoice finalization:
- Overdue credits are zeroed: Credits with
end_datebefore the effective date are set to zero - Compensations are applied:
MonthlyCompensationcalculates and applies credit-based discounts to invoice items - Expected consumption is updated: Linear consumption projections are recalculated
When a grace period is used, the effective date for zeroing credits is always the 1st of the current month (not the actual finalization date). This ensures credits with end_date on the 1st are still applied to the previous month's invoice before being zeroed.
Configuration
The grace period is configured in WALDUR_INVOICES settings:
1 2 3 4 5 6 7 | |
Handling Limit Changes
The post_save signal on Resource triggers process_billing_on_resource_save (src/waldur_mastermind/marketplace/handlers.py), which calls MarketplaceBillingService.handle_limits_change when resource.limits changes.
graph TD
A[resource.limits changed] --> B[handle_limits_change]
B --> C{For each limit component}
C --> D{limit_period?}
D -->|MONTH / QUARTERLY / ANNUAL| E[_create_or_update_invoice_item]
D -->|TOTAL| F[_create_invoice_item_for_total_limit]
E --> G{Invoice item exists<br>for this component?}
G -->|Yes| H[_update_invoice_item:<br>Split resource_limit_periods]
G -->|No, periodic| I{Check billing period<br>origin invoice}
I -->|Found on origin invoice| H
I -->|Not found| J[Create new invoice item]
F --> K[Calculate diff from<br>all previous items]
K --> L{diff = 0?}
L -->|Yes| M[Skip]
L -->|No| N[Create incremental item<br>positive or negative price]
Periodic Limit Updates (MONTH, QUARTERLY, ANNUAL)
When a limit changes for a periodic component, _update_invoice_item splits the existing invoice item's resource_limit_periods into old and new segments with date boundaries. The total quantity is recalculated as the sum across all periods.
For QUARTERLY and ANNUAL components, the system looks for the invoice item on the billing period's original invoice (e.g., the January invoice for a Q1 change happening in February), not just the current month's invoice.
Example: A quarterly component with limit changed from 100 to 150 on February 15th updates the January invoice item's resource_limit_periods:
1 2 3 4 | |
TOTAL Limit Updates
For TOTAL period components, the system:
- Sums all previously billed quantities (accounting for negative/compensation items)
- Calculates the difference between the new limit and the total already billed
- Creates a new incremental invoice item for the difference (with negative
unit_pricefor decreases)
Key Source Files
| File | Class/Function | Purpose |
|---|---|---|
src/waldur_mastermind/marketplace/billing.py |
MarketplaceBillingService |
Central billing orchestrator |
src/waldur_mastermind/marketplace/billing_limit.py |
LimitPeriodProcessor |
LIMIT billing type logic |
src/waldur_mastermind/marketplace/billing_usage.py |
BillingUsageProcessor |
USAGE billing type logic |
src/waldur_mastermind/marketplace/handlers.py |
process_billing_on_resource_save |
Signal handler for resource changes |
src/waldur_mastermind/invoices/tasks.py |
create_monthly_invoices |
Monthly invoice creation task |
src/waldur_mastermind/invoices/tasks.py |
finalize_previous_invoices |
Deferred invoice finalization (grace period) |
src/waldur_mastermind/invoices/compensations.py |
MonthlyCompensation |
Credit-based compensation logic |
src/waldur_mastermind/marketplace/enums.py |
BillingTypes, LimitPeriods |
Billing type and period enums |