Skip to content

feat(ledger): add custom currencies support (credit_only) - #4731

Merged
mark-vass-konghq merged 6 commits into
mainfrom
feat/ledger-cc-support
Jul 31, 2026
Merged

feat(ledger): add custom currencies support (credit_only)#4731
mark-vass-konghq merged 6 commits into
mainfrom
feat/ledger-cc-support

Conversation

@mark-vass-konghq

@mark-vass-konghq mark-vass-konghq commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds the ledger foundation for custom-currency credit_only flows.

Custom credits can now be issued, allocated, corrected, and routed without
applying fiat precision or coalescing different managed currencies that share
the same code.

What changed

  • Added custom-currency support to ledger routes and persisted route metadata.
  • Switched ledger routing and collection filters to composite currency identity
    (CurrencyReference / managed custom-currency ID).
  • Preserved optional fiat source currency and cost basis for funded custom
    credits.
  • Kept promotional custom credits source-less and without a cost basis.
  • Added custom-currency handling to credit purchase lifecycle events:
    • credit purchase initiation;
    • payment authorization;
    • payment settlement;
    • promotional credit issuance.
  • Preserved feature filters across the credit purchase lifecycle.
  • Ensured collection separates custom currencies with the same code but
    different managed IDs.
  • Added custom-currency support to allocation, correction, breakage, and
    credit-void ledger paths.
  • Made payment and invoice amount creation the single owner of fiat rounding
    and passed the exact FiatAmount through lifecycle events, so payment records
    and ledger entries use the same amount without ledger-side recalculation.

Credit-only behavior

For a custom-currency credit_only charge:

  • credit balances remain denominated in the custom currency;
  • uncovered usage becomes custom-currency exposure;
  • promotional credits do not create a fiat source or cost basis;
  • fiat-funded credits retain their source fiat currency and cost basis;
  • the ledger does not convert custom credit consumption to fiat;
  • invoice and subscription-level end-to-end behavior is not introduced by this
    PR.

Tests

Added and updated coverage for:

  • custom and fiat route validation;
  • custom-currency precision handling;
  • composite currency routing;
  • same-code managed custom currencies;
  • source-less and fiat-funded custom credits;
  • credit purchase initiation, authorization, and settlement;
  • feature-filtered custom-currency routes;
  • promotional credit issuance and correction;
  • fractional remainder/backfill allocation;
  • exact fiat amount propagation across external-payment, invoice,
    authorization, and settlement flows;
  • custom-currency credit-only allocation and correction.

@mark-vass-konghq mark-vass-konghq self-assigned this Jul 16, 2026
@mark-vass-konghq
mark-vass-konghq requested a review from a team as a code owner July 16, 2026 21:53
@mark-vass-konghq mark-vass-konghq added release-note/feature Release note: Exciting New Features area/billing labels Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

Ledger routing now supports exchange source currencies and v3 keys. FX conversion uses customer receivable routes with explicit source amounts, transaction validation balances per currency, and invoice APIs expose unit configuration snapshots.

Changes

Ledger currency routing and persistence

Layer / File(s) Summary
Exchange-source currency routing and persistence
openmeter/ledger/routing.go, openmeter/ledger/accounts.go, openmeter/ledger/account/..., openmeter/ent/schema/ledger_account.go, openmeter/ledger/collector/*, openmeter/ledger/historical/...
Routes, filters, routing keys, account resolution, persistence, historical queries, and correction flows preserve exchange source currency values.
Currency-aware transaction validation
openmeter/ledger/primitives.go, openmeter/ledger/validations.go, openmeter/ledger/validations_test.go
Currency validation uses explicit checks, transaction invariance is evaluated per currency, and entry precision validation is removed.
Receivable-based FX templates
openmeter/ledger/transactions/*, openmeter/ledger/routingrules/defaults.go
Conversion uses explicit source amounts, customer receivable routes, fiat/custom validation, aggregated errors, and legacy template-code lookup compatibility.
FX lifecycle coverage
openmeter/ledger/transactions/fx_test.go, openmeter/ledger/transactions/customer_test.go, openmeter/ledger/collector/correct_test.go
Tests cover conversion postings, validation errors, route preservation, balances, partial spending, and currency-filtered transactions.
Invoice rate-card unit configuration contracts
api/spec/..., api/v3/client/models_invoices.go, api/v3/handlers/billinginvoices/*, e2e/billinginvoices_v3_test.go
Invoice schemas and clients expose optional unit configuration snapshots, update payloads use a dedicated rate-card type, and API mapping tests cover present and absent configuration.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConvertCurrencyTemplate
  participant CustomerReceivable
  participant Ledger
  ConvertCurrencyTemplate->>CustomerReceivable: resolve exchange-aware source and target subaccounts
  ConvertCurrencyTemplate->>Ledger: submit source and target currency postings
  Ledger-->>ConvertCurrencyTemplate: validate per-currency totals and record transaction
Loading

Possibly related PRs

Suggested labels: release-note/feature, area/billing

Suggested reviewers: galexihu, turip

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding custom currency support to the ledger.
✨ 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/ledger-cc-support

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.

Comment thread pkg/currencyx/currency.go
Comment thread pkg/currencyx/currency.go
Comment thread pkg/currencyx/currency.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (2)
openmeter/billing/rating/service/rate/types.go (1)

32-46: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Collect all validation failures in Validate().
pkg/models.NewNillableGenericValidationError already exists, so this can accumulate the checks into errs and return models.NewNillableGenericValidationError(errors.Join(errs...)) instead of stopping at the first missing field.

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

In `@openmeter/billing/rating/service/rate/types.go` around lines 32 - 46, Update
PricerCalculateInput.Validate to collect every missing-field validation error in
an errs slice instead of returning immediately; append failures for
CurrencyCalculator, FullProgressivelyBilledServicePeriod, and
StandardLineAccessor, then return
models.NewNillableGenericValidationError(errors.Join(errs...)) so nil is
returned when there are no errors.

Source: Coding guidelines

openmeter/billing/worker/subscriptionsync/service/reconciler/reconciler.go (1)

106-108: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against nil Currency.

Good catch bringing in currencyx.Currency! Just a heads-up: if an ApplyInput happens to be instantiated with a nil Currency field, calling i.Currency.Validate() right here will cause a panic. Might be worth adding a quick nil-check first to keep things super safe!

🛡️ Proposed fix
-	if err := i.Currency.Validate(); err != nil {
-		errs = append(errs, fmt.Errorf("currency: %w", err))
-	}
+	if i.Currency == nil {
+		errs = append(errs, fmt.Errorf("currency is required"))
+	} else if err := i.Currency.Validate(); err != nil {
+		errs = append(errs, fmt.Errorf("currency: %w", err))
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/billing/worker/subscriptionsync/service/reconciler/reconciler.go`
around lines 106 - 108, Update the validation logic in the ApplyInput
reconciliation flow to check whether i.Currency is nil before calling
Currency.Validate(); when nil, append an appropriate currency validation error
instead of dereferencing it, while preserving the existing wrapped error
behavior for non-nil currencies.
🧹 Nitpick comments (5)
pkg/currencyx/currency_test.go (1)

244-357: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the remaining formatter and directional-rounding branches.

Please add custom cases using WithDecimalMark and WithThousandsSeparator, plus negative inputs for RoundUp and RoundDown. The current suite would miss broken setter wiring or reversed negative-rounding behavior.

As per path instructions, “Make sure the tests are comprehensive and cover the changes.”

Also applies to: 415-427

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

In `@pkg/currencyx/currency_test.go` around lines 244 - 357, Extend
TestFormatAmount with custom-currency cases that configure WithDecimalMark and
WithThousandsSeparator and assert both separators are reflected in formatted
output. Add negative-value cases covering RoundUp and RoundDown, asserting their
directional rounding behavior; keep the existing table-driven setup and currency
construction flow.

Source: Path instructions

openmeter/billing/charges/flatfee/charge.go (1)

186-188: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update stale error messages to reflect the new currency type.

Hey! 👋 Just a tiny stylistic note. With the great refactor from currencyx.Calculator to currencyx.Currency built via NewCurrencyBuilder, there are a few error messages left behind that still refer to a "currency calculator" instead of the new currency type. It might be nice to update these to keep the terminology consistent.

  • openmeter/billing/charges/flatfee/charge.go#L186-L188: Update the error message to "building currency: %w".
  • openmeter/billing/charges/flatfee/service/linemapper.go#L18-L20: Update the error message to "building currency: %w".
  • openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go#L73-L75: Update the error message to "building currency: %w".
  • openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go#L261-L263: Update the error message to "building currency: %w".
  • openmeter/billing/charges/usagebased/service/linemapper.go#L131-L133: Update the error message to "building currency: %w".
  • openmeter/billing/charges/usagebased/service/linemapper.go#L198-L200: Update the parameter name to currency and the error message to "currency is required".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/billing/charges/flatfee/charge.go` around lines 186 - 188, Update
stale currency terminology in the listed error paths: in
openmeter/billing/charges/flatfee/charge.go:186-188,
openmeter/billing/charges/flatfee/service/linemapper.go:18-20, both sites in
openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go:73-75
and 261-263, and
openmeter/billing/charges/usagebased/service/linemapper.go:131-133, change the
wrapped error prefix to “building currency: %w”. In
openmeter/billing/charges/usagebased/service/linemapper.go:198-200, rename the
parameter to currency and update the required-value error to “currency is
required”.
openmeter/ledger/chargeadapter/usagebased.go (1)

289-296: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Update the error message to reflect the new builder.

Since the code now dynamically builds a currency instead of fetching a calculator, consider tweaking the error context to accurately reflect the action being performed.

💡 Suggested tweak
 	currency, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat).
 		WithCode(intent.GetCurrency()).
 		Build()
 	if err != nil {
-		return nil, fmt.Errorf("get currency calculator: %w", err)
+		return nil, fmt.Errorf("build currency: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/ledger/chargeadapter/usagebased.go` around lines 289 - 296, Update
the error context in the currency construction block of the charge calculation
flow to describe building or creating the currency rather than fetching a
currency calculator. Keep the existing wrapped error and validation behavior
unchanged.
openmeter/billing/charges/usagebased/service/run/correct.go (1)

51-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify redundant nil checks.

Hey! Since we're returning early just above if i.CurrencyCalculator is nil, we don't need to wrap the next validation step in if i.CurrencyCalculator != nil again. We can simplify both of these methods nicely!

  • openmeter/billing/charges/usagebased/service/run/correct.go#L51-L59: remove the if i.CurrencyCalculator != nil wrapper in ReconcileCreditRealizationsInput.Validate().
  • openmeter/billing/charges/usagebased/service/run/correct.go#L165-L173: remove the if i.CurrencyCalculator != nil wrapper in CorrectAllCreditRealizationsInput.Validate().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/billing/charges/usagebased/service/run/correct.go` around lines 51
- 59, Remove the redundant nil-check wrappers in
ReconcileCreditRealizationsInput.Validate() at
openmeter/billing/charges/usagebased/service/run/correct.go:51-59 and
CorrectAllCreditRealizationsInput.Validate() at
openmeter/billing/charges/usagebased/service/run/correct.go:165-173. After each
existing nil early return, call CurrencyCalculator.Validate() directly while
preserving the current wrapped error behavior.
openmeter/billing/rating/service/rate/tieredgraduated.go (1)

133-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the currency presence check.

Hey! Since the || operator short-circuits in Go, if we make it past the i.Currency == nil check, we already know i.Currency is not nil. We can safely drop the redundant i.Currency != nil && piece to make this a bit cleaner!

♻️ Proposed fix
-	if i.Currency == nil || i.Currency != nil && i.Currency.Details().Code == "" {
+	if i.Currency == nil || i.Currency.Details().Code == "" {
 		return fmt.Errorf("currency is required")
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/billing/rating/service/rate/tieredgraduated.go` around lines 133 -
135, In the currency validation condition, simplify the redundant nil check
after the short-circuiting `i.Currency == nil` check. Update the validation in
the surrounding rate logic to directly test `i.Currency.Details().Code == ""`
for the non-nil case, preserving the existing “currency is required” error
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/v3/handlers/customers/credits/convert.go`:
- Around line 106-112: Update the currency construction used before
purchaseAmount rounding to use the settlement currency (`inv.Currency` or
`ext.Currency`) that denominates `CreditAmount × CostBasis`, rather than
`charge.Intent.Currency`. Apply the same change to both currency-building
occurrences so `RoundToPrecision` uses the API-returned currency’s precision and
supports custom credit currencies.

In `@openmeter/app/stripe/calculator.go`:
- Around line 43-52: Update StripeCalculator.FormatAmount to delegate directly
to c.currency.FormatAmount with the original alpacadecimal.Decimal, removing the
integer special case and Float64 conversion. Preserve exact formatting and avoid
ignoring conversion accuracy.

In `@openmeter/billing/charges/creditpurchase/charge.go`:
- Around line 164-169: Update the normalization flow around the currency builder
and CreditAmount assignment to resolve a currencyx.Currency for both standard
and custom credit currencies, preserving custom precision instead of silently
skipping rounding. Thread the resolved currency into the normalization
operation, and explicitly propagate or return currency-resolution errors rather
than ignoring Build failures.

In `@openmeter/billing/charges/usagebased/service/run/preview.go`:
- Around line 77-84: Update validation methods to accumulate all errors in errs
and return models.NewNillableGenericValidationError(errors.Join(errs...))
instead of returning early. In
openmeter/billing/charges/usagebased/service/run/preview.go (77-84) and
service/statemachine.go (82-89), append CurrencyCalculator validation failures
and use else-if; statemachine.go must wrap the final return. Apply the same errs
accumulation and wrapped return in
openmeter/billing/charges/flatfee/service/realizations/creditsonly.go (33-40),
service/run/create.go (85-92), and service/run/credits.go (78-85). In
openmeter/billing/charges/usagebased/handler.go (82-89), replace the redundant
currency != nil check with an else-if chained to the nil check.

In `@openmeter/ledger/chargeadapter/creditpurchase.go`:
- Around line 463-468: Update the caller around the currency resolution and
advanceAttributions invocation to pass the already-resolved currencyx.Currency
instead of rebuilding charge.Intent.Currency as CurrencyTypeFiat. Remove the
redundant fiat-only construction and preserve the existing error handling for
resolving the currency, so custom credit currency codes reach allocation
successfully.

In `@openmeter/ledger/transactions/codes.go`:
- Line 28: Rename the legacy currency conversion constant from
legacyTemplateCodeConvertCurrency to
transactionTemplateCodeConvertCurrencyLegacy, preserving its
TransactionTemplateCode type and string value.

In `@openmeter/ledger/transactions/fx.go`:
- Around line 27-60: The Validate methods must aggregate all validation failures
instead of returning early, then return
models.NewNillableGenericValidationError(errors.Join(errs...)). In
openmeter/ledger/transactions/fx.go, update ConvertCurrencyTemplate.Validate to
append each prefixed amount, cost-basis, and currency error while preserving all
validation checks. In openmeter/ledger/transactions/customer.go, update the
source validation at lines 46-48 to append its prefixed error to the existing
aggregate; no other validation behavior should change.
- Around line 32-45: The FX transaction validation currently checks positivity
but not consistency between amounts and cost basis. In
openmeter/ledger/transactions/fx.go lines 32-45, update the validation around
ValidateTransactionAmount and ValidateCostBasis to verify SourceAmount equals
TargetAmount multiplied by CostBasis using the applicable currency rounding
rules. In openmeter/ledger/transactions/fx_test.go lines 72-156, add a positive
but inconsistent amount/cost-basis case and assert that transaction validation
rejects it.

In `@pkg/currencyx/allocation.go`:
- Around line 249-263: Update the currency validation flow to stop dereferencing
a nil currency after recording the “currency is required” error. Guard
currency.Validate and currency.IsRoundedToPrecision behind a non-nil check while
preserving amount validation and existing error accumulation.

In `@pkg/currencyx/currency.go`:
- Around line 176-193: Update both currency Validate methods, including
FiatCurrency.Validate and the validator covering the referenced later range, to
wrap each validation failure with its field context before adding it to errs.
Use the appropriate fields code, name, and precision, then preserve
errors.Join(errs...) inside models.NewNillableGenericValidationError so all
failures remain collected with structured attribution.

---

Outside diff comments:
In `@openmeter/billing/rating/service/rate/types.go`:
- Around line 32-46: Update PricerCalculateInput.Validate to collect every
missing-field validation error in an errs slice instead of returning
immediately; append failures for CurrencyCalculator,
FullProgressivelyBilledServicePeriod, and StandardLineAccessor, then return
models.NewNillableGenericValidationError(errors.Join(errs...)) so nil is
returned when there are no errors.

In `@openmeter/billing/worker/subscriptionsync/service/reconciler/reconciler.go`:
- Around line 106-108: Update the validation logic in the ApplyInput
reconciliation flow to check whether i.Currency is nil before calling
Currency.Validate(); when nil, append an appropriate currency validation error
instead of dereferencing it, while preserving the existing wrapped error
behavior for non-nil currencies.

---

Nitpick comments:
In `@openmeter/billing/charges/flatfee/charge.go`:
- Around line 186-188: Update stale currency terminology in the listed error
paths: in openmeter/billing/charges/flatfee/charge.go:186-188,
openmeter/billing/charges/flatfee/service/linemapper.go:18-20, both sites in
openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go:73-75
and 261-263, and
openmeter/billing/charges/usagebased/service/linemapper.go:131-133, change the
wrapped error prefix to “building currency: %w”. In
openmeter/billing/charges/usagebased/service/linemapper.go:198-200, rename the
parameter to currency and update the required-value error to “currency is
required”.

In `@openmeter/billing/charges/usagebased/service/run/correct.go`:
- Around line 51-59: Remove the redundant nil-check wrappers in
ReconcileCreditRealizationsInput.Validate() at
openmeter/billing/charges/usagebased/service/run/correct.go:51-59 and
CorrectAllCreditRealizationsInput.Validate() at
openmeter/billing/charges/usagebased/service/run/correct.go:165-173. After each
existing nil early return, call CurrencyCalculator.Validate() directly while
preserving the current wrapped error behavior.

In `@openmeter/billing/rating/service/rate/tieredgraduated.go`:
- Around line 133-135: In the currency validation condition, simplify the
redundant nil check after the short-circuiting `i.Currency == nil` check. Update
the validation in the surrounding rate logic to directly test
`i.Currency.Details().Code == ""` for the non-nil case, preserving the existing
“currency is required” error behavior.

In `@openmeter/ledger/chargeadapter/usagebased.go`:
- Around line 289-296: Update the error context in the currency construction
block of the charge calculation flow to describe building or creating the
currency rather than fetching a currency calculator. Keep the existing wrapped
error and validation behavior unchanged.

In `@pkg/currencyx/currency_test.go`:
- Around line 244-357: Extend TestFormatAmount with custom-currency cases that
configure WithDecimalMark and WithThousandsSeparator and assert both separators
are reflected in formatted output. Add negative-value cases covering RoundUp and
RoundDown, asserting their directional rounding behavior; keep the existing
table-driven setup and currency construction flow.
🪄 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: 963426f0-fe72-436f-8c33-be299383ac22

📥 Commits

Reviewing files that changed from the base of the PR and between f1eac14 and 27b9295.

⛔ Files ignored due to path filters (8)
  • openmeter/ent/db/ledgersubaccountroute.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute/ledgersubaccountroute.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (87)
  • .agents/skills/currencyx/SKILL.md
  • .agents/skills/currencyx/agents/openai.yaml
  • api/v3/handlers/customers/credits/convert.go
  • openmeter/app/stripe/calculator.go
  • openmeter/billing/charges/creditpurchase/charge.go
  • openmeter/billing/charges/creditpurchase/service/create.go
  • openmeter/billing/charges/creditpurchase/service/external_test.go
  • openmeter/billing/charges/flatfee/charge.go
  • openmeter/billing/charges/flatfee/handler.go
  • openmeter/billing/charges/flatfee/service/creditsonly.go
  • openmeter/billing/charges/flatfee/service/lineengine.go
  • openmeter/billing/charges/flatfee/service/linemapper.go
  • openmeter/billing/charges/flatfee/service/realizations/correct.go
  • openmeter/billing/charges/flatfee/service/realizations/creditsonly.go
  • openmeter/billing/charges/flatfee/service/realizations/credittheninvoice.go
  • openmeter/billing/charges/flatfee/service/realizations/preview.go
  • openmeter/billing/charges/models/creditrealization/correction.go
  • openmeter/billing/charges/models/creditrealization/correction_test.go
  • openmeter/billing/charges/models/creditrealization/realizations.go
  • openmeter/billing/charges/usagebased/handler.go
  • openmeter/billing/charges/usagebased/service/creditsonly_test.go
  • openmeter/billing/charges/usagebased/service/lineengine.go
  • openmeter/billing/charges/usagebased/service/linemapper.go
  • openmeter/billing/charges/usagebased/service/run/correct.go
  • openmeter/billing/charges/usagebased/service/run/create.go
  • openmeter/billing/charges/usagebased/service/run/credits.go
  • openmeter/billing/charges/usagebased/service/run/preview.go
  • openmeter/billing/charges/usagebased/service/statemachine.go
  • openmeter/billing/charges/usagebased/service/triggers.go
  • openmeter/billing/invoicedetailedline.go
  • openmeter/billing/invoicedetailedline_test.go
  • openmeter/billing/invoicelinediscount.go
  • openmeter/billing/models/creditsapplied/model.go
  • openmeter/billing/models/totals/model.go
  • openmeter/billing/models/totals/model_test.go
  • openmeter/billing/rating/detailedline.go
  • openmeter/billing/rating/detailedline_test.go
  • openmeter/billing/rating/service/detailedline.go
  • openmeter/billing/rating/service/mutator/credits_test.go
  • openmeter/billing/rating/service/mutator/discountpercentage.go
  • openmeter/billing/rating/service/rate/tieredgraduated.go
  • openmeter/billing/rating/service/rate/tieredgraduated_test.go
  • openmeter/billing/rating/service/rate/types.go
  • openmeter/billing/worker/subscriptionsync/service/reconcile.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge_test.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchchargeflatfee.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchchargeusagebased.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/reconciler.go
  • openmeter/billing/worker/subscriptionsync/service/sync.go
  • openmeter/billing/worker/subscriptionsync/service/targetstate/targetstate.go
  • openmeter/billing/worker/subscriptionsync/service/targetstate/targetstateitem.go
  • openmeter/ent/schema/ledger_account.go
  • openmeter/ledger/account/adapter/subaccount.go
  • openmeter/ledger/accounts.go
  • openmeter/ledger/accounts_test.go
  • openmeter/ledger/chargeadapter/creditpurchase.go
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/chargeadapter/flatfee_test.go
  • openmeter/ledger/chargeadapter/usagebased.go
  • openmeter/ledger/chargeadapter/usagebased_test.go
  • openmeter/ledger/collector/correct_test.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/ledger_fx_test.go
  • openmeter/ledger/primitives.go
  • openmeter/ledger/routing.go
  • openmeter/ledger/routing_test.go
  • openmeter/ledger/routingrules/defaults.go
  • openmeter/ledger/transactions/accrual.go
  • openmeter/ledger/transactions/codes.go
  • openmeter/ledger/transactions/correction_leg.go
  • openmeter/ledger/transactions/customer.go
  • openmeter/ledger/transactions/fx.go
  • openmeter/ledger/transactions/fx_test.go
  • openmeter/ledger/validations.go
  • openmeter/ledger/validations_test.go
  • pkg/currencyx/allocation.go
  • pkg/currencyx/allocation_test.go
  • pkg/currencyx/code.go
  • pkg/currencyx/costbasis.go
  • pkg/currencyx/currency.go
  • pkg/currencyx/currency_test.go
  • pkg/currencyx/fiat.go
  • pkg/currencyx/fiat_test.go
  • pkg/currencyx/validation.go
  • test/app/stripe/invoice_credits_test.go
  • tools/migrate/migrations/20260716143421_add_ledger_sub_account_source.down.sql
  • tools/migrate/migrations/20260716143421_add_ledger_sub_account_source.up.sql
💤 Files with no reviewable changes (6)
  • pkg/currencyx/fiat_test.go
  • openmeter/ledger/ledger_fx_test.go
  • pkg/currencyx/validation.go
  • .agents/skills/currencyx/SKILL.md
  • .agents/skills/currencyx/agents/openai.yaml
  • pkg/currencyx/fiat.go

Comment thread api/v3/handlers/customers/credits/convert.go Outdated
Comment thread openmeter/app/stripe/calculator.go
Comment thread openmeter/billing/charges/creditpurchase/charge.go Outdated
Comment thread openmeter/billing/charges/usagebased/service/run/preview.go
Comment thread openmeter/ledger/chargeadapter/creditpurchase.go Outdated
Comment thread openmeter/ledger/transactions/codes.go Outdated
Comment thread openmeter/ledger/transactions/fx.go Outdated
Comment thread openmeter/ledger/transactions/fx.go Outdated
Comment thread pkg/currencyx/allocation.go
Comment thread pkg/currencyx/currency.go
@mark-vass-konghq
mark-vass-konghq force-pushed the feat/ledger-cc-support branch from 27b9295 to df5d509 Compare July 17, 2026 07:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
openmeter/ledger/account/adapter/subaccount.go (1)

159-170: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Bump slice capacity to 9.

Hey there! With the new Source field added, there are now up to 9 possible predicates appended in this block. Bumping the capacity up to 9 is a tiny optimization that will save a reallocation when a route filter has all its fields present.

✨ Proposed fix
-		routePredicates := make([]predicate.LedgerSubAccountRoute, 0, 8)
+		routePredicates := make([]predicate.LedgerSubAccountRoute, 0, 9)
		if normalizedRoute.Currency != "" {
			routePredicates = append(routePredicates, dbledgersubaccountroute.Currency(string(normalizedRoute.Currency)))
		}
		if normalizedRoute.Source.IsPresent() {
			source, _ := normalizedRoute.Source.Get()
			if source != nil {
				routePredicates = append(routePredicates, dbledgersubaccountroute.Source(*source))
			} else {
				routePredicates = append(routePredicates, dbledgersubaccountroute.SourceIsNil())
			}
		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/ledger/account/adapter/subaccount.go` around lines 159 - 170,
Increase the initial capacity of routePredicates in the subaccount route-filter
construction from 8 to 9, keeping the existing predicate append logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@openmeter/ledger/account/adapter/subaccount.go`:
- Around line 159-170: Increase the initial capacity of routePredicates in the
subaccount route-filter construction from 8 to 9, keeping the existing predicate
append logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0cebdd96-1574-4487-8719-a4831f213358

📥 Commits

Reviewing files that changed from the base of the PR and between 27b9295 and df5d509.

⛔ Files ignored due to path filters (8)
  • openmeter/ent/db/ledgersubaccountroute.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute/ledgersubaccountroute.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ledgersubaccountroute_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (20)
  • openmeter/ent/schema/ledger_account.go
  • openmeter/ledger/account/adapter/subaccount.go
  • openmeter/ledger/accounts.go
  • openmeter/ledger/accounts_test.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/ledger_fx_test.go
  • openmeter/ledger/primitives.go
  • openmeter/ledger/routing.go
  • openmeter/ledger/routing_test.go
  • openmeter/ledger/routingrules/defaults.go
  • openmeter/ledger/transactions/accrual.go
  • openmeter/ledger/transactions/codes.go
  • openmeter/ledger/transactions/correction_leg.go
  • openmeter/ledger/transactions/customer.go
  • openmeter/ledger/transactions/fx.go
  • openmeter/ledger/transactions/fx_test.go
  • openmeter/ledger/validations.go
  • openmeter/ledger/validations_test.go
  • tools/migrate/migrations/20260716220228_add_ledger_sub_account_source.down.sql
  • tools/migrate/migrations/20260716220228_add_ledger_sub_account_source.up.sql
💤 Files with no reviewable changes (1)
  • openmeter/ledger/ledger_fx_test.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • openmeter/ledger/routingrules/defaults.go
  • openmeter/ledger/transactions/accrual.go
  • openmeter/ent/schema/ledger_account.go
  • openmeter/ledger/transactions/codes.go
  • openmeter/ledger/transactions/customer.go
  • openmeter/ledger/accounts.go
  • openmeter/ledger/primitives.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/validations.go
  • openmeter/ledger/routing_test.go
  • openmeter/ledger/validations_test.go
  • openmeter/ledger/transactions/correction_leg.go
  • openmeter/ledger/transactions/fx_test.go
  • openmeter/ledger/routing.go
  • openmeter/ledger/transactions/fx.go

@mark-vass-konghq

Copy link
Copy Markdown
Contributor Author

@greptileai
@CodeRabbit

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@mark-vass-konghq Got it — I’ll review the PR.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mark-vass-konghq
mark-vass-konghq force-pushed the feat/ledger-cc-support branch 6 times, most recently from 9101f5f to dc13ad1 Compare July 24, 2026 11:37
@mark-vass-konghq
mark-vass-konghq force-pushed the feat/ledger-cc-support branch from dc13ad1 to 562a50d Compare July 28, 2026 14:21
@mark-vass-konghq mark-vass-konghq changed the title feat(ledger): add custom currencies support feat(ledger): add custom currencies support (credit_only) Jul 28, 2026
@mark-vass-konghq
mark-vass-konghq force-pushed the feat/ledger-cc-support branch 3 times, most recently from ff20ce3 to ff473c0 Compare July 28, 2026 16:18
Comment thread openmeter/ledger/transactions/fx.go Outdated

@GAlexIHU GAlexIHU left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

please get rid "idempotency"

The ledger should correctly book whatever is requested, not invent business mechanisms. Why ever would 2 identical transactions be invalid in sequence? Imagine conceptually how bad it would be if you couldn't buy the same thing twice IRL

Comment thread openmeter/ent/schema/ledger_account.go Outdated
field.String("routing_key").Immutable(),
// Literal routing values (denormalized from routing_key for query filtering; not FKs).
field.String("currency").Immutable(),
field.String("exchange_source_currency").

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.

nit: lets not call exchange_source_currency, the "exchange" only makes sense for CC and even then it's not necessarily an exchange, let's call it cost_basis_currency or revenue_source_currency or source_fiat revenue_fiat, something of the like... (should be meaningful for FIAT as well)

annotations,
inputs...,
))
transactionGroupInput := transactions.WithIdempotencyKey(

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.

this is not needed & i think its conceptually dangerous, remove it please

  • groups roughly map to business events
  • charges lifecycle defines/protects payment flow correctness, its not our business
  • conceptually, why would the ledger decline transactions? thats a business problem

@@ -0,0 +1,190 @@
package historical

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.

why did you think this is needed?

ledger.AnnotationBreakagePlanID,
}

type transactionGroupFingerprintPayload struct {

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.

i think your fingerprinting only works if the adapter is invoked with the exact same input. that scenario wouldn't even happen in production, when it would be called with semantically same inputs we'd just receive a conflict. this is also a good sign as to why this ledger lvl idempotency doesnt make sense... also, why would two identical groups be invalid (conceptually from ledger side)?

require.NotEmpty(t, authRef.TransactionGroupID)

// and: the same authorization event is replayed.
replayedAuthRef, err := env.handler.OnCreditPurchasePaymentAuthorized(t.Context(), authorizationInput)

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.

this is not a meaningful testcase, what made you concerned with replay?

Comment on lines +29 to +38
field.String("idempotency_scope").
Optional().
Nillable().
Immutable(),
field.String("idempotency_key").
Optional().
Nillable().
Immutable().
MaxLen(256),
field.String("input_fingerprint").

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.

please get rid of these (see other comments for justifications)

// Re-denominate what the customer owes from the custom-currency IOU
// into the fiat amount actually being paid, before authorizing payment
// against it. Authorize/Settle only ever move real (fiat) money.
templates = append(templates, transactions.ConvertCurrencyTemplate{

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.

conversion should preserve everything present on the receivable account. you're dropping feature filters here, e.g.

# 100 ACME credits for api-calls costing $50:

+100 ACME  receivable [no feature]   ← conversion
-100 ACME  receivable [api-calls]    ← purchase

+$50 USD   receivable [api-calls]    ← authorization
-$50 USD   receivable [no feature]   ← conversion

@mark-vass-konghq
mark-vass-konghq force-pushed the feat/ledger-cc-support branch from 647a48d to 9df4341 Compare July 29, 2026 16:04
@mark-vass-konghq
mark-vass-konghq changed the base branch from main to codex/custom-currency-productcatalog-subscription July 29, 2026 16:05
@chrisgacsal
chrisgacsal force-pushed the codex/custom-currency-productcatalog-subscription branch from 5437aab to 14ba071 Compare July 29, 2026 22:45
Base automatically changed from codex/custom-currency-productcatalog-subscription to main July 30, 2026 08:53
@mark-vass-konghq
mark-vass-konghq force-pushed the feat/ledger-cc-support branch from 9df4341 to 9df8492 Compare July 30, 2026 09:28
Comment thread openmeter/ledger/transactions/fx.go Outdated
@mark-vass-konghq
mark-vass-konghq force-pushed the feat/ledger-cc-support branch from 798007b to cb77977 Compare July 30, 2026 12:01
Comment on lines +489 to +491
amount := creditAmount.
Mul(costBasis).
RoundBank(int32(fiatCurrency.Details().Precision))

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.

P1 Settlement Records Still Diverge

The ledger now uses banker's rounding, but the external-payment authorization and invoice gathering paths still calculate the same fiat amount with RoundToPrecision, which rounds midpoints away from zero. For a custom-currency purchase worth USD 1.005, those paths record 1.01 while this branch authorizes and settles 1.00 in the ledger. Apply the same rounding rule when creating the payment and invoice amounts so their records remain consistent with the ledger.

Prompt To Fix With AI
This is a comment left during a code review.
Path: openmeter/ledger/chargeadapter/creditpurchase.go
Line: 489-491

Comment:
**Settlement Records Still Diverge**

The ledger now uses banker's rounding, but the external-payment authorization and invoice gathering paths still calculate the same fiat amount with `RoundToPrecision`, which rounds midpoints away from zero. For a custom-currency purchase worth USD `1.005`, those paths record `1.01` while this branch authorizes and settles `1.00` in the ledger. Apply the same rounding rule when creating the payment and invoice amounts so their records remain consistent with the ledger.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

@GAlexIHU

Copy link
Copy Markdown
Contributor

i think we should simplify the route currency representation before merging this.

right now Route has both Currency and CustomCurrency, meaning every caller has to know that the actual identity is the combination of those two fields. the invariance bug is a good example of how easy it is to accidentally use only the code.

we already have the right domain abstraction for this: currencies.CurrencyReference.

type Route struct {
	Currency currencies.CurrencyReference

	CostBasis         *alpacadecimal.Decimal
	CostBasisCurrency *currencyx.Code // fiat only

	// other dimensions...
}

a hydrated reference already contains everything the ledger needs:

  • code
  • managed custom currency ID
  • the resolved currency definition, including precision

the currencies pkg could own a stable serialization for ledger dimensions:

func (r CurrencyReference) MarshalText() ([]byte, error)
func ParseCurrencyReference([]byte) (CurrencyReference, error)

roughly:

USD
custom:v1:CREDITS:<managed-id>:2

a hydrated ref serializes into the ledger dimension. parsing that value reconstructs a hydrated ref from the embedded snapshot. no currency service lookup, lifecycle loading, or cost-basis loading involved.

this would let us keep one currency string dimension in storage and remove the ledger-local CustomCurrencyIdentity plus the separate custom ID / precision / version columns.

the result is:

  • one typed currency field on the route
  • identity comparison through CurrencyReference.Equal
  • precision available locally
  • immutable historical behavior
  • no paired fields callers can accidentally treat independently

the only related cleanup is that CurrencyReference.IsResolved() currently conflates “currency definition is hydrated” with “cost-basis history was expanded”. the ledger only needs the first, so those states should probably be separated.

Comment thread openmeter/ledger/transactions/fx.go Outdated
// rounding difference the original booking's precision left behind. Partial
// corrections recompute the proportional source amount from the supplied
// cost basis.
func (t ConvertCurrencyTemplate) correct(scope CorrectionInput) ([]ledger.TransactionInput, error) {

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.

i don't think we should implement this yet. credit purchases currently have no refund/payment-correction flow, so production code cannot reach it with the required exact fiat amount.

lets keep it explicitly unsupported until the charge layer provides both exact amounts.

annotations := chargeAnnotationsForCreditPurchaseCharge(charge)
featureFilters := charge.Intent.FeatureFilters.Normalize()

settlementAmount, settlementCurrency, err := settlementPaymentAmount(charge.Intent.Currency, charge.Intent.Settlement, charge.Intent.CreditAmount, costBasis)

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.

can we pass the exact FiatAmount through PaymentEventInput instead of recalculating it here?

charges already calculates and persists this value for both external and invoice settlements. the ledger should book that same amount so rounding has one owner and the payment realization always matches the ledger.

func (i GetBalanceServiceInput) bookedRoute() ledger.RouteFilter {
route := i.featureRoute()
route.Currency = i.Currency
route.Currency = currencies.NewCurrencyReference(i.Currency)

@GAlexIHU GAlexIHU Jul 30, 2026

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.

can we add an explicit fiat-only guard here?

lets keep custom currency support in customerbalance out of scope for this PR

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (108 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

@GAlexIHU GAlexIHU left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice job! Lets fix these two small ones and then it should be good to merge

Comment on lines +122 to +127
fiatAmount := charge.Intent.CreditAmount
if charge.Intent.Currency.IsCustom() {
fiatAmount = fiatCurrency.RoundToPrecision(
charge.Intent.CreditAmount.Mul(externalSettlement.CostBasis),
)
}

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.

no, costbasis is still meaningful for fiat currencies, now FIAT purchases disregard costbasis, just remove the IsCustom branching

Comment thread openmeter/currencies/currency_test.go Outdated

serialized, err := reference.MarshalText()
require.NoError(t, err)
require.Equal(t, "custom:v1:CREDITS:"+custom.ID+":2", string(serialized))

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.

char ":" is unfortunately allowed for custom currency keying, lets use | as the delimiter, that is guarded against

@mark-vass-konghq
mark-vass-konghq force-pushed the feat/ledger-cc-support branch from 69b7ec7 to f99c388 Compare July 31, 2026 12:08
@mark-vass-konghq
mark-vass-konghq enabled auto-merge (squash) July 31, 2026 14:43
// GetCurrency returns the fiat currency real money settles in. Promotional
// settlements never move real money, so they return an empty currency rather
// than an error.
func (s Settlement) GetCurrency() (currencyx.FiatCode, error) {

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.

nit: shouldn't this return *currencyx.FiatCode, error instead? (using lo.ToPtr)

// NewCustomCurrency builds a custom currency value for tests that only need
// the resolved calculator (code, precision) and a stable fixture managed ID,
// not a persisted custom_currencies row.
func NewCustomCurrency[T ~string](t testing.TB, code T, precision uint32) currencies.Currency {

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.

why do we have a subpackage for these two methods? also why's that subpackage called currency (it can get unnecessarily confusing)... can these live in the parent testutils please?

})
}

func ParseCurrencyReference(value []byte) (CurrencyReference, error) {

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.

very very nit: this convenience is nice, but as we have encoding.TextMarshaller it could we nice if we implemented encoding.TextUnmarshaller and this just wrapped that (still this API is nicer at place of use)

we don't do XML, v3 AIP filters honor textmarshallers, there wouldn't be other interplay (+ we already implement marshaller)

@mark-vass-konghq
mark-vass-konghq merged commit d22a986 into main Jul 31, 2026
26 checks passed
@mark-vass-konghq
mark-vass-konghq deleted the feat/ledger-cc-support branch July 31, 2026 16:17
mark-vass-konghq added a commit that referenced this pull request Aug 3, 2026
fix: active entitlement validation blocking meter delete (#4835)

Co-authored-by: Robert Borbely <robert.borbely@konghq.com>

fix: meter delete in tx (#4838)

Co-authored-by: Alex Goth <64845621+GAlexIHU@users.noreply.github.com>
mark-vass-konghq added a commit that referenced this pull request Aug 3, 2026
fix: active entitlement validation blocking meter delete (#4835)

Co-authored-by: Robert Borbely <robert.borbely@konghq.com>

fix: meter delete in tx (#4838)

Co-authored-by: Alex Goth <64845621+GAlexIHU@users.noreply.github.com>
mark-vass-konghq added a commit that referenced this pull request Aug 4, 2026
Co-authored-by: Alex Goth <64845621+GAlexIHU@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants