mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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" },
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user