Delivery Platform Integrations
Connect with third-party delivery services for expanded reach.
Overview
Olympus Cloud integrates with major delivery platforms:
| Platform | Features | API Type |
|---|---|---|
| DoorDash | Orders, Menu, Status | REST API |
| Uber Eats | Orders, Menu, Status | REST API |
| Grubhub | Orders, Menu, Status | REST API |
| Postmates | Orders via Uber Eats | REST 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)
}
Menu Sync
// 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 Status | DoorDash | Uber Eats | Grubhub |
|---|---|---|---|
open | created | placed | submitted |
sent | confirmed | accepted | confirmed |
preparing | preparing | preparing | in_progress |
ready | ready_for_pickup | ready | ready |
completed | picked_up | picked_up | completed |
cancelled | cancelled | cancelled | cancelled |
Menu Synchronization
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
| Metric | Description |
|---|---|
delivery.orders.received | Orders received by platform |
delivery.orders.errors | Order processing errors |
delivery.menu.sync.duration | Menu sync time |
delivery.status.updates | Status update count |
Related Documentation
- Orders API - Order management
- Menu API - Menu management
- Webhooks - Webhook configuration