Skip to main content

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-stripe package 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.auth for 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_VALIDATORS setting

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() and prefetch_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:

  1. SMTP (Gmail, Sendgrid, etc.) - Simple, works for MVP
  2. AWS SES - Scalable, cost-effective
  3. 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 code
  • develop - Development branch
  • feature/feature-name - Feature branches
  • hotfix/issue-name - Hotfix branches

Workflow:

  1. Create feature branch from develop
  2. Develop and test locally
  3. Create pull request to develop
  4. Review and merge
  5. Periodically merge develop to main for 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