mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
3.0 KiB
TypeScript
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" },
|
|
});
|
|
}
|
|
}); |