Spareload — giełda usług transportowych · Prospekt projektu
Część II: Założenia techniczne i stos technologiczny
Wersja robocza 0.1 · 14 listopada 2026
1. Stos technologiczny
| Warstwa | Technologia | Uzasadnienie |
|---|---|---|
| Backend | Python 3.14 + Django 6.1 | Dojrzały ORM, transakcje, panel administracyjny, i18n, bezpieczeństwo „z pudełka”. |
| Baza danych | PostgreSQL 18 | Transakcje ACID, blokady wierszy (SELECT … FOR UPDATE) przy operacjach finansowych. |
| Geo | MVP: haversine w Pythonie → PostGIS (GeoDjango) | Dopasowanie tras w promieniu i w korytarzu trasy. |
| Frontend (MVP) | Szablony Django + Bootstrap 5 | Najszybsza droga do czytelnego UI; bez osobnego builda JS. |
| Frontend (docelowo) | HTMX lub React/Next + Django REST Framework / Ninja | API dla aplikacji mobilnej kierowcy. |
| Płatności | Stripe Connect (manual capture, separate charges & transfers) | Escrow bez licencji KNF; KYB przewoźników po stronie Stripe. |
| Rejestry | VIES (REST, bez klucza) + GUS BIR 1.1 (SOAP, klucz) | Weryfikacja VAT i dane firm przy rejestracji. |
| Zadania w tle | Celery + Redis (MVP: usługa scheduler uruchamia run_automations co 15 min) |
Webhooki Stripe, auto-potwierdzenia, wygasanie dokumentów, przypomnienia. |
| Pliki | Lokalny wolumen → S3-compatible (np. Scaleway/OVH, region UE) | Zdjęcia CMR/POD; dane w UE. |
| Uruchomienie | Docker + docker compose | Identyczne środowisko dev/prod. |
| Produkcja | Gunicorn + Nginx (host, certbot), zarządzany PostgreSQL, Sentry | Standard. |
2. Architektura aplikacji
config/ ustawienia Django, routing główny
accounts/ User (własny model), Company, CompanyDocument (weryfikacja KYB)
market/ City, EmptyRun (pusty przebieg), CargoRequest (zlecenie ładunku),
Offer (oferta przewoźnika), Booking (zlecenie przewozu),
BookingEvent (dziennik), Evidence (dowody), Dispute (reklamacja)
services.py — maszyna stanów zlecenia (jedyne miejsce zmian statusu)
geo.py — odległości, objazd, dopasowanie tras
payments/ PaymentHold (blokady/obciążenia), Payout (transfery/zwroty)
gateway.py — abstrakcja bramki: FakeGateway (demo) / StripeGateway
templates/ UI (Bootstrap 5)
docs/ prospekt + szkice regulaminów
Zasada: widoki są cienkie. Każda zmiana statusu zlecenia i każda operacja pieniężna przechodzi przez market/services.py. Funkcje wykonują się w transaction.atomic() z blokadą wiersza zlecenia, zapisują BookingEvent i wołają bramkę płatności przez interfejs. Dzięki temu Stripe można podmienić bez dotykania widoków.
3. Model domenowy
Company (SHIPPER | CARRIER | PLATFORM)
├─ User (wielu użytkowników na firmę)
├─ CompanyDocument (LICENSE, EU_LICENSE, OCP, REGISTRY) + valid_until + status
│
├─ EmptyRun (przewoźnik) origin → destination, okno dat, pojazd, ładowność,
│ stawka EUR/km, max objazd
├─ CargoRequest (zleceniodawca) origin → destination, okno dat, towar, budżet
│ └─ Offer (przewoźnik) cena, opcjonalnie powiązany EmptyRun
│
└─ Booking ◄── z EmptyRun (rezerwacja kursu) lub z Offer (akceptacja oferty)
├─ PaymentHold CLIENT_PAYMENT | CARRIER_DEPOSIT
├─ Payout CARRIER_PAYOUT | PLATFORM_FEE | REFUND | PENALTY
├─ Evidence PICKUP | DELIVERY | DISPUTE (plik, notatka, GPS, czas)
├─ Dispute powód, roszczenie, odpowiedź przewoźnika, rozstrzygnięcie
└─ BookingEvent pełny dziennik audytowy
4. Maszyna stanów zlecenia (Booking)
┌─────────────── odrzucenie ─────────► REJECTED
│
[rezerwacja] ─► PENDING_CARRIER ── anulowanie zleceniodawcy ─► CANCELLED
│ potwierdzenie (blokada kaucji)
▼
CONFIRMED ──── anulowanie przez przewoźnika ────► FAILED
│ └── reklamacja „niepodjęcie ładunku” ─┐ (kaucja → zleceniodawca)
│ odbiór (dowód) │
▼ │
IN_TRANSIT │
│ dostawa (dowód POD wymagany) │
▼ ▼
DELIVERED ── reklamacja (≤48 h) ────────► DISPUTED
│ │ uznanie przez przewoźnika
│ potwierdzenie / auto po 48 h │ lub decyzja operatora
▼ ▼
COMPLETED ◄──────── rozliczenie ──────── COMPLETED (częściowy zwrot)
lub FAILED (pełny zwrot + kara)
Rozliczenie: jedna funkcja settle(booking, refund, forfeit_deposit)
paid = cena − refund- obciążenie klienta:
paid(częściowe obciążenie autoryzacji) lub zwrotrefund(tryb obciąż i wstrzymaj), - prowizja platformy:
5% × paid, - transfer do przewoźnika:
paid − prowizja, - kaucja: zwolnienie albo obciążenie i przekazanie zleceniodawcy (kara umowna).
5. Abstrakcja płatności
class PaymentGateway(Protocol):
def authorize(hold) -> ref # blokada (manual capture) lub obciążenie
def capture(hold, amount) # obciążenie (częściowe dopuszczalne)
def release(hold) # anulowanie autoryzacji
def refund(hold, amount) # zwrot (tryb obciąż-i-wstrzymaj)
def transfer(payout) -> ref # transfer na connected account
- FakeGateway — tryb demo: zapisuje operacje w bazie bez prawdziwych pieniędzy.
- StripeGateway — zaimplementowana (tryb testowy), parametry wywołań walidowane testem na
stripe-mock: - płatność zleceniodawcy przez Stripe Checkout (stan zlecenia „Oczekuje na płatność” do czasu powrotu lub webhooka),
- konto wypłat przewoźnika: Express, onboarding przez AccountLink,
- karta przewoźnika do kaucji: Checkout
mode=setup, kaucja jako PaymentIntent off-session, PaymentIntent(capture_method="manual")dla autoryzacji,PaymentIntent.capture(amount_to_capture=…)dla częściowego obciążenia,Transfer(destination=acct_…, transfer_group=booking.reference)dla wypłat,Refundw trybie obciąż-i-wstrzymaj.- Tryb wybierany automatycznie: ≤ 7 dni do załadunku → AUTHORIZE, > 7 dni → CHARGE_AND_HOLD.
- Webhook
/stripe/webhook/:checkout.session.completed,account.updated,payment_intent.canceled. Obsługa jest idempotentna (powrót z Checkout i webhook mogą przyjść oba). - Transfer do odbiorcy bez aktywnego konta Stripe dostaje status „czeka” i wykonuje się automatycznie po aktywacji konta.
- Do zrobienia:
charge.dispute.*(chargebacki), odnawianie autoryzacji wygasających po 7 dniach.
6. Dopasowanie tras (matching)
MVP liczy odległości metodą haversine × współczynnik drogowy 1,25:
objazd = d(A, C) + d(C, D) + d(D, B) − d(A, B)
A→B pusty przebieg przewoźnika
C→D ładunek zleceniodawcy
Oferta pasuje, gdy objazd ≤ max_detour_km przewoźnika i okna dat się pokrywają. Wyniki sortowane są rosnąco po objeździe. Parametry rankingu są jawne (wymóg P2B).
Etap 2: PostGIS + routing drogowy (OSRM / GraphHopper, self-hosted), korytarz jako bufor wokół geometrii trasy (ST_DWithin), indeksy GiST.
7. Bezpieczeństwo
- Uprawnienia sprawdzane na poziomie obiektu: każdy widok zlecenia weryfikuje, czy użytkownik należy do firmy-strony.
- Wszystkie zmiany stanu tylko przez POST + CSRF.
- Operacje pieniężne:
select_for_update, idempotencja (klucze idempotencji Stripe =booking.reference + operacja). - Pliki dowodowe: walidacja typu i rozmiaru (≤ 10 MB), w produkcji serwowane przez podpisane URL-e.
- Docelowo: 2FA dla kont firmowych, logi audytowe operatora.
8. Konta demo (tryb DEMO_MODE=1)
Hasło wszystkich kont: demo12345. Na stronie logowania są przyciski szybkiego logowania.
| Login | Rola | Firma |
|---|---|---|
klient1 |
zleceniodawca | Meble Kowalski sp. z o.o. (Wrocław) |
klient2 |
zleceniodawca | Berliner Feinkost GmbH (Berlin) |
klient3 |
zleceniodawca | AgroPol S.A. (Poznań) |
abc |
przewoźnik | ABC Transport sp. z o.o. (Warszawa) — zweryfikowany |
rhone |
przewoźnik | Rhône Logistique SARL (Lyon) — zweryfikowany |
brno |
przewoźnik | Morava Cargo s.r.o. (Brno) — zweryfikowany |
nowy |
przewoźnik | Nowy Trans (Łódź) — niezweryfikowany, wygasłe OCP |
operator |
operator platformy | panel sporów i weryfikacji + panel admina (ADMIN_URL) |
9. Plan rozwoju (roadmap)
| Etap | Zakres |
|---|---|
| 0 — MVP demo (ten etap) | Modele, maszyna stanów, FakeGateway, UI zleceniodawcy/przewoźnika/operatora, dane demo, Docker. |
| 1 — Pilot | ✅ Stripe Connect (test mode), ✅ rejestracja firm + VIES + GUS, ✅ wdrożenie produkcyjne (Docker + nginx). Dalej: e-mail, S3, PostGIS. |
| 2 — Produkcja | Webhooki Stripe, e-CMR, i18n (PL/EN/DE/FR), routing OSRM, monitoring, 2FA. |
| 3 — Skala | API dla TMS, aplikacja mobilna kierowcy, raporty CO₂/ESG, abonamenty. |