Skip to content

feat(customcurrency): support custom currency subscriptions - #4744

Open
GAlexIHU wants to merge 10 commits into
mainfrom
codex/custom-currency-subscription-semantics
Open

feat(customcurrency): support custom currency subscriptions#4744
GAlexIHU wants to merge 10 commits into
mainfrom
codex/custom-currency-subscription-semantics

Conversation

@GAlexIHU

@GAlexIHU GAlexIHU commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What

Add end-to-end subscription-domain support for custom-currency priced items:

  • Store the subscription’s fiat billing currency explicitly as InvoiceCurrency, while preserving the legacy currency JSON field.
  • Materialize a currency reference on every priced subscription item, including currencies inherited from plans, add-ons, or the subscription default.
  • Add dynamic and pinned cost-basis modes.
  • Persist pinned cost bases per subscription, custom currency, and invoice currency.
  • Expose invoice currency, cost-basis mode, pins, and item currency through the APIs and generated clients.
  • Preserve custom currencies through subscription creation, edits, plan changes, add-ons, events, and repository hydration.
  • Keep billing sync behind an explicit safety boundary: subscriptions containing custom-currency billables are currently skipped by event-driven reconciliation or rejected by direct synchronization.

Why

Product catalog plans and add-ons can now use custom currencies, but subscriptions previously assumed that every priced item used the subscription’s fiat currency. That caused custom-currency identity to be lost when catalog resources were materialized into subscriptions.

The change separates two concepts:

  • Item currency: the fiat or managed custom currency in which the rate card is priced.
  • Invoice currency: the fiat currency used for customer billing and invoicing.

A custom-currency item must eventually be converted into the subscription’s invoice currency. Cost-basis modes define whether that conversion rate follows the effective cost basis dynamically or is fixed when the currency pair is introduced.
The billing guard prevents the subscription rollout from accidentally generating incorrect fiat invoice lines before downstream custom-currency billing is fully supported.

How

The workflow determines the invoice currency from the customer and plan:

  • A fiat plan can supply the invoice currency when the customer has none.
  • A custom-currency plan requires the customer to have a fiat currency.
  • Customer and fiat-plan currencies must agree.
  • Customer currency itself remains fiat-only.

Before validation and persistence, the subscription service:

  • Copies the subscription invoice currency onto priced items without an explicit override.
  • Preserves explicit custom-currency identities.
  • Resolves custom references in the subscription namespace.
  • Prevents established item currencies from changing during later edits.

For credit_then_invoice, the service verifies that every custom-currency item has an effective cost basis into the invoice currency:

  • dynamic validates the pair but resolves the applicable cost basis later.
  • pinned stores the selected cost-basis resource when the pair is first introduced.
  • credit_only does not require cost-basis validation.

Persistence adds:

  • cost_basis_mode to subscriptions.
  • A subscription_cost_basis_pins table.
  • Strong references from each pin to the subscription, custom currency, and cost-basis resource.
  • A unique pin per subscription/custom-currency/invoice-currency combination.
  • A constraint requiring every priced subscription item to have a currency, while unpriced items must not have currency fields.

The migration intentionally does not backfill legacy priced items. Existing installations must populate their inherited currency before applying the new constraint.

API schemas and generated Go, JavaScript, and Python clients are regenerated to expose the new fields.

Billing synchronization detects custom-currency billables before constructing fiat billing targets, preventing partial or incorrect reconciliation until billing support is added.

Summary by CodeRabbit

  • New Features
    • Added support for subscriptions using managed custom currencies.
    • Added dynamic or pinned cost-basis selection, including pinned cost-basis details.
    • Subscription responses now show invoice currency and cost-basis information.
    • Subscription items and add-ons preserve resolved currency and pricing metadata.
  • Bug Fixes
    • Improved currency validation and consistency checks for subscriptions and add-ons.
    • Automatic billing synchronization now skips unsupported custom-currency subscriptions safely.
  • Documentation
    • Added a migration guide for enabling custom-currency subscription support.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

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

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

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds custom-currency subscription support. It adds invoice-currency and cost-basis fields, persists dynamic or pinned cost bases, resolves currencies during subscription workflows, skips unsupported billing synchronization, updates API models, and adds migration and integration coverage.

Changes

Custom-currency subscriptions

Layer / File(s) Summary
API contracts and models
api/spec/..., api/v3/...
Subscription contracts and generated models expose invoice currencies, cost-basis modes, cost-basis pins, proration configuration, and subscription response inputs.
Domain validation and workflows
openmeter/subscription/..., openmeter/productcatalog/subscription/...
Subscription creation and updates resolve currencies, validate materialized currency data, select cost bases, persist pins, and preserve effective timestamps.
Persistence and migrations
openmeter/ent/..., openmeter/subscription/repo/..., tools/migrate/...
The database stores cost-basis modes and pins, enforces currency constraints, loads related references, and supports rollback.
API mapping and application wiring
openmeter/productcatalog/subscription/http/..., openmeter/productcatalog/subscription/service/..., app/common/..., cmd/...
Handlers map cost-basis fields, and constructors provide currency and cost-basis services to subscription components.
Billing synchronization boundary
openmeter/billing/worker/...
Synchronization accepts skip options, detects custom-currency billables, and skips or rejects unsupported billing paths.
Validation and integration coverage
openmeter/customer/..., openmeter/subscription/..., test/..., docs/...
Tests cover currency validation, persistence, add-ons, event compatibility, migration behavior, and custom-currency subscription lifecycles.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SubscriptionAPI
  participant SubscriptionWorkflow
  participant CurrencyResolver
  participant SubscriptionRepository
  participant BillingSync

  Client->>SubscriptionAPI: create or change subscription with cost-basis mode
  SubscriptionAPI->>SubscriptionWorkflow: pass invoice currency and cost-basis mode
  SubscriptionWorkflow->>CurrencyResolver: resolve currency and effective cost basis
  CurrencyResolver-->>SubscriptionWorkflow: return currency and cost-basis data
  SubscriptionWorkflow->>SubscriptionRepository: persist subscription and pinned cost bases
  SubscriptionRepository-->>SubscriptionAPI: return hydrated subscription
  SubscriptionAPI-->>Client: return subscription with invoice currency and cost-basis pins
  BillingSync->>SubscriptionWorkflow: synchronize subscription events
  SubscriptionWorkflow-->>BillingSync: skip custom-currency billing or return conflict
Loading
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the pull request's main change: support for custom-currency subscriptions.
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.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/custom-currency-subscription-semantics

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.

@GAlexIHU GAlexIHU added the release-note/feature Release note: Exciting New Features label Jul 17, 2026
@GAlexIHU
GAlexIHU force-pushed the codex/custom-currency-subscription-semantics branch from d6b8deb to 8e4110f Compare July 17, 2026 21:08
@chrisgacsal
chrisgacsal force-pushed the codex/custom-currency-subscriptions branch 4 times, most recently from 8f0d177 to 5d2efe2 Compare August 6, 2026 12:42
Base automatically changed from codex/custom-currency-subscriptions to main August 6, 2026 13:26
@chrisgacsal
chrisgacsal force-pushed the codex/custom-currency-subscription-semantics branch from 8e4110f to b0af507 Compare August 6, 2026 16:15
@chrisgacsal
chrisgacsal force-pushed the codex/custom-currency-subscription-semantics branch from b0af507 to 604e6fd Compare August 12, 2026 09:19
@chrisgacsal
chrisgacsal force-pushed the codex/custom-currency-subscription-semantics branch from 604e6fd to edb984d Compare August 12, 2026 13:57
@chrisgacsal chrisgacsal self-assigned this Aug 12, 2026
@chrisgacsal
chrisgacsal marked this pull request as ready for review August 12, 2026 14:09
@chrisgacsal
chrisgacsal requested a review from a team as a code owner August 12, 2026 14:09

@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: 8

🧹 Nitpick comments (5)
openmeter/customer/customer.go (1)

176-190: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the fiat-only customer-currency rule.

validateCustomerCurrency rejects custom currencies. Add a doc comment that states customer billing currencies must be fiat and explains the subscription invoice-currency constraint.

As per coding guidelines, “Document domain helpers whose names compress important business semantics, including observable behavior and why excluded cases are excluded.”

🤖 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/customer/customer.go` around lines 176 - 190, Add a doc comment for
validateCustomerCurrency stating that customer billing currencies must be fiat
and explaining that subscription invoices require a fiat currency, excluding
custom currencies. Keep the validation behavior unchanged.

Source: Coding guidelines

openmeter/subscription/addon/diff/apply.go (1)

123-129: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the currency validation into a named helper.

getApplyForRateCard must use a callback for subscription.NewAppliesToSpec, but this currency compatibility rule is meaningful domain validation. Move the check into a helper such as validateAddonCurrencyCompatibility and call it from the callback. This keeps the rule testable and avoids hiding validation in a local closure.

As per coding guidelines, “Do not hide type switching, validation, persistence mapping, or meaningful domain translation inside local closures; use named helpers and reserve inline callbacks for obvious, tiny logic.”

🤖 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/subscription/addon/diff/apply.go` around lines 123 - 129, The
currency compatibility check currently embedded in the NewAppliesToSpec callback
should be extracted into a named helper such as
validateAddonCurrencyCompatibility. Update getApplyForRateCard to invoke that
helper from the callback, preserving the existing nil handling,
effective-currency comparison, and ErrPlanAddonCurrencyMismatch error behavior.

Source: Coding guidelines

test/subscription/custom_currency_test.go (2)

37-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a fixed date instead of time.Now() for the frozen clock.

The test anchors everything on wall-clock time. With BillingCadence of P1M and startsAt = now + 1h, a run near a month or DST boundary can behave differently from a run mid-month, and a failure is then hard to reproduce. Other new tests in this stack pin a fixed instant, for example time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC).

♻️ Suggested change
-	now := time.Now().UTC().Truncate(time.Second)
+	now := time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC)
 	clock.FreezeTime(now)
 	defer clock.UnFreeze()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/subscription/custom_currency_test.go` around lines 37 - 39, Replace the
time.Now().UTC() anchor in the test’s frozen-clock setup with a fixed time.Date
instant in UTC, while preserving the existing truncation, FreezeTime call, and
deferred UnFreeze cleanup.

146-216: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Small doc gap: the then block only covers the rejection case.

The subtest name promises dynamic and pinned resolution, and the body asserts both (no pin for dynamic, one pin with the exact cost basis for pinned). The then comment stops at the rejection. Extending it keeps the intent block matching the assertions.

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

In `@test/subscription/custom_currency_test.go` around lines 146 - 216, The then
comment in the “credit then invoice resolves dynamic and pinned cost bases”
subtest should also describe the successful dynamic and pinned outcomes: dynamic
mode resolves without persisting a pin, while pinned mode stores the selected
cost basis pin. Keep the existing rejection expectation and align the intent
comments with the assertions in this test.
openmeter/subscription/apply.go (1)

60-63: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nice fix on the shadowing. One small idea: keep the original error too.

When models.AsValidationIssues fails, only conversionErr propagates. The original apply error, which is the actual cause, is dropped. Joining both keeps the failure traceable.

♻️ Optional tweak
 				issues, conversionErr := models.AsValidationIssues(err)
 				if conversionErr != nil {
-					return wrapError(conversionErr)
+					return wrapError(errors.Join(err, conversionErr))
 				}
🤖 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/subscription/apply.go` around lines 60 - 63, Update the error
branch after models.AsValidationIssues in the apply flow to return an error that
combines conversionErr with the original err, preserving both the conversion
failure and the underlying apply error while retaining the existing wrapping
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/spec/packages/aip-client-javascript/src/index.ts`:
- Line 136: Update the generator or template that produces the SDK root exports
so SubscriptionCostBasisMode is included alongside SubscriptionCostBasisPin,
then regenerate the generated index.ts file. Do not modify the generated file
manually.

In `@api/v3/handlers/subscriptions/change.go`:
- Line 118: Preserve an omitted CostBasisMode throughout the subscription-change
workflow instead of converting nil to an empty value. In
api/v3/handlers/subscriptions/change.go:118-118, retain curr.CostBasisMode when
body.CostBasisMode is nil; in
openmeter/productcatalog/subscription/http/change.go:93-93 and :131-131, keep
the field omitted for custom-plan and referenced-plan changes so the existing
subscription mode remains effective.

In `@docs/migration-guides/2026-07-17-custom-currency-subscriptions.md`:
- Line 5: Update the migration guide filename/date to 2026-08-10 and replace the
nonexistent migration reference 20260717195001 with
20260810084018_custom_currency_subscription_semantics.

In `@openmeter/productcatalog/subscription/service/create_test.go`:
- Around line 231-241: Align the `then` comments in
`TestCreateInlineCreditOnlyPlanSkipsCurrencyCostBasis` with the asserted
successful creation behavior: document that validation skips the missing
custom-currency cost basis for the credit-only plan and returns no error. Update
the corresponding repeated comments in the later assertion block as well.

In `@openmeter/productcatalog/subscription/service/service.go`:
- Around line 58-59: Update Config.Validate to wrap the joined field errors with
models.NewNillableGenericValidationError before returning, preserving the
generic validation classification and existing field context; keep New’s
handling unchanged.

In `@openmeter/subscription/repo/subscriptionrepo.go`:
- Around line 163-194: Move all field validation from the transaction callback
into CreateCostBasisPinEntityInput.Validate, collecting namespace, subscription
ID, custom currency ID, cost basis ID, and invoice currency errors and returning
models.NewNillableGenericValidationError(errors.Join(errs...)). Invoke Validate
on the inputs before opening the transaction, leaving the callback responsible
only for building and persisting SubscriptionCostBasisPin entities.

In `@openmeter/subscription/testutils/mock.go`:
- Around line 44-45: Update MockService.Update and the corresponding UpdateFn
signature to accept variadic subscription.UpdateOption values, then forward
options when invoking UpdateFn so mock-backed workflows preserve
subscription.WithCostBasisEffectiveAt and other update options.

In `@tools/migrate/subscription_custom_currency_semantics_test.go`:
- Around line 162-166: Extend the rollback test after Migrate(previousVersion)
to set currency = NULL for the existing priced itemID and require the update
succeeds, confirming the restored subscription_item_currency_has_price
constraint is active. Keep the existing assertion that
subscription_cost_basis_pins is removed.

---

Nitpick comments:
In `@openmeter/customer/customer.go`:
- Around line 176-190: Add a doc comment for validateCustomerCurrency stating
that customer billing currencies must be fiat and explaining that subscription
invoices require a fiat currency, excluding custom currencies. Keep the
validation behavior unchanged.

In `@openmeter/subscription/addon/diff/apply.go`:
- Around line 123-129: The currency compatibility check currently embedded in
the NewAppliesToSpec callback should be extracted into a named helper such as
validateAddonCurrencyCompatibility. Update getApplyForRateCard to invoke that
helper from the callback, preserving the existing nil handling,
effective-currency comparison, and ErrPlanAddonCurrencyMismatch error behavior.

In `@openmeter/subscription/apply.go`:
- Around line 60-63: Update the error branch after models.AsValidationIssues in
the apply flow to return an error that combines conversionErr with the original
err, preserving both the conversion failure and the underlying apply error while
retaining the existing wrapping behavior.

In `@test/subscription/custom_currency_test.go`:
- Around line 37-39: Replace the time.Now().UTC() anchor in the test’s
frozen-clock setup with a fixed time.Date instant in UTC, while preserving the
existing truncation, FreezeTime call, and deferred UnFreeze cleanup.
- Around line 146-216: The then comment in the “credit then invoice resolves
dynamic and pinned cost bases” subtest should also describe the successful
dynamic and pinned outcomes: dynamic mode resolves without persisting a pin,
while pinned mode stores the selected cost basis pin. Keep the existing
rejection expectation and align the intent comments with the assertions in this
test.
🪄 Autofix

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 Plus

Run ID: de7706e2-9e1b-429a-afc2-40d429da40a0

📥 Commits

Reviewing files that changed from the base of the PR and between 2064f94 and edb984d.

⛔ Files ignored due to path filters (50)
  • api/client/go/client.gen.go is excluded by !api/client/**
  • api/client/javascript/src/client/schemas.ts is excluded by !api/client/**
  • api/client/javascript/src/zod/index.ts is excluded by !api/client/**
  • api/client/python/openmeter/_generated/models/__init__.py is excluded by !**/_generated/**, !api/client/**
  • api/client/python/openmeter/_generated/models/_enums.py is excluded by !**/_generated/**, !api/client/**
  • api/client/python/openmeter/_generated/models/_models.py is excluded by !**/_generated/**, !api/client/**
  • api/client/python/openmeter/_generated/types.py is excluded by !**/_generated/**, !api/client/**
  • api/openapi.cloud.yaml is excluded by !**/openapi.cloud.yaml
  • api/openapi.yaml is excluded by !**/openapi.yaml
  • api/v3/openapi.yaml is excluded by !**/openapi.yaml
  • go.sum is excluded by !**/*.sum, !**/*.sum
  • openmeter/ent/db/client.go is excluded by !**/ent/db/**
  • openmeter/ent/db/currencycostbasis.go is excluded by !**/ent/db/**
  • openmeter/ent/db/currencycostbasis/currencycostbasis.go is excluded by !**/ent/db/**
  • openmeter/ent/db/currencycostbasis/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/currencycostbasis_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/currencycostbasis_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/currencycostbasis_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/cursor.go is excluded by !**/ent/db/**
  • openmeter/ent/db/customcurrency.go is excluded by !**/ent/db/**
  • openmeter/ent/db/customcurrency/customcurrency.go is excluded by !**/ent/db/**
  • openmeter/ent/db/customcurrency/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/customcurrency_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/customcurrency_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/customcurrency_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/ent.go is excluded by !**/ent/db/**
  • openmeter/ent/db/entmixinaccessor.go is excluded by !**/ent/db/**
  • openmeter/ent/db/expose.go is excluded by !**/ent/db/**
  • openmeter/ent/db/hook/hook.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/**
  • openmeter/ent/db/paginate.go is excluded by !**/ent/db/**
  • openmeter/ent/db/predicate/predicate.go is excluded by !**/ent/db/**
  • openmeter/ent/db/runtime.go is excluded by !**/ent/db/**
  • openmeter/ent/db/setorclear.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscription.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscription/subscription.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscription/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscription_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscription_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscription_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptioncostbasispin.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptioncostbasispin/subscriptioncostbasispin.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptioncostbasispin/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptioncostbasispin_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptioncostbasispin_delete.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptioncostbasispin_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptioncostbasispin_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/tx.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (100)
  • api/api.gen.go
  • api/spec/packages/aip-client-javascript/src/index.ts
  • api/spec/packages/aip-client-javascript/src/models/operations/subscriptions.ts
  • api/spec/packages/aip-client-javascript/src/models/schemas.ts
  • api/spec/packages/aip-client-javascript/src/models/types.ts
  • api/spec/packages/aip/src/subscriptions/subscription.tsp
  • api/spec/packages/legacy/src/productcatalog/subscription.tsp
  • api/spec/packages/legacy/src/types.tsp
  • api/v3/api.gen.go
  • api/v3/client/models_subscriptions.go
  • api/v3/handlers/subscriptions/change.go
  • api/v3/handlers/subscriptions/convert.go
  • api/v3/handlers/subscriptions/convert_test.go
  • app/common/subscription.go
  • cmd/billing-worker/wire_gen.go
  • cmd/jobs/internal/wire_gen.go
  • cmd/server/wire_gen.go
  • docs/migration-guides/2026-07-17-custom-currency-subscriptions.md
  • openmeter/billing/worker/subscriptionsync/reconciler/reconciler.go
  • openmeter/billing/worker/subscriptionsync/reconciler/reconciler_test.go
  • openmeter/billing/worker/subscriptionsync/service.go
  • openmeter/billing/worker/subscriptionsync/service/base_test.go
  • openmeter/billing/worker/subscriptionsync/service/currency_boundary_test.go
  • openmeter/billing/worker/subscriptionsync/service/handlers.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge_test.go
  • openmeter/billing/worker/subscriptionsync/service/service.go
  • openmeter/billing/worker/subscriptionsync/service/sync.go
  • openmeter/billing/worker/subscriptionsync/service/targetstate/targetstate.go
  • openmeter/billing/worker/worker.go
  • openmeter/customer/customer.go
  • openmeter/customer/customer_test.go
  • openmeter/ent/schema/custom_currencies.go
  • openmeter/ent/schema/subscription.go
  • openmeter/ent/schema/subscription_cost_basis_pin.go
  • openmeter/productcatalog/subscription/http/change.go
  • openmeter/productcatalog/subscription/http/create.go
  • openmeter/productcatalog/subscription/http/mapping.go
  • openmeter/productcatalog/subscription/http/mapping_test.go
  • openmeter/productcatalog/subscription/service/change.go
  • openmeter/productcatalog/subscription/service/change_test.go
  • openmeter/productcatalog/subscription/service/create.go
  • openmeter/productcatalog/subscription/service/create_test.go
  • openmeter/productcatalog/subscription/service/migrate.go
  • openmeter/productcatalog/subscription/service/migrate_test.go
  • openmeter/productcatalog/subscription/service/plan_test.go
  • openmeter/productcatalog/subscription/service/service.go
  • openmeter/server/server_test.go
  • openmeter/subscription/addon/diff/apply.go
  • openmeter/subscription/addon/diff/apply_test.go
  • openmeter/subscription/addon/extend.go
  • openmeter/subscription/addon/service/change_test.go
  • openmeter/subscription/addon/service/create_test.go
  • openmeter/subscription/addon/service/currency_test.go
  • openmeter/subscription/addon/service/list_test.go
  • openmeter/subscription/apply.go
  • openmeter/subscription/currency.go
  • openmeter/subscription/currency_validation_test.go
  • openmeter/subscription/errors_test.go
  • openmeter/subscription/events_test.go
  • openmeter/subscription/patch/patch_test.go
  • openmeter/subscription/repo/mapping.go
  • openmeter/subscription/repo/subscriptionitemrepo_test.go
  • openmeter/subscription/repo/subscriptionrepo.go
  • openmeter/subscription/repo/subscriptionrepo_test.go
  • openmeter/subscription/repository.go
  • openmeter/subscription/service.go
  • openmeter/subscription/service/cost_basis_persistence_test.go
  • openmeter/subscription/service/currency.go
  • openmeter/subscription/service/currency_materialization_test.go
  • openmeter/subscription/service/currency_test.go
  • openmeter/subscription/service/service.go
  • openmeter/subscription/service/service_test.go
  • openmeter/subscription/service/servicevalidation.go
  • openmeter/subscription/service/servicevalidation_test.go
  • openmeter/subscription/service/sync_test.go
  • openmeter/subscription/subscription.go
  • openmeter/subscription/subscriptionspec.go
  • openmeter/subscription/subscriptionview.go
  • openmeter/subscription/testutils/builder.go
  • openmeter/subscription/testutils/compare.go
  • openmeter/subscription/testutils/mock.go
  • openmeter/subscription/testutils/repository.go
  • openmeter/subscription/testutils/service.go
  • openmeter/subscription/uniqueness_test.go
  • openmeter/subscription/workflow/service.go
  • openmeter/subscription/workflow/service/addon.go
  • openmeter/subscription/workflow/service/addon_test.go
  • openmeter/subscription/workflow/service/currency_test.go
  • openmeter/subscription/workflow/service/service.go
  • openmeter/subscription/workflow/service/service_test.go
  • openmeter/subscription/workflow/service/subscription.go
  • openmeter/subscription/workflow/service/subscription_test.go
  • test/billing/subscription_suite.go
  • test/customer/customer.go
  • test/customer/testenv.go
  • test/subscription/custom_currency_test.go
  • test/subscription/framework_test.go
  • tools/migrate/migrations/20260810084018_custom_currency_subscription_semantics.down.sql
  • tools/migrate/migrations/20260810084018_custom_currency_subscription_semantics.up.sql
  • tools/migrate/subscription_custom_currency_semantics_test.go
💤 Files with no reviewable changes (1)
  • openmeter/subscription/service/currency_materialization_test.go

Comment thread api/spec/packages/aip-client-javascript/src/index.ts
Comment thread api/v3/handlers/subscriptions/change.go
Comment thread docs/migration-guides/2026-07-17-custom-currency-subscriptions.md Outdated
Comment thread openmeter/productcatalog/subscription/service/create_test.go
Comment thread openmeter/productcatalog/subscription/service/service.go Outdated
Comment thread openmeter/subscription/repo/subscriptionrepo.go Outdated
Comment thread openmeter/subscription/testutils/mock.go Outdated
Comment thread tools/migrate/subscription_custom_currency_semantics_test.go
@GAlexIHU

Copy link
Copy Markdown
Contributor Author

👍 ✅ (apart from CC addons being blocked)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@docs/migration-guides/2026-08-10-custom-currency-subscriptions.md`:
- Around line 3-6: Update the migration guide to remove the rollout step and
description for migration 20260810064730_backfill_subscription_item_currencies.
Document only the supported preflight and recovery process, without stating that
legacy priced items are automatically backfilled before the currency constraint
migration.

In `@openmeter/subscription/repository_test.go`:
- Around line 22-79: Extend the validation test table around
CreateCostBasisPinEntityInput validation with a case that clears two required
fields, such as Namespace and SubscriptionID, and expects an error containing
both corresponding validation messages. Ensure the assertion verifies collection
of both errors rather than only the first returned message, preserving the
existing single-field cases.

In `@openmeter/subscription/repository.go`:
- Around line 94-95: Update the invoice currency validation in the surrounding
validation method: split the i.InvoiceCurrency.Validate() and IsFiat() checks so
validation errors are handled separately. Wrap and retain the Validate() error
with invoice-currency context, while continuing to report a distinct
invalid-currency error for non-fiat values.
🪄 Autofix

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 Plus

Run ID: a4a0d879-1d6c-491f-aea6-1f10679e08b8

📥 Commits

Reviewing files that changed from the base of the PR and between edb984d and 3f9a303.

📒 Files selected for processing (15)
  • docs/migration-guides/2026-08-10-custom-currency-subscriptions.md
  • openmeter/customer/customer.go
  • openmeter/productcatalog/subscription/service/create_test.go
  • openmeter/productcatalog/subscription/service/service.go
  • openmeter/subscription/addon/diff/apply.go
  • openmeter/subscription/addon/service/currency_test.go
  • openmeter/subscription/addon/service/service.go
  • openmeter/subscription/errors.go
  • openmeter/subscription/repo/subscriptionrepo.go
  • openmeter/subscription/repository.go
  • openmeter/subscription/repository_test.go
  • openmeter/subscription/testutils/mock.go
  • openmeter/subscription/testutils/mock_test.go
  • openmeter/subscription/workflow/service/subscription_test.go
  • tools/migrate/subscription_custom_currency_semantics_test.go
💤 Files with no reviewable changes (2)
  • openmeter/subscription/errors.go
  • openmeter/subscription/addon/service/service.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • openmeter/customer/customer.go
  • openmeter/subscription/workflow/service/subscription_test.go
  • tools/migrate/subscription_custom_currency_semantics_test.go
  • openmeter/productcatalog/subscription/service/service.go
  • openmeter/subscription/repo/subscriptionrepo.go

Comment thread docs/migration-guides/2026-08-10-custom-currency-subscriptions.md
Comment thread openmeter/subscription/repository_test.go
Comment thread openmeter/subscription/repository.go Outdated
@chrisgacsal
chrisgacsal force-pushed the codex/custom-currency-subscription-semantics branch from 3f9a303 to 4e3ae0e Compare August 12, 2026 19:28

@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: 1

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

Inline comments:
In `@e2e/custom_currencies_productcatalog_v3_test.go`:
- Line 254: Update the status assertion in the affected test to expect
http.StatusOK, matching the success status returned by the subscription addon
creation endpoint in create.go; leave the request and error handling unchanged.
🪄 Autofix

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 Plus

Run ID: 1431fb2a-0cc8-4d90-b610-d3c2173d300d

📥 Commits

Reviewing files that changed from the base of the PR and between 3f9a303 and 4e3ae0e.

📒 Files selected for processing (3)
  • e2e/custom_currencies_productcatalog_v3_test.go
  • openmeter/subscription/repository.go
  • openmeter/subscription/repository_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • openmeter/subscription/repository_test.go
  • openmeter/subscription/repository.go

Comment thread e2e/custom_currencies_productcatalog_v3_test.go
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.

3 participants