mirror of https://github.com/kortix-ai/suna.git
fix subscription functions
This commit is contained in:
parent
b525c56c26
commit
838070519a
|
@ -1,28 +1,283 @@
|
||||||
import {serve} from "https://deno.land/std@0.168.0/http/server.ts";
|
import {serve} from "https://deno.land/std@0.168.0/http/server.ts";
|
||||||
import {billingFunctionsWrapper, stripeFunctionHandler} from "https://deno.land/x/basejump@v2.0.3/billing-functions/mod.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";
|
import Stripe from "https://esm.sh/stripe@11.1.0?target=deno";
|
||||||
|
|
||||||
const defaultAllowedHost = Deno.env.get("ALLOWED_HOST") || "http://localhost:3000";
|
console.log("Starting billing functions...");
|
||||||
|
|
||||||
|
const defaultAllowedHost = Deno.env.get("ALLOWED_HOST") || "http://localhost:3000";
|
||||||
|
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, {
|
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
|
// This is needed to use the Fetch API rather than relying on the Node http
|
||||||
// package.
|
// package.
|
||||||
apiVersion: "2022-11-15",
|
apiVersion: "2022-11-15",
|
||||||
httpClient: Stripe.createFetchHttpClient(),
|
httpClient: Stripe.createFetchHttpClient(),
|
||||||
});
|
});
|
||||||
|
console.log("Stripe client initialized");
|
||||||
|
|
||||||
|
console.log("Setting up stripe handler...");
|
||||||
const stripeHandler = stripeFunctionHandler({
|
const stripeHandler = stripeFunctionHandler({
|
||||||
stripeClient,
|
stripeClient,
|
||||||
defaultPlanId: Deno.env.get("STRIPE_DEFAULT_PLAN_ID") as string,
|
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
|
defaultTrialDays: Deno.env.get("STRIPE_DEFAULT_TRIAL_DAYS") ? Number(Deno.env.get("STRIPE_DEFAULT_TRIAL_DAYS")) : undefined
|
||||||
});
|
});
|
||||||
|
console.log("Stripe handler configured");
|
||||||
const billingEndpoint = billingFunctionsWrapper(stripeHandler, {
|
|
||||||
allowedURLs: [defaultAllowedHost]
|
|
||||||
});
|
|
||||||
|
|
||||||
serve(async (req) => {
|
serve(async (req) => {
|
||||||
const response = await billingEndpoint(req);
|
console.log("Received request:", req.method, req.url);
|
||||||
return response;
|
|
||||||
});
|
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, [defaultAllowedHost])) {
|
||||||
|
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, [defaultAllowedHost]) || !validateUrl(body.args.cancel_url, [defaultAllowedHost])) {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -8,11 +8,13 @@ import { setupNewSubscription } from "@/lib/actions/billing";
|
||||||
import { SubmitButton } from "@/components/ui/submit-button";
|
import { SubmitButton } from "@/components/ui/submit-button";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { siteConfig } from "@/lib/home";
|
import { siteConfig } from "@/lib/home";
|
||||||
|
|
||||||
|
// Create SUBSCRIPTION_PLANS using stripePriceId from siteConfig
|
||||||
export const SUBSCRIPTION_PLANS = {
|
export const SUBSCRIPTION_PLANS = {
|
||||||
FREE: 'price_1RGJ9GG6l1KZGqIroxSqgphC',
|
FREE: siteConfig.cloudPricingItems.find(item => item.name === 'Free')?.stripePriceId || '',
|
||||||
BASIC: 'price_1RGJ9LG6l1KZGqIrd9pwzeNW',
|
PRO: siteConfig.cloudPricingItems.find(item => item.name === 'Pro')?.stripePriceId || '',
|
||||||
PRO: 'price_1RGJ9JG6l1KZGqIrVUU4ZRv6'
|
ENTERPRISE: siteConfig.cloudPricingItems.find(item => item.name === 'Enterprise')?.stripePriceId || '',
|
||||||
} as const;
|
};
|
||||||
|
|
||||||
interface PlanComparisonProps {
|
interface PlanComparisonProps {
|
||||||
accountId?: string | null;
|
accountId?: string | null;
|
||||||
|
|
|
@ -90,6 +90,7 @@ export const siteConfig = {
|
||||||
"Single user",
|
"Single user",
|
||||||
"Standard response time",
|
"Standard response time",
|
||||||
],
|
],
|
||||||
|
stripePriceId: 'price_1RGJ9GG6l1KZGqIroxSqgphC',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Pro",
|
name: "Pro",
|
||||||
|
@ -106,6 +107,7 @@ export const siteConfig = {
|
||||||
"5 team members",
|
"5 team members",
|
||||||
"Custom integrations",
|
"Custom integrations",
|
||||||
],
|
],
|
||||||
|
stripePriceId: 'price_1RGJ9LG6l1KZGqIrd9pwzeNW',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Enterprise",
|
name: "Enterprise",
|
||||||
|
@ -124,6 +126,7 @@ export const siteConfig = {
|
||||||
"Custom AI model training",
|
"Custom AI model training",
|
||||||
],
|
],
|
||||||
showContactSales: true,
|
showContactSales: true,
|
||||||
|
stripePriceId: 'price_1RGJ9JG6l1KZGqIrVUU4ZRv6',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
companyShowcase: {
|
companyShowcase: {
|
||||||
|
|
Loading…
Reference in New Issue