Skip to content

feat: charges advancer - #4065

Merged
turip merged 3 commits into
mainfrom
feat/charge-advancer
Apr 2, 2026
Merged

turip merged 3 commits into
mainfrom
feat/charge-advancer

Conversation

@turip

@turip turip commented Apr 2, 2026 •

Copy link
Copy Markdown
Member

Overview

  • Adds a charges autoadvancer (openmeter/billing/charges/worker/advance/) that periodically lists all customers with non-final charges past their
    advance_after deadline and advances them sequentially, collecting and logging errors along the way.
  • Introduces AdvanceChargesEvent in openmeter/billing/charges/events.go and wires it into the billing worker so that when charges are enabled,
    the event triggers async charge advancement for a specific customer.
  • Extends the charges search adapter and service with a paginated ListCustomersToAdvance method that queries distinct (namespace, customer_id)
    pairs from the ChargesSearchV1 view filtered by advance_after <= now and non-terminal statuses.
  • Wires the ChargesAutoAdvancer into the jobs application via Wire DI, adds a minute-interval cron job (conditional on credits being enabled),
    and provides CLI commands (advance-charges list, advance-charges customer, advance-charges all) for manual operation.
  • The billing worker's ChargesService dependency is optional — when nil (credits disabled), the AdvanceChargesEvent handler no-ops gracefully,
    keeping the existing invoice advancement path completely unchanged.

Notes for reviewer

Summary by CodeRabbit

  • New Features

    • Added an advance-charges CLI with list, customer, and all commands (supports namespace scoping).
    • Added a scheduled job to automatically advance charges every minute when enabled.
    • Added asynchronous event-based processing to handle charge advancement.
  • Tests

    • Added integration tests covering customer listing eligibility, deduplication, ordering, pagination, and namespace filtering.

@coderabbitai

coderabbitai Bot commented Apr 2, 2026 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% 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 title 'feat: charges advancer' directly summarizes the main change: implementing a charges autoadvancer system with all its supporting infrastructure.

✏️ 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/charge-advancer

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 marked this pull request as ready for review April 2, 2026 13:30
@turip
turip requested a review from a team as a code owner April 2, 2026 13:30
@turip
turip force-pushed the feat/charge-advancer branch from 2dd2d2f to 3c0df79 Compare April 2, 2026 13:30
@turip turip added the release-note/misc Miscellaneous changes label Apr 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
cmd/jobs/quickstart/cronjobs.go (1)

85-101: Consider guarding against overlapping charge-advance runs.

Small reliability/perf hardening: if one run takes longer than a minute, the next scheduled run can start before the previous one finishes.

Proposed change
 import (
 	"log/slog"
 	"os"
+	"sync/atomic"
 	"time"
@@
 		namespaces := []string{"default"}
 		batchSize := 10
+		var chargesAdvanceRunning atomic.Bool
@@
 		if internal.App.ChargesAutoAdvancer != nil {
 			_, err = s.NewJob(
 				gocron.DurationJob(time.Minute),
 				gocron.NewTask(func() {
+					if !chargesAdvanceRunning.CompareAndSwap(false, true) {
+						slog.Warn("Skipping charge advancement; previous run still in progress")
+						return
+					}
+					defer chargesAdvanceRunning.Store(false)
+
 					slog.Info("Advancing charges")
 
 					err := internal.App.ChargesAutoAdvancer.All(cmd.Context(), namespaces)
 					if err != nil {
 						slog.Error("Error advancing charges", "error", err)
 					}
 				}),
 			)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/jobs/quickstart/cronjobs.go` around lines 85 - 101, The scheduled
charge-advance job created with s.NewJob (using gocron.DurationJob(time.Minute)
and calling internal.App.ChargesAutoAdvancer.All) can overlap if a run exceeds
one minute; add a guard so only one run executes at a time (e.g., a
package-level sync.Mutex or an atomic boolean) around the body that calls
ChargesAutoAdvancer.All to skip or defer concurrent executions; update the
s.NewJob task to acquire the guard before calling
internal.App.ChargesAutoAdvancer.All and release it afterwards, and ensure
errors are still logged when skipped or when All returns an error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openmeter/billing/charges/service.go`:
- Around line 203-208: ListCustomersToAdvanceInput.Validate currently only
checks AdvanceAfterLTE but allows a partially filled pagination.Page that causes
negative offsets in adapter/search.go when calling page.Offset()/page.Limit();
update ListCustomersToAdvanceInput.Validate to also validate the Page when it's
partially populated (e.g., if Page.PageSize>0 or Page.PageNumber>0) by invoking
the pagination.Page.Validate() (or equivalent) so only fully valid pages are
accepted while preserving zero-value page support for internal non-paginated
calls.

In `@openmeter/billing/worker/worker.go`:
- Around line 195-200: The AdvanceChargesEvent path currently forwards events
straight to w.asyncAdvanceChargesHandler.Handle, skipping the namespace-lock
short-circuit used elsewhere; update the grouphandler.NewGroupEventHandler
callback for AdvanceChargesEvent to first check the namespace lock (use the same
check used by other handlers, e.g., the existing namespace lock predicate or
method on w that other handlers use) and return nil when the namespace is locked
so locked namespaces are skipped instead of retried; ensure you keep the
reference to w.asyncAdvanceChargesHandler.Handle for normal processing when the
namespace is not locked.

---

Nitpick comments:
In `@cmd/jobs/quickstart/cronjobs.go`:
- Around line 85-101: The scheduled charge-advance job created with s.NewJob
(using gocron.DurationJob(time.Minute) and calling
internal.App.ChargesAutoAdvancer.All) can overlap if a run exceeds one minute;
add a guard so only one run executes at a time (e.g., a package-level sync.Mutex
or an atomic boolean) around the body that calls ChargesAutoAdvancer.All to skip
or defer concurrent executions; update the s.NewJob task to acquire the guard
before calling internal.App.ChargesAutoAdvancer.All and release it afterwards,
and ensure errors are still logged when skipped or when All returns an error.
🪄 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: 53ab813a-19ac-4cdd-aeea-52c72bd92f4b

📥 Commits

Reviewing files that changed from the base of the PR and between 729f8e0 and 3c0df79.

📒 Files selected for processing (15)
  • app/common/billing.go
  • app/common/openmeter_billingworker.go
  • cmd/jobs/billing/advancecharges/advancecharges.go
  • cmd/jobs/billing/billing.go
  • cmd/jobs/internal/wire.go
  • cmd/jobs/internal/wire_gen.go
  • cmd/jobs/quickstart/cronjobs.go
  • openmeter/billing/charges/adapter.go
  • openmeter/billing/charges/adapter/search.go
  • openmeter/billing/charges/events.go
  • openmeter/billing/charges/service.go
  • openmeter/billing/charges/service/list.go
  • openmeter/billing/charges/worker/advance/advance.go
  • openmeter/billing/charges/worker/asyncadvance/asyncadvance.go
  • openmeter/billing/worker/worker.go

Comment thread openmeter/billing/charges/adapter/search.go
Comment thread openmeter/billing/charges/service.go
Comment thread openmeter/billing/worker/worker.go
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@turip
turip enabled auto-merge (squash) April 2, 2026 14:19
@turip
turip merged commit a6bfcf1 into main Apr 2, 2026
24 of 25 checks passed
@turip
turip deleted the feat/charge-advancer branch April 2, 2026 14:22

This branch was previously deployed

1 inactive deployment
dev — 96cde50b Deployed Apr 2, 2026 by turip via Migration Checks #13157
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants