Files
2026-06-01 20:19:26 -04:00

93 lines
3.0 KiB
TypeScript

import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.39.3";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, content-type",
};
function stripeHeaders(secretKey: string, accountId?: string | null) {
return {
Authorization: `Bearer ${secretKey}`,
...(accountId?.startsWith("acct_") ? { "Stripe-Account": accountId } : {}),
};
}
serve(async (req) => {
if (req.method === "OPTIONS") return new Response(null, { headers: corsHeaders });
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const webhookUrl = `${supabaseUrl}/functions/v1/stripe-webhook`;
// Today 00:00 UTC -> unix
const now = new Date();
const startOfDay = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
const createdGte = Math.floor(startOfDay.getTime() / 1000);
const results: any[] = [];
try {
const { data: mappings, error: mErr } = await supabase
.from("stripe_account_mappings")
.select("association_id, stripe_account_id, stripe_secret_key")
.eq("is_active", true);
if (mErr) throw mErr;
for (const m of mappings || []) {
if (!m.stripe_secret_key) continue;
const url = `https://api.stripe.com/v1/payment_intents?limit=100&created[gte]=${createdGte}`;
const r = await fetch(url, { headers: stripeHeaders(m.stripe_secret_key, m.stripe_account_id) });
if (!r.ok) {
results.push({ association_id: m.association_id, error: `list failed: ${r.status} ${await r.text()}` });
continue;
}
const list = await r.json();
let processed = 0;
let posted = 0;
for (const pi of list.data || []) {
if (pi.status !== "succeeded") continue;
processed++;
const fakeEvent = {
id: `evt_backfill_${pi.id}`,
type: "payment_intent.succeeded",
account: m.stripe_account_id,
data: { object: pi },
};
const wr = await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(fakeEvent),
});
if (wr.ok) posted++;
else console.error("webhook replay failed", pi.id, wr.status, await wr.text());
}
results.push({
association_id: m.association_id,
stripe_account_id: m.stripe_account_id,
succeeded_today: processed,
replayed: posted,
});
}
return new Response(JSON.stringify({ ok: true, since: startOfDay.toISOString(), results }), {
status: 200,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
} catch (err) {
console.error("backfill error:", err);
return new Response(JSON.stringify({ error: (err as Error).message, results }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});