fix: implement stripe portal and upgrade button logic
This commit is contained in:
parent
bc763b0f80
commit
3d4efa9281
3 changed files with 97 additions and 2 deletions
|
|
@ -166,7 +166,30 @@ const DashboardSubscription: React.FC<DashboardSubscriptionProps> = ({ user, pla
|
||||||
Obtenha análises ilimitadas, histórico completo e acesso às funcionalidades profissionais.
|
Obtenha análises ilimitadas, histórico completo e acesso às funcionalidades profissionais.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button className="bg-white hover:bg-gray-100 text-gray-900 font-bold px-8 py-3 rounded-xl transition-all shadow-lg hover:shadow-xl hover:-translate-y-0.5 whitespace-nowrap">
|
<button
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const { data: { session } } = await supabase.auth.getSession();
|
||||||
|
if (!session) return alert("Faça login novamente");
|
||||||
|
|
||||||
|
const res = await fetch(`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/stripe-checkout`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${session.access_token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ plan: "mensal" }) // Default to monthly
|
||||||
|
});
|
||||||
|
|
||||||
|
const { url, error } = await res.json();
|
||||||
|
if (error) throw new Error(error);
|
||||||
|
if (url) window.location.href = url;
|
||||||
|
} catch (e) {
|
||||||
|
alert("Erro ao iniciar checkout: " + e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="bg-white hover:bg-gray-100 text-gray-900 font-bold px-8 py-3 rounded-xl transition-all shadow-lg hover:shadow-xl hover:-translate-y-0.5 whitespace-nowrap"
|
||||||
|
>
|
||||||
Fazer Upgrade Agora
|
Fazer Upgrade Agora
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ const Dashboard: React.FC<DashboardProps> = ({ user, onLogout, onOpenAdmin, onOp
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/stripe-checkout`, {
|
const response = await fetch(`${import.meta.env.VITE_SUPABASE_URL}/functions/v1/stripe-portal`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
|
||||||
72
supabase/functions/stripe-portal/index.ts
Normal file
72
supabase/functions/stripe-portal/index.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
|
||||||
|
/// <reference lib="deno.ns" />
|
||||||
|
import Stripe from "npm:stripe@16.12.0";
|
||||||
|
|
||||||
|
const STRIPE_SECRET_KEY = Deno.env.get("STRIPE_SECRET_KEY") ?? "";
|
||||||
|
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
|
||||||
|
const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY") ?? "";
|
||||||
|
const SITE_URL = Deno.env.get("SITE_URL") ?? "http://localhost:3000";
|
||||||
|
|
||||||
|
const stripe = new Stripe(STRIPE_SECRET_KEY, { apiVersion: "2025-11-17.clover" });
|
||||||
|
|
||||||
|
const corsHeaders = {
|
||||||
|
"access-control-allow-origin": "*",
|
||||||
|
"access-control-allow-headers": "authorization, x-client-info, apikey, content-type",
|
||||||
|
"access-control-allow-methods": "POST, OPTIONS",
|
||||||
|
};
|
||||||
|
|
||||||
|
function json(data: unknown, status = 200) {
|
||||||
|
return new Response(JSON.stringify(data), {
|
||||||
|
status,
|
||||||
|
headers: { "content-type": "application/json", ...corsHeaders },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUserFromJwt(jwt: string) {
|
||||||
|
const res = await fetch(`${SUPABASE_URL}/auth/v1/user`, {
|
||||||
|
headers: {
|
||||||
|
apikey: SUPABASE_ANON_KEY,
|
||||||
|
authorization: `Bearer ${jwt}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!res.ok) return null;
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getStripeCustomerId(userId: string) {
|
||||||
|
const res = await fetch(`${SUPABASE_URL}/rest/v1/stripe_customers?user_id=eq.${userId}&select=stripe_customer_id`, {
|
||||||
|
headers: {
|
||||||
|
apikey: SUPABASE_ANON_KEY,
|
||||||
|
authorization: `Bearer ${SUPABASE_ANON_KEY}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const rows = await res.json();
|
||||||
|
return rows?.[0]?.stripe_customer_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.serve(async (req) => {
|
||||||
|
if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders });
|
||||||
|
if (req.method !== "POST") return json({ ok: false, error: "Method not allowed" }, 405);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auth = req.headers.get("authorization") || "";
|
||||||
|
const jwt = auth.startsWith("Bearer ") ? auth.slice(7) : "";
|
||||||
|
if (!jwt) return json({ ok: false, error: "Missing Authorization" }, 401);
|
||||||
|
|
||||||
|
const user = await getUserFromJwt(jwt);
|
||||||
|
if (!user?.id) return json({ ok: false, error: "Invalid token" }, 401);
|
||||||
|
|
||||||
|
const customerId = await getStripeCustomerId(user.id);
|
||||||
|
if (!customerId) return json({ ok: false, error: "Customer not found" }, 404);
|
||||||
|
|
||||||
|
const session = await stripe.billingPortal.sessions.create({
|
||||||
|
customer: customerId,
|
||||||
|
return_url: `${SITE_URL}/dashboard`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return json({ ok: true, url: session.url });
|
||||||
|
} catch (err) {
|
||||||
|
return json({ ok: false, error: String((err as any)?.message ?? err) }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue