ING-67 test(credits): Cover cross-entity scenarios#5532
Merged
Conversation
Decision 5.5 of the multi-entity billing dive-in states that enterprise customers centralize prepaid wallet purchases through HQ but bill through regional entities. Credits are gated by currency only — there is no entity check — so a wallet under one billing entity must apply to an invoice on another as long as the currencies match. The current `Credits::AppliedPrepaidCreditsService` selects wallets via `customer.wallets.active.with_positive_balance.where(balance_currency: invoice.currency)`, which already meets the contract. The ticket is test-only: pin the existing behaviour and add the two QA scenarios from the dive-in explicitly. Replace the earlier generic "different billing entities" context with two explicit scenarios that mirror the dive-in's QA wording. The first scenario constructs a USD wallet bound to a `us` billing entity and a USD invoice bound to a `eu` billing entity (same customer, same organization), then asserts that the service applies credits across the entity boundary and decrements the wallet balance. The second scenario constructs an EUR wallet bound to a `us` billing entity and a USD invoice bound to the same `us` entity, then asserts the wallet is skipped and its balance stays untouched. Together the two contexts pin down that the gate is currency, not entity — a future change that flipped the relationship would break exactly one of the two scenarios. Production code is unchanged. Both contexts live in `spec/services/credits/applied_prepaid_credits_service_spec.rb` alongside the existing currency-mismatch coverage.
aquinofb
approved these changes
May 20, 2026
D1353L
pushed a commit
that referenced
this pull request
May 26, 2026
## Context Decision 5.5 of the multi-entity billing dive-in states that enterprise customers centralize prepaid wallet purchases through HQ but bill through regional entities. Credits are gated by currency only — there is no entity check — so a wallet under one billing entity must apply to an invoice on another as long as the currencies match. `Credits::AppliedPrepaidCreditsService` already meets that contract: it selects wallets via `customer.wallets.active.with_positive_balance.where(balance_currency: invoice.currency)`. The ticket is test-only, so this PR pins the existing behaviour and adds the two QA scenarios from the dive-in explicitly. ## Description The earlier coverage in `spec/services/credits/applied_prepaid_credits_service_spec.rb` had a generic "different billing entities" context. That context is replaced with two explicit scenarios that mirror the dive-in's QA wording so a future regression maps cleanly back to the decision document. The first scenario constructs a USD wallet bound to a `us` billing entity and a USD invoice bound to a `eu` billing entity — same customer, same organization. It asserts the service applies credits across the entity boundary and that the wallet balance is decremented. This is the centralized-purchase, regional-billing path that motivated the decision. The second scenario constructs an EUR wallet bound to a `us` billing entity and a USD invoice bound to the same `us` entity. It asserts the wallet is skipped and the balance stays untouched. This is the negative case that proves the gate is currency, not entity: a future change that flipped the relationship would break exactly one of the two scenarios, making the intent legible from the failure. Production code is unchanged.
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
Decision 5.5 of the multi-entity billing dive-in states that enterprise customers centralize prepaid wallet purchases through HQ but bill through regional entities. Credits are gated by currency only — there is
no entity check — so a wallet under one billing entity must apply to an invoice on another as long as the currencies match.
Credits::AppliedPrepaidCreditsServicealready meets that contract: it selects walletsvia
customer.wallets.active.with_positive_balance.where(balance_currency: invoice.currency). The ticket is test-only, so this PR pins the existing behaviour and adds the two QA scenarios from the dive-inexplicitly.
Description
The earlier coverage in
spec/services/credits/applied_prepaid_credits_service_spec.rbhad a generic "different billing entities" context. That context is replaced with two explicit scenarios that mirror thedive-in's QA wording so a future regression maps cleanly back to the decision document.
The first scenario constructs a USD wallet bound to a
usbilling entity and a USD invoice bound to aeubilling entity — same customer, same organization. It asserts the service applies credits across theentity boundary and that the wallet balance is decremented. This is the centralized-purchase, regional-billing path that motivated the decision.
The second scenario constructs an EUR wallet bound to a
usbilling entity and a USD invoice bound to the sameusentity. It asserts the wallet is skipped and the balance stays untouched. This is the negativecase that proves the gate is currency, not entity: a future change that flipped the relationship would break exactly one of the two scenarios, making the intent legible from the failure.
Production code is unchanged.