import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.1"; 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", }; serve(async (req) => { if (req.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!; const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!; const QBO_CLIENT_ID = Deno.env.get("QBO_CLIENT_ID"); const QBO_CLIENT_SECRET = Deno.env.get("QBO_CLIENT_SECRET"); if (!QBO_CLIENT_ID || !QBO_CLIENT_SECRET) { return new Response(JSON.stringify({ error: "QBO credentials not configured" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } const supabaseAdmin = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY); const url = new URL(req.url); try { if (req.method === "GET") { // Handle OAuth callback from QBO const code = url.searchParams.get("code"); const realmId = url.searchParams.get("realmId"); if (!code) { return new Response("Missing authorization code", { status: 400 }); } // Determine the redirect URI (same as this function URL) const redirectUri = `${SUPABASE_URL}/functions/v1/qbo-auth`; // Exchange code for tokens const tokenResp = await fetch("https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "Authorization": `Basic ${btoa(`${QBO_CLIENT_ID}:${QBO_CLIENT_SECRET}`)}`, }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: redirectUri, }), }); if (!tokenResp.ok) { const errText = await tokenResp.text(); console.error("Token exchange failed:", errText); return new Response(`Token exchange failed: ${errText}`, { status: 500 }); } const tokens = await tokenResp.json(); const expiry = (Date.now() + tokens.expires_in * 1000).toString(); // Store tokens in company_settings const entries: Record = { qbo_access_token: tokens.access_token, qbo_refresh_token: tokens.refresh_token, qbo_token_expiry: expiry, }; if (realmId) { entries.qbo_realm_id = realmId; } for (const [key, value] of Object.entries(entries)) { const { data: existing } = await supabaseAdmin.from("company_settings").select("id").eq("key", key).maybeSingle(); if (existing) { await supabaseAdmin.from("company_settings").update({ value }).eq("key", key); } else { await supabaseAdmin.from("company_settings").insert({ key, value }); } } // Return a success HTML page return new Response( `

✅ QuickBooks Connected!

Your QuickBooks Online account has been successfully linked.

You can close this window and return to the app.

`, { headers: { "Content-Type": "text/html" } }, ); } if (req.method === "POST") { // Generate the QBO authorization URL const body = await req.json(); const { action } = body; if (action === "get_auth_url") { const redirectUri = `${SUPABASE_URL}/functions/v1/qbo-auth`; const authUrl = `https://appcenter.intuit.com/connect/oauth2?` + `client_id=${QBO_CLIENT_ID}` + `&redirect_uri=${encodeURIComponent(redirectUri)}` + `&scope=com.intuit.quickbooks.accounting` + `&response_type=code` + `&state=qbo_connect`; return new Response(JSON.stringify({ authUrl, redirectUri }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } if (action === "check_status") { const { data: tokenSetting } = await supabaseAdmin .from("company_settings") .select("value") .eq("key", "qbo_refresh_token") .maybeSingle(); return new Response(JSON.stringify({ connected: !!tokenSetting?.value, }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } } return new Response(JSON.stringify({ error: "Method not allowed" }), { status: 405, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } catch (err) { console.error("QBO auth error:", err); return new Response(JSON.stringify({ error: err instanceof Error ? err.message : "Unknown error" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" }, }); } });