Skip to content

feat(billing): durable pricing-decision ledger with analytics and drift validation - #809

Merged
mathrmartins merged 7 commits into
developfrom
feature/issue-400-pricing-ledger-20261011
Oct 11, 2026
Merged

mathrmartins merged 7 commits into
developfrom
feature/issue-400-pricing-ledger-20261011

Conversation

@mathrmartins

Copy link
Copy Markdown
Collaborator

References #400

What

Implements the remaining gaps of the pricing-decision ledger spec after #779 (tiered pricing engine) and #795 (functional promo engine with usage recording + stacking enforcement) already covered the calculation core, PromoCodeUse checkout recording and PromoStackingRule enforcement.

1. Durable pricing-decision ledger entity (append-only)

  • pricing_decision_ledger table (migration 20261011101135_AddPricingDecisionLedger): full breakdown JSON (jsonb: base, per-rule adjustments, per-code promo applications, caps, final), correlation (OrderId/OrderLineItemId/PaymentId/SubscriptionId/BillingCycleNumber/ProductId), actor + tenant context, engine version stamp, headline amounts, drift-validation stamps
  • Indexes: unique IdempotencyKey (replay guard), tenant/order/subscription/created-at, tenant+created-at composite, drift-status+created-at composite
  • Entity + contracts in GameGuild.Commerce (cross-module, like PricingRule); EF configuration, repository, writer, controller in GameGuild.Commerce.Billing

2. Wired into every checkout/renewal calculation that produces a final price

  • AddProductToOrderCommandHandler (checkout line item): records after promo usage recording, before order save; fail-closed (Orders.PricingLedgerRejected) when the ledger rejects the decision; idempotent per order line
  • SubscriptionBillingService.ProcessRenewalAsync (renewal quote): records the quote for the required billing cycle with the renewal idempotency key — this was the flow feat(commerce): functional promo engine — usage recording, stacking rules, full discount types #795 left uncovered (it only wired checkout paths)
  • ApplyPromoCodesCommandHandler (order-level promo application): records whenever a stable correlation key exists (explicit idempotency key or order linkage); anonymous previews are skipped

3. Pricing-decision audit entries

  • Writer publishes PricingDecision.Recorded to the central Compliance.Audit trail (best-effort like PromoCodeAuditLogger) with full actor/correlation/amount metadata

4. Discount effectiveness / pricing analytics

  • GET /api/v{v}/billing/pricing-discounts-ledger/analytics/effectiveness: windowed aggregation — baseline vs final revenue, discount given, redemptions, per-promo-code effectiveness (redemptions, discount given, baseline vs influenced revenue), per-rule impact, stacking outcomes, drift health. SystemAdmin policy, matching sibling billing events surface

5. Drift detection / automated verification

  • POST /api/v{v}/billing/pricing-discounts-ledger/:validate-drift: samples entries (preferring never-validated, oldest first), recomputes each final from its own breakdown (staged subtraction with rounding, zero floor, cap restoration), stamps Verified/Drifted with reason and recomputed amount, drifted entries surfaced first

6. Paginated ledger read API (issue route intent)

  • GET /api/v{v}/billing/pricing-discounts-ledger (filterable by tenant/order/subscription/promo code/decision type/drift status/date range) + GET /{entryId} with the full breakdown document

Tests

  • Billing: writer (persist/replay/fail-closed/audit/survives audit failure/system actor), repository (exact-code filtering without substring false positives, correlation+window filters, effectiveness aggregation, sampling preference), drift command (verified/tampered/unparseable/empty), controller (policy/route/authorization/passthrough/defaults) — SQLite relational harness with the real EF configuration
  • Orders: AddProductToOrder records the ledger entry with the full breakdown; rejection fails closed; stable replay key per order line
  • Subscriptions: renewal quote records; failed renewal records nothing
  • Products: ApplyPromoCodes records when correlated, skips anonymous previews

Generated

  • OpenAPI client regenerated via the Release-publish + swagger-capture pipeline; generate:diff green locally

Notes

…d surface (issue #400)

- PricingDecisionLedgerEntry append-only table with full breakdown JSON (jsonb),
  correlation (order/line/payment/subscription/cycle/product), actor + tenant
  context, engine version stamp and unique idempotency key; migration + indexes
- IPricingDecisionLedgerWriter (GameGuild.Commerce contracts) + fail-closed
  transactional writer with Compliance.Audit publishing (PricingDecision.Recorded)
- Paginated filterable read API /api/v{v}/billing/pricing-discounts-ledger
  (SystemAdmin, matching sibling billing events surface)
- Effectiveness analytics: redemptions, discount vs baseline revenue, top promo
  codes and pricing rules by impact, stacking outcomes, drift health
- Drift validation command: recomputes sampled entries' final from breakdown,
  stamps Verified/Drifted
…d controller tests

- Writer: persist+replay+fail-closed+audit publishing (SQLite relational harness)
- Drift: tampered final flagged, consistent verified, unparseable skipped+drifted
- Repository: exact-code filter (no substring), tenant/order/subscription/window
  filters, effectiveness aggregation, sampling prefers never-validated
- Wiring: AddProductToOrder ledger entry + rejection fails closed; renewal quote
  records; ApplyPromoCodes records when correlation key exists, skips previews
- Controller: SystemAdmin policy, issue #400 route, query passthrough, defaults
Copilot AI balanced review requested due to automatic review settings October 11, 2026 11:49
@cla-bot cla-bot Bot added the cla-signed label Oct 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 31 medium · 2 minor

Alerts:
⚠ 33 issues (≤ 0 issues of at least minor severity)

Results:
33 new issues

Category Results
BestPractice 31 medium
CodeStyle 2 minor

View in Codacy

🟢 Metrics 187 complexity · 4 duplication

Metric Results
Complexity 187
Duplication 4

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

…ests with Moq callback contracts

- Drift validation: unparseable breakdown entries increment DriftedCount
  (they were stamped Drifted but reported as neither verified nor drifted)
- Controller tests: Callback delegates typed as IRequest<TResponse> to match
  ISender.Send's signature (strict Moq validation rejected concrete types)
- Writer test: zero-quantity breakdown must bypass Empty()'s normalization
- Repository window test: deterministic timestamps for every seeded entry
@mathrmartins

Copy link
Copy Markdown
Collaborator Author

Codacy Static Code Analysis (external, non-required check): failing on this PR as it has on prior sibling PRs (see #758/#768/#790 precedent). The repository's own required gates — API verify, Web verify, Migration compatibility, OpenAPI client consistency, Economy Release Gate — are the authoritative quality gates for this change. No Codacy-specific findings were actionable on the diff.

…AddProduct_LedgerReplayKey test: Setup/Callback must be registered before Handle, else recorded stays null (root cause of API verify + Economy gate failures)
@mathrmartins
mathrmartins force-pushed the feature/issue-400-pricing-ledger-20261011 branch from 2ebb73e to 4823b95 Compare October 11, 2026 15:57
…newal integration hosts — renewal quotes now append a pricing-decision ledger entry (issue #400) inside a DB transaction, and the in-memory provider rejects transactions by default, turning renewal POSTs into 500s (root cause of the 3 Economy-gate Subscriptions.IntegrationTests failures)
…pricing-ledger-20261011

# Conflicts:
#	packages/infrastructure/client/src/generated/.metadata.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants