Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metered pricing #5114

Open
frankie567 opened this issue Feb 26, 2025 · 0 comments
Open

Metered pricing #5114

frankie567 opened this issue Feb 26, 2025 · 0 comments
Assignees

Comments

@frankie567
Copy link
Member

frankie567 commented Feb 26, 2025

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:

{
  "id": "SUBSCRIPTION_1",
  // ...
  "prices": [
    {
      "id": "PRODUCT_PRICE_1"
      // ...
    },
    {
      "id": "PRODUCT_PRICE_2"
      // ...
    }
  ]
}

Order line items

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:

  1. Simple: create one item per order with the currently set ProductPrice.
  2. 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.

  1. Query all the events that are not yet invoiced, i.e., the ones with a NULL Order Item ID.
  2. Compute the meter value, grouped by metered price
  3. 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

  1. Optional because we might have line items that are not tied to a product price, like a proration or credit.

@github-project-automation github-project-automation bot moved this to Backlog in Backlog Feb 26, 2025
@frankie567 frankie567 self-assigned this Feb 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Backlog
Status: No status
Development

No branches or pull requests

1 participant