Skip to content

feat(pricing): Convert usage into each ServicePricing's pricing unit - #29

Merged
JoseSzycho merged 1 commit into
mainfrom
feat/unit-conversion
Oct 19, 2026
Merged

JoseSzycho merged 1 commit into
mainfrom
feat/unit-conversion

Conversation

@JoseSzycho

Copy link
Copy Markdown
Collaborator

Why

Offers price memory per GiB-second and egress per GB, but the meters count bytes. The provider sent the per-GiB rate unchanged, so OpenMeter charged it per byte: Datum's staging bill for the current period is about $1.97B. Invoices go to Stripe on Nov 10.

How

  • Usage is converted, not the price. A ServicePricing's unitConversion (feat(pricing): Convert usage into the pricing unit billing#124, billing v0.4.2) becomes the rate card's unit_config. OpenMeter divides the usage by the factor before pricing, so rates and tier limits stay as the Offer states them, and invoices read 50 x $0.01 per GiB-second instead of byte counts times a per-byte price.
  • Plans go through OpenMeter's v3 API. v1 can't carry unit_config: it hides such plans from listings and refuses to read, change or migrate to them. A subscription moving onto a converting plan uses v3 change. Everything else stays on v1. There's no v3 Go client, so internal/openmeter/v3.go is a small HTTP helper using OpenMeter's generated v3 types.
  • The plan's change-detection hash moved into the plan description. v3 stores plan labels but never returns them; a label made the provider publish a new version on every reconcile (caught by the e2e).
  • UsageQuery spend reads each subscription's plan version from v3 and rates it with OpenMeter's rating code, conversion included.
  • Dev OpenMeter enables unitConfig. Staging gets the same setting from this bundle, so no infra change is needed.

Rollout notes

  • Every existing plan (only payg-3 on staging) gets one new version, because v3 can't read the old hash. Subscriptions migrate once and the current period splits, with the same rates and nothing billed twice.
  • A subscription moved onto a converting plan loses its metadata (v3 labels can't hold /). It's tracing only; nothing reads it.
  • Accounts don't get converted prices until service-catalog fans out unitConversion and they move to a new Offer (migrateFromOffer).

Tests

  • Unit tests and lint pass.
  • e2e invoice (green): adds a byte-seconds meter priced per GiB-second. 53,687,091,200 byte-seconds must bill 50 at $0.01. Every line's billed quantity, unit price and Stripe line are checked against the Offer. Total $3.40.
  • e2e offer (green): its checks now read the rate card description and the plan's hash.

🤖 Generated with Claude Code

Offers price memory per GiB-second and egress per GB, but meters count
bytes, and the plan sent the per-GiB rate unchanged, so OpenMeter charged
it per byte (Datum's staging bill for the period is $1.97B).

A ServicePricing's unitConversion (billing v0.4.2) becomes the rate card's
unit_config: OpenMeter divides the usage by the factor before pricing, so
rates and tier bounds stay as the Offer states them and invoices read
"50 x $0.01" per GiB-second.

OpenMeter's v1 API cannot carry unit_config (it hides such plans from
listings and refuses to read, change or migrate to them), so plans are
written and read through v3, and a subscription moving onto a converting
plan changes through v3. v3 never returns plan labels, so the spec hash
lives in the plan description. UsageQuery spend reads the plan's rate
cards from v3 and rates with the conversion. Dev OpenMeter enables
unitConfig; the invoice e2e bills a byte-seconds meter per GiB-second.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JoseSzycho
JoseSzycho merged commit 9f1aa8a into main Oct 19, 2026
7 of 8 checks passed
@JoseSzycho JoseSzycho self-assigned this Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants