# Create / easy.affiliate REST-API (Baskets)

## 📝 Einleitung

Um neue Orders **inklusive Warenkorb** automatisiert im easy.affiliate-System anzulegen, steht ein REST-konformer Webservice zur Verfügung. Über die HTTP-Methode `POST` wird pro Aufruf eine Bestellung mit ihren Warenkorb-Positionen übermittelt.

Die Bestellung durchläuft dabei dieselbe Logik wie ein getrackter Verkauf mit Warenkorb (`/trck/ebasket/`): Publisher-Zuordnung, Provision je Position (Regeln nach Artikel, Marke, Kategorie oder Default des Triggers), Bestellwert sowie serverseitige Tags und Postbacks der Kampagne. **Bestellwert, Provision und Status werden immer vom System berechnet** und können nicht übergeben werden.

Jeder User erhält hierfür einen Authentifizierungs-Token und eine Login-ID, die im Frontend einsehbar sind.

## 🔐 Authentifizierung

Für den Zugriff sind folgende Header erforderlich:

| Header | Beschreibung | Typ |
|---|---|---|
| `Content-Type` | Muss auf `application/json` gesetzt sein | String |
| `X-Network-ID` | Netzwerk-ID des Mandanten (muss > 0 sein, **nicht** `-1`). Die Kampagne muss zu diesem Mandanten gehören. | Integer |
| `X-Auth-Token` | Dein API-Token (im Frontend sichtbar) | String |
| `X-Auth-ID` | Deine Login-ID (im Frontend sichtbar) | Integer |

**Berechtigung:** Nur Admin-Logins mit Lese- und Schreibrecht auf die Bestellungen (`administration_orders`). Der Login muss zum Mandanten aus `X-Network-ID` gehören.

## 📩 Endpunkte

- **Admin:** `https://SUBDOMAIN.de/ws/V6/admin/REST/Baskets`

## 🧪 Beispiel (cURL)

```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Network-ID: 1" \
  -H "X-Auth-Token: ADMIN_APIUSER_TOKEN" \
  -H "X-Auth-ID: ADMIN_APIUSER_LOGIN_ID" \
  -d '{
    "campaign_id": 123,
    "trigger_id": 5,
    "ordertoken": "E-10045",
    "emid": "5f0123456789abcdef012345",
    "basket": [
      {"article_number": "A-1", "productname": "Hemd", "category": "Herren", "amount": 2, "price": 19.90, "additional": {"brand": "X"}},
      {"article_number": "B-7", "amount": 1, "price": 5}
    ]
  }' \
  https://SUBDOMAIN.de/ws/V6/admin/REST/Baskets
```

## 🧾 Body-Parameter (Order)

| Parameter | Beschreibung | Datentyp | Pflichtfeld |
|---|---|---|---|
| `campaign_id` | ID der Kampagne, der die Order zugeordnet wird. | Integer | ✅ Ja |
| `trigger_id` | ID eines Triggers dieser Kampagne (z. B. Sale-Trigger). | Integer | ✅ Ja |
| `ordertoken` | Eindeutiger Identifier für die Order (eigene Bestellnummer). 1–255 druckbare ASCII-Zeichen, ohne Leerzeichen und ohne `(` `)` `;`. | String | ✅ Ja |
| `basket` | Liste der Warenkorb-Positionen (min. 1, max. 200), siehe unten. | Array | ✅ Ja |
| `emid` | Klick-/Action-ID aus dem Tracking (24-stellig). Muss zu einem Klick dieser Kampagne gehören. | String | Nein* |
| `project_id` | Direkte Zuordnung per Projekt (Publisher). Das Projekt muss mit der Kampagne verbunden sein. | Integer | Nein* |
| `currency` | Bestellwährung als 3-stelliger ISO-Code (z. B. EUR, CHF). Default = Kampagnen-Einstellung, sonst EUR. Unbekannte Währungen werden abgelehnt. | String | Nein |
| `trigger_tstamp` | Bestellzeitpunkt als Unix-Timestamp (Sekunden). Fehlt der Wert, wird die aktuelle Zeit genutzt. | Integer | Nein |
| `descr` | Freitext-Beschreibung der Order (max. 255 Zeichen). | String | Nein |

\* **Publisher-Zuordnung:** Es darf **entweder** `emid` **oder** `project_id` übergeben werden, nicht beides. Beide werden vorab geprüft: Eine unbekannte/abgelaufene `emid`, eine `emid` einer anderen Kampagne oder ein nicht verbundenes Projekt wird abgelehnt, statt die Order still dem Systempartner zuzuordnen. Ohne beide Angaben entscheidet die Kampagne, ob die Order an den Systempartner geht (Antwort enthält dann `notice: NO_SESSION_FOUND`).

**Nicht erlaubt:** `turnover`, `commission`, `status` und alle weiteren, nicht aufgeführten Felder werden mit einem Fehler abgelehnt.

## 🛒 Body-Parameter (Warenkorb-Position in `basket`)

| Parameter | Beschreibung | Datentyp | Pflichtfeld |
|---|---|---|---|
| `article_number` | Artikelnummer / SKU (1–255 Zeichen). Provisionsregeln nach Artikel greifen darauf. | String | ✅ Ja |
| `amount` | Menge, ganze Zahl ≥ 1. | Integer | ✅ Ja |
| `price` | Preis **einer** Einheit, ≥ 0, **nur Punkt** als Dezimaltrenner (z. B. `19.90`), max. 4 Nachkommastellen. Bei Brutto-Tracking der Kampagne wird die MwSt. wie beim Tracking abgezogen. | Float | ✅ Ja |
| `productname` | Produktname (max. 255 Zeichen). | String | Nein |
| `category` | Kategorie für Provisionsregeln nach Kategorie. Leer = Artikelnummer. | String | Nein |
| `additional` | Objekt `{ "key": "value", … }` mit max. 50 flachen Zusatzfeldern, wird an der Position gespeichert. `additional.brand` wird für Provisionsregeln nach Marke genutzt. Keys: 1–50 Zeichen aus `A-Z a-z 0-9 _ -`; reservierte Keys (z. B. `currency`, `emid`, `campaign_id`, `commission`, `turnover`) sind nicht erlaubt. | Object | Nein |

Der Bestellwert der Order ergibt sich aus der Summe `price × amount` aller Positionen, die Provision aus der Summe der Positions-Provisionen.

**Dublettenschutz:** Dieselbe Kombination aus `campaign_id` + `trigger_id` + `ordertoken` wird abgelehnt (`ORDER_EXISTS`), die Antwort enthält die `order_id` der vorhandenen Order. Ein erneuter Aufruf, z. B. nach einem Timeout, ist dadurch gefahrlos möglich.

## 📤 Antwort

**Erfolg:**

```json
{
  "order_inserted": true,
  "order_id": 4711815,
  "emid": "5f0123456789abcdef012345",
  "sessionid": "5f0123456789abcdef000001",
  "basket": [
    {"id": 981234, "productid": "A-1", "productname": "Hemd"},
    {"id": 981235, "productid": "B-7", "productname": ""}
  ]
}
```

**Fehler:** `order_inserted: false`, `order_id: -1`, dazu `error` und `error_description`. Alle Eingabefehler werden gesammelt in einer Antwort geliefert.

| Error | Bedeutung |
|---|---|
| `INVALID_REQUEST` | Eingabe ungültig (Pflichtfeld fehlt, Format falsch, unbekanntes Feld, Trigger gehört nicht zur Kampagne, `emid`/`project_id` passt nicht, Währung unbekannt). Details in `error_description`. |
| `ORDER_EXISTS` | Order mit diesem `ordertoken` existiert bereits für Kampagne und Trigger; `order_id` enthält die vorhandene Order. |
| `TRACKING_UNAVAILABLE` | Das Tracking hat nicht geantwortet, die Order wurde vermutlich nicht angelegt. Erneut senden (Dublettenschutz greift). |
| sonstige | Ablehnung durch die Tracking-Logik bzw. Kampagnen-Einstellungen. |

## 📘 Hinweise

**Wichtige Implementierungsdetails:**

- **Ein Aufruf = eine Order:** Der Body ist ein einzelnes JSON-Objekt, kein Array. Mehrere Orders werden mit mehreren Aufrufen angelegt.
- **ordertoken:** Muss je Kampagne und Trigger eindeutig sein – idealerweise die externe Bestellnummer des Shops.
- **trigger_id:** Wenn du diese nicht kennst, frage im Frontend deine Kampagnenkonfiguration ab.
- **Preise:** Immer mit Punkt als Dezimaltrenner übergeben (`12.50`, nicht `12,50`).