Conversation
aquinofb
force-pushed
the
ing-112
branch
5 times, most recently
from
July 5, 2026 02:15
ebd45d4 to
d5a4324
Compare
aquinofb
marked this pull request as ready for review
July 6, 2026 14:49
aquinofb
force-pushed
the
ing-112
branch
2 times, most recently
from
July 6, 2026 15:23
2461bfe to
b599a37
Compare
aquinofb
requested review from
a team,
annvelents,
groyoh,
mariohd and
vincent-pochet
and removed request for
a team
July 6, 2026 19:51
mariohd
approved these changes
Jul 8, 2026
brunomiguelpinto
approved these changes
Jul 9, 2026
Contributor
|
we should extract the allocation logic to be used in the 2 lanes to be easier to maintain |
groyoh
reviewed
Jul 9, 2026
groyoh
left a comment
Contributor
There was a problem hiding this comment.
I haven't been able to look deeply into the business logic but the code coverage looks sound so I'll trust that.
## Context Lago consumes wallet credits through two paths that diverge for multi-wallet customers. Billing already cascades across wallets in priority order, but ongoing usage stopped at the first wallet, absorbing all usage there and going negative instead of spilling onto the next. The negative ongoing balance is customer-visible and breaks the two-wallet (paid + free) setup. ## Description Add Wallets::Balance::AllocateOngoingUsageByWalletsService, which mirrors the billing-side cascade: it nets current, draft, progressive and pay-in-advance usage per fee, then distributes it across the customer's active wallets in priority order. Each applicable wallet fills to its balance and spills the overflow onto the next; the last applicable wallet absorbs the remainder and may go negative, since ongoing usage has no invoice to carry the excess. An over-billed fee key reduces a per-currency budget the same way billing credits reduce the invoice total, so wallets never absorb more than the customer's net unbilled usage. A wallet with an active threshold-based recurring rule keeps the previous behavior (absorbs everything, may go negative) so the rule can fire and refill it. RefreshWalletsService runs the allocator once and feeds each wallet its precomputed amount. Because the cascade makes allocations interdependent, every refresh now persists all wallets: the previous target_wallet_ids narrowing would leave non-target wallets stale. The per-fee single-wallet assignment is replaced, which leaves FindApplicableOnFeesService and BuildAllocationRulesService without callers; they are removed in a follow-up PR to keep this one focused. Pre-existing scenario specs documenting the old "whole fee to the first applicable wallet" behavior are updated to the new billing-parity cascade.
## Context Review feedback on the cascade PR. ## Description Touch updated_at alongside last_ongoing_balance_sync_at in the bulk wallet sync, so wallets whose ongoing values did not change do not keep a stale updated_at. Assert the catch-all wallet in every cascade scenario, pin that a fee targeting an underfunded wallet goes negative on that wallet without spilling, and add visual headers to the cascade scenarios.
## Context Review follow-up: touch_all sets updated_at together with the named column in one statement, replacing the manual update_all pair.
aquinofb
added a commit
that referenced
this pull request
Jul 16, 2026
## Context The ongoing-usage cascade (#5825) replaced the per-fee single-wallet assignment. FindApplicableOnFeesService and BuildAllocationRulesService lost their only caller there and are now dead code. ## Description Delete both services and their specs. No other code references them.
annvelents
approved these changes
Jul 16, 2026
annvelents
left a comment
Contributor
There was a problem hiding this comment.
Amazing work on refactoring! 👏 🤩
left just one comment
## Context Review feedback: some organizations have customers with hundreds of wallets and send usage frequently, so their wallets refresh often. The allocator re-read each wallet's balance with one query per wallet. ## Description Read all balances up front with a single query. Freshness is the same because the per-wallet reads already happened before the allocation loop, and a single statement gives one consistent snapshot across wallets instead of staggered reads.
aquinofb
added a commit
that referenced
this pull request
Jul 17, 2026
## Context The ongoing-usage cascade (#5825) replaced the per-fee single-wallet assignment. FindApplicableOnFeesService and BuildAllocationRulesService lost their only caller there and are now dead code. ## Description Delete both services and their specs. No other code references them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Lago consumes wallet credits through two paths that diverge for multi-wallet customers. Billing already cascades across wallets in priority order, but ongoing usage stopped at the first wallet, taking it negative instead of spilling onto the next. The negative ongoing balance is customer-visible (UI, API, threshold rules) and breaks the two-wallet (paid + free) setup.
Linear: https://linear.app/getlago/issue/ING-112
Behavior
Wallet B= priority 1 (consumed first),Wallet A= priority 2. Ongoing balance per wallet after the given usage:Changes
Wallets::Balance::AllocateOngoingUsageByWalletsService, mirroring the billing cascade (Credits::AllocatePrepaidCreditsByWalletsService): net usage per fee, distribute across active wallets in priority order, fill each to its balance, spill the overflow, and let the last applicable wallet go negative. An over-billed fee key reduces a per-currency budget the same way billing credits reduce the invoice total.RefreshWalletsServiceruns the allocator once and feeds each wallet its precomputed amount. Because the cascade makes allocations interdependent, every refresh persists all wallets; the previoustarget_wallet_idsnarrowing would leave non-target wallets stale.FindApplicableOnFeesServiceandBuildAllocationRulesServicewithout callers. They are deleted in [ING-112] Remove unused wallet allocation services #5884 (~600 lines of pure deletion) to keep this one focused on the behavior change.Validation
The specs from this branch (they encode the expected cascade behavior) were run against
mainand against this branch -- same 46 examples:main(old allocation)Per-scenario ongoing balances (each row is one failing-then-fixed spec):
mainPreserved behavior passes on both codebases: single wallet going negative, threshold-rule wallets absorbing and refilling, and ongoing-balance alerts firing as the balance crosses into negative.
Edge cases pinned by the new allocator unit suite (the service does not exist on
main, so no old-code column):