Skip to content

feat(customcurrencies): support usage-based credit then invoice - #4780

Merged
turip merged 6 commits into
mainfrom
feat/custom-currency-usagebased-credit-then-invoice
Jul 27, 2026
Merged

feat(customcurrencies): support usage-based credit then invoice#4780
turip merged 6 commits into
mainfrom
feat/custom-currency-usagebased-credit-then-invoice

Conversation

@turip

@turip turip commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

This change enables usage-based charges settled through customer credits to use a custom credit currency while the customer invoice remains denominated in fiat.

Usage is realized in the custom currency and available customer credits are allocated in that same currency. Any usage not covered by credits becomes overage and is converted to the invoice currency using the cost basis selected for the charge.

The overage must be invoiced as a credit purchase. The invoice line represents the uncovered custom-currency amount purchased at the charge's persisted cost basis, with the resulting fiat amount becoming the customer's outstanding balance.

Business behavior

  • usage and credit allocation remain denominated in the charge's custom currency
  • the persisted cost basis determines the fiat value of uncovered usage
  • the invoice contains one credit-purchase line for the overage
  • the credit-purchase quantity is the uncovered custom-currency amount
  • the credit-purchase unit price is the persisted cost basis
  • invoice collection and payment use the resulting fiat amount
  • final and progressive realization follow the same settlement behavior, including late-arriving usage

This keeps customer credit accounting in the credit currency while ensuring invoices, outstanding balances, and payments remain fiat-denominated.

Validation

  • make test-nocache — 6,379 tests passed, 15 skipped
  • focused usage-based charge and subscription-sync lifecycle suites

Summary by CodeRabbit

  • New Features
    • Added conversion of custom-currency overages into fiat amounts using cost basis.
    • Introduced custom-currency overage handling for credit-then-invoice, including lifecycle persistence and fiat overage placeholder/overage annotations.
  • Bug Fixes
    • Payment authorization and settlement now properly skip or reject flows when the fiat amount is zero.
    • Invoice collection timing now considers explicit overage collection deadlines.
  • Documentation
    • Updated testing guidance for fixed UTC timestamps.
  • Tests
    • Expanded unit and lifecycle coverage for custom-currency conversion, validations, collection timing, and zero-value payment behavior.

Greptile Summary

Adds custom-currency usage settlement through fiat invoices.

  • Converts uncovered custom-currency usage to fiat using the persisted cost basis.
  • Represents overage as a credit-purchase invoice line and propagates fiat amounts through collection and payment.
  • Adds lifecycle, conversion, collection-timing, and zero-value payment coverage.

Confidence Score: 5/5

The PR appears safe to merge within this follow-up review because no new blocking failure eligible for a distinct comment remains.

No blocking failure eligible for this follow-up review remains.

Important Files Changed

Filename Overview
openmeter/billing/charges/meta/customcurrency.go Adds validated conversion of custom-currency overage into a rounded fiat amount.
openmeter/billing/charges/models/creditpurchase/detailedline.go Adds construction and consistency validation for fiat credit-purchase invoice lines.
openmeter/billing/charges/usagebased/service/linemapper.go Maps custom-currency realization results onto fiat-denominated overage lines.
openmeter/billing/charges/usagebased/service/run/invoice.go Routes custom-currency overage accrual through the dedicated handler and validates its fiat result.
openmeter/billing/charges/usagebased/service/run/payment.go Skips authorization and settlement when a realization requires no fiat transaction.
openmeter/billing/service/invoicecalc/collectionat.go Incorporates explicit overage collection deadlines into invoice collection timing.
openmeter/ledger/chargeadapter/usagebased.go Extends the usage-based ledger handler interface for custom-currency overage accrual.
openmeter/billing/charges/service/usagebased_test.go Adds final and progressive lifecycle coverage for custom-currency credit-then-invoice settlement.

Sequence Diagram

sequenceDiagram
    participant Usage as Usage Rating
    participant Credits as Credit Allocation
    participant Charge as Usage Charge
    participant Invoice as Fiat Invoice
    participant Ledger as Ledger
    Usage->>Charge: Realize usage in custom currency
    Charge->>Credits: Allocate matching customer credits
    Credits-->>Charge: Return uncovered custom-currency amount
    Charge->>Charge: Convert overage using persisted cost basis
    Charge->>Invoice: Create fiat credit-purchase line
    Invoice->>Ledger: Accrue and settle fiat balance
Loading

Reviews (3): Last reviewed commit: "fix(customcurrencies): preserve overage ..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds custom-currency overage conversion to fiat, validated invoice lines, custom overage accrual callbacks, updated realization and invoice flows, zero-fiat payment handling, collection-time support, and credit-then-invoice lifecycle tests.

Changes

Custom-currency overage flow

Layer / File(s) Summary
Conversion and detailed-line contracts
openmeter/billing/charges/meta/..., openmeter/billing/charges/models/creditpurchase/..., openmeter/billing/charges/usagebased/charge.go
Adds validated custom-currency-to-fiat conversion and fiat credit-purchase detailed lines using resolved cost basis.
Handler and payment event contracts
openmeter/billing/charges/usagebased/handler.go, openmeter/ledger/chargeadapter/usagebased.go, openmeter/billing/charges/testutils/handlers.go
Adds custom overage accrual callbacks, fiat amount validation, and supporting handler implementations.
Realization runs and fiat overage lines
openmeter/billing/charges/usagebased/service/run/..., openmeter/billing/charges/usagebased/service/line*, openmeter/billing/service/invoicecalc/...
Updates allocation and fiat-transaction decisions, maps custom overages into fiat invoice lines, and honors explicit collection-period overrides.
Invoice accrual and payment booking
openmeter/billing/charges/usagebased/service/run/...
Routes custom overage accrual through the new handler and skips payment operations for zero fiat totals.
Lifecycle and test-suite coverage
openmeter/billing/charges/service/..., .agents/skills/charges/SKILL.md
Adds lifecycle scenarios, overage assertions, suite helpers, and UTC timestamp guidance.

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

Sequence Diagram(s)

sequenceDiagram
  participant UsageBasedService
  participant RealizationRun
  participant LineMapper
  participant InvoiceService
  participant PaymentService
  UsageBasedService->>RealizationRun: create and allocate realization
  RealizationRun->>LineMapper: custom-currency overage totals
  LineMapper->>InvoiceService: fiat overage invoice line
  InvoiceService->>PaymentService: positive fiat amount
  PaymentService-->>InvoiceService: authorization and settlement
Loading

Possibly related PRs

Suggested reviewers: tothandras, chrisgacsal, galexihu

🚥 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: custom-currency support for usage-based credit-then-invoice flow.
✨ 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/custom-currency-usagebased-credit-then-invoice

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.

Comment thread openmeter/ledger/chargeadapter/usagebased.go
Comment thread openmeter/billing/charges/usagebased/service/linemapper.go
@turip turip added area/billing release-note/feature Release note: Exciting New Features labels Jul 24, 2026
@turip
turip marked this pull request as ready for review July 24, 2026 15:21
@turip
turip requested a review from a team as a code owner July 24, 2026 15:21

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

🧹 Nitpick comments (1)
openmeter/billing/charges/usagebased/service/run/create.go (1)

180-219: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Extract the shared "custom-currency overage → fiat-zero" rule instead of duplicating it.

Both sites independently reimplement "round totals, and for custom currency, convert overage to fiat to decide if NoFiatTransactionRequired/no-fiat-transaction should be true" — with a subtle difference (one skips the conversion call when totals are already zero, the other always runs it). Same effective result today, but two hand-rolled copies of this rule are an easy way for future changes to silently diverge.

  • openmeter/billing/charges/usagebased/service/run/create.go#L180-L219: extract this block's noFiatTransactionRequired derivation (lines 196-207) into a small shared helper (e.g. a method on Charge) that takes the rounded totals and settlement-mode flag.
  • openmeter/billing/charges/usagebased/service/creditheninvoice.go#L877-L892: replace this block's noFiatTransactionRequired derivation with a call to the same shared helper, dropping the divergent unguarded implementation.
🤖 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/billing/charges/usagebased/service/run/create.go` around lines 180
- 219, Extract the custom-currency overage-to-fiat-zero decision from the
noFiatTransactionRequired derivation in
openmeter/billing/charges/usagebased/service/run/create.go#L180-L219 into a
shared Charge helper accepting rounded totals and the settlement-mode flag,
preserving the existing zero-total guard and conversion behavior. Replace the
duplicated derivation in
openmeter/billing/charges/usagebased/service/creditheninvoice.go#L877-L892 with
the same helper so both paths use one rule.
🤖 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/billing/charges/usagebased/service/linemapper.go`:
- Around line 207-248: Preserve the existing FeatureKey when replacing
stdLine.UsageBased in populateCustomCurrencyOverageFromRun. Capture the current
stdLine.UsageBased.FeatureKey before constructing the new
billing.UsageBasedLine, then assign it to the replacement line while leaving the
other overage fields unchanged.

In `@openmeter/ledger/chargeadapter/usagebased.go`:
- Around line 179-186: The OnCustomCurrencyOverageAccrued method lacks the
inline implementation TODO requested for deferred custom-currency overage
booking. Add a TODO[implement] comment at the unsupported-return stub in
OnCustomCurrencyOverageAccrued, while preserving its validation and
meta.ErrCustomCurrencyNotSupported behavior.

---

Nitpick comments:
In `@openmeter/billing/charges/usagebased/service/run/create.go`:
- Around line 180-219: Extract the custom-currency overage-to-fiat-zero decision
from the noFiatTransactionRequired derivation in
openmeter/billing/charges/usagebased/service/run/create.go#L180-L219 into a
shared Charge helper accepting rounded totals and the settlement-mode flag,
preserving the existing zero-total guard and conversion behavior. Replace the
duplicated derivation in
openmeter/billing/charges/usagebased/service/creditheninvoice.go#L877-L892 with
the same helper so both paths use one rule.
🪄 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 Plus

Run ID: 260fe640-8a04-447c-9481-989a565f8406

📥 Commits

Reviewing files that changed from the base of the PR and between 6457e4b and 35c6769.

📒 Files selected for processing (29)
  • .agents/skills/charges/SKILL.md
  • openmeter/billing/charges/meta/customcurrency.go
  • openmeter/billing/charges/meta/customcurrency_test.go
  • openmeter/billing/charges/models/creditpurchase/detailedline.go
  • openmeter/billing/charges/models/creditpurchase/detailedline_test.go
  • openmeter/billing/charges/service/base_test.go
  • openmeter/billing/charges/service/flatfee_costbasis_test.go
  • openmeter/billing/charges/service/handlers_test.go
  • openmeter/billing/charges/service/usagebased_costbasis_test.go
  • openmeter/billing/charges/service/usagebased_test.go
  • openmeter/billing/charges/testutils/handlers.go
  • openmeter/billing/charges/usagebased/charge.go
  • openmeter/billing/charges/usagebased/handler.go
  • openmeter/billing/charges/usagebased/service/create.go
  • openmeter/billing/charges/usagebased/service/creditheninvoice.go
  • openmeter/billing/charges/usagebased/service/creditsonly.go
  • openmeter/billing/charges/usagebased/service/lineengine.go
  • openmeter/billing/charges/usagebased/service/linemapper.go
  • openmeter/billing/charges/usagebased/service/linemapper_test.go
  • openmeter/billing/charges/usagebased/service/run/create.go
  • openmeter/billing/charges/usagebased/service/run/invoice.go
  • openmeter/billing/charges/usagebased/service/run/payment.go
  • openmeter/billing/charges/usagebased/service/run/payment_test.go
  • openmeter/billing/charges/usagebased/service/run/service.go
  • openmeter/billing/consts.go
  • openmeter/billing/service/invoicecalc/collectionat.go
  • openmeter/billing/service/invoicecalc/collectionat_test.go
  • openmeter/ledger/chargeadapter/usagebased.go
  • openmeter/ledger/chargeadapter/usagebased_test.go
💤 Files with no reviewable changes (2)
  • openmeter/billing/charges/usagebased/service/run/service.go
  • openmeter/billing/charges/service/flatfee_costbasis_test.go

Comment thread openmeter/billing/charges/usagebased/service/linemapper.go
Comment thread openmeter/ledger/chargeadapter/usagebased.go
@turip
turip merged commit fa82c04 into main Jul 27, 2026
43 of 44 checks passed
@turip
turip deleted the feat/custom-currency-usagebased-credit-then-invoice branch July 27, 2026 08:31
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