Skip to content

feat: credit priority and effective at#4066

Merged
turip merged 4 commits intomainfrom
feat/credit-priority-and-effective-at
Apr 2, 2026
Merged

feat: credit priority and effective at#4066
turip merged 4 commits intomainfrom
feat/credit-priority-and-effective-at

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented Apr 2, 2026

Overview

Add support for priority. EffectiveAt wiring is there, but it's not yet supported by the handler.

Notes for reviewer

Summary by CodeRabbit

  • New Features

    • Credit purchases now include optional priority and effective date fields; priority influences credit allocation routing.
  • Tests

    • Added end-to-end tests verifying persistence, retrieval, and prioritized ledger routing for credit purchases.
  • Chores

    • Database schema updated with migration to add nullable effective date and priority columns.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79277062-b4fb-4d2f-8ef8-efa8951daf4f

📥 Commits

Reviewing files that changed from the base of the PR and between e4eaddb and c12fc3d.

⛔ Files ignored due to path filters (9)
  • openmeter/ent/db/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_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/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (8)
  • openmeter/billing/charges/creditpurchase/adapter/charge.go
  • openmeter/billing/charges/creditpurchase/adapter/mapper.go
  • openmeter/billing/charges/creditpurchase/chargecreditpurchase.go
  • openmeter/ent/schema/chargescreditpurchase.go
  • openmeter/ledger/chargeadapter/creditpurchase.go
  • test/credits/sanity_test.go
  • tools/migrate/migrations/20260402130955_add_credit_purchase_effective_at_priority.down.sql
  • tools/migrate/migrations/20260402130955_add_credit_purchase_effective_at_priority.up.sql

📝 Walkthrough

Walkthrough

Adds optional EffectiveAt and Priority fields to the credit-purchase intent, persists and maps them in the adapter/ent schema/migrations, wires Priority into ledger routing, and adds tests asserting persistence and FBO routing. No public API signatures changed.

Changes

Cohort / File(s) Summary
Schema & Migrations
openmeter/ent/schema/chargescreditpurchase.go, tools/migrate/migrations/20260402130955_add_credit_purchase_effective_at_priority.up.sql, tools/migrate/migrations/20260402130955_add_credit_purchase_effective_at_priority.down.sql
Add nullable, nillable, immutable effective_at (timestamptz) and priority (bigint) columns to ChargeCreditPurchase schema and DB migrations. Review DB compatibility and migration ordering.
Domain Model
openmeter/billing/charges/creditpurchase/chargecreditpurchase.go
Extend Intent with EffectiveAt *time.Time and Priority *int; add CalculateEffectiveAt(); normalize EffectiveAt in Normalized(); Validate() now rejects provided EffectiveAt with a specific error and wraps validation errors nillably. Pay attention to validation semantics.
Adapter & Mapper
openmeter/billing/charges/creditpurchase/adapter/charge.go, openmeter/billing/charges/creditpurchase/adapter/mapper.go
Persist normalized EffectiveAt and Priority when creating ChargeCreditPurchase; map DB fields back into Intent (uses convert.SafeToUTC). Validate normalization and nil handling.
Ledger Handler
openmeter/ledger/chargeadapter/creditpurchase.go
Set CreditPriority on ledger transaction templates for promotional and standard flows; added helper to pick intent priority or default. Review transaction template changes and FBO routing impact.
Tests
test/credits/sanity_test.go
Add TestCreditPurchasePersistsPriority; extend helpers to assert prioritized FBO balances and accept optional effectiveAt/priority. Check test coverage and assumptions about default priority.

Sequence Diagram(s)

sequenceDiagram
participant Client
participant Service as ChargeService
participant Adapter as ChargeAdapter
participant DB as Database
participant Ledger

Client->>Service: Create CreditPurchase Intent (amount, priority?, effectiveAt?)
Service->>Service: Intent.Normalized(), Validate()
Service->>Adapter: CreateCharge(intent)
Adapter->>DB: persist ChargeCreditPurchase (amount, effective_at?, priority?)
DB-->>Adapter: persisted entity
Adapter-->>Service: created Charge
Service->>Ledger: Issue transactions (CreditPriority from intent or default)
Ledger-->>Service: transaction result
Service-->>Client: response (charge created)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • tothandras
  • GAlexIHU
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding credit priority and effective-at fields across the codebase.

✏️ 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/credit-priority-and-effective-at

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.

@turip turip force-pushed the feat/credit-priority-and-effective-at branch from 13fb2ba to f958a25 Compare April 2, 2026 13:46
@turip turip force-pushed the feat/credit-priority-and-effective-at branch from f958a25 to 301b456 Compare April 2, 2026 13:48
@turip turip marked this pull request as ready for review April 2, 2026 14:07
@turip turip requested a review from a team as a code owner April 2, 2026 14:07
@turip turip added release-note/feature Release note: Exciting New Features area/billing labels Apr 2, 2026
@turip turip force-pushed the feat/credit-priority-and-effective-at branch from 11a8650 to c12fc3d Compare April 2, 2026 14:20
@turip turip enabled auto-merge (squash) April 2, 2026 14:21
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

Caution

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

⚠️ Outside diff range comments (1)
test/credits/sanity_test.go (1)

962-972: ⚠️ Potential issue | 🟠 Major

Don’t assume the requested priority bucket already exists.

Line 968 now looks up a concrete priority route with GetSubAccountForRoute. In the new priority test, only the priority 7 bucket is created, so asking for the default-priority bucket can error before Line 450 gets to assert zero.

💡 Suggested fix
-	subAccount, err := customerAccounts.FBOAccount.GetSubAccountForRoute(s.T().Context(), ledger.CustomerFBORouteParams{
+	subAccount, err := customerAccounts.FBOAccount.GetOrCreateSubAccountForRoute(s.T().Context(), ledger.CustomerFBORouteParams{
 		Currency:       code,
 		CostBasis:      costBasis,
 		CreditPriority: priority,
 	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/credits/sanity_test.go` around lines 962 - 972, The test assumes the
requested priority sub-account exists; change mustCustomerFBOBalanceWithPriority
to handle a missing priority bucket by calling
customerAccounts.FBOAccount.GetSubAccountForRoute (the current call) but if it
returns a not-found/missing-subaccount error, return alpacadecimal.Decimal zero
instead of failing; otherwise proceed to fetch the balance as before. In short,
wrap GetSubAccountForRoute error handling in mustCustomerFBOBalanceWithPriority
to detect "sub-account not found" and treat that case as zero balance rather
than propagating the error.
🧹 Nitpick comments (1)
test/credits/sanity_test.go (1)

408-451: Please add an external-settlement priority case too.

This new test proves priority on the promotional flow, but Line 176 in openmeter/ledger/chargeadapter/creditpurchase.go also routes OnCreditPurchaseInitiated by priority, and that path has the trickier advance/accrued attribution logic.

As per coding guidelines **/*_test.go: Make sure the tests are comprehensive and cover the changes. Keep a strong focus on unit tests and in-code integration tests.

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

In `@test/credits/sanity_test.go` around lines 408 - 451, Add a second test case
to TestCreditPurchasePersistsEffectiveAtAndPriority that exercises the
external-settlement path: create another intent using
creditpurchase.NewSettlement(creditpurchase.ExternalSettlement{}) (keep the same
effectiveAt and priority), submit it via Charges.Create (using
createCreditPurchaseIntent, createLedgerBackedCustomer, and the same
servicePeriod), then fetch the charge (mustGetChargeByID ->
AsCreditPurchaseCharge) and assert the Intent.EffectiveAt and Intent.Priority
match the originals and that balances reflect the external settlement semantics
by checking mustCustomerFBOBalanceWithPriority and mustCustomerFBOBalance as
done for the promotional case; this covers the OnCreditPurchaseInitiated routing
in creditpurchase.go which handles advance/accrued attribution.
🤖 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/billing/charges/creditpurchase/chargecreditpurchase.go`:
- Around line 111-115: The current guard in chargecreditpurchase.go
unconditionally rejects any non-nil EffectiveAt, making the new EffectiveAt
persistence/mapping unreachable and preventing
TestCreditPurchasePersistsEffectiveAtAndPriority from passing; remove or modify
the if i.EffectiveAt != nil check so that non-nil EffectiveAt values are allowed
to proceed to the DB setter/mapping logic (i.e., only return validation errors
from models.NewNillableGenericValidationError(errors.Join(errs...)) when errs is
non-empty), ensuring the create paths that validate intent before DB setters
remain functional and EffectiveAt/priority are persisted.

In `@openmeter/ledger/chargeadapter/creditpurchase.go`:
- Around line 80-85: Replace the use of the full credit field with the
already-reduced value: in the transactions.IssueCustomerReceivableTemplate
construction swap charge.Intent.CreditAmount for the previously-computed
issuableAmount (the value reduced by advanceOutstanding) so customer receivables
are issued only for issuableAmount; ensure this change is made where
IssueCustomerReceivableTemplate is populated and that
h.creditPurchasePriority(charge) and CostBasis usage remain unchanged.

---

Outside diff comments:
In `@test/credits/sanity_test.go`:
- Around line 962-972: The test assumes the requested priority sub-account
exists; change mustCustomerFBOBalanceWithPriority to handle a missing priority
bucket by calling customerAccounts.FBOAccount.GetSubAccountForRoute (the current
call) but if it returns a not-found/missing-subaccount error, return
alpacadecimal.Decimal zero instead of failing; otherwise proceed to fetch the
balance as before. In short, wrap GetSubAccountForRoute error handling in
mustCustomerFBOBalanceWithPriority to detect "sub-account not found" and treat
that case as zero balance rather than propagating the error.

---

Nitpick comments:
In `@test/credits/sanity_test.go`:
- Around line 408-451: Add a second test case to
TestCreditPurchasePersistsEffectiveAtAndPriority that exercises the
external-settlement path: create another intent using
creditpurchase.NewSettlement(creditpurchase.ExternalSettlement{}) (keep the same
effectiveAt and priority), submit it via Charges.Create (using
createCreditPurchaseIntent, createLedgerBackedCustomer, and the same
servicePeriod), then fetch the charge (mustGetChargeByID ->
AsCreditPurchaseCharge) and assert the Intent.EffectiveAt and Intent.Priority
match the originals and that balances reflect the external settlement semantics
by checking mustCustomerFBOBalanceWithPriority and mustCustomerFBOBalance as
done for the promotional case; this covers the OnCreditPurchaseInitiated routing
in creditpurchase.go which handles advance/accrued attribution.
🪄 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: e497e000-5f97-43c5-b11c-a06d636ef4dd

📥 Commits

Reviewing files that changed from the base of the PR and between 729f8e0 and e4eaddb.

⛔ Files ignored due to path filters (9)
  • openmeter/ent/db/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/chargecreditpurchase.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargecreditpurchase_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/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (8)
  • openmeter/billing/charges/creditpurchase/adapter/charge.go
  • openmeter/billing/charges/creditpurchase/adapter/mapper.go
  • openmeter/billing/charges/creditpurchase/chargecreditpurchase.go
  • openmeter/ent/schema/chargescreditpurchase.go
  • openmeter/ledger/chargeadapter/creditpurchase.go
  • test/credits/sanity_test.go
  • tools/migrate/migrations/20260402130955_add_credit_purchase_effective_at_priority.down.sql
  • tools/migrate/migrations/20260402130955_add_credit_purchase_effective_at_priority.up.sql

Comment thread openmeter/billing/charges/creditpurchase/chargecreditpurchase.go
Comment on lines 80 to +85
transactions.IssueCustomerReceivableTemplate{
At: charge.CreatedAt,
Amount: charge.Intent.CreditAmount,
Currency: charge.Intent.Currency,
CostBasis: &costBasis,
At: charge.CreatedAt,
Amount: charge.Intent.CreditAmount,
Currency: charge.Intent.Currency,
CostBasis: &costBasis,
CreditPriority: lo.ToPtr(h.creditPurchasePriority(charge)),
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.

⚠️ Potential issue | 🔴 Critical

Use issuableAmount here instead of the full credit amount.

Line 82 still issues charge.Intent.CreditAmount, even though the block above already reduced it by advanceOutstanding. With a partial backfill, this over-credits the customer and puts the extra balance into the selected priority bucket.

💡 Suggested fix
 		transactions.IssueCustomerReceivableTemplate{
 			At:             charge.CreatedAt,
-			Amount:         charge.Intent.CreditAmount,
+			Amount:         issuableAmount,
 			Currency:       charge.Intent.Currency,
 			CostBasis:      &costBasis,
 			CreditPriority: lo.ToPtr(h.creditPurchasePriority(charge)),
 		},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
transactions.IssueCustomerReceivableTemplate{
At: charge.CreatedAt,
Amount: charge.Intent.CreditAmount,
Currency: charge.Intent.Currency,
CostBasis: &costBasis,
At: charge.CreatedAt,
Amount: charge.Intent.CreditAmount,
Currency: charge.Intent.Currency,
CostBasis: &costBasis,
CreditPriority: lo.ToPtr(h.creditPurchasePriority(charge)),
transactions.IssueCustomerReceivableTemplate{
At: charge.CreatedAt,
Amount: issuableAmount,
Currency: charge.Intent.Currency,
CostBasis: &costBasis,
CreditPriority: lo.ToPtr(h.creditPurchasePriority(charge)),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/ledger/chargeadapter/creditpurchase.go` around lines 80 - 85,
Replace the use of the full credit field with the already-reduced value: in the
transactions.IssueCustomerReceivableTemplate construction swap
charge.Intent.CreditAmount for the previously-computed issuableAmount (the value
reduced by advanceOutstanding) so customer receivables are issued only for
issuableAmount; ensure this change is made where IssueCustomerReceivableTemplate
is populated and that h.creditPurchasePriority(charge) and CostBasis usage
remain unchanged.

Amount: charge.Intent.CreditAmount,
Currency: charge.Intent.Currency,
CostBasis: &costBasis,
CreditPriority: lo.ToPtr(h.creditPurchasePriority(charge)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is not strictly needed as we default on next layer

@turip turip merged commit 9f763d9 into main Apr 2, 2026
27 of 28 checks passed
@turip turip deleted the feat/credit-priority-and-effective-at branch April 2, 2026 14:26
@coderabbitai coderabbitai bot mentioned this pull request Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants