ENERGY SOLUTIONS
Server-Analyse: STARQstrom Bestandssystem
v1.0
Datum
25.02.2026
Server
62.108.57.65
Erstellt von
ProcesslyAI

Inhaltsverzeichnis

Server-Analyse: STARQstrom Bestandssystem

Datum: 25.02.2026 | Server: 62.108.57.65 (Hetzner) | Zugang: SSH (User: helio)


1. Zusammenfassung

Der Server hostet drei separate Systeme, nicht eine „Middleware“:

# System Domain Pfad Tech
1 Website starqstrom.de /var/www/starqstrom-production/ Statamic CMS 5.0 + Laravel 12 + Livewire 3.5 + PHP 8.3
2 Middleware middleware.starqstrom.de /var/www/starqstrom-middleware/production/ Pure Laravel 12 + PHP 8.4 + SQLite + Horizon
3 Wattform API wechselprozess.services Extern (SOLVIT) SOLVIT Backend

Zusätzlich existieren Staging-Umgebungen für Website und Middleware.

Drei Schlüsselerkenntnisse

  1. SOLVIT hat bereits eine API („Wattform“) — 7+ Endpoints aktiv in Nutzung
  2. Die eigene Auftragsstrecke existiert bereits — 7-Schritte Livewire Order Flow ersetzt das SOLVIT-Widget
  3. Die Middleware speichert KEINE Kundendaten — nur HubSpot-Sync-Metadaten

2. Laufende Services

Service Version Zweck
nginx - Reverse Proxy + SSL (Let’s Encrypt)
php8.3-fpm 8.3 Website (Statamic)
php8.4-fpm 8.4 Middleware
redis-server - Sessions, Cache, Queues
supervisor - Laravel Horizon (Queue Worker)

3. Website (starqstrom.de)

Tech-Stack

Custom Packages

Routen

GET /bestellung/{channel?}   → Livewire Order Flow (privat/gewerbe)
GET /styleguide              → Design-System-Referenz

Livewire Order Flow (7 Steps)

Step Klasse Felder
1 LocationStep PLZ, Stadt, Straße, Verbrauch (kWh), Zählertyp (KME/MME/IMS), Rabattcode
2 TariffSelectionStep Tarife von Wattform API, Tarifauswahl
3 PersonalDataStep Anrede, Vorname, Nachname, Email, Telefon, Geburtsdatum, Firma (B2B), Ansprechpartner, alt. Rechnungsadresse
4 DeliveryAndMeterStep Zählernummer, Auftragsgrund (Wechsel/Einzug), Wunsch-Lieferbeginn, Vorversorger
5 BankDetailsStep Zahlungsart (SEPA/Selbstzahler), IBAN, Kontoinhaber (IBAN-Validierung via Wattform)
6 SummaryStep Zusammenfassung aller Daten → Submit an Wattform + Middleware
7 SuccessStep Bestätigungsseite mit Auftragsnummer

Enums

Enum Werte
CustomerChannel privat (B2C), gewerbe (B2B)
OrderType Lieferantenwechsel, Einzug
SupplyType Strom
MeterType KME (Analog), MME (Digital/mME), IMS (Smart/iMSys)
PaymentMethod sepa_lastschrift, selbstzahlung
DeliveryStartType (Wechsel vs. Einzug Timing)

Query-Parameter (URL-Prefilling)

Die Auftragsstrecke unterstützt URL-Parameter für Vorausfüllung:

/bestellung/privat?postalCode=10115&personCount=2&discountCode=STARQ10
/bestellung/gewerbe?zip=10115&usage=50000&type=gewerbe

Aliases: zippostalCode, typechannel, usagemanualConsumption


4. Wattform API (SOLVIT)

Konfiguration (aus .env)

WATTFORM_BASE_URL=https://wechselprozess.services/solvreg/resources
WATTFORM_ORIGIN=https://starqstrom.de
WATTFORM_REFERER=https://starqstrom.de/
WATTFORM_DEFAULT_MANDANT=starqstrom

Authentifizierung

Endpoints

# Endpoint Method Parameter Response Caching
1 /tarife/{plz}/{city} GET strasse, stromverbrauch, kanal (privat/gewerbe), waermepumpenoption, selbstzahlerMsb, zaehlertyp, rabattcode { strom: [...tarife] } Nein (dynamisch)
2 /auftraege POST OrderData (JSON, siehe unten) { auftragsnummer: "..." } oder { created: timestamp } N/A
3 /postleitzahlen/{plz} GET PLZ Array von Orten 1h Cache
4 /strassen/{plz}/{city} GET Versorgungsart Array von Straßen 1h Cache
5 Rabattcode-Endpoint POST discountCode true / false (plain text) Nein
6 IBAN-Endpoint POST iban, accountHolder { gueltig: true/false } Nein
7 Versorger-Endpoint POST query, supplyType Array von Versorgern Nein

OrderData (POST /auftraege) — Komplette Datenstruktur

{
  "stammdaten": {
    "anrede": "Herr",
    "titel": null,
    "vorname": "Max",
    "nachname": "Mustermann",
    "geburtsdatum": "1990-01-15",
    "email": "max@example.de",
    "telefon": "030-12345678",
    "mobile": null,
    "firma": null,
    "ansprechpartner": null,
    "fax": null,
    "starqstromKundennummer": null
  },
  "bankverbindung": {
    "kontoinhaber": "Max Mustermann",
    "kontonummer": null,
    "bankleitzahl": null,
    "bankinstitut": null,
    "bic": null,
    "iban": "DE89370400440532013000",
    "einzugErlaubt": true
  },
  "lieferadresse": {
    "anrede": "Herr",
    "titel": null,
    "vorname": "Max",
    "nachname": "Mustermann",
    "land": "DE",
    "ort": "Berlin",
    "postfach": null,
    "postleitzahl": "10115",
    "strasse": "Musterstraße",
    "zusatz": null,
    "hausnummer": "42"
  },
  "rechnungsadresse": { ... },
  "auftraege": {
    "strom": [{
      "art": "Lieferantenwechsel",
      "lieferbeginn": {
        "art": "Wunschtermin",
        "datum": "2026-04-01"
      },
      "geraet": {
        "zaehlernummer": "1EMH0012345678",
        "zaehlertyp": "KME"
      },
      "vorversorger": {
        "name": "Vattenfall",
        "kundennummer": "V-123456"
      },
      "tarif": {
        "tarifName": "STARQ&fair",
        "referenzbezeichner": "...",
        "zaehlertyp": "KME",
        "tarifTyp": "AllInklusive",
        "mindestvertragslaufzeit": 12,
        "mindestvertragslaufzeitEinheit": "Monate",
        "jahresverbrauch": 2500,
        "arbeitspreis": 0.2890,
        "grundpreis": 9.95,
        "arbeitspreisBrutto": 0.3439,
        "grundpreisBrutto": 11.84
      },
      "objekttyp": null,
      "organisationstyp": null,
      "nettopreiseKommuniziert": false
    }]
  },
  "werbungOk": false,
  "rabattcode": null,
  "werbenderKunde": null,
  "kundennummer": null
}

Tarif-Response (GET /tarife) — Wichtige Felder


5. Middleware (middleware.starqstrom.de)

Tech-Stack

Konfiguration (aus .env)

MIDDLEWARE_BASE_URL=https://middleware.starqstrom.de
HUBSPOT_ACCESS_TOKEN=pat-eu1-030dffea-...
HUBSPOT_PORTAL_ID=146570641
HUBSPOT_BASE_CONTACT_FORM_GUID=19cc30f7-fb65-4798-883e-99d8e4983131
HUBSPOT_API_TOKEN=40415d7d... (für eingehende Requests von Website)
HUBSPOT_B2B_PIPELINE_ID=2784063729
HUBSPOT_NEW_LEAD_STAGE_ID=3818694860

API-Endpunkte

Endpoint Method Auth Zweck
POST /api/hubspot/orders/submit POST X-HubSpot-API-Key Header SLP-Bestellung empfangen → async ProcessOrderJob
POST /api/hubspot/rlm-leads/submit POST (multipart) X-HubSpot-API-Key Header RLM-Lead + Dateianhänge → sync Verarbeitung
GET / GET - Health Check (name, status, version, environment)

Datenbank-Schema (SQLite)

contacts:

Feld Typ Zweck
id INTEGER Primary Key
hashed_id VARCHAR MD5 von Email (Deduplizierung)
external_id VARCHAR Externe Referenz
source VARCHAR „Website“
email VARCHAR Einziges PII-Feld!
company_id INTEGER FK zu companies
hubspot_id VARCHAR HubSpot Object ID
sync_status VARCHAR pending/syncing/synced/failed
synced_at DATETIME Letzter erfolgreicher Sync
sync_attempts INTEGER Anzahl Versuche
sync_error TEXT Letzte Fehlermeldung

companies:

id, hashed_id, external_id, source, hubspot_id, sync_status, synced_at, sync_attempts, sync_error

deals:

id, hashed_id, external_id, source, title, contact_id, company_id, hubspot_id, sync_status, synced_at, sync_attempts, sync_error

Order Processing Flow

POST /api/hubspot/orders/submit
  ↓ Validierung (orderReference, customerChannel, notificationData)
  ↓ Dispatch ProcessOrderJob (async, Redis Queue "orders")
  ↓
ProcessOrderJob:
  ↓ OrderDataMapper.mapOrderData() → { contact, company, deal }
  ↓
  └&horz;&horz; DirectSyncService.syncContact()
  │   └&horz;&horz; Prüfe ob Contact existiert (hashed_id = MD5(email))
  │   └&horz;&horz; Marketing Consent? → Forms API (GDPR/DOI)
  │   └&horz;&horz; Kein Consent? → Contacts API (Upsert by email)
  │
  └&horz;&horz; DirectSyncService.syncCompany() [nur B2B]
  │   └&horz;&horz; hashed_id = MD5(company_name) ← PROBLEM: Kein Upsert!
  │   └&horz;&horz; Immer CREATE → Company-Duplikate in HubSpot
  │
  └&horz;&horz; DirectSyncService.syncDeal()
  │   └&horz;&horz; hashed_id = MD5(deal_title)
  │   └&horz;&horz; Upsert + 30+ Properties
  │
  └&horz;&horz; Associations erstellen
      └&horz;&horz; Contact ↔ Company (Type 1)
      └&horz;&horz; Deal ↔ Contact (Type 3)
      └&horz;&horz; Deal ↔ Company (Type 5)

HubSpot Property Mappings

Contact → HubSpot

Internes Feld HubSpot Property Wert/Mapping
salutation salutation Herr/Frau/Firma
firstname firstname
lastname lastname
email email Unique Identifier
phone phone
street + street_nr address Formatiert: „Musterstr. 42“
postal_code zip
city city
country country „Deutschland“
lifecycle_stage lifecyclestage „opportunity“ (statisch)
lead_status hs_lead_status „Qualifiziert“ (statisch)
- kontaktquelle „Website“ (Custom)

Deal → HubSpot (30+ Properties)

Internes Feld HubSpot Property Wert/Mapping
title dealname „{Tarif} - {Kundenname}“
pipeline pipeline B2B: 2784063729, B2C: default
dealstage dealstage B2B: 3818694864, B2C: 2743940327 („Auftrag gewonnen“)
amount amount gesamtpreisBrutto oder abschlag×12
closedate closedate Lieferbeginn-Datum
delivery_start delivery_start Lieferbeginn
discount_approval discount_approval Rabattcode
- jahresverbrauch__kwh_ kWh/Jahr
- netto_arbeitspreis ct/kWh netto
- grundpreis EUR/Monat netto
- tarif Tarifname
- mindestvertragslaufzeit_in_monaten Monate
- zahlernummer Zählernummer
- zahlerart „slp“ (statisch)
- zahlertyp KME→„Analog“, MME→„Digital“, IMS→„Smart“
- zahlungsart sepalastschrift / selbstzahlung_uberweisung
- kontoinhaber
- iban
- lieferadresse_strae Straße
- lieferadresse_hausnr Hausnummer
- lieferadresse_hausnummernzusatz
- lieferadresse_plz PLZ
- lieferadresse_stadt Stadt
- auftragsgrund Wechsel / Einzug
- rechnungsadresse__stra_e_
- rechnungsadresse__hausnr__
- rechnungsadresse__hausnummernzusatz_
- rechnungsadresse__plz_
- rechnungsadresse__stadt_
- kontaktquelle „Website“
- kundentyp

Company → HubSpot

Internes Feld HubSpot Property
name name
email → domain domain
email e_mail_company
phone phone
street + street_nr address
postal_code zip
city city
country country

6. Bekannte Probleme

Company-Duplikate (bestätigt im Code)

// DirectSyncService.php:
// NOTE: We always create a new company instead of upserting by domain.
// Reason: The 'domain' property is not configured as unique in HubSpot
// TODO: Consider configuring 'domain' as unique in HubSpot to enable deduplication.

Impact: Jede B2B-Bestellung erstellt eine neue Company in HubSpot.

Lösung für STARQ Platform: Domain als unique Property in HubSpot konfigurieren + E-Mail-Domain-basiertes Upsert.

Fehlende Rückmeldung von SOLVIT

Die Wattform API hat kein Webhook/Callback-System. Die Website schickt Bestellungen ab, bekommt eine auftragsnummer zurück — aber danach gibt es keine Statusupdates (Klärfälle, Ablehnungen, Vertragsstatus).

Kein RLM über Wattform

RLM-Leads werden nur an die Middleware geschickt (mit Dateianhängen), NICHT an die Wattform API. Der B2B-Flow geht direkt in HubSpot — es gibt keinen automatisierten SOLVIT-Weg für RLM.


7. Dateipfade (Referenz)

Website (/var/www/starqstrom-production/current/)

app/
  Http/Integrations/
    Wattform/
      WattformConnector.php          ← Saloon HTTP Connector
      Requests/
        GetTariffs.php               ← GET /tarife/{plz}/{city}
        SubmitOrder.php              ← POST /auftraege
        GetPostalCodes.php           ← PLZ-Validierung
        GetStreets.php               ← Straßen-Lookup
        ValidateDiscountCode.php     ← Rabattcode prüfen
        ValidateIban.php             ← IBAN validieren
        SearchSupplier.php           ← Vorversorger suchen
      DataTransferObjects/
        OrderData.php                ← Komplette Bestelldaten
        TariffData.php               ← Tarif-Struktur
        TariffRequestData.php        ← Tarif-Abfrage-Parameter
        StammdatenData.php           ← Persönliche Daten
        BankverbindungData.php       ← SEPA-Daten
        StromAuftragData.php         ← Strom-Auftrag
        AddressData.php              ← Adresse
        LieferbeginnData.php         ← Lieferbeginn
        GeraetData.php               ← Zähler-Daten
        VorversorgerData.php         ← Vorversorger
    Middleware/
      MiddlewareConnector.php        ← HTTP Connector zur Middleware
      Requests/
        SubmitOrderToMiddleware.php   ← POST /api/hubspot/orders/submit
        SubmitRlmLeadToMiddleware.php ← POST /api/hubspot/rlm-leads/submit
  Livewire/OrderFlow/
    OrderFlowController.php          ← Haupt-Controller (Session, Steps, Navigation)
    LocationStep.php                 ← Step 1: Standort
    TariffSelectionStep.php          ← Step 2: Tarifauswahl
    PersonalDataStep.php             ← Step 3: Persönliche Daten
    DeliveryAndMeterStep.php         ← Step 4: Zähler & Lieferbeginn
    BankDetailsStep.php              ← Step 5: Bankverbindung
    SummaryStep.php                  ← Step 6: Zusammenfassung & Submit
    SuccessStep.php                  ← Step 7: Bestätigung
  Services/
    OrderService.php                 ← Wattform API Orchestrierung
    ProductTariffMatcher.php         ← Tarif-Matching
  Enums/
    CustomerChannel.php              ← privat/gewerbe
    OrderType.php                    ← Lieferantenwechsel/Einzug
    MeterType.php                    ← KME/MME/IMS
    PaymentMethod.php                ← SEPA/Selbstzahlung
    SupplyType.php                   ← Strom

Middleware (/var/www/starqstrom-middleware/production/current/)

app/
  Http/Controllers/Api/HubSpot/
    OrderController.php              ← POST /api/hubspot/orders/submit
    RlmLeadsController.php           ← POST /api/hubspot/rlm-leads/submit
  Jobs/
    ProcessOrderJob.php              ← Async Order-Verarbeitung
    SyncContactToHubSpotJob.php
    SyncCompanyToHubSpotJob.php
    SyncDealToHubSpotJob.php
  Models/
    Contact.php                      ← Sync-Metadaten
    Company.php                      ← Sync-Metadaten
    Deal.php                         ← Sync-Metadaten
  Services/HubSpot/
    DirectSyncService.php            ← Direkter HubSpot-Sync (kein Queue)
    HubSpotFormsService.php          ← Forms API (für Marketing Consent / GDPR)
    HubSpotFileUploadService.php     ← Datei-Upload nach HubSpot
    Mappers/
      OrderDataMapper.php            ← Order → Contact + Company + Deal
      ContactFieldMapper.php         ← Internes Format → HubSpot Properties
      DealFieldMapper.php            ← Internes Format → HubSpot Properties
      CompanyFieldMapper.php         ← Internes Format → HubSpot Properties
      RlmLeadDataMapper.php          ← RLM-Lead Mapping
    RlmLeadService.php               ← RLM-Lead Verarbeitung
  Services/Order/
    OrderProcessingService.php       ← Order Pipeline

8. Implikationen für STARQ Platform (Rails)

Was muss portiert werden

  1. Wattform API Client — Saloon Connector → Ruby HTTP Client (Faraday/HTTParty)
  2. HubSpot Sync — DirectSyncService + Mappers → Rails Service Objects
  3. Order Processing — ProcessOrderJob → Sidekiq Job
  4. Contact/Company/Deal Models — SQLite → PostgreSQL
  5. Field Mappings — 30+ Deal-Properties, Contact, Company (exakte Property-Names!)

Was NICHT portiert werden muss

  1. Statamic CMS — Website bleibt bestehen, sendet nur an neue API
  2. Livewire Order Flow — bleibt in Statamic, nur API-Ziel ändert sich
  3. Deployer — wird durch Kamal ersetzt

Neue Features (nicht im Bestandssystem)

  1. SOLVIT Proxy — Wattform-Calls über STARQ Platform (Transparenz, Logging)
  2. Company Dedup — Domain als unique + Fuzzy-Matching
  3. Webhook-System — Statusupdates von SOLVIT empfangen
  4. RLM über Wattform — Automatisierter B2B-Flow
  5. Partner Portal — Dashboard, Provisionen
  6. Pricing Engine (Phase 3) — Eigene Preisberechnung