Skip to content

feat(ledger): transaction listing API#4147

Merged
GAlexIHU merged 3 commits intomainfrom
feat/transaction-listing
Apr 16, 2026
Merged

feat(ledger): transaction listing API#4147
GAlexIHU merged 3 commits intomainfrom
feat/transaction-listing

Conversation

@GAlexIHU
Copy link
Copy Markdown
Contributor

@GAlexIHU GAlexIHU commented Apr 15, 2026

Overview

  • extra annotations on transactions (so we can filter by them)
  • page based transaction listing API (long-term goal is to change public API to be cursor based, will likely be an increment after discussions)
  • add cursored point-in-time balance evaluation
  • implement handlers through temp customerbalance facade

Notes for reviewer

  • balance and account APIs will be completely refactored with better package boundaries (eventually)

Summary by CodeRabbit

  • New Features

    • Paginated customer credit transactions endpoint with type and currency filters.
    • Transactions show available balance before/after and surface charge/subscription/feature labels.
    • Historical balance queries support cursor-scoped views (balance as-of a transaction).
  • Chores

    • Server/router now wires ledger/account resolver and respects the credits feature flag.
    • Ledger/collector flows and transaction commits now propagate annotations so metadata is retained.

@GAlexIHU GAlexIHU requested a review from a team as a code owner April 15, 2026 14:54
@GAlexIHU GAlexIHU added the release-note/misc Miscellaneous changes label Apr 15, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 2026

Caution

Review failed

Pull request was closed or merged during review

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
📝 Walkthrough

Walkthrough

Adds a paginated customer credit-transactions endpoint, threads Ledger and AccountResolver through DI and server wiring, makes balance lookups cursor-aware, implements page-based ledger transaction listing and transaction cursors, and propagates per-charge annotations into committed ledger transactions.

Changes

Cohort / File(s) Summary
Customer Credits Handler
api/v3/handlers/customers/credits/handler.go, api/v3/handlers/customers/credits/list_transactions.go, api/v3/handlers/customers/credits/list_transactions_test.go
Adds ListCreditTransactions handler, request/response/params types, pagination/filters validation, facade call, mapping to API models, and unit tests for conversions.
Server Routing & Wiring
api/v3/server/routes.go, api/v3/server/server.go, cmd/server/main.go, cmd/server/wire.go, cmd/server/wire_gen.go, openmeter/server/router/router.go, openmeter/server/server.go
Expose and wire Ledger and AccountResolver through configs/DI; require them when credits enabled; construct real or noop components depending on feature flag.
Ledger Primitives & API
openmeter/ledger/primitives.go, openmeter/ledger/query.go, openmeter/ledger/noop/noop.go
Introduce TransactionCursor, Transaction.Cursor(), extend Account.GetBalance to accept after *TransactionCursor, add Ledger.ListTransactionsByPage and new list input filters. Update noop implementations.
Historical Ledger & Repo
openmeter/ledger/historical/adapter/ledger.go, .../adapter/ledger_test.go, openmeter/ledger/historical/ledger.go, openmeter/ledger/historical/repo.go, openmeter/ledger/historical/transaction.go, openmeter/ledger/historical/adapter/sumentries_query.go
Implement page-based repo listing with account/currency/annotation/credit-movement filters, ordering by booked/created/ID, add ListTransactionsByPage and cursor-aware sum-entry predicates, plus tests.
Customer Balance Facade & Service
openmeter/ledger/customerbalance/facade.go, openmeter/ledger/customerbalance/service.go, openmeter/ledger/customerbalance/noop.go, openmeter/ledger/customerbalance/facade_test.go, openmeter/ledger/customerbalance/service_test.go
Add GetBalanceInput{CustomerID,Currency,After}, expose Facade.GetBalance(ctx,input) and ListCreditTransactions passthroughs; propagate after cursor through service and noop; update tests.
Customer Balance Transactions
openmeter/ledger/customerbalance/transactions.go, openmeter/ledger/customerbalance/transactions_test.go
Implement credit-transaction listing, types (CreditTransactionType, inputs/results), ledger→model mapping, running before/after balance computation using cursor, and tests.
Annotations & Charge Adapters
openmeter/ledger/annotations.go, openmeter/ledger/chargeadapter/annotations.go, openmeter/ledger/chargeadapter/...{creditpurchase,flatfee,usagebased}.go, related tests
Add subscription/feature annotation keys and helpers; compute per-charge annotations and attach them to each resolved transaction input before commit; tests verify annotations persisted.
Collector / Commit Path
openmeter/ledger/collector/collect.go, openmeter/ledger/collector/correct.go, openmeter/ledger/collector/service.go
Add Annotations to collector input structs; use provided annotations as group metadata and ensure each resolved transaction input carries those annotations prior to ledger commit.
Noop & Account Implementations, Tests
openmeter/ledger/account/account.go, openmeter/ledger/account/subaccount.go, openmeter/ledger/ledger_test.go, openmeter/ledger/transactions/correction_test.go, test/credits/sanity_test.go
Propagate after *TransactionCursor through GetBalance calls, adapt tests to pass nil cursors, and update noop account methods.
App wiring & misc
app/common/customerbalance.go, openmeter/ledger/customerbalance/testenv_test.go, openmeter/billing/creditgrant/noop.go, various _test.go`
Reduce NewCustomerBalanceService DI params and explicitly set historical ledger; add noop credit-grant service; wire HistoricalLedger into customerbalance test env; add/update tests.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as ListCreditTransactions Handler
    participant Ledger as Ledger Service
    participant Historical as Historical Repo
    participant DB as Database
    participant Balance as CustomerBalance Facade

    Client->>Handler: GET /customers/{id}/credits/transactions
    Handler->>Handler: parse & validate page + filters
    Handler->>Ledger: resolve customer's FBO account
    Ledger-->>Handler: accountID

    alt account exists
        Handler->>Historical: ListTransactionsByPage(page, accountID, currency?, movement?, annotations?)
        Historical->>DB: query transactions (account, currency, annotations, movement) with pagination
        DB-->>Historical: transactions + total
        Historical-->>Handler: paginated results

        alt transactions non-empty
            Handler->>Balance: GetBalance(ctx, CustomerID, Currency, After=firstTx.Cursor())
            Balance->>Ledger: query balance after cursor
            Ledger-->>Balance: Balance
            Balance-->>Handler: Decimal balance
            Handler->>Handler: compute running before/after balances and map items
        end
    else no account
        Handler-->>Client: empty page response
    end

    Handler-->>Client: PagePaginationResponse[BillingCreditTransaction]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

release-note/feature, area/billing

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.95% 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 clearly summarizes the main change: introducing a transaction listing API for the ledger system.

✏️ 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/transaction-listing

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.

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 (5)
api/v3/server/server.go (1)

223-223: Consider guarding handler creation with the new dependencies too.

Since this constructor now requires config.Ledger and config.AccountResolver, adding them to the if condition would make partial wiring fail safer.

🔧 Suggested tweak
-if config.CustomerBalanceFacade != nil && config.Credits.Enabled {
+if config.CustomerBalanceFacade != nil && config.Credits.Enabled && config.Ledger != nil && config.AccountResolver != nil {
 	customersCreditsHandler = customerscreditshandler.New(resolveNamespace, config.CustomerService, config.CustomerBalanceFacade, config.CreditGrantService, config.Ledger, config.AccountResolver, httptransport.WithErrorHandler(config.ErrorHandler))
 }

Based on learnings: Ensure credits.enabled is guarded at multiple layers: ledger-backed customer credit handlers in api/v3/server, customer ledger hooks, and namespace/default-account provisioning must each be wired separately and stay disabled when credits are off.

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

In `@api/v3/server/server.go` at line 223, The customers credit handler is now
constructed with additional deps (config.Ledger and config.AccountResolver) but
the existing enabled check only verifies credits.enabled; update the guard
around the customerscreditshandler.New call so it only constructs
customersCreditsHandler when credits are enabled AND both config.Ledger and
config.AccountResolver are non-nil/available; reference the
customersCreditsHandler variable and the customerscreditshandler.New constructor
and short-circuit creation when either config.Ledger or config.AccountResolver
is missing to ensure safe wiring when ledger-backed credits are disabled.
openmeter/ledger/chargeadapter/creditpurchase.go (1)

81-85: Optional cleanup: extract the repeated input-annotation loop.

Same block appears in three places; a small helper would make this easier to maintain.

♻️ Suggested refactor
+func withAnnotationsOnInputs(inputs []ledger.TransactionInput, annotations models.Annotations) []ledger.TransactionInput {
+	for i, input := range inputs {
+		if input != nil {
+			inputs[i] = transactions.WithAnnotations(input, annotations)
+		}
+	}
+	return inputs
+}
...
-	for i, input := range inputs {
-		if input != nil {
-			inputs[i] = transactions.WithAnnotations(input, annotations)
-		}
-	}
+	inputs = withAnnotationsOnInputs(inputs, annotations)

Also applies to: 135-139, 251-255

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

In `@openmeter/ledger/chargeadapter/creditpurchase.go` around lines 81 - 85,
Extract the repeated loop that applies annotations to inputs into a small helper
(e.g., annotateInputs or applyAnnotations) that takes the inputs slice and
annotations and returns the updated slice; replace the three occurrences of the
inline loop (the blocks that iterate over inputs, check for nil, and call
transactions.WithAnnotations) with calls to this helper so all sites (the ones
using variables inputs and annotations and calling transactions.WithAnnotations)
share the same logic and reduce duplication.
openmeter/ledger/chargeadapter/usagebased_test.go (1)

324-345: Consider extracting shared test helper.

The transactionAnnotations helper is identical to the one in creditpurchase_test.go. If more charge adapter tests need this pattern, it might be worth extracting to ledgertestutils.IntegrationEnv or a shared test helper to reduce duplication.

That said, this is a minor point and doesn't block the PR.

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

In `@openmeter/ledger/chargeadapter/usagebased_test.go` around lines 324 - 345,
The transactionAnnotations helper in usageBasedHandlerTestEnv is duplicated from
creditpurchase_test.go; extract it to a shared test helper (e.g., add a method
TransactionAnnotations(ctx context.Context, db *DBType, namespace string,
groupID string) []models.Annotations on ledgertestutils.IntegrationEnv or a
package-level helper in ledgertestutils) and update
usageBasedHandlerTestEnv.transactionAnnotations to call that shared helper (or
remove the duplicate and call ledgertestutils.TransactionAnnotations directly)
so both tests use the single implementation referenced by the unique symbols
transactionAnnotations, usageBasedHandlerTestEnv, and
ledgertestutils.IntegrationEnv.
api/v3/handlers/customers/credits/list_transactions.go (2)

210-218: Verify balance roll-forward direction matches API expectations.

The balance calculation walks backwards through time (newest to oldest). Just want to confirm the semantics are intentional:

  • After = balance immediately after this transaction was applied
  • Before = balance immediately before this transaction was applied

This means for a "consumed" tx with amount -10 and After = 100, we get Before = 110. That reads correctly to me, but worth a quick sanity check that API consumers expect this interpretation.

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

In `@api/v3/handlers/customers/credits/list_transactions.go` around lines 210 -
218, The current applyCreditTransactionBalances function computes balances by
walking from newest to oldest using runningBalance and sets
API.AvailableBalance.After = runningBalance and Before =
runningBalance.Sub(items[i].Amount); confirm whether API consumers expect After
to be the post-transaction balance and Before to be pre-transaction; if they
expect the opposite, either reverse the iteration order or swap the assignments
(set After = runningBalance.Sub(items[i].Amount) and Before = runningBalance)
and update any tests; refer to applyCreditTransactionBalances,
mappedCreditTransaction.Amount, and API.AvailableBalance.After/Before when
making the change.

280-290: Consider whether missing FBO entry is truly an error condition.

The query already filters transactions by the FBO account ID (line 111), so transactions returned should always have an FBO entry. If this error ever fires, it might indicate a data integrity issue or a bug in the query filtering.

A debug log here could help surface such edge cases without failing the request, but the current defensive error is also reasonable. Just noting the trade-off.

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

In `@api/v3/handlers/customers/credits/list_transactions.go` around lines 280 -
290, The current creditTransactionEntry function treats a missing customer FBO
entry as an error; instead, log a debug message with the transaction ID and
return (nil, nil) so the request doesn't fail for this edge case, and then
update any callers (e.g., the transactions listing path that calls
creditTransactionEntry) to treat a nil entry as "skip this transaction" rather
than a fatal error; modify creditTransactionEntry to emit a debug log with
tx.ID().ID and return nil, nil, and adjust the caller(s) to check for entry ==
nil and continue.
🤖 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/ledger/historical/ledger.go`:
- Around line 61-65: ListTransactionsByPage currently calls
l.repo.ListTransactionsByPage without validating the pagination input; mirror
the validation performed by ListTransactions by validating params (the same
page/pagination checks used in ListTransactions) before calling
l.repo.ListTransactionsByPage, and return an early wrapped error if validation
fails (e.g., return fmt.Errorf("invalid pagination: %w", err)). Ensure the
validation logic references the same validation routine or checks used by
ListTransactions so both entry points enforce the same page constraints.

In `@openmeter/server/router/router.go`:
- Around line 108-109: Config.Validate() must guard the new credits
dependencies: if the credits feature flag (credits.enabled) is true then
validate that the Config fields Ledger and AccountResolver are non-nil and
return a descriptive validation error otherwise; if credits.enabled is false,
allow Ledger and AccountResolver to be nil. Update the Validate method to check
the boolean flag and fail-fast with a clear message referencing Ledger and
AccountResolver so wiring issues surface at startup (this affects the
ledger-backed customer credit handlers, customer ledger hooks, and
namespace/default-account provisioning paths).

---

Nitpick comments:
In `@api/v3/handlers/customers/credits/list_transactions.go`:
- Around line 210-218: The current applyCreditTransactionBalances function
computes balances by walking from newest to oldest using runningBalance and sets
API.AvailableBalance.After = runningBalance and Before =
runningBalance.Sub(items[i].Amount); confirm whether API consumers expect After
to be the post-transaction balance and Before to be pre-transaction; if they
expect the opposite, either reverse the iteration order or swap the assignments
(set After = runningBalance.Sub(items[i].Amount) and Before = runningBalance)
and update any tests; refer to applyCreditTransactionBalances,
mappedCreditTransaction.Amount, and API.AvailableBalance.After/Before when
making the change.
- Around line 280-290: The current creditTransactionEntry function treats a
missing customer FBO entry as an error; instead, log a debug message with the
transaction ID and return (nil, nil) so the request doesn't fail for this edge
case, and then update any callers (e.g., the transactions listing path that
calls creditTransactionEntry) to treat a nil entry as "skip this transaction"
rather than a fatal error; modify creditTransactionEntry to emit a debug log
with tx.ID().ID and return nil, nil, and adjust the caller(s) to check for entry
== nil and continue.

In `@api/v3/server/server.go`:
- Line 223: The customers credit handler is now constructed with additional deps
(config.Ledger and config.AccountResolver) but the existing enabled check only
verifies credits.enabled; update the guard around the
customerscreditshandler.New call so it only constructs customersCreditsHandler
when credits are enabled AND both config.Ledger and config.AccountResolver are
non-nil/available; reference the customersCreditsHandler variable and the
customerscreditshandler.New constructor and short-circuit creation when either
config.Ledger or config.AccountResolver is missing to ensure safe wiring when
ledger-backed credits are disabled.

In `@openmeter/ledger/chargeadapter/creditpurchase.go`:
- Around line 81-85: Extract the repeated loop that applies annotations to
inputs into a small helper (e.g., annotateInputs or applyAnnotations) that takes
the inputs slice and annotations and returns the updated slice; replace the
three occurrences of the inline loop (the blocks that iterate over inputs, check
for nil, and call transactions.WithAnnotations) with calls to this helper so all
sites (the ones using variables inputs and annotations and calling
transactions.WithAnnotations) share the same logic and reduce duplication.

In `@openmeter/ledger/chargeadapter/usagebased_test.go`:
- Around line 324-345: The transactionAnnotations helper in
usageBasedHandlerTestEnv is duplicated from creditpurchase_test.go; extract it
to a shared test helper (e.g., add a method TransactionAnnotations(ctx
context.Context, db *DBType, namespace string, groupID string)
[]models.Annotations on ledgertestutils.IntegrationEnv or a package-level helper
in ledgertestutils) and update usageBasedHandlerTestEnv.transactionAnnotations
to call that shared helper (or remove the duplicate and call
ledgertestutils.TransactionAnnotations directly) so both tests use the single
implementation referenced by the unique symbols transactionAnnotations,
usageBasedHandlerTestEnv, and ledgertestutils.IntegrationEnv.
🪄 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: 0fd2f635-9370-4888-9442-ed86635a0484

📥 Commits

Reviewing files that changed from the base of the PR and between 171cbc2 and b425601.

📒 Files selected for processing (39)
  • api/v3/handlers/customers/credits/handler.go
  • api/v3/handlers/customers/credits/list_transactions.go
  • api/v3/handlers/customers/credits/list_transactions_test.go
  • api/v3/server/routes.go
  • api/v3/server/server.go
  • cmd/server/main.go
  • cmd/server/wire.go
  • cmd/server/wire_gen.go
  • openmeter/ledger/account/account.go
  • openmeter/ledger/account/subaccount.go
  • openmeter/ledger/annotations.go
  • openmeter/ledger/chargeadapter/annotations.go
  • openmeter/ledger/chargeadapter/creditpurchase.go
  • openmeter/ledger/chargeadapter/creditpurchase_test.go
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/chargeadapter/usagebased.go
  • openmeter/ledger/chargeadapter/usagebased_test.go
  • openmeter/ledger/collector/collect.go
  • openmeter/ledger/collector/correct.go
  • openmeter/ledger/collector/service.go
  • openmeter/ledger/customerbalance/facade.go
  • openmeter/ledger/customerbalance/facade_test.go
  • openmeter/ledger/customerbalance/noop.go
  • openmeter/ledger/customerbalance/service.go
  • openmeter/ledger/customerbalance/service_test.go
  • openmeter/ledger/historical/adapter/ledger.go
  • openmeter/ledger/historical/adapter/ledger_test.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/historical/ledger.go
  • openmeter/ledger/historical/repo.go
  • openmeter/ledger/historical/transaction.go
  • openmeter/ledger/ledger_test.go
  • openmeter/ledger/noop/noop.go
  • openmeter/ledger/primitives.go
  • openmeter/ledger/query.go
  • openmeter/ledger/transactions/correction_test.go
  • openmeter/server/router/router.go
  • openmeter/server/server.go
  • test/credits/sanity_test.go

Comment thread openmeter/ledger/historical/ledger.go
Comment thread openmeter/server/router/router.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: 4

♻️ Duplicate comments (1)
openmeter/ledger/historical/ledger.go (1)

61-65: ⚠️ Potential issue | 🟠 Major

Please validate page params before repo call (same as ListTransactions).

At Line 62, this path skips input validation, so invalid pagination can reach the repo/DB. It should mirror the validation guard used in ListTransactions.

Suggested patch
 func (l *Ledger) ListTransactionsByPage(ctx context.Context, params ledger.ListTransactionsByPageInput) (pagepagination.Result[ledger.Transaction], error) {
+	if err := params.Validate(); err != nil {
+		return pagepagination.Result[ledger.Transaction]{}, fmt.Errorf("failed to validate list transactions by page input: %w", err)
+	}
+
 	res, err := l.repo.ListTransactionsByPage(ctx, params)
 	if err != nil {
 		return pagepagination.Result[ledger.Transaction]{}, fmt.Errorf("list transactions by page: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/ledger/historical/ledger.go` around lines 61 - 65,
ListTransactionsByPage currently forwards params to
l.repo.ListTransactionsByPage without validating pagination, allowing invalid
page params to hit the repo; mirror the same validation guard used in
ListTransactions by validating the incoming params (the page/pagination fields)
before calling l.repo.ListTransactionsByPage and return a wrapped error if
validation fails. Locate the ListTransactionsByPage method and add the same
param validation logic used in ListTransactions (use the same
function/validation call or checks that guard against invalid page size/number),
then only call l.repo.ListTransactionsByPage when validation passes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/v3/handlers/customers/credits/list_transactions.go`:
- Around line 126-133: The page-level running balance is seeded from
items[0].Currency and reused for all items, which produces wrong
AvailableBalance when items contain mixed currencies; update the logic in
list_transactions.go (around customerFBOBalance and
applyCreditTransactionBalances usage) to maintain separate running balances
keyed by item.Currency: either enforce/validate a single-currency filter
earlier, or compute a map[currency]runningBalance by finding the first item for
each distinct currency, calling customerFBOBalance(ctx, request, currency,
&firstCursor) per currency, then pass the correct running balance for each item
(or call applyCreditTransactionBalances per-currency group) so each transaction
uses its currency-specific running balance.
- Around line 163-170: The switch that maps filters currently treats
BillingCreditTransactionTypeAdjusted as
ledger.ListTransactionsCreditMovementUnspecified which causes adjusted queries
to look like empty results; update the handler so that
BillingCreditTransactionTypeAdjusted is not converted into a fake "unspecified"
predicate — either return a 400 Bad Request when an adjusted filter is received
(reject the unsupported filter) or implement and return a real adjusted
predicate from the ledger layer instead of using
ledger.ListTransactionsCreditMovementUnspecified; change the case for
BillingCreditTransactionTypeAdjusted in the same switch (the code around
mapCreditTransaction()/list_transactions.go where
BillingCreditTransactionTypeFunded/Consumed/Adjusted are handled) to perform one
of those two fixes and ensure callers receive an error or a correct predicate
rather than a silent Total=0.

In `@api/v3/server/server.go`:
- Around line 76-77: When credits are enabled, fail fast in Config.Validate():
check cfg.Credits.Enabled (or credits.enabled) and if true ensure the server
dependencies cfg.Server.Ledger and cfg.Server.AccountResolver are non-nil; if
either is nil return a clear validation error indicating that
customersCreditsHandler dependencies are missing so the server cannot start with
credits enabled. Update Config.Validate() to perform these checks (referencing
Config.Validate, customersCreditsHandler, Ledger, AccountResolver, and
credits.enabled) rather than allowing construction to proceed with nil
dependencies.

In `@openmeter/ledger/historical/adapter/ledger.go`:
- Around line 324-325: The current code applies entryPredicates to the
eager-loaded Entries edge via q.Where(entryPredicates...), which trims the
Entries returned on each Transaction; instead, move the predicates produced by
listTransactionsEntryPredicates into the parent transaction query (apply them to
the main transaction query builder) so the transaction selection is scoped but
the eager-loaded Entries edge is loaded completely (remove
q.Where(entryPredicates...) from the Entries eager-load). Update the same
pattern where q.Where(entryPredicates...) is used (the other occurrences noted)
so ListTransactions and related functions select transactions with the
predicates but always preload all Entries for matched transactions.

---

Duplicate comments:
In `@openmeter/ledger/historical/ledger.go`:
- Around line 61-65: ListTransactionsByPage currently forwards params to
l.repo.ListTransactionsByPage without validating pagination, allowing invalid
page params to hit the repo; mirror the same validation guard used in
ListTransactions by validating the incoming params (the page/pagination fields)
before calling l.repo.ListTransactionsByPage and return a wrapped error if
validation fails. Locate the ListTransactionsByPage method and add the same
param validation logic used in ListTransactions (use the same
function/validation call or checks that guard against invalid page size/number),
then only call l.repo.ListTransactionsByPage when validation passes.
🪄 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: d6771e25-d53b-418d-a1ad-b8d3beefd4b7

📥 Commits

Reviewing files that changed from the base of the PR and between b425601 and de598ef.

📒 Files selected for processing (39)
  • api/v3/handlers/customers/credits/handler.go
  • api/v3/handlers/customers/credits/list_transactions.go
  • api/v3/handlers/customers/credits/list_transactions_test.go
  • api/v3/server/routes.go
  • api/v3/server/server.go
  • cmd/server/main.go
  • cmd/server/wire.go
  • cmd/server/wire_gen.go
  • openmeter/ledger/account/account.go
  • openmeter/ledger/account/subaccount.go
  • openmeter/ledger/annotations.go
  • openmeter/ledger/chargeadapter/annotations.go
  • openmeter/ledger/chargeadapter/creditpurchase.go
  • openmeter/ledger/chargeadapter/creditpurchase_test.go
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/chargeadapter/usagebased.go
  • openmeter/ledger/chargeadapter/usagebased_test.go
  • openmeter/ledger/collector/collect.go
  • openmeter/ledger/collector/correct.go
  • openmeter/ledger/collector/service.go
  • openmeter/ledger/customerbalance/facade.go
  • openmeter/ledger/customerbalance/facade_test.go
  • openmeter/ledger/customerbalance/noop.go
  • openmeter/ledger/customerbalance/service.go
  • openmeter/ledger/customerbalance/service_test.go
  • openmeter/ledger/historical/adapter/ledger.go
  • openmeter/ledger/historical/adapter/ledger_test.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/historical/ledger.go
  • openmeter/ledger/historical/repo.go
  • openmeter/ledger/historical/transaction.go
  • openmeter/ledger/ledger_test.go
  • openmeter/ledger/noop/noop.go
  • openmeter/ledger/primitives.go
  • openmeter/ledger/query.go
  • openmeter/ledger/transactions/correction_test.go
  • openmeter/server/router/router.go
  • openmeter/server/server.go
  • test/credits/sanity_test.go
✅ Files skipped from review due to trivial changes (6)
  • openmeter/ledger/chargeadapter/usagebased.go
  • openmeter/server/router/router.go
  • openmeter/ledger/customerbalance/noop.go
  • openmeter/ledger/collector/collect.go
  • openmeter/ledger/chargeadapter/annotations.go
  • openmeter/server/server.go
🚧 Files skipped from review as they are similar to previous changes (18)
  • openmeter/ledger/account/subaccount.go
  • cmd/server/main.go
  • openmeter/ledger/historical/transaction.go
  • api/v3/server/routes.go
  • openmeter/ledger/transactions/correction_test.go
  • openmeter/ledger/historical/repo.go
  • openmeter/ledger/collector/service.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/account/account.go
  • openmeter/ledger/customerbalance/service.go
  • api/v3/handlers/customers/credits/handler.go
  • openmeter/ledger/customerbalance/facade_test.go
  • openmeter/ledger/historical/adapter/ledger_test.go
  • openmeter/ledger/chargeadapter/usagebased_test.go
  • openmeter/ledger/annotations.go
  • openmeter/ledger/ledger_test.go
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/customerbalance/facade.go

Comment thread api/v3/handlers/customers/credits/list_transactions.go Outdated
Comment thread api/v3/handlers/customers/credits/list_transactions.go Outdated
Comment thread api/v3/server/server.go
Comment on lines +324 to +325
entryPredicates := listTransactionsEntryPredicates(input)

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.

⚠️ Potential issue | 🟠 Major

Don’t trim the transaction’s entry set here.

q.Where(entryPredicates...) narrows the eager-loaded Entries edge itself, so any account/currency-scoped page now returns partial ledger.Transaction objects. That’s a pretty sharp behavior change from ListTransactions, and callers lose the counter-entries they’d normally expect on a transaction. I’d keep the scoping predicate on the parent transaction query and still load all entries for the matched rows.

💡 Safer query shape
 func (r *repo) ListTransactionsByPage(ctx context.Context, input ledger.ListTransactionsByPageInput) (pagepagination.Result[*ledgerhistorical.Transaction], error) {
 	entryPredicates := listTransactionsEntryPredicates(input)

 	query := r.db.LedgerTransaction.Query().
 		Where(ledgertransactiondb.Namespace(input.Namespace)).
 		WithEntries(func(q *db.LedgerEntryQuery) {
-			if len(entryPredicates) > 0 {
-				q.Where(entryPredicates...)
-			}
 			q.Order(
 				ledgerentrydb.ByCreatedAt(),
 				ledgerentrydb.ByID(),
 			)
 			q.WithSubAccount(func(sq *db.LedgerSubAccountQuery) {
 				sq.WithAccount()
 				sq.WithRoute()
 			})
 		}).
 		Order(
 			ledgertransactiondb.ByBookedAt(entsql.OrderDesc()),
 			ledgertransactiondb.ByCreatedAt(entsql.OrderDesc()),
 			ledgertransactiondb.ByID(entsql.OrderDesc()),
 		)

-	if len(input.AccountIDs) > 0 {
-		query = query.Where(
-			ledgertransactiondb.HasEntriesWith(
-				ledgerentrydb.HasSubAccountWith(
-					ledgersubaccountdb.AccountIDIn(input.AccountIDs...),
-				),
-			),
-		)
-	}
-
-	if input.Currency != nil {
-		query = query.Where(
-			ledgertransactiondb.HasEntriesWith(
-				ledgerentrydb.HasSubAccountWith(
-					ledgersubaccountdb.HasRouteWith(
-						ledgersubaccountroutedb.Currency(string(*input.Currency)),
-					),
-				),
-			),
-		)
-	}
+	if len(entryPredicates) > 0 {
+		query = query.Where(ledgertransactiondb.HasEntriesWith(entryPredicates...))
+	}

Also applies to: 328-339, 347-367, 404-424

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

In `@openmeter/ledger/historical/adapter/ledger.go` around lines 324 - 325, The
current code applies entryPredicates to the eager-loaded Entries edge via
q.Where(entryPredicates...), which trims the Entries returned on each
Transaction; instead, move the predicates produced by
listTransactionsEntryPredicates into the parent transaction query (apply them to
the main transaction query builder) so the transaction selection is scoped but
the eager-loaded Entries edge is loaded completely (remove
q.Where(entryPredicates...) from the Entries eager-load). Update the same
pattern where q.Where(entryPredicates...) is used (the other occurrences noted)
so ListTransactions and related functions select transactions with the
predicates but always preload all Entries for matched transactions.

turip
turip previously approved these changes Apr 15, 2026
ID: charge.ID,
},
charge.Intent.Subscription,
ptrIfNotEmpty(charge.State.FeatureID),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
ptrIfNotEmpty(charge.State.FeatureID),
lo.EmptyableToPtr(charge.State.FeatureID),

return ListCreditTransactionsResponse{}, fmt.Errorf("list transactions: %w", err)
}

items, err := mapCreditTransactions(result.Items)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would add some validation here that the list result is single currency only, as single currency is assumed on the rest of execution flow.

return req, nil
},
func(ctx context.Context, request ListCreditTransactionsRequest) (ListCreditTransactionsResponse, error) {
creditMovement, empty := creditMovementFromTypeFilter(request.TypeFilter)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think there's way too much logic here inside the http handler. It would be better if we just provide a service call in my opinion so that we are not mixing the http mapping and the actual calculation logic.

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

♻️ Duplicate comments (2)
openmeter/ledger/customerbalance/transactions.go (2)

151-165: ⚠️ Potential issue | 🟠 Major

Don’t make adjusted look like “no data.”

creditTransactionType() can emit CreditTransactionTypeAdjusted, and the API layer exposes that enum too, but this mapping turns an adjusted filter into empty=true. Clients end up getting TotalCount=0 instead of either real matches or a clear “unsupported filter” error. Please either reject adjusted up front or add a real ledger predicate for it.

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

In `@openmeter/ledger/customerbalance/transactions.go` around lines 151 - 165, The
mapping in ledgerCreditMovement treats CreditTransactionTypeAdjusted as
"unspecified" (returning ListTransactionsCreditMovementUnspecified, true, nil)
which hides adjusted matches; update ledgerCreditMovement (and call sites if
needed) to reject adjusted explicitly by returning an error (e.g.,
fmt.Errorf("adjusted credit transaction filter not supported")) so clients get a
clear unsupported-filter error, or alternatively implement a proper ledger
predicate for adjusted by returning a distinct
ledger.ListTransactionsCreditMovement value and false along with nil error;
modify the function ledgerCreditMovement accordingly (change the
CreditTransactionTypeAdjusted case) so adjusted is not conflated with
unspecified.

127-133: ⚠️ Potential issue | 🟠 Major

Running balances are still wrong on mixed-currency pages.

This seeds one running balance from items[0].Currency and reuses it for every row on the page. If the page contains multiple currencies and input.Currency is nil, every later currency gets the wrong Before/After values. Easiest fix is to require a single-currency query here; otherwise you need separate running balances per currency.

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

In `@openmeter/ledger/customerbalance/transactions.go` around lines 127 - 133, The
code seeds one running balance using items[0].Currency and applies it to all
rows, which breaks when the page contains mixed currencies; update the logic in
the block that calls GetBalance and applyCreditTransactionBalances so it either
(A) enforces a single-currency query by returning an error or requiring
input.Currency to be non-nil before proceeding, or (B) computes separate running
balances per currency: group items by Currency, call s.GetBalance(ctx,
input.CustomerID, routeFilter(<currency>), cursor) for each currency group's
first item, then call applyCreditTransactionBalances on each group with its
corresponding runningBalance.Settled(). Reference symbols: GetBalance,
applyCreditTransactionBalances, input.Currency, items, routeFilter,
ListCreditTransactionsResult.
🧹 Nitpick comments (1)
api/v3/handlers/customers/credits/handler.go (1)

34-35: Trim the unused ledger wiring for now.

ListCreditTransactions() goes through balanceFacade, and nothing in this handler uses ledger or accountResolver. Threading both through New() makes DI and tests noisier without buying anything yet, so I’d keep the handler boundary on the facade until a call site actually needs the lower-level deps.

Also applies to: 44-54

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

In `@api/v3/handlers/customers/credits/handler.go` around lines 34 - 35, Remove
the unused lower-level ledger wiring from the handler by deleting the ledger and
accountResolver fields and removing their injection from New(); keep the handler
boundary using only balanceFacade which ListCreditTransactions() uses. Update
the handler struct to remove ledger and accountResolver, adjust the New(...)
constructor signature to stop accepting ledger.AccountResolver and
ledger.Ledger, and remove any assignments for those fields in New so DI/tests no
longer require those dependencies until a call site needs them.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/v3/handlers/customers/credits/convert.go`:
- Around line 369-377: The toAPIBillingCreditTransactionType function currently
maps any unknown customerbalance.CreditTransactionType to
BillingCreditTransactionTypeAdjusted; explicitly handle
customerbalance.CreditTransactionTypeAdjusted by returning
api.BillingCreditTransactionTypeAdjusted and change the default branch to
surface unmapped values (e.g., return an error or a sentinel/unmapped value)
instead of silently coercing them so callers can detect unknown/future enum
values; update the function signature and all callers of
toAPIBillingCreditTransactionType accordingly to handle the error or unmapped
result and reference customerbalance.CreditTransactionType and
api.BillingCreditTransactionType in your changes.

In `@openmeter/ledger/customerbalance/transactions.go`:
- Around line 168-183: The helper fboAccountIDFromCustomerAccounts silently
returns "" on unexpected FBO types which masks resolver contract errors; change
fboAccountIDFromCustomerAccounts to return (string, error) and have
customerFBOAccountID propagate that error (returning a descriptive error when
accounts.FBOAccount is not a *ledgeraccount.CustomerFBOAccount), then update
callers such as ListCreditTransactions (and any use of customerFBOAccountID) to
handle the error instead of falling back to emptyCreditTransactions; reference
functions: customerFBOAccountID, fboAccountIDFromCustomerAccounts,
ListCreditTransactions, emptyCreditTransactions, and
ledgeraccount.CustomerFBOAccount.

---

Duplicate comments:
In `@openmeter/ledger/customerbalance/transactions.go`:
- Around line 151-165: The mapping in ledgerCreditMovement treats
CreditTransactionTypeAdjusted as "unspecified" (returning
ListTransactionsCreditMovementUnspecified, true, nil) which hides adjusted
matches; update ledgerCreditMovement (and call sites if needed) to reject
adjusted explicitly by returning an error (e.g., fmt.Errorf("adjusted credit
transaction filter not supported")) so clients get a clear unsupported-filter
error, or alternatively implement a proper ledger predicate for adjusted by
returning a distinct ledger.ListTransactionsCreditMovement value and false along
with nil error; modify the function ledgerCreditMovement accordingly (change the
CreditTransactionTypeAdjusted case) so adjusted is not conflated with
unspecified.
- Around line 127-133: The code seeds one running balance using
items[0].Currency and applies it to all rows, which breaks when the page
contains mixed currencies; update the logic in the block that calls GetBalance
and applyCreditTransactionBalances so it either (A) enforces a single-currency
query by returning an error or requiring input.Currency to be non-nil before
proceeding, or (B) computes separate running balances per currency: group items
by Currency, call s.GetBalance(ctx, input.CustomerID, routeFilter(<currency>),
cursor) for each currency group's first item, then call
applyCreditTransactionBalances on each group with its corresponding
runningBalance.Settled(). Reference symbols: GetBalance,
applyCreditTransactionBalances, input.Currency, items, routeFilter,
ListCreditTransactionsResult.

---

Nitpick comments:
In `@api/v3/handlers/customers/credits/handler.go`:
- Around line 34-35: Remove the unused lower-level ledger wiring from the
handler by deleting the ledger and accountResolver fields and removing their
injection from New(); keep the handler boundary using only balanceFacade which
ListCreditTransactions() uses. Update the handler struct to remove ledger and
accountResolver, adjust the New(...) constructor signature to stop accepting
ledger.AccountResolver and ledger.Ledger, and remove any assignments for those
fields in New so DI/tests no longer require those dependencies until a call site
needs them.
🪄 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: 36be7749-67a7-4f9c-ade9-3a3b754838c3

📥 Commits

Reviewing files that changed from the base of the PR and between de598ef and 394c43e.

📒 Files selected for processing (17)
  • api/v3/handlers/customers/credits/convert.go
  • api/v3/handlers/customers/credits/handler.go
  • api/v3/handlers/customers/credits/list_transactions.go
  • api/v3/handlers/customers/credits/list_transactions_test.go
  • api/v3/server/routes.go
  • api/v3/server/server.go
  • app/common/customerbalance.go
  • openmeter/billing/creditgrant/noop.go
  • openmeter/ledger/chargeadapter/annotations.go
  • openmeter/ledger/customerbalance/facade.go
  • openmeter/ledger/customerbalance/noop.go
  • openmeter/ledger/customerbalance/service.go
  • openmeter/ledger/customerbalance/testenv_test.go
  • openmeter/ledger/customerbalance/transactions.go
  • openmeter/ledger/customerbalance/transactions_test.go
  • openmeter/ledger/historical/ledger.go
  • openmeter/ledger/primitives.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • openmeter/ledger/customerbalance/service.go
  • openmeter/ledger/primitives.go

Comment on lines +369 to +377
func toAPIBillingCreditTransactionType(txType customerbalance.CreditTransactionType) api.BillingCreditTransactionType {
switch txType {
case customerbalance.CreditTransactionTypeFunded:
return api.BillingCreditTransactionTypeFunded
case customerbalance.CreditTransactionTypeConsumed:
return api.BillingCreditTransactionTypeConsumed
default:
return api.BillingCreditTransactionTypeAdjusted
}
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.

⚠️ Potential issue | 🟠 Major

Don’t coerce unknown transaction types to adjusted.

The default branch turns any bad or future customerbalance.CreditTransactionType into a valid-looking adjustment, so clients can get silently mislabeled data. Please handle CreditTransactionTypeAdjusted explicitly and leave unknown values unmapped or error out instead.

💡 Minimal safer mapping
 func toAPIBillingCreditTransactionType(txType customerbalance.CreditTransactionType) api.BillingCreditTransactionType {
 	switch txType {
 	case customerbalance.CreditTransactionTypeFunded:
 		return api.BillingCreditTransactionTypeFunded
 	case customerbalance.CreditTransactionTypeConsumed:
 		return api.BillingCreditTransactionTypeConsumed
+	case customerbalance.CreditTransactionTypeAdjusted:
+		return api.BillingCreditTransactionTypeAdjusted
 	default:
-		return api.BillingCreditTransactionTypeAdjusted
+		return api.BillingCreditTransactionTypeNaked
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v3/handlers/customers/credits/convert.go` around lines 369 - 377, The
toAPIBillingCreditTransactionType function currently maps any unknown
customerbalance.CreditTransactionType to BillingCreditTransactionTypeAdjusted;
explicitly handle customerbalance.CreditTransactionTypeAdjusted by returning
api.BillingCreditTransactionTypeAdjusted and change the default branch to
surface unmapped values (e.g., return an error or a sentinel/unmapped value)
instead of silently coercing them so callers can detect unknown/future enum
values; update the function signature and all callers of
toAPIBillingCreditTransactionType accordingly to handle the error or unmapped
result and reference customerbalance.CreditTransactionType and
api.BillingCreditTransactionType in your changes.

Comment on lines +168 to +183
func (s *Service) customerFBOAccountID(ctx context.Context, customerID customer.CustomerID) (string, error) {
accounts, err := s.AccountResolver.GetCustomerAccounts(ctx, customerID)
if err != nil {
return "", err
}

return fboAccountIDFromCustomerAccounts(accounts), nil
}

func fboAccountIDFromCustomerAccounts(accounts ledger.CustomerAccounts) string {
if fbo, ok := accounts.FBOAccount.(*ledgeraccount.CustomerFBOAccount); ok {
return fbo.ID().ID
}

return ""
}
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.

⚠️ Potential issue | 🟠 Major

Fail fast on an unexpected FBO account type.

Returning "" here makes ListCreditTransactions() fall back to emptyCreditTransactions(...), so a broken resolver contract gets surfaced as “no transactions.” It’d be safer to return an error instead of silently converting an internal mismatch into an empty page.

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

In `@openmeter/ledger/customerbalance/transactions.go` around lines 168 - 183, The
helper fboAccountIDFromCustomerAccounts silently returns "" on unexpected FBO
types which masks resolver contract errors; change
fboAccountIDFromCustomerAccounts to return (string, error) and have
customerFBOAccountID propagate that error (returning a descriptive error when
accounts.FBOAccount is not a *ledgeraccount.CustomerFBOAccount), then update
callers such as ListCreditTransactions (and any use of customerFBOAccountID) to
handle the error instead of falling back to emptyCreditTransactions; reference
functions: customerFBOAccountID, fboAccountIDFromCustomerAccounts,
ListCreditTransactions, emptyCreditTransactions, and
ledgeraccount.CustomerFBOAccount.

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

♻️ Duplicate comments (1)
api/v3/handlers/customers/credits/convert.go (1)

369-377: ⚠️ Potential issue | 🟠 Major

Don’t coerce unknown ledger transaction types to adjusted

Line 375/Line 376 currently maps any unknown/future value to adjusted, which can silently mislabel data. Please handle adjusted explicitly and keep unknown values detectable (e.g., error/sentinel).

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

In `@api/v3/handlers/customers/credits/convert.go` around lines 369 - 377, The
switch in toAPIBillingCreditTransactionType currently falls through unknown
values to BillingCreditTransactionTypeAdjusted; instead add an explicit case for
customerbalance.CreditTransactionTypeAdjusted returning
api.BillingCreditTransactionTypeAdjusted, and change the default branch so
unknown/future customerbalance.CreditTransactionType values are detectable (for
example return a new sentinel api.BillingCreditTransactionTypeUnknown or
otherwise surface an error) rather than coercing them to Adjusted; update the
api enum (BillingCreditTransactionType) to include the Unknown sentinel if
needed and adjust callers to handle it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/v3/handlers/customers/credits/convert.go`:
- Around line 312-327: The helper fromAPIBillingCreditTransactionType currently
returns nil for unknown api.BillingCreditTransactionType values which silently
drops the filter; change its signature to return
(*customerbalance.CreditTransactionType, error), map known cases to a pointer to
the corresponding customerbalance.CreditTransactionType, and on the default
branch return a descriptive error (e.g., fmt.Errorf("unknown
BillingCreditTransactionType: %v", *filter)). Update all callers to handle the
error and propagate/return a 400 validation error to the client when the
transaction-type is invalid.

---

Duplicate comments:
In `@api/v3/handlers/customers/credits/convert.go`:
- Around line 369-377: The switch in toAPIBillingCreditTransactionType currently
falls through unknown values to BillingCreditTransactionTypeAdjusted; instead
add an explicit case for customerbalance.CreditTransactionTypeAdjusted returning
api.BillingCreditTransactionTypeAdjusted, and change the default branch so
unknown/future customerbalance.CreditTransactionType values are detectable (for
example return a new sentinel api.BillingCreditTransactionTypeUnknown or
otherwise surface an error) rather than coercing them to Adjusted; update the
api enum (BillingCreditTransactionType) to include the Unknown sentinel if
needed and adjust callers to handle it.
🪄 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: 26905041-5465-4917-868a-d518f56170ba

📥 Commits

Reviewing files that changed from the base of the PR and between 394c43e and 9d7bc73.

📒 Files selected for processing (1)
  • api/v3/handlers/customers/credits/convert.go

Comment on lines +312 to +327
func fromAPIBillingCreditTransactionType(filter *api.BillingCreditTransactionType) *customerbalance.CreditTransactionType {
if filter == nil {
return nil
}

var txType customerbalance.CreditTransactionType
switch *filter {
case api.BillingCreditTransactionTypeFunded:
txType = customerbalance.CreditTransactionTypeFunded
case api.BillingCreditTransactionTypeConsumed:
txType = customerbalance.CreditTransactionTypeConsumed
case api.BillingCreditTransactionTypeAdjusted:
txType = customerbalance.CreditTransactionTypeAdjusted
default:
return nil
}
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.

⚠️ Potential issue | 🟠 Major

Invalid transaction-type filters currently get silently dropped

On Line 326, unknown values return nil, which effectively removes the filter instead of rejecting bad input. That can broaden results unexpectedly for clients.

💡 Suggested fix
-func fromAPIBillingCreditTransactionType(filter *api.BillingCreditTransactionType) *customerbalance.CreditTransactionType {
+func fromAPIBillingCreditTransactionType(filter *api.BillingCreditTransactionType) (*customerbalance.CreditTransactionType, error) {
 	if filter == nil {
-		return nil
+		return nil, nil
 	}
 
 	var txType customerbalance.CreditTransactionType
 	switch *filter {
 	case api.BillingCreditTransactionTypeFunded:
 		txType = customerbalance.CreditTransactionTypeFunded
 	case api.BillingCreditTransactionTypeConsumed:
 		txType = customerbalance.CreditTransactionTypeConsumed
 	case api.BillingCreditTransactionTypeAdjusted:
 		txType = customerbalance.CreditTransactionTypeAdjusted
 	default:
-		return nil
+		return nil, models.NewGenericValidationError(fmt.Errorf("invalid transaction type: %s", *filter))
 	}
 
-	return &txType
+	return &txType, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v3/handlers/customers/credits/convert.go` around lines 312 - 327, The
helper fromAPIBillingCreditTransactionType currently returns nil for unknown
api.BillingCreditTransactionType values which silently drops the filter; change
its signature to return (*customerbalance.CreditTransactionType, error), map
known cases to a pointer to the corresponding
customerbalance.CreditTransactionType, and on the default branch return a
descriptive error (e.g., fmt.Errorf("unknown BillingCreditTransactionType: %v",
*filter)). Update all callers to handle the error and propagate/return a 400
validation error to the client when the transaction-type is invalid.

turip
turip previously approved these changes Apr 16, 2026
@GAlexIHU GAlexIHU merged commit 4f61789 into main Apr 16, 2026
23 of 24 checks passed
@GAlexIHU GAlexIHU deleted the feat/transaction-listing branch April 16, 2026 11:13
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