// Avria Sign — public endpoints: lookup envelope by token, submit signature import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.0"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", }; Deno.serve(async (req) => { if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders }); const url = new URL(req.url); const action = url.searchParams.get("action") || "lookup"; const admin = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!); try { if (action === "lookup") { const tokenStr = url.searchParams.get("token"); if (!tokenStr) return json({ error: "missing token" }, 400); const { data: recip, error: rErr } = await admin .from("signature_recipients") .select("id, envelope_id, name, email, status, signed_at") .eq("signing_token", tokenStr) .maybeSingle(); if (rErr || !recip) return json({ error: "invalid token" }, 404); const { data: env } = await admin .from("signature_envelopes") .select("id, document_name, document_url, signed_document_url, status, email_body, association_id, voided_at, voided_reason") .eq("id", recip.envelope_id) .maybeSingle(); // If voided, return early if (env?.status === "voided" || env?.voided_at) { return json({ recipient: recip, envelope: env, voided: true }); } // Fetch fields for this recipient const { data: fields } = await admin .from("signature_fields") .select("id, field_type, page_number, x_ratio, y_ratio, width_ratio, height_ratio, required") .eq("recipient_id", recip.id) .order("page_number"); // log view event (only first time) if (recip.status === "pending") { await admin.from("signature_events").insert({ envelope_id: recip.envelope_id, recipient_id: recip.id, event_type: "viewed", ip_address: req.headers.get("x-forwarded-for") || null, user_agent: req.headers.get("user-agent") || null, }); await admin.from("signature_recipients").update({ status: "viewed" }).eq("id", recip.id).eq("status", "pending"); } return json({ recipient: recip, envelope: env, fields: fields || [] }); } if (action === "sign" && req.method === "POST") { const body = await req.json(); const { token, signature_data_url, signature_method } = body; if (!token || !signature_data_url) return json({ error: "missing fields" }, 400); const { data: recip } = await admin .from("signature_recipients") .select("id, envelope_id, status") .eq("signing_token", token) .maybeSingle(); if (!recip) return json({ error: "invalid token" }, 404); if (recip.status === "signed") return json({ error: "already signed" }, 400); // Refuse if envelope voided const { data: envCheck } = await admin .from("signature_envelopes").select("status, voided_at").eq("id", recip.envelope_id).maybeSingle(); if (envCheck?.status === "voided" || envCheck?.voided_at) { return json({ error: "This envelope has been voided and can no longer be signed." }, 400); } const ip = req.headers.get("x-forwarded-for") || null; const ua = req.headers.get("user-agent") || null; await admin.from("signature_recipients").update({ signature_data_url, signature_method: signature_method || "draw", status: "signed", signed_at: new Date().toISOString(), signed_ip: ip, signed_user_agent: ua, }).eq("id", recip.id); await admin.from("signature_events").insert({ envelope_id: recip.envelope_id, recipient_id: recip.id, event_type: "signed", ip_address: ip, user_agent: ua, details: { method: signature_method || "draw" }, }); const { data: allRecips } = await admin .from("signature_recipients") .select("status") .eq("envelope_id", recip.envelope_id); const allSigned = (allRecips || []).every(r => r.status === "signed"); if (allSigned) { await admin.from("signature_envelopes").update({ status: "completed", completed_at: new Date().toISOString(), }).eq("id", recip.envelope_id); await admin.from("signature_events").insert({ envelope_id: recip.envelope_id, event_type: "completed", }); admin.functions.invoke("avria-sign-stamp", { body: { envelope_id: recip.envelope_id }, }).catch(e => console.warn("stamp invoke failed:", e)); } return json({ ok: true, all_signed: allSigned }); } return json({ error: "unknown action" }, 400); } catch (err: any) { console.error("avria-sign-public error:", err); return json({ error: err.message || String(err) }, 500); } }); function json(payload: unknown, status = 200) { return new Response(JSON.stringify(payload), { status, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); }