Files
alva/supabase/functions/create-portal-session/index.ts
T
gpt-engineer-app[bot] 5c2357fdc1 Changes
Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com>
2026-07-11 18:19:54 +00:00

86 lines
2.9 KiB
TypeScript

import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.0";
import { z } from "npm:zod@3.23.8";
import { type StripeEnv, createStripeClient } from "../_shared/stripe.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, x-supabase-client-platform, x-supabase-api-version, apikey, content-type",
"Access-Control-Allow-Methods": "POST, OPTIONS",
};
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);
const BodySchema = z.object({
return_url: z.string().url(),
environment: z.enum(["sandbox", "live"]),
});
Deno.serve(async (req) => {
if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });
if (req.method !== "POST") {
return new Response(JSON.stringify({ error: "Method not allowed" }), {
status: 405,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
try {
const token = req.headers.get("Authorization")?.replace("Bearer ", "") ?? "";
const { data: userData, error: authErr } = await supabase.auth.getUser(token);
if (authErr || !userData?.user) {
return new Response(JSON.stringify({ error: "Ej inloggad" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const parsed = BodySchema.safeParse(await req.json());
if (!parsed.success) {
return new Response(JSON.stringify({ error: "Ogiltiga fält" }), {
status: 400,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
const env: StripeEnv = parsed.data.environment;
// Hämta senaste subscription för denna user i rätt env
const { data: sub } = await supabase
.from("subscriptions")
.select("stripe_customer_id")
.eq("user_id", userData.user.id)
.eq("environment", env)
.order("created_at", { ascending: false })
.limit(1)
.maybeSingle();
if (!sub?.stripe_customer_id) {
return new Response(
JSON.stringify({ error: "Inget abonnemang hittades för detta konto." }),
{ status: 404, headers: { ...corsHeaders, "Content-Type": "application/json" } },
);
}
const stripe = createStripeClient(env);
const portal = await stripe.billingPortal.sessions.create({
customer: sub.stripe_customer_id as string,
return_url: parsed.data.return_url,
});
return new Response(JSON.stringify({ url: portal.url }), {
status: 200,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
} catch (e: any) {
console.error("create-portal-session error:", e);
return new Response(JSON.stringify({ error: e?.message ?? "Kunde inte öppna kundportal" }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
});