# Versandservice

Die eigenen Versandservices des Logistikunternehmens nutzen: Versandservices listen → Konfiguration eines Versandservice laden → Preis schätzen → Auftrag anlegen → bezahlen → zurücklesen → tracken → Testauftrag 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"}'
```

Verwenden Sie `access_token` so:

```
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. Versandservices listen

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

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

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

Jede Zeile hat `service_code`, `name`, `offer_pickup`, `allow_warehouse_delivery`. Eine leere Liste bedeutet: diesem Kunden ist kein Versandservice zugewiesen.

**Überprüfung:** Sie haben einen `service_code` erfasst (Beispiel unten: `intl_express`).

## 3. Konfiguration dieses Versandservice laden

**REST:** `GET /api/v1/customer/shipping-orders/services/{serviceCode}/config` — [REST-Handbuch](/api/documentation#/paths/v1-customer-shipping-orders-services-serviceCode--config/get)

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

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

**Überprüfung:** Sie haben mindestens eine Lager-`id`, wenn dieser Versandservice die Abgabe im Lager zulässt. `403` bedeutet: diesem Kunden ist dieser Versandservice nicht erlaubt.

## 4. Preis schätzen

**REST:** `POST /api/v1/customer/shipping-orders/services/{serviceCode}/estimate-price`

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

`origin_type`: `warehouse` (Abgabe im Lager) oder `pickup` (das Unternehmen holt ab). Passen Sie das an, was Schritt 3 für den Versandservice zulässt.

**Überprüfung:** `result` ist true und Sie haben einen Preis (oder ein Kennzeichen „Angebot nötig“ für manuelle Preisbildung). Noch nicht anlegen, wenn Ziel oder Paket abgelehnt werden.

## 5. Auftrag anlegen

**REST:** `POST /api/v1/customer/shipping-orders/services/{serviceCode}/orders`

Senden Sie `Idempotency-Key`.

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipping-orders/services/intl_express/orders \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-ship-001" \
  -d '{
    "origin_type": "warehouse",
    "warehouse_id": 7,
    "reference": "DEV-SHIP-001",
    "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",
    "package": [{
      "weight": 2,
      "length": 30,
      "width": 20,
      "height": 15,
      "weight_unit": 2,
      "dimension_unit": 2,
      "quantity": 1
    }]
  }'
```

**Überprüfung:** die Antwort hat eine Auftrags-`id`. Speichern Sie `id`, `tracking_number` / `reference_number`, sofern vorhanden.

## 6. Bezahlen

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

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

Optional: zuerst `GET /api/v1/customer/shipping-orders/{id}/payment-info`. Ein `402` bedeutet, das Guthaben reicht nicht — aufladen, dann erneut versuchen.

**Überprüfung:** der Auftrag ist nicht mehr zahlbar, oder `remaining_balance` ist `0`.

## 7. Auftrag abrufen und verfolgen

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

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

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

Wenn eine `tracking_number` am Auftrag steht, öffentliches Tracking (kein Token):

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

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

**Überprüfung:** das Detail ist der Auftrag dieses Kunden. Öffentliches Tracking findet ihn, sobald eine Nummer existiert.

## 8. Ereignisbenachrichtigungen konfigurieren

| Einstellung | Ereignis | Wann |
|---|---|---|
| `order_create_webhook_url` | `order.created` | `id` und Sendungsnummern speichern |
| `tracking_event_webhook_url` | `tracking.event` | Zeitlinie |
| `order_status_change_webhook_url` | `order.status_change` | Status in Ihrem System |

**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** über den Rohkörper: `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:** ein Test-Anlegen erzeugt `order.created` mit dieser `id`.

## 9. Testauftrag stornieren

**REST:** `POST /api/v1/customer/shipping-orders/{id}/cancel`

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

**Überprüfung:** eine zweite Stornierung ist ungefährlich, oder die API sagt, der Auftrag ist bereits storniert. `422` bedeutet: dieser Status lässt sich nicht stornieren.

## Testliste

Verwenden Sie eine Test-`reference` wie `DEV-SHIP-001`:

- [ ] Die Liste der Versandservices ist nicht leer; Sie haben einen `service_code` erfasst.
- [ ] Die Konfiguration liefert Lager / Verpackung für diesen Versandservice.
- [ ] Die Schätzung liefert einen Preis (oder ein klares Kennzeichen „Angebot nötig“).
- [ ] Anlegen liefert eine `id`; derselbe `Idempotency-Key` legt keinen zweiten Auftrag an.
- [ ] Bezahlen gelingt, **oder** Sie haben bestätigt, dass das Guthaben aufgeladen werden muss (`402`).
- [ ] Detail zeigt den Auftrag dieses Kunden.
- [ ] Öffentliches Tracking findet die Sendung, sobald eine Sendungsnummer existiert.
- [ ] Stornieren gelingt, **oder** Sie haben bestätigt, dass dieser Status sich nicht stornieren lässt.
