Payment Service (Port 8019) - THE FINAL SERVICE! 🎯¶
Overview¶
The Payment Service is the most complex backend service, providing comprehensive subscription management, Razorpay payment gateway integration, and billing operations for the entire Machine Agents platform. This service handles subscription lifecycle management, Free Plan trials, currency detection, invoice management, and payment webhooks.
Port: 8019
Path: machineagents-be/payment-service/src/main.py
Lines of Code: 5,876 (LARGEST SERVICE)
Total Functions: 156
File Size: 259 KB
Framework: FastAPI
Payment Gateway: Razorpay
Database: MongoDB (Cosmos DB) with 9 collections
[!IMPORTANT] > THE FINAL SERVICE! This documentation completes the comprehensive backend service documentation project covering all 23 services across ~500,000+ words of technical documentation.
Architecture¶
graph TB
subgraph "Frontend Layer"
Dashboard[User Dashboard]
Checkout[Payment Checkout]
end
subgraph "Payment Service (Port 8019)"
API[FastAPI Application<br/>5,876 lines]
subgraph "Core Components"
SubService[SubscriptionService<br/>Dynamic Plan Builder]
Validator[Validation Layer<br/>15+ validators]
Calculator[Pricing Calculator<br/>Currency Detection]
end
subgraph "Razorpay Integration"
RClient[Razorpay Client<br/>Timeout: 15s]
Circuit[Circuit Breaker<br/>5 failures → 5min timeout]
Retry[Retry Logic<br/>tenacity]
end
subgraph "Caching Layer"
PlanCache[Plan Cache<br/>5 min TTL]
DynamicCache[Dynamic Cache<br/>5 min TTL]
end
subgraph "Async Processing"
ThreadPool[Thread Pool<br/>10 workers]
Executor[AsyncIO Loop]
end
end
subgraph "External Services"
Razorpay[Razorpay Gateway<br/>Payment Processing]
end
subgraph "Database (MongoDB/CosmosDB)"
Collections[11 Collections]
Users[(users_multichatbot_v2)]
PayHistory[(payment_history)]
Subs[(subscriptions)]
SubID[(subscriptionID)]
BaseFeatures[(baseFeatures)]
FeaturesGlobal[(featuresGlobal)]
AddFeatures[(additional_featuresGlobal)]
FeaturesPerUser[(features_per_user)]
RazorPlans[(razorpay_plans)]
UserSubs[(user_subscriptions)]
Invoices[(subscription_invoices)]
end
Dashboard -->|Subscription Requests| API
Checkout -->|Payment Flow| API
API --> SubService
API --> Validator
API --> Calculator
SubService -->|Query Plans| Collections
API -->|Create Order/Subscription| RClient
RClient -->|Retry on Failure| Retry
RClient -->|Track Failures| Circuit
RClient <-->|API Calls| Razorpay
Razorpay -->|Webhooks| API
API -->|5min Cache| PlanCache
API -->|5min Cache| DynamicCache
API -->|Async DB Ops| ThreadPool
ThreadPool --> Executor
API --> Users
API --> PayHistory
SubService --> Subs
SubService --> SubID
SubService --> BaseFeatures
SubService --> FeaturesGlobal
SubService --> AddFeatures
API --> FeaturesPerUser
API --> RazorPlans
API --> UserSubs
API --> Invoices
style API fill:#2196F3,color:#fff
style SubService fill:#4CAF50,color:#fff
style RClient fill:#FF9800,color:#fff
style Circuit fill:#F44336,color:#fff
style PlanCache fill:#9C27B0,color:#fff
style Collections fill:#607D8B,color:#fff
Key Features¶
✅ Razorpay Payment Gateway Integration
✅ Dynamic Subscription System (4-collection architecture)
✅ Free Plan Support (90-day trials)
✅ Multi-Currency (INR/USD with automatic detection)
✅ Circuit Breaker Pattern (prevents API cascading failures)
✅ Subscription Lifecycle Management (create, cancel, update, renew)
✅ Invoice Management (tracking, statistics, resending)
✅ Webhook Handling (subscription & payment events)
✅ Comprehensive Validation (15+ validation functions)
✅ ** Thread Pool Executor** (10 workers for async operations)
✅ 5-Minute Plan Caching (performance optimization)
✅ Retry Logic with Exponential Backoff (tenacity)
Database Collections (11)¶
| Collection | Purpose | Key Fields |
|---|---|---|
users_multichatbot_v2 |
User data | user_id, email, pending_order_id, phone_no, country_code |
payment_history |
Payment transactions | paymentID, user_id, amount, currency, status |
subscriptions |
Plan definitions | subscription_id, pricing, offers, baseFeatureID, featureIDs |
subscriptionID |
Plan mapping | subscription_id, subscription_plan, billing_cycle |
baseFeatures |
Value-based features | baseFeatureID, no_of_chatbots, no_of_chat_sessions, no_of_linkcrawls |
featuresGlobal |
Boolean features | featureID, feature (normalized to snake_case) |
additional_featuresGlobal |
Additional boolean features | featureID, feature (extended feature set) |
features_per_user |
User feature assignments | user_id, feature limits and booleans |
razorpay_plans |
Razorpay plan sync | category, billing_cycle, currency, razorpay_plan_id |
user_subscriptions |
User subscription state | user_id, status, created_at, subscription_id, plan_category |
subscription_invoices |
Invoice records | invoice_id, user_id, amount, status, subscription_context |
[!IMPORTANT] > 11 Collections Total - Final audit revealed 2 additional collections:
additional_featuresGlobal(extended feature set) andfeatures_per_user(direct user feature assignment tracking).
Configuration Constants¶
Currency & Location Settings (Lines 471-475)¶
# Currency and location settings
INDIA_CURRENCY = "INR"
FOREIGN_CURRENCY = "USD"
INDIAN_COUNTRY_CODE = "IN"
Purpose: Standardize currency and country code handling across the service.
Subscription Configuration (Line 477)¶
Rationale: Razorpay has maximum subscription limits. Using 50-100 years simulates "unlimited" without complex renewal logic.
Webhook Configuration (Line 469)¶
Purpose: Verify webhook signatures from Razorpay to prevent spoofing.
[!WARNING] If
RAZORPAY_WEBHOOK_SECRETis not set, webhook signature verification may fail, exposing the service to malicious webhook requests.
Utility Helper Functions¶
calculate_discounted_amount(pricing, offers) (Lines 634-639)¶
Purpose: Calculate final amount after applying discount percentage.
Parameters:
pricing(int): Base price in smallest currency unit (paise/cents)offers(int): Discount percentage (0-100)
Returns: Discounted amount (int)
Implementation:
def calculate_discounted_amount(pricing: int, offers: int) -> int:
"""Calculate discounted amount based on pricing and offers"""
if pricing <= 0:
return 0
discount_amount = (pricing * offers) // 100
return pricing - discount_amount
Example:
# 20% discount on ₹3999
final_amount = calculate_discounted_amount(3999, 20)
# Returns: 3199 (₹3999 - 20% = ₹3199)
create_razorpay_order(order_data) (Lines 629-631)¶
Purpose: Async wrapper for creating Razorpay orders using unified API wrapper.
Parameters:
order_data(dict): Order creation payload
Returns: Razorpay order response
Implementation:
async def create_razorpay_order(order_data):
"""Create Razorpay order using unified wrapper"""
return await razorpay_api.call_api("order.create", order_data)
Dynamic Subscription System¶
4-Collection Architecture¶
The Payment Service implements a sophisticated 4-collection dynamic subscription system:
graph LR
A[subscriptions<br/>Plan Pricing & Features] --> D[Frontend API Response]
B[subscriptionID<br/>Plan Name & Cycle] --> D
C[baseFeatures<br/>Numeric Limits] --> D
E[featuresGlobal<br/>Boolean Features] --> D
SubService[SubscriptionService] --> A
SubService --> B
SubService --> C
SubService --> E
SubService --> D
style SubService fill:#4CAF50,color:#fff
Collection Relationships:
-
subscriptionscontains: -
pricing:{"INR": "1999", "USD": "25"} offers:{"INR": "20", "USD": "20"}(discount %)baseFeatureID: Links tobaseFeatures-
featureIDs: Array offeatureIDlinks tofeaturesGlobal -
subscriptionIDmaps: -
subscription_plan:"starter"|"pro"|"business"|"free" billing_cycle:"monthly"|"yearly"-
subscription_id: Unique identifier -
baseFeaturesdefines: -
no_of_chatbots:5|15|50|"custom" no_of_chat_sessions:1000|5000|20000no_of_linkcrawls:50|200|1000no_of_users:1|3|10-
grace_period:7|14|30(days) -
featuresGlobalstores: featureID:"F001","F002", etc.feature:"Advanced Analytics","Custom Branding", etc.- Normalized to
snake_case:"advanced_analytics","custom_branding"
SubscriptionService Class (Lines 119-386)¶
Purpose: Dynamically aggregates subscription plans from 4 collections.
Key Methods:
-
_get_all_plan_categories()(Lines 128-162) -
Returns:
["free", "starter", "pro", "business"] -
"Free" always first, then database order
-
_get_subscription_ids_for_category()(Lines 164-173) -
Gets all subscription IDs for a category (monthly + yearly)
-
_extract_pricing_by_cycle()(Lines 198-228) -
Returns:
{"monthly": {"INR": 1999, "USD": 25}, "yearly": {"INR": 1999, "USD": 25}} -
_extract_offers_by_cycle()(Lines 230-260) -
Returns:
{"monthly": {"INR": 20, "USD": 20}, "yearly": {"INR": 45, "USD": 45}} -
_extract_base_features_by_cycle()(Lines 262-300) -
Maps database fields to API fields:
no_of_chatbots→no_of_chatbotsno_of_linkcrawls→website_pages_crawl_cap
-
_build_features_dict()(Lines 302-324) -
Builds:
{"advanced_analytics": true, "custom_branding": false, ...} -
get_all_subscription_plans()(Lines 361-386) - Main entry point - returns complete plan array for frontend
Example Plan Output:
{
"category": "pro",
"pricing": {
"monthly": { "INR": 3999, "USD": 50 },
"yearly": { "INR": 3999, "USD": 50 }
},
"offers": {
"monthly": { "INR": 20, "USD": 20 },
"yearly": { "INR": 45, "USD": 45 }
},
"features": {
"advanced_analytics": true,
"custom_branding": true,
"api_access": false
},
"no_of_chatbots": { "monthly": 15, "yearly": 15 },
"no_of_chat_sessions": { "monthly": 5000, "yearly": 5000 },
"website_pages_crawl_cap": { "monthly": 200, "yearly": 200 },
"no_of_users": { "monthly": 3, "yearly": 3 },
"grace_period": { "monthly": 14, "yearly": 14 }
}
Caching Strategy¶
Plan Cache (Lines 59-62, 504-522)¶
Function: get_cached_subscription_plans()
Caches:
- All subscription IDs
- Plan categories
- Billing cycles
Refresh: Every 5 minutes OR on cache miss
Dynamic Plan Cache (Lines 388-420)¶
Function: get_dynamic_subscription_plans()
Caches:
- Complete aggregated plans from 4 collections
- Includes pricing, offers, features, base features
Refresh: Every 5 minutes OR on cache miss
Razorpay Integration¶
Client Initialization (Lines 584-613)¶
RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID")
RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET")
RAZORPAY_TIMEOUT = int(os.getenv("RAZORPAY_TIMEOUT", "15")) # 15 seconds
client_razorpay = razorpay.Client(auth=(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET))
client_razorpay.timeout = RAZORPAY_TIMEOUT
⚠️ SECURITY VALIDATION:
if not RAZORPAY_KEY_ID or not RAZORPAY_KEY_SECRET:
logger.error("Missing Razorpay credentials!")
raise ValueError("RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET must be set")
Logged (masked):
Circuit Breaker Pattern (Lines 426-430)¶
_razorpay_failures = 0
_razorpay_last_failure = 0
CIRCUIT_BREAKER_THRESHOLD = 5
CIRCUIT_BREAKER_TIMEOUT = 300 # 5 minutes
Purpose: Prevent cascading failures when Razorpay API is down.
Logic:
- If 5 consecutive failures → Circuit OPEN
- Wait 5 minutes
- Circuit HALF-OPEN (attempt 1 request)
- If successful → Circuit CLOSED
- If failed → Circuit OPEN again
Retry Logic with Tenacity (Lines 615-627)¶
def should_retry_razorpay_error(retry_state):
"""Determine if we should retry based on the exception type"""
if retry_state.outcome.failed:
exception = retry_state.outcome.exception()
# Don't retry client errors (4xx) - these won't succeed on retry
if isinstance(exception, razorpay.errors.BadRequestError):
return False
if isinstance(exception, razorpay.errors.SignatureVerificationError):
return False
# Retry on network errors, server errors, timeouts, etc.
return True
return False
Smart Retry Strategy:
- ✅ Retry: Network errors, 5xx server errors, timeouts
- ❌ Don't Retry: 4xx client errors (bad request, invalid signature)
Multi-Currency Support¶
Currency Detection (Lines 641-673, 675-677)¶
Function: detect_user_location(user_data, request_headers)
Detection Priority:
- User profile
country_code(highest priority) - Phone number prefix (
+91or91→ India) - Request headers:
cf-ipcountry(Cloudflare)x-country-code(custom)- Default: India (
IN)
Currency Mapping:
def get_currency_for_location(country_code: str) -> str:
return "INR" if country_code == "IN" else "USD"
Pricing Calculation (Lines 752-827)¶
Function: get_currency_pricing_info(pricing_data, offers_data, currency, country_code)
Returns:
{
"base_price": 3999,
"discount_percentage": 20,
"final_amount": 3199,
"currency": "INR",
"is_indian_customer": true,
"tax_info": {
"gst_applicable": true,
"gst_rate": 18.0,
"razorpay_fees_applicable": true,
"note": "GST and Razorpay fees will be handled automatically by Razorpay"
}
}
Tax Logic:
- INR: 18% GST applies
- USD: No GST, but Razorpay international fees apply
Free Plan System¶
Configuration (Lines 479-492)¶
FREE_PLAN_CONFIG = {
"enabled": True,
"default_trial_days": {
"starter": 90, # 90 days free for Starter
"pro": 90, # 90 days free for Pro
"business": 90 # 90 days free for Business
},
"min_trial_days": 1,
"max_trial_days": 365,
"eligible_plans": ["starter", "pro", "business"],
"require_payment_method": True, # Payment method required during trial
"auto_billing_after_trial": True
}
Environment Override:
def get_free_plan_config():
"""Get Free Plan configuration with environment variable overrides"""
config = FREE_PLAN_CONFIG.copy()
# Override from environment
if os.getenv("FREE_PLAN_ENABLED"):
config["enabled"] = os.getenv("FREE_PLAN_ENABLED").lower() == "true"
# Override trial days per plan
for plan in ["starter", "pro", "business"]:
env_key = f"FREE_PLAN_TRIAL_DAYS_{plan.upper()}"
if os.getenv(env_key):
config["default_trial_days"][plan] = int(os.getenv(env_key))
return config
Free Plan Validation (Lines 850-937)¶
Function 1: validate_free_plan_eligibility(user_id)
Checks:
- User has no existing active subscription
- User hasn't already used Free Plan
Function 2: validate_free_plan_request(underlying_plan, trial_days)
Checks:
- Underlying plan is in eligible_plans
- Trial days within min/max range
- Free Plan feature is enabled
Subscription Lifecycle¶
Subscription Status Constants (Lines 64-69)¶
ACTIVE_SUBSCRIPTION_STATUSES = ["active", "authenticated", "created"]
TERMINAL_SUBSCRIPTION_STATUSES = ["cancelled", "expired", "halted"]
PENDING_SUBSCRIPTION_STATUSES = ["pending", "pending_verification"]
Check Active Subscription (Lines 71-117)¶
Function: check_active_subscription(user_id, executor)
Returns: Tuple[bool, Optional[Dict], str]
Example:
has_active, subscription, message = await check_active_subscription("User_123", executor)
# Possible returns:
# (False, None, "No subscription found")
# (True, {...}, "Active subscription found with status: active")
# (False, {...}, "Subscription exists but is terminated with status: cancelled")
# (True, {...}, "Subscription is pending activation with status: pending")
Unlimited Subscription Payload (Lines 681-750)¶
Function: create_unlimited_subscription_payload(...)
Purpose: Create Razorpay subscription payload for "effectively unlimited" subscriptions.
Parameters:
plan_id: Razorpay plan IDquantity: Number of units (default: 1)trial_days: Trial period (0 = immediate billing)billing_cycle:"monthly"|"yearly"user_data: User information for notescustomer_info: Customer details
Implementation:
For Trial Subscriptions (trial_days > 0):
current_time = datetime.now(timezone.utc)
start_time = current_time + timedelta(days=trial_days)
end_date = start_time + timedelta(days=UNLIMITED_SUBSCRIPTION_YEARS * 365) # 50 years
payload = {
"plan_id": plan_id,
"start_at": int(start_time.timestamp()),
"end_at": int(end_date.timestamp()),
"customer_notify": True
}
For Immediate Subscriptions (trial_days = 0):
if billing_cycle.lower() == "yearly":
total_count = 100 # 100 years
else:
total_count = 1200 # 1200 months = 100 years
payload = {
"plan_id": plan_id,
"total_count": total_count,
"customer_notify": True
}
Note on 50 Years:
Razorpay subscriptions have a maximum lifetime. Using 50-100 years provides "effectively unlimited" subscriptions without requiring subscription renewal logic.
Comprehensive Validation System¶
Validation Functions (Lines 939-1044)¶
1. Customer Information Validation
def validate_customer_information(customer_email, customer_name, customer_phone):
"""
Validates:
- Email format
- Name (min 2 chars)
- Phone (optional, but if provided, must be valid)
"""
2. Plan Category Validation
def validate_plan_category(plan_category):
"""
Checks plan exists in database
Returns: {"valid": True/False, "message": "..."}
"""
3. Billing Cycle Validation
4. Trial Days Validation
def validate_trial_days(trial_days, min_days=1, max_days=365):
"""
Validates trial days within range
"""
5. Email Format Validation
def validate_email_format(email):
"""
Uses regex: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
"""
Error Response Helpers (Lines 1047-1074)¶
1. Generic Error Response
def create_error_response(error_message, status_code=400):
return JSONResponse(
status_code=status_code,
content={"error": error_message}
)
2. Validation Error Response
def create_validation_error_response(validation_result):
return JSONResponse(
status_code=400,
content={"error": validation_result["message"]}
)
3. Server Error Response
def create_server_error_response(error_message, exception=None):
if exception:
logger.error(f"Server error: {error_message} - Exception: {str(exception)}")
return JSONResponse(
status_code=500,
content={"error": f"Internal server error: {error_message}"}
)
Thread Pool Execution¶
Configuration (Lines 432-436)¶
executor = ThreadPoolExecutor(max_workers=10)
# Add a shutdown hook to close the executor
atexit.register(lambda: executor.shutdown(wait=True))
Purpose: Run synchronous MongoDB/Razorpay operations asynchronously.
Pattern:
loop = asyncio.get_event_loop()
def _sync_operation():
return collection.find_one({"user_id": user_id})
result = await loop.run_in_executor(executor, _sync_operation)
Used For:
- MongoDB queries
- Razorpay API calls
- Blocking I/O operations
MongoDB Document Serialization (Lines 26-57)¶
Function: serialize_mongo_doc(doc)
Purpose: Convert MongoDB documents to JSON-serializable format.
Handles:
datetime→ ISO format stringdate→ ISO format string_idfield → removed (MongoDB ObjectId)- Nested dictionaries (recursive)
- Lists (recursive)
Example:
mongo_doc = {
"_id": ObjectId("507f1f77bcf86cd799439011"),
"created_at": datetime(2024, 3, 15, 10, 30, 0),
"user": {
"name": "John Doe",
"joined": datetime(2024, 1, 1)
}
}
serialized = serialize_mongo_doc(mongo_doc)
# {
# "created_at": "2024-03-15T10:30:00",
# "user": {
# "name": "John Doe",
# "joined": "2024-01-01T00:00:00"
# }
# }
Database Indexes (Lines 566-582)¶
Created on Startup:
users_collection.create_index("pending_order_id")
users_collection.create_index("user_id")
payment_history_collection.create_index("paymentID")
# Compound index for sorting (CRITICAL for performance!)
user_subscriptions_collection.create_index([(\"user_id\", 1), (\"created_at\", -1)])
razorpay_plans_collection.create_index([(\"category\", 1), (\"billing_cycle\", 1), (\"currency\", 1)])
Purpose:
- Faster user lookups
- Efficient subscription sorting (newest first)
- Quick plan queries by category + billing cycle + currency
Request Monitoring¶
Logging Middleware (Lines 447-458)¶
@app.middleware("http")
async def log_requests(request, call_next):
start_time = time.time()
logger.info(f"Incoming request: {request.method} {request.url}")
response = await call_next(request)
process_time = time.time() - start_time
logger.info(f"Request completed: {request.method} {request.url} - Status: {response.status_code} - Time: {process_time:.2f}s")
return response
Log Output:
Incoming request: POST http://localhost:8019/v2/create-subscription
Request completed: POST http://localhost:8019/v2/create-subscription - Status: 200 - Time: 2.34s
Request Counters (Lines 422-424)¶
Purpose: Track concurrent requests (used for monitoring/debugging).
CORS Configuration (Lines 438-445)¶
[!WARNING] > CRITICAL SECURITY VULNERABILITY: Permissive CORS allows requests from ANY origin.
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # ⚠️ SECURITY ISSUE!
allow_credentials=True,
allow_methods=["*"],
allow_headers=[\"*\"],
)
Recommended Fix:
ALLOWED_ORIGINS = os.getenv(
"ALLOWED_ORIGINS",
"https://machineagents.ai,https://staging.machineagents.ai,http://localhost:3000"
).split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Environment Variables¶
Path: payment-service/.env
Required:
# MongoDB
MONGO_URI=mongodb://...
MONGO_DB_NAME=Machine_agent_dev
# Razorpay
RAZORPAY_KEY_ID=rzp_test_...
RAZORPAY_KEY_SECRET=...
RAZORPAY_WEBHOOK_SECRET=whsec_...
RAZORPAY_TIMEOUT=15
# Free Plan (Optional Overrides)
FREE_PLAN_ENABLED=true
FREE_PLAN_TRIAL_DAYS_STARTER=90
FREE_PLAN_TRIAL_DAYS_PRO=90
FREE_PLAN_TRIAL_DAYS_BUSINESS=90
Dependencies¶
Path: payment-service/requirements.txt
fastapi
pymongo
uvicorn
python-multipart
razorpay>=1.3.0
python-dotenv
tenacity>=8.0.0
pymilvus==2.3.4 # ⚠️ NOT USED! (Should be removed)
⚠️ Unused Dependency:
pymilvusis NOT used anywhere in the payment service
Recommended requirements.txt:
Dockerfile Configuration¶
Path: payment-service/Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ /app/src/
EXPOSE 8019
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8019"]
Key Points:
- Base image:
python:3.9-slim - No system dependencies (unlike other services with FFmpeg, etc.)
- Source code copied to
/app/src/ - Port exposed:
8019
API Endpoints (40+)¶
Core Subscription Management¶
1. GET / - Root Endpoint¶
Purpose: Service health check and info
Response:
2. GET /health - Health Check (Lines 2014-2050)¶
Purpose: Comprehensive service health status
Response:
{
"status": "healthy",
"timestamp": "2024-03-15T10:30:00Z",
"razorpay": {
"configured": true,
"timeout": 15,
"circuit_breaker_status": "closed"
},
"database": {
"connected": true,
"collections": 9
},
"cache": {
"plan_cache_age": 45,
"dynamic_cache_age": 32
}
}
3. GET /v2/subscription-plans - Get Subscription Plans (Lines 1910-1969)¶
Purpose: Get all available subscription plans with currency-specific pricing
Query Parameters:
country_code(optional): Override country detection
Headers:
cf-ipcountry: Cloudflare country codex-country-code: Custom country header
Response:
{
"plans": [
{
"category": "starter",
"pricing": {
"monthly": { "INR": 1999, "USD": 25 },
"yearly": { "INR": 1999, "USD": 25 }
},
"offers": {
"monthly": { "INR": 20, "USD": 20 },
"yearly": { "INR": 45, "USD": 45 }
},
"features": {
"advanced_analytics": true,
"custom_branding": false
},
"no_of_chatbots": { "monthly": 5, "yearly": 5 },
"no_of_chat_sessions": { "monthly": 1000, "yearly": 1000 },
"website_pages_crawl_cap": { "monthly": 50, "yearly": 50 }
}
],
"currency": "INR",
"country_code": "IN"
}
4. POST /v2/select-subscription - Select Subscription (Lines 2052-2203)¶
Purpose: User selects a subscription plan (stores selection, doesn't charge)
Form Data:
user_id(required)subscription_plan(required):"starter"|"pro"|"business"billing_cycle(required):"monthly"|"yearly"
Response:
{
"message": "Subscription plan selected successfully",
"user_id": "User_123",
"subscription_plan": "pro",
"billing_cycle": "monthly",
"subscription_id": "sub_67890"
}
5. POST /v2/create-subscription - Create Paid Subscription (Lines 2741-3174)¶
Purpose: Create a Razorpay subscription with immediate or trial billing
Form Data:
user_id(required)plan_category(required)billing_cycle(required)quantity(default: 1)trial_days(default: 0)customer_email(required)customer_name(required)customer_phone(optional)
Response:
{
"subscription_id": "sub_1234567890",
"status": "created",
"short_url": "https://rzp.io/i/abc123",
"plan_category": "pro",
"billing_cycle": "monthly",
"trial_end": "2024-06-15T10:30:00Z",
"first_billing_date": "2024-06-15",
"subscription_status": "authenticated",
"user_subscription_id": "..."
}
6. POST /v2/create-free-trial-subscription - Create Free Trial (Lines 2707-2739)¶
Purpose: Create free trial with auto-billing after trial period
Form Data:
user_id(required)plan_category(required)billing_cycle(required)trial_days(required)customer_email(required)customer_name(required)customer_phone(required)
Response:
{
"subscription_id": "sub_trial_123",
"status": "created",
"trial_end": "2024-06-15T10:30:00Z",
"message": "Free trial subscription created successfully"
}
7. POST /v2/create-free-plan-subscription - Create Free Plan (Lines 2205-2300)¶
Purpose: Create Free Plan subscription (user selects underlying plan for trial)
Form Data:
user_id(required)underlying_plan_category(required): Plan to activate after trialbilling_cycle(required)customer_email(required)customer_name(required)customer_phone(optional)
Response:
{
"subscription_id": "sub_free_123",
"status": "created",
"subscription_type": "free_plan",
"underlying_plan": "pro",
"trial_days": 90,
"trial_end": "2024-06-15T10:30:00Z"
}
Payment Processing¶
8. POST /v2/create-order - Create Payment Order (Lines 3176-3444)¶
Purpose: Create Razorpay payment order for selected subscription
Form Data:
user_id(required)
Response:
{
"order_id": "order_1234567890",
"amount": 3199,
"currency": "INR",
"razorpay_key_id": "rzp_test_...",
"subscription_details": {
"plan": "pro",
"billing_cycle": "monthly",
"base_price": 3999,
"discount": 20,
"final_amount": 3199
}
}
9. POST /v2/verify-payment - Verify Payment (Lines 3446-3577)¶
Purpose: Verify Razorpay payment signature and activate subscription
Form Data:
razorpay_order_id(required)razorpay_payment_id(required)razorpay_signature(required)user_id(required)
Response:
{
"message": "Payment verified successfully",
"payment_id": "pay_1234567890",
"order_id": "order_1234567890",
"subscription_activated": true,
"features_assigned": true
}
10. POST /v2/verify-subscription - Verify Subscription Payment (Lines 4046-4274)¶
Purpose: Verify subscription payment and activate
Form Data:
razorpay_subscription_id(required)razorpay_payment_id(required)razorpay_signature(optional)user_id(required)
Response:
{
"message": "Subscription verified and activated successfully",
"subscription_id": "sub_1234567890",
"status": "active",
"features_assigned": true,
"invoice_created": true
}
11. POST /v2/verify-free-trial-subscription - Verify Free Trial (Lines 3837-4044)¶
Purpose: Verify free trial setup and confirm payment mandate
Form Data:
razorpay_subscription_id(required)razorpay_payment_id(optional)razorpay_signature(optional)user_id(required)
Response:
{
"message": "Free trial subscription verified successfully",
"subscription_id": "sub_trial_123",
"status": "active",
"trial_end": "2024-06-15T10:30:00Z",
"features_assigned": true
}
Subscription Lifecycle Management¶
12. POST /v2/pause-subscription - Pause Subscription (Lines 4793-4802)¶
Purpose: Pause an active subscription
Form Data:
subscription_id(required)pause_at(default:"now")
Response:
{
"message": "Subscription paused successfully",
"subscription_id": "sub_1234567890",
"status": "paused"
}
13. POST /v2/resume-subscription - Resume Subscription (Lines 4804-4812)¶
Purpose: Resume a paused subscription
Form Data:
subscription_id(required)
Response:
{
"message": "Subscription resumed successfully",
"subscription_id": "sub_1234567890",
"status": "active"
}
14. POST /v2/cancel-subscription - Cancel Subscription (Lines 4814-4823)¶
Purpose: Cancel subscription at cycle end or immediately
Form Data:
subscription_id(required)cancel_at_cycle_end(default:true)
Response:
{
"message": "Subscription will be cancelled at cycle end",
"subscription_id": "sub_1234567890",
"status": "cancelled",
"cancel_at_cycle_end": true,
"features_removed": false
}
15. POST /v2/cancel-subscription-immediate - Immediate Cancel (Lines 4825-4833)¶
Purpose: Cancel subscription immediately with feature removal
Form Data:
subscription_id(required)
Response:
{
"message": "Subscription cancelled immediately",
"subscription_id": "sub_1234567890",
"status": "cancelled",
"features_removed": true
}
16. POST /v2/upgrade-subscription - Upgrade Subscription (Lines 4835-5074)¶
Purpose: Upgrade subscription with pro-rata billing
Form Data:
user_id(required)new_plan_category(required)new_billing_cycle(required)
Response:
{
"message": "Subscription upgraded successfully",
"old_subscription": {
"subscription_id": "sub_old_123",
"plan": "starter",
"status": "cancelled"
},
"new_subscription": {
"subscription_id": "sub_new_456",
"plan": "pro",
"status": "active",
"prorated_amount": 1500
},
"features_upgraded": true
}
Subscription Status & Information¶
17. GET /v2/subscription-status/{user_id} - Get Status (Lines 5227-5329)¶
Purpose: Get comprehensive subscription status for user
Response:
{
"user_id": "User_123",
"has_subscription": true,
"subscription": {
"subscription_id": "sub_1234567890",
"razorpay_subscription_id": "sub_1234567890",
"status": "active",
"plan_category": "pro",
"billing_cycle": "monthly",
"current_start": "2024-03-01T00:00:00Z",
"current_end": "2024-04-01T00:00:00Z",
"charge_at": "2024-04-01T00:00:00Z",
"auto_renew": true
},
"is_trial": false
}
18. GET /v2/trial-status/{user_id} - Get Trial Status (Lines 5076-5225)¶
Purpose: Get comprehensive trial status including remaining days
Response:
{
"user_id": "User_123",
"is_trial": true,
"trial_status": "active",
"trial_start": "2024-03-01T00:00:00Z",
"trial_end": "2024-06-01T00:00:00Z",
"trial_days_total": 90,
"trial_days_remaining": 45,
"trial_days_used": 45,
"trial_percentage_complete": 50.0,
"underlying_plan": "pro",
"billing_cycle": "monthly",
"first_billing": {
"date": "2024-06-01",
"amount": 3199,
"currency": "INR"
},
"auto_billing_enabled": true
}
19. GET /v2/check-payment-status/{order_id} - Check Payment (Lines 3719-3756)¶
Purpose: Check status of payment order
Response:
{
"order_id": "order_1234567890",
"status": "paid",
"amount": 3199,
"currency": "INR",
"payment_id": "pay_1234567890",
"is_stale": false
}
Feature Management¶
20. POST /v2/assign-features/{user_id}/{subscription_id} - Assign Features (Lines 3579-3681)¶
Purpose: Assign subscription features to user
Response:
{
"message": "Features assigned successfully",
"user_id": "User_123",
"features_assigned": {
"no_of_chatbots": "15",
"no_of_chat_sessions": "5000",
"website_pages_crawl_cap": "200",
"advanced_analytics": true,
"custom_branding": true
}
}
21. GET /v2/user-features/{user_id} - Get User Features (Lines 3683-3707)¶
Purpose: Get features currently assigned to user
Response:
{
"user_id": "User_123",
"features": {
"no_of_chatbots": "15",
"no_of_chat_sessions": "5000",
"no_of_linkcrawls": "200",
"advanced_analytics": true,
"custom_branding": true,
"api_access": false
}
}
Webhook Handling¶
22. POST /razorpay-webhook - Razorpay Webhook (Lines 3758-3835)¶
Purpose: Handle Razorpay webhook notifications
Headers:
x-razorpay-signature: Webhook signature
Events Handled:
payment.authorizedpayment.capturedpayment.failedsubscription.activatedsubscription.chargedsubscription.cancelledsubscription.pausedsubscription.resumedsubscription.pendingsubscription.haltedsubscription.authenticated
Response:
{
"status": "success",
"event": "subscription.activated",
"message": "Webhook processed successfully"
}
Invoice Management¶
23. GET /v2/razorpay-invoices/subscription/{subscription_id} - Get Subscription Invoices (Lines 5629-5655)¶
Purpose: Get all Razorpay-generated invoices for a subscription
Query Parameters:
limit(default: 10)skip(default: 0)
Response:
{
"subscription_id": "sub_1234567890",
"invoices": [
{
"id": "inv_123",
"amount": 3199,
"currency": "INR",
"status": "paid",
"issued_at": 1710000000,
"paid_at": 1710001000,
"subscription_id": "sub_1234567890"
}
],
"count": 5,
"total": 50
}
24. GET /v2/razorpay-invoices/user/{user_id} - Get User Invoices (Lines 5657-5721)¶
Purpose: Get all invoices for a user's subscriptions
Query Parameters:
limit(default: 10)skip(default: 0)
Response:
{
"user_id": "User_123",
"invoices": [...],
"count": 10,
"total invoices": 25,
"subscriptions_count": 2
}
25. GET /v2/razorpay-invoices/details/{invoice_id} - Get Invoice Details (Lines 5723-5759)¶
Purpose: Get detailed information about a specific invoice
Response:
{
"invoice_id": "inv_123",
"amount": 3199,
"currency": "INR",
"status": "paid",
"customer_details": {
"name": "John Doe",
"email": "john@example.com"
},
"line_items": [...],
"issued_at": 1710000000,
"paid_at": 1710001000
}
26. GET /v2/razorpay-invoices/stats/{user_id} - Invoice Statistics (Lines 5761-5843)¶
Purpose: Get invoice statistics for a user
Response:
{
"user_id": "User_123",
"total_invoices": 12,
"total_amount_paid": 38388,
"currency": "INR",
"first_invoice_date": "2024-01-01T00:00:00Z",
"last_invoice_date": "2024-03-15T00:00:00Z",
"paid_invoices": 11,
"pending_invoices": 1,
"plan_distribution": {
"pro_monthly": 10,
"pro_yearly": 2
},
"average_invoice_amount": 3199,
"payment_success_rate": 91.67
}
27. POST /v2/razorpay-invoices/resend/{invoice_id} - Resend Invoice (Lines 5845-5869)¶
Purpose: Resend a Razorpay invoice to customer
Form Data:
medium(default:"email"):"email"|"sms"
Response:
Admin Endpoints¶
28. GET /admin/free-plan-config - Get Free Plan Config (Lines 1584-1600)¶
Purpose: Get current Free Plan configuration
Response:
{
"enabled": true,
"default_trial_days": {
"starter": 90,
"pro": 90,
"business": 90
},
"min_trial_days": 1,
"max_trial_days": 365,
"eligible_plans": ["starter", "pro", "business"],
"require_payment_method": true,
"auto_billing_after_trial": true
}
29. POST /admin/free-plan-config - Update Free Plan Config (Lines 1602-1651)¶
Purpose: Update Free Plan configuration
Form Data:
enabled(required)starter_trial_days(default: 90)pro_trial_days(default: 90)business_trial_days(default: 90)
Response:
30. POST /admin/create-razorpay-plans - Create Razorpay Plans (Lines 1653-1793)¶
Purpose: Create all Razorpay plans from database (admin setup)
Response:
{
"message": "Razorpay plans created successfully",
"plans_created": 12,
"details": [
{
"category": "starter",
"billing_cycle": "monthly",
"currency": "INR",
"razorpay_plan_id": "plan_1234567890"
}
]
}
31. GET /admin/list-razorpay-plans - List Razorpay Plans (Lines 1795-1827)¶
Purpose: List all created Razorpay plans
Response:
{
"plans": [
{
"category": "starter",
"billing_cycle": "monthly",
"currency": "INR",
"razorpay_plan_id": "plan_1234567890",
"amount": 1999,
"interval": "monthly"
}
],
"count": 12
}
32. DELETE /admin/delete-all-razorpay-plans - Delete Razorpay Plans (Lines 1829-1862)¶
Purpose: Delete all Razorpay plan tracking records
Response:
33. POST /admin/convert-trial-to-paid - Convert Trial (Lines 5376-5500)¶
Purpose: Manually convert free trial to paid (admin/support)
Form Data:
user_id(required)force_conversion(default:false)
Response:
{
"message": "Trial converted to paid subscription successfully",
"user_id": "User_123",
"subscription_id": "sub_1234567890",
"new_status": "active",
"trial_ended_early": true
}
Debug Endpoints¶
34. GET /test-razorpay - Test Razorpay (Lines 1971-2012)¶
Purpose: Test Razorpay configuration
Response:
{
"message": "Razorpay configuration is working",
"key_id_configured": true,
"secret_configured": true,
"timeout": 15,
"circuit_breaker_status": "closed"
}
35. GET /debug/subscription-data - Debug Subscription Data (Lines 1864-1908)¶
Purpose: Debug endpoint to check subscription data structure
Response:
{
"subscriptions_count": 4,
"sample_subscription": {
"subscription_id": "SUB_001",
"pricing": { "INR": "1999", "USD": "25" },
"offers": { "INR": "20", "USD": "20" },
"baseFeatureID": "BF_001",
"featureIDs": ["F001", "F002"]
},
"subscription_ids_count": 8,
"base_features_count": 4,
"features_global_count": 15
}
36. POST /debug/form - Debug Form Data (Lines 5502-5514)¶
Purpose: Debug endpoint to see form data received
Response:
{
"form_data": {
"user_id": "User_123",
"plan_category": "pro"
},
"headers": {...},
"method": "POST"
}
37. GET /debug/subscription-status/{subscription_id} - Debug Subscription (Lines 5516-5559)¶
Purpose: Debug subscription status from Razorpay
Response:
{
"subscription_id": "sub_1234567890",
"razorpay_data": {
"status": "active",
"current_start": 1710000000,
"current_end": 1710086400
},
"local_record": {
"status": "active",
"user_id": "User_123",
"plan_category": "pro"
},
"status_match": true
}
Test Pages¶
38. GET /test - Test Page (Lines 5335-5374)¶
Purpose: Serve HTML test page for Razorpay payment service
Returns: HTML page for testing payment flows
RazorpayAPIWrapper Class (Lines 1424-1530)¶
Purpose: Unified Razorpay API wrapper with retry logic, circuit breaker, and timeout handling.
Key Methods:
call_api(operation, *args, **kwargs)¶
Purpose: Unified API call method with comprehensive error handling
Supported Operations:
Orders:
order.createorder.fetch
Subscriptions:
subscription.createsubscription.fetchsubscription.cancelsubscription.pausesubscription.resume
Plans:
plan.createplan.fetchplan.fetch_all
Invoice:
invoice.fetchinvoice.fetch_allinvoice.notify_by
Payment:
payment.fetch
Example Usage:
# Create order
order_data = razorpay_api.call_api('order.create', amount=100000, currency='INR')
# Create subscription
subscription_data = razorpay_api.call_api('subscription.create', payload)
# Fetch plan
plan = razorpay_api.call_api('plan.fetch', 'plan_1234567890')
# Cancel subscription
result = razorpay_api.call_api('subscription.cancel', 'sub_1234567890')
Retry Configuration:
@retry(
stop=stop_after_attempt(3), # Max 3 attempts
wait=wait_exponential(multiplier=1, min=2, max=10), # 2s, 4s, 8s
retry=retry_if_result(should_retry_razorpay_error),
before_sleep=before_sleep_log(logger, logging.WARNING)
)
Circuit Breaker Integration:
- Checks circuit status before call
- Updates failure count on error
- Resets on success
Webhook Event Handlers¶
handle_subscription_webhook(event, webhook_data) (Lines 4511-4716)¶
Purpose: Handle subscription-related webhook events
Events Handled:
1. subscription.activated
- Updates user subscription status to "active"
- Assigns features to user
- Sends confirmation email
2. subscription.charged
- Records payment in payment_history
- Creates invoice record
- Updates subscription billing dates
3. subscription.cancelled
- Updates status to "cancelled"
- Removes features if immediate cancellation
- Keeps features if cancel_at_cycle_end
4. subscription.paused
- Updates status to "paused"
- Optionally removes features
5. subscription.resumed
- Updates status to "active"
- Re-assigns features
6. subscription.pending
- Updates status to "pending"
- Awaits payment authorization
7. subscription.halted
- Updates status to "halted"
- Removes features
- Sends payment failure notification
8. subscription.authenticated
- Initial authorization
- Features not yet assigned
- Awaits first charge
handle_payment_webhook(event, webhook_data) (Lines 4718-4791)¶
Purpose: Handle payment-related webhook events
Events Handled:
1. payment.authorized
- Payment authorized, awaiting capture
2. payment.captured
- Payment successful
- Records in payment_history
- Activates subscription if first payment
3. payment.failed
- Payment failed
- Updates subscription status
- Sends failure notification
- May halt subscription after retries
Internal Helper Functions¶
create_subscription_base(...) (Lines 1076-1314)¶
Purpose: Base function for creating subscriptions (refactored common logic)
Parameters:
user_id,plan_category,billing_cyclequantity,trial_dayscustomer_email,customer_name,customer_phonerequest,force_trial
Returns: Subscription creation response
manage_subscription_base(subscription_id, action, **action_params) (Lines 1316-1422)¶
Purpose: Unified base function for subscription operations
Actions:
pause: Pause subscriptionresume: Resume subscriptioncancel: Cancel subscriptioncancel_immediate: Immediate cancel
Returns: Operation result with database update
assign_subscription_features_to_user(user_id, plan_config) (Lines 4305-4417)¶
Purpose: Assign features based on plan configuration
Feature Mapping:
{
"no_of_chatbots": "15",
"no_of_chat_sessions": "5000",
"website_pages_crawl_cap": "200",
"no_of_users": "3",
"grace_period": "14",
"advanced_analytics": true,
"custom_branding": true,
"api_access": false
}
remove_subscription_features_from_user(user_id, reason) (Lines 4419-4509)¶
Purpose: Remove/downgrade features on subscription cancellation
Reasons:
subscription_cancelledsubscription_haltedpayment_failedsubscription_expired
Behavior:
- Resets numeric limits to 0
- Sets boolean features to
false - Logs removal reason
convert_features_dict_to_feature_ids(features_dict) (Lines 4276-4303)¶
Purpose: Convert boolean features dictionary to featureIDs array
Example:
# Input
{
"advanced_analytics": true,
"custom_branding": true,
"api_access": false
}
# Output
["F001", "F002"] # Only enabled features
Summary¶
Service Statistics¶
- Total Lines: 5,876 (LARGEST service!)
- Total Functions: 156
- File Size: 259 KB
- Database Collections: 11 (updated after final audit)
- Razorpay Timeout: 15 seconds
- Thread Pool Workers: 10
- Cache Duration: 5 minutes
- Circuit Breaker Threshold: 5 failures
- Circuit Breaker Timeout: 5 minutes
Key Features¶
✅ Dynamic Subscription System - 4-collection aggregation
✅ Razorpay Integration - Orders, subscriptions, invoices, webhooks
✅ Free Plan Support - 90-day trials with auto-billing
✅ Multi-Currency - INR/USD with automatic detection
✅ Circuit Breaker - Fault tolerance for Razorpay API
✅ Retry Logic - Smart retry with exponential backoff
✅ Comprehensive Validation - 15+ validation functions
✅ Thread Pool Execution - Async MongoDB/Razorpay operations
✅ 5-Minute Caching - Plan and subscription data
✅ Request Monitoring - Logging middleware
Critical Issues¶
🔴 CRITICAL - Permissive CORS - Allows requests from any origin
🟡 MEDIUM - Unused Dependency - pymilvus not used
🟡 MEDIUM - Hardcoded Defaults - Default to India for currency detection
Code Quality¶
⚠️ Massive Monolithic File - 5,876 lines in single file (consider splitting)
✅ Comprehensive Validation - 15+ validation functions
✅ Error Handling - Standardized error responses
✅ Type Hints - Clear parameter and return types
✅ Logging - Extensive logging throughout
✅ Circuit Breaker - Fault tolerance pattern
Recommendations¶
Immediate Actions (Security):
- ✅ Restrict CORS to specific domains
- ✅ Remove unused
pymilvusdependency - ✅ Add rate limiting to payment endpoints
- ✅ Implement request authentication middleware
Performance Improvements:
- ✅ Consider Redis for caching (instead of in-memory)
- ✅ Add database connection pooling
- ✅ Implement webhook signature verification caching
Code Quality:
- ✅ Split into modular routers (subscriptions, webhooks, invoices, etc.)
- ✅ Extract validation logic to separate module
- ✅ Extract Razorpay client to separate service class
- ✅ Add comprehensive unit tests
- ✅ Add API documentation with OpenAPI/Swagger
🎉 FINAL SERVICE DOCUMENTED!¶
This completes the comprehensive backend service documentation project!
Total Services Documented: 23/23 (100%) ✅
Total Documentation Words: ~500,000+ words
Total Lines of Code Documented: ~50,000+ lines
All Backend Services:
- ✅ Auth Service (Port 8001)
- ✅ User Service (Port 8002)
- ✅ Create Chatbot Service (Port 8003)
- ✅ Selection Chatbot Service (Port 8004)
- ✅ Data Crawling Service (Port 8005)
- ✅ State Selection 3D Service (Port 8006)
- ✅ State Selection Text Service (Port 8007)
- ✅ State Selection Voice Service (Port 8008)
- ✅ System Prompt Service (Port 8009/8012)
- ✅ Chatbot Maintenance Service (Port 8010/8013)
- ✅ Response 3D Chatbot Service (Port 8011) - MOST CRITICAL
- ✅ Response Text Chatbot Service (Port 8012/8009)
- ✅ Response Voice Chatbot Service (Port 8013/8010)
- ✅ Chat History Service (Port 8014)
- ✅ Client Data Collection Service (Port 8015)
- ✅ LLM Model Service (Port 8016)
- ✅ Homepage Chatbot Service (Port 8017)
- ✅ Remote Physio Service (Port 8018) - CLIENT-SPECIFIC
- ✅ Payment Service (Port 8019) - FINAL SERVICE! 🎯
- ✅ Superadmin Service (Port 8020)
- ✅ Gateway Service (Port 8000) - API GATEWAY
- ✅ Gateway Route Service (Port 8000) - 3,604 lines
- ✅ Shared Module - Infrastructure Library
Documentation Complete: Payment Service (Port 8019) ✅
ENTIRE BACKEND DOCUMENTATION COMPLETE! 🎉🚀