Bestell-Endpunkt (Orders)

Endpoint

Orders v2.32.0

Bestellungen: Export, Import (POST/PUT) und Delete via generischem OrderBuilder-Service.


Übersicht

OperationMethodEndpointScope
ExportGET/xpanel/xoport/Export/JSON/ordersorders:read
CreatePOST/xpanel/xoport/Import/JSON/ordersorders:write
UpdatePUT/xpanel/xoport/Import/JSON/ordersorders:write
DeleteDELETE/xpanel/xoport/Delete/JSON/ordersorders:write

Hinweis: Import/Delete erfordern orders:write. Lagerbuchung erfolgt automatisch beim Create. Bestehende Audit-Logs (Status-History) bleiben revisionssicher erhalten.


Import (POST / PUT)

Erstellt oder aktualisiert eine Bestellung. POST = Create, PUT mit orders_id = Update. Alle Felder werden über den zentralen OrderBuilder-Service validiert und persistiert.

Minimal-Payload (Create)

{
  "data": [{
    "customers_id": 51,
    "payment_method": "PayPal",
    "products": [
      {"products_id": 42, "quantity": 2, "products_price": "29.99"}
    ]
  }]
}

Voll-Payload mit allen Optionen

{
  "data": [{
    "customers_id": 51,
    "language_id": 2,
    "payment_method": "Stripe",
    "shipping_method": "DHL",
    "tax_flag": 1,
    "totals_mode": "auto",
    "expected_total": "59.98",
    "strict_totals": true,
    "currency": "EUR",
    "products": [
      {
        "products_id": 42,
        "quantity": 2,
        "products_price": "29.99",
        "products_tax": "19.00",
        "attributes": [
          {"option_id": 1, "value_id": 5}
        ]
      }
    ],
    "external_reference": {
      "source": "amazon",
      "external_id": "AMZ-12345"
    },
    "tracking": [
      {"service_provider": "DHL", "code": "1234567890", "link": "https://nolp.dhl.de/?piececode=1234567890"}
    ]
  }]
}

Totals-Modi (v2.21.0)

Über totals_mode wird gesteuert, wie die Bestellsummen berechnet werden:

ModusVerhaltenVerwendung
auto DefaultKeine totals-Werte im Payload → Shop rechnet komplett neu. Mit totals-Werten → diese werden übernommen.Standardfall: Caller hat keine Totals, Shop berechnet sie.
recalcLädt products_price + products_tax frisch aus der DB (Kundengruppen-Preis + Specials + Kategorie-/Kundenrabatt, Land-aware) und baut Subtotal/Tax/Total neu. Zusätzlich werden EK (products_ek_price) und GTIN nachgeladen. Manuelle Positionen (products_id=0) bleiben unangetastet.Sichere Re-Berechnung erzwingen; Dropship-Parität zum Checkout.
verbatimShop übernimmt die mitgesendeten totals-Werte 1:1, OHNE Neuberechnung.Marktplatz-Importe mit feststehender Total-Summe (Amazon, Otto, eBay).

tax_flag Auto-Derive & Preis-Interpretation

Wenn tax_flag nicht explizit gesetzt ist, wird es aus der Kundengruppe abgeleitet:

  • customers_groups.show_tax = 1 + DISPLAY_PRICE_WITH_TAX = true → tax_flag = 1 (Brutto)
  • customers_groups.show_tax = 0 → tax_flag = 0 (Netto)
  • Steuerbefreite Gruppen (z.B. EU-USt-ID) → tax_flag = 2 (Steuerfrei)

Interpretation von products_price je nach tax_flag

tax_flagproducts_price istot_subtotalot_taxot_total
0 (Netto)nettoΣ(netto × qty)Σ(netto × rate/100), addiertSubtotal + Shipping + Tax
1 (Brutto)bruttoΣ(brutto × qty)informativ („inkl. MwSt.")Subtotal + Shipping (Tax NICHT addiert)
2 (Steuerfrei)netto = bruttoΣ(price × qty)0Subtotal + Shipping

⚠️ Wichtig für Marktplatz-Importe: Wenn der Marketplace Netto-Preise schickt, MUSS tax_flag: 0 explizit gesetzt werden — sonst werden die Preise (bei einer Brutto-Kundengruppe) als Brutto interpretiert.


language_id Auto-Derive (v2.21.0)

Wenn language_id nicht im Payload steht, wird kaskadiert aufgelöst:

  1. Payload-Wert (wenn gesetzt)
  2. customers.language_id aus dem Kundenstamm
  3. DEFAULT_LANGUAGE aus der Shop-Konfiguration (Code-Lookup in languages-Tabelle)
  4. Fallback: 1 (Englisch)

Hinweis: $_SESSION['languages_id'] ist im xoPort-Kontext bewusst KEIN Fallback — sonst würde die Sprache des Admin-API-Callers ungewollt auf die Kundenbestellung übertragen werden.


Drift-Check: expected_total + strict_totals

Optional kann der Caller den erwarteten Gesamtbetrag mitschicken — der Shop vergleicht und schlägt bei Abweichung Alarm:

  • expected_total (decimal): Erwartete ot_total-Summe nach Berechnung.
  • strict_totals (boolean, Default false):
    • false → Drift wird als Warnung in der Response ausgegeben, Bestellung wird trotzdem erstellt.
    • true → Bestellung wird abgelehnt, wenn berechneter Total ≠ expected_total (Toleranz: 0,01 €).

Verwendung: Marktplatz-Importe, wo der Marketplace die finale Summe schon kennt — Shop validiert, dass Preis × Menge × Steuer + Versand auch lokal die gleiche Summe ergeben.


Komfort-Felder & Checkout-Parität (v2.28.0–v2.32.0)

Für Anbindungen, die nur Kundennummer, abweichende Lieferadresse und SKU/Menge kennen — die Bestellung wird so angelegt, dass sie Preise, Merkmale, Versand und Bestätigung exakt wie eine manuelle Shop-Bestellung trägt (Dropship-Parität, z. B. SPV → SST).

Feld / VerhaltenTypBeschreibung
Adress-Auto-BefüllungautoBei customers_id > 0 und fehlendem/unvollständigem billing wird die Standard-Rechnungsadresse des Kunden geladen. Explizit gelieferte Felder gewinnen. delivery fällt bei fehlenden Pflichtfeldern auf billing zurück. v2.28.0
SKU-/products_model-AuflösungautoPositionen können nur {products_model, quantity} liefern — products_id und Name werden aus der Shop-DB aufgelöst. Unbekanntes Modell → Warnung PRODUCT_MODEL_NOT_FOUND. v2.28.0
apply_default_attributesbool (Default false)Zieht je Position ohne eigene attributes die vorausgewählten Optionswerte inkl. signiertem Aufpreis — auch Anzeige-Merkmale (Options-Typ 5, z. B. „Ausführung“/„Schutzart“), wie der Checkout sie mitführt. v2.28.0 / v2.30.0
compute_shippingbool (Default false)Ohne explizite totals.shipping wird der günstigste Versand „headless“ über den Frontend-Versand-Stack (shipping::cheapest()) für die Lieferadresse berechnet und als ot_shipping geschrieben. Best-effort: Fehler → Warnung SHIPPING_AUTO_FAILED / SHIPPING_AUTO_EMPTY, die Bestellung bleibt erhalten. v2.30.0
send_confirmation_emailbool (Default false)Nach erfolgreichem Anlegen wird die Standard-Bestellbestätigung an den Besteller versendet (inkl. Kopien aus SEND_EXTRA_ORDER_EMAILS_TO). Ergebnis als confirmation_email_sent (true/false) im Datensatz. v2.29.0
Kategorie-/Kundenrabatt im recalcautoDer recalc-Modus wendet zusätzlich zu Gruppenpreis und Specials den Kategorie-/Kundenrabatt (customers_discount + customers_groups_discount, inkl. Subkategorie-Vererbung) an — der günstigere effektive Preis gewinnt. Problemfall → Warnung CATEGORY_DISCOUNT_UNAVAILABLE. v2.31.0
additional_addressstringAdresszusatz pro Adressblock — wird nach customers_additional_address, delivery_additional_address und billing_additional_address übernommen. v2.32.0

Steuerzeile pro Satz (v2.32.0): ot_tax wird je Steuersatz mit rate-genauem Titel im Frontend-Format geschrieben — „zzgl. USt. 19 %“ (Netto) bzw. „inkl. USt. 19 %“ (Brutto), sofern MODULE_ORDER_TOTAL_PREFIX=true.


Sub-Resources

external_reference — Marktplatz-Anbindung

Verknüpft die Bestellung mit einer externen Quelle. Erlaubte Felder:

  • source (string, required): z.B. amazon, ebay, otto, kaufland.
  • external_id (string, required): ID/Nummer beim Marketplace.
  • marketplace_id (int, optional): Interne XONIC-Marketplace-ID.

tracking — Sendungsverfolgung

Array von Tracking-Einträgen. Pro Eintrag erlaubt:

  • service_provider (alias: method): Carrier-Name (DHL, DPD, UPS, …).
  • code (alias: tracking_number): Sendungsnummer.
  • link: Tracking-URL.
  • label_link: Versandlabel-URL (PDF).
  • external_id: ID beim Versanddienstleister.
  • is_retoure (bool): Retoure-Sendung statt Ausgangssendung.

status_history — Status-Verlauf

Append-only Audit-Log. Pro Eintrag: orders_status_id, comments, customer_notified (0/1), date_added.

customer_notified auf Order-Ebene

Boolean, kontrolliert ob beim Status-Wechsel der Bestätigungs-Mail-Versand getriggert wird.


Beispiel-Request (Create)

curl -X POST "https://shop.de/xpanel/xoport/Import/JSON/orders" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": [{
      "customers_id": 51,
      "payment_method": "PayPal",
      "tax_flag": 0,
      "expected_total": "71.38",
      "products": [{"products_id": 42, "quantity": 2, "products_price": "29.99", "products_tax": "19.00"}]
    }]
  }'

Beispiel-Response (Success)

{
  "success": true,
  "api_version": "2.32.0",
  "type": "orders",
  "operation": "create",
  "data": [{
    "orders_id": 46482,
    "customers_id": 51,
    "ot_subtotal": "59.98",
    "ot_tax": "11.40",
    "ot_total": "71.38",
    "totals_mode": "auto",
    "tax_flag": 0,
    "language_id": 2
  }],
  "warnings": []
}

Beispiel-Response (Drift-Check fehlgeschlagen, strict_totals=true)

{
  "success": false,
  "api_version": "2.32.0",
  "error": {
    "code": "TOTAL_DRIFT",
    "message": "Calculated ot_total 71.38 differs from expected_total 70.00 (diff: 1.38).",
    "details": {"expected": "70.00", "calculated": "71.38", "tolerance": "0.01"}
  }
}

Fehlercodes

CodeBedeutung
INVALID_PAYLOADPflichtfelder fehlen oder Datentyp falsch.
CUSTOMER_NOT_FOUNDcustomers_id existiert nicht.
PRODUCT_NOT_FOUNDproducts_id existiert nicht.
TOTAL_DRIFTexpected_total ≠ berechneter Total und strict_totals=true.
STOCK_INSUFFICIENTLagerbestand reicht nicht (wenn auto_stock=true).
DUPLICATE_EXTERNAL_REFERENCEKombination source + external_id existiert bereits.

Nicht-fatale Warnungen (Bestellung wird trotzdem angelegt, Rückgabe in warnings[]): PRODUCT_MODEL_NOT_FOUND, RECALC_PRODUCT_NOT_FOUND, CATEGORY_DISCOUNT_UNAVAILABLE, SHIPPING_AUTO_FAILED, SHIPPING_AUTO_EMPTY, TOTAL_DRIFT (außer bei strict_totals=true), UNKNOWN_FIELD.


Export (GET)

Bestellungen lesen — Query-Parameter:

ParameterTypBeschreibung
orders_idintEinzelne Bestellung
customers_idintNach Kunden filtern
orders_statusintNach Status filtern
date_from / date_todateBestelldatums-Bereich (YYYY-MM-DD)
limit / offsetintPagination (Default: 100)