# Lagerung und Versand

Waren ins Lager einbuchen und anschließend auslagern: Konfiguration → Preis anfragen → Lagerauftrag anlegen → bezahlen → noch vorrätige Positionen listen → Auslagerung schätzen → Auslagerung anlegen → bezahlen → tracken → stornieren.

Diese Anleitung dokumentiert ausschließlich die folgenden Operationen. Führen Sie sie der Reihe nach aus. Die Authentifizierung erfolgt über ein **Kundenkonto**.

Ersetzen Sie `YOUR_HOST` und `ACCESS_TOKEN` durch die Werte Ihrer Umgebung.

## 1. Anmelden (Kunde)

```bash
curl -X POST https://YOUR_HOST/api/v1/user/customer/login \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"your_password"}'
```

```
Authorization: Bearer ACCESS_TOKEN
```

GraphQL nutzt denselben Header auf `POST /api/graphql`.

**Überprüfung:** Login liefert `access_token`. Ein späterer Aufruf ohne Token liefert `401`.

## 2. Lagerkonfiguration

**REST:** `GET /api/v1/customer/storage-orders/config` — [REST-Handbuch](/api/documentation#/paths/v1-customer-storage-orders-config/get)

```bash
curl https://YOUR_HOST/api/v1/customer/storage-orders/config \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

**GraphQL:** `customerStorageOrderConfig` ([GraphQL-Handbuch](/api/graphql/documentation#/customer/customerStorageOrderConfig))

**Überprüfung:** Sie haben eine Lager-`id` erfasst und, wenn der Katalog nicht leer ist, eine Verpackungs-`id`.

## 3. Preis für die Lagerung anfragen

**REST:** `POST /api/v1/customer/storage-orders/calculate-price`

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders/calculate-price \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "warehouse_id": 7,
    "start_date": "2026-09-10",
    "end_date": "2026-10-10",
    "items": [{
      "qty": 1,
      "length": 30,
      "width": 20,
      "height": 15,
      "dimension_unit": 2,
      "weight": 2,
      "weight_unit": 2
    }]
  }'
```

**Überprüfung:** `success` oder `result` ist true und Sie haben einen Preis. Fehlende `warehouse_id` / Daten ergeben `400`.

## 4. Lagerauftrag anlegen

**REST:** `POST /api/v1/customer/storage-orders`

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-store-001" \
  -d '{
    "warehouse_id": 7,
    "start_date": "2026-09-10",
    "end_date": "2026-10-10",
    "items": [{
      "description": "Box A",
      "qty": 1,
      "length": 30,
      "width": 20,
      "height": 15,
      "dimension_unit": 2,
      "weight": 2,
      "weight_unit": 2,
      "value": 100
    }]
  }'
```

**Überprüfung:** die Antwort hat `data.id`. Speichern Sie diese Lagerauftrags-ID.

## 5. Lagerung bezahlen

**REST:** `POST /api/v1/customer/storage-orders/{id}/pay`

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders/1024/pay \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

Optional: zuerst `GET /api/v1/customer/storage-orders/{id}/payment-info`.

**REST:** `GET /api/v1/customer/storage-orders/{id}` — [REST-Handbuch](/api/documentation#/paths/v1-customer-storage-orders-id/get)

**GraphQL:** `customerStorageOrderShow` ([GraphQL-Handbuch](/api/graphql/documentation#/customer/customerStorageOrderShow))

**Überprüfung:** der Lagerauftrag ist bezahlt / bestätigt. `402` bedeutet: Guthaben aufladen, dann erneut versuchen.

Die Auslagerung darunter funktioniert erst, wenn die Pakete im Lager **eingegangen** sind. Für einen Test warten Sie, bis das Personal (oder ein Test-Wareneingang) sie als eingegangen markiert hat, und fahren Sie dann fort.

## 6. Noch vorrätige Positionen listen

**REST:** `GET /api/v1/customer/shipout-orders/available-items` — [REST-Handbuch](/api/documentation#/paths/v1-customer-shipout-orders-available-items/get)

```bash
curl "https://YOUR_HOST/api/v1/customer/shipout-orders/available-items?warehouse_id=7" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

**GraphQL:** `customerShipoutAvailableItems` ([GraphQL-Handbuch](/api/graphql/documentation#/customer/customerShipoutAvailableItems))

**Überprüfung:** Sie haben eine oder mehrere `storage_package_ids` erfasst (Beispiel `5001`). Eine leere Liste bedeutet: noch nichts eingegangen — keine Auslagerung anlegen. `403` bedeutet: Auslagerung ist für diesen Kunden deaktiviert.

## 7. Auslagerung schätzen und anlegen

**REST:** `GET /api/v1/customer/shipout-orders/services?warehouse_id=7` — [REST-Handbuch](/api/documentation#/paths/v1-customer-shipout-orders-services/get)

Erfassen Sie einen `service_code`.

**REST:** `POST /api/v1/customer/shipout-orders/services/{serviceCode}/estimate` — [REST-Handbuch](/api/documentation#/paths/v1-customer-shipout-orders-services-serviceCode--estimate/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/services/intl_express/estimate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "warehouse_id": 7,
    "delivery_postcode": "M5V2H1",
    "delivery_country": "CA",
    "packages": [{
      "weight": 2.5,
      "length": 30,
      "width": 20,
      "height": 15,
      "weight_unit": 2,
      "dimension_unit": 2
    }]
  }'
```

**REST:** `POST /api/v1/customer/shipout-orders/services/{serviceCode}/orders` — [REST-Handbuch](/api/documentation#/paths/v1-customer-shipout-orders-services-serviceCode--orders/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/services/intl_express/orders \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-shipout-001" \
  -d '{
    "warehouse_id": 7,
    "storage_package_ids": [5001],
    "delivery_name": "Jane Recipient",
    "delivery_telephone": "5555555555",
    "delivery_email": "jane@example.com",
    "delivery_address_1": "123 King St W",
    "delivery_city": "Toronto",
    "delivery_province": "ON",
    "delivery_country": "CA",
    "delivery_postcode": "M5V2H1"
  }'
```

**GraphQL:** `customerCreateShipoutOrder` ([GraphQL-Handbuch](/api/graphql/documentation#/customer/customerCreateShipoutOrder))

**Überprüfung:** die Antwort hat eine Auslagerungs-`id`. Die gewählten Lagerpakete sind an diese Anfrage gebunden.

## 8. Auslagerung bezahlen, tracken, Ereignisse

**REST:** `POST /api/v1/customer/shipout-orders/{id}/pay` — [REST-Handbuch](/api/documentation#/paths/v1-customer-shipout-orders-id--pay/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/8001/pay \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**GraphQL:** `customerPayShipout` ([GraphQL-Handbuch](/api/graphql/documentation#/customer/customerPayShipout))

**REST:** `GET /api/v1/customer/shipout-orders/{id}` — [REST-Handbuch](/api/documentation#/paths/v1-customer-shipout-orders-id/get)

Wenn eine Sendungsnummer existiert:

```bash
curl https://YOUR_HOST/api/v1/tracking/SR123456789012
```

[REST-Handbuch](/api/documentation#/operations/getPublicTracking) · [GraphQL-Handbuch](/api/graphql/documentation#/tracking/trackingPublic)

**REST:** `PUT /api/v1/webhook-settings` — [REST-Handbuch](/api/documentation#/paths/v1-webhook-settings/put)

**GraphQL:** `webhookSettingsUpdate` ([GraphQL-Handbuch](/api/graphql/documentation#/webhooks/webhookSettingsUpdate)).

Prüfen Sie **v2**: `HMAC_SHA256(timestamp + "." + raw_body, secret)` gegen `X-Webhook-Signature-V2`. Deduplizieren Sie über `X-Webhook-Event-Id`. Antworten Sie **2xx in unter 3 Sekunden**.

```php
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE_V2'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $raw, $sharedSecret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
```

**Überprüfung:** Bezahlen bucht einen Betrag (oder `402` / `422` mit klarem Grund). Öffentliches Tracking findet die Sendung, sobald eine Nummer existiert.

## 9. Test-Auslagerung stornieren

**REST:** `POST /api/v1/customer/shipout-orders/{id}/cancel` — [REST-Handbuch](/api/documentation#/paths/v1-customer-shipout-orders-id--cancel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/8001/cancel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**GraphQL:** `customerCancelShipout` ([GraphQL-Handbuch](/api/graphql/documentation#/customer/customerCancelShipout))

Das gibt die Sperre der Lagerpakete frei. Die Lagerung selbst wird mit `POST /api/v1/customer/storage-orders/{id}/cancel` storniert, solange das noch zulässig ist.

**Überprüfung:** `422` bedeutet: dieser Status lässt sich nicht stornieren. Nach erfolgreicher Stornierung der Auslagerung listet Schritt 6 die Pakete wieder.

## Testliste

- [ ] Die Lagerkonfiguration liefert eine Lager-`id`.
- [ ] Die Preisanfrage für die Lagerung liefert einen Preis.
- [ ] Anlegen der Lagerung liefert `data.id`.
- [ ] Bezahlen der Lagerung gelingt, **oder** Sie haben bestätigt, dass das Guthaben aufgeladen werden muss.
- [ ] Verfügbare Positionen listen eingegangene Pakete (`storage_package_ids`).
- [ ] Die Schätzung der Auslagerung liefert einen Preis oder `has_items_needing_quote`.
- [ ] Anlegen der Auslagerung liefert eine `id` und sperrt diese Pakete.
- [ ] Bezahlen der Auslagerung gelingt (oder `402` / `422` ist verstanden).
- [ ] Öffentliches Tracking findet die Sendung, sobald eine Sendungsnummer existiert.
- [ ] Stornieren der Auslagerung gibt die Pakete frei, **oder** dieser Status lässt sich nicht stornieren.
