Skip to content

feat: fix prorating credit corrections#4363

Open
turip wants to merge 14 commits into
mainfrom
feat/subs-sync-test-coverage
Open

feat: fix prorating credit corrections#4363
turip wants to merge 14 commits into
mainfrom
feat/subs-sync-test-coverage

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented May 15, 2026

Overview

Extends credit-then-invoice subscription sync coverage and the flat-fee CTI behavior needed by those scenarios.

Code Changes

  • Add sync_credittheninvoice_test.go as the CTI subscription-sync suite, with converted scenarios for happy path provisioning, prorating, gathering/draft/issued invoice flows, aligned invoicing/cancellation, one-time fees, rate-card tax propagation, instant billing, usage-based updates, and ledger balance expectations.
  • Keep the remaining copied CTI scenarios present but skipped where they still need conversion, and keep TestDiscountSynchronization skipped for the known flat-fee CTI discount allocation issue.
  • Add shared subscription-sync charge assertion helpers on SuiteBase, including exact charge count matching, expected charge status, settlement mode, periods, invoice-at where applicable, tax config, and subscription linkage.
  • Add flat-fee CTI zero-amount handling: zero-amount charges are still tracked as charges, but they do not materialize billable gathering invoice lines.
  • Add flat-fee CTI mutable standard-line reconciliation so charge runs, credit allocations/corrections, detailed lines, and totals are reconciled when a mutable draft line is rebuilt from an updated intent.
  • Add the CreditNotesSupportedByLineUpdater default feature flag for flat-fee CTI immutable-proration behavior. With the default unsupported path, immutable prorated lines record the unsupported-credit-note warning without creating replacement billable work for the already-invoiced period.
  • Add charges service tests for zero-amount CTI flat fees and immutable proration with credit notes both available and unavailable.
  • Move TestUncollectableCollection from subscription sync tests into test/billing/collection_test.go.

Notes for Reviewer

  • Manual edit/delete/ignore sync scenarios are intentionally absent from the CTI suite because charge-managed items cannot be manually edited through invoice-line sync.
  • The original generic rate-card tax testcase is represented as CTI-specific TestRateCardTaxSyncFlatFee and TestRateCardTaxSyncUsageBased cases.
  • TestDiscountSynchronization remains skipped because flat-fee CTI currently allocates credits before percentage discounts are applied, which causes the discounted line to fail with credits-not-consumed validation.

Validation

  • go test -count=1 -tags=dynamic ./openmeter/billing/worker/subscriptionsync/service -run '^TestCreditThenInvoiceScenarios$'
  • make lint-go-fast

Summary by CodeRabbit

  • New Features

    • Toggle to control credit-note support for flat-fee billing behavior.
  • Bug Fixes

    • Zero-amount charges no longer create gathering or invoice lines.
  • Improvements

    • Targeted credit reconciliation with timestamped allocation/reconciliation.
    • Improved reconciliation of invoice lines to updated intents; clearer immutable vs mutable update handling.
  • Tests

    • New and expanded tests for zero-amount, immutable/mutable proration, reconciliation flows.
  • Documentation

    • Added guidance on helper usage and a utility library usage note.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7bc1a3a8-cf7d-4ce0-a67a-4a13501c3e1d

📥 Commits

Reviewing files that changed from the base of the PR and between f0b63db and 90be488.

📒 Files selected for processing (18)
  • .agents/skills/samber-lo/SKILL.md
  • AGENTS.md
  • openmeter/billing/charges/features.go
  • openmeter/billing/charges/flatfee/service/create.go
  • openmeter/billing/charges/flatfee/service/creditheninvoice.go
  • openmeter/billing/charges/flatfee/service/lineengine.go
  • openmeter/billing/charges/flatfee/service/realizations/correct.go
  • openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go
  • openmeter/billing/charges/flatfee/service/service.go
  • openmeter/billing/charges/flatfee/service/statemachine.go
  • openmeter/billing/charges/flatfee/service/triggers.go
  • openmeter/billing/charges/service/base_test.go
  • openmeter/billing/charges/service/invoicable_test.go
  • openmeter/billing/worker/subscriptionsync/service/base_test.go
  • openmeter/billing/worker/subscriptionsync/service/sync_credittheninvoice_test.go
  • openmeter/billing/worker/subscriptionsync/service/sync_test.go
  • test/billing/collection_test.go
  • test/credits/credit_then_invoice_test.go
✅ Files skipped from review due to trivial changes (2)
  • openmeter/billing/charges/features.go
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • openmeter/billing/charges/flatfee/service/triggers.go
  • openmeter/billing/charges/flatfee/service/create.go
  • openmeter/billing/charges/service/base_test.go
  • openmeter/billing/charges/flatfee/service/service.go
  • test/billing/collection_test.go
  • openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go
  • openmeter/billing/charges/flatfee/service/lineengine.go
  • openmeter/billing/worker/subscriptionsync/service/base_test.go
  • openmeter/billing/charges/service/invoicable_test.go
  • openmeter/billing/worker/subscriptionsync/service/sync_test.go
  • openmeter/billing/charges/flatfee/service/realizations/correct.go
  • openmeter/billing/charges/flatfee/service/creditheninvoice.go
  • test/credits/credit_then_invoice_test.go

📝 Walkthrough

Walkthrough

Adds a CreditNotes feature flag and threads it through flat-fee service/state machines; implements credit reconciliation and standard-line-to-intent reconciliation; changes immutable-proration patching and zero-amount behavior to avoid creating gathering lines; and adds tests, helpers, and docs.

Changes

Credit-Notes Support and Immutable Proration Reconciliation

Layer / File(s) Summary
Feature flag infrastructure and wiring
openmeter/billing/charges/features.go, openmeter/billing/charges/flatfee/service/service.go, openmeter/billing/charges/flatfee/service/lineengine.go, openmeter/billing/charges/flatfee/service/triggers.go
CreditNotesSupportedByLineUpdater feature flag is added and threaded through service initialization and state-machine/line-engine plumbing; service stores the flag atomically and exposes a test setter.
State-machine config & helpers
openmeter/billing/charges/flatfee/service/statemachine.go
StateMachineConfig/stateMachine gain CreditNotesSupported and helper predicates for inside-service-period / zero vs non-zero amount checks.
Credit reconciliation API
openmeter/billing/charges/flatfee/service/realizations/correct.go
New ReconcileCreditRealizationsInput, ReconcileCreditRealizationsResult, and Service.ReconcileCredits reconcile run credit realizations to a monetary target: positive delta allocates credits, negative delta creates corrections, zero delta is a no-op.
Standard line reconciliation API
openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go
New ReconcileStandardLineToIntentInput, ReconcileStandardLineToIntentResult, and Service.ReconcileStandardLineToIntent validate identifiers, compute prorated targets, reconcile credits, regenerate/merge detailed lines, persist lines, and update run totals/metadata.
Immutable proration patch generation & zero-amount handling
openmeter/billing/charges/flatfee/service/create.go, openmeter/billing/charges/flatfee/service/creditheninvoice.go
Patch generation includes an immutable-run fallback that emits a delete-line when credit-notes are unsupported; proration is computed from a local normalized intent and applied from inputs; zero-amount charges do not create gathering lines; mutable-run updates reconcile using timestamped allocations (clock.Now()).
Test infra and subscription-sync assertions
openmeter/billing/charges/service/base_test.go, openmeter/billing/worker/subscriptionsync/service/base_test.go, openmeter/billing/charges/service/invoicable_test.go, test/credits/credit_then_invoice_test.go, test/billing/collection_test.go, openmeter/billing/worker/subscriptionsync/service/sync_test.go
Adds/updates tests for immutable proration (conditional on credit-note support), zero-amount handling, shrink/extend/reconcile scenarios; extends test helpers to assert charges for invoice lines and subscription-sync expectations.
Docs & agent guidance
.agents/skills/samber-lo/SKILL.md, AGENTS.md
Adds guidance for samber/lo usage and AGENTS coding rules for small helpers and Validate() error implementations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • tothandras
  • GAlexIHU
  • borbelyr-kong
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: fix prorating credit corrections' directly matches the core objective of implementing flat-fee CTI zero-amount handling and mutable standard-line credit reconciliation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/subs-sync-test-coverage

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 force-pushed the feat/subs-sync-test-coverage branch 2 times, most recently from 03d97fe to c1176de Compare May 15, 2026 14:35
@turip turip marked this pull request as ready for review May 15, 2026 14:36
@turip turip requested a review from a team as a code owner May 15, 2026 14:36
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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
test/billing/collection_test.go (1)

69-69: ⚡ Quick win

Use test-scoped context here.

Line 69 should use s.T().Context() so cancellation/lifecycle stays tied to the test harness.

Suggested fix
-	ctx := context.Background()
+	ctx := s.T().Context()
As per coding guidelines: "In tests, prefer `t.Context()` when a `testing.T` or `testing.TB` is available instead of using `context.Background()` to keep cancellation and lifecycle tied to the test harness."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/billing/collection_test.go` at line 69, Replace the test-scoped
background context with the test harness context: instead of creating ctx :=
context.Background() in the test (the ctx variable in collection_test.go), call
s.T().Context() to derive the context tied to the testing.T lifecycle so
cancellations/timeouts propagate correctly from the test runner.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openmeter/billing/charges/flatfee/service/service.go`:
- Around line 95-97: The setter SetCreditNotesSupportedByLineUpdater currently
writes s.creditNotesSupported concurrently with reads in AdvanceCharge,
TriggerPatch, and newStateMachineForStandardLine, risking a data race; either
restrict this setter to startup/tests (documenting that it must not be called at
runtime) or make the field safe for concurrent access by replacing the bool with
a concurrency-safe primitive (e.g., use sync/atomic with an atomic.Bool or
protect access with a sync.RWMutex on the service struct) and update all
reads/writes (in AdvanceCharge, TriggerPatch, newStateMachineForStandardLine,
and SetCreditNotesSupportedByLineUpdater) to use the chosen synchronization
method.

In `@openmeter/billing/worker/subscriptionsync/service/base_test.go`:
- Around line 534-535: The test currently asserts charge.Intent.FeatureKey
equals itemKey but feature keys can differ due to custom mappings; update the
assertion to compare charge.Intent.FeatureKey against the expected feature key
from the rate card/mapping used to build the charge (e.g., derive
expectedFeatureKey from the rate card or productcatalog mapping used in the test
setup) instead of itemKey—replace the itemKey reference in the assertion for
charge.Intent.FeatureKey with that expectedFeatureKey (keep the other
assertions, e.g., productcatalog.NewPriceFrom(...) for price, unchanged).

---

Nitpick comments:
In `@test/billing/collection_test.go`:
- Line 69: Replace the test-scoped background context with the test harness
context: instead of creating ctx := context.Background() in the test (the ctx
variable in collection_test.go), call s.T().Context() to derive the context tied
to the testing.T lifecycle so cancellations/timeouts propagate correctly from
the test runner.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 782a300b-251c-4b6a-9f28-3165c4ee4c4a

📥 Commits

Reviewing files that changed from the base of the PR and between ff255ce and c1176de.

📒 Files selected for processing (15)
  • openmeter/billing/charges/features.go
  • openmeter/billing/charges/flatfee/service/create.go
  • openmeter/billing/charges/flatfee/service/creditheninvoice.go
  • openmeter/billing/charges/flatfee/service/lineengine.go
  • openmeter/billing/charges/flatfee/service/realizations/correct.go
  • openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go
  • openmeter/billing/charges/flatfee/service/service.go
  • openmeter/billing/charges/flatfee/service/statemachine.go
  • openmeter/billing/charges/flatfee/service/triggers.go
  • openmeter/billing/charges/service/base_test.go
  • openmeter/billing/charges/service/invoicable_test.go
  • openmeter/billing/worker/subscriptionsync/service/base_test.go
  • openmeter/billing/worker/subscriptionsync/service/sync_credittheninvoice_test.go
  • openmeter/billing/worker/subscriptionsync/service/sync_test.go
  • test/billing/collection_test.go

Comment thread openmeter/billing/charges/flatfee/service/service.go Outdated
Comment thread openmeter/billing/worker/subscriptionsync/service/base_test.go Outdated
@turip turip changed the title Feat/subs sync test coverage feat: fix prorating credit corrections May 15, 2026
@turip turip added release-note/bug-fix Release note: Bug Fixes area/billing labels May 15, 2026
Comment thread openmeter/billing/charges/flatfee/service/realizations/correct.go
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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openmeter/billing/charges/flatfee/service/creditheninvoice.go`:
- Around line 299-333: The current short-circuit in the s.CreditNotesSupported
== false branch exits on any immutable amount change which drops billable work
for extensions; change the logic to only short-circuit for the already-invoiced
overlap: compute the overlap between currentRun (line/invoice period) and the
incoming change and compare the prorationed amounts for that overlap rather than
the whole charge (use currentRun.LineID/currentRun.InvoiceID and billing.LineID
semantics to identify the invoiced period); if the overlap's
AmountAfterProration differs, emit the delete patch via
s.AddInvoicePatch(invoiceupdater.NewDeleteLinePatch(...)) and set
s.Charge.State.AmountAfterProration only for the overlap handling, but do not
return early when there is additional non-invoiced tail—allow creation of
replacement billable work for the newly added tail (i.e., only short-circuit
when the overlap would otherwise be corrected, not when the overall new amount
includes extra tail).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b185ede0-c310-4bf4-b563-3f71ac84c583

📥 Commits

Reviewing files that changed from the base of the PR and between c1176de and 9acfc3a.

📒 Files selected for processing (3)
  • openmeter/billing/charges/flatfee/service/creditheninvoice.go
  • openmeter/billing/worker/subscriptionsync/service/base_test.go
  • test/credits/credit_then_invoice_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • openmeter/billing/worker/subscriptionsync/service/base_test.go

Comment thread openmeter/billing/charges/flatfee/service/creditheninvoice.go Outdated
@turip turip force-pushed the feat/subs-sync-test-coverage branch from f0b63db to 90be488 Compare May 15, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants