You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Design document for metered pricing, aka usage-based pricing
What we have
Events ingestion able to store arbitrary events with metadata for a given customer
Meters that filters and computes a value from the events
What we need
A new type of ProductPrice to tie together:
A meter
A price per unit
A number of "free" (included) units
Note
We currently focus solely on unit pricing before expanding to tiered or volume pricing.
A way to track the active prices on a given subscription.
Currently, we can only track one price, but we'll need to track multiple prices. Typically, a fixed price and one or more metered prices.
A way to track individual line items on an onder.
Currently, we can only track a single price and a total amount.
A way to track, whenever an event come in, the current price applied to the customer, let's call it Billing Cycle Manager.
A way to track which events we didn't invoice yet
Subscriptions
Create a new table SubscriptionProductPrice which ties together a subscription and a product price. Migration will be needed to move the current price from the subscription table to this new table. In the API, we will then have an output like this:
Create a new table OrderItem which details the line items of an order. An OrderItem will mainly consist of a label, amount and optionally a product price1.
Migration will need to create OrderItem for each existing order. Two solutions:
Simple: create one item per order with the currently set ProductPrice.
Complex: pull all line items from Stripe, including proration and credits for full history.
Billing Cycle Manager
During a Cycle
Whenever an event comes in, the billing cycle manager should be triggered to see if this event is associated with an active customer's subscription.
If that's the case, then it should add a new record. It could look like the following:
ID
Start Date
End Date
Customer ID
Price ID
Direction
Event ID
Order Item ID
R1
2025-01-01T00:00:00
2025-01-01T00:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_1
NULL
R2
2025-01-01T01:00:00
2025-01-01T01:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_2
NULL
R3
2025-01-01T02:00:00
2025-01-01T02:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_3
NULL
Note
For metered usage events, the Start Date and End Date are identical, representing a point-in-time occurrence. This table design supports both metered usage (point-in-time) and fixed pricing periods (time ranges) with a consistent schema. Fixed pricing periods will be detailed in the next section.
If the price changes during the cycle (because the customer upgraded/downgraded their subscription), then new events will be added with the new price:
ID
Start Date
End Date
Customer ID
Price ID
Direction
Event ID
Order Item ID
R1
2025-01-01T00:00:00
2025-01-01T00:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_1
NULL
R2
2025-01-01T01:00:00
2025-01-01T01:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_2
NULL
R3
2025-01-01T02:00:00
2025-01-01T02:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_3
NULL
R4
2025-01-10T00:00:00
2025-01-10T00:00:00
CUSTOMER_1
METERED_PRICE_2
DEBIT
EVENT_4
NULL
At the End of a Cycle
When it's time to compute the invoice for a customer, the billing cycle manager should be triggered to compute the usage-based amount.
Query all the events that are not yet invoiced, i.e., the ones with a NULL Order Item ID.
Compute the meter value, grouped by metered price
Compute the amount for each meter value
In the end, we'll have one line item per metered price.
ID
Start Date
End Date
Customer ID
Price ID
Direction
Event ID
Order Item ID
R1
2025-01-01T00:00:00
2025-01-01T00:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_1
ORDER_ITEM_1
R2
2025-01-01T01:00:00
2025-01-01T01:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_2
ORDER_ITEM_1
R3
2025-01-01T02:00:00
2025-01-01T02:00:00
CUSTOMER_1
METERED_PRICE_1
DEBIT
EVENT_3
ORDER_ITEM_1
R4
2025-01-10T00:00:00
2025-01-10T00:00:00
CUSTOMER_1
METERED_PRICE_2
DEBIT
EVENT_4
ORDER_ITEM_2
By doing this, we'll be able to show precisely on the invoice what meter and what price was applied:
Meter 1 — 30 units — $0.01/unit — $0.30
Meter 2 — 10 units — $0.05/unit — $0.50
Generalization to fixed pricing
A challenge we'll also face is to manage subscription cycles ourselves. Currently, it's done by Stripe but we want to move that in-house.
This manager should be able to help us manage fixed prices cycles too, given the following rules:
A cycle is always prepaid at the beginning of a cycle.
If a plan changes during the cycle, the system should:
Add the amount due for the remaining days of the cycle for this new plan
Subtract the amount already paid for the remaining days of the cycle for the old plan.
The idea is to have system events that'll trigger the billing cycle manager when a new cycle starts or when a plan changes, besides giving a clear way for users to track billing cycles from the dashboard.
At the start of a cycle
At the start of a cycle, a billing_cycle event is created. This event will trigger the billing cycle manager to create a new record like the following:
ID
Start Date
End Date
Customer ID
Price ID
Direction
Event ID
Order Item ID
R1
2025-01-01T00:00:00
2025-02-01T00:00:00
CUSTOMER_1
FIXED_PRICE_1
DEBIT
EVENT_1
NULL
Since we always bill at the beginning of the cycle, another process should trigger the invoice creation and payment processing right after, meaning we'll fill the OrderItem column.
During a cycle
Let's say the customer changes their plan on 2025-01-20. The system should create a plan_change event that will trigger the billing cycle manager to create a new records like the following:
ID
Start Date
End Date
Customer ID
Price ID
Direction
Event ID
Order Item ID
R1
2025-01-01T00:00:00
2025-02-01T00:00:00
CUSTOMER_1
FIXED_PRICE_1
DEBIT
EVENT_1
ORDER_ITEM_1
R2
2025-01-20T00:00:00
2025-02-01T00:00:00
CUSTOMER_1
FIXED_PRICE_1
CREDIT
EVENT_2
NULL
R3
2025-01-20T00:00:00
2025-02-01T00:00:00
CUSTOMER_1
FIXED_PRICE_2
DEBIT
EVENT_2
NULL
We see the event trigger two record:
A credit for the unused time of the old plan
A debit for the remaining time of the new plan
gantt
title Subscription Periods
dateFormat YYYY-MM-DD
axisFormat %b %d
Original Plan (DEBIT) :done, 2025-01-01, 2025-02-01
Unused Period (CREDIT) :crit, 2025-01-20, 2025-02-01
New Plan (DEBIT) :active, 2025-01-20, 2025-02-01
Loading
At the start of the next cycle
The billing_cycle event is triggered again, and we add a new record:
ID
Start Date
End Date
Customer ID
Price ID
Direction
Event ID
Order Item ID
R1
2025-01-01T00:00:00
2025-02-01T00:00:00
CUSTOMER_1
FIXED_PRICE_1
DEBIT
EVENT_1
ORDER_ITEM_1
R2
2025-01-20T00:00:00
2025-02-01T00:00:00
CUSTOMER_1
FIXED_PRICE_1
CREDIT
EVENT_2
NULL
R3
2025-01-20T00:00:00
2025-02-01T00:00:00
CUSTOMER_1
FIXED_PRICE_2
DEBIT
EVENT_2
NULL
R4
2025-02-01T00:00:00
2025-03-01T00:00:00
CUSTOMER_1
FIXED_PRICE_2
DEBIT
EVENT_3
NULL
The invoice process is triggered and will compute the line items for the unpaid events.
Key points
Events ingestion system saves all the actions done by the customer on their subscription plan (cycle renewal, plan changes, usage, etc.)
The billing cycle manager is triggered by these events to create records to keep track of customer's billing on a time period.
The invoicing process use the billing cycle manager to compute the final invoice for the customer.
Footnotes
Optional because we might have line items that are not tied to a product price, like a proration or credit. ↩
The text was updated successfully, but these errors were encountered:
Design document for metered pricing, aka usage-based pricing
What we have
What we need
Note
We currently focus solely on unit pricing before expanding to tiered or volume pricing.
Subscriptions
Create a new table
SubscriptionProductPrice
which ties together a subscription and a product price. Migration will be needed to move the current price from the subscription table to this new table. In the API, we will then have an output like this:Order line items
Create a new table
OrderItem
which details the line items of an order. AnOrderItem
will mainly consist of a label, amount and optionally a product price1.Migration will need to create
OrderItem
for each existing order. Two solutions:Billing Cycle Manager
During a Cycle
Whenever an event comes in, the billing cycle manager should be triggered to see if this event is associated with an active customer's subscription.
If that's the case, then it should add a new record. It could look like the following:
Note
For metered usage events, the Start Date and End Date are identical, representing a point-in-time occurrence. This table design supports both metered usage (point-in-time) and fixed pricing periods (time ranges) with a consistent schema. Fixed pricing periods will be detailed in the next section.
If the price changes during the cycle (because the customer upgraded/downgraded their subscription), then new events will be added with the new price:
At the End of a Cycle
When it's time to compute the invoice for a customer, the billing cycle manager should be triggered to compute the usage-based amount.
In the end, we'll have one line item per metered price.
By doing this, we'll be able to show precisely on the invoice what meter and what price was applied:
Generalization to fixed pricing
A challenge we'll also face is to manage subscription cycles ourselves. Currently, it's done by Stripe but we want to move that in-house.
This manager should be able to help us manage fixed prices cycles too, given the following rules:
The idea is to have system events that'll trigger the billing cycle manager when a new cycle starts or when a plan changes, besides giving a clear way for users to track billing cycles from the dashboard.
At the start of a cycle
At the start of a cycle, a
billing_cycle
event is created. This event will trigger the billing cycle manager to create a new record like the following:Since we always bill at the beginning of the cycle, another process should trigger the invoice creation and payment processing right after, meaning we'll fill the
OrderItem
column.During a cycle
Let's say the customer changes their plan on 2025-01-20. The system should create a
plan_change
event that will trigger the billing cycle manager to create a new records like the following:We see the event trigger two record:
At the start of the next cycle
The
billing_cycle
event is triggered again, and we add a new record:The invoice process is triggered and will compute the line items for the unpaid events.
Key points
Footnotes
Optional because we might have line items that are not tied to a product price, like a proration or credit. ↩
The text was updated successfully, but these errors were encountered: