Bookkeeping Exchange - Technical Architecture
Bookkeeping Exchange - Technical Architecture
5. Technical Implementation & Architecture
Document Version: 1.0 Last Updated: 2025-12-26
5.1 Technology Stack
5.1.1 Backend
Framework: Django 5.0+
- Mature, well-documented framework
- Built-in admin panel
- Excellent ORM
- Strong security features
- Large ecosystem of packages
Python Version: 3.11+
Key Django Packages:
django==5.0+ # Core framework
psycopg2-binary # PostgreSQL adapter
django-environ # Environment variable management
pillow # Image handling (if needed for profiles)
Stripe Integration:
stripe # Stripe Python SDK
dj-stripe # Django + Stripe integration
Email:
django-ses # AWS SES (optional)
# OR use Django's built-in SMTP backend
Development Tools:
django-debug-toolbar # Development debugging
django-extensions # Helpful management commands
ipython # Better REPL
Production:
gunicorn # WSGI server
whitenoise # Static file serving
5.1.2 Database
Primary Database: PostgreSQL 15+
- Reliable, scalable
- JSON field support (for certifications, metadata)
- Full-text search capabilities
- Strong consistency
Database Configuration:
- Connection pooling via pgBouncer (optional for production)
- Regular automated backups (daily)
- SSL/TLS encryption for production
5.1.3 Frontend
Approach: Django Templates + Modern CSS/JS
- Server-side rendering for SEO and performance
- Progressive enhancement
- Minimal JavaScript dependencies
CSS Framework Options:
- Option 1: Tailwind CSS (utility-first, customizable)
- Option 2: Bootstrap 5 (component-rich, familiar)
- Option 3: Custom CSS (lightweight, full control)
Recommended: Tailwind CSS for rapid development and customization
JavaScript:
- Vanilla JavaScript (no framework required for MVP)
- Libraries:
- Alpine.js (optional, for interactive components)
- htmx (optional, for AJAX without writing JS)
- Stripe Elements (for payment forms)
Build Tools:
- Django Compressor or Whitenoise for static files
- Optional: Vite for modern JS/CSS bundling
5.1.4 Payment Processing
Provider: Stripe
Integration Approach:
- Use
dj-stripepackage for Django integration - Stripe Checkout for subscription management
- Stripe Payment Intents for lead purchases
- Webhooks for payment status updates
Stripe Products to Use:
- Subscriptions: Monthly recurring billing
- Payment Intents: One-time lead purchases
- Customers: Store bookkeeper payment info
- Webhooks: Listen for payment events
5.1.5 Infrastructure & Deployment
Containerization: Docker
- Dockerfile for Django application
- Docker Compose for local development
- Separate containers for Django, PostgreSQL, Nginx (optional)
Deployment Environment:
- Development: Local Docker
- Staging: Docker on development server (192.168.1.x)
- Production: VPS with Docker
Web Server:
- Development: Django runserver
- Production: Nginx (reverse proxy) + Gunicorn (WSGI)
SSL/TLS:
- Let's Encrypt for free SSL certificates
- Certbot for automatic renewal
5.2 Application Architecture
5.2.1 Django Project Structure
bookkeeping_exchange/ # Project root
├── docker-compose.yml # Docker orchestration
├── Dockerfile # Django container
├── requirements.txt # Python dependencies
├── .env.example # Environment variables template
├── manage.py # Django management script
├── config/ # Project configuration
│ ├── __init__.py
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py # Common settings
│ │ ├── development.py # Dev-specific settings
│ │ ├── production.py # Prod-specific settings
│ ├── urls.py # Root URL configuration
│ ├── wsgi.py # WSGI entry point
├── apps/ # Django applications
│ ├── accounts/ # User authentication & profiles
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── forms.py
│ │ ├── urls.py
│ │ ├── admin.py
│ │ ├── managers.py
│ │ └── templates/accounts/
│ ├── leads/ # Lead management
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── forms.py
│ │ ├── urls.py
│ │ ├── admin.py
│ │ └── templates/leads/
│ ├── bookkeepers/ # Bookkeeper-specific functionality
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── forms.py
│ │ ├── urls.py
│ │ └── templates/bookkeepers/
│ ├── payments/ # Stripe integration, subscriptions
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── stripe_handlers.py
│ │ ├── webhooks.py
│ │ ├── urls.py
│ │ └── templates/payments/
│ ├── core/ # Shared utilities
│ │ ├── models.py # Configuration model
│ │ ├── utils.py
│ │ ├── middleware.py
│ │ └── templatetags/
│ └── dashboard/ # User dashboards
│ ├── views.py
│ ├── urls.py
│ └── templates/dashboard/
├── templates/ # Global templates
│ ├── base.html # Base template
│ ├── home.html # Landing page
│ ├── includes/ # Reusable components
│ │ ├── header.html
│ │ ├── footer.html
│ │ └── forms/
│ └── emails/ # Email templates
│ ├── base_email.html
│ ├── lead_purchased.html
│ └── trial_expiring.html
├── static/ # Static files
│ ├── css/
│ │ ├── main.css
│ │ └── components/
│ ├── js/
│ │ ├── main.js
│ │ └── components/
│ └── images/
├── media/ # User uploads (if needed)
└── logs/ # Application logs
5.2.2 Database Architecture
Connection Settings:
# settings/base.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': env('DB_HOST'),
'PORT': env('DB_PORT', default='5432'),
'CONN_MAX_AGE': 600, # Connection pooling
'OPTIONS': {
'sslmode': 'require', # Production only
}
}
}
Migrations Strategy:
- Use Django migrations for all schema changes
- Version control all migration files
- Test migrations on staging before production
- Keep migrations small and focused
5.2.3 User Authentication
Django Built-in Auth:
- Use
django.contrib.authfor authentication - Extend User model via OneToOne profiles
- Email as username (set
USERNAME_FIELD = 'email')
Custom User Model:
# apps/accounts/models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
username = None # Remove username
email = models.EmailField(unique=True)
user_type = models.CharField(max_length=20, choices=USER_TYPE_CHOICES)
email_verified = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
Email Verification:
- Generate unique token on registration
- Send verification email with link
- Verify token, set
email_verified=True - Required before full account access
Password Requirements:
- Use Django's built-in password validators
- Minimum 8 characters
- Must contain uppercase, lowercase, number
- Django's
AUTH_PASSWORD_VALIDATORSsetting
5.2.4 Payment Integration
Stripe Setup:
# apps/payments/stripe_handlers.py
import stripe
from django.conf import settings
stripe.api_key = settings.STRIPE_SECRET_KEY
# Create Stripe customer
def create_stripe_customer(user):
customer = stripe.Customer.create(
email=user.email,
name=f"{user.first_name} {user.last_name}",
metadata={'user_id': user.id}
)
return customer.id
# Create subscription
def create_subscription(customer_id, price_id):
subscription = stripe.Subscription.create(
customer=customer_id,
items=[{'price': price_id}],
trial_period_days=30,
)
return subscription
# Process lead purchase
def charge_for_lead(customer_id, amount, lead_id):
payment_intent = stripe.PaymentIntent.create(
customer=customer_id,
amount=int(amount * 100), # Convert to cents
currency='usd',
metadata={'lead_id': lead_id},
)
return payment_intent
Webhook Handling:
# apps/payments/webhooks.py
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
import stripe
@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)
# Handle events
if event['type'] == 'invoice.payment_succeeded':
handle_subscription_payment_success(event['data']['object'])
elif event['type'] == 'invoice.payment_failed':
handle_subscription_payment_failed(event['data']['object'])
elif event['type'] == 'customer.subscription.deleted':
handle_subscription_canceled(event['data']['object'])
return HttpResponse(status=200)
5.2.5 Email System
Email Backend Configuration:
# settings/production.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = env('EMAIL_HOST') # e.g., smtp.gmail.com
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = env('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD')
DEFAULT_FROM_EMAIL = 'Bookkeeping Exchange <noreply@bookkeeping-exchange.com>'
Email Templates:
- HTML + Plain text versions
- Django template inheritance
- Use
django.core.mail.EmailMultiAlternatives
Example Email Sending:
# apps/core/utils.py
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
def send_templated_email(to_email, subject, template_name, context):
html_content = render_to_string(f'emails/{template_name}.html', context)
text_content = render_to_string(f'emails/{template_name}.txt', context)
email = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=settings.DEFAULT_FROM_EMAIL,
to=[to_email]
)
email.attach_alternative(html_content, "text/html")
email.send()
Background Email Sending (Future):
- Use Celery + Redis for async email sending
- For MVP: Send emails synchronously (acceptable for low volume)
5.2.6 Security Considerations
Django Security Settings:
# settings/production.py
SECRET_KEY = env('SECRET_KEY') # Generate strong secret key
DEBUG = False
ALLOWED_HOSTS = ['bookkeeping-exchange.com', 'www.bookkeeping-exchange.com']
# HTTPS
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# HSTS
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Content Security
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
# CSRF
CSRF_USE_SESSIONS = True
CSRF_COOKIE_HTTPONLY = True
Authentication Security:
- Password hashing: Django default (PBKDF2)
- Session management: Secure cookies
- Rate limiting: Django-ratelimit or nginx
- Account lockout: After N failed login attempts (future)
Payment Security:
- Never store credit card numbers
- Use Stripe's secure checkout/elements
- PCI compliance handled by Stripe
- Validate webhook signatures
Data Privacy:
- Encrypt sensitive data at rest (future enhancement)
- HTTPS everywhere in production
- Minimal data collection
- Clear consent flows
5.2.7 Performance Optimization
Database Optimization:
- Indexes on frequently queried fields
select_related()andprefetch_related()for joins- Database connection pooling
- Query optimization via Django Debug Toolbar
Caching (Future):
- Redis for session storage
- Cache database queries
- Cache template fragments
- Page caching for public pages
Static Files:
- Whitenoise for static file serving
- Minify CSS/JS
- Image optimization
- CDN for static assets (future)
Code Optimization:
- Lazy loading of non-critical JS
- Async/defer script tags
- Database query optimization
- Pagination for large datasets
5.2.8 Monitoring & Logging
Logging Configuration:
# settings/base.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'logs/django.log',
'maxBytes': 1024*1024*10, # 10MB
'backupCount': 5,
'formatter': 'verbose',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'INFO',
'propagate': True,
},
'apps': {
'handlers': ['file', 'console'],
'level': 'DEBUG',
'propagate': False,
},
},
}
What to Log:
- User registrations
- Lead submissions
- Lead purchases
- Payment transactions
- Subscription changes
- Errors and exceptions
- Admin actions
Monitoring (Future):
- Sentry for error tracking
- Application performance monitoring (APM)
- Uptime monitoring (UptimeRobot, Pingdom)
- Database performance monitoring
5.3 Deployment
5.3.1 Docker Configuration
Dockerfile:
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Set work directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt /app/
RUN pip install --upgrade pip && pip install -r requirements.txt
# Copy project
COPY . /app/
# Collect static files
RUN python manage.py collectstatic --noinput
# Run gunicorn
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"]
docker-compose.yml:
version: '3.8'
services:
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
restart: unless-stopped
web:
build: .
command: gunicorn config.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/app
- static_volume:/app/staticfiles
- media_volume:/app/media
ports:
- "8000:8000"
env_file:
- .env
depends_on:
- db
restart: unless-stopped
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- static_volume:/app/staticfiles
- media_volume:/app/media
ports:
- "80:80"
- "443:443"
depends_on:
- web
restart: unless-stopped
volumes:
postgres_data:
static_volume:
media_volume:
5.3.2 Environment Variables
.env.example:
# Django
SECRET_KEY=your-secret-key-here
DEBUG=False
ALLOWED_HOSTS=bookkeeping-exchange.com,www.bookkeeping-exchange.com
# Database
DB_NAME=bookkeeping_exchange
DB_USER=postgres
DB_PASSWORD=secure_password
DB_HOST=db
DB_PORT=5432
# Stripe
STRIPE_PUBLIC_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_MONTHLY_PRICE_ID=price_...
# Email
EMAIL_HOST=smtp.gmail.com
EMAIL_HOST_USER=noreply@bookkeeping-exchange.com
EMAIL_HOST_PASSWORD=app_password
EMAIL_PORT=587
EMAIL_USE_TLS=True
# Other
DEFAULT_FROM_EMAIL=Bookkeeping Exchange <noreply@bookkeeping-exchange.com>
SUPPORT_EMAIL=support@bookkeeping-exchange.com
ADMIN_EMAIL=admin@bookkeeping-exchange.com
5.3.3 Deployment Process
Initial Deployment:
# 1. Clone repository on VPS
git clone <repository_url>
cd bookkeeping_exchange
# 2. Create .env file
cp .env.example .env
# Edit .env with production values
# 3. Build and start containers
docker-compose up -d --build
# 4. Run migrations
docker-compose exec web python manage.py migrate
# 5. Create superuser
docker-compose exec web python manage.py createsuperuser
# 6. Create initial configuration
docker-compose exec web python manage.py create_initial_config
# 7. Configure Stripe webhooks
# Point Stripe webhook to: https://domain.com/payments/webhook/
# 8. Test deployment
# Visit https://domain.com
Subsequent Deployments:
# 1. Pull latest code
git pull origin main
# 2. Rebuild containers
docker-compose up -d --build
# 3. Run migrations
docker-compose exec web python manage.py migrate
# 4. Collect static files
docker-compose exec web python manage.py collectstatic --noinput
# 5. Restart services
docker-compose restart web
5.3.4 Backup Strategy
Database Backups:
# Create backup
docker-compose exec db pg_dump -U $DB_USER $DB_NAME > backup_$(date +%Y%m%d).sql
# Automated daily backups (cron job)
0 2 * * * cd /path/to/project && ./backup.sh
backup.sh:
#!/bin/bash
BACKUP_DIR="/backups/bookkeeping_exchange"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$TIMESTAMP.sql"
# Create backup
docker-compose exec -T db pg_dump -U $DB_USER $DB_NAME > $BACKUP_FILE
# Compress backup
gzip $BACKUP_FILE
# Delete backups older than 30 days
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +30 -delete
Restore from Backup:
# Stop application
docker-compose down
# Restore database
gunzip -c backup_20250126.sql.gz | docker-compose exec -T db psql -U $DB_USER $DB_NAME
# Start application
docker-compose up -d
5.4 Testing Strategy
5.4.1 Test Types
Unit Tests:
- Model methods
- Form validation
- Utility functions
- Business logic
Integration Tests:
- View tests (Django TestCase)
- Form submission flows
- Payment processing (Stripe test mode)
- Email sending
End-to-End Tests (Future):
- Selenium or Playwright
- Full user flows
- Payment flows
- Multi-browser testing
5.4.2 Test Configuration
# config/settings/test.py
from .base import *
# Use in-memory SQLite for tests (faster)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
# Disable password hashing for tests (faster)
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
# Use console email backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Stripe test mode
STRIPE_SECRET_KEY = 'sk_test_...'
STRIPE_PUBLIC_KEY = 'pk_test_...'
Run Tests:
# Run all tests
python manage.py test
# Run specific app tests
python manage.py test apps.leads
# Run with coverage
coverage run --source='.' manage.py test
coverage report
5.4.3 Example Tests
# apps/leads/tests.py
from django.test import TestCase
from apps.leads.models import Lead
from apps.accounts.models import BusinessOwner, User
class LeadModelTest(TestCase):
def setUp(self):
self.user = User.objects.create_user(
email='test@example.com',
password='TestPass123'
)
self.business_owner = BusinessOwner.objects.create(
user=self.user,
business_name='Test Business',
phone='555-555-5555'
)
def test_lead_creation(self):
lead = Lead.objects.create(
business_owner=self.business_owner,
business_name='Test Business',
industry='Construction',
annual_revenue_range='100k-250k',
email='test@example.com',
phone='555-555-5555',
city='Chicago',
state='IL'
)
self.assertEqual(lead.status, 'pending')
self.assertTrue(lead.is_active)
def test_lead_pricing_calculation(self):
lead = Lead.objects.create(
business_owner=self.business_owner,
annual_revenue_range='100k-250k',
# ... other required fields
)
price = lead.calculate_price()
self.assertEqual(price, 150.00) # Based on pricing rules
5.5 Third-Party Integrations
5.5.1 Stripe
Products Needed:
- Subscriptions (monthly recurring billing)
- Payment Intents (one-time lead purchases)
- Webhooks (payment status updates)
Configuration:
- Create products in Stripe Dashboard
- Set up webhook endpoint
- Test in Stripe test mode
- Switch to live mode for production
5.5.2 Email Service
Options:
- SMTP (Gmail, Sendgrid, etc.) - Simple, works for MVP
- AWS SES - Scalable, cost-effective
- Sendgrid/Mailgun - Feature-rich, good deliverability
For MVP: Use standard SMTP (Gmail App Password or similar)
5.5.3 Domain & DNS
DNS Records:
A @ -> VPS IP address
A www -> VPS IP address
MX @ -> mail server (if using custom email)
TXT @ -> SPF record (email authentication)
TXT @ -> DKIM record (email authentication)
5.6 Development Workflow
5.6.1 Local Development
# 1. Clone repository
git clone <repo_url>
cd bookkeeping_exchange
# 2. Create virtual environment
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 3. Install dependencies
pip install -r requirements.txt
# 4. Set up .env file
cp .env.example .env
# Edit .env with local values
# 5. Run migrations
python manage.py migrate
# 6. Create superuser
python manage.py createsuperuser
# 7. Create initial config
python manage.py create_initial_config
# 8. Run development server
python manage.py runserver
Or with Docker:
docker-compose -f docker-compose.dev.yml up
5.6.2 Git Workflow
Branching Strategy:
main- Production-ready codedevelop- Development branchfeature/feature-name- Feature brancheshotfix/issue-name- Hotfix branches
Workflow:
- Create feature branch from
develop - Develop and test locally
- Create pull request to
develop - Review and merge
- Periodically merge
developtomainfor releases
5.7 Management Commands
Custom Django Commands:
# apps/core/management/commands/create_initial_config.py
from django.core.management.base import BaseCommand
from apps.core.models import Configuration
class Command(BaseCommand):
help = 'Create initial configuration with default values'
def handle(self, *args, **options):
config, created = Configuration.objects.get_or_create(id=1)
if created:
self.stdout.write(self.style.SUCCESS('Configuration created'))
else:
self.stdout.write(self.style.WARNING('Configuration already exists'))
Useful Commands:
# Create initial config
python manage.py create_initial_config
# Send test email
python manage.py send_test_email admin@example.com
# Check for expired trials
python manage.py check_trial_expirations
# Sync Stripe subscriptions
python manage.py sync_stripe_subscriptions
Next Document: PRD_6_ROADMAP.md - MVP scope and future roadmap