fix subscription functions

This commit is contained in:
Adam Cohen Hillel 2025-04-21 16:17:30 +01:00
parent b525c56c26
commit 838070519a
3 changed files with 273 additions and 13 deletions

View File

@ -1,28 +1,283 @@
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";
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, {
// 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
});
const billingEndpoint = billingFunctionsWrapper(stripeHandler, {
allowedURLs: [defaultAllowedHost]
});
console.log("Stripe handler configured");
serve(async (req) => {
const response = await billingEndpoint(req);
return response;
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, [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"
}
}
);
}
});

View File

@ -8,11 +8,13 @@ import { setupNewSubscription } from "@/lib/actions/billing";
import { SubmitButton } from "@/components/ui/submit-button";
import { Button } from "@/components/ui/button";
import { siteConfig } from "@/lib/home";
// Create SUBSCRIPTION_PLANS using stripePriceId from siteConfig
export const SUBSCRIPTION_PLANS = {
FREE: 'price_1RGJ9GG6l1KZGqIroxSqgphC',
BASIC: 'price_1RGJ9LG6l1KZGqIrd9pwzeNW',
PRO: 'price_1RGJ9JG6l1KZGqIrVUU4ZRv6'
} as const;
FREE: siteConfig.cloudPricingItems.find(item => item.name === 'Free')?.stripePriceId || '',
PRO: siteConfig.cloudPricingItems.find(item => item.name === 'Pro')?.stripePriceId || '',
ENTERPRISE: siteConfig.cloudPricingItems.find(item => item.name === 'Enterprise')?.stripePriceId || '',
};
interface PlanComparisonProps {
accountId?: string | null;

View File

@ -90,6 +90,7 @@ export const siteConfig = {
"Single user",
"Standard response time",
],
stripePriceId: 'price_1RGJ9GG6l1KZGqIroxSqgphC',
},
{
name: "Pro",
@ -106,6 +107,7 @@ export const siteConfig = {
"5 team members",
"Custom integrations",
],
stripePriceId: 'price_1RGJ9LG6l1KZGqIrd9pwzeNW',
},
{
name: "Enterprise",
@ -124,6 +126,7 @@ export const siteConfig = {
"Custom AI model training",
],
showContactSales: true,
stripePriceId: 'price_1RGJ9JG6l1KZGqIrVUU4ZRv6',
},
],
companyShowcase: {