Maxmove LogoDocs

Die Entwicklerdokumentation ist auf Englisch verfügbar.

Offer same-day delivery at checkout

A recipe for your own shop: price Maxmove delivery when the customer enters an address, show it only where it is available, and book it at the quoted price once the order is paid.

Als Markdown ansehen

This recipe adds Maxmove as a shipping option to a checkout you build yourself. Shopify, WooCommerce, and Shopware shops get the same flow from the ready-made integrations.

Code examples use BASE_URL and headers from the Quickstart. The PHP examples use Guzzle with the API key set as a default header.

The flow#

  1. Quote when the address is complete

    When the customer has entered a shipping address with coordinates, your server requests a quote. Never call the API from the browser: the API key must stay on your server.

  2. Show or hide the option

    Show Maxmove delivery with the quoted price. Hide it when the quote fails, for example because the address is outside the service area.

  3. Keep the quote with the checkout

    Store id and valid_until of the quote in the checkout session. A quote is valid for about 5 minutes, so request a new one when the customer takes longer.

  4. Book once the order is paid

    Create the delivery with the quote_id and exactly the quoted route, plus the contacts. Then follow Sync shop orders into deliveries for tracking and status updates.

Quote the address#

Quote from your store to the customer's address. Send the same vehicle_type_id, items, and scheduled_at you will book later, because all of them are part of the quote. Omit scheduled_at for delivery as soon as possible.

const STORE = { address: 'Ehrenstraße 15, 50672 Köln', latitude: 50.9385, longitude: 6.9469 };
 
export async function quoteCheckoutDelivery(checkout: Checkout) {
  const res = await fetch(`${BASE_URL}/quotes`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      pickup: STORE,
      dropoff: {
        address: checkout.shippingAddress.formatted,
        latitude: checkout.shippingAddress.latitude,
        longitude: checkout.shippingAddress.longitude,
      },
      vehicle_type_id: 'courier',
      scheduled_at: checkout.deliverySlot ?? undefined,
    }),
  });
 
  const body = await res.json();
  if (!res.ok) return { available: false, reason: body.error.code };
 
  await saveQuoteOnCheckout(checkout.id, { quoteId: body.id, validUntil: body.valid_until });
  return { available: true, amountCents: body.amount_cents, currency: body.currency };
}

Maxmove does not geocode. Use the coordinates your address autocomplete returns, and quote again when the customer changes the address.

Show the price#

amount_cents is the total price in cents of currency, including VAT. breakdown shows how it is made up if you want to display surcharges or extras. What you charge your customer for shipping is up to you; Maxmove bills you the delivery on your monthly invoice.

duration_min is the estimated driving time of the route, not a delivery promise. A courier still has to accept the delivery and reach your store.

Hide the option when the quote fails:

StatusCodeWhat to do at checkout
400service_area_unavailableThe address is outside the service area. Offer your other shipping methods.
400vehicle_does_not_fitThe items don't fit the vehicle type. Quote a larger one, or hide the option.
400invalid_requestCheck the address coordinates and scheduled_at.

Book the paid order#

Create the delivery with the stored quote_id and the same route you quoted. Add the contacts and instructions, which are not part of the quote. Use your order id as Idempotency-Key so a retry never books a second courier.

export async function bookPaidOrder(order: PaidOrder) {
  const res = await fetch(`${BASE_URL}/deliveries`, {
    method: 'POST',
    headers: { ...headers, 'Idempotency-Key': `shop-order-${order.id}` },
    body: JSON.stringify({
      quote_id: order.maxmoveQuoteId,
      external_id: order.number,
      vehicle_type_id: 'courier',
      scheduled_at: order.deliverySlot ?? undefined,
      pickup: {
        ...STORE,
        contact: { name: 'Feinkost Ehrenfeld', phone: '+49 221 1234567' },
      },
      dropoff: {
        address: order.shippingAddress.formatted,
        latitude: order.shippingAddress.latitude,
        longitude: order.shippingAddress.longitude,
        contact: { name: order.customer.name, phone: order.customer.phone },
      },
    }),
  });
 
  const body = await res.json();
  if (res.ok) return saveDeliveryOnOrder(order.id, { deliveryId: body.id, trackingUrl: body.tracking_url });
  if (body.error.code === 'quote_expired' || body.error.code === 'quote_mismatch') {
    return bookWithFreshQuote(order);
  }
  throw new Error(`${body.error.code}: ${body.error.message} (${body.request_id})`);
}

When the quote has expired#

Customers often pay after the quote's 5 minutes. Then the delivery fails with 400 quote_expired, and nothing is booked. Decide how your shop handles a price change:

  • Request a new quote and book with it. The price can differ from what the customer saw. Most shops absorb small differences.
  • Book without quote_id. Maxmove prices the route when you create the delivery and returns the price in amount_cents.

A request that fails with quote_expired or quote_mismatch creates nothing and does not use up its Idempotency-Key, so book again with the same key and the new quote_id. See Idempotency.

Test it#

Test keys accept the same requests and run the same service area check as live keys, but no courier is dispatched and nothing is billed. The Robocourier moves each test delivery through every status, so you can check your order updates end to end. See Test mode.

Hat das Ihre Frage beantwortet?