Files
acmcc/supabase/functions/stripe-transactions/index.ts
T
2026-06-01 20:19:26 -04:00

151 lines
5.2 KiB
TypeScript

import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version",
};
Deno.serve(async (req) => {
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
try {
const authHeader = req.headers.get("Authorization");
if (!authHeader) {
return new Response(JSON.stringify({ error: "Missing authorization" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
const supabaseAnonKey = Deno.env.get("SUPABASE_ANON_KEY")!;
const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
// Validate user via JWT claims (works with signing-keys auth)
const token = authHeader.replace("Bearer ", "");
const authClient = createClient(supabaseUrl, supabaseAnonKey);
const { data: claimsData, error: userError } = await authClient.auth.getClaims(token);
if (userError || !claimsData?.claims?.sub) {
console.error("Auth failed:", userError);
return new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const userId = claimsData.claims.sub;
// Check admin role
const serviceClient = createClient(supabaseUrl, serviceRoleKey);
const { data: roles } = await serviceClient
.from("user_roles")
.select("role")
.eq("user_id", userId);
const isAdmin = roles?.some((r: any) => ["admin", "manager"].includes(r.role));
if (!isAdmin) {
return new Response(JSON.stringify({ error: "Forbidden" }), {
status: 403,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const url = new URL(req.url);
const mappingId = url.searchParams.get("mapping_id");
const limit = Math.min(parseInt(url.searchParams.get("limit") || "10"), 25);
// Fetch the stripe account mapping(s)
let query = serviceClient
.from("stripe_account_mappings")
.select("id, stripe_secret_key, label, association_id, associations(name)")
.eq("is_active", true);
if (mappingId) {
query = query.eq("id", mappingId);
}
const { data: mappings, error: mapError } = await query;
if (mapError) throw mapError;
if (!mappings || mappings.length === 0) {
return new Response(JSON.stringify({ transactions: [], accounts: [] }), {
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
// If no specific mapping requested, return accounts list + transactions from first account
const accounts = mappings.map((m: any) => ({
id: m.id,
name: m.label || (m.associations as any)?.name || "Unknown Account",
hasSecretKey: !!m.stripe_secret_key,
}));
// Pick which mapping to fetch transactions from
const targetMapping = mappingId
? mappings[0]
: mappings[0]; // default to first
if (!targetMapping?.stripe_secret_key) {
return new Response(
JSON.stringify({
transactions: [],
accounts,
error: "No secret key configured for this account.",
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// Fetch recent charges from Stripe
const stripeRes = await fetch(
`https://api.stripe.com/v1/charges?limit=${limit}&expand[]=data.customer`,
{
headers: {
Authorization: `Bearer ${targetMapping.stripe_secret_key}`,
},
}
);
if (!stripeRes.ok) {
const errBody = await stripeRes.text();
console.error("Stripe API error:", stripeRes.status, errBody);
return new Response(
JSON.stringify({
transactions: [],
accounts,
error: `Stripe API error: ${stripeRes.status}`,
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const stripeData = await stripeRes.json();
const transactions = (stripeData.data || []).map((charge: any) => ({
id: charge.id,
amount: charge.amount,
currency: charge.currency,
status: charge.status,
created: charge.created,
description: charge.description || null,
customer_name: charge.customer?.name || null,
customer_email: charge.customer?.email || charge.billing_details?.email || null,
payment_method: charge.payment_method_details?.type || null,
}));
return new Response(
JSON.stringify({ transactions, accounts }),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error: unknown) {
console.error("Error in stripe-transactions:", error);
const message = error instanceof Error ? error.message : "Unknown error";
return new Response(JSON.stringify({ error: message }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});