Skip to content

chore: get rid of billing.Period#4184

Merged
turip merged 1 commit into
mainfrom
chore/get-rid-of-billing-period
Apr 20, 2026
Merged

chore: get rid of billing.Period#4184
turip merged 1 commit into
mainfrom
chore/get-rid-of-billing-period

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented Apr 20, 2026

Overview

next patches will require it.

Notes for reviewer

Summary by CodeRabbit

  • Refactor
    • Updated invoice and billing period representations to use a standardized period type with From/To field naming instead of Start/End across invoicing, rating, and subscription synchronization systems.
    • Consolidated period handling throughout invoices, service periods, and billing calculations for improved consistency.

@turip turip requested a review from a team as a code owner April 20, 2026 14:35
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

This PR migrates the billing system from billing.Period (with Start/End fields) to timeutil.ClosedPeriod (with From/To fields). The change propagates across invoice types, adapters, services, workers, Stripe integration, and tests, replacing field names consistently throughout the codebase.

Changes

Cohort / File(s) Summary
Period Type Definition & Removal
openmeter/billing/invoiceline.go
Deleted the Period struct and all associated methods (Validate, Truncate, Equal, IsEmpty, Contains, Duration, ToClosedPeriod), removing this custom type entirely.
Core Invoice Types
openmeter/billing/stdinvoice.go, openmeter/billing/stdinvoiceline.go, openmeter/billing/invoicedetailedline.go, openmeter/billing/invoicelinesplitgroup.go
Updated exported struct fields from *Period/Period to *timeutil.ClosedPeriod/timeutil.ClosedPeriod, changing Start/End to From/To throughout.
Invoice Adapters
openmeter/billing/adapter/invoice.go, openmeter/billing/adapter/invoicelinesplitgroup.go, openmeter/billing/adapter/stdinvoicelines.go, openmeter/billing/adapter/stdinvoicelinemapper.go
Updated period mapping between API/domain models and database persistence, reading/writing from From/To fields and converting mapPeriodFromDB to return timeutil.ClosedPeriod.
Invoice Services
openmeter/billing/service/invoice.go, openmeter/billing/service/invoicecalc/period.go, openmeter/billing/service/invoicecalc/details.go, openmeter/billing/service/quantitysnapshot.go
Updated invoice calculation and aggregation logic to use From/To fields for period comparisons and boundary tracking.
Stripe Integration
openmeter/app/stripe/appinvoice.go
Updated getPeriod to construct InvoiceItemPeriodParams using line.ServicePeriod.From.Unix() and line.ServicePeriod.To.Unix().
HTTP API Layer
openmeter/billing/httpdriver/invoice.go, openmeter/billing/httpdriver/invoiceline.go, openmeter/billing/httpdriver/invoice_test.go
Updated mapPeriodToAPI to accept *timeutil.ClosedPeriod and read From/To fields; updated line-to-API mappings accordingly.
Charging Services
openmeter/billing/charges/flatfee/service/invoice.go, openmeter/billing/charges/usagebased/service/run/invoice.go, openmeter/billing/charges/usagebased/service/run/payment.go
Removed .ToClosedPeriod() conversions and now pass line.Period directly to handlers and adapters.
Line Hierarchy & Gathering
openmeter/billing/gatheringinvoice.go, openmeter/billing/lineengine/splitlinegroup.go
Updated period construction in gathering line/split line hierarchy to use timeutil.ClosedPeriod{From, To}.
Target State & Subscription Sync
openmeter/billing/worker/subscriptionsync/service/targetstate/phaseiterator.go, openmeter/billing/worker/subscriptionsync/service/targetstate/phaseiterator_test.go, openmeter/billing/worker/subscriptionsync/service/targetstate/targetstate.go, openmeter/billing/worker/subscriptionsync/service/targetstate/targetstateitem.go
Updated SubscriptionItemWithPeriods type and related logic to use timeutil.ClosedPeriod fields (From/To); adjusted continuity correction and period advancement logic.
Subscription Sync Service
openmeter/billing/worker/subscriptionsync/service/base_test.go, openmeter/billing/worker/subscriptionsync/service/persistedstate/item.go, openmeter/billing/worker/subscriptionsync/service/reconciler/patch.go, openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge.go, openmeter/billing/worker/subscriptionsync/service/reconciler/patchinvoicelinehierarchy.go, openmeter/billing/worker/subscriptionsync/service/sync_test.go, openmeter/billing/worker/subscriptionsync/service/syncbillinganchor_test.go
Updated patch/intent reconciliation and period boundary comparisons to use From/To; updated test expectations accordingly.
Rating & Pricing Services
openmeter/billing/rating/service/testutil/ubptest.go, openmeter/billing/rating/service/mutator/credits_test.go, openmeter/billing/rating/service/rate/*.go (dynamic, package, tieredgraduated, tieredvolume, unit) test files
Changed TestFullPeriod from billing.Period to timeutil.ClosedPeriod; updated test assertions to compare against testutil.TestFullPeriod directly instead of .ToClosedPeriod().
Charging Service Tests
openmeter/billing/charges/service/creditpurchase_test.go, openmeter/billing/charges/service/invoicable_test.go, openmeter/billing/charges/usagebased/service/run/payment_test.go
Updated test assertions to expect timeutil.ClosedPeriod and removed .ToClosedPeriod() conversion calls.
Notification Integration
openmeter/notification/internal/rule.go
Updated test invoice line Period construction to use timeutil.ClosedPeriod{From, To}.
Integration Tests
test/app/custominvoicing/invocing_test.go, test/app/stripe/invoice_test.go, test/billing/adapter_test.go, test/billing/collection_test.go, test/billing/invoice_test.go, test/billing/suite.go, test/billing/tax_test.go, test/billing/taxcode_dual_write_test.go, test/billing/ubpflatfee_test.go, test/customer/subject.go
Updated all test setup and assertions to construct periods using timeutil.ClosedPeriod{From, To} instead of billing.Period{Start, End}.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

area/billing, release-note/misc

Suggested reviewers

  • GAlexIHU
  • tothandras
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: removing the custom billing.Period type across the codebase in favor of timeutil.ClosedPeriod.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/get-rid-of-billing-period

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@turip turip added release-note/ignore Ignore this change when generating release notes area/billing labels Apr 20, 2026
@turip turip enabled auto-merge (squash) April 20, 2026 14:38
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
openmeter/billing/worker/subscriptionsync/service/sync_test.go (1)

4613-4629: ⚠️ Potential issue | 🟡 Minor

Point this line-period assertion at the created invoice.

This block says it’s checking the standard invoice, but expectLines still receives gatheringInvoice, so a regression in the created invoice’s period mapping could slip through.

Suggested fix
-	s.expectLines(gatheringInvoice, subsView.Subscription.ID, []expectedLine{
+	s.expectLines(invoice, subsView.Subscription.ID, []expectedLine{

As per coding guidelines, "Make sure the tests are comprehensive and cover the changes. Keep a strong focus on unit tests and in-code integration tests."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/worker/subscriptionsync/service/sync_test.go` around lines
4613 - 4629, The test is asserting line periods against gatheringInvoice but
should target the created invoice; update the s.expectLines call to pass the
created invoice variable (e.g., createdInvoice) instead of gatheringInvoice so
the period mapping for the actually-created invoice is validated; keep the same
expectedLine block and subscription ID (subsView.Subscription.ID) and run the
test to confirm it now fails/errs where the regression exists.
🧹 Nitpick comments (2)
openmeter/billing/charges/service/creditpurchase_test.go (1)

587-590: Tiny nit: you could reuse servicePeriod here.

The literal on lines 587-590 is identical to the servicePeriod variable defined at lines 439-442, so you could just assert s.Equal(servicePeriod, line.Period) and dodge the duplicate MustParseTimeInLocation calls. Totally optional — feel free to ignore if you prefer the assertion to spell out the expected values explicitly.

♻️ Proposed tweak
-		s.Equal(timeutil.ClosedPeriod{
-			From: datetime.MustParseTimeInLocation(s.T(), "2026-01-01T00:00:00Z", time.UTC).AsTime(),
-			To:   datetime.MustParseTimeInLocation(s.T(), "2026-02-01T00:00:00Z", time.UTC).AsTime(),
-		}, line.Period)
+		s.Equal(servicePeriod, line.Period)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/charges/service/creditpurchase_test.go` around lines 587 -
590, Replace the duplicated literal ClosedPeriod construction with the existing
servicePeriod variable to avoid redundant MustParseTimeInLocation calls: update
the assertion from s.Equal(timeutil.ClosedPeriod{...}, line.Period) to
s.Equal(servicePeriod, line.Period), referencing the servicePeriod defined
earlier and the line.Period being asserted; this uses the already-parsed times
and removes the duplicate parsing logic.
openmeter/billing/worker/subscriptionsync/service/targetstate/targetstateitem.go (1)

70-73: Tiny nit: you can just pass r.BillingPeriod through. 🪄

Since r.BillingPeriod is now already a timeutil.ClosedPeriod, rebuilding it field-by-field is equivalent to the field itself. Not a correctness issue at all — feel free to ignore if you prefer the explicit form.

✨ Optional simplification
-				BillingPeriod: timeutil.ClosedPeriod{
-					From: r.BillingPeriod.From,
-					To:   r.BillingPeriod.To,
-				},
+				BillingPeriod: r.BillingPeriod,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@openmeter/billing/worker/subscriptionsync/service/targetstate/targetstateitem.go`
around lines 70 - 73, The BillingPeriod construction is redundant: instead of
rebuilding a timeutil.ClosedPeriod from r.BillingPeriod fields, just assign
BillingPeriod: r.BillingPeriod; update the code in targetstateitem.go where the
struct is created (refer to the BillingPeriod field and the variable r) to pass
r.BillingPeriod directly rather than reconstructing a new timeutil.ClosedPeriod
with From/To.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@openmeter/billing/worker/subscriptionsync/service/sync_test.go`:
- Around line 4613-4629: The test is asserting line periods against
gatheringInvoice but should target the created invoice; update the s.expectLines
call to pass the created invoice variable (e.g., createdInvoice) instead of
gatheringInvoice so the period mapping for the actually-created invoice is
validated; keep the same expectedLine block and subscription ID
(subsView.Subscription.ID) and run the test to confirm it now fails/errs where
the regression exists.

---

Nitpick comments:
In `@openmeter/billing/charges/service/creditpurchase_test.go`:
- Around line 587-590: Replace the duplicated literal ClosedPeriod construction
with the existing servicePeriod variable to avoid redundant
MustParseTimeInLocation calls: update the assertion from
s.Equal(timeutil.ClosedPeriod{...}, line.Period) to s.Equal(servicePeriod,
line.Period), referencing the servicePeriod defined earlier and the line.Period
being asserted; this uses the already-parsed times and removes the duplicate
parsing logic.

In
`@openmeter/billing/worker/subscriptionsync/service/targetstate/targetstateitem.go`:
- Around line 70-73: The BillingPeriod construction is redundant: instead of
rebuilding a timeutil.ClosedPeriod from r.BillingPeriod fields, just assign
BillingPeriod: r.BillingPeriod; update the code in targetstateitem.go where the
struct is created (refer to the BillingPeriod field and the variable r) to pass
r.BillingPeriod directly rather than reconstructing a new timeutil.ClosedPeriod
with From/To.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 191d8b56-f616-4938-a727-c820c67f5033

📥 Commits

Reviewing files that changed from the base of the PR and between 3212212 and f2c61f3.

📒 Files selected for processing (55)
  • openmeter/app/stripe/appinvoice.go
  • openmeter/billing/adapter/invoice.go
  • openmeter/billing/adapter/invoicelinesplitgroup.go
  • openmeter/billing/adapter/stdinvoicelinemapper.go
  • openmeter/billing/adapter/stdinvoicelines.go
  • openmeter/billing/charges/flatfee/service/invoice.go
  • openmeter/billing/charges/service/creditpurchase_test.go
  • openmeter/billing/charges/service/invoicable_test.go
  • openmeter/billing/charges/usagebased/service/run/invoice.go
  • openmeter/billing/charges/usagebased/service/run/payment.go
  • openmeter/billing/charges/usagebased/service/run/payment_test.go
  • openmeter/billing/gatheringinvoice.go
  • openmeter/billing/httpdriver/invoice.go
  • openmeter/billing/httpdriver/invoice_test.go
  • openmeter/billing/httpdriver/invoiceline.go
  • openmeter/billing/invoicedetailedline.go
  • openmeter/billing/invoiceline.go
  • openmeter/billing/invoicelinesplitgroup.go
  • openmeter/billing/lineengine/splitlinegroup.go
  • openmeter/billing/rating/service/mutator/credits_test.go
  • openmeter/billing/rating/service/rate/dynamic_test.go
  • openmeter/billing/rating/service/rate/package_test.go
  • openmeter/billing/rating/service/rate/tieredgraduated_test.go
  • openmeter/billing/rating/service/rate/tieredvolume_test.go
  • openmeter/billing/rating/service/rate/unit_test.go
  • openmeter/billing/rating/service/testutil/ubptest.go
  • openmeter/billing/service/invoice.go
  • openmeter/billing/service/invoicecalc/details.go
  • openmeter/billing/service/invoicecalc/period.go
  • openmeter/billing/service/quantitysnapshot.go
  • openmeter/billing/stdinvoice.go
  • openmeter/billing/stdinvoice_test.go
  • openmeter/billing/stdinvoiceline.go
  • openmeter/billing/worker/subscriptionsync/service/base_test.go
  • openmeter/billing/worker/subscriptionsync/service/persistedstate/item.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/invoiceupdater/patch.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchinvoicelinehierarchy.go
  • openmeter/billing/worker/subscriptionsync/service/sync_test.go
  • openmeter/billing/worker/subscriptionsync/service/syncbillinganchor_test.go
  • openmeter/billing/worker/subscriptionsync/service/targetstate/phaseiterator.go
  • openmeter/billing/worker/subscriptionsync/service/targetstate/phaseiterator_test.go
  • openmeter/billing/worker/subscriptionsync/service/targetstate/targetstate.go
  • openmeter/billing/worker/subscriptionsync/service/targetstate/targetstateitem.go
  • openmeter/notification/internal/rule.go
  • test/app/custominvoicing/invocing_test.go
  • test/app/stripe/invoice_test.go
  • test/billing/adapter_test.go
  • test/billing/collection_test.go
  • test/billing/invoice_test.go
  • test/billing/suite.go
  • test/billing/tax_test.go
  • test/billing/taxcode_dual_write_test.go
  • test/billing/ubpflatfee_test.go
  • test/customer/subject.go
💤 Files with no reviewable changes (1)
  • openmeter/billing/invoiceline.go

@turip turip merged commit 4516800 into main Apr 20, 2026
25 of 27 checks passed
@turip turip deleted the chore/get-rid-of-billing-period branch April 20, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing release-note/ignore Ignore this change when generating release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants