Files
acmcc/supabase/functions/avria-sign-public/index.ts
T
2026-06-01 20:19:26 -04:00

140 lines
5.1 KiB
TypeScript

// 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" },
});
}