diff --git a/.env.development b/.env.development index 84670f852..0eca799ff 100644 --- a/.env.development +++ b/.env.development @@ -40,12 +40,6 @@ CONFIG_PATH=${PWD}/config.json # Path to the sourcebot config file (if one exist # Redis REDIS_URL="redis://localhost:6379" -# Stripe -# STRIPE_SECRET_KEY: z.string().optional(), -# STRIPE_PRODUCT_ID: z.string().optional(), -# STRIPE_WEBHOOK_SECRET: z.string().optional(), -# STRIPE_ENABLE_TEST_CLOCKS=false - # Agents # GITHUB_APP_ID= diff --git a/packages/db/prisma/migrations/20260401200619_remove_stripe_billing/migration.sql b/packages/db/prisma/migrations/20260401200619_remove_stripe_billing/migration.sql new file mode 100644 index 000000000..436c17849 --- /dev/null +++ b/packages/db/prisma/migrations/20260401200619_remove_stripe_billing/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - You are about to drop the column `stripeCustomerId` on the `Org` table. All the data in the column will be lost. + - You are about to drop the column `stripeLastUpdatedAt` on the `Org` table. All the data in the column will be lost. + - You are about to drop the column `stripeSubscriptionStatus` on the `Org` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Org" DROP COLUMN "stripeCustomerId", +DROP COLUMN "stripeLastUpdatedAt", +DROP COLUMN "stripeSubscriptionStatus"; + +-- DropEnum +DROP TYPE "StripeSubscriptionStatus"; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 609c71b89..0c58714b6 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -19,11 +19,6 @@ enum ConnectionSyncStatus { FAILED } -enum StripeSubscriptionStatus { - ACTIVE - INACTIVE -} - enum ChatVisibility { PRIVATE PUBLIC @@ -283,10 +278,6 @@ model Org { memberApprovalRequired Boolean @default(true) - stripeCustomerId String? - stripeSubscriptionStatus StripeSubscriptionStatus? - stripeLastUpdatedAt DateTime? - /// List of pending invites to this organization invites Invite[] diff --git a/packages/shared/src/entitlements.ts b/packages/shared/src/entitlements.ts index 0effb0a58..de841a0dd 100644 --- a/packages/shared/src/entitlements.ts +++ b/packages/shared/src/entitlements.ts @@ -30,7 +30,6 @@ export type Plan = keyof typeof planLabels; // eslint-disable-next-line @typescript-eslint/no-unused-vars const entitlements = [ "search-contexts", - "billing", "anonymous-access", "multi-tenancy", "sso", diff --git a/packages/shared/src/env.server.ts b/packages/shared/src/env.server.ts index 95006d875..3b852cac1 100644 --- a/packages/shared/src/env.server.ts +++ b/packages/shared/src/env.server.ts @@ -163,12 +163,6 @@ const options = { SMTP_PASSWORD: z.string().optional(), EMAIL_FROM_ADDRESS: z.string().email().optional(), - // Stripe - STRIPE_SECRET_KEY: z.string().optional(), - STRIPE_PRODUCT_ID: z.string().optional(), - STRIPE_WEBHOOK_SECRET: z.string().optional(), - STRIPE_ENABLE_TEST_CLOCKS: booleanSchema.default('false'), - LOGTAIL_TOKEN: z.string().optional(), LOGTAIL_HOST: z.string().url().optional(), diff --git a/packages/web/package.json b/packages/web/package.json index 35eefe539..ceee7d2a8 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -11,7 +11,6 @@ "openapi:generate": "tsx tools/generateOpenApi.ts", "generate:protos": "proto-loader-gen-types --includeComments --longs=Number --enums=String --defaults --oneofs --grpcLib=@grpc/grpc-js --keepCase --includeDirs=../../vendor/zoekt/grpc/protos --outDir=src/proto zoekt/webserver/v1/webserver.proto zoekt/webserver/v1/query.proto", "dev:emails": "email dev --dir ./src/emails", - "stripe:listen": "stripe listen --forward-to http://localhost:3000/api/stripe", "tool:decrypt-jwe": "tsx tools/decryptJWE.ts" }, "dependencies": { @@ -102,8 +101,6 @@ "@sourcebot/schemas": "workspace:*", "@sourcebot/shared": "workspace:*", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", - "@stripe/react-stripe-js": "^3.1.1", - "@stripe/stripe-js": "^5.6.0", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-query": "^5.53.3", "@tanstack/react-table": "^8.20.5", @@ -187,7 +184,6 @@ "slate-history": "^0.113.1", "slate-react": "^0.117.1", "strip-json-comments": "^5.0.1", - "stripe": "^17.6.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "use-stick-to-bottom": "^1.1.3", diff --git a/packages/web/src/__mocks__/prisma.ts b/packages/web/src/__mocks__/prisma.ts index 8a0d9544c..acf687294 100644 --- a/packages/web/src/__mocks__/prisma.ts +++ b/packages/web/src/__mocks__/prisma.ts @@ -19,9 +19,6 @@ export const MOCK_ORG: Org = { imageUrl: null, metadata: null, memberApprovalRequired: false, - stripeCustomerId: null, - stripeSubscriptionStatus: null, - stripeLastUpdatedAt: null, inviteLinkEnabled: false, inviteLinkId: null } diff --git a/packages/web/src/actions.ts b/packages/web/src/actions.ts index 60ffe332e..f3581cbed 100644 --- a/packages/web/src/actions.ts +++ b/packages/web/src/actions.ts @@ -10,7 +10,7 @@ import { prisma } from "@/prisma"; import { render } from "@react-email/components"; import * as Sentry from '@sentry/nextjs'; import { generateApiKey, getTokenFromConfig, hashSecret } from "@sourcebot/shared"; -import { ApiKey, ConnectionSyncJobStatus, Org, OrgRole, Prisma, RepoIndexingJobStatus, RepoIndexingJobType, StripeSubscriptionStatus } from "@sourcebot/db"; +import { ApiKey, ConnectionSyncJobStatus, Org, OrgRole, Prisma, RepoIndexingJobStatus, RepoIndexingJobType } from "@sourcebot/db"; import { createLogger } from "@sourcebot/shared"; import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type"; import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type"; @@ -22,8 +22,6 @@ import { createTransport } from "nodemailer"; import { Octokit } from "octokit"; import { auth } from "./auth"; import { getOrgFromDomain } from "./data/org"; -import { getSubscriptionForOrg } from "./ee/features/billing/serverUtils"; -import { IS_BILLING_ENABLED } from "./ee/features/billing/stripe"; import InviteUserEmail from "./emails/inviteUserEmail"; import JoinRequestApprovedEmail from "./emails/joinRequestApprovedEmail"; import JoinRequestSubmittedEmail from "./emails/joinRequestSubmittedEmail"; @@ -188,31 +186,12 @@ export const withTenancyModeEnforcement = async(mode: TenancyMode, fn: () => export const completeOnboarding = async (domain: string): Promise<{ success: boolean } | ServiceError> => sew(() => withAuth((userId) => withOrgMembership(userId, domain, async ({ org }) => { - // If billing is not enabled, we can just mark the org as onboarded. - if (!IS_BILLING_ENABLED) { - await prisma.org.update({ - where: { id: org.id }, - data: { - isOnboarded: true, - } - }); - - // Else, validate that the org has an active subscription. - } else { - const subscriptionOrError = await getSubscriptionForOrg(org.id, prisma); - if (isServiceError(subscriptionOrError)) { - return subscriptionOrError; + await prisma.org.update({ + where: { id: org.id }, + data: { + isOnboarded: true, } - - await prisma.org.update({ - where: { id: org.id }, - data: { - isOnboarded: true, - stripeSubscriptionStatus: StripeSubscriptionStatus.ACTIVE, - stripeLastUpdatedAt: new Date(), - } - }); - } + }); return { success: true, diff --git a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx index 8c70f5699..79b671539 100644 --- a/packages/web/src/app/[domain]/components/navigationMenu/index.tsx +++ b/packages/web/src/app/[domain]/components/navigationMenu/index.tsx @@ -4,8 +4,6 @@ import { auth } from "@/auth"; import { Button } from "@/components/ui/button"; import { NavigationMenu as NavigationMenuBase } from "@/components/ui/navigation-menu"; import { Separator } from "@/components/ui/separator"; -import { getSubscriptionInfo } from "@/ee/features/billing/actions"; -import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; import { env } from "@sourcebot/shared"; import { ServiceErrorException } from "@/lib/serviceError"; import { isServiceError } from "@/lib/utils"; @@ -16,7 +14,6 @@ import { MeControlDropdownMenu } from "../meControlDropdownMenu"; import WhatsNewIndicator from "../whatsNewIndicator"; import { NavigationItems } from "./navigationItems"; import { ProgressIndicator } from "./progressIndicator"; -import { TrialIndicator } from "./trialIndicator"; import { redirect } from "next/navigation"; import { AppearanceDropdownMenu } from "../appearanceDropdownMenu"; @@ -28,7 +25,6 @@ interface NavigationMenuProps { export const NavigationMenu = async ({ domain, }: NavigationMenuProps) => { - const subscription = IS_BILLING_ENABLED ? await getSubscriptionInfo(domain) : null; const session = await auth(); const isAuthenticated = session?.user !== undefined; @@ -134,7 +130,6 @@ export const NavigationMenu = async ({ numberOfReposWithFirstTimeIndexingJobsInProgress={numberOfReposWithFirstTimeIndexingJobsInProgress} sampleRepos={sampleRepos} /> - {session ? ( { - const domain = useDomain(); - const captureEvent = useCaptureEvent(); - - if (isServiceError(subscription)) { - captureEvent('wa_trial_nav_subscription_fetch_fail', { - errorCode: subscription.errorCode, - }); - return null; - } - - if (!subscription || subscription.status !== "trialing") { - return null; - } - - return ( - captureEvent('wa_trial_nav_pressed', {})}> -
- - - {/* eslint-disable-next-line react-hooks/purity -- Date.now() during render is intentional for displaying remaining trial days */} - {Math.ceil((subscription.nextBillingDate * 1000 - Date.now()) / (1000 * 60 * 60 * 24))} days left in trial - -
- - ); -}; diff --git a/packages/web/src/app/[domain]/components/upgradeGuard.tsx b/packages/web/src/app/[domain]/components/upgradeGuard.tsx deleted file mode 100644 index f948eafa7..000000000 --- a/packages/web/src/app/[domain]/components/upgradeGuard.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client'; - -import { Redirect } from "@/app/components/redirect"; -import { useDomain } from "@/hooks/useDomain"; -import { usePathname } from "next/navigation"; -import { useMemo } from "react"; - -interface UpgradeGuardProps { - children: React.ReactNode; -} - -export const UpgradeGuard = ({ children }: UpgradeGuardProps) => { - const domain = useDomain(); - const pathname = usePathname(); - - const content = useMemo(() => { - if (!pathname.endsWith('/upgrade')) { - return ( - - ) - } else { - return children; - } - }, [domain, children, pathname]); - - return content; -} - - diff --git a/packages/web/src/app/[domain]/layout.tsx b/packages/web/src/app/[domain]/layout.tsx index 3a1f48b05..3a4371cae 100644 --- a/packages/web/src/app/[domain]/layout.tsx +++ b/packages/web/src/app/[domain]/layout.tsx @@ -3,16 +3,13 @@ import { auth } from "@/auth"; import { getOrgFromDomain } from "@/data/org"; import { isServiceError } from "@/lib/utils"; import { OnboardGuard } from "./components/onboardGuard"; -import { UpgradeGuard } from "./components/upgradeGuard"; import { cookies, headers } from "next/headers"; import { getSelectorsByUserAgent } from "react-device-detect"; import { MobileUnsupportedSplashScreen } from "./components/mobileUnsupportedSplashScreen"; import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME } from "@/lib/constants"; import { SyntaxReferenceGuide } from "./components/syntaxReferenceGuide"; import { SyntaxGuideProvider } from "./components/syntaxGuideProvider"; -import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; import { notFound, redirect } from "next/navigation"; -import { getSubscriptionInfo } from "@/ee/features/billing/actions"; import { PendingApprovalCard } from "./components/pendingApproval"; import { SubmitJoinRequest } from "./components/submitJoinRequest"; import { hasEntitlement } from "@sourcebot/shared"; @@ -152,23 +149,6 @@ export default async function Layout(props: LayoutProps) { } } - if (IS_BILLING_ENABLED) { - const subscription = await getSubscriptionInfo(domain); - if ( - subscription && - ( - isServiceError(subscription) || - (subscription.status !== "active" && subscription.status !== "trialing") - ) - ) { - return ( - - {children} - - ) - } - } - const headersList = await headers(); const cookieStore = await cookies() const userAgent = headersList.get('user-agent'); diff --git a/packages/web/src/app/[domain]/settings/billing/page.tsx b/packages/web/src/app/[domain]/settings/billing/page.tsx deleted file mode 100644 index 6c5ce8d0a..000000000 --- a/packages/web/src/app/[domain]/settings/billing/page.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { getCurrentUserRole } from "@/actions" -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" -import { getSubscriptionBillingEmail, getSubscriptionInfo } from "@/ee/features/billing/actions" -import { ChangeBillingEmailCard } from "@/ee/features/billing/components/changeBillingEmailCard" -import { ManageSubscriptionButton } from "@/ee/features/billing/components/manageSubscriptionButton" -import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe" -import { ServiceErrorException } from "@/lib/serviceError" -import { isServiceError } from "@/lib/utils" -import { CalendarIcon, DollarSign, Users } from "lucide-react" -import type { Metadata } from "next" -import { notFound } from "next/navigation" - -export const metadata: Metadata = { - title: "Billing | Settings", - description: "Manage your subscription and billing information", -} - -interface BillingPageProps { - params: Promise<{ - domain: string - }> -} - -export default async function BillingPage(props: BillingPageProps) { - const params = await props.params; - - const { - domain - } = params; - - if (!IS_BILLING_ENABLED) { - notFound(); - } - - const subscription = await getSubscriptionInfo(domain) - - if (isServiceError(subscription)) { - throw new ServiceErrorException(subscription); - } - - if (!subscription) { - throw new Error("Subscription not found"); - } - - const currentUserRole = await getCurrentUserRole(domain) - if (isServiceError(currentUserRole)) { - throw new ServiceErrorException(currentUserRole); - } - - const billingEmail = await getSubscriptionBillingEmail(domain); - if (isServiceError(billingEmail)) { - throw new ServiceErrorException(billingEmail); - } - - return ( -
-
-

Billing

-

Manage your subscription and billing information

-
-
- {/* Billing Email Card */} - - - - - Subscription Plan - - - {subscription.status === "trialing" - ? "You are currently on a free trial" - : `You are currently on the ${subscription.plan} plan.`} - - - -
-
- -
-

Seats

-

{subscription.seats} active users

-
-
-
-
-
- -
-

{subscription.status === "trialing" ? "Trial End Date" : "Next Billing Date"}

-

{new Date(subscription.nextBillingDate * 1000).toLocaleDateString()}

-
-
-
-
-
- -
-

Billing Amount

-

${(subscription.perSeatPrice * subscription.seats).toFixed(2)} per month

-
-
-
-
- - - -
- -
-
- ) -} diff --git a/packages/web/src/app/[domain]/settings/layout.tsx b/packages/web/src/app/[domain]/settings/layout.tsx index 0eb17b83e..857cef673 100644 --- a/packages/web/src/app/[domain]/settings/layout.tsx +++ b/packages/web/src/app/[domain]/settings/layout.tsx @@ -2,7 +2,6 @@ import React from "react" import { Metadata } from "next" import { SidebarNav } from "./components/sidebar-nav" import { NavigationMenu } from "../components/navigationMenu" -import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; import { redirect } from "next/navigation"; import { auth } from "@/auth"; import { isServiceError } from "@/lib/utils"; @@ -84,12 +83,6 @@ export const getSidebarNavItems = async () => } return [ - ...(IS_BILLING_ENABLED ? [ - { - title: "Billing", - href: `/${SINGLE_TENANT_ORG_DOMAIN}/settings/billing`, - } - ] : []), ...(role === OrgRole.OWNER ? [ { title: "Access", diff --git a/packages/web/src/app/[domain]/settings/members/components/inviteMemberCard.tsx b/packages/web/src/app/[domain]/settings/members/components/inviteMemberCard.tsx index d7a8ab736..da248dd94 100644 --- a/packages/web/src/app/[domain]/settings/members/components/inviteMemberCard.tsx +++ b/packages/web/src/app/[domain]/settings/members/components/inviteMemberCard.tsx @@ -29,11 +29,10 @@ export const inviteMemberFormSchema = z.object({ interface InviteMemberCardProps { currentUserRole: OrgRole; - isBillingEnabled: boolean; seatsAvailable?: boolean; } -export const InviteMemberCard = ({ currentUserRole, isBillingEnabled, seatsAvailable = true }: InviteMemberCardProps) => { +export const InviteMemberCard = ({ currentUserRole, seatsAvailable = true }: InviteMemberCardProps) => { const [isInviteDialogOpen, setIsInviteDialogOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const domain = useDomain(); @@ -164,7 +163,7 @@ export const InviteMemberCard = ({ currentUserRole, isBillingEnabled, seatsAvail Invite Team Members - {`Your team is growing! By confirming, you will be inviting ${form.getValues().emails.length} new members to your organization. ${isBillingEnabled ? "Your subscription's seat count will be adjusted when a member accepts their invitation." : ""}`} + {`Your team is growing! By confirming, you will be inviting ${form.getValues().emails.length} new members to your organization.`}
diff --git a/packages/web/src/app/[domain]/settings/members/page.tsx b/packages/web/src/app/[domain]/settings/members/page.tsx index be2c1e8f0..8a16e5726 100644 --- a/packages/web/src/app/[domain]/settings/members/page.tsx +++ b/packages/web/src/app/[domain]/settings/members/page.tsx @@ -7,7 +7,6 @@ import { Tabs, TabsContent } from "@/components/ui/tabs"; import { TabSwitcher } from "@/components/ui/tab-switcher"; import { InvitesList } from "./components/invitesList"; import { getOrgInvites, getMe, getOrgAccountRequests } from "@/actions"; -import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; import { ServiceErrorException } from "@/lib/serviceError"; import { getSeats, hasEntitlement, SOURCEBOT_UNLIMITED_SEATS } from "@sourcebot/shared"; import { RequestsList } from "./components/requestsList"; @@ -99,7 +98,6 @@ export default async function MembersSettingsPage(props: MembersSettingsPageProp diff --git a/packages/web/src/app/[domain]/upgrade/page.tsx b/packages/web/src/app/[domain]/upgrade/page.tsx deleted file mode 100644 index 3d4e0e306..000000000 --- a/packages/web/src/app/[domain]/upgrade/page.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { SourcebotLogo } from "@/app/components/sourcebotLogo"; -import { Footer } from "@/app/components/footer"; -import { OrgSelector } from "../components/orgSelector"; -import { EnterpriseUpgradeCard } from "@/ee/features/billing/components/enterpriseUpgradeCard"; -import { TeamUpgradeCard } from "@/ee/features/billing/components/teamUpgradeCard"; -import { redirect } from "next/navigation"; -import { isServiceError } from "@/lib/utils"; -import Link from "next/link"; -import { ArrowLeftIcon } from "@radix-ui/react-icons"; -import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch"; -import { env } from "@sourcebot/shared"; -import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; -import { getSubscriptionInfo } from "@/ee/features/billing/actions"; - -export default async function Upgrade(props: { params: Promise<{ domain: string }> }) { - const params = await props.params; - - const { - domain - } = params; - - if (!IS_BILLING_ENABLED) { - redirect(`/${domain}`); - } - - const subscription = await getSubscriptionInfo(domain); - if (!subscription) { - redirect(`/${domain}`); - } - - if (!isServiceError(subscription) && subscription.status === "active") { - redirect(`/${domain}`); - } - - const isTrialing = !isServiceError(subscription) ? subscription.status === "trialing" : false; - - return ( -
- {isTrialing && ( - -
- Return to dashboard -
- - )} - -
- -

- {isTrialing ? - "Upgrade your trial." : - "Your subscription has expired." - } -

-

- {isTrialing ? - "Upgrade now to get the most out of Sourcebot." : - "Please upgrade to continue using Sourcebot." - } -

-
- - {env.SOURCEBOT_TENANCY_MODE === 'multi' && ( - - )} - -
- - -
- -
-
- ) -} \ No newline at end of file diff --git a/packages/web/src/app/api/(server)/stripe/route.ts b/packages/web/src/app/api/(server)/stripe/route.ts deleted file mode 100644 index 212860437..000000000 --- a/packages/web/src/app/api/(server)/stripe/route.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { headers } from 'next/headers'; -import { NextRequest } from 'next/server'; -import Stripe from 'stripe'; -import { prisma } from '@/prisma'; -import { StripeSubscriptionStatus } from '@sourcebot/db'; -import { stripeClient } from '@/ee/features/billing/stripe'; -import { env } from '@sourcebot/shared'; -import { createLogger } from "@sourcebot/shared"; - -const logger = createLogger('stripe-webhook'); - -export async function POST(req: NextRequest) { - const body = await req.text(); - const signature = (await headers()).get('stripe-signature'); - - if (!signature) { - return new Response('No signature', { status: 400 }); - } - - if (!stripeClient) { - return new Response('Stripe client not initialized', { status: 500 }); - } - - if (!env.STRIPE_WEBHOOK_SECRET) { - return new Response('Stripe webhook secret not set', { status: 500 }); - } - - try { - const event = stripeClient.webhooks.constructEvent( - body, - signature, - env.STRIPE_WEBHOOK_SECRET - ); - - if (event.type === 'customer.subscription.deleted') { - const subscription = event.data.object as Stripe.Subscription; - const customerId = subscription.customer as string; - - const org = await prisma.org.findFirst({ - where: { - stripeCustomerId: customerId - } - }); - - if (!org) { - return new Response('Org not found', { status: 404 }); - } - - await prisma.org.update({ - where: { - id: org.id - }, - data: { - stripeSubscriptionStatus: StripeSubscriptionStatus.INACTIVE, - stripeLastUpdatedAt: new Date() - } - }); - logger.info(`Org ${org.id} subscription status updated to INACTIVE`); - - return new Response(JSON.stringify({ received: true }), { - status: 200 - }); - } else if (event.type === 'customer.subscription.created') { - const subscription = event.data.object as Stripe.Subscription; - const customerId = subscription.customer as string; - - const org = await prisma.org.findFirst({ - where: { - stripeCustomerId: customerId - } - }); - - if (!org) { - return new Response('Org not found', { status: 404 }); - } - - await prisma.org.update({ - where: { - id: org.id - }, - data: { - stripeSubscriptionStatus: StripeSubscriptionStatus.ACTIVE, - stripeLastUpdatedAt: new Date() - } - }); - logger.info(`Org ${org.id} subscription status updated to ACTIVE`); - - return new Response(JSON.stringify({ received: true }), { - status: 200 - }); - } else { - logger.info(`Received unknown event type: ${event.type}`); - return new Response(JSON.stringify({ received: true }), { - status: 202 - }); - } - - } catch (err) { - logger.error('Error processing webhook:', err); - return new Response( - 'Webhook error: ' + (err as Error).message, - { status: 400 } - ); - } -} diff --git a/packages/web/src/ee/features/billing/actions.ts b/packages/web/src/ee/features/billing/actions.ts deleted file mode 100644 index d66e94eec..000000000 --- a/packages/web/src/ee/features/billing/actions.ts +++ /dev/null @@ -1,248 +0,0 @@ -'use server'; - -import { getMe, sew, withAuth } from "@/actions"; -import { ServiceError, stripeClientNotInitialized, notFound } from "@/lib/serviceError"; -import { withOrgMembership } from "@/actions"; -import { prisma } from "@/prisma"; -import { OrgRole } from "@sourcebot/db"; -import { stripeClient } from "./stripe"; -import { isServiceError } from "@/lib/utils"; -import { env } from "@sourcebot/shared"; -import { StatusCodes } from "http-status-codes"; -import { ErrorCode } from "@/lib/errorCodes"; -import { headers } from "next/headers"; -import { getSubscriptionForOrg } from "./serverUtils"; -import { createLogger } from "@sourcebot/shared"; - -const logger = createLogger('billing-actions'); - -export const createOnboardingSubscription = async (domain: string) => sew(() => - withAuth(async (userId) => - withOrgMembership(userId, domain, async ({ org }) => { - const user = await getMe(); - if (isServiceError(user)) { - return user; - } - - if (!stripeClient) { - return stripeClientNotInitialized(); - } - - const test_clock = env.STRIPE_ENABLE_TEST_CLOCKS === 'true' ? await stripeClient.testHelpers.testClocks.create({ - frozen_time: Math.floor(Date.now() / 1000) - }) : null; - - // Use the existing customer if it exists, otherwise create a new one. - const customerId = await (async () => { - if (org.stripeCustomerId) { - return org.stripeCustomerId; - } - - const customer = await stripeClient.customers.create({ - name: org.name, - email: user.email ?? undefined, - test_clock: test_clock?.id, - description: `Created by ${user.email} on ${domain} (id: ${org.id})`, - }); - - await prisma.org.update({ - where: { - id: org.id, - }, - data: { - stripeCustomerId: customer.id, - } - }); - - return customer.id; - })(); - - const existingSubscription = await getSubscriptionForOrg(org.id, prisma); - if (!isServiceError(existingSubscription)) { - return { - statusCode: StatusCodes.BAD_REQUEST, - errorCode: ErrorCode.SUBSCRIPTION_ALREADY_EXISTS, - message: "Attempted to create a trial subscription for an organization that already has an active subscription", - } satisfies ServiceError; - } - - - const prices = await stripeClient.prices.list({ - product: env.STRIPE_PRODUCT_ID, - expand: ['data.product'], - }); - - try { - const subscription = await stripeClient.subscriptions.create({ - customer: customerId, - items: [{ - price: prices.data[0].id, - }], - trial_period_days: 14, - trial_settings: { - end_behavior: { - missing_payment_method: 'cancel', - }, - }, - payment_settings: { - save_default_payment_method: 'on_subscription', - }, - }); - - if (!subscription) { - return { - statusCode: StatusCodes.INTERNAL_SERVER_ERROR, - errorCode: ErrorCode.STRIPE_CHECKOUT_ERROR, - message: "Failed to create subscription", - } satisfies ServiceError; - } - - return { - subscriptionId: subscription.id, - } - } catch (e) { - logger.error(e); - return { - statusCode: StatusCodes.INTERNAL_SERVER_ERROR, - errorCode: ErrorCode.STRIPE_CHECKOUT_ERROR, - message: "Failed to create subscription", - } satisfies ServiceError; - } - }, /* minRequiredRole = */ OrgRole.OWNER) - )); - -export const createStripeCheckoutSession = async (domain: string) => sew(() => - withAuth((userId) => - withOrgMembership(userId, domain, async ({ org }) => { - if (!org.stripeCustomerId) { - return notFound(); - } - - if (!stripeClient) { - return stripeClientNotInitialized(); - } - - const orgMembers = await prisma.userToOrg.findMany({ - where: { - orgId: org.id, - }, - select: { - userId: true, - } - }); - const numOrgMembers = orgMembers.length; - - const origin = (await headers()).get('origin')!; - const prices = await stripeClient.prices.list({ - product: env.STRIPE_PRODUCT_ID, - expand: ['data.product'], - }); - - const stripeSession = await stripeClient.checkout.sessions.create({ - customer: org.stripeCustomerId as string, - payment_method_types: ['card'], - line_items: [ - { - price: prices.data[0].id, - quantity: numOrgMembers - } - ], - mode: 'subscription', - payment_method_collection: 'always', - success_url: `${origin}/${domain}/settings/billing`, - cancel_url: `${origin}/${domain}`, - }); - - if (!stripeSession.url) { - return { - statusCode: StatusCodes.INTERNAL_SERVER_ERROR, - errorCode: ErrorCode.STRIPE_CHECKOUT_ERROR, - message: "Failed to create checkout session", - } satisfies ServiceError; - } - - return { - url: stripeSession.url, - } - }) - )); - -export const getCustomerPortalSessionLink = async (domain: string): Promise => sew(() => - withAuth((userId) => - withOrgMembership(userId, domain, async ({ org }) => { - if (!org.stripeCustomerId) { - return notFound(); - } - - if (!stripeClient) { - return stripeClientNotInitialized(); - } - - const origin = (await headers()).get('origin')!; - const portalSession = await stripeClient.billingPortal.sessions.create({ - customer: org.stripeCustomerId as string, - return_url: `${origin}/${domain}/settings/billing`, - }); - - return portalSession.url; - }, /* minRequiredRole = */ OrgRole.OWNER) - )); - -export const getSubscriptionBillingEmail = async (domain: string): Promise => sew(() => - withAuth(async (userId) => - withOrgMembership(userId, domain, async ({ org }) => { - if (!org.stripeCustomerId) { - return notFound(); - } - - if (!stripeClient) { - return stripeClientNotInitialized(); - } - - const customer = await stripeClient.customers.retrieve(org.stripeCustomerId); - if (!('email' in customer) || customer.deleted) { - return notFound(); - } - return customer.email!; - }) - )); - -export const changeSubscriptionBillingEmail = async (domain: string, newEmail: string): Promise<{ success: boolean } | ServiceError> => sew(() => - withAuth((userId) => - withOrgMembership(userId, domain, async ({ org }) => { - if (!org.stripeCustomerId) { - return notFound(); - } - - if (!stripeClient) { - return stripeClientNotInitialized(); - } - - await stripeClient.customers.update(org.stripeCustomerId, { - email: newEmail, - }); - - return { - success: true, - } - }, /* minRequiredRole = */ OrgRole.OWNER) - )); - -export const getSubscriptionInfo = async (domain: string) => sew(() => - withAuth(async (userId) => - withOrgMembership(userId, domain, async ({ org }) => { - const subscription = await getSubscriptionForOrg(org.id, prisma); - - if (isServiceError(subscription)) { - return subscription; - } - - return { - status: subscription.status, - plan: "Team", - seats: subscription.items.data[0].quantity!, - perSeatPrice: subscription.items.data[0].price.unit_amount! / 100, - nextBillingDate: subscription.current_period_end!, - } - }) - )); diff --git a/packages/web/src/ee/features/billing/components/changeBillingEmailCard.tsx b/packages/web/src/ee/features/billing/components/changeBillingEmailCard.tsx deleted file mode 100644 index 674b8fac8..000000000 --- a/packages/web/src/ee/features/billing/components/changeBillingEmailCard.tsx +++ /dev/null @@ -1,109 +0,0 @@ -"use client" - -import { useToast } from "@/components/hooks/use-toast" -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form" -import { Input } from "@/components/ui/input" -import { changeSubscriptionBillingEmail } from "@/ee/features/billing/actions" -import useCaptureEvent from "@/hooks/useCaptureEvent" -import { useDomain } from "@/hooks/useDomain" -import { isServiceError } from "@/lib/utils" -import { zodResolver } from "@hookform/resolvers/zod" -import { OrgRole } from "@sourcebot/db" -import { Loader2 } from "lucide-react" -import { useRouter } from "next/navigation" -import { useState } from "react" -import { useForm } from "react-hook-form" -import * as z from "zod" - -const formSchema = z.object({ - email: z.string().email("Please enter a valid email address"), -}) - -interface ChangeBillingEmailCardProps { - currentUserRole: OrgRole, - billingEmail: string -} - -export function ChangeBillingEmailCard({ currentUserRole, billingEmail }: ChangeBillingEmailCardProps) { - const domain = useDomain() - const [isLoading, setIsLoading] = useState(false) - const { toast } = useToast() - const captureEvent = useCaptureEvent(); - const router = useRouter() - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - email: billingEmail, - }, - }) - - const onSubmit = async (values: z.infer) => { - setIsLoading(true) - const newEmail = values.email || billingEmail - const result = await changeSubscriptionBillingEmail(domain, newEmail) - if (!isServiceError(result)) { - toast({ - description: "✅ Billing email updated successfully!", - }) - captureEvent('wa_billing_email_updated_success', {}) - router.refresh() - } else { - toast({ - description: "❌ Failed to update billing email. Please try again.", - }) - captureEvent('wa_billing_email_updated_fail', { - errorCode: result.errorCode, - }) - } - setIsLoading(false) - } - - return ( - - - - Billing Email - - The email address for your billing account - - -
- - ( - - - - - - - )} - /> -
- -
- - -
-
- ) -} - diff --git a/packages/web/src/ee/features/billing/components/checkout.tsx b/packages/web/src/ee/features/billing/components/checkout.tsx deleted file mode 100644 index 41dc7dc41..000000000 --- a/packages/web/src/ee/features/billing/components/checkout.tsx +++ /dev/null @@ -1,90 +0,0 @@ -'use client'; - -import { SourcebotLogo } from "@/app/components/sourcebotLogo"; -import { useToast } from "@/components/hooks/use-toast"; -import { Button } from "@/components/ui/button"; -import { useDomain } from "@/hooks/useDomain"; -import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; -import { ErrorCode } from "@/lib/errorCodes"; -import { isServiceError } from "@/lib/utils"; -import { Check, Loader2 } from "lucide-react"; -import { useCallback, useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; -import { OnboardingSteps, TEAM_FEATURES } from "@/lib/constants"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; -import { createOnboardingSubscription } from "../actions"; - -export const Checkout = () => { - const domain = useDomain(); - const { toast } = useToast(); - const errorCode = useNonEmptyQueryParam('errorCode'); - const errorMessage = useNonEmptyQueryParam('errorMessage'); - const [isLoading, setIsLoading] = useState(false); - const router = useRouter(); - const captureEvent = useCaptureEvent(); - - useEffect(() => { - if (errorCode === ErrorCode.STRIPE_CHECKOUT_ERROR && errorMessage) { - toast({ - description: `⚠️ Stripe checkout failed with error: ${errorMessage}`, - variant: "destructive", - }); - captureEvent('wa_onboard_checkout_fail', { - errorCode: errorMessage, - }); - } - }, [errorCode, errorMessage, toast, captureEvent]); - - const onCheckout = useCallback(() => { - setIsLoading(true); - createOnboardingSubscription(domain) - .then((response) => { - if (isServiceError(response)) { - toast({ - description: `❌ Stripe checkout failed with error: ${response.message}`, - variant: "destructive", - }) - captureEvent('wa_onboard_checkout_fail', { - errorCode: response.errorCode, - }); - } else { - captureEvent('wa_onboard_checkout_success', {}); - router.push(`/${domain}/onboard?step=${OnboardingSteps.Complete}`); - } - }) - .finally(() => { - setIsLoading(false); - }); - }, [domain, router, toast, captureEvent]); - - return ( -
- -

Start your 14 day free trial

-

Cancel anytime. No credit card required.

-
    - {TEAM_FEATURES.map((feature, index) => ( -
  • -
    - -
    -

    {feature}

    -
  • - ))} -
-
- -
-
- ) -} \ No newline at end of file diff --git a/packages/web/src/ee/features/billing/components/enterpriseUpgradeCard.tsx b/packages/web/src/ee/features/billing/components/enterpriseUpgradeCard.tsx deleted file mode 100644 index 74de5cf08..000000000 --- a/packages/web/src/ee/features/billing/components/enterpriseUpgradeCard.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import { ENTERPRISE_FEATURES, SOURCEBOT_SUPPORT_EMAIL } from "@/lib/constants"; -import { UpgradeCard } from "./upgradeCard"; -import Link from "next/link"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; - - -export const EnterpriseUpgradeCard = () => { - const captureEvent = useCaptureEvent(); - - const onClick = () => { - captureEvent('wa_enterprise_upgrade_card_pressed', {}); - } - - return ( - - - - ) -} \ No newline at end of file diff --git a/packages/web/src/ee/features/billing/components/manageSubscriptionButton.tsx b/packages/web/src/ee/features/billing/components/manageSubscriptionButton.tsx deleted file mode 100644 index 25a04a86a..000000000 --- a/packages/web/src/ee/features/billing/components/manageSubscriptionButton.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client" - -import { useState } from "react" -import { useRouter } from "next/navigation" -import { isServiceError } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { useDomain } from "@/hooks/useDomain"; -import { OrgRole } from "@sourcebot/db"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; -import { ExternalLink, Loader2 } from "lucide-react"; -import { getCustomerPortalSessionLink } from "@/ee/features/billing/actions" - -export function ManageSubscriptionButton({ currentUserRole }: { currentUserRole: OrgRole }) { - const [isLoading, setIsLoading] = useState(false) - const router = useRouter() - const domain = useDomain(); - const captureEvent = useCaptureEvent(); - - const redirectToCustomerPortal = async () => { - setIsLoading(true) - const session = await getCustomerPortalSessionLink(domain); - if (isServiceError(session)) { - captureEvent('wa_manage_subscription_button_create_portal_session_fail', { - errorCode: session.errorCode, - }); - setIsLoading(false); - } else { - captureEvent('wa_manage_subscription_button_create_portal_session_success', {}) - router.push(session) - // @note: we don't want to set isLoading to false here since we want to show the loading - // spinner until the page is redirected. - } - } - - const isOwner = currentUserRole === OrgRole.OWNER - return ( -
- -
- ) -} \ No newline at end of file diff --git a/packages/web/src/ee/features/billing/components/teamUpgradeCard.tsx b/packages/web/src/ee/features/billing/components/teamUpgradeCard.tsx deleted file mode 100644 index 26ff5276f..000000000 --- a/packages/web/src/ee/features/billing/components/teamUpgradeCard.tsx +++ /dev/null @@ -1,59 +0,0 @@ -'use client'; - -import { UpgradeCard } from "./upgradeCard"; -import { useToast } from "@/components/hooks/use-toast"; -import { useDomain } from "@/hooks/useDomain"; -import { isServiceError } from "@/lib/utils"; -import { useCallback, useState } from "react"; -import { useRouter } from "next/navigation"; -import { TEAM_FEATURES } from "@/lib/constants"; -import useCaptureEvent from "@/hooks/useCaptureEvent"; -import { createStripeCheckoutSession } from "../actions"; - -interface TeamUpgradeCardProps { - buttonText: string; -} - -export const TeamUpgradeCard = ({ buttonText }: TeamUpgradeCardProps) => { - const domain = useDomain(); - const { toast } = useToast(); - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - const captureEvent = useCaptureEvent(); - - const onClick = useCallback(() => { - captureEvent('wa_team_upgrade_card_pressed', {}); - setIsLoading(true); - createStripeCheckoutSession(domain) - .then((response) => { - if (isServiceError(response)) { - toast({ - description: `❌ Stripe checkout failed with error: ${response.message}`, - variant: "destructive", - }); - captureEvent('wa_team_upgrade_checkout_fail', { - errorCode: response.errorCode, - }); - } else { - router.push(response.url); - captureEvent('wa_team_upgrade_checkout_success', {}); - } - }) - .finally(() => { - setIsLoading(false); - }); - }, [domain, router, toast, captureEvent]); - - return ( - - ) -} \ No newline at end of file diff --git a/packages/web/src/ee/features/billing/components/upgradeCard.tsx b/packages/web/src/ee/features/billing/components/upgradeCard.tsx deleted file mode 100644 index 9d24b254b..000000000 --- a/packages/web/src/ee/features/billing/components/upgradeCard.tsx +++ /dev/null @@ -1,55 +0,0 @@ -'use client'; - -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" -import { Check, Loader2 } from "lucide-react"; - - -interface UpgradeCardProps { - title: string; - description: string; - price: string; - priceDescription: string; - features: string[]; - buttonText: string; - onClick?: () => void; - isLoading?: boolean; -} - -export const UpgradeCard = ({ title, description, price, priceDescription, features, buttonText, onClick, isLoading = false }: UpgradeCardProps) => { - return ( - onClick?.()} - > - - {title} - {description} - - -
-

{price}

-

{priceDescription}

-
-
    - {features.map((feature, index) => ( -
  • - - {feature} -
  • - ))} -
-
- - - -
- ) -} \ No newline at end of file diff --git a/packages/web/src/ee/features/billing/serverUtils.ts b/packages/web/src/ee/features/billing/serverUtils.ts deleted file mode 100644 index bf2eb6a8c..000000000 --- a/packages/web/src/ee/features/billing/serverUtils.ts +++ /dev/null @@ -1,80 +0,0 @@ -import 'server-only'; - -import { notFound, orgInvalidSubscription, ServiceError, stripeClientNotInitialized } from "@/lib/serviceError"; -import { isServiceError } from "@/lib/utils"; -import { Prisma } from "@sourcebot/db"; -import Stripe from "stripe"; -import { stripeClient } from "./stripe"; - -export const incrementOrgSeatCount = async (orgId: number, prisma: Prisma.TransactionClient) => { - if (!stripeClient) { - return stripeClientNotInitialized(); - } - - const subscription = await getSubscriptionForOrg(orgId, prisma); - if (isServiceError(subscription)) { - return subscription; - } - - const existingSeatCount = subscription.items.data[0].quantity; - const newSeatCount = (existingSeatCount || 1) + 1; - - await stripeClient.subscriptionItems.update( - subscription.items.data[0].id, - { - quantity: newSeatCount, - proration_behavior: 'create_prorations', - } - ); -} - -export const decrementOrgSeatCount = async (orgId: number, prisma: Prisma.TransactionClient) => { - if (!stripeClient) { - return stripeClientNotInitialized(); - } - - const subscription = await getSubscriptionForOrg(orgId, prisma); - if (isServiceError(subscription)) { - return subscription; - } - - const existingSeatCount = subscription.items.data[0].quantity; - const newSeatCount = (existingSeatCount || 1) - 1; - - await stripeClient.subscriptionItems.update( - subscription.items.data[0].id, - { - quantity: newSeatCount, - proration_behavior: 'create_prorations', - } - ); -} - -export const getSubscriptionForOrg = async (orgId: number, prisma: Prisma.TransactionClient): Promise => { - const org = await prisma.org.findUnique({ - where: { - id: orgId, - }, - }); - - if (!org) { - return notFound(); - } - - if (!org.stripeCustomerId) { - return notFound(); - } - - if (!stripeClient) { - return stripeClientNotInitialized(); - } - - const subscriptions = await stripeClient.subscriptions.list({ - customer: org.stripeCustomerId - }); - - if (subscriptions.data.length === 0) { - return orgInvalidSubscription(); - } - return subscriptions.data[0]; -} \ No newline at end of file diff --git a/packages/web/src/ee/features/billing/stripe.ts b/packages/web/src/ee/features/billing/stripe.ts deleted file mode 100644 index c8ca0af7a..000000000 --- a/packages/web/src/ee/features/billing/stripe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import 'server-only'; -import { env } from '@sourcebot/shared' -import Stripe from "stripe"; -import { hasEntitlement } from '@sourcebot/shared'; - -export const IS_BILLING_ENABLED = hasEntitlement('billing') && env.STRIPE_SECRET_KEY !== undefined; - -export const stripeClient = - IS_BILLING_ENABLED - ? new Stripe(env.STRIPE_SECRET_KEY!) - : undefined; \ No newline at end of file diff --git a/packages/web/src/features/userManagement/actions.ts b/packages/web/src/features/userManagement/actions.ts index 02869c659..298ebfa69 100644 --- a/packages/web/src/features/userManagement/actions.ts +++ b/packages/web/src/features/userManagement/actions.ts @@ -3,12 +3,9 @@ import { sew } from "@/actions"; import { ErrorCode } from "@/lib/errorCodes"; import { notFound, ServiceError } from "@/lib/serviceError"; -import { isServiceError } from "@/lib/utils"; import { prisma } from "@/prisma"; import { withAuthV2, withMinimumOrgRole } from "@/withAuthV2"; import { OrgRole, Prisma } from "@sourcebot/db"; -import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; -import { decrementOrgSeatCount } from "@/ee/features/billing/serverUtils"; import { StatusCodes } from "http-status-codes"; export const removeMemberFromOrg = async (memberId: string): Promise<{ success: boolean } | ServiceError> => sew(() => @@ -54,13 +51,6 @@ export const removeMemberFromOrg = async (memberId: string): Promise<{ success: } }); - if (IS_BILLING_ENABLED) { - const result = await decrementOrgSeatCount(org.id, tx); - if (isServiceError(result)) { - throw result; - } - } - return null; }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }); @@ -101,13 +91,6 @@ export const leaveOrg = async (): Promise<{ success: boolean } | ServiceError> = } }); - if (IS_BILLING_ENABLED) { - const result = await decrementOrgSeatCount(org.id, tx); - if (isServiceError(result)) { - throw result; - } - } - return null; }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }); diff --git a/packages/web/src/lib/authUtils.ts b/packages/web/src/lib/authUtils.ts index f250ad233..6326be64b 100644 --- a/packages/web/src/lib/authUtils.ts +++ b/packages/web/src/lib/authUtils.ts @@ -9,8 +9,6 @@ import { createLogger } from "@sourcebot/shared"; import { getAuditService } from "@/ee/features/audit/factory"; import { StatusCodes } from "http-status-codes"; import { ErrorCode } from "./errorCodes"; -import { IS_BILLING_ENABLED } from "@/ee/features/billing/stripe"; -import { incrementOrgSeatCount } from "@/ee/features/billing/serverUtils"; import { getOrgFromDomain } from "@/data/org"; const logger = createLogger('web-auth-utils'); @@ -262,13 +260,6 @@ export const addUserToOrganization = async (userId: string, orgId: number): Prom } }); - if (IS_BILLING_ENABLED) { - const result = await incrementOrgSeatCount(orgId, tx); - if (isServiceError(result)) { - throw result; - } - } - // Delete the account request if it exists since we've added the user to the org const accountRequest = await tx.accountRequest.findUnique({ where: { diff --git a/packages/web/src/lib/constants.ts b/packages/web/src/lib/constants.ts index 21bb97d54..d6dbaf5e9 100644 --- a/packages/web/src/lib/constants.ts +++ b/packages/web/src/lib/constants.ts @@ -4,24 +4,9 @@ export enum OnboardingSteps { CreateOrg = 'create-org', ConnectCodeHost = 'connect-code-host', InviteTeam = 'invite-team', - Checkout = 'checkout', Complete = 'complete', } -export const ENTERPRISE_FEATURES = [ - "All Team features", - "Dedicated Slack support channel", - "Single tenant deployment", - "Advanced security features", -] - -export const TEAM_FEATURES = [ - "Index thousands of repos from multiple code hosts (GitHub, GitLab, Gerrit, Gitea, etc.). Self-hosted code hosts supported.", - "Public and private repos supported.", - "Create shareable links to code snippets.", - "Built on-top of zoekt, Google's code search engine. Blazingly fast and powerful (regex, symbol) code search.", -] - export const MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME = 'sb.mobile-unsupported-splash-screen-dismissed'; export const AGENTIC_SEARCH_TUTORIAL_DISMISSED_COOKIE_NAME = 'sb.agentic-search-tutorial-dismissed'; export const OPTIONAL_PROVIDERS_LINK_SKIPPED_COOKIE_NAME = 'sb.optional-providers-link-skipped'; diff --git a/packages/web/src/lib/errorCodes.ts b/packages/web/src/lib/errorCodes.ts index a3e897eac..0f589f28b 100644 --- a/packages/web/src/lib/errorCodes.ts +++ b/packages/web/src/lib/errorCodes.ts @@ -12,7 +12,6 @@ export enum ErrorCode { ORG_NOT_FOUND = 'ORG_NOT_FOUND', CONNECTION_SYNC_ALREADY_SCHEDULED = 'CONNECTION_SYNC_ALREADY_SCHEDULED', ORG_DOMAIN_ALREADY_EXISTS = 'ORG_DOMAIN_ALREADY_EXISTS', - ORG_INVALID_SUBSCRIPTION = 'ORG_INVALID_SUBSCRIPTION', INVALID_CREDENTIALS = 'INVALID_CREDENTIALS', INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS', CONNECTION_NOT_FAILED = 'CONNECTION_NOT_FAILED', @@ -20,10 +19,7 @@ export enum ErrorCode { INVALID_INVITE = 'INVALID_INVITE', INVALID_INVITE_LINK = 'INVALID_INVITE_LINK', INVITE_LINK_NOT_ENABLED = 'INVITE_LINK_NOT_ENABLED', - STRIPE_CHECKOUT_ERROR = 'STRIPE_CHECKOUT_ERROR', SECRET_ALREADY_EXISTS = 'SECRET_ALREADY_EXISTS', - SUBSCRIPTION_ALREADY_EXISTS = 'SUBSCRIPTION_ALREADY_EXISTS', - STRIPE_CLIENT_NOT_INITIALIZED = 'STRIPE_CLIENT_NOT_INITIALIZED', ACTION_DISALLOWED_IN_TENANCY_MODE = 'ACTION_DISALLOWED_IN_TENANCY_MODE', SEARCH_CONTEXT_NOT_FOUND = 'SEARCH_CONTEXT_NOT_FOUND', MISSING_ORG_DOMAIN_HEADER = 'MISSING_ORG_DOMAIN_HEADER', diff --git a/packages/web/src/lib/serviceError.ts b/packages/web/src/lib/serviceError.ts index cb4af0227..a34a15805 100644 --- a/packages/web/src/lib/serviceError.ts +++ b/packages/web/src/lib/serviceError.ts @@ -128,14 +128,6 @@ export const orgDomainExists = (): ServiceError => { } } -export const orgInvalidSubscription = (): ServiceError => { - return { - statusCode: StatusCodes.BAD_REQUEST, - errorCode: ErrorCode.ORG_INVALID_SUBSCRIPTION, - message: "Invalid subscription", - } -} - export const secretAlreadyExists = (): ServiceError => { return { statusCode: StatusCodes.CONFLICT, @@ -152,10 +144,3 @@ export const invalidGitRef = (ref: string): ServiceError => { }; } -export const stripeClientNotInitialized = (): ServiceError => { - return { - statusCode: StatusCodes.INTERNAL_SERVER_ERROR, - errorCode: ErrorCode.STRIPE_CLIENT_NOT_INITIALIZED, - message: "Stripe client is not initialized.", - } -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b2cd9e5d5..659ac594d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8974,8 +8974,6 @@ __metadata: "@sourcebot/schemas": "workspace:*" "@sourcebot/shared": "workspace:*" "@ssddanbrown/codemirror-lang-twig": "npm:^1.0.0" - "@stripe/react-stripe-js": "npm:^3.1.1" - "@stripe/stripe-js": "npm:^5.6.0" "@tailwindcss/typography": "npm:^0.5.16" "@tanstack/eslint-plugin-query": "npm:^5.74.7" "@tanstack/react-query": "npm:^5.53.3" @@ -9083,7 +9081,6 @@ __metadata: slate-history: "npm:^0.113.1" slate-react: "npm:^0.117.1" strip-json-comments: "npm:^5.0.1" - stripe: "npm:^17.6.0" tailwind-merge: "npm:^2.5.2" tailwindcss: "npm:^3.4.1" tailwindcss-animate: "npm:^1.0.7" @@ -9119,26 +9116,6 @@ __metadata: languageName: node linkType: hard -"@stripe/react-stripe-js@npm:^3.1.1": - version: 3.5.1 - resolution: "@stripe/react-stripe-js@npm:3.5.1" - dependencies: - prop-types: "npm:^15.7.2" - peerDependencies: - "@stripe/stripe-js": ">=1.44.1 <7.0.0" - react: ">=16.8.0 <20.0.0" - react-dom: ">=16.8.0 <20.0.0" - checksum: 10c0/999f52c420657a9a4f287de12f4e1c560e168f10f8e12f6c6f4314fb170478d4c594fc8dcec7f3bdc4413f21cbc23155f8849b19ed0589e5bcb558f065c63cfe - languageName: node - linkType: hard - -"@stripe/stripe-js@npm:^5.6.0": - version: 5.10.0 - resolution: "@stripe/stripe-js@npm:5.10.0" - checksum: 10c0/0309007baaf939931de0e18ec8230bdfa41b1644cd5f65d72168ef0a05ea4d14da9a95ff628c94f9f1e222577296369b7490534803ce1ce18d08a557ae562644 - languageName: node - linkType: hard - "@swc/helpers@npm:0.5.15": version: 0.5.15 resolution: "@swc/helpers@npm:0.5.15" @@ -9603,7 +9580,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:>=8.1.0, @types/node@npm:^22.7.5": +"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:^22.7.5": version: 22.13.11 resolution: "@types/node@npm:22.13.11" dependencies: @@ -19011,7 +18988,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -21242,16 +21219,6 @@ __metadata: languageName: node linkType: hard -"stripe@npm:^17.6.0": - version: 17.7.0 - resolution: "stripe@npm:17.7.0" - dependencies: - "@types/node": "npm:>=8.1.0" - qs: "npm:^6.11.0" - checksum: 10c0/df67c6d455bd0dd87140640924c220fa9581fc00c3267d171f407c8d088f946f61e3ae7e88a89e7dd705b10fd5254630fc943222eb6f003390ebafbd391f81b2 - languageName: node - linkType: hard - "strnum@npm:^2.1.2": version: 2.1.2 resolution: "strnum@npm:2.1.2"