diff --git a/docs/karrio-shipping.md b/docs/karrio-shipping.md new file mode 100644 index 0000000..ab330df --- /dev/null +++ b/docs/karrio-shipping.md @@ -0,0 +1,150 @@ +# Karrio — plateforme d'expédition multi-transporteurs (self-hosted) + +Karrio est l'**EasyPost/Shippo open-source** (LGPL), auto-hébergé sur le serveur de prod. +Il fournit, sous une **API unifiée**, le **calcul de tarif d'expédition + étiquettes + suivi** +à travers de nombreux transporteurs (BYOCA : on branche nos propres comptes, **0 frais par étiquette**). + +Objectif : calculer les frais de livraison au **checkout de la boutique matériel** (`/store`) et +gérer l'expédition des commandes (#54). Déployé le 2026-06-04. + +> ⚠️ Ce document ne contient **aucun secret**. Les identifiants vivent sur le serveur +> (`/opt/karrio/.env`, `/opt/karrio/ADMIN_CREDS.txt`). + +--- + +## Accès + +| Quoi | URL | +|---|---| +| **Dashboard** (GUI gestion transporteurs) | https://karrio.gigafibre.ca — connexion `admin@targo.ca` (mdp : `/opt/karrio/ADMIN_CREDS.txt`) | +| **API publique** | https://api.karrio.gigafibre.ca | +| **API interne** (depuis le hub) | `http://karrio.api:5002` (réseau Docker `erpnext_erpnext`) | +| Auth API | `POST /api/token {email,password}` → JWT `access` | + +DNS Cloudflare : `karrio` + `api.karrio`.gigafibre.ca → 96.125.196.67, **DNS-only** (proxied=false, +comme `msg`/`erp`, pour le challenge Let's Encrypt). TLS via Traefik (resolver `letsencrypt`). + +--- + +## Architecture + +- **Emplacement** : `/opt/karrio/` (compose) ; source clonée dans `/opt/karrio-src/` (Git). +- **Conteneurs** : `karrio.api` (5002) · `karrio.worker` · `karrio.dashboard` (3002) · `karrio.db` + (postgres:16 dédié) · `karrio.redis` · `karrio.mail` (maildev). +- **Réseaux Docker** : `karrio_default` (interne) + `erpnext_erpnext` (pour que le hub joigne l'API) + + `proxy` (Traefik). L'attache aux réseaux externes est dans `docker-compose.override.yml` (persistante). +- **Images** : `karrio/server` + `karrio/dashboard` **2026.1.31**. L'`api`/`worker` utilisent une + **image custom** `karrio-server-targo:2026.1.31` (transporteurs canadiens supplémentaires, voir plus bas). + +### `Dockerfile.carriers` (transporteurs canadiens opt-in) +```dockerfile +FROM karrio.docker.scarf.sh/karrio/server:2026.1.31 +# Transporteurs non bundlés dans l'image OSS (paquets PyPI gratuits) +RUN /karrio/venv/bin/pip install --no-cache-dir "karrio==2026.1.31" \ + karrio_canpar karrio_nationex karrio_dicom karrio_boxknight +``` +Build : `docker build -t karrio-server-targo:2026.1.31 -f Dockerfile.carriers .` + +### `docker-compose.override.yml` (réseaux + image custom + labels Traefik) +```yaml +networks: + erpnext_erpnext: { external: true } + proxy: { external: true } +services: + api: + image: karrio-server-targo:2026.1.31 + networks: [default, erpnext_erpnext, proxy] + labels: + - traefik.enable=true + - traefik.docker.network=proxy + - traefik.http.routers.karrio-api.rule=Host(`api.karrio.gigafibre.ca`) + - traefik.http.routers.karrio-api.entrypoints=websecure + - traefik.http.routers.karrio-api.tls.certresolver=letsencrypt + - traefik.http.services.karrio-api.loadbalancer.server.port=5002 + worker: + image: karrio-server-targo:2026.1.31 + dashboard: + networks: [default, proxy] + labels: + - traefik.enable=true + - traefik.docker.network=proxy + - traefik.http.routers.karrio-dash.rule=Host(`karrio.gigafibre.ca`) + - traefik.http.routers.karrio-dash.entrypoints=websecure + - traefik.http.routers.karrio-dash.tls.certresolver=letsencrypt + - traefik.http.services.karrio-dash.loadbalancer.server.port=3002 +``` + +### Variables `.env` clés (valeurs réelles sur le serveur) +``` +KARRIO_TAG=2026.1.31 +DATABASE_HOST=karrio.db # nom de conteneur, PAS "db" (collision ERPNext) +REDIS_HOST=karrio.redis # idem +USE_HTTPS=True +KARRIO_PUBLIC_URL=https://api.karrio.gigafibre.ca +DASHBOARD_URL=https://karrio.gigafibre.ca +ALLOW_SIGNUP=False +ADMIN_EMAIL=admin@targo.ca +# SECRET_KEY / JWT_SECRET / OIDC_RSA_PRIVATE_KEY / ADMIN_PASSWORD / DATABASE_PASSWORD : générés +``` + +--- + +## Transporteurs disponibles + +- **Bundlés (image OSS)** : Canada Post, Purolator, UPS, FedEx, DHL (×4), GLS, USPS, DPD, Chronopost, + La Poste, Sendle, Australia Post, **eShipper**, **Freightcom**, generic… (~29). +- **Ajoutés (image custom)** : 🇨🇦 **Canpar**, **Nationex** (QC), **Dicom / GLS Canada**, **BoxKnight** (last-mile Montréal). +- **Agrégateurs canadiens** (1 compte → multi-transporteurs à tarifs négociés, **Canpar inclus**) : + **eShipper**, **Freightcom** (déjà bundlés). +- Dispo sur PyPI si besoin (international) : `karrio_tge, karrio_geodis, karrio_asendia, karrio_aramex`. + Non publiés : shiptime, loomis, intelcom, ics_courier. + +> Karrio OSS ne bundle qu'un sous-ensemble ; les autres transporteurs sont des paquets PyPI +> `karrio_` **gratuits** (pas une limitation de licence — du packaging). On les cuit dans +> l'image custom. + +--- + +## Pièges rencontrés (résolus) + +1. **Collision de hostname** : `api` étant sur le réseau partagé `erpnext_erpnext`, `db`/`redis` + résolvaient vers le **Postgres/Redis d'ERPNext** (« password auth failed for user karrio »). + → `.env` : `DATABASE_HOST=karrio.db`, `REDIS_HOST=karrio.redis` (noms de conteneurs non ambigus). +2. **Attache réseau** : `docker network connect` est perdu au recreate → réseaux dans l'`override.yml`. +3. **Admin non auto-semé** : créer manuellement — + binaire = `/karrio/venv/bin/karrio` (absent du PATH en `bash -lc` → utiliser `docker exec ... sh -c`). + ``` + docker exec -e KPW= karrio.api sh -c "cd /karrio/app && karrio shell < /tmp/mkadmin.py" + # mkadmin.py : get_user_model().objects.create_superuser('admin@targo.ca', os.environ['KPW']) + ``` +4. Réinit DB si besoin : `docker compose down -v` (sinon vieux creds persistent dans le volume). + +--- + +## Intégration prévue (hub → #54) + +`services/targo-hub/lib/shipping.js` exposera `quoteRates({origin, dest, parcel})` : +1. token JWT (`POST /api/token`) → cache, +2. appel API Karrio (rates) avec origine (entrepôt) + destination (client) + colis + (poids = `Item.weight_per_unit` ERPNext), +3. options de tarif → ligne « Frais de livraison » au checkout boutique, +4. `/store/checkout` → Sales Invoice ERPNext (`update_stock`) + Stripe via `lib/payments.js`. + +--- + +## Sources + +- Karrio — dépôt : +- Karrio — intégrations transporteurs : +- Karrio — Canada Post : +- Karrio — Purolator : +- Karrio — plateforme / éditions : +- Karrio — starter Next.js (dashboard) : +- Extension Canpar (PyPI) : +- Agrégateurs canadiens — eShipper : +- Agrégateurs canadiens — Freightcom : +- ShipTime (transporteurs/rabais, non Karrio-natif) : +- Comparatif shipping APIs 2026 : +- Comparatif transporteurs Canada 2026 : + +_Mémoire interne liée : `project_karrio_shipping.md`._