Skip to main content

Delivery Platform Integrations

Connect with third-party delivery services for expanded reach.

Overview

Olympus Cloud integrates with major delivery platforms:

PlatformFeaturesAPI Type
DoorDashOrders, Menu, StatusREST API
Uber EatsOrders, Menu, StatusREST API
GrubhubOrders, Menu, StatusREST API
PostmatesOrders via Uber EatsREST API

Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Delivery Platforms │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ DoorDash │ │ Uber Eats│ │ Grubhub │ │ Custom │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼─────────────┼─────────────┼─────────────┼───────────────┘
│ │ │ │
└─────────────┴──────┬──────┴─────────────┘

┌──────────────▼───────────────┐
│ Delivery Integration Hub │
│ • Order normalization │
│ • Menu synchronization │
│ • Status updates │
└──────────────┬───────────────┘

┌──────────────▼───────────────┐
│ Commerce Service │
│ • Order processing │
│ • Kitchen routing │
│ • Inventory updates │
└──────────────────────────────┘

DoorDash Integration

Setup

# config/integrations/doordash.yaml
doordash:
developer_id: "your-developer-id"
key_id: "your-key-id"
signing_secret: "your-signing-secret"
base_url: "https://openapi.doordash.com"
webhook_url: "https://api.olympuscloud.ai/webhooks/doordash"

Authentication

// src/integrations/doordash/auth.rs
use jsonwebtoken::{encode, Header, EncodingKey, Algorithm};

pub fn create_jwt_token(
developer_id: &str,
key_id: &str,
signing_secret: &str,
) -> Result<String> {
let now = chrono::Utc::now();

let claims = DoorDashClaims {
aud: "doordash".into(),
iss: developer_id.into(),
kid: key_id.into(),
iat: now.timestamp(),
exp: (now + chrono::Duration::minutes(5)).timestamp(),
};

let header = Header {
alg: Algorithm::HS256,
kid: Some(key_id.into()),
..Default::default()
};

encode(
&header,
&claims,
&EncodingKey::from_base64_secret(signing_secret)?
)
}

Receive Orders

// src/integrations/doordash/orders.rs
#[derive(Deserialize)]
pub struct DoorDashOrder {
pub external_delivery_id: String,
pub store_id: String,
pub items: Vec<DoorDashItem>,
pub customer: DoorDashCustomer,
pub pickup_time: DateTime<Utc>,
pub dropoff_address: Address,
}

pub async fn handle_doordash_order(
order: DoorDashOrder,
ctx: &TenantContext,
) -> Result<Order> {
// Normalize to Olympus order format
let olympus_order = Order {
id: Uuid::new_v4().to_string(),
order_type: OrderType::Delivery,
source: OrderSource::DoorDash,
external_id: Some(order.external_delivery_id),
items: order.items.into_iter().map(|i| normalize_item(i)).collect(),
customer: normalize_customer(order.customer),
scheduled_time: Some(order.pickup_time),
delivery_address: Some(order.dropoff_address),
..Default::default()
};

// Create order in system
let created = order_service.create(ctx, olympus_order).await?;

// Send to kitchen
kds_service.route_order(&created).await?;

Ok(created)
}

Update Order Status

pub async fn update_doordash_status(
external_id: &str,
status: OrderStatus,
) -> Result<()> {
let client = reqwest::Client::new();
let token = create_jwt_token(&config)?;

let doordash_status = match status {
OrderStatus::Preparing => "confirmed",
OrderStatus::Ready => "ready_for_pickup",
OrderStatus::Completed => "picked_up",
OrderStatus::Cancelled => "cancelled",
_ => return Ok(()),
};

client
.patch(&format!(
"https://openapi.doordash.com/drive/v2/deliveries/{}",
external_id
))
.header("Authorization", format!("Bearer {}", token))
.json(&json!({ "status": doordash_status }))
.send()
.await?;

Ok(())
}

Uber Eats Integration

Setup

# config/integrations/ubereats.yaml
ubereats:
client_id: "your-client-id"
client_secret: "your-client-secret"
base_url: "https://api.uber.com/v1"
webhook_url: "https://api.olympuscloud.ai/webhooks/ubereats"

OAuth Authentication

// src/integrations/ubereats/auth.rs
pub async fn get_access_token(
client_id: &str,
client_secret: &str,
) -> Result<String> {
let client = reqwest::Client::new();

let response = client
.post("https://login.uber.com/oauth/v2/token")
.form(&[
("client_id", client_id),
("client_secret", client_secret),
("grant_type", "client_credentials"),
("scope", "eats.store eats.order"),
])
.send()
.await?;

let token_response: TokenResponse = response.json().await?;
Ok(token_response.access_token)
}
// src/integrations/ubereats/menu.rs
pub async fn sync_menu_to_ubereats(
store_id: &str,
menu: &Menu,
token: &str,
) -> Result<()> {
let client = reqwest::Client::new();

let ubereats_menu = UberEatsMenu {
menus: vec![UberEatsMenuSection {
id: menu.id.clone(),
title: LocalizedText::new(&menu.name),
categories: menu.categories.iter().map(|c| {
UberEatsCategory {
id: c.id.clone(),
title: LocalizedText::new(&c.name),
items: c.items.iter().map(|i| {
UberEatsItem {
id: i.id.clone(),
title: LocalizedText::new(&i.name),
description: i.description.clone().map(|d| LocalizedText::new(&d)),
price: i.price as i32,
modifier_groups: convert_modifiers(&i.modifier_groups),
}
}).collect(),
}
}).collect(),
}],
};

client
.put(&format!(
"https://api.uber.com/v1/eats/stores/{}/menus",
store_id
))
.header("Authorization", format!("Bearer {}", token))
.json(&ubereats_menu)
.send()
.await?;

Ok(())
}

Webhook Handler

// src/integrations/ubereats/webhooks.rs
pub async fn handle_ubereats_webhook(
event: UberEatsEvent,
ctx: &TenantContext,
) -> Result<()> {
match event.event_type.as_str() {
"orders.notification" => {
let order: UberEatsOrder = serde_json::from_value(event.meta.resource)?;
handle_new_order(order, ctx).await?;
}
"orders.cancel" => {
let order_id = event.meta.resource_id;
order_service.cancel(&order_id, "Cancelled by Uber Eats").await?;
}
_ => {
tracing::info!("Unhandled Uber Eats event: {}", event.event_type);
}
}

Ok(())
}

Grubhub Integration

Setup

# config/integrations/grubhub.yaml
grubhub:
api_key: "your-api-key"
restaurant_id: "your-restaurant-id"
base_url: "https://api.grubhub.com"
webhook_url: "https://api.olympuscloud.ai/webhooks/grubhub"

Order Processing

// src/integrations/grubhub/orders.rs
pub async fn handle_grubhub_order(
order: GrubhubOrder,
ctx: &TenantContext,
) -> Result<Order> {
let olympus_order = Order {
id: Uuid::new_v4().to_string(),
order_type: OrderType::Delivery,
source: OrderSource::Grubhub,
external_id: Some(order.order_id),
items: order.line_items.into_iter().map(|i| {
OrderLineItem {
menu_item_id: i.menu_item_id,
name: i.name,
quantity: i.quantity,
unit_price: i.price,
modifiers: i.special_instructions.map(|s| vec![s]).unwrap_or_default(),
notes: i.diner_notes,
}
}).collect(),
subtotal: order.subtotal,
tax: order.tax,
total: order.total,
..Default::default()
};

order_service.create(ctx, olympus_order).await
}

Unified Order Model

Order Normalization

// src/integrations/normalize.rs
pub trait DeliveryOrder {
fn to_olympus_order(&self, ctx: &TenantContext) -> Order;
}

impl DeliveryOrder for DoorDashOrder {
fn to_olympus_order(&self, ctx: &TenantContext) -> Order {
Order {
tenant_id: ctx.tenant_id.clone(),
location_id: self.store_id.clone(),
order_type: OrderType::Delivery,
source: OrderSource::DoorDash,
external_id: Some(self.external_delivery_id.clone()),
// ... normalize fields
}
}
}

impl DeliveryOrder for UberEatsOrder {
fn to_olympus_order(&self, ctx: &TenantContext) -> Order {
Order {
tenant_id: ctx.tenant_id.clone(),
location_id: self.store.id.clone(),
order_type: OrderType::Delivery,
source: OrderSource::UberEats,
external_id: Some(self.id.clone()),
// ... normalize fields
}
}
}

Status Mapping

Olympus StatusDoorDashUber EatsGrubhub
opencreatedplacedsubmitted
sentconfirmedacceptedconfirmed
preparingpreparingpreparingin_progress
readyready_for_pickupreadyready
completedpicked_uppicked_upcompleted
cancelledcancelledcancelledcancelled

Automatic Sync

// src/integrations/menu_sync.rs
pub struct MenuSyncService {
doordash: DoorDashClient,
ubereats: UberEatsClient,
grubhub: GrubhubClient,
}

impl MenuSyncService {
pub async fn sync_menu(
&self,
ctx: &TenantContext,
location_id: &str,
) -> Result<SyncResult> {
let menu = menu_service.get_active_menu(ctx, location_id).await?;
let integrations = integration_service.get_enabled(ctx, location_id).await?;

let mut results = SyncResult::default();

for integration in integrations {
let result = match integration.platform {
Platform::DoorDash => {
self.doordash.sync_menu(&integration.store_id, &menu).await
}
Platform::UberEats => {
self.ubereats.sync_menu(&integration.store_id, &menu).await
}
Platform::Grubhub => {
self.grubhub.sync_menu(&integration.store_id, &menu).await
}
};

results.add(integration.platform, result);
}

Ok(results)
}
}

Availability Updates

// Update item availability across platforms
pub async fn update_availability(
ctx: &TenantContext,
item_id: &str,
available: bool,
) -> Result<()> {
let integrations = integration_service.get_enabled(ctx).await?;

for integration in integrations {
match integration.platform {
Platform::DoorDash => {
doordash.update_item_availability(
&integration.store_id,
item_id,
available
).await?;
}
Platform::UberEats => {
ubereats.update_item_status(
&integration.store_id,
item_id,
if available { "AVAILABLE" } else { "UNAVAILABLE" }
).await?;
}
// ... other platforms
}
}

Ok(())
}

Store Hours

Sync Operating Hours

pub async fn sync_store_hours(
ctx: &TenantContext,
location_id: &str,
hours: &[BusinessHours],
) -> Result<()> {
let integrations = integration_service.get_enabled(ctx, location_id).await?;

for integration in integrations {
let platform_hours = convert_to_platform_hours(&integration.platform, hours);

match integration.platform {
Platform::DoorDash => {
doordash.update_store_hours(&integration.store_id, &platform_hours).await?;
}
Platform::UberEats => {
ubereats.update_store_hours(&integration.store_id, &platform_hours).await?;
}
// ... other platforms
}
}

Ok(())
}

Error Handling

Retry Logic

pub async fn with_retry<F, T, E>(
operation: F,
max_retries: u32,
) -> Result<T, E>
where
F: Fn() -> Future<Output = Result<T, E>>,
E: std::error::Error,
{
let mut attempts = 0;

loop {
match operation().await {
Ok(result) => return Ok(result),
Err(e) if attempts < max_retries => {
attempts += 1;
let delay = Duration::from_secs(2u64.pow(attempts));
tokio::time::sleep(delay).await;
}
Err(e) => return Err(e),
}
}
}

Monitoring

Metrics

MetricDescription
delivery.orders.receivedOrders received by platform
delivery.orders.errorsOrder processing errors
delivery.menu.sync.durationMenu sync time
delivery.status.updatesStatus update count