Skip to content

feat(productcatalog): expose taxCodeId in taxConfig API#4224

Merged
borbelyr-kong merged 9 commits into
mainfrom
feat/expose-taxcode-on-api
Apr 29, 2026
Merged

feat(productcatalog): expose taxCodeId in taxConfig API#4224
borbelyr-kong merged 9 commits into
mainfrom
feat/expose-taxcode-on-api

Conversation

@borbelyr-kong
Copy link
Copy Markdown
Contributor

@borbelyr-kong borbelyr-kong commented Apr 24, 2026

Summary by CodeRabbit

  • New Features

    • Added optional tax code identifier support for tax configurations; ID takes precedence over provided Stripe codes.
    • Automatic backfill of Stripe code from stored tax-code mappings when using the tax code identifier.
  • Deprecated

    • Stripe and external invoicing fields marked deprecated in favor of the tax code identifier.
  • Tests

    • Expanded coverage for tax-code resolution across plans, addons, subscriptions; improved test isolation to avoid shared-state issues.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

Adds an optional TaxCodeId/tax_code_id to tax config models and a new productcatalog.ResolveTaxConfig function; updates API generators, mappings, service callsites to delegate tax-code resolution/backfill, and expands tests to cover TaxCodeID-first semantics and backfilling behavior.

Changes

Cohort / File(s) Summary
API schema & generated code
api/api.gen.go, api/v3/api.gen.go, api/spec/packages/legacy/src/productcatalog/tax.tsp, api/spec/packages/aip/src/billing/tax.tsp
Added TaxCodeId/tax_code_id to TaxConfig/BillingTaxConfig, marked old stripe/external fields deprecated, and regenerated embedded OpenAPI/swagger blobs.
Core resolution implementation
openmeter/productcatalog/tax.go
New exported ResolveTaxConfig(ctx, svc, namespace, cfg) that centralizes precedence: if TaxCodeID present → fetch/backfill Stripe mapping or error; else if Stripe.Code present → upsert/mint TaxCode and stamp TaxCodeID.
Callsites delegating to resolver
openmeter/productcatalog/addon/service/addon.go, openmeter/productcatalog/plan/service/plan.go, openmeter/subscription/service/synchelpers.go, openmeter/billing/service/profile.go
Replaced inline Stripe-to-TaxCode logic with calls to productcatalog.ResolveTaxConfig; preserved error paths.
HTTP/API mapping & conversion
openmeter/productcatalog/http/mapping.go, api/v3/handlers/billingprofiles/convert.go, api/v3/handlers/billingprofiles/convert.gen.go
Bidirectional mapping of TaxCodeId/TaxCodeID added; removed ignore directive so generated converters include the field.
Tests — tax-code resolution coverage
openmeter/productcatalog/addon/service/taxcode_test.go, openmeter/productcatalog/plan/service/taxcode_test.go, openmeter/subscription/service/service_test.go, openmeter/productcatalog/tax_test.go, test/billing/taxcode_dual_write_test.go
New and extended tests cover TaxCodeID-only create/update flows, negative cases for unknown IDs, backfill behavior, precedence when both provided, and unit tests for ResolveTaxConfig.
Test hygiene (clone rate-cards)
openmeter/productcatalog/subscription/service/..., openmeter/subscription/..., openmeter/subscription/addon/diff/restore_test.go
Replaced shared rate-card pointers with cloned instances and moved per-subtest initialization to avoid cross-test mutation.
TaxCode validation tweak
openmeter/taxcode/taxcode.go
Adjusted TaxCodeAppMapping.Validate to skip app-type validation when tax code is empty to avoid spurious errors.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Caller (service)
  participant Resolver as productcatalog.ResolveTaxConfig
  participant TaxSvc as taxcode.Service

  rect rgba(200,230,255,0.5)
  Caller->>Resolver: ResolveTaxConfig(ctx, svc, namespace, cfg)
  end

  alt cfg == nil
    Resolver-->>Caller: no-op / nil
  else if cfg.TaxCodeID != nil
    Resolver->>TaxSvc: GetTaxCode(ctx, TaxCodeID)
    alt found
      TaxSvc-->>Resolver: TaxCode (with app mappings)
      Resolver->>Resolver: backfill cfg.Stripe from mapping (or clear if none)
      Resolver-->>Caller: success (cfg mutated)
    else not found
      TaxSvc-->>Resolver: not found
      Resolver-->>Caller: validation error (TaxCode not found)
    end
  else if cfg.Stripe != nil && cfg.Stripe.Code != ""
    Resolver->>TaxSvc: GetOrCreateByAppMapping(ctx, app=Stripe, code)
    TaxSvc-->>Resolver: TaxCode (created or existing)
    Resolver->>Resolver: set cfg.TaxCodeID from TaxCode.ID
    Resolver-->>Caller: success (cfg mutated)
  else
    Resolver-->>Caller: no-op (nothing to resolve)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

release-note/feature, area/billing

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% 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 accurately describes the main change: exposing a new taxCodeId field in the TaxConfig API across multiple layers of the codebase.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/expose-taxcode-on-api

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
openmeter/subscription/service/service_test.go (1)

1104-1105: Small test harness tweak: prefer t.Context() here.

These subtests can use the test-scoped context directly instead of context.WithCancel(context.Background()).

✅ Suggested change
-		ctx, cancel := context.WithCancel(context.Background())
-		defer cancel()
+		ctx := t.Context()

As per coding guidelines, **/*_test.go: “In tests, prefer t.Context() when a testing.T or testing.TB is available instead of context.Background() to keep cancellation and test-scoped lifecycle tied to the test harness”.

Also applies to: 1182-1183

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

In `@openmeter/subscription/service/service_test.go` around lines 1104 - 1105,
Replace the manual test context created with context.WithCancel by using the
test-scoped context t.Context(): where you currently have "ctx, cancel :=
context.WithCancel(context.Background()); defer cancel()", change it to "ctx :=
t.Context()" in the test function(s) (and similarly update the other occurrence
around lines referenced in the review); this ties cancellation/lifecycle to the
testing harness and removes the unused cancel variable.
openmeter/productcatalog/addon/service/addon.go (1)

206-239: Please add a mismatch guard when both stripe.code and taxCodeId are provided.

Right now, if both are sent and disagree, the Stripe branch wins and the provided taxCodeId is silently ignored. That makes ambiguous payloads hard to debug.

💡 Suggested guard
 		case meta.TaxConfig.Stripe != nil && meta.TaxConfig.Stripe.Code != "":
 			// Existing path: resolve/create TaxCode from Stripe code.
 			resolved, err := s.taxCode.GetOrCreateByAppMapping(ctx, taxcode.GetOrCreateByAppMappingInput{
 				Namespace: namespace,
 				AppType:   app.AppTypeStripe,
 				TaxCode:   meta.TaxConfig.Stripe.Code,
 			})
 			if err != nil {
 				return fmt.Errorf("failed to resolve tax code for stripe code %s: %w", meta.TaxConfig.Stripe.Code, err)
 			}
+			if meta.TaxConfig.TaxCodeID != nil && *meta.TaxConfig.TaxCodeID != resolved.ID {
+				return models.NewGenericValidationError(
+					fmt.Errorf("taxCodeId %s does not match stripe code %s", *meta.TaxConfig.TaxCodeID, meta.TaxConfig.Stripe.Code),
+				)
+			}
 			tc = resolved
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/productcatalog/addon/service/addon.go` around lines 206 - 239, Add
a guard to detect and reject mismatched inputs when both
meta.TaxConfig.Stripe.Code and meta.TaxConfig.TaxCodeID are provided: in the
decision logic around the switch (or at the start of the Stripe case in
addon.go) compare the Stripe code to the taxCode resolved by
s.taxCode.GetTaxCode/GetOrCreateByAppMapping (or fetch the TaxCode by TaxCodeID
and compare its Stripe app mapping) and if they disagree return a validation
error (e.g., models.NewGenericValidationError) instead of silently preferring
Stripe; reference meta.TaxConfig.Stripe, meta.TaxConfig.TaxCodeID,
s.taxCode.GetOrCreateByAppMapping and s.taxCode.GetTaxCode to locate where to
implement the check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@openmeter/productcatalog/addon/service/addon.go`:
- Around line 206-239: Add a guard to detect and reject mismatched inputs when
both meta.TaxConfig.Stripe.Code and meta.TaxConfig.TaxCodeID are provided: in
the decision logic around the switch (or at the start of the Stripe case in
addon.go) compare the Stripe code to the taxCode resolved by
s.taxCode.GetTaxCode/GetOrCreateByAppMapping (or fetch the TaxCode by TaxCodeID
and compare its Stripe app mapping) and if they disagree return a validation
error (e.g., models.NewGenericValidationError) instead of silently preferring
Stripe; reference meta.TaxConfig.Stripe, meta.TaxConfig.TaxCodeID,
s.taxCode.GetOrCreateByAppMapping and s.taxCode.GetTaxCode to locate where to
implement the check.

In `@openmeter/subscription/service/service_test.go`:
- Around line 1104-1105: Replace the manual test context created with
context.WithCancel by using the test-scoped context t.Context(): where you
currently have "ctx, cancel := context.WithCancel(context.Background()); defer
cancel()", change it to "ctx := t.Context()" in the test function(s) (and
similarly update the other occurrence around lines referenced in the review);
this ties cancellation/lifecycle to the testing harness and removes the unused
cancel variable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3e6c43a6-d2a7-4e91-b538-aea0c15b51d8

📥 Commits

Reviewing files that changed from the base of the PR and between 5f30643 and ad633ae.

⛔ Files ignored due to path filters (6)
  • 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/_models.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
📒 Files selected for processing (9)
  • api/api.gen.go
  • api/spec/packages/legacy/src/productcatalog/tax.tsp
  • openmeter/productcatalog/addon/service/addon.go
  • openmeter/productcatalog/addon/service/taxcode_test.go
  • openmeter/productcatalog/http/mapping.go
  • openmeter/productcatalog/plan/service/plan.go
  • openmeter/productcatalog/plan/service/taxcode_test.go
  • openmeter/subscription/service/service_test.go
  • openmeter/subscription/service/synchelpers.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
openmeter/subscription/service/service_test.go (1)

1104-1105: Use t.Context() in these new subtests.

These new DB-backed subtests can take their context straight from the test harness instead of wrapping context.Background() again.

As per coding guidelines, "In tests, prefer t.Context() when a testing.T or testing.TB is available instead of using context.Background() to keep cancellation and lifecycle tied to the test harness."

Also applies to: 1182-1183

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

In `@openmeter/subscription/service/service_test.go` around lines 1104 - 1105,
Replace local context creation using context.WithCancel(context.Background()) in
the DB-backed subtests with the test harness context t.Context(); specifically,
remove the ctx, cancel := context.WithCancel(context.Background()) and defer
cancel() in the subtests and use ctx := t.Context() (or pass t.Context()
directly) so the subtest lifecycle and cancellation are tied to testing.T;
update the occurrences around the blocks that use ctx (the variables created by
context.WithCancel) at the two locations mentioned to use t.Context() instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openmeter/productcatalog/addon/service/addon.go`:
- Around line 206-239: The switch currently prefers meta.TaxConfig.Stripe.Code
over meta.TaxConfig.TaxCodeID and silently overwrites the supplied TaxCodeID;
update the logic in the addon service (the switch handling meta.TaxConfig in
addon.go, where s.taxCode.GetOrCreateByAppMapping and s.taxCode.GetTaxCode are
used and tc is set) to validate when both meta.TaxConfig.Stripe != nil and
meta.TaxConfig.TaxCodeID != nil: resolve the tax code for the Stripe code via
GetOrCreateByAppMapping (or GetTaxCode after mapping) and resolve the tax code
by TaxCodeID via GetTaxCode, compare their canonical IDs (e.g., resolved.ID or
NamespacedID), and if they differ return a GenericValidationError indicating the
mismatch; only if they match proceed to set tc and backfill the other
mapping/field (do not silently overwrite the provided TaxCodeID).

---

Nitpick comments:
In `@openmeter/subscription/service/service_test.go`:
- Around line 1104-1105: Replace local context creation using
context.WithCancel(context.Background()) in the DB-backed subtests with the test
harness context t.Context(); specifically, remove the ctx, cancel :=
context.WithCancel(context.Background()) and defer cancel() in the subtests and
use ctx := t.Context() (or pass t.Context() directly) so the subtest lifecycle
and cancellation are tied to testing.T; update the occurrences around the blocks
that use ctx (the variables created by context.WithCancel) at the two locations
mentioned to use t.Context() instead.
🪄 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: a81f8f21-ba0a-422d-9805-295628ca1d91

📥 Commits

Reviewing files that changed from the base of the PR and between ad633ae and e876d7c.

⛔ Files ignored due to path filters (7)
  • 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/_models.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
📒 Files selected for processing (15)
  • api/api.gen.go
  • api/spec/packages/aip/src/billing/tax.tsp
  • api/spec/packages/legacy/src/productcatalog/tax.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/billingprofiles/convert.gen.go
  • api/v3/handlers/billingprofiles/convert.go
  • openmeter/billing/service/profile.go
  • openmeter/productcatalog/addon/service/addon.go
  • openmeter/productcatalog/addon/service/taxcode_test.go
  • openmeter/productcatalog/http/mapping.go
  • openmeter/productcatalog/plan/service/plan.go
  • openmeter/productcatalog/plan/service/taxcode_test.go
  • openmeter/subscription/service/service_test.go
  • openmeter/subscription/service/synchelpers.go
  • openmeter/taxcode/taxcode.go
💤 Files with no reviewable changes (1)
  • api/v3/handlers/billingprofiles/convert.go
✅ Files skipped from review due to trivial changes (2)
  • api/spec/packages/aip/src/billing/tax.tsp
  • api/spec/packages/legacy/src/productcatalog/tax.tsp
🚧 Files skipped from review as they are similar to previous changes (2)
  • api/api.gen.go
  • openmeter/productcatalog/http/mapping.go

Comment thread openmeter/productcatalog/addon/service/addon.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/billing/taxcode_dual_write_test.go`:
- Around line 473-475: Update the C4 docstring at the top of the test to match
the new semantics: instead of saying "FK must be erased, not left stale" change
it to reflect that a bare TaxCodeID passed by the caller is honored (i.e., the
FK may be preserved/used) and that this test covers the branch where callers
pass a stale TaxCodeID alongside a nil Stripe object; update the header comment
near TestProfileUpdateBareTaxCodeIDIsHonored so it mirrors the explanatory lines
already present in the body (lines describing behavior on nil Stripe and non-nil
DefaultTaxConfig).
- Around line 153-155: Update the test docstring to match the new behavior:
change the comment above the renamed test (A4b) to state that the adapter now
preserves TaxCodeID (does not erase the stale FK) and backfills Stripe.Code from
the app mapping during resolveDefaultTaxCode, so the intent aligns with the
assertions that TaxCodeID is preserved and Stripe.Code is populated; reference
the test name (A4b) and the resolveDefaultTaxCode logic to locate where the
comment should be edited.
🪄 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: e288bcf7-a31d-41ac-a63b-40464a233cff

📥 Commits

Reviewing files that changed from the base of the PR and between e876d7c and 915a6f6.

📒 Files selected for processing (1)
  • test/billing/taxcode_dual_write_test.go

Comment thread test/billing/taxcode_dual_write_test.go
Comment thread test/billing/taxcode_dual_write_test.go
@borbelyr-kong borbelyr-kong force-pushed the feat/expose-taxcode-on-api branch from 915a6f6 to 450939e Compare April 27, 2026 10:58
@borbelyr-kong borbelyr-kong force-pushed the feat/expose-taxcode-on-api branch from d4de06e to 6459b31 Compare April 28, 2026 16:09
@borbelyr-kong borbelyr-kong marked this pull request as ready for review April 28, 2026 16:10
@borbelyr-kong borbelyr-kong requested a review from a team as a code owner April 28, 2026 16:10
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
openmeter/subscription/workflow/service/subscription_test.go (1)

837-858: Avoid reusing the same rc1 pointer across multiple phases.

Nice improvement overall, but this still leaves one mutable alias in the test input (&rc1 used twice). Using distinct clones per phase would fully eliminate cross-phase coupling in this setup.

Suggested refactor
 			rc1 := productcatalog.UsageBasedRateCard{
 				RateCardMeta: productcatalog.RateCardMeta{
 					Key:         subscriptiontestutils.ExampleFeatureKey,
@@
 				},
 				BillingCadence: subscriptiontestutils.ISOMonth,
 			}
+			rc1Phase1 := rc1.Clone()
+			rc1Phase3 := rc1.Clone()
@@
 							RateCards: productcatalog.RateCards{
-								&rc1,
+								rc1Phase1,
 							},
@@
 							RateCards: productcatalog.RateCards{
-								&rc1,
+								rc1Phase3,
 							},

Also applies to: 900-902, 924-925

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

In `@openmeter/subscription/workflow/service/subscription_test.go` around lines
837 - 858, The test currently reuses the same rc1 UsageBasedRateCard instance
pointer across multiple phases which creates mutable aliasing; update the test
to produce distinct instances (clones) for each phase instead of passing &rc1
multiple times — for example create separate variables like rc1PhaseA, rc1PhaseB
by deep-copying the RateCardMeta/UsageBasedRateCard fields or by calling a
helper that returns a new productcatalog.UsageBasedRateCard populated from rc1
(preserving RateCardMeta, EntitlementTemplate, TaxConfig, Price and
BillingCadence) and use those distinct variables wherever &rc1 is currently
passed (references to rc1, UsageBasedRateCard, RateCardMeta,
NewEntitlementTemplateFrom, NewPriceFrom indicate where to clone).
openmeter/productcatalog/addon/service/addon.go (1)

203-205: Wrap resolver errors with rate-card context.

This currently returns the raw resolver error from inside a loop, which makes debugging harder when multiple rate cards exist.

💡 Suggested tweak
-		if err := productcatalog.ResolveTaxConfig(ctx, s.taxCode, namespace, meta.TaxConfig); err != nil {
-			return err
-		}
+		if err := productcatalog.ResolveTaxConfig(ctx, s.taxCode, namespace, meta.TaxConfig); err != nil {
+			return fmt.Errorf("resolving tax config for rate card %q: %w", meta.Key, err)
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/productcatalog/addon/service/addon.go` around lines 203 - 205, The
loop calling productcatalog.ResolveTaxConfig currently returns the raw error
directly; change it to wrap the error with rate-card context so callers know
which rate card failed: when ResolveTaxConfig(ctx, s.taxCode, namespace,
meta.TaxConfig) returns err, return a wrapped error that includes identifying
info such as the rate card identifier/metadata (e.g. meta.Name or meta.ID), the
namespace variable, and s.taxCode (use fmt.Errorf("resolving tax config for
rate-card %s namespace %s taxCode %s: %w", meta.Name, namespace, s.taxCode, err)
or your project’s errors.Wrapf style) so the error message clearly identifies
the failing rate card and inputs.
openmeter/subscription/service/service_test.go (1)

1104-1105: Use t.Context() directly in these test subtests.

These test blocks should use t.Context() instead of manually creating a context with context.Background() and WithCancel(). This keeps the context lifecycle tied to the test harness, which is cleaner and ensures proper cleanup when the test finishes.

-		ctx, cancel := context.WithCancel(context.Background())
-		defer cancel()
+		ctx := t.Context()

This applies to lines 1104-1105 and 1182-1183.

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

In `@openmeter/subscription/service/service_test.go` around lines 1104 - 1105,
Replace the manual context creation (ctx, cancel :=
context.WithCancel(context.Background()); defer cancel()) used inside the test
subtests with the test harness context by using ctx := t.Context(), updating the
subtests that currently declare variables ctx and cancel (and remove the defer
cancel call); apply this change to both occurrences where ctx/cancel are created
so the subtest context lifecycle is tied to t.Context() instead of a manually
created background context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@openmeter/productcatalog/addon/service/addon.go`:
- Around line 203-205: The loop calling productcatalog.ResolveTaxConfig
currently returns the raw error directly; change it to wrap the error with
rate-card context so callers know which rate card failed: when
ResolveTaxConfig(ctx, s.taxCode, namespace, meta.TaxConfig) returns err, return
a wrapped error that includes identifying info such as the rate card
identifier/metadata (e.g. meta.Name or meta.ID), the namespace variable, and
s.taxCode (use fmt.Errorf("resolving tax config for rate-card %s namespace %s
taxCode %s: %w", meta.Name, namespace, s.taxCode, err) or your project’s
errors.Wrapf style) so the error message clearly identifies the failing rate
card and inputs.

In `@openmeter/subscription/service/service_test.go`:
- Around line 1104-1105: Replace the manual context creation (ctx, cancel :=
context.WithCancel(context.Background()); defer cancel()) used inside the test
subtests with the test harness context by using ctx := t.Context(), updating the
subtests that currently declare variables ctx and cancel (and remove the defer
cancel call); apply this change to both occurrences where ctx/cancel are created
so the subtest context lifecycle is tied to t.Context() instead of a manually
created background context.

In `@openmeter/subscription/workflow/service/subscription_test.go`:
- Around line 837-858: The test currently reuses the same rc1 UsageBasedRateCard
instance pointer across multiple phases which creates mutable aliasing; update
the test to produce distinct instances (clones) for each phase instead of
passing &rc1 multiple times — for example create separate variables like
rc1PhaseA, rc1PhaseB by deep-copying the RateCardMeta/UsageBasedRateCard fields
or by calling a helper that returns a new productcatalog.UsageBasedRateCard
populated from rc1 (preserving RateCardMeta, EntitlementTemplate, TaxConfig,
Price and BillingCadence) and use those distinct variables wherever &rc1 is
currently passed (references to rc1, UsageBasedRateCard, RateCardMeta,
NewEntitlementTemplateFrom, NewPriceFrom indicate where to clone).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1638b5be-28fc-4bc7-9426-a2ea06030409

📥 Commits

Reviewing files that changed from the base of the PR and between 915a6f6 and 6459b31.

⛔ Files ignored due to path filters (7)
  • 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/_models.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
📒 Files selected for processing (23)
  • api/api.gen.go
  • api/spec/packages/aip/src/billing/tax.tsp
  • api/spec/packages/legacy/src/productcatalog/tax.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/billingprofiles/convert.gen.go
  • api/v3/handlers/billingprofiles/convert.go
  • openmeter/billing/service/profile.go
  • openmeter/productcatalog/addon/service/addon.go
  • openmeter/productcatalog/addon/service/taxcode_test.go
  • openmeter/productcatalog/http/mapping.go
  • openmeter/productcatalog/plan/service/plan.go
  • openmeter/productcatalog/plan/service/taxcode_test.go
  • openmeter/productcatalog/subscription/service/change_test.go
  • openmeter/productcatalog/subscription/service/migrate_test.go
  • openmeter/productcatalog/subscription/service/plan_test.go
  • openmeter/productcatalog/tax.go
  • openmeter/subscription/addon/diff/restore_test.go
  • openmeter/subscription/service/service_test.go
  • openmeter/subscription/service/synchelpers.go
  • openmeter/subscription/workflow/service/addon_test.go
  • openmeter/subscription/workflow/service/subscription_test.go
  • openmeter/taxcode/taxcode.go
  • test/billing/taxcode_dual_write_test.go
💤 Files with no reviewable changes (1)
  • api/v3/handlers/billingprofiles/convert.go
✅ Files skipped from review due to trivial changes (5)
  • api/spec/packages/legacy/src/productcatalog/tax.tsp
  • api/spec/packages/aip/src/billing/tax.tsp
  • openmeter/productcatalog/subscription/service/plan_test.go
  • openmeter/taxcode/taxcode.go
  • openmeter/productcatalog/subscription/service/migrate_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • openmeter/productcatalog/http/mapping.go
  • api/v3/handlers/billingprofiles/convert.gen.go
  • openmeter/subscription/service/synchelpers.go
  • test/billing/taxcode_dual_write_test.go
  • openmeter/productcatalog/plan/service/taxcode_test.go
  • openmeter/productcatalog/addon/service/taxcode_test.go

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
test/billing/taxcode_dual_write_test.go (1)

153-155: ⚠️ Potential issue | 🟡 Minor

These two test banners still describe the old FK-clearing behavior.

Quick cleanup needed: the headers at Line 153 and Line 519 still say stale FK should be erased, but both tests now assert the opposite (TaxCodeID is honored/preserved with Stripe backfill).

Suggested docstring fix
-// A4b: Round-trip clear — fetch the profile (TaxCodeID populated), clear only Stripe in-place,
-// then update; the stale FK must be erased, not persisted. Behavior is kept non-nil so that
-// DefaultTaxConfig is not normalized to nil by the adapter.
+// A4b: Round-trip read-modify-write — fetch profile (TaxCodeID populated), clear only Stripe
+// in-place, then update. Bare TaxCodeID is honored, so FK is preserved and Stripe.Code is
+// backfilled from stored app mapping. Behavior stays non-nil to avoid nil-normalization.
-// C4: Round-trip Stripe clear with pre-populated TaxCodeID — FK must be erased, not left stale.
-// Covers the branch where the caller explicitly passes a stale TaxCodeID alongside nil Stripe.
-// Behavior is kept non-nil so DefaultTaxConfig is not normalized to nil by the adapter.
+// C4: Bare TaxCodeID input is honored — caller supplies TaxCodeID without Stripe.Code.
+// Service validates the entity, preserves FK, and backfills Stripe.Code from app mapping.
+// Behavior stays non-nil to avoid nil-normalization by the adapter.

Also applies to: 519-521

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

In `@test/billing/taxcode_dual_write_test.go` around lines 153 - 155, Update the
two test banners in test/billing/taxcode_dual_write_test.go that still claim the
"stale FK must be erased" behavior to reflect the current behavior where
TaxCodeID is honored/preserved (Stripe backfill) and DefaultTaxConfig remains
non-nil; specifically edit the header/comments that reference the FK-clearing
behavior near the blocks testing round-trip clear and Stripe backfill so they
describe that TaxCodeID is preserved and not erased, matching the test
assertions that expect TaxCodeID to be retained.
🧹 Nitpick comments (1)
test/billing/taxcode_dual_write_test.go (1)

157-157: Use test-scoped context instead of context.Background().

Replace ctx := context.Background() with ctx := s.T().Context() on lines 157, 434, 479, and 523 to tie cancellation and lifecycle to the test harness.

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

In `@test/billing/taxcode_dual_write_test.go` at line 157, The tests currently
create a root context with ctx := context.Background(); replace those with a
test-scoped context by assigning ctx := s.T().Context() wherever ctx is
initialized (search for ctx := context.Background() in this test file) so the
context lifecycle and cancellation are tied to the test harness rather than the
global background context; update all occurrences to use s.T().Context() and
ensure subsequent code uses that ctx variable unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@test/billing/taxcode_dual_write_test.go`:
- Around line 153-155: Update the two test banners in
test/billing/taxcode_dual_write_test.go that still claim the "stale FK must be
erased" behavior to reflect the current behavior where TaxCodeID is
honored/preserved (Stripe backfill) and DefaultTaxConfig remains non-nil;
specifically edit the header/comments that reference the FK-clearing behavior
near the blocks testing round-trip clear and Stripe backfill so they describe
that TaxCodeID is preserved and not erased, matching the test assertions that
expect TaxCodeID to be retained.

---

Nitpick comments:
In `@test/billing/taxcode_dual_write_test.go`:
- Line 157: The tests currently create a root context with ctx :=
context.Background(); replace those with a test-scoped context by assigning ctx
:= s.T().Context() wherever ctx is initialized (search for ctx :=
context.Background() in this test file) so the context lifecycle and
cancellation are tied to the test harness rather than the global background
context; update all occurrences to use s.T().Context() and ensure subsequent
code uses that ctx variable unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2faf9518-cdf3-45b8-8c80-4eec66e98e74

📥 Commits

Reviewing files that changed from the base of the PR and between 6459b31 and 7664017.

📒 Files selected for processing (1)
  • test/billing/taxcode_dual_write_test.go

Comment thread openmeter/productcatalog/tax.go
Comment thread openmeter/productcatalog/tax.go
Comment thread openmeter/billing/service/profile.go
tothandras
tothandras previously approved these changes Apr 29, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
openmeter/productcatalog/subscription/service/change_test.go (1)

50-57: ⚠️ Potential issue | 🟠 Major

Prefer t.Context() over context.Background() in these test subtests.

All three instances (lines 56, 162, 290) have t *testing.T available in scope. Using t.Context() keeps cancellation and lifecycle properly tied to the test harness. Since the context import is only used for these Background() calls, you can also remove it entirely.

Suggested refactor
 import (
-	"context"
 	"fmt"
 	"testing"
 	"time"
@@
-			ctx := context.Background()
+			ctx := t.Context()
@@
-			ctx := context.Background()
+			ctx := t.Context()
@@
-			ctx := context.Background()
+			ctx := t.Context()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/productcatalog/subscription/service/change_test.go` around lines 50
- 57, Tests in change_test.go create a root context using context.Background()
even though the test harness's t *testing.T is in scope; replace each "ctx :=
context.Background()" with "ctx := t.Context()" (these occurrences create the
local ctx variable in the subtests) so the context is tied to the test
lifecycle, and then remove the now-unused "context" import from the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@openmeter/productcatalog/subscription/service/change_test.go`:
- Around line 50-57: Tests in change_test.go create a root context using
context.Background() even though the test harness's t *testing.T is in scope;
replace each "ctx := context.Background()" with "ctx := t.Context()" (these
occurrences create the local ctx variable in the subtests) so the context is
tied to the test lifecycle, and then remove the now-unused "context" import from
the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0ea79d09-36cd-4213-a79d-2116edfbb112

📥 Commits

Reviewing files that changed from the base of the PR and between 6459b31 and 74bfcfd.

⛔ Files ignored due to path filters (7)
  • 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/_models.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
📒 Files selected for processing (24)
  • api/api.gen.go
  • api/spec/packages/aip/src/billing/tax.tsp
  • api/spec/packages/legacy/src/productcatalog/tax.tsp
  • api/v3/api.gen.go
  • api/v3/handlers/billingprofiles/convert.gen.go
  • api/v3/handlers/billingprofiles/convert.go
  • openmeter/billing/service/profile.go
  • openmeter/productcatalog/addon/service/addon.go
  • openmeter/productcatalog/addon/service/taxcode_test.go
  • openmeter/productcatalog/http/mapping.go
  • openmeter/productcatalog/plan/service/plan.go
  • openmeter/productcatalog/plan/service/taxcode_test.go
  • openmeter/productcatalog/subscription/service/change_test.go
  • openmeter/productcatalog/subscription/service/migrate_test.go
  • openmeter/productcatalog/subscription/service/plan_test.go
  • openmeter/productcatalog/tax.go
  • openmeter/productcatalog/tax_test.go
  • openmeter/subscription/addon/diff/restore_test.go
  • openmeter/subscription/service/service_test.go
  • openmeter/subscription/service/synchelpers.go
  • openmeter/subscription/workflow/service/addon_test.go
  • openmeter/subscription/workflow/service/subscription_test.go
  • openmeter/taxcode/taxcode.go
  • test/billing/taxcode_dual_write_test.go
💤 Files with no reviewable changes (1)
  • api/v3/handlers/billingprofiles/convert.go
✅ Files skipped from review due to trivial changes (3)
  • openmeter/productcatalog/subscription/service/plan_test.go
  • openmeter/productcatalog/subscription/service/migrate_test.go
  • api/v3/handlers/billingprofiles/convert.gen.go
🚧 Files skipped from review as they are similar to previous changes (10)
  • api/spec/packages/legacy/src/productcatalog/tax.tsp
  • api/spec/packages/aip/src/billing/tax.tsp
  • openmeter/productcatalog/http/mapping.go
  • openmeter/subscription/workflow/service/addon_test.go
  • openmeter/billing/service/profile.go
  • openmeter/productcatalog/plan/service/taxcode_test.go
  • openmeter/productcatalog/addon/service/taxcode_test.go
  • openmeter/productcatalog/addon/service/addon.go
  • openmeter/productcatalog/tax.go
  • openmeter/subscription/workflow/service/subscription_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants