mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-12 20:35:24 +00:00
add back paywall and also add support for incrememnting seat count on invite redemption
This commit is contained in:
parent
53dac384af
commit
6caed350d3
8 changed files with 279 additions and 165 deletions
|
|
@ -1,8 +1,8 @@
|
|||
'use server';
|
||||
|
||||
import Ajv from "ajv";
|
||||
import { auth, getCurrentUserOrg } from "./auth";
|
||||
import { notAuthenticated, notFound, ServiceError, unexpectedError, orgDomainExists } from "@/lib/serviceError";
|
||||
import { auth } from "./auth";
|
||||
import { notAuthenticated, notFound, ServiceError, unexpectedError, orgInvalidSubscription } from "@/lib/serviceError";
|
||||
import { prisma } from "@/prisma";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { ErrorCode } from "@/lib/errorCodes";
|
||||
|
|
@ -17,6 +17,7 @@ import { headers } from "next/headers"
|
|||
import { stripe } from "@/lib/stripe"
|
||||
import { getUser } from "@/data/user";
|
||||
import { Session } from "next-auth";
|
||||
import Stripe from "stripe";
|
||||
|
||||
const ajv = new Ajv({
|
||||
validateFormats: false,
|
||||
|
|
@ -309,6 +310,35 @@ export const redeemInvite = async (invite: Invite, userId: string): Promise<{ su
|
|||
withAuth(async () => {
|
||||
try {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const org = await tx.org.findUnique({
|
||||
where: {
|
||||
id: invite.orgId,
|
||||
}
|
||||
});
|
||||
|
||||
if (!org) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
// Incrememnt the seat count. We check if the subscription is valid in the redeem page so we return an error if that's not the case here
|
||||
if (org.stripeCustomerId) {
|
||||
const subscription = await fetchSubscription(org.id);
|
||||
if (isServiceError(subscription)) {
|
||||
return orgInvalidSubscription();
|
||||
}
|
||||
|
||||
const existingSeatCount = subscription.items.data[0].quantity;
|
||||
const newSeatCount = (existingSeatCount || 1) + 1
|
||||
|
||||
await stripe.subscriptionItems.update(
|
||||
subscription.items.data[0].id,
|
||||
{
|
||||
quantity: newSeatCount,
|
||||
proration_behavior: 'create_prorations',
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
await tx.userToOrg.create({
|
||||
data: {
|
||||
userId,
|
||||
|
|
@ -412,6 +442,11 @@ export async function fetchStripeClientSecret(name: string, domain: string) {
|
|||
mode: 'subscription',
|
||||
subscription_data: {
|
||||
trial_period_days: 7,
|
||||
trial_settings: {
|
||||
end_behavior: {
|
||||
missing_payment_method: 'cancel',
|
||||
},
|
||||
},
|
||||
},
|
||||
payment_method_collection: 'if_required',
|
||||
return_url: `${origin}/onboard/complete?session_id={CHECKOUT_SESSION_ID}&org_name=${name}&org_domain=${domain}`,
|
||||
|
|
@ -420,63 +455,58 @@ export async function fetchStripeClientSecret(name: string, domain: string) {
|
|||
return stripeSession.client_secret!;
|
||||
}
|
||||
|
||||
export async function getSubscriptionCheckoutRedirect(orgId: number) {
|
||||
const org = await prisma.org.findUnique({
|
||||
where: {
|
||||
id: orgId,
|
||||
},
|
||||
});
|
||||
export const getSubscriptionCheckoutRedirect = async (domain: string) =>
|
||||
withAuth((session) =>
|
||||
withOrgMembership(session, domain, async (orgId) => {
|
||||
const org = await prisma.org.findUnique({
|
||||
where: {
|
||||
id: orgId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!org || !org.stripeCustomerId) {
|
||||
return notFound();
|
||||
}
|
||||
if (!org || !org.stripeCustomerId) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const existingStripeSubscription = await fetchSubscription(orgId);
|
||||
|
||||
const origin = (await headers()).get('origin')
|
||||
const prices = await stripe.prices.list({
|
||||
product: 'prod_RkeYDKNFsZJROd',
|
||||
expand: ['data.product'],
|
||||
});
|
||||
|
||||
const createNewSubscription = async () => {
|
||||
const stripeSession = await stripe.checkout.sessions.create({
|
||||
customer: org.stripeCustomerId as string,
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price: prices.data[0].id,
|
||||
quantity: 1
|
||||
const orgMembers = await prisma.userToOrg.findMany({
|
||||
where: {
|
||||
orgId,
|
||||
},
|
||||
select: {
|
||||
userId: true,
|
||||
}
|
||||
],
|
||||
mode: 'subscription',
|
||||
payment_method_collection: 'always',
|
||||
success_url: `${origin}/settings/billing`,
|
||||
cancel_url: `${origin}`,
|
||||
});
|
||||
});
|
||||
const numOrgMembers = orgMembers.length;
|
||||
|
||||
return stripeSession.url;
|
||||
}
|
||||
const origin = (await headers()).get('origin')
|
||||
const prices = await stripe.prices.list({
|
||||
product: 'prod_RkeYDKNFsZJROd',
|
||||
expand: ['data.product'],
|
||||
});
|
||||
|
||||
const createNewSubscription = async () => {
|
||||
const stripeSession = await stripe.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 we don't have an existing stripe subscription
|
||||
if (isServiceError(existingStripeSubscription)) {
|
||||
const checkoutUrl = await createNewSubscription();
|
||||
return checkoutUrl;
|
||||
} else {
|
||||
if (existingStripeSubscription.status === "cancelled") {
|
||||
const checkoutUrl = await createNewSubscription();
|
||||
return checkoutUrl;
|
||||
}
|
||||
return stripeSession.url;
|
||||
}
|
||||
|
||||
const portalSession = await stripe.billingPortal.sessions.create({
|
||||
customer: org.stripeCustomerId as string,
|
||||
return_url: `${origin}/settings/billing`,
|
||||
});
|
||||
|
||||
return portalSession.url;
|
||||
}
|
||||
}
|
||||
const newSubscriptionUrl = await createNewSubscription();
|
||||
return newSubscriptionUrl;
|
||||
})
|
||||
)
|
||||
|
||||
export async function fetchStripeSession(sessionId: string) {
|
||||
const stripeSession = await stripe.checkout.sessions.retrieve(sessionId);
|
||||
|
|
@ -521,10 +551,7 @@ export async function fetchSubscription(orgId: number) {
|
|||
})
|
||||
|
||||
if (subscriptions.data.length === 0) {
|
||||
return {
|
||||
status: "no_subscription",
|
||||
message: "No subscription found for this organization"
|
||||
}
|
||||
return notFound();
|
||||
}
|
||||
return subscriptions.data[0];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
"use client"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { getSubscriptionCheckoutRedirect} from "@/actions"
|
||||
import { getSubscriptionCheckoutRedirect } from "@/actions"
|
||||
import { isServiceError } from "@/lib/utils"
|
||||
|
||||
|
||||
export function CheckoutButton({ orgId }: { orgId: number }) {
|
||||
export function CheckoutButton({ domain }: { domain: string }) {
|
||||
const redirectToCheckout = async () => {
|
||||
const redirectUrl = await getSubscriptionCheckoutRedirect(orgId)
|
||||
|
||||
if(isServiceError(redirectUrl)) {
|
||||
const redirectUrl = await getSubscriptionCheckoutRedirect(domain)
|
||||
|
||||
if (isServiceError(redirectUrl)) {
|
||||
console.error("Failed to create checkout session")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
window.location.href = redirectUrl!;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button className="w-full" onClick={redirectToCheckout}>Choose Pqweqwro</Button>
|
||||
</div>
|
||||
<Button className="w-full" onClick={redirectToCheckout}>Renew Membership</Button>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,71 +1,93 @@
|
|||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Check } from "lucide-react"
|
||||
import { EnterpriseContactUsButton } from "./enterpriseContactUsButton"
|
||||
import { CheckoutButton } from "./checkoutButton"
|
||||
import Image from "next/image";
|
||||
import logoDark from "@/public/sb_logo_dark_large.png";
|
||||
import logoLight from "@/public/sb_logo_light_large.png";
|
||||
|
||||
const proFeatures = [
|
||||
"Unlimited projects",
|
||||
"Priority support",
|
||||
"Advanced analytics",
|
||||
"Custom integrations",
|
||||
"Team collaboration tools",
|
||||
]
|
||||
|
||||
const enterpriseFeatures = [
|
||||
"All Pro features",
|
||||
"Dedicated account manager",
|
||||
"Custom SLA",
|
||||
"On-premise deployment option",
|
||||
"Advanced security features",
|
||||
]
|
||||
const teamFeatures = [
|
||||
"Index hundreds of repos from multiple code hosts (GitHub, GitLab, Gerrit, Gitea, etc.). Self-hosted code sources supported",
|
||||
"Public and private repos supported",
|
||||
"Create sharable links to code snippets",
|
||||
"9x5 email support team@sourcebot.dev",
|
||||
]
|
||||
|
||||
export function PaywallCard({ orgId }: { orgId: number }) {
|
||||
const enterpriseFeatures = [
|
||||
"All Team features",
|
||||
"Dedicated Slack support channel",
|
||||
"Single tenant deployment",
|
||||
"Advanced security features",
|
||||
]
|
||||
|
||||
export async function PaywallCard({ domain }: { domain: string }) {
|
||||
return (
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Team</CardTitle>
|
||||
<CardDescription>For professional developers and small teams</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-3xl font-bold">$10</p>
|
||||
<p className="text-sm text-muted-foreground">per user / month</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
{proFeatures.map((feature, index) => (
|
||||
<li key={index} className="flex items-center">
|
||||
<Check className="mr-2 h-4 w-4 text-green-500" />
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<CheckoutButton orgId={orgId} />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Enterprise</CardTitle>
|
||||
<CardDescription>For large organizations with custom needs</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-3xl font-bold">Custom</p>
|
||||
<p className="text-sm text-muted-foreground">tailored to your needs</p>
|
||||
<ul className="mt-4 space-y-2">
|
||||
{enterpriseFeatures.map((feature, index) => (
|
||||
<li key={index} className="flex items-center">
|
||||
<Check className="mr-2 h-4 w-4 text-green-500" />
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<EnterpriseContactUsButton />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<div className="max-w-4xl mx-auto px-4 py-8">
|
||||
<div className="max-h-44 w-auto mb-4 flex justify-center">
|
||||
<Image
|
||||
src={logoDark}
|
||||
className="h-18 md:h-40 w-auto hidden dark:block"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
<Image
|
||||
src={logoLight}
|
||||
className="h-18 md:h-40 w-auto block dark:hidden"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-center mb-8 text-primary">
|
||||
Your subscription has expired.
|
||||
</h2>
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
<Card className="border-2 border-primary/20 shadow-lg transition-all duration-300 hover:shadow-xl hover:border-primary/50 flex flex-col">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-2xl font-bold text-primary">Team</CardTitle>
|
||||
<CardDescription className="text-base">For professional developers and small teams</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-grow">
|
||||
<div className="mb-4">
|
||||
<p className="text-4xl font-bold text-primary">$10</p>
|
||||
<p className="text-sm text-muted-foreground">per user / month</p>
|
||||
</div>
|
||||
<ul className="space-y-3">
|
||||
{teamFeatures.map((feature, index) => (
|
||||
<li key={index} className="flex items-center">
|
||||
<Check className="mr-3 h-5 w-5 text-green-500 flex-shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<CheckoutButton domain={domain} />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card className="border-2 border-primary/20 shadow-lg transition-all duration-300 hover:shadow-xl hover:border-primary/50 flex flex-col">
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="text-2xl font-bold text-primary">Enterprise</CardTitle>
|
||||
<CardDescription className="text-base">For large organizations with custom needs</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-grow">
|
||||
<div className="mb-4">
|
||||
<p className="text-4xl font-bold text-primary">Custom</p>
|
||||
<p className="text-sm text-muted-foreground">tailored to your needs</p>
|
||||
</div>
|
||||
<ul className="space-y-3">
|
||||
{enterpriseFeatures.map((feature, index) => (
|
||||
<li key={index} className="flex items-center">
|
||||
<Check className="mr-3 h-5 w-5 text-green-500 flex-shrink-0" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<EnterpriseContactUsButton />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@ import { prisma } from "@/prisma";
|
|||
import { PageNotFound } from "./components/pageNotFound";
|
||||
import { auth } from "@/auth";
|
||||
import { getOrgFromDomain } from "@/data/org";
|
||||
import { fetchSubscription } from "@/actions";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
import { PaywallCard } from "./components/payWall/paywallCard";
|
||||
import { NavigationMenu } from "./components/navigationMenu";
|
||||
import { Footer } from "./components/footer";
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode,
|
||||
|
|
@ -38,5 +43,18 @@ export default async function Layout({
|
|||
return <PageNotFound />
|
||||
}
|
||||
|
||||
const subscription = await fetchSubscription(org.id);
|
||||
if (isServiceError(subscription) || (subscription.status !== "active" && subscription.status !== "trialing")) {
|
||||
return (
|
||||
<div className="flex flex-col items-center overflow-hidden min-h-screen">
|
||||
<NavigationMenu
|
||||
domain={domain}
|
||||
/>
|
||||
<PaywallCard domain={domain} />
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import { UpgradeToast } from "./components/upgradeToast";
|
|||
import Link from "next/link";
|
||||
import { getOrgFromDomain } from "@/data/org";
|
||||
import { PageNotFound } from "./components/pageNotFound";
|
||||
import { Footer } from "./components/footer";
|
||||
|
||||
|
||||
export default async function Home({ params: { domain } }: { params: { domain: string } }) {
|
||||
|
|
@ -109,13 +110,7 @@ export default async function Home({ params: { domain } }: { params: { domain: s
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer className="w-full mt-auto py-4 flex flex-row justify-center items-center gap-4">
|
||||
<Link href="https://sourcebot.dev" className="text-gray-400 text-sm hover:underline">About</Link>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<Link href="https://github.com/sourcebot-dev/sourcebot/issues/new" className="text-gray-400 text-sm hover:underline">Support</Link>
|
||||
<Separator orientation="vertical" className="h-4" />
|
||||
<Link href="mailto:team@sourcebot.dev" className="text-gray-400 text-sm hover:underline">Contact Us</Link>
|
||||
</footer>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import { AcceptInviteButton } from "./components/acceptInviteButton"
|
|||
import Image from "next/image";
|
||||
import logoDark from "@/public/sb_logo_dark_large.png";
|
||||
import logoLight from "@/public/sb_logo_light_large.png";
|
||||
import { fetchSubscription } from "@/actions";
|
||||
import { isServiceError } from "@/lib/utils";
|
||||
|
||||
interface RedeemPageProps {
|
||||
searchParams?: {
|
||||
|
|
@ -60,34 +62,6 @@ export default async function RedeemPage({ searchParams }: RedeemPageProps) {
|
|||
if (user.email !== invite.recipientEmail) {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center mt-8 mb-8 md:mt-18 w-full px-5">
|
||||
<div className="max-h-44 w-auto mb-4">
|
||||
<Image
|
||||
src={logoDark}
|
||||
className="h-18 md:h-40 w-auto hidden dark:block"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
<Image
|
||||
src={logoLight}
|
||||
className="h-18 md:h-40 w-auto block dark:hidden"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center items-center">
|
||||
<h1>This invite doesn't belong to you. You're currenly signed in with ${user.email}</h1>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
const orgName = await prisma.org.findUnique({
|
||||
where: { id: invite.orgId },
|
||||
select: { name: true },
|
||||
});
|
||||
|
||||
if (!orgName) {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center mt-8 mb-8 md:mt-18 w-full px-5">
|
||||
<div className="max-h-44 w-auto mb-4">
|
||||
<Image
|
||||
src={logoDark}
|
||||
|
|
@ -103,16 +77,87 @@ export default async function RedeemPage({ searchParams }: RedeemPageProps) {
|
|||
/>
|
||||
</div>
|
||||
<div className="flex justify-center items-center">
|
||||
<h1>This organization wasn't found. Please contact your organization owner.</h1>
|
||||
<h1>This invite doesn't belong to you. You're currenly signed in with ${user.email}</h1>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
const org = await prisma.org.findUnique({
|
||||
where: { id: invite.orgId },
|
||||
});
|
||||
|
||||
if (!org) {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center mt-8 mb-8 md:mt-18 w-full px-5">
|
||||
<div className="max-h-44 w-auto mb-4">
|
||||
<Image
|
||||
src={logoDark}
|
||||
className="h-18 md:h-40 w-auto hidden dark:block"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
<Image
|
||||
src={logoLight}
|
||||
className="h-18 md:h-40 w-auto block dark:hidden"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center items-center">
|
||||
<h1>This organization wasn't found. Please contact your organization owner.</h1>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const stripeCustomerId = org.stripeCustomerId;
|
||||
if (stripeCustomerId) {
|
||||
const subscription = await fetchSubscription(org.id);
|
||||
console.log(org);
|
||||
console.log(subscription);
|
||||
if (isServiceError(subscription)) {
|
||||
return (
|
||||
<div className="flex flex-col justify-center items-center mt-8 mb-8 md:mt-18 w-full px-5">
|
||||
<div className="max-h-44 w-auto mb-4">
|
||||
<Image
|
||||
src={logoDark}
|
||||
className="h-18 md:h-40 w-auto hidden dark:block"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
<Image
|
||||
src={logoLight}
|
||||
className="h-18 md:h-40 w-auto block dark:hidden"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center items-center">
|
||||
<h1>This organization's subscription has expired. Please renew the subscription and try again.</h1>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center h-screen px-6">
|
||||
<h1 className="text-2xl font-bold">You have been invited to org {orgName.name}</h1>
|
||||
<div className="flex flex-col justify-center items-center mt-8 mb-8 md:mt-18 w-full px-5">
|
||||
<div className="max-h-44 w-auto mb-4">
|
||||
<Image
|
||||
src={logoDark}
|
||||
className="h-18 md:h-40 w-auto hidden dark:block"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
<Image
|
||||
src={logoLight}
|
||||
className="h-18 md:h-40 w-auto block dark:hidden"
|
||||
alt={"Sourcebot logo"}
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between items-center w-full max-w-2xl">
|
||||
<h1 className="text-2xl font-bold">You have been invited to org {org.name}</h1>
|
||||
<AcceptInviteButton invite={invite} userId={user.id} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ export enum ErrorCode {
|
|||
NOT_FOUND = 'NOT_FOUND',
|
||||
CONNECTION_SYNC_ALREADY_SCHEDULED = 'CONNECTION_SYNC_ALREADY_SCHEDULED',
|
||||
ORG_DOMAIN_ALREADY_EXISTS = 'ORG_DOMAIN_ALREADY_EXISTS',
|
||||
ORG_INVALID_SUBSCRIPTION = 'ORG_INVALID_SUBSCRIPTION',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,4 +91,12 @@ export const orgDomainExists = (): ServiceError => {
|
|||
errorCode: ErrorCode.ORG_DOMAIN_ALREADY_EXISTS,
|
||||
message: "Organization domain already exists, please try a different one.",
|
||||
}
|
||||
}
|
||||
|
||||
export const orgInvalidSubscription = (): ServiceError => {
|
||||
return {
|
||||
statusCode: StatusCodes.BAD_REQUEST,
|
||||
errorCode: ErrorCode.ORG_INVALID_SUBSCRIPTION,
|
||||
message: "Invalid subscription",
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue