Skip to content

feat(api): credit grants handler#4075

Merged
tothandras merged 7 commits intomainfrom
feat/api-v3-credit-grants
Apr 7, 2026
Merged

feat(api): credit grants handler#4075
tothandras merged 7 commits intomainfrom
feat/api-v3-credit-grants

Conversation

@tothandras
Copy link
Copy Markdown
Contributor

@tothandras tothandras commented Apr 2, 2026

Summary by CodeRabbit

  • New Features

    • Create customer credit adjustments
    • Create, list, and retrieve customer credit grants
  • Changes

    • Removed "void grant" endpoint
    • Credit grants no longer expose expiration fields (effective_at, expires_after, expires_at)
    • Credit availability policy limited to creation-time allocation only
  • Removals

    • Deprecated entitlement JSON type constants removed

@tothandras tothandras added the release-note/feature Release note: Exciting New Features label Apr 2, 2026
@tothandras tothandras requested a review from a team as a code owner April 2, 2026 16:56
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 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
📝 Walkthrough

Walkthrough

Adds a CreditAdjustment model and create endpoint, removes the void-credit-grant operation, introduces a credit-grant billing service with Create/Get/List, adds handlers (list/create/get), updates generated API/server routing, and wires the new service through DI and router configs.

Changes

Cohort / File(s) Summary
TypeSpec models & ops
api/spec/packages/aip/src/customers/credits/adjustment.tsp, api/spec/packages/aip/src/customers/credits/grant.tsp, api/spec/packages/aip/src/customers/credits/operations.tsp
Added CreditAdjustment model (currency, amount, name/description/labels). Removed OnAuthorization/OnSettlement enum members and timing fields (effective_at, expires_after, expires_at) from CreditGrant. Replaced adjustment ops interface and added create operation; commented out void-grant operation.
API routing specs
api/spec/packages/aip/src/konnect.tsp, api/spec/packages/aip/src/openmeter.tsp
Added routed CustomerCreditAdjustmentsEndpoints at /openmeter/customers/{customerId}/credits/adjustments and commented out the voidGrant route member under credit-grant endpoints.
Generated API & server
api/v3/api.gen.go, api/v3/server/routes.go, api/v3/server/server.go
Regenerated API types: added BillingCreditAdjustment, adjusted BillingCreditGrant and request types, removed grant timing fields, added CreateCreditAdjustment wiring, removed VoidCreditGrant from interfaces/routes, and updated embedded swagger blob.
Handlers & conversion
api/v3/handlers/customers/credits/convert.go, api/v3/handlers/customers/credits/create_grant.go, api/v3/handlers/customers/credits/get_grant.go, api/v3/handlers/customers/credits/list_grants.go, api/v3/handlers/customers/credits/get_balance.go, api/v3/handlers/customers/credits/handler.go
Added conversion utilities between internal domain and API types, implemented List/Create/Get credit-grant handlers, replaced local balance mapper with convertBalance, and updated handler interface/constructor to accept creditGrantService.
New billing service
openmeter/billing/creditgrant/service.go, openmeter/billing/creditgrant/service/service.go
Introduced creditgrant.Service interface and concrete implementation with Create/Get/List, domain types (FundingMethod, PurchaseTerms, filters), input validation, and logic to translate to/from credit-purchase intents/charges.
Dependency wiring & app server
app/common/creditgrant.go, cmd/server/wire.go, cmd/server/wire_gen.go, cmd/server/main.go, openmeter/server/router/router.go, openmeter/server/server.go
Added Wire provider and constructor for the credit-grant service, included it in Application and router/server configs, and wired it into server initialization and router config.
Deleted/other
api/types/entitlement.go
Removed obsolete entitlement type alias and constants.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant V3Server as V3 Server
  participant Handler as customersCredits Handler
  participant CreditGrantSvc as creditgrant.Service
  participant ChargesSvc as creditpurchase/Charges
  participant CustomerSvc as customer.Service

  Client->>V3Server: POST /customers/{id}/credits/grants
  V3Server->>Handler: CreateCreditGrant(params, body)
  Handler->>CreditGrantSvc: Create(CreateInput)
  CreditGrantSvc->>CustomerSvc: GetCustomer(namespace, customerId)
  CreditGrantSvc->>ChargesSvc: Create creditpurchase.Intent/Charge
  ChargesSvc-->>CreditGrantSvc: creditpurchase.Charge
  CreditGrantSvc-->>Handler: creditpurchase.Charge
  Handler-->>V3Server: 201 Created (BillingCreditGrant)
  V3Server-->>Client: HTTP 201 response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

area/billing, area/api

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% 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 'feat(api): credit grants handler' directly relates to the main changes—it introduces credit grant handlers and operations in the API spec and implementation.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-v3-credit-grants

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.

Comment thread openmeter/billing/creditgrant/service/service.go
Comment thread api/v3/handlers/customers/credits/convert.go Outdated
Comment thread api/v3/handlers/customers/credits/convert.go
Comment thread api/v3/handlers/customers/credits/convert.go Outdated
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: 6

Caution

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

⚠️ Outside diff range comments (1)
api/v3/api.gen.go (1)

3147-3212: ⚠️ Potential issue | 🟡 Minor

The grant lifecycle docs are now out of sync with the public schema.

This request no longer exposes timing fields, but the generated BillingCreditGrantStatus docs still describe pending / active / expired in terms of effective_at and expires_at. That makes the published contract confusing for SDK consumers. Please fix the source spec comments and regenerate.

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

In `@api/v3/api.gen.go` around lines 3147 - 3212, The generated docs for
BillingCreditGrantStatus are out-of-date because CreateCreditGrantRequest no
longer exposes timing fields (effective_at, expires_at); update the source
OpenAPI/spec comments for BillingCreditGrantStatus (and any related schema
comments) to remove references to effective_at/expires_at and instead describe
the new lifecycle semantics (e.g., how pending/active/expired are determined
without those fields), then regenerate api.gen.go so the comment block above
type BillingCreditGrantStatus and any references in
CreateCreditGrantRequest/Purchase/TaxConfig match the current public schema.
🧹 Nitpick comments (5)
openmeter/billing/creditgrant/service.go (1)

97-105: Avoid silently accepting unused purchase terms

On Line 97-Line 105, Purchase is allowed even when FundingMethod is none, but those fields are later ignored. I’d recommend rejecting that combination to keep API behavior explicit and predictable.

Suggested validation tweak
 if i.FundingMethod != FundingMethodNone && i.Purchase == nil {
 	errs = append(errs, errors.New("purchase terms are required for funded grants"))
 }
+
+if i.FundingMethod == FundingMethodNone && i.Purchase != nil {
+	errs = append(errs, errors.New("purchase terms must be empty when funding method is none"))
+}
 
 if i.Purchase != nil {
 	if err := i.Purchase.Currency.Validate(); err != nil {
 		errs = append(errs, fmt.Errorf("purchase currency: %w", err))
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/creditgrant/service.go` around lines 97 - 105, The
validation currently allows i.Purchase when i.FundingMethod == FundingMethodNone
and later ignores those fields; update the validation in the same function
(where i is validated) to reject the combination by appending an error when
i.FundingMethod == FundingMethodNone && i.Purchase != nil (e.g., "purchase terms
not allowed for non-funded grants"), and keep the existing currency validation
but guard it so it only runs when i.Purchase != nil and i.FundingMethod !=
FundingMethodNone to avoid validating ignored fields.
openmeter/server/router/router.go (1)

138-248: Add an explicit credits-enabled guard for CreditGrantService

Since CreditGrantService is now part of Config, it’d be safer to enforce: if credits are enabled, the service must be non-nil. That makes failures deterministic at startup instead of later in request handling.

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

Suggested guard in config validation
 func (c Config) Validate() error {
+	if c.Credits.Enabled && c.CreditGrantService == nil {
+		return errors.New("credit grant service is required when credits are enabled")
+	}
+
 	if c.NamespaceManager == nil {
 		return errors.New("namespace manager is required")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/server/router/router.go` around lines 138 - 248, Config.Validate
must explicitly guard CreditGrantService when credits are enabled: add a check
in Config.Validate that returns an error if the credits.enabled flag is true and
c.CreditGrantService is nil. Implement this by reading your config flag (e.g., a
boolean field like c.CreditsEnabled or a nested c.Credits.Enabled) and if it is
true and CreditGrantService == nil, return errors.New("credit grant service is
required when credits are enabled"); keep the check near the other
connector/service validations in the Validate() method.
api/spec/packages/aip/src/customers/credits/adjustment.tsp (1)

16-23: Clarify adjustment sign semantics in the field docs

Tiny docs nit: on Line 16 and Line 22, “granted” reads like only positive grants. For an adjustment endpoint, consider wording like “adjusted amount” and (if true) explicitly state positive vs negative behavior.

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

In `@api/spec/packages/aip/src/customers/credits/adjustment.tsp` around lines 16 -
23, Update the field JSDoc to clarify sign semantics: change "The currency of
the granted credits." (currency: Currencies.CurrencyCode) to indicate it's the
currency for the adjustment (e.g., "The currency of the adjusted credits."), and
replace "Granted credit amount." on the adjustment amount field with wording
like "Adjusted amount; positive values grant credits, negative values deduct
credits." Ensure you update only the documentation comments for the relevant
fields (currency and the adjustment amount) in the adjustment.tsp model.
api/spec/packages/aip/src/customers/credits/grant.tsp (1)

258-279: Consider removing (not commenting) deprecated contract fields.

Keeping full commented-out schema blocks in the TypeSpec source makes the contract harder to read and maintain. A short changelog note/PR reference is usually cleaner than inline commented definitions.

As per coding guidelines: "Review the TypeSpec code for conformity with TypeSpec best practices."

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

In `@api/spec/packages/aip/src/customers/credits/grant.tsp` around lines 258 -
279, Remove the commented-out deprecated schema fields (effective_at,
expires_after, expires_at) from the TypeSpec source so the contract is not
cluttered with dead code; delete the entire commented blocks that reference
those symbols and, if needed, add a short changelog/PR note near the top of the
file or in the PR description documenting the removal rather than leaving inline
commented definitions.
api/spec/packages/aip/src/customers/credits/operations.tsp (1)

81-98: Can we remove the old void operation instead of parking it as comments?

Keeping a full removed endpoint in the TypeSpec source gets stale fast and makes the contract a bit harder to trust. I’d just delete it and let git/history preserve the old shape.

As per coding guidelines, "Define API specifications using TypeSpec in api/spec/ as the source of truth for API contracts, then regenerate OpenAPI and SDKs with make gen-api."

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

In `@api/spec/packages/aip/src/customers/credits/operations.tsp` around lines 81 -
98, Remove the commented-out legacy endpoint block for
CustomerCreditGrantVoidOperations (the commented interface and its voidGrant
method including `@post/`@operationId/@summary annotations) from the TypeSpec file
so the API spec no longer contains stale commented endpoints; rely on git
history to retain the previous shape and then run the usual generation step
(make gen-api) to update derived artifacts.
🤖 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 52-60: The convertGrantStatus function currently maps only
"created" and "active" and defaults everything else to
api.BillingCreditGrantStatusActive; update
convertGrantStatus(creditpurchase.Charge) to explicitly map all known domain
statuses (at minimum "active", "created", and "voided") to their corresponding
api.BillingCreditGrantStatus values and stop silently defaulting unknown strings
to Active — change the function to return (api.BillingCreditGrantStatus, error)
(or otherwise return a clearly invalid/unknown enum value) and return an error
for any unrecognized charge.Status so callers fail closed instead of
misreporting unknown states.
- Around line 244-256: The parsed per_unit_cost_basis must be validated as >0;
update validation to reject zero/negative values by adding an IsPositive() check
after alpacadecimal.NewFromString(...) and setting purchase.PerUnitCostBasis (or
centralize it in creditgrant.CreateInput.Validate()); specifically, in the
handler around the block that parses body.Purchase.PerUnitCostBasis ensure you
call costBasis.IsPositive() (or equivalent) and return an error like
"per_unit_cost_basis must be positive" if false, or alternatively add the same
positive check inside creditgrant.CreateInput.Validate() so CreateInput and
purchase.PerUnitCostBasis cannot be zero/negative.

In `@api/v3/handlers/customers/credits/get_grant.go`:
- Around line 17-20: GetCreditGrantParams currently includes CustomerID but that
value isn't used: update the handler that builds creditgrant.GetInput (and
related lookups used by GetCreditGrant handler) to pass args.CustomerID into the
service query or, after fetching the grant via creditgrant.GetInput (which
currently uses Namespace + ChargeID), add an ownership check comparing the
returned grant's CustomerID to args.CustomerID and return a not-found/forbidden
error if they differ; specifically modify where creditgrant.GetInput is
constructed and where the service returns the grant to either include CustomerID
in the input or validate ownership before returning in the GetCreditGrant
handler (also apply the same fix to the other similar handler referenced around
lines 32-35).

In `@api/v3/server/routes.go`:
- Around line 372-374: The CreateCreditAdjustment handler currently delegates to
unimplemented.CreateCreditAdjustment, exposing a 501 for the public API; either
remove/hide the route from the published spec or implement the handler now:
replace the unimplemented call in Server.CreateCreditAdjustment with the real
logic (e.g., parse request body, validate customerId, call your service/repo
method such as s.Service.CreateCreditAdjustment or s.AdjustmentStore.Create,
handle errors and write appropriate HTTP responses), or if staged, remove the
public registration so the contract matches implementation.
- Around line 337-347: The grant route currently only checks
s.customersCreditsHandler but can still panic if the CreditGrantService is nil;
update the guard in ListCreditGrants (and the other grant handlers around the
same area) to require both s.customersCreditsHandler != nil AND
s.CreditGrantService != nil before calling
customersCreditsHandler.ListCreditGrants().With(...).ServeHTTP; if either is
nil, call unimplemented.ListCreditGrants(w, r, customerId, params) (or the
corresponding unimplemented handler) and return. Ensure you apply the same
pattern to the other grant routes referenced (the blocks around lines 349–358
and 360–370) so grant-specific handlers are gated separately from balance
handlers.

In `@openmeter/billing/creditgrant/service/service.go`:
- Around line 126-128: The current call to s.chargesService.ListCharges(ctx,
listInput) ignores the FundingMethod, Status, and Currency filters; add a
fail-fast guard before invoking ListCharges that validates listInput (check the
fields named FundingMethod, Status, and Currency on the request/listInput struct
used by service.go) and return a clear error when any of these filters are set
(non-nil or non-empty) so callers are not silently given unfiltered results;
keep the TODO comment and replace the guard with real filtering logic once
ListCharges supports those filters.

---

Outside diff comments:
In `@api/v3/api.gen.go`:
- Around line 3147-3212: The generated docs for BillingCreditGrantStatus are
out-of-date because CreateCreditGrantRequest no longer exposes timing fields
(effective_at, expires_at); update the source OpenAPI/spec comments for
BillingCreditGrantStatus (and any related schema comments) to remove references
to effective_at/expires_at and instead describe the new lifecycle semantics
(e.g., how pending/active/expired are determined without those fields), then
regenerate api.gen.go so the comment block above type BillingCreditGrantStatus
and any references in CreateCreditGrantRequest/Purchase/TaxConfig match the
current public schema.

---

Nitpick comments:
In `@api/spec/packages/aip/src/customers/credits/adjustment.tsp`:
- Around line 16-23: Update the field JSDoc to clarify sign semantics: change
"The currency of the granted credits." (currency: Currencies.CurrencyCode) to
indicate it's the currency for the adjustment (e.g., "The currency of the
adjusted credits."), and replace "Granted credit amount." on the adjustment
amount field with wording like "Adjusted amount; positive values grant credits,
negative values deduct credits." Ensure you update only the documentation
comments for the relevant fields (currency and the adjustment amount) in the
adjustment.tsp model.

In `@api/spec/packages/aip/src/customers/credits/grant.tsp`:
- Around line 258-279: Remove the commented-out deprecated schema fields
(effective_at, expires_after, expires_at) from the TypeSpec source so the
contract is not cluttered with dead code; delete the entire commented blocks
that reference those symbols and, if needed, add a short changelog/PR note near
the top of the file or in the PR description documenting the removal rather than
leaving inline commented definitions.

In `@api/spec/packages/aip/src/customers/credits/operations.tsp`:
- Around line 81-98: Remove the commented-out legacy endpoint block for
CustomerCreditGrantVoidOperations (the commented interface and its voidGrant
method including `@post/`@operationId/@summary annotations) from the TypeSpec file
so the API spec no longer contains stale commented endpoints; rely on git
history to retain the previous shape and then run the usual generation step
(make gen-api) to update derived artifacts.

In `@openmeter/billing/creditgrant/service.go`:
- Around line 97-105: The validation currently allows i.Purchase when
i.FundingMethod == FundingMethodNone and later ignores those fields; update the
validation in the same function (where i is validated) to reject the combination
by appending an error when i.FundingMethod == FundingMethodNone && i.Purchase !=
nil (e.g., "purchase terms not allowed for non-funded grants"), and keep the
existing currency validation but guard it so it only runs when i.Purchase != nil
and i.FundingMethod != FundingMethodNone to avoid validating ignored fields.

In `@openmeter/server/router/router.go`:
- Around line 138-248: Config.Validate must explicitly guard CreditGrantService
when credits are enabled: add a check in Config.Validate that returns an error
if the credits.enabled flag is true and c.CreditGrantService is nil. Implement
this by reading your config flag (e.g., a boolean field like c.CreditsEnabled or
a nested c.Credits.Enabled) and if it is true and CreditGrantService == nil,
return errors.New("credit grant service is required when credits are enabled");
keep the check near the other connector/service validations in the Validate()
method.
🪄 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: 943aa1d2-1be4-43f8-8813-fcd427e160b4

📥 Commits

Reviewing files that changed from the base of the PR and between fe83424 and 66486a7.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (23)
  • api/spec/packages/aip/src/customers/credits/adjustment.tsp
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/spec/packages/aip/src/customers/credits/operations.tsp
  • api/spec/packages/aip/src/konnect.tsp
  • api/spec/packages/aip/src/openmeter.tsp
  • api/types/entitlement.go
  • api/v3/api.gen.go
  • api/v3/handlers/customers/credits/convert.go
  • api/v3/handlers/customers/credits/create_grant.go
  • api/v3/handlers/customers/credits/get_balance.go
  • api/v3/handlers/customers/credits/get_grant.go
  • api/v3/handlers/customers/credits/handler.go
  • api/v3/handlers/customers/credits/list_grants.go
  • api/v3/server/routes.go
  • api/v3/server/server.go
  • app/common/creditgrant.go
  • cmd/server/main.go
  • cmd/server/wire.go
  • cmd/server/wire_gen.go
  • openmeter/billing/creditgrant/service.go
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/server/router/router.go
  • openmeter/server/server.go
💤 Files with no reviewable changes (1)
  • api/types/entitlement.go

Comment thread api/v3/handlers/customers/credits/convert.go
Comment thread api/v3/handlers/customers/credits/convert.go
Comment thread api/v3/handlers/customers/credits/get_grant.go
Comment thread api/v3/server/routes.go
Comment thread api/v3/server/routes.go
Comment thread openmeter/billing/creditgrant/service/service.go Outdated
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

Caution

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

⚠️ Outside diff range comments (1)
api/v3/api.gen.go (1)

310-323: ⚠️ Potential issue | 🟡 Minor

Enum docs and actual values are out of sync

Nice cleanup on the enum values, but the type docs still mention removed values (on_authorization, on_settlement) at Line 1809 and Line 1810. That can mislead SDK/API consumers.

Please fix this in the OpenAPI/TypeSpec source and regenerate this file (rather than editing this file directly).

As per coding guidelines, all generated files must have // Code generated by X, DO NOT EDIT. headers at the top; never edit files bearing this header.

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

In `@api/v3/api.gen.go` around lines 310 - 323, The generated enum docs for
BillingCreditAvailabilityPolicy are stale—remove the references to the removed
values ("on_authorization", "on_settlement") from the OpenAPI/TypeSpec source
where BillingCreditAvailabilityPolicy is defined, ensure the enum definition
only contains the current member (BillingCreditAvailabilityPolicyOnCreation) and
update its description accordingly, then regenerate api.gen.go so the docblock
and enum values stay in sync and the file contains the required "// Code
generated by ..." header; do not edit api.gen.go directly.
🧹 Nitpick comments (2)
api/v3/handlers/customers/credits/convert.go (1)

134-143: Handle all payment.Status values explicitly for robustness.

The code does sensibly default unknown payment statuses to Pending, but you might want to add payment.StatusOpen (or similar) to the switch if it exists in the domain. Currently:

  • Authorized → Authorized
  • Settled → Settled
  • Everything else → Pending

If other statuses exist (e.g., StatusFailed, StatusOpen), they'd silently become Pending.

openmeter/billing/creditgrant/service.go (1)

144-186: ListInput validation looks good, but optional filters are silently accepted.

The validation properly checks required fields and validates optional filters when present. However, I noticed from reviewing the service implementation that FundingMethod, Status, and Currency filters pass validation here but are currently ignored downstream (see the TODO at line 137 in service/service.go).

Since validation passes but the filters have no effect, clients might not realize their filters aren't being applied. Consider either:

  1. Rejecting unsupported filters in validation until implemented
  2. Documenting this limitation clearly

This is a behavior quirk rather than a correctness bug, so flagging for awareness.

Optional: Fail fast on unsupported filters
 func (i ListInput) Validate() error {
 	var errs []error

 	if i.Namespace == "" {
 		errs = append(errs, errors.New("namespace is required"))
 	}

 	if i.CustomerID == "" {
 		errs = append(errs, errors.New("customer ID is required"))
 	}

+	// Filters are validated but not yet supported downstream
+	if i.FundingMethod != nil || i.Status != nil || i.Currency != nil {
+		errs = append(errs, errors.New("list filters are not yet supported"))
+	}
+
-	if i.FundingMethod != nil {
-		if err := i.FundingMethod.Validate(); err != nil {
-			errs = append(errs, err)
-		}
-	}
-
-	if i.Status != nil {
-		if err := i.Status.Validate(); err != nil {
-			errs = append(errs, err)
-		}
-	}
-
-	if i.Currency != nil {
-		if err := i.Currency.Validate(); err != nil {
-			errs = append(errs, fmt.Errorf("currency: %w", err))
-		}
-	}

 	return models.NewNillableGenericValidationError(errors.Join(errs...))
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/creditgrant/service.go` around lines 144 - 186,
ListInput.Validate currently accepts optional filters (FundingMethod, Status,
Currency) but those filters are ignored downstream; to fail fast, update
ListInput.Validate (the Validate method on the ListInput type) to reject
unsupported filters by appending validation errors when FundingMethod, Status,
or Currency are non-nil (e.g., errors.New("funding method filter not
supported"), errors.New("status filter not supported"), errors.New("currency
filter not supported")), then return them via
models.NewNillableGenericValidationError(errors.Join(errs...)); this ensures
callers get a clear validation error instead of silently ignored filters.
🤖 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/api.gen.go`:
- Around line 8830-8832: Add the same guard used by other credit endpoints to
the CreateCreditAdjustment handler: detect if s.customersCreditsHandler == nil
and if so call unimplemented.CreateCreditAdjustment(w, r, customerId) and
return; modify the CreateCreditAdjustment implementation (the handler that
currently unconditionally calls unimplemented.CreateCreditAdjustment) to perform
this nil-check before delegating to s.customersCreditsHandler so the
credits.enabled feature flag gates the endpoint consistently.

---

Outside diff comments:
In `@api/v3/api.gen.go`:
- Around line 310-323: The generated enum docs for
BillingCreditAvailabilityPolicy are stale—remove the references to the removed
values ("on_authorization", "on_settlement") from the OpenAPI/TypeSpec source
where BillingCreditAvailabilityPolicy is defined, ensure the enum definition
only contains the current member (BillingCreditAvailabilityPolicyOnCreation) and
update its description accordingly, then regenerate api.gen.go so the docblock
and enum values stay in sync and the file contains the required "// Code
generated by ..." header; do not edit api.gen.go directly.

---

Nitpick comments:
In `@openmeter/billing/creditgrant/service.go`:
- Around line 144-186: ListInput.Validate currently accepts optional filters
(FundingMethod, Status, Currency) but those filters are ignored downstream; to
fail fast, update ListInput.Validate (the Validate method on the ListInput type)
to reject unsupported filters by appending validation errors when FundingMethod,
Status, or Currency are non-nil (e.g., errors.New("funding method filter not
supported"), errors.New("status filter not supported"), errors.New("currency
filter not supported")), then return them via
models.NewNillableGenericValidationError(errors.Join(errs...)); this ensures
callers get a clear validation error instead of silently ignored filters.
🪄 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: a311604d-4393-4a84-9acf-9febf3eb8a31

📥 Commits

Reviewing files that changed from the base of the PR and between 66486a7 and d384629.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (8)
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/customers/credits/convert.go
  • api/v3/handlers/customers/credits/get_grant.go
  • api/v3/server/routes.go
  • openmeter/billing/creditgrant/service.go
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/ledger/chargeadapter/creditpurchase.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/v3/handlers/customers/credits/get_grant.go

Comment thread api/v3/api.gen.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

Caution

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

⚠️ Outside diff range comments (2)
api/v3/api.gen.go (2)

3121-3143: ⚠️ Potential issue | 🟡 Minor

Use adjustment-specific field descriptions here.

CreateCreditAdjustmentRequest.Amount and .Currency still describe "granted credits", so the new adjustment payload reads like a grant request in the generated docs. Easy thing to misread on the client side; please update the source descriptions and regenerate.

As per coding guidelines, "All generated files must have // Code generated by X, DO NOT EDIT. headers at the top; never edit files bearing this header".

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

In `@api/v3/api.gen.go` around lines 3121 - 3143, The generated struct
CreateCreditAdjustmentRequest has misleading field docs for Amount and Currency
(they reference "granted credits"); update the source model/comments that define
CreateCreditAdjustmentRequest.Amount and CreateCreditAdjustmentRequest.Currency
in the generator input (not the generated file) to describe adjustment-specific
semantics (e.g., adjustment amount and adjustment currency) and then re-run the
codegen to regenerate api.gen.go so the correct descriptions appear; also ensure
the generated file retains the required "// Code generated by X, DO NOT EDIT."
header.

310-323: ⚠️ Potential issue | 🟡 Minor

Regenerate the credit docs from the updated source contract.

These changes narrow BillingCreditAvailabilityPolicy to on_creation and remove the grant timing fields, but the generated descriptions later in this file still mention on_authorization, on_settlement, effective_at, and expires_at. That makes the published contract read as self-contradictory for API consumers. Please fix the source schema/comments and regenerate instead of patching this file directly.

As per coding guidelines, "All generated files must have // Code generated by X, DO NOT EDIT. headers at the top; never edit files bearing this header".

Also applies to: 3145-3210

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

In `@api/v3/api.gen.go` around lines 310 - 323, The generated file
api.v3/api.gen.go shows BillingCreditAvailabilityPolicy trimmed to only
BillingCreditAvailabilityPolicyOnCreation but the later documentation still
references removed enum values (on_authorization, on_settlement) and fields
(effective_at, expires_at); do not hand-edit this generated file—update the
source schema and/or comment blocks that define BillingCreditAvailabilityPolicy
and the related grant-timing fields in the source contract so the descriptive
text matches the new enum, then re-run the generator to regenerate
api.v3/api.gen.go (and any other affected generated outputs), and ensure the
regenerated files include the required "// Code generated by X, DO NOT EDIT."
header.
🧹 Nitpick comments (1)
api/spec/packages/aip/src/customers/credits/operations.tsp (1)

81-98: Prefer removing retired operations instead of keeping them commented out.

Small cleanup suggestion: delete this commented interface block so the spec stays a clean source of truth (and avoids confusion during future contract scans).

As per coding guidelines: “The declared API should be accurate, in parity with the actual implementation, and easy to understand for the user.”

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

In `@api/spec/packages/aip/src/customers/credits/operations.tsp` around lines 81 -
98, Remove the retired commented interface block for
CustomerCreditGrantVoidOperations (including the voidGrant method and its
annotations such as `@operationId`("void-credit-grant")) from the spec file so the
API surface only contains active declarations; locate the commented interface
block referencing CustomerCreditGrantVoidOperations and voidGrant and delete the
entire commented-out section to keep the spec clean and accurate.
🤖 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_grants.go`:
- Around line 69-72: The conversion function convertAPIStatusToChargeStatus
currently only handles active and pending and does an unsafe cast for other
BillingCreditGrantStatus values; update convert.go to explicitly handle
BillingCreditGrantStatusExpired and BillingCreditGrantStatusVoided (either map
them to appropriate meta.ChargeStatus constants or return an explicit
error/unsupported value), avoid the unchecked meta.ChargeStatus(status)
fallback, and then adjust the caller in list_grants.go (where
convertAPIStatusToChargeStatus is used to set req.Status) to handle the new
error/unsupported-case (e.g., skip setting req.Status or return a validation
error) so filtering won’t produce invalid meta.ChargeStatus values.

---

Outside diff comments:
In `@api/v3/api.gen.go`:
- Around line 3121-3143: The generated struct CreateCreditAdjustmentRequest has
misleading field docs for Amount and Currency (they reference "granted
credits"); update the source model/comments that define
CreateCreditAdjustmentRequest.Amount and CreateCreditAdjustmentRequest.Currency
in the generator input (not the generated file) to describe adjustment-specific
semantics (e.g., adjustment amount and adjustment currency) and then re-run the
codegen to regenerate api.gen.go so the correct descriptions appear; also ensure
the generated file retains the required "// Code generated by X, DO NOT EDIT."
header.
- Around line 310-323: The generated file api.v3/api.gen.go shows
BillingCreditAvailabilityPolicy trimmed to only
BillingCreditAvailabilityPolicyOnCreation but the later documentation still
references removed enum values (on_authorization, on_settlement) and fields
(effective_at, expires_at); do not hand-edit this generated file—update the
source schema and/or comment blocks that define BillingCreditAvailabilityPolicy
and the related grant-timing fields in the source contract so the descriptive
text matches the new enum, then re-run the generator to regenerate
api.v3/api.gen.go (and any other affected generated outputs), and ensure the
regenerated files include the required "// Code generated by X, DO NOT EDIT."
header.

---

Nitpick comments:
In `@api/spec/packages/aip/src/customers/credits/operations.tsp`:
- Around line 81-98: Remove the retired commented interface block for
CustomerCreditGrantVoidOperations (including the voidGrant method and its
annotations such as `@operationId`("void-credit-grant")) from the spec file so the
API surface only contains active declarations; locate the commented interface
block referencing CustomerCreditGrantVoidOperations and voidGrant and delete the
entire commented-out section to keep the spec clean and accurate.
🪄 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: 70ca4e8b-6cc9-4edb-ba3a-a549cfa7a24e

📥 Commits

Reviewing files that changed from the base of the PR and between d384629 and bd29012.

⛔ Files ignored due to path filters (1)
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
📒 Files selected for processing (23)
  • api/spec/packages/aip/src/customers/credits/adjustment.tsp
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/spec/packages/aip/src/customers/credits/operations.tsp
  • api/spec/packages/aip/src/konnect.tsp
  • api/spec/packages/aip/src/openmeter.tsp
  • api/types/entitlement.go
  • api/v3/api.gen.go
  • api/v3/handlers/customers/credits/convert.go
  • api/v3/handlers/customers/credits/create_grant.go
  • api/v3/handlers/customers/credits/get_balance.go
  • api/v3/handlers/customers/credits/get_grant.go
  • api/v3/handlers/customers/credits/handler.go
  • api/v3/handlers/customers/credits/list_grants.go
  • api/v3/server/routes.go
  • api/v3/server/server.go
  • app/common/creditgrant.go
  • cmd/server/main.go
  • cmd/server/wire.go
  • cmd/server/wire_gen.go
  • openmeter/billing/creditgrant/service.go
  • openmeter/billing/creditgrant/service/service.go
  • openmeter/server/router/router.go
  • openmeter/server/server.go
💤 Files with no reviewable changes (1)
  • api/types/entitlement.go
✅ Files skipped from review due to trivial changes (4)
  • cmd/server/main.go
  • openmeter/server/server.go
  • api/spec/packages/aip/src/customers/credits/adjustment.tsp
  • api/v3/handlers/customers/credits/convert.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • api/v3/handlers/customers/credits/get_balance.go
  • api/v3/handlers/customers/credits/handler.go
  • api/v3/server/routes.go
  • api/spec/packages/aip/src/customers/credits/grant.tsp
  • api/spec/packages/aip/src/konnect.tsp
  • api/v3/handlers/customers/credits/create_grant.go
  • cmd/server/wire.go
  • openmeter/billing/creditgrant/service.go

Comment thread api/v3/handlers/customers/credits/list_grants.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: 2

🧹 Nitpick comments (1)
api/v3/handlers/customers/credits/convert.go (1)

84-88: Silent nil returns may mask conversion failures

When AsInvoiceSettlement() or AsExternalSettlement() return errors (lines 86-88, 107-109), or when convertAvailabilityPolicy fails (lines 114-116), the function silently returns nil. This makes it indistinguishable from a legitimate promotional grant.

If these errors represent data inconsistencies (e.g., settlement type says "invoice" but the data can't be decoded as invoice settlement), returning nil could hide bugs. Consider logging a warning or returning an error so callers can handle the unexpected case.

💡 One option: return an error alongside the purchase
-func convertPurchase(charge creditpurchase.Charge) *creditGrantPurchase {
+func convertPurchase(charge creditpurchase.Charge) (*creditGrantPurchase, error) {
 	settlement := charge.Intent.Settlement
 
 	switch settlement.Type() {
 	case creditpurchase.SettlementTypeInvoice:
 		inv, err := settlement.AsInvoiceSettlement()
 		if err != nil {
-			return nil
+			return nil, fmt.Errorf("failed to convert invoice settlement: %w", err)
 		}
 		// ... rest of invoice case
 
 	case creditpurchase.SettlementTypeExternal:
 		ext, err := settlement.AsExternalSettlement()
 		if err != nil {
-			return nil
+			return nil, fmt.Errorf("failed to convert external settlement: %w", err)
 		}
 		// ... rest of external case
 
 	default:
-		return nil
+		return nil, nil // Promotional grants have no purchase block
 	}
 }

Also applies to: 105-116

🤖 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 84 - 88, The
handler currently swallows decoding/conversion failures in cases like
creditpurchase.SettlementTypeInvoice when settlement.AsInvoiceSettlement(),
settlement.AsExternalSettlement(), and convertAvailabilityPolicy fail by
returning nil; instead, change these branches to surface the error (or at
minimum log a warning with context) so callers can distinguish a real
promotional grant from a conversion failure. Specifically, in the branches that
call AsInvoiceSettlement, AsExternalSettlement, and convertAvailabilityPolicy,
replace the silent "return nil" with returning a descriptive error (or calling
your logger with the error and relevant identifiers) and propagate that error
out of the surrounding function so upstream code can handle/alert on data
inconsistencies.
🤖 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 241-243: The switch's default branch blindly casts an unknown API
status string to meta.ChargeStatus, producing an invalid downstream value;
instead update the default branch to return a well-defined sentinel (e.g.,
meta.ChargeStatusUnknown) or an error: modify the default case in the
status-conversion function to map unknown strings to meta.ChargeStatusUnknown
(create that constant in the meta package if it doesn't exist) or change the
function signature to return (meta.ChargeStatus, error) and return a descriptive
error for unknown statuses rather than casting arbitrarily.
- Around line 35-38: The conversion currently casts charge.Intent.Priority (a
*int) directly to int16 and assigns to grant.Priority, which can overflow if
domain input is out of expected range; update the conversion (the block handling
charge.Intent.Priority -> grant.Priority) to validate the value is within the
allowed bounds (e.g., 1..1000 as per API spec) before casting—if the pointer is
non-nil and value out of range, return/propagate a validation error (or handle
per existing error flow) instead of blindly casting; reference
charge.Intent.Priority, grant.Priority, and Intent.Validate() when adding this
check.

---

Nitpick comments:
In `@api/v3/handlers/customers/credits/convert.go`:
- Around line 84-88: The handler currently swallows decoding/conversion failures
in cases like creditpurchase.SettlementTypeInvoice when
settlement.AsInvoiceSettlement(), settlement.AsExternalSettlement(), and
convertAvailabilityPolicy fail by returning nil; instead, change these branches
to surface the error (or at minimum log a warning with context) so callers can
distinguish a real promotional grant from a conversion failure. Specifically, in
the branches that call AsInvoiceSettlement, AsExternalSettlement, and
convertAvailabilityPolicy, replace the silent "return nil" with returning a
descriptive error (or calling your logger with the error and relevant
identifiers) and propagate that error out of the surrounding function so
upstream code can handle/alert on data inconsistencies.
🪄 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: 8db8844f-21d6-43d7-b5a4-dab1e33e4637

📥 Commits

Reviewing files that changed from the base of the PR and between bd29012 and 18d8d96.

📒 Files selected for processing (2)
  • api/v3/handlers/customers/credits/convert.go
  • api/v3/handlers/customers/credits/list_grants.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/v3/handlers/customers/credits/list_grants.go

Comment thread api/v3/handlers/customers/credits/convert.go
Comment thread api/v3/handlers/customers/credits/convert.go
Comment thread api/v3/handlers/customers/credits/convert.go Outdated
Comment thread openmeter/billing/creditgrant/service/service.go Outdated
turip
turip previously approved these changes Apr 7, 2026
@tothandras tothandras force-pushed the feat/api-v3-credit-grants branch from 2df7efe to ea120fb Compare April 7, 2026 11:41
@tothandras tothandras enabled auto-merge (squash) April 7, 2026 11:42
@tothandras tothandras merged commit ad4379a into main Apr 7, 2026
24 checks passed
@tothandras tothandras deleted the feat/api-v3-credit-grants branch April 7, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants