Bestell-Endpunkt (Orders)
Orders v2.32.0
Bestellungen: Export, Import (POST/PUT) und Delete via generischem OrderBuilder-Service.
Übersicht
| Operation | Method | Endpoint | Scope |
|---|---|---|---|
| Export | GET | /xpanel/xoport/Export/JSON/orders | orders:read |
| Create | POST | /xpanel/xoport/Import/JSON/orders | orders:write |
| Update | PUT | /xpanel/xoport/Import/JSON/orders | orders:write |
| Delete | DELETE | /xpanel/xoport/Delete/JSON/orders | orders: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:
| Modus | Verhalten | Verwendung |
|---|---|---|
auto Default | Keine totals-Werte im Payload → Shop rechnet komplett neu. Mit totals-Werten → diese werden übernommen. | Standardfall: Caller hat keine Totals, Shop berechnet sie. |
recalc | Lä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. |
verbatim | Shop ü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_flag | products_price ist | ot_subtotal | ot_tax | ot_total |
|---|---|---|---|---|
0 (Netto) | netto | Σ(netto × qty) | Σ(netto × rate/100), addiert | Subtotal + Shipping + Tax |
1 (Brutto) | brutto | Σ(brutto × qty) | informativ („inkl. MwSt.") | Subtotal + Shipping (Tax NICHT addiert) |
2 (Steuerfrei) | netto = brutto | Σ(price × qty) | 0 | Subtotal + 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:
- Payload-Wert (wenn gesetzt)
customers.language_idaus dem KundenstammDEFAULT_LANGUAGEaus der Shop-Konfiguration (Code-Lookup inlanguages-Tabelle)- 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): Erwarteteot_total-Summe nach Berechnung.strict_totals(boolean, Defaultfalse):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 / Verhalten | Typ | Beschreibung |
|---|---|---|
| Adress-Auto-Befüllung | auto | Bei 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ösung | auto | Positionen 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_attributes | bool (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_shipping | bool (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_email | bool (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 recalc | auto | Der 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_address | string | Adresszusatz 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
| Code | Bedeutung |
|---|---|
INVALID_PAYLOAD | Pflichtfelder fehlen oder Datentyp falsch. |
CUSTOMER_NOT_FOUND | customers_id existiert nicht. |
PRODUCT_NOT_FOUND | products_id existiert nicht. |
TOTAL_DRIFT | expected_total ≠ berechneter Total und strict_totals=true. |
STOCK_INSUFFICIENT | Lagerbestand reicht nicht (wenn auto_stock=true). |
DUPLICATE_EXTERNAL_REFERENCE | Kombination 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:
| Parameter | Typ | Beschreibung |
|---|---|---|
orders_id | int | Einzelne Bestellung |
customers_id | int | Nach Kunden filtern |
orders_status | int | Nach Status filtern |
date_from / date_to | date | Bestelldatums-Bereich (YYYY-MM-DD) |
limit / offset | int | Pagination (Default: 100) |