import { buildInvitationUrl, generateInvitationToken, getOrderUpdateForDeliveryInvitationAction, hashInvitationToken, normalizeAvailableSlots, resolvePublicAppUrl, } from "../_shared/delivery-invitations.ts"; import { channelFromProvider, createServiceClient, json } from "../_shared/chatbot.ts"; import { insertIntegrationEvent } from "../_shared/integration-events.ts"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", }; Deno.serve(async (request) => { if (request.method === "OPTIONS") { return new Response("ok", { headers: corsHeaders }); } if (request.method !== "POST") { return new Response(JSON.stringify({ error: "Method not allowed" }), { status: 405, headers: { ...corsHeaders, "Content-Type": "application/json", }, }); } try { const body = (await request.json()) as { orderId?: string; orderNumber?: string; customerName?: string; customerPhone?: string; customerMessenger?: string; availableSlots?: string[]; }; if (!body.orderId) { return json({ error: "orderId is required" }, 400); } const token = generateInvitationToken(); const tokenHash = await hashInvitationToken(token); const supabase = createServiceClient(); const orderUpdate = getOrderUpdateForDeliveryInvitationAction("create_delivery_invitation"); const { data: currentOrder, error: orderError } = await supabase .from("orders") .select("id, status, delivery_agreement_status, delivery_flow_started_at") .eq("id", body.orderId) .single(); if (orderError) { throw orderError; } const { data: existingInvitation, error: existingInvitationError } = await supabase .from("delivery_invitations") .select("id, state, available_slots, order_number, customer_name, customer_phone, customer_messenger, delivery_date, delivery_time, sent_at, opened_at, confirmed_at") .eq("order_id", body.orderId) .maybeSingle(); if (existingInvitationError) { throw existingInvitationError; } if (currentOrder.delivery_flow_started_at || existingInvitation) { return new Response( JSON.stringify({ ok: true, alreadyStarted: true, invitation: existingInvitation ? { orderId: body.orderId, state: existingInvitation.state, availableSlots: existingInvitation.available_slots || [], orderNumber: existingInvitation.order_number || body.orderNumber || null, customerName: existingInvitation.customer_name || body.customerName || null, customerPhone: existingInvitation.customer_phone || body.customerPhone || null, customerMessenger: existingInvitation.customer_messenger || body.customerMessenger || null, } : { orderId: body.orderId, state: "awaiting_choice", }, }), { headers: { ...corsHeaders, "Content-Type": "application/json", }, }, ); } const invitationPayload = { order_id: body.orderId, token_hash: tokenHash, state: "awaiting_choice", order_number: body.orderNumber || null, customer_name: body.customerName || null, customer_phone: body.customerPhone || null, customer_messenger: body.customerMessenger || null, available_slots: normalizeAvailableSlots(body.availableSlots), sent_at: new Date().toISOString(), }; const { error: invitationError } = await supabase.from("delivery_invitations").insert(invitationPayload); if (invitationError) { throw invitationError; } const { error: updateError } = await supabase .from("orders") .update({ status: orderUpdate?.status, delivery_agreement_status: orderUpdate?.deliveryAgreementStatus, ready_for_delivery_at: currentOrder.ready_for_delivery_at || new Date().toISOString(), delivery_flow_started_at: new Date().toISOString(), delivery_flow_source: body.source || "n8n", }) .eq("id", body.orderId); if (updateError) { throw updateError; } const { error: historyError } = await supabase.from("order_history").insert({ order_id: body.orderId, action: "Создание приглашения доставки", old_status: currentOrder.status, new_status: orderUpdate?.status, metadata: { old_delivery_agreement_status: currentOrder.delivery_agreement_status, new_delivery_agreement_status: orderUpdate?.deliveryAgreementStatus, channel: channelFromProvider("telegram"), }, }); if (historyError) { throw historyError; } await insertIntegrationEvent(supabase, { order_id: body.orderId, event_type: "delivery_invitation_created", direction: "outbound", status: "success", payload: { token_hash: tokenHash, available_slots: invitationPayload.available_slots, }, }); const publicBaseUrl = resolvePublicAppUrl(request); return new Response( JSON.stringify({ ok: true, invitation: { orderId: body.orderId, token, url: buildInvitationUrl(publicBaseUrl, token), state: "awaiting_choice", availableSlots: invitationPayload.available_slots, }, }), { headers: { ...corsHeaders, "Content-Type": "application/json", }, }, ); } catch (error) { return new Response( JSON.stringify({ ok: false, error: error instanceof Error ? error.message : "Unexpected error", }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json", }, }, ); } });