# Order lifecycle

A shipment order goes through the same stages regardless of carrier:

`service_structure` → [`order_valuation`] → `order_send` →
`waybill` / `dispatch_code` / `turn_in` → optionally `cancel_order`

`order_valuation` is **optional** — see step 2. Examples below use the
[PHP SDK](/sdk); each stage names the underlying API method, so the flow maps
1:1 to raw [envelope requests](/legacy/hmac-signing) from any language. Full
payload shapes live in the [API v2 reference](/reference/v2-legacy).

:::tip[Not sure which service to use?]

If you don't pass a fixed `service_id` but want to pick from the services
that can actually carry the parcel, start with
[Choosing a service from the offer](/guides/choosing-a-service) and come back
here at `order_send`.

:::

## 1. Pick a service — `service_structure`

Fetch the services available on your account and let the user (or your
config) pick one — [don't hardcode the
list](/guides/best-practices#dont-hardcode-carriers-and-services):

```php
$structure = $client->getServiceStructure();
$services  = $structure->getServices();      // carriers + service ids
$packages  = $structure->getPackageTypes();  // PACZKA, PALETA, ...
$options   = $structure->getOptions();       // COD, insurance, ...
```

## 2. (Optional) Price the shipment — `order_valuation`

Get a quote before committing. The same order object works for valuation and
creation:

```php
use AlsendoOne\SDK\DTO\Address;
use AlsendoOne\SDK\DTO\Request\OrderRequest;
use AlsendoOne\SDK\DTO\Request\PickupRequest;
use AlsendoOne\SDK\DTO\Request\ShipmentRequest;
use AlsendoOne\SDK\Type\Service;

$service = Service::from(Service::DpdCourier);

$order = OrderRequest::create()
    ->setService($service)
    ->setSenderAddress(new Address(/* ... */))
    ->setReceiverAddress(new Address(/* ... */))
    ->addShipment(new ShipmentRequest('PACZKA', 2.5, 40, 30, 20))
    ->setPickup(new PickupRequest('COURIER', '2026-08-19', '10:00', '16:00'));

$valuation = $client->getValuation($order);

foreach ($valuation->getPriceTable() as $price) {
    // Prices are in groszy (1 PLN = 100 gr)
    printf("Service %d: %.2f PLN gross\n",
        $price->getServiceId(), $price->getPriceGross() / 100);
}
```

:::note[Skipping valuation entirely?]

Many integrations create orders without ever calling `order_valuation` —
they know the service and take the price as invoiced. That's fine. Just
remember the price is only settled **at creation**: order-level changes and
surcharges (see step 2b) mean an assumed price can differ. Read the real
amount back from `getOrder()` / `getOrders()` (`price` / `price_gross`, in
groszy) after `order_send`.

:::

## 2b. (Optional) Re-price the final payload — `order_valuation`

If you changed the order after an earlier quote — added a pickup, options,
customs or a point — re-run `order_valuation` on the **final** payload before
sending. Order-level changes can move the price, so the earlier quote may be
stale:

- a **courier pickup scheduled for the same day** may be surcharged,
- additional services / COD add cost,
- point routing or customs handling can change the applicable tariff.

```php
// $order now carries everything you're about to send (service, pickup, options, customs…)
$finalPrice = $client->getValuation($order)->getPriceForService($service);
// Show/store the confirmed price, then send the *same* $order in step 3.
```

:::tip[Same object, no surprises]

Quote what you are about to send — valuate the final `$order`, then pass that
exact object to `order_send`.

:::

## 3. Create the order — `order_send`

```php
$created = $client->sendOrder($order);

$orderId = $created->getId();
$created->getWaybillNumber();   // carrier tracking number
$created->getTrackingUrl();     // public tracking link
$created->getStatus();          // e.g. NEW
```

:::caution

`order_send` is **not idempotent** — a retried timeout can create a
duplicate order. See
[best practices](/guides/best-practices#be-careful-with-retries).

:::

The `pickup` object decides how the parcel reaches the carrier (courier
pickup vs. your own drop-off) — the details have their own guide:
[Pickup types](/guides/pickups).

## 4. Documents — `waybill`, `dispatch_code`, `turn_in`

Every document is returned base64-encoded:

```php
// Shipping label (PDF) — required on the parcel
$waybill = $client->getWaybill($orderId);
file_put_contents('label.pdf', base64_decode($waybill->getWaybill()));

// Dispatch/return code — some carriers require it at drop-off;
// carriers that don't issue one return no code
$dispatch = $client->getDispatchCode($orderId);

// Turn-in confirmation for a batch of parcels handed to one courier
$turnIn = $client->getTurnIn([$orderId, $anotherOrderId]);
```

## 5. Track it — `tracking`

Poll `getTracking($waybillNumber)` or receive push notifications — see the
[Tracking guide](/guides/tracking).

## 6. Cancel if needed — `cancel_order`

```php
$client->cancelOrder($orderId);   // throws ApiException if not cancellable
```

Orders can be cancelled only until the carrier takes over the parcel; after
that the API returns an error envelope. Note that a cancelled order also
stops serving its documents — `waybill` and `turn_in` return an API error
for it, so download anything you need before cancelling.

## Inspecting orders later

```php
$order  = $client->getOrder($orderId);        // full detail (incl. price / price_gross, in groszy)
$orders = $client->getOrders(page: 1, limit: 25);  // paginated list, max 25/page
```

---

**See also:** [Choosing a service from the offer](/guides/choosing-a-service) ·
[Pickup types](/guides/pickups) · [Tracking](/guides/tracking) ·
[Best practices](/guides/best-practices)
