import type { ActionFunctionArgs } from "@remix-run/node"; import { authenticate } from "../shopify.server"; import { createBookingFromOrder, type OrderWebhookPayload } from "../services/booking.server"; // Converts the cart's dd_* attributes into a confirmed Booking, releases // the matching Redis hold, and writes the slot back onto the order via a // metafield so staff (and, later, POS) see it natively on the order record // — IMPLEMENTATION_PLAN.md §5.2 / §2 data flow. Idempotent: Shopify // redelivers webhooks, and booking.server.ts's upsert-on-orderId handles // that safely. export const action = async ({ request }: ActionFunctionArgs) => { const { shop, topic, payload, admin } = await authenticate.webhook(request); console.log(`Received ${topic} webhook for ${shop}`); const order = payload as unknown as OrderWebhookPayload; await createBookingFromOrder(shop, order); if (admin) { const bookingSummary = summarizeBookingAttributes(order); if (bookingSummary) { await admin.graphql( `#graphql mutation setBookingMetafield($metafields: [MetafieldsSetInput!]!) { metafieldsSet(metafields: $metafields) { userErrors { field message } } }`, { variables: { metafields: [ { ownerId: order.admin_graphql_api_id, namespace: "delivery_datetime", key: "booking", type: "json", value: JSON.stringify(bookingSummary), }, ], }, }, ); } } return new Response(); }; function summarizeBookingAttributes(order: OrderWebhookPayload) { const attrs = order.note_attributes ?? []; const get = (key: string) => attrs.find((a) => a.name === key)?.value; const method = get("dd_method"); const date = get("dd_date"); const startMin = get("dd_start_min"); const endMin = get("dd_end_min"); const locationId = get("dd_location_id"); if (!method || !date || !startMin || !endMin || !locationId) return null; return { method, date, startMin: Number(startMin), endMin: Number(endMin), locationId }; }