Skip to content

Add modular billing credit ledger#212

Merged
ishaanxgupta merged 20 commits into
mainfrom
codex/modular-billing-credits
Jun 1, 2026
Merged

Add modular billing credit ledger#212
ishaanxgupta merged 20 commits into
mainfrom
codex/modular-billing-credits

Conversation

@ishaanxgupta
Copy link
Copy Markdown
Member

Summary

  • Adds a modular billing package with Mongo-backed credit wallets, lots, reservations, ledger entries, usage events, and Razorpay helpers.
  • Moves editable plan/top-up/token consumption rules into src/utils/billing.py.
  • Replaces the placeholder billing route with thin checkout, summary, webhook, top-up, and ledger APIs.
  • Wires v2 memory ingest/batch ingest to reserve credits before workflow start and debit/refund/release credits from Temporal lifecycle activities.

Tests

  • python -m pytest tests\test_billing.py
  • python -m compileall src\billing src\api\routes\billing.py src\api\routes\v2\memory.py src\api\routes\v2\activities.py src\api\routes\v2\jobs.py src\utils\billing.py
  • git diff --check

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Fails
🚫

🔐 This PR modifies sensitive files: src/config/settings.py. These require review by a core maintainer (@ishaanxgupta or @ved015) before merging.

Warnings
⚠️

📦 This PR changes 2987 lines (additions + deletions). Large PRs are harder to review thoroughly — consider splitting it.

Messages
📖

✅ Targeting main. Please squash commits before merging to keep the git history clean.

Generated by 🚫 dangerJS against 3ead8fa

@ishaanxgupta ishaanxgupta marked this pull request as ready for review June 1, 2026 06:48
@ishaanxgupta ishaanxgupta requested a review from ved015 as a code owner June 1, 2026 06:48
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

✅ Staging Deployment Report

Item Value
Branch codex/modular-billing-credits
Commit 6e24d1a
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

Comment thread src/api/routes/billing.py Fixed
Comment thread src/billing/service.py Fixed
Comment thread src/billing/store.py Fixed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🔍 API Schema Diff

---REPORT---

🔄 Modified

  • 🟡 CHANGED: root['paths']['/api/billing/plans']['get']['responses']['200']['content']['application/json']['schema']['items']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['summary']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['operationId']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['properties']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummaryResponse']['properties']['plans']['items']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][1]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][2]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][4]

Auto-generated by API Schema Diff workflow

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a modular credit ledger system for billing, replacing the previous Razorpay routes. It adds a new src/billing package to manage credit estimates, reservations, debits, and grants, and integrates these operations into the memory ingest and job workflows. Feedback on the changes highlights several critical concurrency issues in src/billing/store.py where non-atomic state checks and updates could lead to double debits, duplicate refunds, or negative lot balances. Additionally, a severe security vulnerability was identified in the payment verification route where a missing checkout record bypasses ownership checks. Other recommendations include wrapping synchronous database calls in asyncio.to_thread to avoid blocking the FastAPI event loop, adding missing indexes on id fields in MongoDB collections, and safely parsing JSON in the webhook handler to prevent unhandled 500 errors.

Comment thread src/billing/store.py Outdated
Comment thread src/billing/store.py Outdated
Comment thread src/api/routes/billing.py
Comment thread src/api/routes/billing.py Outdated
Comment thread src/billing/store.py
Comment thread src/billing/store.py Outdated
Comment thread src/api/routes/billing.py Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 1, 2026

Greptile Summary

This PR introduces a complete modular billing system: Mongo-backed credit wallets, credit lots, reservations, a ledger, Razorpay checkout/webhook helpers, and wires v2 memory ingest/batch/retry/cancel routes into the credit lifecycle.

  • Core ledger (src/billing/store.py): All multi-step mutations (grant_credits, reserve_credits, commit_debit, release_reservation) are now wrapped in MongoDB transactions, fixing the sequencing and partial-failure bugs flagged in earlier rounds. Duplicate-key handling uses isinstance with the imported pymongo.errors.DuplicateKeyError class and separate in-memory dicts for checkouts vs. webhook events.
  • Route integration: ingest_memory_v2, batch_ingest_memory_v2, retry_job, and cancel_job all gate on a billing_reservation_created flag so that only freshly-created reservations are released on failure, avoiding the duplicate-release bug. The webhook handler now marks events as processed after the grant runs (not before), and the event-ID fallback is an empty string rather than the ambient event ID.
  • Tests: Regression tests cover idempotent grants, reused-reservation guards, cross-account isolation, cancel/retry failure paths, and the checkout-vs-event-record collision.

Confidence Score: 4/5

Safe to merge after reviewing the two minor observations; no correctness-breaking issues found in the billing lifecycle.

All previously flagged issues — transaction ordering, DuplicateKeyError matching, webhook event-before-grant ordering, cross-account reservation reuse, empty-event-id dedup, and the ingest/retry/cancel reservation lifecycle — are resolved in this HEAD. The remaining comments are non-blocking: a missing current_period_start field in Pro subscription grants, a silent fallback job type in commit_job_billing, and a non-thread-safe singleton initialisation. The billing system is new and touches a financial-critical path, so the score is held at 4 to encourage careful production monitoring.

src/billing/store.py and src/billing/service.py warrant extra attention during production rollout — the store's MongoDB transaction semantics and singleton initialisation under concurrent workers are the areas most likely to surface subtle issues at scale.

Important Files Changed

Filename Overview
src/billing/store.py 948-line core ledger. All critical mutations are now MongoDB-transaction-wrapped; DuplicateKeyError caught by concrete type; separate dicts for checkouts vs. webhook events. Previously flagged sequencing bugs are addressed.
src/api/routes/billing.py Webhook handler now marks events processed after the grant, raises 400 on missing event ID, and rejects unknown package IDs; verify endpoint raises 400 when checkout is absent; payment_id fallback is empty string.
src/api/routes/v2/jobs.py retry_job uses a single wide try block: InsufficientCredits returns 402, all other exceptions release the freshly-created reservation then call mark_failed. cancel_job releases billing only after the cancel+mark_cancelled block succeeds.
src/api/routes/v2/memory.py Both ingest routes pre-compute job_id, set billing_reservation_created=True only on fresh reservations, and release only that flag in exception handlers — correctly avoiding release of already-running jobs' reservations.
src/billing/service.py Clean thin service layer. commit_job_billing re-estimates (no buffer) at commit time; grant_pro_subscription/grant_topup use deterministic idempotency keys keyed on payment_id.
src/api/routes/v2/activities.py mark_job_succeeded_activity commits billing before marking succeeded; mark_job_dead_letter_activity releases billing before marking dead-letter; both are idempotent on retry.

Sequence Diagram

sequenceDiagram
    participant Client
    participant IngestRoute as ingest_memory_v2
    participant BillingService
    participant BillingStore
    participant MongoDB
    participant Temporal

    Client->>IngestRoute: POST /v2/memory/ingest
    IngestRoute->>BillingService: reserve_job_credits(user, job_id, job_type)
    BillingService->>BillingStore: ensure_account (upsert + trial grant)
    BillingStore->>MongoDB: transaction: lot + wallet + ledger
    BillingService->>BillingStore: reserve_credits(account_id, job_id, amount)
    BillingStore->>MongoDB: transaction: reservation(reserving→active) + wallet.$inc
    BillingService-->>IngestRoute: "ReservationResult(created=True)"
    IngestRoute->>Temporal: start_job_workflow(job)
    alt Workflow start fails
        Temporal--xIngestRoute: Exception
        IngestRoute->>BillingStore: "release_reservation (if created=True)"
        BillingStore->>MongoDB: transaction: reservation→released + wallet restore
        IngestRoute-->>Client: 503
    else Workflow starts
        Temporal-->>IngestRoute: ok
        IngestRoute-->>Client: 202 job handle
        Note over Temporal: Job runs…
        Temporal->>BillingStore: commit_debit (via mark_job_succeeded_activity)
        BillingStore->>MongoDB: transaction: lots consumed + wallet updated + reservation committed + ledger
    end
Loading

Fix All in Cursor Fix All in Codex Fix All in Claude Code

Reviews (13): Last reviewed commit: "Handle grant webhook and retry failure e..." | Re-trigger Greptile

Comment thread src/api/routes/billing.py Outdated
Comment thread src/billing/store.py
Comment thread src/billing/store.py
Comment thread src/billing/store.py
Comment thread src/billing/store.py
Comment thread src/api/routes/billing.py Outdated
Comment thread src/billing/store.py Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🔍 API Schema Diff

---REPORT---

🔄 Modified

  • 🟡 CHANGED: root

Auto-generated by API Schema Diff workflow

Copy link
Copy Markdown
Member Author

Addressed the relevant bot findings on the latest PR branch:

  • Removed the unused imports flagged by the code-quality comments.
  • Hardened Razorpay verification so grants require a server-side checkout record owned by the current user.
  • Added safe webhook JSON parsing.
  • Moved synchronous billing service/store calls out of the async route loop via asyncio.to_thread.
  • Added missing Mongo indexes for billing document id fields.
  • Made reservation debit idempotency safer by atomically claiming reservations before wallet/lot mutation.
  • Prevented cross-account reservation reuse and added regression coverage.
  • Added a changelog entry for the user-facing billing API work.

Validation run locally:

  • python -m pytest tests\test_billing.py -> 10 passed
  • python -m compileall src\billing src\api\routes\billing.py src\api\routes\v2\memory.py src\api\routes\v2\jobs.py src\utils\billing.py
  • git diff --check

The broader API test subset could not run locally because this Windows environment is missing fastapi; the failing CI import path was patched in 0aaa7a2, and the full GitHub CI has restarted on that commit.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

✅ Staging Deployment Report

Item Value
Branch codex/modular-billing-credits
Commit 8c7b097
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🔍 API Schema Diff

---REPORT---

🔄 Modified

  • 🟡 CHANGED: root['paths']['/api/billing/plans']['get']['responses']['200']['content']['application/json']['schema']['items']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['summary']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['operationId']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['properties']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummaryResponse']['properties']['plans']['items']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][2]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][1]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][4]

Auto-generated by API Schema Diff workflow

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

✅ Staging Deployment Report

Item Value
Branch codex/modular-billing-credits
Commit ac6b66a
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🔍 API Schema Diff

---REPORT---

🔄 Modified

  • 🟡 CHANGED: root['paths']['/api/billing/plans']['get']['responses']['200']['content']['application/json']['schema']['items']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['summary']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['operationId']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['properties']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummaryResponse']['properties']['plans']['items']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][2]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][1]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][4]

Auto-generated by API Schema Diff workflow

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

✅ Staging Deployment Report

Item Value
Branch codex/modular-billing-credits
Commit b2217ea
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🔍 API Schema Diff

---REPORT---

🔄 Modified

  • 🟡 CHANGED: root['paths']['/api/billing/plans']['get']['responses']['200']['content']['application/json']['schema']['items']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['summary']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['operationId']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['properties']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummaryResponse']['properties']['plans']['items']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][1]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][4]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][2]

Auto-generated by API Schema Diff workflow

Comment thread src/api/routes/v2/jobs.py
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

✅ Staging Deployment Report

Item Value
Branch codex/modular-billing-credits
Commit 8c222a6
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🔍 API Schema Diff

---REPORT---

🔄 Modified

  • 🟡 CHANGED: root['paths']['/api/billing/plans']['get']['responses']['200']['content']['application/json']['schema']['items']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['summary']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['operationId']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['properties']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummaryResponse']['properties']['plans']['items']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][4]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][2]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][1]

Auto-generated by API Schema Diff workflow

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

✅ Staging Deployment Report

Item Value
Branch codex/modular-billing-credits
Commit 9d3af6f
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🔍 API Schema Diff

---REPORT---

🔄 Modified

  • 🟡 CHANGED: root['paths']['/api/billing/plans']['get']['responses']['200']['content']['application/json']['schema']['items']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['summary']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['operationId']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['properties']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummaryResponse']['properties']['plans']['items']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][2]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][4]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][1]

Auto-generated by API Schema Diff workflow

Comment thread src/api/routes/billing.py
Comment thread src/api/routes/v2/jobs.py
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

✅ Staging Deployment Report

Item Value
Branch codex/modular-billing-credits
Commit 07d9381
Environment Staging
Health http://3.6.255.148:8001/health
API Docs http://3.6.255.148:8001/docs
Smoke Tests success

🟢 Staging is live and healthy! Test your changes at the staging URL above.

Ready to ship? Comment /promote on this PR to merge to main and deploy to production.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🔍 API Schema Diff

---REPORT---

🔄 Modified

  • 🟡 CHANGED: root['paths']['/api/billing/plans']['get']['responses']['200']['content']['application/json']['schema']['items']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['summary']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['operationId']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/order']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['requestBody']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['paths']['/api/billing/razorpay/verify']['post']['responses']['200']['content']['application/json']['schema']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['properties']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummaryResponse']['properties']['plans']['items']['$ref']
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][2]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][4]
  • 🟡 CHANGED: root['components']['schemas']['BillingSummary']['required'][1]

Auto-generated by API Schema Diff workflow

@ishaanxgupta ishaanxgupta merged commit 7b0855a into main Jun 1, 2026
16 of 17 checks passed
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.

1 participant