mirror of https://github.com/kortix-ai/suna.git
285 lines
13 KiB
TypeScript
285 lines
13 KiB
TypeScript
import {serve} from "https://deno.land/std@0.168.0/http/server.ts";
|
|
import {stripeFunctionHandler} from "https://deno.land/x/basejump@v2.0.3/billing-functions/mod.ts";
|
|
import { requireAuthorizedBillingUser } from "https://deno.land/x/basejump@v2.0.3/billing-functions/src/require-authorized-billing-user.ts";
|
|
import getBillingStatus from "https://deno.land/x/basejump@v2.0.3/billing-functions/src/wrappers/get-billing-status.ts";
|
|
import createSupabaseServiceClient from "https://deno.land/x/basejump@v2.0.3/billing-functions/lib/create-supabase-service-client.ts";
|
|
import validateUrl from "https://deno.land/x/basejump@v2.0.3/billing-functions/lib/validate-url.ts";
|
|
|
|
import Stripe from "https://esm.sh/stripe@11.1.0?target=deno";
|
|
|
|
console.log("Starting billing functions...");
|
|
|
|
const defaultAllowedHost = Deno.env.get("ALLOWED_HOST") || "http://localhost:3000";
|
|
const allowedHosts = [defaultAllowedHost, "https://www.suna.so", "https://suna.so", "https://staging.suna.so"];
|
|
console.log("Default allowed host:", defaultAllowedHost);
|
|
|
|
export const corsHeaders = {
|
|
"Access-Control-Allow-Origin": "*",
|
|
"Access-Control-Allow-Headers":
|
|
"authorization, x-client-info, apikey, content-type",
|
|
};
|
|
|
|
console.log("Initializing Stripe client...");
|
|
const stripeClient = new Stripe(Deno.env.get("STRIPE_API_KEY") as string, {
|
|
// This is needed to use the Fetch API rather than relying on the Node http
|
|
// package.
|
|
apiVersion: "2022-11-15",
|
|
httpClient: Stripe.createFetchHttpClient(),
|
|
});
|
|
console.log("Stripe client initialized");
|
|
|
|
console.log("Setting up stripe handler...");
|
|
const stripeHandler = stripeFunctionHandler({
|
|
stripeClient,
|
|
defaultPlanId: Deno.env.get("STRIPE_DEFAULT_PLAN_ID") as string,
|
|
defaultTrialDays: Deno.env.get("STRIPE_DEFAULT_TRIAL_DAYS") ? Number(Deno.env.get("STRIPE_DEFAULT_TRIAL_DAYS")) : undefined
|
|
});
|
|
console.log("Stripe handler configured");
|
|
|
|
serve(async (req) => {
|
|
console.log("Received request:", req.method, req.url);
|
|
|
|
if (req.method === "OPTIONS") {
|
|
console.log("Handling OPTIONS request");
|
|
return new Response("ok", {headers: corsHeaders});
|
|
}
|
|
|
|
try {
|
|
const body = await req.json();
|
|
console.log("Request body:", body);
|
|
|
|
if (!body.args?.account_id) {
|
|
console.log("Missing account_id in request");
|
|
return new Response(
|
|
JSON.stringify({ error: "Account id is required" }),
|
|
{
|
|
status: 400,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
switch (body.action) {
|
|
case "get_plans":
|
|
console.log("Getting plans");
|
|
try {
|
|
const plans = await stripeHandler.getPlans(body.args);
|
|
console.log("Plans retrieved:", plans.length);
|
|
return new Response(
|
|
JSON.stringify(plans),
|
|
{
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json",
|
|
},
|
|
}
|
|
);
|
|
} catch (e) {
|
|
console.log("Error getting plans:", e);
|
|
return new Response(
|
|
JSON.stringify({ error: "Failed to get plans" }),
|
|
{
|
|
status: 500,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
case "get_billing_portal_url":
|
|
console.log("Getting billing portal URL for account:", body.args.account_id);
|
|
if (!validateUrl(body.args.return_url, allowedHosts)) {
|
|
console.log("Invalid return URL:", body.args.return_url);
|
|
return new Response(
|
|
JSON.stringify({ error: "Return url is not allowed" }),
|
|
{
|
|
status: 400,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
return await requireAuthorizedBillingUser(req, {
|
|
accountId: body.args.account_id,
|
|
authorizedRoles: ["owner"],
|
|
async onBillableAndAuthorized(roleInfo) {
|
|
console.log("User authorized for billing portal, role info:", roleInfo);
|
|
try {
|
|
const response = await stripeHandler.getBillingPortalUrl({
|
|
accountId: roleInfo.account_id,
|
|
subscriptionId: roleInfo.billing_subscription_id,
|
|
customerId: roleInfo.billing_customer_id,
|
|
returnUrl: body.args.return_url,
|
|
});
|
|
console.log("Billing portal URL generated");
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
billing_enabled: roleInfo.billing_enabled,
|
|
...response,
|
|
}),
|
|
{
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json",
|
|
},
|
|
}
|
|
);
|
|
} catch (e) {
|
|
console.log("Error getting billing portal URL:", e);
|
|
return new Response(
|
|
JSON.stringify({ error: "Failed to generate billing portal URL" }),
|
|
{
|
|
status: 500,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
},
|
|
});
|
|
|
|
case "get_new_subscription_url":
|
|
console.log("Getting new subscription URL for account:", body.args.account_id);
|
|
if (!validateUrl(body.args.success_url, allowedHosts) || !validateUrl(body.args.cancel_url, allowedHosts)) {
|
|
console.log("Invalid success or cancel URL:", body.args.success_url, body.args.cancel_url);
|
|
return new Response(
|
|
JSON.stringify({ error: "Success or cancel url is not allowed" }),
|
|
{
|
|
status: 400,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
return await requireAuthorizedBillingUser(req, {
|
|
accountId: body.args.account_id,
|
|
authorizedRoles: ["owner"],
|
|
async onBillableAndAuthorized(roleInfo) {
|
|
console.log("User authorized for new subscription, role info:", roleInfo);
|
|
try {
|
|
const response = await stripeHandler.getNewSubscriptionUrl({
|
|
accountId: roleInfo.account_id,
|
|
planId: body.args.plan_id,
|
|
successUrl: body.args.success_url,
|
|
cancelUrl: body.args.cancel_url,
|
|
billingEmail: roleInfo.billing_email,
|
|
customerId: roleInfo.billing_customer_id,
|
|
});
|
|
console.log("New subscription URL generated");
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
billing_enabled: roleInfo.billing_enabled,
|
|
...response,
|
|
}),
|
|
{
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json",
|
|
},
|
|
}
|
|
);
|
|
} catch (e) {
|
|
console.log("Error getting new subscription URL:", e);
|
|
return new Response(
|
|
JSON.stringify({ error: "Failed to generate new subscription URL" }),
|
|
{
|
|
status: 500,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
},
|
|
});
|
|
|
|
case "get_billing_status":
|
|
console.log("Getting billing status for account:", body.args.account_id);
|
|
return await requireAuthorizedBillingUser(req, {
|
|
accountId: body.args.account_id,
|
|
authorizedRoles: ["owner"],
|
|
async onBillableAndAuthorized(roleInfo) {
|
|
console.log("User authorized, role info:", roleInfo);
|
|
const supabaseClient = createSupabaseServiceClient();
|
|
console.log("Getting billing status...");
|
|
try {
|
|
const response = await getBillingStatus(
|
|
supabaseClient,
|
|
roleInfo,
|
|
stripeHandler
|
|
);
|
|
console.log("Billing status response:", response);
|
|
|
|
return new Response(
|
|
JSON.stringify({
|
|
...response,
|
|
status: response.status || "not_setup",
|
|
billing_enabled: roleInfo.billing_enabled,
|
|
}),
|
|
{
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json",
|
|
},
|
|
}
|
|
);
|
|
} catch (e) {
|
|
console.log("Error getting billing status:", e);
|
|
return new Response(
|
|
JSON.stringify({ error: "Internal server error" }),
|
|
{
|
|
status: 500,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
},
|
|
});
|
|
|
|
default:
|
|
console.log("Invalid action requested:", body.action);
|
|
return new Response(
|
|
JSON.stringify({ error: "Invalid action" }),
|
|
{
|
|
status: 400,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
} catch (e) {
|
|
console.log("Error processing request:", e);
|
|
return new Response(
|
|
JSON.stringify({ error: "Internal server error" }),
|
|
{
|
|
status: 500,
|
|
headers: {
|
|
...corsHeaders,
|
|
"Content-Type": "application/json"
|
|
}
|
|
}
|
|
);
|
|
}
|
|
});
|