03-Development / 03.05.API-Development

03.05.API Development

03.05. API Development

Local Development Setup

Native (without Docker)

cd /opt/bnc/bnc-cpt/bnc-cpt-api/src/python/cpt-api
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # Edit with your credentials
python run.py           # http://localhost:8100
cd /opt/bnc/bnc-cpt/bnc-cpt-utl
make do-setup-api          # Build and start on port 8100
make do-setup-api-no-cache # Rebuild from scratch

Verify

curl http://localhost:8100/health
curl http://localhost:8100/docs     # Swagger UI

Project Structure

src/python/cpt-api/
├── run.py                     # Dev entry point (uvicorn with reload)
├── pyproject.toml             # Poetry config, test markers
├── requirements.txt           # Pip dependencies
├── pytest.ini                 # Test configuration
├── .env.example               # Environment template
└── app/
    ├── main.py                # FastAPI init, CORS, rate limiting, Redis lifecycle
    ├── config.py              # Auto-generated by tpl-gen (DO NOT EDIT)
    ├── api/v1/
    │   ├── api.py             # Router aggregator
    │   └── routers/
    │       ├── auth.py        # JWT login (form + JSON)
    │       ├── payment.py     # Create intent, verify, receipts, webhooks, capture
    │       ├── tesla.py       # Tesla OAuth, sessions, reports, charging invoices
    │       ├── pricing.py     # CPQ catalog, quotes, voucher validation
    │       ├── dev_proxy.py   # Dev tunnel registration for OAuth relay
    │       └── worker.py      # Async background task worker
    ├── models/
    │   ├── auth.py            # UserLogin, TokenResponse, TokenData
    │   ├── db.py              # Database models
    │   ├── order_session.py   # OrderSession state model
    │   ├── payment.py         # PaymentPlan, PaymentMethod, PaymentStatus
    │   ├── tesla.py           # Tesla OAuth, VehicleData, session models
    │   ├── tesla_acquisition.py # Tesla vehicle acquisition models
    │   ├── pricing.py         # CatalogResponse, QuoteRequest/Response, voucher models
    │   └── dev_proxy.py       # DevProxy registration/status models
    ├── services/
    │   ├── auth.py            # JWT + bcrypt
    │   ├── payment.py         # Payment orchestration (Redis-backed)
    │   ├── pdf.py             # WeasyPrint HTML→PDF orchestrator
    │   ├── cpq.py             # Configure-Price-Quote (tier pricing, vouchers)
    │   ├── i18n.py            # Backend internationalization (9 locales)
    │   ├── dev_proxy.py       # Dev proxy tunnel registry
    │   ├── order_session_service.py  # OrderSession CRUD (Redis-backed)
    │   ├── report_field_schema/
    │   │   └── __init__.py    # Brand-agnostic formatting engine (FieldDef, FieldType, RegionContext)
    │   ├── report_renderer/
    │   │   ├── __init__.py    # Package init
    │   │   ├── base.py        # Shared report rendering logic, i18n label wiring
    │   │   ├── basic.py       # Basic plan report renderer
    │   │   ├── pro.py         # Pro plan report renderer
    │   │   ├── factory.py     # ReportRendererFactory (plan → renderer)
    │   │   └── helpers.py     # Shared helpers (safe_num, safe_str, translate_api_value)
    │   ├── vehicle_providers/
    │   │   └── tesla/
    │   │       ├── __init__.py
    │   │       ├── fleet.py         # Tesla Fleet API v2 (OAuth, sessions, reports)
    │   │       ├── acquisition.py   # Vehicle data acquisition pipeline
    │   │       ├── auth.py          # Tesla OAuth token management
    │   │       ├── client.py        # HTTP client for Tesla API
    │   │       ├── fields.py        # Tesla field definitions (250 fields)
    │   │       └── normalization.py # Raw API → canonical data normalization
    │   └── payment_providers/
    │       ├── base.py        # Abstract provider interface
    │       ├── factory.py     # Provider factory
    │       ├── stripe_provider.py
    │       ├── paypal_provider.py
    │       ├── mobilepay_provider.py   # Placeholder
    │       ├── googlepay_provider.py   # Placeholder
    │       └── applepay_provider.py    # Placeholder
    ├── core/
    │   ├── exceptions.py      # Custom exceptions (Payment, Tesla, DevProxy)
    │   ├── dependencies.py    # JWT auth dependencies
    │   ├── redis.py           # Async Redis client singleton
    │   ├── cleanup.py         # Resource cleanup handlers
    │   ├── cloud_tasks.py     # GCP Cloud Tasks integration
    │   ├── database.py        # Database connection management
    │   ├── encryption.py      # Token encryption/decryption
    │   ├── gcs.py             # Google Cloud Storage client
    │   ├── logging_config.py  # Cloud JSON logging formatter
    │   ├── privacy.py         # PII redaction and privacy controls
    │   ├── rate_limiter.py    # slowapi rate limiting setup
    │   ├── resilience.py      # Retry and circuit breaker patterns
    │   └── tracing.py         # Request tracing and correlation IDs
    ├── i18n/
    │   └── locales/           # Backend translation files (9 locales)
    │       ├── en.json
    │       ├── fi.json
    │       ├── sv.json
    │       ├── de.json
    │       ├── fr.json
    │       ├── es.json
    │       ├── it.json
    │       ├── da.json
    │       └── no.json
    └── templates/
        ├── receipt.html       # Jinja2 receipt PDF template
        ├── basic_report.html  # Basic plan vehicle report template
        └── pro_report.html    # Pro plan vehicle report template

Key Architecture

Payment Providers (Strategy Pattern)

Each provider implements BasePaymentProvider:

class BasePaymentProvider(ABC):
    def create_payment_intent(plan, amount, ...) -> dict
    def verify_payment(payment_id) -> dict
    def can_handle_payment_id(payment_id) -> bool  # Self-identification
    def is_configured() -> bool
    def get_provider_name() -> str

To add a new provider: 1. Create new_provider.py implementing BasePaymentProvider 2. Add to factory.py imports and _providers list 3. No changes needed to PaymentService

Payment ID patterns: Stripe pi_*/cs_*, PayPal 5O*/3L*, MobilePay mp_*

Configuration (config.py)

All settings via Pydantic BaseSettings with env var override:

settings = Settings()  # Reads from .env or OS environment
settings.APP_VERSION   # "0.3.0" default, overridden by APP_VERSION env var
settings.PAYMENT_PLANS # {"basic": {"amount": 1900, "currency": "eur"}, ...}

Note: config.py is auto-generated by tpl-gen from bnc-cpt-api/src/tpl/src/python/cpt-api/app/config.py.tpl. Do not edit it directly — edit the template and regenerate.

Server-side validated payment plans (cents): Basic 1900, Pro 2900, Expert 14900.

Charging Invoice Endpoints

Method Path Description
GET /tesla/session/{id}/vehicles/{vin}/charging-invoices Invoice metadata + totals
GET /tesla/session/{id}/vehicles/{vin}/charging-invoices/{invoice_id} Single invoice PDF
GET /tesla/session/{id}/vehicles/{vin}/charging-invoices.zip Per-vehicle invoice ZIP
GET /tesla/session/{id}/charging-invoices.zip Session-level ZIP (all vehicles, subfolders)
GET /tesla/session/{id}/vehicles/{vin}/charging-history.csv Charging history CSV

All endpoints also have /orders/{order_id}/... aliases. Query param months (1-24, default 24) controls the lookback window.

The session-level ZIP organizes invoices in per-vehicle subfolders: {vehicle-name}-{vin-suffix}/invoice-file.pdf.

Rate Limiting

Endpoint Group Limit
Payment 10/min
Verification 30/min
PDF downloads 10/min
Webhooks 100/min

Testing

# From bnc-cpt-utl/
make test-api             # All tests
make test-api-cov         # With coverage
make test-api-payment     # Payment tests only
make test-api-debug       # Verbose output
make test-api-shell       # Shell into test container

# Native
cd src/python/cpt-api
pytest                    # All tests
pytest -m payment         # By marker
pytest -m "not slow"      # Skip slow tests

Test markers: unit, integration, slow, payment, auth, tesla

Versioning

The API version is derived from git tags and injected at deploy time.

How it works

  1. Git tag (e.g., v0.2.2) is the source of truth
  2. CD workflow reads the tag and passes APP_VERSION env var to Cloud Run
  3. config.py picks up the env var via Pydantic settings
  4. Version visible at /, /api, /health endpoints and Swagger UI

Bumping the version

cd /opt/bnc/bnc-cpt/bnc-cpt-utl

# Bump patch: v0.2.2 -> v0.2.3
TARGET_PROJ=/opt/bnc/bnc-cpt/bnc-cpt-api ./run -a do_version_bump

# Bump minor: v0.2.2 -> v0.3.0
BUMP=minor TARGET_PROJ=/opt/bnc/bnc-cpt/bnc-cpt-api ./run -a do_version_bump

# Push the tag
git -C /opt/bnc/bnc-cpt/bnc-cpt-api push origin v0.2.3

Increment logic: patch 0-9 then minor rolls, minor 0-9 then major rolls.

Checking deployed version

curl -s https://dev.api.carpulsetracker.com/ | jq .version
# "v0.2.2"

CI/CD

Unified Workflow (ci.yaml)

# Manual deploy to test environment
gh workflow run ci.yaml -f environment=tst --repo csitea/bnc-cpt-api

# Monitor
gh run list --workflow=ci.yaml --repo csitea/bnc-cpt-api

Post-Deploy Testing (post-deploy-test job in ci.yaml)

After deployment, automated smoke tests run against the deployed API:

Test Endpoint Expected
Health check GET /health HTTP 200
Swagger UI GET /docs HTTP 200
Payment endpoint POST /api/v1/payment/create-intent Not HTTP 500

The job: - Authenticates with GCP and fetches test credentials from Secret Manager - Runs curl-based smoke tests against https://{env}.api.carpulsetracker.com - Produces a GitHub Actions step summary with results - Skips prd environment (production is never auto-tested)

Environment Variables

Required for local development (see .env.example):

Variable Description Default
APP_NAME Application name Car Pulse Tracker API
APP_VERSION Version string 0.3.0
DEBUG Enable debug mode True
HOST Bind address 0.0.0.0
PORT Bind port 443 (run.py overrides to 8100 for local dev)
REDIS_URL Redis connection URL redis://localhost:6379/0
JWT_SECRET_KEY JWT signing key (required)
STRIPE_SECRET_KEY Stripe API key (required for Stripe)
PAYPAL_CLIENT_ID PayPal client ID (required for PayPal)
ADMIN_USERNAME Admin login admin
ADMIN_PASSWORD_HASH bcrypt hash (required)

Production URLs

Environment API Swagger UI
prd https://api.carpulsetracker.com https://api.carpulsetracker.com/docs
dev https://dev.api.carpulsetracker.com https://dev.api.carpulsetracker.com/docs
tst https://tst.api.carpulsetracker.com https://tst.api.carpulsetracker.com/docs
inf https://inf.api.carpulsetracker.com https://inf.api.carpulsetracker.com/docs