Pedidos
Reciba y decida pedidos
Construya el ciclo de pedidos: conecte un local, suscríbase a webhooks, verifique las entregas y gestione cada pedido con las llamadas accept, deny, ready y complete.
Este es el núcleo de una integración con Maple. Cuando un cliente hace un pedido en un local al que usted está conectado, Maple envía el pedido a su webhook; usted decide si lo prepara e informa su avance a medida que se mueve. Nada de esto involucra pagos: de eso se encarga Maple.
Una integración de pedidos funcional consta de dos partes:
| # | Qué construye | Esfuerzo habitual |
|---|---|---|
| 1 | Recibir pedidos: un endpoint de webhook con verificación de firma | ~1–2 días |
| 2 | Decidir pedidos: llamadas accept / deny / status hacia Maple | ~1–2 días |
Si aún no lo ha hecho, revise Cómo funciona Maple para conocer el modelo de objetos, y siga el Inicio rápido para obtener una clave y conectar un local de prueba.
Paso 1 — Conecte un local
Los pedidos llegan a usted solo cuando su app es el receptor conectado de un local:
curl -X POST $MAPLE_BASE/locations/{locationId}/connection \ -H "Authorization: Bearer $MAPLE_KEY"Las conexiones son exclusivas por entorno: si otra app ya tiene el local, recibirá un 409. GET y DELETE en la misma ruta consultan y eliminan su conexión.
Paso 2 — Suscríbase a los webhooks
Registre un endpoint HTTPS y los tipos de evento que desea. El catálogo completo está en GET /v1/webhook_event_types; para el ciclo de pedidos normalmente querrá estos:
curl -X POST $MAPLE_BASE/webhook_subscriptions \ -H "Authorization: Bearer $MAPLE_KEY" \ -H "Content-Type: application/json" \ -d '{ "notification_url": "https://your-app.example.com/maple/webhooks", "event_types": ["order.notification", "order.validation_requested", "order.cancelled"] }'La respuesta incluye un secreto de firma de un solo uso (mwhsec_…). Guárdelo; no se vuelve a mostrar. La URL debe ser HTTPS pública: se rechazan las direcciones privadas e internas.
Paso 3 — Verifique cada entrega
Cada entrega es un sobre al estilo de Stripe:
{ "object": "event", "id": "evt_...", "type": "order.notification", "created": 1765432100, "data": { "order_id": "ord_...", "location_id": "str_...", "...": "the order content — see Step 4" }}Las entregas incluyen dos encabezados: maple-webhook-id (el id del evento) y maple-webhook-signature con el formato t=<unix_seconds>,v1=<hex_hmac>. La cadena firmada es {timestamp}.{subscription_id}.{notification_url}.{raw_body}. Verifíquela contra el cuerpo sin procesar, sin analizar:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyMapleWebhook(params: { signatureHeader: string; // maple-webhook-signature subscriptionId: string; // your subscription id (dws_…) notificationUrl: string; // the exact URL you registered rawBody: string; // the unparsed request body signingSecret: string; // mwhsec_… toleranceSeconds?: number; // default 300}): boolean { const parts = Object.fromEntries(params.signatureHeader.split(',').map((p) => p.split('='))); const timestamp = Number(parts.t); const provided = parts.v1 ?? '';
// Reject stale deliveries to prevent replay. const skew = Math.abs(Math.floor(Date.now() / 1000) - timestamp); if (!Number.isFinite(timestamp) || skew > (params.toleranceSeconds ?? 300)) return false;
const signed = `${timestamp}.${params.subscriptionId}.${params.notificationUrl}.${params.rawBody}`; const expected = createHmac('sha256', params.signingSecret).update(signed).digest('hex'); return expected.length === provided.length && timingSafeEqual(Buffer.from(expected), Buffer.from(provided));}La verificación anterior rechaza marcas de tiempo vencidas (de más de ~5 minutos) para evitar repeticiones. Además, elimine duplicados con el id del sobre: la entrega es al menos una vez. El contrato de confiabilidad completo, con el calendario de reintentos y el libro de eventos, está en Webhooks.
Ponerse al día tras una caída
Cuando su receptor no estuvo disponible (el software local estaba apagado o la red cayó), reconcilie así:
- Llame a
GET /v1/orders?since=<timestamp>&limit=100con su último punto de control exitoso (exclusivo, ISO 8601 UTC). Consince, los resultados vienen del más antiguo al más reciente yhas_moreindica que hay otra página. - Mientras
has_moresea true, repita la solicitud constarting_afterigual al último id de pedido de la página anterior. Continúe hasta vaciar el rezago y luego avance su punto de control. - Llame a
POST /v1/orders/{orderId}/resendcuando necesite que Maple publique unorder.notificationnuevo para un pedido activo que no recibió su webhook. Requiere el alcance (scope)webhooks:write, crea una entrega nueva, firmada y con reintentos, y no cambia el estado del pedido. Los pedidos en estado terminal (REJECTED,CUSTOMER_CANCELLEDoSTORE_CANCELLED) no se pueden reenviar ni reactivar y devuelven400; reconcílielos conGET /v1/orders?since=...yGET /v1/orders/{orderId}. - Use el registro de eventos en lugar de
resendcuando importe conservar la identidad original del evento.
Esperar también funciona en una caída corta: el calendario de reintentos vuelve a entregar un evento fallido durante unas 10 horas antes de considerarlo fallido por completo.
Paso 4 — Lea el pedido
El data del payload de order.notification es el contenido del pedido: sus IDs, cliente, artículos y totales, de modo que puede empezar a prepararlo directamente desde el webhook sin otra llamada. Usa los mismos nombres de campo y montos en centavos enteros que el recurso del pedido, pero es un objeto más pequeño: order_id (no id), location_id, fulfillment_type, scheduled_for, delivery_address, customer, line_items y totals. No incluye object, id, livemode, created, status, payment_status, external_id ni payment, y los artículos no llevan tax por artículo. order.validation_requested lleva el mismo data más validation_id:
{ "order_id": "ord_...", "location_id": "str_...", "fulfillment_type": "delivery", "scheduled_for": null, "delivery_address": { "street": "123 Main Street", "unit": "Apt 4B", "city": "New York", "state": "NY", "zip": "10001", "notes": "Leave at the side door" }, "customer": { "name": "Alex", "phone": "+14155551234", "phone_last_four": "1234" }, "line_items": [ { "name": "Latte", "menu_entity_id": "itm-latte", "quantity": 1, "base_price": 450, "modifiers": [{ "name": "Oat milk", "menu_entity_id": "mod-oat", "quantity": 1, "price": 75 }] } ], "totals": { "currency": "USD", "subtotal": 525, "tax": 42, "surcharge": 0, "tip": 0, "delivery_fee": 599, "delivery_tip": 0, "total": 1166 }}GET /v1/orders/{orderId} devuelve en cambio el recurso completo del pedido, que añade object, id, livemode, created, status, payment_status, external_id, payment y el tax por artículo. Consúltelo en cualquier momento para obtener el estado actual y autorizado:
{ "object": "order", "id": "ord_...", "livemode": false, "created": 1765432100, "status": "PENDING", "payment_status": "unpaid", "fulfillment_type": "delivery", "scheduled_for": null, "location_id": "str_...", "delivery_address": { "street": "123 Main Street", "unit": "Apt 4B", "city": "New York", "state": "NY", "zip": "10001", "notes": "Leave at the side door" }, "customer": { "name": "Alex", "phone": "+14155551234", "phone_last_four": "1234" }, "line_items": [ { "name": "Latte", "menu_entity_id": "itm-latte", "quantity": 1, "base_price": 450, "tax": 36, "modifiers": [{ "name": "Oat milk", "menu_entity_id": "mod-oat", "quantity": 1, "price": 75 }] } ], "totals": { "currency": "USD", "subtotal": 525, "tax": 42, "surcharge": 0, "tip": 0, "delivery_fee": 599, "delivery_tip": 0, "total": 1166 }, "external_id": null, "payment": { "provider": "stripe", "status": "unpaid", "payment_link_url": "https://..." }}Algunos puntos que conviene tener presentes:
- El dinero está en centavos de USD enteros.
450son $4.50. Nunca lo analice como número decimal. Todos los montos entotalsy en los artículos siguen esta convención. - Los totales vienen precalculados.
totals.totales el valor autorizado. No debe recalcular ningún precio. - Los artículos hacen referencia a su menú mediante
menu_entity_id, cuyo valor es elexternalIdque usted publicó para ese artículo. Publique primero un menú y estos coincidirán con su propio catálogo. (Los payloads de menú usan camelCase; los de pedidos y webhooks usan snake_case; consulte Convenciones.) - Los modificadores son las opciones de primer nivel seleccionadas directamente. Las selecciones de modificadores anidados más profundas no se expanden en el recurso del pedido en v1.
- Los datos del cliente son intencionalmente mínimos. Recibe el nombre y el número de teléfono del cliente (
phoneen formato E.164, másphone_last_fourpara mostrar). Los pedidos a domicilio también incluyendelivery_addresscon la calle, la unidad, la ciudad, el estado, el código postal y las instrucciones de entrega; esnullen pedidos para recoger o cuando Maple no tiene una dirección guardada. Las seis claves de la dirección están presentes siempre que el bloque tenga datos. - Revise
scheduled_forantes de enviar la comanda. Esnullen pedidos para ahora. Cuando tiene valor (ISO 8601, UTC), el cliente eligió esa hora de recogida o entrega; usted recibe el pedido de inmediato, así que programe la preparación parascheduled_for, no para el momento de recibirlo.
Paso 5 — Decida e informe el avance
Responda mediante los endpoints de decisión a medida que avanza el pedido. Ninguno recibe cuerpo, salvo donde se indica:
| Llamada | Cuándo |
|---|---|
POST /v1/orders/{orderId}/accept |
Puede prepararlo |
POST /v1/orders/{orderId}/deny |
No puede; { "reason": "…" } es opcional |
POST /v1/orders/{orderId}/ready |
Listo para recoger o para entregar al repartidor |
POST /v1/orders/{orderId}/complete |
Entregado |
POST /v1/orders/{orderId}/cancel |
Debe cancelar después de aceptar; { "reason": "…" } es opcional |
POST /v1/orders/{orderId}/status |
Transición detallada (vea abajo) |
curl -X POST $MAPLE_BASE/orders/{orderId}/accept \ -H "Authorization: Bearer $MAPLE_KEY"{ "object": "order_decision", "order_id": "ord_...", "decision": "accept", "status": "received" }Si prefiere un solo endpoint, POST /v1/orders/{orderId}/status recibe una transición explícita:
{ "status": "ACCEPTED" }Valores válidos: ACCEPTED, READY, IN_DELIVERY, FULFILLED, REJECTED, STORE_CANCELLED. Para saber qué significa cada estado y las transiciones permitidas entre ellos, consulte el Ciclo de vida del pedido.
Opcional — Prevalide los pedidos
La prevalidación es opcional y se activa suscribiéndose a order.validation_requested:
- Si no se suscribe, no hay paso de validación. Maple envía
order.notificationdirectamente y su accept/deny es la única barrera. - Si se suscribe, Maple le pide confirmar que cada pedido se puede preparar (disponibilidad de artículos, viabilidad de precios, posibilidad de inyección en el POS) antes de enviar la notificación, y espera su respuesta. Por lo tanto, activarla es un compromiso: un pedido que no valide a tiempo se rechaza (vea abajo).
Ese es todo el mecanismo: la suscripción es el interruptor.
Una vez suscrito, responda a cada order.validation_requested con un resultado. Incluya el validation_id del data de ese evento para que el resultado quede asociado a ese intento; un resultado de un intento vencido o anterior se rechaza con 404:
curl -X POST $MAPLE_BASE/orders/{orderId}/validation_result \ -H "Authorization: Bearer $MAPLE_KEY" \ -H "Content-Type: application/json" \ -d '{ "validation_id": "<validation_id del evento>", "status": "valid", "partner_reference": "pos_quote_123" }'Para bloquear el pedido:
{ "validation_id": "<validation_id del evento>", "status": "invalid", "reason": { "code": "ITEM_AVAILABILITY", "explanation": "86'd", "item_external_ids": ["itm-latte"] }}Un resultado valid permite que el pedido pase a la notificación; invalid lo bloquea antes de que se cobre al cliente. Tiene unos 5 minutos para responder antes de que la solicitud venza (el recurso incluye expires_at, a ~300 segundos). Si vence, Maple solicita la validación una vez más; si esa segunda solicitud tampoco se responde, el pedido se rechaza sin notificación, así que suscríbase solo cuando su manejador responda de forma confiable y a tiempo. Es la misma verificación de disponibilidad que haría al aceptar, solo que antes, para que los clientes no paguen por algo que usted no puede preparar.
Cómo se ve “terminado”
| Usted construye | Usted obtiene |
|---|---|
| Un manejador de webhook que verifica, elimina duplicados y enruta | Pedidos en tiempo real con totales precalculados |
| Llamadas accept / deny / ready / complete / status | Los clientes ven el avance del pedido en vivo |
| (Opcional) respuestas de validación | Pedidos inválidos bloqueados antes de cobrar al cliente |
Un webhook y un puñado de llamadas de decisión. Los pagos y los precios quedan de nuestro lado, y el equipo de Maple está disponible durante toda su integración.