mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
143 lines
5.1 KiB
TypeScript
143 lines
5.1 KiB
TypeScript
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<string, string> = {
|
|
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(
|
|
`<!DOCTYPE html><html><body style="font-family:sans-serif;text-align:center;padding:60px">
|
|
<h1>✅ QuickBooks Connected!</h1>
|
|
<p>Your QuickBooks Online account has been successfully linked.</p>
|
|
<p>You can close this window and return to the app.</p>
|
|
<script>setTimeout(()=>window.close(),3000)</script>
|
|
</body></html>`,
|
|
{ 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" },
|
|
});
|
|
}
|
|
});
|