Skip to content

fix: disallow cross fiat credit purchases#4165

Merged
turip merged 1 commit intomainfrom
fix/disallow-cross-fiat-credit-purchase
Apr 17, 2026
Merged

fix: disallow cross fiat credit purchases#4165
turip merged 1 commit intomainfrom
fix/disallow-cross-fiat-credit-purchase

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented Apr 17, 2026

Overview

Forbid purchasing credits for a given fiat currency from a different currency.

Notes for reviewer

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Enhanced billing validation to ensure settlement currency matches credit purchase currency. Transactions with mismatched currencies will now be rejected during charge creation.

@turip turip requested a review from a team as a code owner April 17, 2026 11:18
@turip turip added release-note/bug-fix Release note: Bug Fixes area/billing labels Apr 17, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

Credit purchase intent validation now checks that settlement currency matches the intent's currency for invoice and external settlement types. When a mismatch is detected, the validation fails with an error. A comprehensive test validates both settlement type variants.

Changes

Cohort / File(s) Summary
Settlement Currency Validation
openmeter/billing/charges/creditpurchase/charge.go
Enhanced Intent.Validate() to verify settlement currency alignment with intent currency across InvoiceSettlement and ExternalSettlement types.
Settlement Currency Validation Tests
openmeter/billing/charges/service/creditpurchase_test.go
New test method validates that credit purchase creation rejects mismatched settlement currencies for both external and invoice settlement variants.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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 main change: adding validation to prevent purchasing credits in one fiat currency from a different currency settlement.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 fix/disallow-cross-fiat-credit-purchase

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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/billing/charges/creditpurchase/charge.go (2)

148-150: Heads-up: the EffectiveAt early-return drops accumulated errs.

Not introduced by this PR, but now that errs accumulates currency-mismatch errors right above, returning a bare errors.New(...) here means a caller who sets both EffectiveAt and a mismatched settlement currency will only see the "effective at is not yet supported" message. Might be worth appending to errs and falling through to the final NewNillableGenericValidationError for consistency with the rest of Validate().

♻️ Suggested tweak
-	if i.EffectiveAt != nil {
-		return errors.New("effective at is not yet supported")
-	}
+	if i.EffectiveAt != nil {
+		errs = append(errs, errors.New("effective at is not yet supported"))
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/charges/creditpurchase/charge.go` around lines 148 - 150,
The current Validate() branch returns a fresh error when i.EffectiveAt != nil,
which discards any previously appended validation errors in errs (e.g., currency
mismatch); instead append a descriptive error about EffectiveAt to errs (same
type/format used elsewhere) and let execution fall through to the final
NewNillableGenericValidationError call so all accumulated errs are returned
together; update the logic around the EffectiveAt check in Validate() to push
the error into errs (referencing the errs variable and the EffectiveAt field)
rather than returning immediately.

131-146: Nice currency guard — consider DRY-ing the two arms.

The two case bodies are nearly identical. Since both InvoiceSettlement and ExternalSettlement embed GenericSettlement (which carries Currency), you could collapse this into a single accessor/helper and avoid the copy-paste. Totally optional, just a readability nit.

♻️ One possible shape
-	switch i.Settlement.Type() {
-	case SettlementTypeInvoice:
-		settlement, err := i.Settlement.AsInvoiceSettlement()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("settlement: %w", err))
-		} else if settlement.Currency != i.Currency {
-			errs = append(errs, fmt.Errorf("settlement currency %q must match credit currency %q", settlement.Currency, i.Currency))
-		}
-	case SettlementTypeExternal:
-		settlement, err := i.Settlement.AsExternalSettlement()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("settlement: %w", err))
-		} else if settlement.Currency != i.Currency {
-			errs = append(errs, fmt.Errorf("settlement currency %q must match credit currency %q", settlement.Currency, i.Currency))
-		}
-	}
+	if settlementCurrency, ok := i.Settlement.GenericCurrency(); ok && settlementCurrency != i.Currency {
+		errs = append(errs, fmt.Errorf("settlement currency %q must match credit currency %q", settlementCurrency, i.Currency))
+	}

(where GenericCurrency() returns the embedded GenericSettlement.Currency for invoice/external and ("", false) for promotional.)

As per coding guidelines: "In general when reviewing the Golang code make readability and maintainability a priority".

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

In `@openmeter/billing/charges/creditpurchase/charge.go` around lines 131 - 146,
Collapse the duplicated currency checks in the switch by introducing a single
accessor that extracts the embedded GenericSettlement.Currency (e.g., add a
method like GenericCurrency() on the settlement type/interface that returns
(string, bool) or similar) and then replace the two nearly identical branches
for SettlementTypeInvoice and SettlementTypeExternal with one shared path that
calls i.Settlement.GenericCurrency(), checks the boolean, and compares the
returned currency to i.Currency, appending the same error messages
(fmt.Errorf("settlement: %w") for accessor errors and the currency mismatch
message) so the logic in the switch no longer duplicates
AsInvoiceSettlement/AsExternalSettlement handling.
openmeter/billing/charges/service/creditpurchase_test.go (1)

99-99: Prefer s.T().Context() over context.Background() in new tests.

Small nit, but the guideline asks us to tie test contexts to the test harness lifecycle for new test code.

-	ctx := context.Background()
+	ctx := s.T().Context()

As per coding guidelines: "Use t.Context() when a testing.T or testing.TB is available instead of introducing context.Background() to keep cancellation and test-scoped lifecycle tied to the test harness".

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

In `@openmeter/billing/charges/service/creditpurchase_test.go` at line 99, Replace
the test-local context creation using context.Background() with the test harness
context by using the suite's testing.T context; specifically change the usage
that creates the variable ctx (currently set from context.Background()) to
obtain it from s.T().Context() so the test context is tied to the test lifecycle
(look for the ctx := context.Background() in creditpurchase_test.go and replace
with ctx from s.T().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/billing/charges/creditpurchase/charge.go`:
- Around line 148-150: The current Validate() branch returns a fresh error when
i.EffectiveAt != nil, which discards any previously appended validation errors
in errs (e.g., currency mismatch); instead append a descriptive error about
EffectiveAt to errs (same type/format used elsewhere) and let execution fall
through to the final NewNillableGenericValidationError call so all accumulated
errs are returned together; update the logic around the EffectiveAt check in
Validate() to push the error into errs (referencing the errs variable and the
EffectiveAt field) rather than returning immediately.
- Around line 131-146: Collapse the duplicated currency checks in the switch by
introducing a single accessor that extracts the embedded
GenericSettlement.Currency (e.g., add a method like GenericCurrency() on the
settlement type/interface that returns (string, bool) or similar) and then
replace the two nearly identical branches for SettlementTypeInvoice and
SettlementTypeExternal with one shared path that calls
i.Settlement.GenericCurrency(), checks the boolean, and compares the returned
currency to i.Currency, appending the same error messages
(fmt.Errorf("settlement: %w") for accessor errors and the currency mismatch
message) so the logic in the switch no longer duplicates
AsInvoiceSettlement/AsExternalSettlement handling.

In `@openmeter/billing/charges/service/creditpurchase_test.go`:
- Line 99: Replace the test-local context creation using context.Background()
with the test harness context by using the suite's testing.T context;
specifically change the usage that creates the variable ctx (currently set from
context.Background()) to obtain it from s.T().Context() so the test context is
tied to the test lifecycle (look for the ctx := context.Background() in
creditpurchase_test.go and replace with ctx from s.T().Context()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 08bada66-ce95-4482-a05e-92fcc8a5fffa

📥 Commits

Reviewing files that changed from the base of the PR and between c842d91 and a8a4a9d.

📒 Files selected for processing (2)
  • openmeter/billing/charges/creditpurchase/charge.go
  • openmeter/billing/charges/service/creditpurchase_test.go

@turip turip enabled auto-merge (squash) April 17, 2026 11:31
@turip turip requested review from GAlexIHU and tothandras April 17, 2026 11:41
@turip turip merged commit 476cef5 into main Apr 17, 2026
28 of 31 checks passed
@turip turip deleted the fix/disallow-cross-fiat-credit-purchase branch April 17, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants