Skip to content

feat(customcurrency): materialize subscription item currencies#4742

Open
GAlexIHU wants to merge 1 commit into
codex/custom-currency-productcatalog-subscriptionfrom
codex/custom-currency-subscriptions
Open

feat(customcurrency): materialize subscription item currencies#4742
GAlexIHU wants to merge 1 commit into
codex/custom-currency-productcatalog-subscriptionfrom
codex/custom-currency-subscriptions

Conversation

@GAlexIHU

@GAlexIHU GAlexIHU commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add nullable fiat and managed custom-currency references to subscription items
  • materialize the effective rate-card currency when subscription items are created or recreated
  • cover plan creation, updates, add-on application, cancellation, and direct sync paths
  • hydrate legacy rows with no stored currency without rejecting them
  • reject newly persisted priced items whose currency was not materialized

Rollout boundary

This is the expand-and-write breakpoint for subscription custom currencies.

  • the schema migration is additive and does not backfill existing subscription items
  • existing rows with no item currency remain readable
  • normal rewrites materialize those rows
  • Subscription.Currency keeps its current meaning
  • this PR does not introduce invoice-currency, customer-fiat, cost-basis, billing, or subscription-sync semantic changes

Deployed environments can backfill existing rows after this version is rolled out. The semantic change can then land separately.

Validation

  • make generate
  • make lint-go
  • POSTGRES_HOST=127.0.0.1 go test -tags=dynamic ./openmeter/subscription/... -count=1
  • versioned migration up/down/up test, including legacy null-row preservation
  • full root test run: all packages passed except the expected test/notification failure because Svix was not running on 127.0.0.1:8071

Greptile Summary

This PR materializes effective currencies on subscription items. The main changes are:

  • Add nullable fiat and managed custom-currency references to subscription items.
  • Materialize inherited currencies during creation, updates, synchronization, and add-on application.
  • Hydrate legacy rows while rejecting new priced items without a stored currency.
  • Add Ent relationships, database constraints, migrations, and lifecycle tests.

Confidence Score: 4/5

The custom-currency write path needs tenant ownership validation before merging.

  • Subscription lifecycle paths consistently materialize priced-item currencies.
  • Database checks preserve legacy rows and prevent conflicting currency references.
  • Managed currency IDs can be stored without confirming that the currency belongs to the item's namespace.

openmeter/subscription/repo/subscriptionitemrepo.go and the subscription-item custom-currency schema relationship

Security Review

The new custom-currency reference is not scoped to the subscription item's namespace. A cross-namespace managed currency can therefore be persisted and hydrated unless the repository validates tenant ownership before writing.

Important Files Changed

Filename Overview
openmeter/subscription/subscriptionspec.go Adds item-level and specification-level materialization of effective rate-card currencies.
openmeter/subscription/addon/diff/apply.go Materializes add-on currencies when an add-on introduces a price or creates a new item.
openmeter/subscription/repo/subscriptionitemrepo.go Persists and eagerly loads currency references, but does not validate custom-currency namespace ownership.
openmeter/subscription/repo/mapping.go Hydrates fiat, managed custom, and absent legacy references into rate-card metadata.
openmeter/ent/schema/subscription.go Adds currency fields, an immutable custom-currency edge, indexes, and consistency checks.
tools/migrate/migrations/20260717150347_add_subscription_item_currency_references.up.sql Adds currency columns, consistency constraints, a custom-currency foreign key, and an index.
openmeter/subscription/service/service.go Materializes item currencies before subscription creation and update.
openmeter/subscription/service/sync.go Materializes inherited currencies at the shared synchronization boundary.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Plan or add-on rate card] --> B[Subscription item specification]
    B --> C{Priced item has currency?}
    C -- No --> D[Materialize effective currency]
    C -- Yes --> E[Preserve currency identity]
    D --> F[Subscription item repository]
    E --> F
    F --> G{Currency type}
    G -- Fiat --> H[Store currency code]
    G -- Managed custom --> I[Store custom currency ID]
    H --> J[Subscription item row]
    I --> J
    J --> K[Eager-load and hydrate identity]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Plan or add-on rate card] --> B[Subscription item specification]
    B --> C{Priced item has currency?}
    C -- No --> D[Materialize effective currency]
    C -- Yes --> E[Preserve currency identity]
    D --> F[Subscription item repository]
    E --> F
    F --> G{Currency type}
    G -- Fiat --> H[Store currency code]
    G -- Managed custom --> I[Store custom currency ID]
    H --> J[Subscription item row]
    I --> J
    J --> K[Eager-load and hydrate identity]
Loading

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
openmeter/subscription/repo/subscriptionitemrepo.go:170-175
**Cross-Namespace Currency Reference**

When a managed currency from another namespace reaches this repository, the code persists its ID without checking it against `input.Namespace`. The new foreign key only covers `custom_currency_id`, and hydration also performs no namespace check, so the item can load and use another tenant's currency instead of rejecting the write.

Reviews (1): Last reviewed commit: "feat(customcurrency): materialize subscr..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used (3)

  • Context used - CLAUDE.md (source)
  • Context used - AGENTS.md (source)
  • Context used - api/spec/AGENTS.md (source)

Summary by CodeRabbit

  • New Features

    • Subscription items now preserve either fiat or custom currency details, including across plan changes and add-on updates.
    • Currency information is automatically materialized when creating, updating, syncing, or migrating subscriptions.
    • Custom currency references are validated and retained when subscription items are stored and retrieved.
  • Bug Fixes

    • Improved handling of legacy subscription items missing currency information.
    • Workflow service initialization now reports configuration errors instead of failing silently.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Subscription items now persist fiat or custom currency references, materialize effective currency identities across subscription and addon flows, reconstruct currencies from storage, and validate workflow service dependencies during initialization.

Changes

Subscription currency support

Layer / File(s) Summary
Currency reference schema and migrations
openmeter/ent/schema/..., tools/migrate/...
Subscription items gain nullable fiat/custom currency references, constraints, indexes, relationships, and migration tests.
Currency identity materialization
openmeter/productcatalog/subscription/plan.go, openmeter/subscription/...
Plans and subscription specs preserve currency identities; create, update, sync, and addon flows materialize effective currencies.
Subscription item currency persistence
openmeter/subscription/repo/...
Repository creation stores currency references, queries load custom currencies, and mapping reconstructs currency metadata.
Workflow constructor validation and wiring
openmeter/subscription/workflow/service/..., app/common/..., test/...
Workflow construction validates required dependencies and propagates initialization errors through application and test setup paths.

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

Possibly related PRs

Suggested labels: area/billing

Suggested reviewers: turip, mark-vass-konghq, tothandras

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding materialized currency handling for subscription items, including custom currency support.
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
📝 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-subscriptions

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
Comment on lines +170 to +175
currencyRef, err := currencyadapter.ToDBCurrencyReference(input.RateCard.AsMeta().Currency, true)
if err != nil {
return def, fmt.Errorf("invalid subscription item currency: %w", err)
}
cmd.SetNillableFiatCurrencyCode(currencyRef.FiatCurrencyCode)
cmd.SetNillableCustomCurrencyID(currencyRef.CustomCurrencyID)

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 security Cross-Namespace Currency Reference

When a managed currency from another namespace reaches this repository, the code persists its ID without checking it against input.Namespace. The new foreign key only covers custom_currency_id, and hydration also performs no namespace check, so the item can load and use another tenant's currency instead of rejecting the write.

Prompt To Fix With AI
This is a comment left during a code review.
Path: openmeter/subscription/repo/subscriptionitemrepo.go
Line: 170-175

Comment:
**Cross-Namespace Currency Reference**

When a managed currency from another namespace reaches this repository, the code persists its ID without checking it against `input.Namespace`. The new foreign key only covers `custom_currency_id`, and hydration also performs no namespace check, so the item can load and use another tenant's currency instead of rejecting the write.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

@GAlexIHU
GAlexIHU marked this pull request as ready for review July 17, 2026 17:45
@GAlexIHU
GAlexIHU requested a review from a team as a code owner July 17, 2026 17:45

@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 `@openmeter/ent/schema/subscription.go`:
- Around line 273-277: Make custom-currency relationships tenant-scoped end to
end: update the subscription schema edge to use namespace and currency ID,
regenerate the migration and extend its database test to reject cross-namespace
currencies; update the mapping hydration path to reject namespace mismatches and
the subscription item save path to validate managed-currency ownership, adding
repository coverage for cross-namespace identities. Apply these changes at
openmeter/ent/schema/subscription.go:273-277,
tools/migrate/migrations/20260717150347_add_subscription_item_currency_references.up.sql:2,
tools/migrate/subscription_item_currency_references_test.go:133-149,
openmeter/subscription/repo/mapping.go:131-135,
openmeter/subscription/repo/subscriptionitemrepo.go:170-175, and
openmeter/subscription/repo/subscriptionitemrepo_test.go:65-129.
🪄 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: 6b6f6390-f688-448a-9c63-065d54815580

📥 Commits

Reviewing files that changed from the base of the PR and between 6a58931 and 1230145.

⛔ Files ignored due to path filters (18)
  • openmeter/ent/db/client.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/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.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/subscriptionitem.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptionitem/subscriptionitem.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptionitem/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptionitem_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptionitem_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/subscriptionitem_update.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (25)
  • app/common/subscription.go
  • openmeter/ent/schema/custom_currencies.go
  • openmeter/ent/schema/subscription.go
  • openmeter/productcatalog/subscription/plan.go
  • openmeter/subscription/addon/diff/apply.go
  • openmeter/subscription/addon/diff/apply_test.go
  • openmeter/subscription/plan.go
  • openmeter/subscription/repo/mapping.go
  • openmeter/subscription/repo/subscriptionitemrepo.go
  • openmeter/subscription/repo/subscriptionitemrepo_test.go
  • openmeter/subscription/service/currency_materialization_test.go
  • openmeter/subscription/service/service.go
  • openmeter/subscription/service/sync.go
  • openmeter/subscription/subscriptionspec.go
  • openmeter/subscription/testutils/service.go
  • openmeter/subscription/workflow/service/addon_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/testenv.go
  • tools/migrate/migrations/20260717150347_add_subscription_item_currency_references.down.sql
  • tools/migrate/migrations/20260717150347_add_subscription_item_currency_references.up.sql
  • tools/migrate/subscription_item_currency_references_test.go

Comment on lines +273 to +277
edge.From("custom_currency", CustomCurrency.Type).
Ref("subscription_items").
Field("custom_currency_id").
Unique().
Immutable(),

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Managed currency references are not namespace-scoped.

The schema, persistence, and hydration paths all accept a custom-currency ID without proving it belongs to the subscription item’s namespace.

  • openmeter/ent/schema/subscription.go#L273-L277: model the relationship using both namespace and currency ID.
  • tools/migrate/migrations/20260717150347_add_subscription_item_currency_references.up.sql#L2-L2: regenerate the migration with the tenant-scoped foreign key.
  • tools/migrate/subscription_item_currency_references_test.go#L133-L149: verify the database rejects a currency from another namespace.
  • openmeter/subscription/repo/mapping.go#L131-L135: reject loaded custom currencies whose namespace differs from the item.
  • openmeter/subscription/repo/subscriptionitemrepo.go#L170-L175: validate managed-currency ownership before saving.
  • openmeter/subscription/repo/subscriptionitemrepo_test.go#L65-L129: cover repository rejection of cross-namespace identities.
📍 Affects 6 files
  • openmeter/ent/schema/subscription.go#L273-L277 (this comment)
  • tools/migrate/migrations/20260717150347_add_subscription_item_currency_references.up.sql#L2-L2
  • tools/migrate/subscription_item_currency_references_test.go#L133-L149
  • openmeter/subscription/repo/mapping.go#L131-L135
  • openmeter/subscription/repo/subscriptionitemrepo.go#L170-L175
  • openmeter/subscription/repo/subscriptionitemrepo_test.go#L65-L129
🤖 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/ent/schema/subscription.go` around lines 273 - 277, Make
custom-currency relationships tenant-scoped end to end: update the subscription
schema edge to use namespace and currency ID, regenerate the migration and
extend its database test to reject cross-namespace currencies; update the
mapping hydration path to reject namespace mismatches and the subscription item
save path to validate managed-currency ownership, adding repository coverage for
cross-namespace identities. Apply these changes at
openmeter/ent/schema/subscription.go:273-277,
tools/migrate/migrations/20260717150347_add_subscription_item_currency_references.up.sql:2,
tools/migrate/subscription_item_currency_references_test.go:133-149,
openmeter/subscription/repo/mapping.go:131-135,
openmeter/subscription/repo/subscriptionitemrepo.go:170-175, and
openmeter/subscription/repo/subscriptionitemrepo_test.go:65-129.

Sources: Coding guidelines, Path instructions

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.

1 participant