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
curl http://localhost:8100/health
curl http://localhost:8100/docs # Swagger UI
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
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_*
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.
| 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.
| Endpoint Group | Limit |
|---|---|
| Payment | 10/min |
| Verification | 30/min |
| PDF downloads | 10/min |
| Webhooks | 100/min |
# 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
The API version is derived from git tags and injected at deploy time.
v0.2.2) is the source of truthAPP_VERSION env var to Cloud Runconfig.py picks up the env var via Pydantic settings/, /api, /health endpoints and Swagger UIcd /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.
curl -s https://dev.api.carpulsetracker.com/ | jq .version
# "v0.2.2"
ci.yaml)ci-cd: api-build-deploymaster or manually via workflow_dispatch.inf and dev on push; manual to tst/prd via dispatch).APP_VERSION in Cloud Run env vars.# 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-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)
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) |
| 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 |