Skip to content

feat(api): credits v3 [WIP]#3938

Draft
tothandras wants to merge 1 commit intomainfrom
feat/credits-api
Draft

feat(api): credits v3 [WIP]#3938
tothandras wants to merge 1 commit intomainfrom
feat/credits-api

Conversation

@tothandras
Copy link
Contributor

No description provided.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

Important

Review skipped

Ignore keyword(s) in the title.

⛔ Ignored keywords (1)
  • WIP

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2796d47f-0c45-424e-a518-9879495b426e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/credits-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

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


/**
* The charge that will be used to purchase the credit.
* Omit for promotional credit grants.
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a comment: External charges should be entered to ensure correct revenue recognition.

* Defaults to 1.0 (1:1 ratio between credit and charge currency).
*/
@visibility(Lifecycle.Read, Lifecycle.Create)
per_unit_cost_basis?: Shared.Numeric = "1.0";
Copy link
Member

Choose a reason for hiding this comment

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

This should be > 0. If 0 then they should use the promotional settlement type.

Comment on lines +83 to +87
* Voiding is a forward-looking, irreversible operation. Credits already
* consumed by usage remain unaffected — only the remaining balance is
* forfeited. The grant transitions to `voided` status and a breakage
* ledger entry is recorded for the forfeited amount. Only `pending`
* and `active` grants can be voided.
Copy link
Contributor

Choose a reason for hiding this comment

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

We could change terminology as I think (based on metered entitlements) it was confusing for most ppl, in general I think customers expected

  • void to be retroactive
  • cancel to be forward looking

wdyt @hekike @tothandras ?

Copy link
Contributor

Choose a reason for hiding this comment

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

So void would do correction against already burned usage. Cancel would zero out available amount ?

*/
@visibility(Lifecycle.Read)
@example("200.00")
pending: Shared.Numeric;
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't technically exist in the current accounting structure, we have numbers for receivable values, and we can create a dimension specifically for credit purchases if we want to track that separately but that would include all not yet paid balances which isn't exactly pending

* - `adjusted`: Manual or system adjustment.
*/
@friendlyName("BillingCreditLedgerEntryType")
enum CreditLedgerEntryType {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: if you intend this for / this is based on the history view we discussed, I'd propose changing the name, (at least for) I'd use this for more export / revrecog usecases where i'd expect this to be an almost 1:1 match with internal representations

Copy link
Contributor

Choose a reason for hiding this comment

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

That's an interesting point because we said UI should be more simple than ledger but ledger export is an important use-case.

*/
@friendlyName("BillingCreditLedgerActorType")
@summary("Credit ledger actor type")
enum CreditLedgerActorType {
Copy link
Contributor

Choose a reason for hiding this comment

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

Q: should we maybe approach it in a way where

  1. System
    1.1 SubTypes (like Automation, Payment clearance...)
  2. User
    2.1 (Exact user like name, id, stg.... + type like Customer, Admin...)

...Shared.Resource;

/**
* Funding or settlement method of the grant.
Copy link
Contributor

Choose a reason for hiding this comment

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

This has nothing to do with the settlement. Comment should be:

Funding method of the grant. e.g. paid in external system

Invoice: "invoice",

// The grant is funded outside the system
ExternalSettlement: "external_settlement",
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd do:

/**
 * The funding method describes how the grant is funded or settled.
 *
 * - `none`: No funding workflow applies, for example promotional grants
 * - `invoice`: The grant is funded by an in-system invoice flow
 * - `external`: The grant is funded outside the system (e.g., wire transfer, external invoice, or manual reconciliation)
 */
@friendlyName("BillingCreditFundingMethod")
@summary("Credit funding method")
enum CreditFundingMethod {
  // No funding workflow applies
  None: "none",
  // The grant is funded by an in-system invoice flow
  Invoice: "invoice",
  // The grant is funded outside the system
  External: "external",
}

Settlement" does carry a connotation that payment was completed, which is a stronger claim than what you actually know. All you can assert is that OpenMeter isn't managing the funding workflow for this grant.

*/
@friendlyName("BillingCreditAvailabilityPolicy")
@summary("Credit availability policy")
enum CreditAvailabilityPolicy {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider:

/**
 * When credits become available for consumption after a purchase.
 *
 * - `on_creation`: Credits are available as soon as the grant is created.
 * - `on_authorization`: Credits are available once the payment is authorized.
 * - `on_settlement`: Credits are available once the payment is settled.
 */
@friendlyName("BillingCreditAvailabilityPolicy")
@summary("Credit availability policy")
enum CreditAvailabilityPolicy {
  // Credits become eligible when the grant is created
  OnCreation: "on_creation",

  // Credits become eligible when payment is authorized
  OnAuthorization: "on_authorization",

  // Credits become eligible when payment is settled
  OnSettlement: "on_settlement",
}

* Present when a payment or settlement workflow applies to the grant.
*/
@visibility(Lifecycle.Read, Lifecycle.Create)
payment?: {
Copy link
Contributor

Choose a reason for hiding this comment

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

I find it confusing to have both purchase and payment. Move these under purchase?

@visibility(Lifecycle.Create, Lifecycle.Read)
@minValue(1)
@maxValue(1000)
priority: int16 = 10;
Copy link
Contributor

Choose a reason for hiding this comment

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

is max value aligned with ledger DB schema? cc @GAlexIHU

* - `adjusted`: Manual or system adjustment.
*/
@friendlyName("BillingCreditLedgerEntryType")
enum CreditLedgerEntryType {
Copy link
Contributor

Choose a reason for hiding this comment

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

That's an interesting point because we said UI should be more simple than ledger but ledger export is an important use-case.

@friendlyName("BillingCreditLedgerEntry")
@summary("Credit ledger entry")
model CreditLedgerEntry {
...OmitProperties<Shared.Resource, "updated_at" | "deleted_at">;
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: add comment:

// Ledger entries are immutable

Comment on lines +83 to +87
* Voiding is a forward-looking, irreversible operation. Credits already
* consumed by usage remain unaffected — only the remaining balance is
* forfeited. The grant transitions to `voided` status and a breakage
* ledger entry is recorded for the forfeited amount. Only `pending`
* and `active` grants can be voided.
Copy link
Contributor

Choose a reason for hiding this comment

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

So void would do correction against already burned usage. Cancel would zero out available amount ?

* External settlement of a credit grant.
*/
@friendlyName("UpdateCreditGrantExternalPaymentSettlementRequest")
model CreditGrantExternalPaymentSettlement {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't naming say it's a payload/body/create? Feels weird it's not tied to an operation

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants