CODITECT Cloud Backend - Complete Commercial Flow Requirements
Date: December 1, 2025, 5:00 AM EST Purpose: Define all components needed for complete user → payment → license → usage flow Target: Production-ready commercial license management system
🎯 Complete User Journey
User Journey: Sign Up → Pay → Get License → Run CODITECT → Continue Paying Monthly
┌─────────────────┐
│ 1. Sign Up │ User creates account
└────────┬────────┘
│
▼
┌─────────────────┐
│ 2. Choose Plan │ Select subscription tier
└────────┬────────┘
│
▼
┌─────────────────┐
│ 3. Pay │ Stripe checkout flow
└────────┬────────┘
│
▼
┌─────────────────┐
│ 4. Get License │ License key emailed
└────────┬────────┘
│
▼
┌─────────────────┐
│ 5. Install │ Download & install CODITECT
└────────┬────────┘
│
▼
┌─────────────────┐
│ 6. Activate │ Enter license key
└────────┬────────┘
│
▼
┌─────────────────┐
│ 7. Validate │ Check license with cloud
└────────┬────────┘
│
▼
┌─────────────────┐
│ 8. Run │ Use CODITECT with heartbeats
└────────┬────────┘
│
▼
┌─────────────────┐
│ 9. Renew │ Monthly subscription charge
└─────────────────┘
│
│ (loop back to step 8)
│
▼
[Continue using]
📊 Current State vs Required Components
Legend
- ✅ Complete - Fully implemented and tested
- ⏳ Partial - Infrastructure ready, implementation incomplete
- ❌ Missing - Not started
- 🔴 Critical - Blocking commercial launch
Step 1: User Registration
Current State: ❌ Missing (0%)
What We Have:
- Nothing related to user registration
What We Need:
1.1 Frontend Registration Form 🔴
Status: ❌ Missing Estimated Time: 2 days Priority: P0 (Critical)
Requirements:
// Registration form fields
interface RegistrationForm {
email: string; // Primary identifier
password: string; // Min 8 chars, complexity requirements
fullName: string; // User's full name
companyName?: string; // Optional for business users
agreeToTerms: boolean; // Legal requirement
}
Components Needed:
- Sign-up page (React/Next.js)
- Form validation
- Password strength indicator
- Terms of Service acceptance
- Privacy policy link
- Email verification flow
Dependencies:
- Frontend web application (not yet built)
- Hosting (can use GKE with Ingress)
1.2 Backend User Registration API 🔴
Status: ❌ Missing Estimated Time: 2 days Priority: P0 (Critical)
Endpoints Needed:
# POST /api/v1/auth/register
# Request:
{
"email": "user@example.com",
"password": "SecurePass123!",
"full_name": "John Doe",
"company_name": "Acme Corp"
}
# Response:
{
"user_id": "usr_abc123",
"email": "user@example.com",
"status": "pending_verification",
"verification_email_sent": true
}
Implementation Requirements:
- Hash passwords (bcrypt, Argon2)
- Generate email verification token
- Store user in PostgreSQL
- Send verification email
- Handle duplicate email prevention
- Rate limiting (prevent spam registrations)
Database Schema:
CREATE TABLE users (
user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(255) NOT NULL,
company_name VARCHAR(255),
email_verified BOOLEAN DEFAULT FALSE,
verification_token VARCHAR(255),
verification_token_expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP,
status VARCHAR(50) DEFAULT 'active' -- active, suspended, deleted
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_verification_token ON users(verification_token);
1.3 Email Verification 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
Flow:
- User clicks verification link in email
- Backend validates token
- Mark email as verified
- Redirect to login or onboarding
Endpoints:
# GET /api/v1/auth/verify-email?token={verification_token}
# Response: Redirect to /login with success message
# POST /api/v1/auth/resend-verification
# Request: {"email": "user@example.com"}
# Response: {"message": "Verification email sent"}
Email Service Integration:
-
Option 1: SendGrid (recommended)
- Cost: Free tier (100 emails/day), $15/month (40K emails)
- Easy API integration
- Template support
- Deliverability tracking
-
Option 2: AWS SES
- Cost: $0.10 per 1,000 emails
- More complex setup
- Requires domain verification
Email Template:
Subject: Verify your CODITECT account
Hi {full_name},
Welcome to CODITECT! Please verify your email address by clicking the link below:
{verification_url}
This link expires in 24 hours.
If you didn't create this account, please ignore this email.
Thanks,
The CODITECT Team
1.4 Firebase Authentication Integration ⏳
Status: ⏳ Partial (Firebase project created, not configured) Estimated Time: 1 day Priority: P0 (Critical)
Current State:
- Firebase service account exists
- Key stored in Secret Manager
- Django middleware stub exists
Remaining Work:
- Configure Firebase Authentication in console
- Enable Google OAuth provider
- Enable GitHub OAuth provider
- Update Django middleware to validate tokens
- Test OAuth flow end-to-end
Why Firebase?
- Social login (Google, GitHub) reduces friction
- Secure token validation
- No password management (for OAuth users)
- Industry-standard authentication
Step 2: Choose Subscription Plan
Current State: ❌ Missing (0%)
What We Have:
- Nothing related to subscription plans
What We Need:
2.1 Subscription Plans Definition 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
Recommended Plans:
Free Trial (14 days):
- 1 concurrent seat
- All features
- No credit card required
- Auto-expires after 14 days
Starter ($29/month):
- 3 concurrent seats
- All core features
- Email support
- Monthly billing only
Professional ($99/month):
- 10 concurrent seats
- Priority support
- Advanced features (custom agents)
- Annual billing option (save 20%)
Team ($299/month):
- 50 concurrent seats
- Dedicated support
- SSO integration
- Usage analytics
- Annual billing option (save 20%)
Enterprise (Custom pricing):
- Unlimited seats
- On-premise deployment option
- Custom contract
- SLA guarantees
Database Schema:
CREATE TABLE subscription_plans (
plan_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
plan_name VARCHAR(100) UNIQUE NOT NULL, -- 'starter', 'professional', 'team', 'enterprise'
display_name VARCHAR(255) NOT NULL, -- 'Starter Plan'
description TEXT,
max_concurrent_seats INTEGER NOT NULL,
monthly_price_cents INTEGER, -- NULL for free trial
annual_price_cents INTEGER, -- NULL if annual not available
stripe_monthly_price_id VARCHAR(255), -- Stripe Price ID
stripe_annual_price_id VARCHAR(255),
features JSONB, -- {"priority_support": true, "custom_agents": false}
is_active BOOLEAN DEFAULT TRUE,
trial_duration_days INTEGER, -- NULL if no trial
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Seed data
INSERT INTO subscription_plans VALUES
('starter', 'Starter Plan', 3, 2900, NULL, 'price_starter_monthly', NULL),
('professional', 'Professional Plan', 10, 9900, 95040, 'price_pro_monthly', 'price_pro_annual'),
('team', 'Team Plan', 50, 29900, 287040, 'price_team_monthly', 'price_team_annual');
2.2 Plan Selection UI 🔴
Status: ❌ Missing Estimated Time: 2 days Priority: P0 (Critical)
Components:
- Pricing page showing all plans
- Feature comparison table
- "Start Free Trial" vs "Subscribe" CTAs
- Plan upgrade/downgrade flow
- Billing cycle toggle (monthly/annual)
Example UI Flow:
┌─────────────────────────────────────────┐
│ Choose Your Plan │
├─────────────────────────────────────────┤
│ │
│ [Free Trial] [Starter] [Pro] [Team]│
│ $0 $29/mo $99/mo $299/mo│
│ │
│ 1 seat 3 seats 10 seats 50 │
│ 14 days All core Priority SSO │
│ All features features support │
│ │
│ [Try Free] [Subscribe] [Subscribe] │
└─────────────────────────────────────────┘
2.3 Plan Selection API 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
Endpoints:
# GET /api/v1/plans
# Response: List of available subscription plans
{
"plans": [
{
"plan_id": "plan_abc123",
"plan_name": "starter",
"display_name": "Starter Plan",
"monthly_price_cents": 2900,
"max_concurrent_seats": 3,
"features": {...}
},
...
]
}
# POST /api/v1/subscriptions/select-plan
# Request: {"plan_id": "plan_abc123", "billing_cycle": "monthly"}
# Response: {"checkout_url": "https://checkout.stripe.com/..."}
Step 3: Payment Processing
Current State: ❌ Missing (0%)
What We Have:
- Nothing related to payment processing
What We Need:
3.1 Stripe Integration 🔴
Status: ❌ Missing Estimated Time: 3 days Priority: P0 (Critical - Blocks revenue!)
Stripe Setup Required:
- Create Stripe account
- Add bank account for payouts
- Configure company information
- Set up tax calculation (Stripe Tax)
- Create products and prices in Stripe Dashboard
Stripe API Integration:
import stripe
# Create checkout session
def create_checkout_session(user_id: str, plan_id: str, billing_cycle: str):
stripe.api_key = settings.STRIPE_SECRET_KEY
# Get plan details
plan = SubscriptionPlan.objects.get(plan_id=plan_id)
# Determine price ID based on billing cycle
price_id = (plan.stripe_annual_price_id if billing_cycle == 'annual'
else plan.stripe_monthly_price_id)
# Create Stripe checkout session
session = stripe.checkout.Session.create(
customer_email=user.email,
mode='subscription',
payment_method_types=['card'],
line_items=[{
'price': price_id,
'quantity': 1
}],
success_url=f'{settings.FRONTEND_URL}/checkout/success?session_id={{CHECKOUT_SESSION_ID}}',
cancel_url=f'{settings.FRONTEND_URL}/pricing',
metadata={
'user_id': user_id,
'plan_id': plan_id
}
)
return session.url
Endpoints:
# POST /api/v1/payments/create-checkout-session
# Request:
{
"plan_id": "plan_abc123",
"billing_cycle": "monthly" // or "annual"
}
# Response:
{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_...",
"session_id": "cs_test_abc123"
}
3.2 Stripe Webhooks 🔴
Status: ❌ Missing Estimated Time: 2 days Priority: P0 (Critical)
Why Webhooks?
- Asynchronous payment processing
- Handle subscription lifecycle events
- Secure server-to-server communication
Webhook Events to Handle:
1. checkout.session.completed (Payment successful)
def handle_checkout_completed(event):
session = event['data']['object']
user_id = session['metadata']['user_id']
stripe_subscription_id = session['subscription']
# Create subscription record
Subscription.objects.create(
user_id=user_id,
stripe_subscription_id=stripe_subscription_id,
status='active',
current_period_start=...,
current_period_end=...
)
# Generate license key
license_key = generate_license_key()
# Create license
License.objects.create(
user_id=user_id,
license_key=license_key,
max_concurrent_seats=plan.max_concurrent_seats,
status='active'
)
# Send license email
send_license_email(user.email, license_key)
2. invoice.payment_succeeded (Monthly renewal successful)
def handle_invoice_paid(event):
invoice = event['data']['object']
subscription_id = invoice['subscription']
# Update subscription period
subscription = Subscription.objects.get(stripe_subscription_id=subscription_id)
subscription.current_period_end = datetime.fromtimestamp(invoice['period_end'])
subscription.save()
# Ensure license remains active
license = License.objects.get(user_id=subscription.user_id)
license.status = 'active'
license.save()
3. invoice.payment_failed (Payment failed)
def handle_invoice_failed(event):
invoice = event['data']['object']
subscription_id = invoice['subscription']
# Mark subscription as past_due
subscription = Subscription.objects.get(stripe_subscription_id=subscription_id)
subscription.status = 'past_due'
subscription.save()
# Suspend license (with grace period)
license = License.objects.get(user_id=subscription.user_id)
license.status = 'suspended'
license.suspended_at = datetime.now()
license.grace_period_ends_at = datetime.now() + timedelta(days=7)
license.save()
# Send payment failed email
send_payment_failed_email(subscription.user.email)
4. customer.subscription.deleted (Subscription cancelled)
def handle_subscription_deleted(event):
subscription_data = event['data']['object']
subscription_id = subscription_data['id']
# Mark subscription as cancelled
subscription = Subscription.objects.get(stripe_subscription_id=subscription_id)
subscription.status = 'cancelled'
subscription.cancelled_at = datetime.now()
subscription.save()
# Deactivate license
license = License.objects.get(user_id=subscription.user_id)
license.status = 'inactive'
license.deactivated_at = datetime.now()
license.save()
Webhook Endpoint:
# POST /api/v1/webhooks/stripe
# Headers: Stripe-Signature (for verification)
@csrf_exempt
def stripe_webhook(request):
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
try:
event = stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
)
except ValueError:
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError:
return HttpResponse(status=400)
# Handle event
if event['type'] == 'checkout.session.completed':
handle_checkout_completed(event)
elif event['type'] == 'invoice.payment_succeeded':
handle_invoice_paid(event)
elif event['type'] == 'invoice.payment_failed':
handle_invoice_failed(event)
elif event['type'] == 'customer.subscription.deleted':
handle_subscription_deleted(event)
return HttpResponse(status=200)
Database Schema:
CREATE TABLE subscriptions (
subscription_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(user_id) NOT NULL,
plan_id UUID REFERENCES subscription_plans(plan_id) NOT NULL,
stripe_subscription_id VARCHAR(255) UNIQUE NOT NULL,
stripe_customer_id VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL, -- active, past_due, cancelled, trialing
billing_cycle VARCHAR(20) NOT NULL, -- monthly, annual
current_period_start TIMESTAMP NOT NULL,
current_period_end TIMESTAMP NOT NULL,
trial_end TIMESTAMP,
cancelled_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_subscriptions_user_id ON subscriptions(user_id);
CREATE INDEX idx_subscriptions_stripe_subscription_id ON subscriptions(stripe_subscription_id);
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
3.3 Payment Success/Failure Handling 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
Success Flow:
- Stripe redirects to
/checkout/success?session_id=cs_... - Frontend fetches session details from backend
- Display success message with license key
- Email license key to user
- Redirect to dashboard or download page
Failure Flow:
- Stripe redirects to
/pricing(cancel URL) - Display "Payment cancelled" message
- Offer to retry payment
Step 4: License Generation & Delivery
Current State: ❌ Missing (0%)
What We Have:
- Nothing related to license generation
What We Need:
4.1 License Key Generation 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
License Key Format:
CODITECT-XXXX-XXXX-XXXX-XXXX-XXXX
Example: CODITECT-A7B2-9C4D-E6F8-1G3H-5J7K
Generation Algorithm:
import secrets
import hashlib
def generate_license_key() -> str:
"""
Generate cryptographically secure license key.
Format: CODITECT-XXXX-XXXX-XXXX-XXXX-XXXX
"""
# Generate 20 random bytes
random_bytes = secrets.token_bytes(20)
# Create base key (alphanumeric, uppercase)
base_key = hashlib.sha256(random_bytes).hexdigest()[:20].upper()
# Format into groups of 4
groups = [base_key[i:i+4] for i in range(0, 20, 4)]
# Return formatted key
return f"CODITECT-{'-'.join(groups)}"
# Validation
def validate_license_key_format(key: str) -> bool:
"""Validate license key format"""
pattern = r'^CODITECT-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
return re.match(pattern, key) is not None
Database Schema:
CREATE TABLE licenses (
license_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(user_id) NOT NULL,
license_key VARCHAR(50) UNIQUE NOT NULL,
subscription_id UUID REFERENCES subscriptions(subscription_id),
max_concurrent_seats INTEGER NOT NULL,
status VARCHAR(50) DEFAULT 'active', -- active, suspended, inactive
issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
activated_at TIMESTAMP,
suspended_at TIMESTAMP,
deactivated_at TIMESTAMP,
grace_period_ends_at TIMESTAMP,
last_validated_at TIMESTAMP,
validation_count INTEGER DEFAULT 0,
metadata JSONB -- Additional license metadata
);
CREATE INDEX idx_licenses_user_id ON licenses(user_id);
CREATE INDEX idx_licenses_license_key ON licenses(license_key);
CREATE INDEX idx_licenses_status ON licenses(status);
-- Active sessions for seat tracking
CREATE TABLE license_sessions (
session_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
license_id UUID REFERENCES licenses(license_id) NOT NULL,
hardware_id VARCHAR(255) NOT NULL, -- Machine fingerprint
user_ip VARCHAR(45), -- IPv4 or IPv6
user_agent TEXT,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_heartbeat_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL, -- TTL for automatic cleanup
UNIQUE(license_id, hardware_id)
);
CREATE INDEX idx_license_sessions_license_id ON license_sessions(license_id);
CREATE INDEX idx_license_sessions_expires_at ON license_sessions(expires_at);
4.2 License Email Delivery 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
Email Template:
Subject: Your CODITECT License Key
Hi {full_name},
Thank you for subscribing to CODITECT {plan_name}!
Your License Key:
┌─────────────────────────────────────────┐
│ CODITECT-A7B2-9C4D-E6F8-1G3H-5J7K │
└─────────────────────────────────────────┘
Plan Details:
- Plan: {plan_display_name}
- Concurrent Seats: {max_concurrent_seats}
- Billing: ${monthly_price}/month
- Next billing date: {next_billing_date}
Getting Started:
1. Download CODITECT: https://coditect.ai/download
2. Install on your machine
3. Run: coditect auth login --license {license_key}
4. Start using CODITECT!
Need help? Visit our documentation: https://docs.coditect.ai
Thanks,
The CODITECT Team
---
Manage subscription: https://app.coditect.ai/billing
Implementation:
def send_license_email(user: User, license: License, subscription: Subscription):
"""Send license key email via SendGrid"""
# Get plan details
plan = subscription.plan
# Create email
message = Mail(
from_email='licenses@coditect.ai',
to_emails=user.email,
subject='Your CODITECT License Key',
html_content=render_template('license_email.html',
full_name=user.full_name,
license_key=license.license_key,
plan_display_name=plan.display_name,
max_concurrent_seats=plan.max_concurrent_seats,
monthly_price=plan.monthly_price_cents / 100,
next_billing_date=subscription.current_period_end
)
)
# Send via SendGrid
sg = SendGridAPIClient(settings.SENDGRID_API_KEY)
response = sg.send(message)
# Log email sent
logger.info(f"License email sent to {user.email}, status: {response.status_code}")
Step 5: Download & Install CODITECT
Current State: ⏳ Partial (40%)
What We Have:
- CODITECT core framework exists
- Can be installed via pip
- Local-first architecture ready
What We Need:
5.1 Distribution Method 🟡
Status: ⏳ Partial Estimated Time: 2 days Priority: P1 (Important)
Options:
Option 1: PyPI Package (Recommended for MVP)
pip install coditect-core
- Pros: Easy distribution, automatic updates, familiar to developers
- Cons: Requires Python installed, not ideal for non-technical users
Option 2: Standalone Binary (Long-term)
# Windows
coditect-setup.exe
# macOS
coditect.dmg
# Linux
coditect.AppImage
- Pros: No dependencies, works for non-technical users
- Cons: Larger file size, OS-specific builds, code signing required
- Tool: PyInstaller or Nuitka for binary creation
Recommendation: Start with PyPI, add standalone binaries in Phase 6
5.2 Installation Wizard 🟡
Status: ❌ Missing Estimated Time: 2 days Priority: P2 (Nice to have)
Flow:
┌─────────────────────────────────────┐
│ Welcome to CODITECT Setup │
│ │
│ 1. Install CODITECT │
│ 2. Enter License Key │
│ 3. Validate & Activate │
│ 4. Configure Settings (optional) │
│ 5. Launch CODITECT │
└─────────────────────────────────────┘
For MVP: Simple CLI is acceptable
# Install
pip install coditect-core
# Enter license
coditect auth login --license CODITECT-XXXX-XXXX-XXXX-XXXX-XXXX
# Done!
Step 6: License Activation
Current State: ⏳ Partial (20%)
What We Have:
- Infrastructure for license validation (GKE, Cloud SQL, Redis)
- Database schema ready (needs migration)
What We Need:
6.1 License Activation Command 🔴
Status: ❌ Missing Estimated Time: 2 days Priority: P0 (Critical)
CLI Command:
coditect auth login --license CODITECT-A7B2-9C4D-E6F8-1G3H-5J7K
Implementation in coditect-core:
# coditect/cli/auth.py
import click
import requests
from coditect.license import LicenseClient
@click.command()
@click.option('--license', required=True, help='Your license key')
def login(license: str):
"""Activate CODITECT with your license key"""
click.echo("Activating CODITECT...")
# Initialize license client
client = LicenseClient(
api_url=os.getenv('CODITECT_LICENSE_API', 'https://api.coditect.ai'),
license_key=license
)
try:
# Validate license format
if not client.validate_key_format(license):
click.echo("❌ Invalid license key format", err=True)
return
# Acquire license (calls cloud API)
result = client.acquire_license()
if result.success:
# Save license locally
client.save_license_config(result.license_data)
click.echo("✅ License activated successfully!")
click.echo(f" Plan: {result.plan_name}")
click.echo(f" Seats: {result.active_seats}/{result.max_seats}")
click.echo(f" Expires: {result.expires_at}")
else:
click.echo(f"❌ Activation failed: {result.error_message}", err=True)
except requests.exceptions.ConnectionError:
click.echo("❌ Cannot reach license server. Check your internet connection.", err=True)
except Exception as e:
click.echo(f"❌ Unexpected error: {str(e)}", err=True)
6.2 Hardware Fingerprinting 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
Purpose: Uniquely identify machines to prevent license sharing
Implementation:
import platform
import hashlib
import uuid
def generate_hardware_id() -> str:
"""
Generate stable hardware fingerprint.
Uses:
- Machine UUID (best, but not always available)
- MAC address (fallback)
- Hostname + OS + Architecture (last resort)
"""
try:
# Try to get machine UUID (most stable)
machine_uuid = uuid.UUID(int=uuid.getnode()).hex
return hashlib.sha256(machine_uuid.encode()).hexdigest()[:32]
except:
# Fallback: combine multiple system attributes
components = [
platform.node(), # Hostname
platform.system(), # OS
platform.machine(), # Architecture
str(uuid.getnode()) # MAC address
]
combined = '-'.join(components)
return hashlib.sha256(combined.encode()).hexdigest()[:32]
# Example output: "a7b2c4d6e8f0a1b3c5d7e9f1a3b5c7d9"
Considerations:
- Must be stable across reboots
- Should survive minor OS updates
- Must be different per machine
- Should work across Windows/macOS/Linux
Step 7: License Validation with Cloud
Current State: ⏳ Partial (50%)
What We Have:
- Cloud infrastructure ready (GKE, Cloud SQL, Redis)
- Network connectivity configured
- External IP accessible (136.114.0.156)
What We Need:
7.1 License Acquisition Endpoint 🔴
Status: ⏳ Partial (stub exists, not implemented) Estimated Time: 2 days Priority: P0 (Critical - Blocking usage!)
Endpoint:
# POST /api/v1/licenses/acquire
# Headers: Authorization: Bearer {firebase_token}
# Request:
{
"license_key": "CODITECT-A7B2-9C4D-E6F8-1G3H-5J7K",
"hardware_id": "a7b2c4d6e8f0a1b3c5d7e9f1a3b5c7d9",
"machine_info": {
"hostname": "johns-macbook",
"os": "macOS 14.1",
"coditect_version": "1.0.0"
}
}
# Response (success):
{
"session_id": "session_abc123",
"signed_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2025-12-01T05:06:00Z",
"max_concurrent_seats": 10,
"current_seats_used": 3,
"plan_name": "Professional",
"heartbeat_interval_seconds": 300
}
# Response (failure - no seats available):
{
"error": "no_seats_available",
"message": "All 10 concurrent seats are in use",
"active_sessions": [
{"hardware_id": "...", "hostname": "...", "started_at": "..."},
...
]
}
# Response (failure - subscription inactive):
{
"error": "subscription_inactive",
"message": "Your subscription is not active. Please update your payment method.",
"grace_period_ends": "2025-12-08T00:00:00Z"
}
Implementation (Django):
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from django.db import transaction
import redis
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def acquire_license(request):
"""Acquire a license seat for a specific machine"""
license_key = request.data.get('license_key')
hardware_id = request.data.get('hardware_id')
machine_info = request.data.get('machine_info', {})
# Validate license key format
if not validate_license_key_format(license_key):
return Response({'error': 'invalid_license_key'}, status=400)
# Get license from database
try:
license = License.objects.select_related('subscription', 'subscription__plan').get(
license_key=license_key,
user_id=request.user.id
)
except License.DoesNotExist:
return Response({'error': 'license_not_found'}, status=404)
# Check license status
if license.status != 'active':
if license.status == 'suspended':
return Response({
'error': 'subscription_inactive',
'message': 'Your subscription is not active',
'grace_period_ends': license.grace_period_ends_at
}, status=402) # 402 Payment Required
else:
return Response({'error': 'license_inactive'}, status=403)
# Check subscription status
subscription = license.subscription
if subscription.status not in ['active', 'trialing']:
return Response({
'error': 'subscription_inactive',
'message': f'Subscription status: {subscription.status}'
}, status=402)
# Atomic seat acquisition using Redis Lua script
redis_client = redis.Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT)
# Lua script for atomic seat check and increment
lua_script = """
local license_key = KEYS[1]
local max_seats = tonumber(ARGV[1])
local ttl_seconds = tonumber(ARGV[2])
local current_seats = redis.call('GET', license_key)
current_seats = current_seats and tonumber(current_seats) or 0
if current_seats < max_seats then
redis.call('INCR', license_key)
redis.call('EXPIRE', license_key, ttl_seconds)
return {1, current_seats + 1}
else
return {0, current_seats}
end
"""
# Execute Lua script
result = redis_client.eval(
lua_script,
1, # Number of keys
f'seats:{license.license_id}', # Key
license.max_concurrent_seats, # Max seats
360 # TTL: 6 minutes (heartbeat every 5 min)
)
success, seat_count = result
if not success:
# No seats available
active_sessions = LicenseSession.objects.filter(
license=license,
expires_at__gt=timezone.now()
).values('hardware_id', 'started_at', 'last_heartbeat_at')
return Response({
'error': 'no_seats_available',
'message': f'All {license.max_concurrent_seats} seats in use',
'current_seats_used': seat_count,
'active_sessions': list(active_sessions)
}, status=429) # 429 Too Many Requests
# Create session record in database
with transaction.atomic():
session = LicenseSession.objects.create(
license=license,
hardware_id=hardware_id,
user_ip=request.META.get('REMOTE_ADDR'),
user_agent=request.META.get('HTTP_USER_AGENT'),
expires_at=timezone.now() + timedelta(minutes=6),
metadata={
'machine_info': machine_info
}
)
# Update license last_validated_at
license.last_validated_at = timezone.now()
license.validation_count += 1
license.save()
# Generate signed license token (with Cloud KMS in Phase 2)
# For now, use Django signing
signed_token = signing.dumps({
'session_id': str(session.session_id),
'license_key': license_key,
'hardware_id': hardware_id,
'expires_at': session.expires_at.isoformat()
})
return Response({
'session_id': str(session.session_id),
'signed_token': signed_token,
'expires_at': session.expires_at,
'max_concurrent_seats': license.max_concurrent_seats,
'current_seats_used': seat_count,
'plan_name': subscription.plan.display_name,
'heartbeat_interval_seconds': 300
}, status=200)
7.2 License Client SDK 🔴
Status: ❌ Missing Estimated Time: 2 days Priority: P0 (Critical)
Implementation in coditect-core:
# coditect/license/client.py
import requests
import json
from pathlib import Path
from typing import Optional
from datetime import datetime, timedelta
class LicenseClient:
"""Client for CODITECT license validation"""
def __init__(self, api_url: str, license_key: str):
self.api_url = api_url
self.license_key = license_key
self.session_id: Optional[str] = None
self.signed_token: Optional[str] = None
self.expires_at: Optional[datetime] = None
def acquire_license(self) -> LicenseResult:
"""Acquire a license seat from the cloud"""
# Get hardware ID
hardware_id = generate_hardware_id()
# Call API
response = requests.post(
f'{self.api_url}/api/v1/licenses/acquire',
json={
'license_key': self.license_key,
'hardware_id': hardware_id,
'machine_info': self._get_machine_info()
},
headers={'Authorization': f'Bearer {self._get_auth_token()}'}
)
if response.status_code == 200:
data = response.json()
# Store session info
self.session_id = data['session_id']
self.signed_token = data['signed_token']
self.expires_at = datetime.fromisoformat(data['expires_at'])
return LicenseResult(
success=True,
license_data=data
)
else:
error_data = response.json()
return LicenseResult(
success=False,
error_code=error_data.get('error'),
error_message=error_data.get('message')
)
def save_license_config(self, license_data: dict):
"""Save license configuration locally"""
config_dir = Path.home() / '.coditect'
config_dir.mkdir(exist_ok=True)
config_file = config_dir / 'license.json'
config_file.write_text(json.dumps({
'license_key': self.license_key,
'session_id': license_data['session_id'],
'signed_token': license_data['signed_token'],
'expires_at': license_data['expires_at'],
'plan_name': license_data['plan_name']
}, indent=2))
Step 8: Running CODITECT with Heartbeats
Current State: ⏳ Partial (30%)
What We Have:
- CODITECT core framework runs locally
- Can execute AI-powered development tasks
What We Need:
8.1 Heartbeat Mechanism 🔴
Status: ❌ Missing Estimated Time: 2 days Priority: P0 (Critical)
Purpose:
- Keep license seat active while CODITECT is running
- Automatically release seat if CODITECT crashes or is force-killed
- Detect zombie sessions (no heartbeat for 6+ minutes)
Heartbeat Endpoint:
# POST /api/v1/licenses/heartbeat
# Headers: Authorization: Bearer {firebase_token}
# Request:
{
"session_id": "session_abc123",
"license_key": "CODITECT-A7B2-9C4D-E6F8-1G3H-5J7K"
}
# Response:
{
"status": "ok",
"expires_at": "2025-12-01T05:11:00Z",
"subscription_status": "active"
}
Implementation (Django):
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def heartbeat(request):
"""Extend license session TTL"""
session_id = request.data.get('session_id')
license_key = request.data.get('license_key')
# Get session
try:
session = LicenseSession.objects.select_related('license').get(
session_id=session_id,
license__license_key=license_key,
license__user_id=request.user.id
)
except LicenseSession.DoesNotExist:
return Response({'error': 'session_not_found'}, status=404)
# Check if session expired
if session.expires_at < timezone.now():
return Response({'error': 'session_expired'}, status=410)
# Extend session TTL
session.last_heartbeat_at = timezone.now()
session.expires_at = timezone.now() + timedelta(minutes=6)
session.save()
# Extend Redis TTL
redis_client.expire(f'seats:{session.license.license_id}', 360)
# Check subscription status
subscription = session.license.subscription
return Response({
'status': 'ok',
'expires_at': session.expires_at,
'subscription_status': subscription.status
}, status=200)
Client Implementation:
# coditect/license/heartbeat.py
import threading
import time
class HeartbeatThread(threading.Thread):
"""Background thread to send heartbeats every 5 minutes"""
def __init__(self, license_client: LicenseClient):
super().__init__(daemon=True)
self.license_client = license_client
self.running = True
def run(self):
"""Send heartbeat every 5 minutes"""
while self.running:
try:
# Sleep first (initial seat acquired via acquire_license)
time.sleep(300) # 5 minutes
# Send heartbeat
result = self.license_client.send_heartbeat()
if not result.success:
logger.error(f"Heartbeat failed: {result.error_message}")
# TODO: Show warning to user
except Exception as e:
logger.error(f"Heartbeat error: {str(e)}")
def stop(self):
"""Stop heartbeat thread"""
self.running = False
8.2 Graceful Shutdown / Seat Release 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
Purpose: Release seat when user exits CODITECT normally
Release Endpoint:
# POST /api/v1/licenses/release
# Request:
{
"session_id": "session_abc123",
"license_key": "CODITECT-A7B2-9C4D-E6F8-1G3H-5J7K"
}
# Response:
{
"status": "released",
"message": "License seat released successfully"
}
Implementation (Django):
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def release_license(request):
"""Release a license seat"""
session_id = request.data.get('session_id')
license_key = request.data.get('license_key')
# Get session
try:
session = LicenseSession.objects.select_related('license').get(
session_id=session_id,
license__license_key=license_key,
license__user_id=request.user.id
)
except LicenseSession.DoesNotExist:
return Response({'error': 'session_not_found'}, status=404)
# Decrement seat count in Redis atomically
redis_client = redis.Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT)
lua_script = """
local key = KEYS[1]
local current = redis.call('GET', key)
if current and tonumber(current) > 0 then
return redis.call('DECR', key)
end
return 0
"""
redis_client.eval(lua_script, 1, f'seats:{session.license.license_id}')
# Delete session from database
session.delete()
return Response({
'status': 'released',
'message': 'License seat released successfully'
}, status=200)
Client Implementation:
# coditect/license/client.py (continued)
import atexit
import signal
class LicenseClient:
def __init__(self, ...):
...
# Register cleanup handlers
atexit.register(self.cleanup)
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
def cleanup(self):
"""Release license seat on exit"""
if self.session_id:
try:
self.release_license()
print("License seat released.")
except Exception as e:
logger.error(f"Error releasing license: {str(e)}")
def _signal_handler(self, signum, frame):
"""Handle termination signals"""
self.cleanup()
exit(0)
def release_license(self):
"""Explicitly release license seat"""
if not self.session_id:
return
response = requests.post(
f'{self.api_url}/api/v1/licenses/release',
json={
'session_id': self.session_id,
'license_key': self.license_key
},
headers={'Authorization': f'Bearer {self._get_auth_token()}'}
)
if response.status_code == 200:
self.session_id = None
self.signed_token = None
Step 9: Monthly Subscription Renewal
Current State: ❌ Missing (0%)
What We Have:
- Nothing related to subscription renewal
What We Need:
9.1 Automatic Renewal via Stripe 🔴
Status: ❌ Missing Estimated Time: Already handled by Stripe + Webhooks Priority: P0 (Critical)
How It Works:
- Stripe automatically charges customer on renewal date
- Stripe sends
invoice.payment_succeededwebhook - Backend updates subscription period
- License remains active (no action needed)
Already Covered: See Step 3.2 - Stripe Webhooks
9.2 Payment Failure Handling 🔴
Status: ❌ Missing Estimated Time: 1 day Priority: P0 (Critical)
Grace Period Flow:
Day 0: Payment fails
↓
Stripe sends invoice.payment_failed webhook
↓
Backend: Mark subscription as past_due
Backend: Set license status to 'suspended'
Backend: Set grace_period_ends_at = now + 7 days
↓
Email user: "Payment failed, please update payment method"
↓
Day 1-6: Grace period
↓
License still works (with warning)
CODITECT shows: "⚠️ Payment failed. Update payment by Dec 8 to continue."
↓
Day 7: Grace period ends
↓
Backend: Set license status to 'inactive'
↓
CODITECT blocks usage: "License inactive. Update payment to continue."
License Validation with Grace Period:
def acquire_license(request):
...
# Check license status with grace period
if license.status == 'suspended':
grace_period_remaining = (license.grace_period_ends_at - timezone.now()).days
if grace_period_remaining > 0:
# Allow usage with warning
return Response({
'session_id': ...,
'signed_token': ...,
'warning': 'payment_failed',
'warning_message': f'Payment failed. Update payment method within {grace_period_remaining} days.',
'grace_period_ends': license.grace_period_ends_at,
'update_payment_url': 'https://app.coditect.ai/billing'
}, status=200)
else:
# Grace period expired
license.status = 'inactive'
license.save()
return Response({
'error': 'license_inactive',
'message': 'Your subscription is inactive. Please update your payment method.',
'update_payment_url': 'https://app.coditect.ai/billing'
}, status=402)
9.3 Customer Billing Portal 🟡
Status: ❌ Missing Estimated Time: 1 day (Stripe provides this!) Priority: P1 (Important)
Stripe Customer Portal:
- Stripe provides pre-built billing portal
- Customers can:
- Update payment method
- View invoices
- Cancel subscription
- Change plan
Implementation:
# POST /api/v1/billing/portal
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def create_billing_portal_session(request):
"""Create Stripe billing portal session"""
subscription = Subscription.objects.get(user_id=request.user.id)
session = stripe.billing_portal.Session.create(
customer=subscription.stripe_customer_id,
return_url=f'{settings.FRONTEND_URL}/dashboard'
)
return Response({
'portal_url': session.url
})
Usage:
User clicks "Manage Billing" in dashboard
↓
Frontend calls /api/v1/billing/portal
↓
Redirect to Stripe portal
↓
User updates payment method
↓
Stripe automatically retries failed payment
↓
Webhook: invoice.payment_succeeded
↓
License reactivated automatically
📊 Complete Feature Matrix
What We Have vs What We Need
| Step | Feature | Status | Est. Time | Priority |
|---|---|---|---|---|
| 1. User Registration | ||||
| Frontend form | ❌ Missing | 2 days | P0 | |
| Backend API | ❌ Missing | 2 days | P0 | |
| Email verification | ❌ Missing | 1 day | P0 | |
| Firebase OAuth | ⏳ Partial | 1 day | P0 | |
| 2. Plan Selection | ||||
| Plan definitions | ❌ Missing | 1 day | P0 | |
| Selection UI | ❌ Missing | 2 days | P0 | |
| Selection API | ❌ Missing | 1 day | P0 | |
| 3. Payment | ||||
| Stripe integration | ❌ Missing | 3 days | P0 | |
| Webhook handling | ❌ Missing | 2 days | P0 | |
| Success/failure flows | ❌ Missing | 1 day | P0 | |
| 4. License Generation | ||||
| Key generation | ❌ Missing | 1 day | P0 | |
| Email delivery | ❌ Missing | 1 day | P0 | |
| 5. Download/Install | ||||
| PyPI package | ⏳ Partial | 2 days | P1 | |
| Installation wizard | ❌ Missing | 2 days | P2 | |
| 6. Activation | ||||
| CLI command | ❌ Missing | 2 days | P0 | |
| Hardware fingerprinting | ❌ Missing | 1 day | P0 | |
| 7. Validation | ||||
| Acquire endpoint | ⏳ Partial | 2 days | P0 | |
| Client SDK | ❌ Missing | 2 days | P0 | |
| 8. Running | ||||
| Heartbeat endpoint | ❌ Missing | 2 days | P0 | |
| Heartbeat thread | ❌ Missing | 1 day | P0 | |
| Graceful release | ❌ Missing | 1 day | P0 | |
| 9. Renewal | ||||
| Auto-renewal | ⏳ Via Stripe | 0 days | P0 | |
| Payment failure | ❌ Missing | 1 day | P0 | |
| Billing portal | ❌ Missing | 1 day | P1 |
Total Estimated Time: 35-40 days of focused development
🎯 Critical Path to Commercial Launch
Phase 1: Core Commerce (10 days) 🔴
Week 1:
- Day 1-2: User registration (frontend + backend)
- Day 3: Email verification
- Day 4: Firebase OAuth integration
- Day 5: Subscription plan definitions
Week 2:
- Day 6-7: Stripe integration
- Day 8-9: Webhook handling (all events)
- Day 10: License generation + email delivery
Deliverable: Users can sign up and purchase subscriptions
Phase 2: License Validation (8 days) 🔴
Week 3:
- Day 11-12: License acquisition endpoint
- Day 13-14: License client SDK
- Day 15: Hardware fingerprinting
- Day 16-17: Heartbeat mechanism
- Day 18: Graceful release
Deliverable: CODITECT validates licenses with cloud
Phase 3: Subscription Management (4 days) 🟡
Week 4:
- Day 19: Payment failure handling
- Day 20: Grace period logic
- Day 21: Billing portal integration
- Day 22: Subscription cancellation flow
Deliverable: Complete subscription lifecycle
Phase 4: Production Hardening (8 days) 🟡
Week 5:
- Day 23-24: Cloud KMS license signing
- Day 25-26: SSL/TLS configuration
- Day 27-28: Monitoring & alerting
- Day 29-30: Production deployment
Deliverable: Production-ready system
Total: 30 days (6 weeks)
💰 Additional Costs
New Services Required
| Service | Purpose | Monthly Cost |
|---|---|---|
| SendGrid | Email delivery (10K emails/mo) | $15 |
| Stripe | Payment processing | 2.9% + $0.30/transaction |
| Domain + SSL | Custom domain with HTTPS | $20 |
| Frontend Hosting | GKE Ingress + CDN | $30 |
| Total New Costs | ~$65 + transaction fees |
Combined with Infrastructure: $60 (staging) + $65 = $125/month for complete staging environment
Production: $500 (infra) + $100 (services) = $600/month
Revenue Projections
Break-even Analysis:
Assuming $29/month Starter plan (most popular):
- Monthly costs: $125 (staging) + $600 (production) = $725
- Break-even: 25 customers (25 × $29 = $725)
- Stripe fees: ~$20 (25 × 2.9% × $29)
- Net break-even: ~27 customers
Conservative Growth:
- Month 1: 10 customers = $290 revenue (−$435 loss)
- Month 3: 30 customers = $870 revenue (+$145 profit)
- Month 6: 100 customers = $2,900 revenue (+$2,175 profit)
- Month 12: 300 customers = $8,700 revenue (+$8,000 profit)
📋 Launch Checklist
MVP Requirements (Must Have)
- User registration working
- Email verification working
- Stripe payment integration complete
- License generation functional
- License validation API operational
- Client SDK in coditect-core
- Heartbeat mechanism working
- Subscription renewal automated
- Payment failure handling implemented
- Basic documentation (getting started guide)
Nice to Have (Can Launch Without)
- Social login (Google/GitHub OAuth)
- Standalone binary installers
- Admin dashboard
- Usage analytics
- Billing portal (Stripe provides basic one)
- Advanced monitoring/alerting
Production Readiness
- SSL/TLS certificates configured
- Cloud KMS license signing
- Security audit completed
- Load testing (100+ concurrent users)
- Disaster recovery plan documented
- Terms of Service published
- Privacy Policy published
- Refund policy defined
🚀 Recommendation
Immediate Next Steps (This Week)
- Fix Firebase Authentication (1 day) - Unblocking P0
- Stripe Account Setup (1 day) - Parallel track
- User Registration Backend (2 days) - Core commerce
- License Generation Logic (1 day) - Quick win
Goal: Have payment processing working by end of week
Short-Term (Next 2 Weeks)
- Complete Phase 1: Core Commerce
- Start Phase 2: License Validation
Goal: First paying customer can use CODITECT by December 15
Medium-Term (Next 4 Weeks)
- Complete Phase 2: License Validation
- Complete Phase 3: Subscription Management
- Start Phase 4: Production Hardening
Goal: Production launch December 27, 2025
✅ Conclusion
Current Reality
We have 75% of the infrastructure but 0% of the commercial flow. The infrastructure is solid, but we cannot accept payments or issue licenses yet.
What's Missing for Revenue
Critical (P0) - 22 days:
- User registration (6 days)
- Payment processing (6 days)
- License validation (10 days)
Total to First Dollar: ~22 days of focused development
Path Forward
Option 1: MVP Launch (30 days)
- Build only P0 features
- Accept first paying customers
- Iterate based on feedback
- Revenue Start: January 1, 2026
Option 2: Polished Launch (45 days)
- Include P1 features (nice to have)
- More refined user experience
- Better monitoring/observability
- Revenue Start: January 15, 2026
Recommendation: Option 1 (MVP Launch) - Get to revenue faster, iterate based on real customer feedback.
Document Created: December 1, 2025, 5:00 AM EST Next Review: December 8, 2025 (after Phase 1 progress) Target MVP Launch: January 1, 2026
Created by: Claude Code (Anthropic AI) For: Hal Casteel, Founder/CEO/CTO, AZ1.AI INC Repository: coditect-cloud-backend