``` ██████╗ ███████╗██╗ ██╗ ██████╗ ██████╗ █████╗ ██╔══██╗██╔════╝██║ ██║██╔═══██╗██╔══██╗██╔══██╗ ██████╔╝█████╗ ██║ ██║██║ ██║██████╔╝███████║ ██╔══██╗██╔══╝ ╚██╗ ██╔╝██║ ██║██╔══██╗██╔══██║ ██║ ██║███████╗ ╚████╔╝ ╚██████╔╝██║ ██║██║ ██║ ╚═╝ ╚═╝╚══════╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ```**AI REVENUE RECOVERY CONTROL PLANE**
Revenue at risk isn't recovered by a recommendation.
It's recovered when the provider confirms the money came back.
AI PROPOSES · POLICY AUTHORIZES · PROVIDER EXECUTES · WEBHOOK OBSERVES · RECONCILIATION PROVES · AUDIT RECORDS
Failed payments create compounding revenue leakage. Systems built to detect failures are not recovery systems. Most retry infrastructure operates as a blunt instrument: fixed intervals, no failure-mode differentiation, no verification that money was actually captured.
REVORA is different. It closes the full loop — from failure signal to provider-confirmed, independently reconciled recovery — under deterministic policy constraints.
PAYMENT FAILURE
→ AI DIAGNOSIS (Gemini 3.6 Flash)
→ RECOVERY DECISION
→ POLICY ENGINE (deterministic guardrail)
→ PROVIDER ACTION (Razorpay order)
→ CUSTOMER CHECKOUT
→ WEBHOOK (payment.captured)
→ HMAC-SHA256 VERIFICATION
→ INDEPENDENT RECONCILIATION
→ RECOVERED
Creating a Razorpay order is not recovery.
Checkout success is not recovery.
Recovery closes only when the provider confirms capture and independent reconciliation matches.
| Stage | What happens |
|---|---|
| OBSERVE | Ingest payment failure signal, error codes, customer history |
| DIAGNOSE | Classify failure mode: transient network, issuer timeout, invalid instrument |
| DECIDE | Gemini 3.6 Flash proposes action + confidence score |
| POLICY CHECK | PolicyEngine evaluates retry budget, status locks, risk thresholds — ALLOW or DENY |
| ACT | Provider adapter creates recovery order if and only if policy granted ALLOW |
| OBSERVE RESULT | Await customer checkout and inbound provider webhook |
| VERIFY | HMAC-SHA256 signature validation + independent Razorpay API reconciliation |
| STOP / RECOVER | Transition to RECOVERED on matching proof, or STOPPED on limit exhaustion |
Layer breakdown
INGESTION LAYER
FastAPI HTTP API, webhook ingress, auth gate
Revenue Risk Engine Failure triage and context extraction
Diagnosis Engine Root-cause classification (transient / permanent / escalation)
REASONING LAYER
AI Router Routes to FAST (simple) or DEEP (complex) AI mode
Gemini 3.6 Flash Analyzes failure telemetry → structured RecoveryDecision
Deterministic Fallback Offline rule-based fallback if AI provider unavailable
CONTROL LAYER
Policy Engine Sole authorization gate. Max 3 attempts. ALLOW or DENY.
Recovery Pipeline Orchestrates diagnosis → decision → policy → provider
EXECUTION LAYER
PaymentProvider Provider-neutral abstract protocol
RazorpayProvider Implemented reference adapter (Razorpay REST API)
VERIFICATION LAYER
Webhook Processor Raw-body HMAC-SHA256 verification + event idempotency
Reconciliation Service Independent two-way API call: amount, currency, order, status
SQLite Repositories Durable storage: payments, recoveries, audit, webhook events
REVORA's recovery logic communicates with payment infrastructure through a clean, provider-neutral protocol:
PaymentProvider (abstract boundary)
├── RazorpayProvider ── implemented · verified · Razorpay Test Mode
├── [StripeProvider] ── future adapter
└── [AdyenProvider] ── future adapter
Precision: Razorpay is the implemented and tested reference adapter. The architecture is provider-neutral at the boundary; it does not claim multi-provider support as a current capability.
Every adapter must supply:
- Order creation
- Independent payment status reconciliation
- Webhook signature verification
The following was verified end-to-end using Razorpay Test Mode:
13-step verified lifecycle
| Step | Action | Real? |
|---|---|---|
| 1 | Failed ₹500.00 INR payment (bank_timeout) enters REVORA |
— |
| 2 | Gemini 3.6 Flash evaluates failure telemetry → RETRY_NOW |
REAL |
| 3 | PolicyEngine checks attempt count (0/3) → ALLOW |
REAL |
| 4 | Razorpay adapter creates authentic recovery order via REST API | REAL |
| 5 | ReconciliationService records server-side expectation: 50000 minor units, INR |
REAL |
| 6 | Customer opens Razorpay Standard Checkout, completes test payment | REAL |
| 7 | Razorpay generates new payment entity (pay_...) bound to recovery order |
REAL |
| 8 | Razorpay dispatches payment.captured webhook over HTTP |
REAL |
| 9 | REVORA validates X-Razorpay-Signature over raw bytes using RAZORPAY_WEBHOOK_SECRET (HMAC-SHA256) |
REAL |
| 10 | Webhook processor claims idempotency lease on event_id |
REAL |
| 11 | ReconciliationService queries Razorpay REST API: status=captured, amount=50000, currency=INR, order match |
REAL |
| 12 | PaymentIntent → CAPTURED, Recovery → RECOVERED |
REAL |
| 13 | Complete chain of custody recorded in append-only SQLite audit repository | REAL |
ORDER CREATED ≠ RECOVERED
CHECKOUT SUCCESS ≠ RECOVERED
RECOVERY = provider-confirmed capture + independent reconciliation
SYNTHETIC — deterministic benchmark, not production financial results.
4,000 cases generated with fixed seed20260825. Validates control-plane decision logic. Does not represent real merchant revenue.
┌─────────────────────────────────────────────────────────────────┐
│ BENCHMARK (seed: 20260825) │
│ 4,000 cases · 1,200 history │
├──────────────────────────────────┬──────────────────────────────┤
│ REVORA Policy Pipeline │ Naive Retry Baseline │
├──────────────────────────────────┼──────────────────────────────┤
│ ₹3,035,091.00 recovered │ ₹1,696,559.00 recovered │
│ 49.82% recovery rate │ 27.85% recovery rate │
│ 1,959 successful │ 1,091 successful │
│ 1,908 stopped by policy │ 2,909 failed retries │
│ 133 escalated to review │ │
│ 2,041 non-recovered │ │
├──────────────────────────────────┴──────────────────────────────┤
│ Total at risk: ₹6,092,450.00 │
├─────────────────────────────────────────────────────────────────┤
│ DELTA +₹1,338,532.00 │
│ +21.97 percentage points │
│ +78.90% relative recovered-money uplift │
└─────────────────────────────────────────────────────────────────┘
All figures are synthetic simulation results. Not real money. Not production data.
Controls currently implemented and verifiable in this repository:
| Control | Implementation |
|---|---|
| Bounded retry policy | PolicyEngine: max 3 attempts, deterministic DENY on exhaustion |
| Terminal-state protection | State machine: RECOVERED/STOPPED cases cannot be re-entered |
| HMAC-SHA256 webhook verification | app/core/webhook_signature.py: constant-time comparison over raw bytes |
| Event idempotency | webhook_events table: duplicate event_id silently acknowledged |
| Independent reconciliation | ReconciliationService: 4-way match (status, amount, currency, order_id) |
| Exact amount/currency/order binding | PaymentIntent: server-side expectation verified at reconciliation |
| API authentication | X-REVORA-API-Key via hmac.compare_digest |
| AI provider fallback | AIRouter: deterministic offline fallback on AIProviderError |
| Secret isolation | app/core/config.py: env-only, redacted from logs and __repr__ |
| Auditable audit records | Append-only SQLite audit trail, sequence-numbered |
# 1. Clone and set up environment
git clone <repository-url>
cd REVORA
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# 2. Configure credentials
cp .env.example .env
# Edit .env — see below for required keys
# 3. Start server
.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
# 4. Run offline test suite (no credentials required)
.venv/bin/pytest -q
# → 684 passed, 1 warning, 124 subtests passedEndpoints after startup:
| Dashboard | http://127.0.0.1:8000 |
| Test Checkout | http://127.0.0.1:8000/checkout |
| API Docs (Swagger) | http://127.0.0.1:8000/docs |
| Health | http://127.0.0.1:8000/health |
Required environment variables
# Razorpay Test Mode (from Razorpay Dashboard → Settings → API Keys)
RAZORPAY_KEY_ID=rzp_test_...
RAZORPAY_KEY_SECRET=<your_razorpay_test_secret>
# Inbound Webhook Secret (from Razorpay Dashboard → Settings → Webhooks)
RAZORPAY_WEBHOOK_SECRET=<your_webhook_secret>
# Merchant API Authentication
REVORA_API_KEY=<your_revora_api_key>
# AI Provider
REVORA_AI_PROVIDER=gemini
GEMINI_API_KEY=<your_gemini_api_key>
# Storage
REVORA_DATABASE_PATH=/tmp/revora.sqlite3
REVORA_DEMO_ENABLED=trueWebhook ingress for local development
Razorpay requires a public URL to deliver webhooks. Use a tunneling utility during local development:
zrok share public 127.0.0.1:8000
# → exposes https://<host>.zrok.ioIn Razorpay Dashboard → Settings → Webhooks:
- URL:
https://<your-zrok-host>/api/webhooks/razorpay - Events:
payment.captured,payment.failed,order.paid - Secret: must match
RAZORPAY_WEBHOOK_SECRET
Tunneling is for local development and demonstration only.
The built-in dashboard exposes three verifiable scenarios:
Scenario A — Live Recovery (Razorpay Test Mode)
Full end-to-end agentic loop: failure → Gemini → policy → order → checkout → webhook → reconciliation → RECOVERED.
Scenario B — Policy Guardrail
Exhausted retry account (3/3 attempts): PolicyEngine returns DENY. Provider never called. Zero Razorpay orders created.
Scenario C — Verified Case Inspection
Inspect an existing reconciled recovery case with complete auditable trail and reconciliation proof.
See docs/demo.md for the complete step-by-step reviewer walkthrough.
| Component | Nature | What it proves |
|---|---|---|
| Gemini 3.6 Flash AI | REAL | Contextual reasoning via google-genai SDK on live failure data |
| Razorpay order creation | REAL | Authenticated REST API call to Razorpay /v1/orders |
| Customer test payment | REAL | Razorpay Standard Checkout SDK, Test Mode cards |
| Webhook delivery | REAL | HTTP delivery from Razorpay servers to /api/webhooks/razorpay |
| HMAC-SHA256 verification | REAL | Constant-time comparison over raw bytes using webhook secret |
| API reconciliation | REAL | Server-to-server call to Razorpay /v1/orders/{id}/payments |
| Recovery state transitions | REAL | State machine driven by verified provider reconciliation |
| Audit trail | REAL | Append-only SQLite event records |
| Benchmark dataset | SYNTHETIC | 4,000 cases, seed 20260825 — validates policy mechanics |
| Batch simulation | SYNTHETIC | Algorithmic comparison: REVORA vs naive retry baseline |
All 14 routes
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/health |
Service + SQLite health probe | None |
GET |
/ |
Interactive control plane dashboard | None |
GET |
/checkout |
Standalone Razorpay Test Mode checkout | None |
GET |
/docs |
Swagger / OpenAPI interactive docs | None |
GET |
/redoc |
ReDoc API reference | None |
POST |
/demo/recovery/prepare |
Prepare isolated recovery scenario (rec_live_{uuid}) |
None |
POST |
/demo/recovery |
Simulate a single recovery pipeline run | None |
POST |
/demo/simulation |
Run 4,000-case synthetic benchmark | None |
POST |
/api/recovery/execute |
Execute real recovery pipeline | X-REVORA-API-Key |
GET |
/api/recovery/{id} |
Retrieve recovery state, execution state, audit trail | X-REVORA-API-Key |
POST |
/api/razorpay/orders |
Create Razorpay Test Mode order + record expectation | X-REVORA-API-Key |
POST |
/api/razorpay/verify |
Verify checkout signature + reconcile | X-REVORA-API-Key |
POST |
/api/webhooks/razorpay |
Ingest Razorpay webhook (HMAC verified) | X-Razorpay-Signature |
File tree
REVORA/
├── app/
│ ├── core/
│ │ ├── config.py # Configuration boundary & secret isolation
│ │ ├── diagnosis_engine.py # Payment failure classification
│ │ ├── money.py # Exact-or-raise currency & minor units math
│ │ ├── policy_engine.py # Deterministic authorization gate
│ │ ├── recovery_agent.py # Agent protocol & deterministic fallback
│ │ ├── recovery_pipeline.py # End-to-end recovery pipeline orchestrator
│ │ └── recovery_agent.py # RecoveryContext & agent protocol
│ ├── models/
│ │ ├── audit.py # Append-only audit events & trail
│ │ ├── decision.py # AI & policy decision dataclasses
│ │ ├── payment.py # Payment domain models
│ │ ├── payment_intent.py # Server-side capture expectations
│ │ ├── recovery.py # Recovery entities & actions
│ │ └── revenue_risk.py # Failure classification enums
│ ├── repositories/
│ │ ├── protocols.py # Storage interface protocols
│ │ ├── in_memory.py # In-memory adapters (dev/test)
│ │ └── sqlite.py # Durable SQLite repository
│ ├── services/
│ │ ├── ai_provider.py # AIRecoveryProvider protocol & fallback
│ │ ├── ai_router.py # FAST / DEEP routing logic
│ │ ├── gemini_provider.py # Google Gemini 3.6 Flash adapter
│ │ ├── razorpay_provider.py # Razorpay PaymentProvider adapter
│ │ ├── razorpay_service.py # Razorpay REST API client
│ │ ├── reconciliation.py # Two-way payment reconciliation
│ │ └── webhook_processor.py # Webhook validation & idempotency
│ ├── simulation/
│ │ ├── batch_runner.py # Deterministic benchmark engine
│ │ └── synthetic_dataset.py # Dataset generator (seed: 20260825)
│ └── main.py # FastAPI app & route definitions
├── static/ # SPA dashboard & checkout
├── tests/ # 684-test automated suite
├── docs/ # Architecture & reviewer docs
│ ├── architecture.md
│ ├── architecture-diagram.md
│ ├── demo.md
│ └── assets/ # SVG diagrams
├── requirements.txt
└── README.md
Engineering challenges encountered during development
1. Shared recovery ID audit pollution
Early demo runs reused static IDs (rec_live_demo). The append-only audit log retained historical webhook + reconciliation events, causing stages 7–9 to appear complete before any payment. Fixed by generating fresh rec_live_{uuid8} identifiers on every /demo/recovery/prepare call.
2. Frontend polling race conditions
Background polling timers persisted after scenario switches, overwriting newer execution states with stale responses. Fixed with monotonic activePollSessionId tokens that immediately invalidate obsolete polling cycles.
3. Payment entity correlation
When a customer completes checkout, Razorpay creates a new payment entity (pay_...) separate from the initial failure. Fixed by establishing PaymentIntent as the correlation nexus binding failure ID, recovery order ID, and captured payment ID.
4. Webhook ingress in local development
Local machines cannot receive external webhooks without public routing. Fixed by integrating headless zrok tunneling with raw-body HMAC validation and an idempotency lease table.
5. Strict reconciliation binding
Webhook receipt alone was insufficient — partial payments or misattributed orders could pass. Fixed with 4-attribute reconciliation: payment status, exact minor units, currency, and order ID association.
The lesson: treat recovery as a verified, auditable state transition — not an optimistic UI event.
- Prototype scope: Designed for evaluation and demonstration; not an off-the-shelf production deployment.
- Gateway coverage: Razorpay is the implemented reference adapter. Other gateways require a
PaymentProviderimplementation. - Benchmark scope: 4,000-case synthetic dataset validates policy mechanics; it does not predict real-world merchant conversion rates.
- Tunneling:
zrokis for local webhook ingress during development. Production requires proper webhook infrastructure. - Horizontal scaling: Production deployment would require async task queues (Celery/Kafka), multi-tenant isolation, and enterprise secret management.
- Additional provider adapters
- Subscription recovery
- Checkout abandonment recovery
| Document | Contents |
|---|---|
| docs/demo.md | Complete reviewer walkthrough: setup, demo paths, Razorpay live flow |
| docs/architecture.md | Deep-dive: components, data flow, invariants, benchmarks |
| docs/architecture-diagram.md | Mermaid architecture diagram |
AI proposes. Policy controls. Providers execute. Reconciliation proves.

