ByteBite Docs
Architecture

Delivery

Hierarchical delivery zones, Mapbox addresses, and cash delivery checkout.

Delivery

Delivery checkout is explicit. Omitting fulfillmentType still creates a pickup order with no address and a zero fee. Payment stays cash / unpaid. There is no BitePay and no driver tracking.

The tenant is the hostname. A restaurantId in the body does not change it.

Rules

Zones live in delivery_rules, one restaurant each. The most specific active rule wins (ADR-017):

STREET > DISTRICT > POSTAL_CODE_CITY > CITY

Match fields are stored already normalized (trim, NFC, lowercase). Comparison is exact. ß is not folded to ss. A house number is not part of a rule. No matching rule means the address is not deliverable. countryCode other than DE is not deliverable.

min_order_cents and delivery_fee_cents are integers. The cart subtotal is compared with the minimum. The fee is snapshotted onto the order. total_cents = subtotal_cents + delivery_fee_cents.

Luigi's seed is one active city rule for normalized berlin (minimum 1500, fee 250). Mario has none. Seeds still create no orders.

Addresses

Mapbox stays behind AddressProvider in the API (ADR-006). packages/domain does not call HTTP.

CallMapboxStored
SuggestSearch Box suggestNo
QuoteSearch Box retrieveNo
Save a new zoneSearch Box retrieve, then keep the match tokensRule row only
CheckoutPermanent forward geocodeorder_delivery_addresses

Empty MAPBOX_ACCESS_TOKEN does not stop the API. Suggest, quote, and delivery checkout return 503. Pickup still works.

The guest sends providerId and the search sessionToken. The server resolves the id again. Street and city in the client body are ignored because checkout does not accept them.

A destination needs street, house number, postal code, and city. A city-only hit can match a zone in the quote and still be rejected at checkout as incomplete.

Checkout

POST /storefront/checkout with fulfillmentType: "delivery":

  • Empty, inactive, or foreign cart lines → 409, cart unchanged.
  • Closed → 409, cart unchanged. This is checked before geocoding.
  • Unknown providerId → 400.
  • Incomplete address → 400.
  • No matching rule → 409 Address is not deliverable.
  • Subtotal under the minimum → 409 Order is below the delivery minimum.
  • Success writes the order, the address snapshot, and clears cart lines. The bb_cart cookie stays.

POST /storefront/delivery/quote returns 200 with deliverable, belowMinimum, the fee, and the subtotal. Closed does not block the quote. An unknown id is 400.

GET /storefront/restaurant includes deliveryAvailable when the hostname restaurant has at least one active rule. The rule list is not public.

GET /storefront/delivery/suggestions?q=&sessionToken= returns { id, label }[]. q is at least 2 characters. sessionToken is a UUID.

Dashboard

Staff session required. The path restaurantId is the tenant.

MethodPath
GET/dashboard/restaurants/:restaurantId/delivery-rules
PUT/dashboard/restaurants/:restaurantId/delivery-rules
GET/dashboard/restaurants/:restaurantId/delivery-suggestions

PUT replaces the restaurant's zones. An existing rule is { id, minOrderCents, deliveryFeeCents, active }. A new rule is { providerId, sessionToken, scope, minOrderCents, deliveryFeeCents, active }. A Luigi rule id on Mario's path is 404. Duplicate match keys are 400.

The dashboard page is /delivery.

Orders and SMS

The public order includes fulfillmentType, deliveryFeeCents, totalCents, and address (null for pickup).

Accept SMS says Estimated delivery or Estimated pickup. Ready SMS for delivery is Your order is on the way.; pickup stays Order is ready for pickup. Reject and timeout text do not change. Checkout still sends nothing.

Not in this slice

BitePay, card_at_door, customer accounts, live driver location, and new order statuses.

On this page