diff --git a/apps/backend/src/api/routes/billing.controller.ts b/apps/backend/src/api/routes/billing.controller.ts index 32afd36b..988eabc1 100644 --- a/apps/backend/src/api/routes/billing.controller.ts +++ b/apps/backend/src/api/routes/billing.controller.ts @@ -2,9 +2,10 @@ import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service'; import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service'; import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; -import { Organization } from '@prisma/client'; +import { Organization, User } from '@prisma/client'; import { BillingSubscribeDto } from '@gitroom/nestjs-libraries/dtos/billing/billing.subscribe.dto'; import { ApiTags } from '@nestjs/swagger'; +import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request'; @ApiTags('Billing') @Controller('/billing') @@ -71,4 +72,17 @@ export class BillingController { ) { return this._stripeService.lifetimeDeal(org.id, body.code); } + + @Post('/add-subscription') + async addSubscription( + @Body() body: { subscription: string }, + @GetUserFromRequest() user: User, + @GetOrgFromRequest() org: Organization + ) { + if (!user.isSuperAdmin) { + throw new Error('Unauthorized'); + } + + await this._subscriptionService.addSubscription(org.id, user.id, body.subscription); + } } diff --git a/apps/backend/src/api/routes/copilot.controller.ts b/apps/backend/src/api/routes/copilot.controller.ts index 2f46b48a..e66a13fd 100644 --- a/apps/backend/src/api/routes/copilot.controller.ts +++ b/apps/backend/src/api/routes/copilot.controller.ts @@ -22,7 +22,7 @@ export class CopilotController { req?.body?.variables?.data?.metadata?.requestType === 'TextareaCompletion' ? 'gpt-4o-mini' - : 'gpt-4o', + : 'gpt-4o-2024-08-06', }), }); diff --git a/apps/frontend/src/components/billing/faq.component.tsx b/apps/frontend/src/components/billing/faq.component.tsx index dc82aa97..34c4026f 100644 --- a/apps/frontend/src/components/billing/faq.component.tsx +++ b/apps/frontend/src/components/billing/faq.component.tsx @@ -4,9 +4,15 @@ import interClass from '@gitroom/react/helpers/inter.font'; import { isGeneral } from '@gitroom/react/helpers/is.general'; const list = [ + { + title: `Can I trust ${isGeneral() ? 'Postiz' : 'Gitroom'}?`, + description: `${isGeneral() ? 'Postiz' : 'Gitroom'} is proudly open-source! We believe in an ethical and transparent culture, meaning Postiz will live forever. You can check the entire code / or use it for your personal use. You can check the open-source repository click here.`, + }, { title: 'What are channels?', - description: `${isGeneral() ? 'Postiz' : 'Gitroom'} allows you to schedule your posts between different channels. + description: `${ + isGeneral() ? 'Postiz' : 'Gitroom' + } allows you to schedule your posts between different channels. A channel is a publishing platform where you can schedule your posts. For example, you can schedule your posts on Twitter, Linkedin, DEV and Hashnode`, }, @@ -35,7 +41,9 @@ export const FAQSection: FC<{ title: string; description: string }> = ( className="bg-sixth p-[24px] border border-tableBorder rounded-[4px] flex flex-col" onClick={changeShow} > -
+
{title}
{!show ? ( diff --git a/apps/frontend/src/components/billing/main.billing.component.tsx b/apps/frontend/src/components/billing/main.billing.component.tsx index 5931980c..48ffd033 100644 --- a/apps/frontend/src/components/billing/main.billing.component.tsx +++ b/apps/frontend/src/components/billing/main.billing.component.tsx @@ -19,6 +19,7 @@ import { useSWRConfig } from 'swr'; import { useUser } from '@gitroom/frontend/components/layout/user.context'; import interClass from '@gitroom/react/helpers/inter.font'; import { useRouter } from 'next/navigation'; +import { isGeneral } from '@gitroom/react/helpers/is.general'; export interface Tiers { month: Array<{ @@ -79,7 +80,7 @@ export const Prorate: FC<{ return (
- (Pay Today ${(price < 0 ? 0 : price).toFixed(1)}) + (Pay Today ${(price < 0 ? 0 : price)?.toFixed(1)})
); }; @@ -106,8 +107,20 @@ export const Features: FC<{ if (currentPricing?.ai) { list.push(`AI auto-complete`); + list.push(`AI copilots`); + list.push(`AI Autocomplete`); } + list.push(`Advanced Picture Editor`); + + if (currentPricing?.image_generator) { + list.push( + `${currentPricing?.image_generation_count} AI Images per month` + ); + } + + list.push(`Marketplace full access`); + return list; }, [pack]); @@ -315,81 +328,106 @@ export const MainBillingComponent: FC<{
- {Object.entries(pricing).map(([name, values]) => ( -
-
{name}
-
-
- $ - {monthlyOrYearly === 'on' - ? values.year_price - : values.month_price} -
-
- {monthlyOrYearly === 'on' ? '/year' : '/month'} -
-
-
- {currentPackage === name.toUpperCase() && - subscription?.cancelAt ? ( -
-
- -
+ {Object.entries(pricing) + .filter((f) => !isGeneral() || f[0] !== 'FREE') + .map(([name, values]) => ( +
+
{name}
+
+
+ $ + {monthlyOrYearly === 'on' + ? values.year_price + : values.month_price}
- ) : ( - - )} - {subscription && - currentPackage !== name.toUpperCase() && - name !== 'FREE' && - !!name && ( - +
+ {monthlyOrYearly === 'on' ? '/year' : '/month'} +
+
+
+ {currentPackage === name.toUpperCase() && + subscription?.cancelAt ? ( +
+
+ +
+
+ ) : ( + )} + {subscription && + currentPackage !== name.toUpperCase() && + name !== 'FREE' && + !!name && ( + + )} +
+
- -
- ))} + ))}
{!!subscription?.id && ( -
+
+ {isGeneral() && !subscription?.cancelAt && ( + + )} +
+ )} + {subscription?.cancelAt && isGeneral() && ( +
+ Your subscription will be cancel at{' '} + {dayjs(subscription.cancelAt).local().format('D MMM, YYYY')} +
+ You will never be charged again
)} diff --git a/apps/frontend/src/components/launches/add.edit.model.tsx b/apps/frontend/src/components/launches/add.edit.model.tsx index 328720c7..1e41e7c9 100644 --- a/apps/frontend/src/components/launches/add.edit.model.tsx +++ b/apps/frontend/src/components/launches/add.edit.model.tsx @@ -381,7 +381,7 @@ export const AddEditModal: FC<{ title: 'AI Content Assistant', }} className="!z-[499]" - instructions="You are an assistant that help the user to schedule their social media posts, do not answers to questions that don't trigger a function call" + instructions="You are an assistant that help the user to schedule their social media posts, everytime somebody write something, try to use a function call, if not prompt the user that the request is invalid and you are here to assists with social media posts" /> )}
diff --git a/apps/frontend/src/components/layout/impersonate.tsx b/apps/frontend/src/components/layout/impersonate.tsx index b8252b78..f13ad7b0 100644 --- a/apps/frontend/src/components/layout/impersonate.tsx +++ b/apps/frontend/src/components/layout/impersonate.tsx @@ -1,9 +1,55 @@ import { Input } from '@gitroom/react/form/input'; -import { useCallback, useMemo, useState } from 'react'; +import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; import useSWR from 'swr'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { useUser } from '@gitroom/frontend/components/layout/user.context'; +import { Select } from '@gitroom/react/form/select'; +import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing'; +import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; +export const Subscription = () => { + const fetch = useFetch(); + const addSubscription: ChangeEventHandler = useCallback( + async (e) => { + const value = e.target.value; + if ( + await deleteDialog( + 'Are you sure you want to add a user subscription?', + 'Add' + ) + ) { + await fetch('/billing/add-subscription', { + method: 'POST', + body: JSON.stringify({ subscription: value }), + }); + + window.location.reload(); + } + }, + [] + ); + + return ( + + ); +}; export const Impersonate = () => { const fetch = useFetch(); const [name, setName] = useState(''); @@ -76,6 +122,7 @@ export const Impersonate = () => { X
+ {user?.tier?.current === 'FREE' && }
) : ( { refreshWhenHidden: false, }); + if (!user) return null; + return ( { - + {(user.tier !== 'FREE' || !isGeneral()) && }
@@ -92,7 +95,11 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => { )}
- {user?.orgId ? :
} + {user?.orgId && (user.tier !== 'FREE' || !isGeneral()) ? ( + + ) : ( +
+ )}
@@ -101,8 +108,77 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
- - <div className="flex flex-1 flex-col">{children}</div> + {user.tier === 'FREE' && isGeneral() ? ( + <> + <div className="text-center mb-[20px] text-xl"> + <h1 className="text-3xl"> + Join 1000+ Entrepreneurs Who Use Postiz + <br /> + To Manage All Your Social Media Channels + </h1> + <br /> + <div className="table mx-auto"> + <div className="flex gap-[5px] items-center"> + <div> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + > + <path + d="M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z" + fill="#06ff00" + /> + </svg> + </div> + <div>100% no-risk trial</div> + </div> + <div className="flex gap-[5px] items-center"> + <div> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + > + <path + d="M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z" + fill="#06ff00" + /> + </svg> + </div> + <div>Pay nothing for the first 7 days</div> + </div> + <div className="flex gap-[5px] items-center"> + <div> + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + > + <path + d="M16.2806 9.21937C16.3504 9.28903 16.4057 9.37175 16.4434 9.46279C16.4812 9.55384 16.5006 9.65144 16.5006 9.75C16.5006 9.84856 16.4812 9.94616 16.4434 10.0372C16.4057 10.1283 16.3504 10.211 16.2806 10.2806L11.0306 15.5306C10.961 15.6004 10.8783 15.6557 10.7872 15.6934C10.6962 15.7312 10.5986 15.7506 10.5 15.7506C10.4014 15.7506 10.3038 15.7312 10.2128 15.6934C10.1218 15.6557 10.039 15.6004 9.96938 15.5306L7.71938 13.2806C7.57865 13.1399 7.49959 12.949 7.49959 12.75C7.49959 12.551 7.57865 12.3601 7.71938 12.2194C7.86011 12.0786 8.05098 11.9996 8.25 11.9996C8.44903 11.9996 8.6399 12.0786 8.78063 12.2194L10.5 13.9397L15.2194 9.21937C15.289 9.14964 15.3718 9.09432 15.4628 9.05658C15.5538 9.01884 15.6514 8.99941 15.75 8.99941C15.8486 8.99941 15.9462 9.01884 16.0372 9.05658C16.1283 9.09432 16.211 9.14964 16.2806 9.21937ZM21.75 12C21.75 13.9284 21.1782 15.8134 20.1068 17.4168C19.0355 19.0202 17.5127 20.2699 15.7312 21.0078C13.9496 21.7458 11.9892 21.9389 10.0979 21.5627C8.20656 21.1865 6.46928 20.2579 5.10571 18.8943C3.74215 17.5307 2.81355 15.7934 2.43735 13.9021C2.06114 12.0108 2.25422 10.0504 2.99218 8.26884C3.73013 6.48726 4.97982 4.96451 6.58319 3.89317C8.18657 2.82183 10.0716 2.25 12 2.25C14.585 2.25273 17.0634 3.28084 18.8913 5.10872C20.7192 6.93661 21.7473 9.41498 21.75 12ZM20.25 12C20.25 10.3683 19.7661 8.77325 18.8596 7.41655C17.9531 6.05984 16.6646 5.00242 15.1571 4.37799C13.6497 3.75357 11.9909 3.59019 10.3905 3.90852C8.79017 4.22685 7.32016 5.01259 6.16637 6.16637C5.01259 7.32015 4.22685 8.79016 3.90853 10.3905C3.5902 11.9908 3.75358 13.6496 4.378 15.1571C5.00242 16.6646 6.05984 17.9531 7.41655 18.8596C8.77326 19.7661 10.3683 20.25 12 20.25C14.1873 20.2475 16.2843 19.3775 17.8309 17.8309C19.3775 16.2843 20.2475 14.1873 20.25 12Z" + fill="#06ff00" + /> + </svg> + </div> + <div>Cancel anytime, hassle-free</div> + </div> + </div> + </div> + <BillingComponent /> + </> + ) : ( + <> + <Title /> + <div className="flex flex-1 flex-col">{children}</div> + </> + )} </div> </div> </div> diff --git a/apps/frontend/src/components/layout/settings.component.tsx b/apps/frontend/src/components/layout/settings.component.tsx index eb694f27..a8854ae2 100644 --- a/apps/frontend/src/components/layout/settings.component.tsx +++ b/apps/frontend/src/components/layout/settings.component.tsx @@ -15,6 +15,7 @@ import { TeamsComponent } from '@gitroom/frontend/components/settings/teams.comp import { isGeneral } from '@gitroom/react/helpers/is.general'; import { useUser } from '@gitroom/frontend/components/layout/user.context'; import { LogoutComponent } from '@gitroom/frontend/components/layout/logout.component'; +import { useSearchParams } from 'next/navigation'; export const SettingsPopup: FC<{ getRef?: Ref<any> }> = (props) => { const { getRef } = props; @@ -33,6 +34,9 @@ export const SettingsPopup: FC<{ getRef?: Ref<any> }> = (props) => { return modal.closeAll(); }, []); + const url = useSearchParams(); + const showLogout = !url.get('onboarding'); + const loadProfile = useCallback(async () => { const personal = await (await fetch('/user/personal')).json(); form.setValue('fullname', personal.name || ''); @@ -188,7 +192,7 @@ export const SettingsPopup: FC<{ getRef?: Ref<any> }> = (props) => { </div> )} {!!user?.tier?.team_members && isGeneral() && <TeamsComponent />} - <LogoutComponent /> + {showLogout && <LogoutComponent />} </div> </form> </FormProvider> diff --git a/apps/frontend/src/components/layout/user.context.tsx b/apps/frontend/src/components/layout/user.context.tsx index 6dd4ff8b..1be1698f 100644 --- a/apps/frontend/src/components/layout/user.context.tsx +++ b/apps/frontend/src/components/layout/user.context.tsx @@ -22,7 +22,7 @@ export const UserContext = createContext< export const ContextWrapper: FC<{ user: User & { orgId: string; - tier: 'FREE' | 'STANDARD' | 'PRO'; + tier: 'FREE' | 'STANDARD' | 'PRO' | 'ULTIMATE' | 'TEAM'; role: 'USER' | 'ADMIN' | 'SUPERADMIN'; totalChannels: number; }; diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts b/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts index 2ea68379..356db032 100644 --- a/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts +++ b/libraries/nestjs-libraries/src/bull-mq-transport/client/bull-mq.client.ts @@ -58,6 +58,8 @@ export class BullMqClient extends ClientProxy { queue .add(packet.pattern, packet.data, { jobId: packet.data.id ?? v4(), + removeOnComplete: true, + removeOnFail: true, ...packet.data.options, }) .then(async (job) => { diff --git a/libraries/nestjs-libraries/src/bull-mq-transport/server/bull-mq.server.ts b/libraries/nestjs-libraries/src/bull-mq-transport/server/bull-mq.server.ts index 4573dfd2..3aef1bc5 100644 --- a/libraries/nestjs-libraries/src/bull-mq-transport/server/bull-mq.server.ts +++ b/libraries/nestjs-libraries/src/bull-mq-transport/server/bull-mq.server.ts @@ -7,7 +7,7 @@ import { import { Job, Worker } from 'bullmq'; import { BULLMQ_MODULE_OPTIONS } from '../constants/bull-mq.constants'; import { WorkerFactory } from '../factories/worker.factory'; -import {IBullMqModuleOptions} from "@gitroom/nestjs-libraries/bull-mq-transport/interfaces/bull-mq-module-options.interface"; +import { IBullMqModuleOptions } from '@gitroom/nestjs-libraries/bull-mq-transport/interfaces/bull-mq-module-options.interface'; @Injectable() export class BullMqServer extends Server implements CustomTransportStrategy { @@ -19,7 +19,7 @@ export class BullMqServer extends Server implements CustomTransportStrategy { constructor( @Inject(BULLMQ_MODULE_OPTIONS) private readonly options: IBullMqModuleOptions, - private readonly workerFactory: WorkerFactory, + private readonly workerFactory: WorkerFactory ) { super(); @@ -29,18 +29,14 @@ export class BullMqServer extends Server implements CustomTransportStrategy { listen(callback: (...optionalParams: unknown[]) => void) { for (const [pattern, handler] of this.messageHandlers) { - if ( - pattern && - handler && - !this.workers.has(pattern) - ) { + if (pattern && handler && !this.workers.has(pattern)) { const worker = this.workerFactory.create( pattern, (job: Job) => { // eslint-disable-next-line no-async-promise-executor return new Promise<unknown>(async (resolve, reject) => { const stream$ = this.transformToObservable( - await handler(job.data.payload, job), + await handler(job.data.payload, job) ); this.send(stream$, (packet) => { if (packet.err) { @@ -52,8 +48,9 @@ export class BullMqServer extends Server implements CustomTransportStrategy { }, { ...this.options, - ...handler?.extras - }, + ...{ removeOnComplete: { count: 0 }, removeOnFail: { count: 0 } }, + ...handler?.extras, + } ); this.workers.set(pattern, worker); this.logger.log(`Registered queue "${pattern}"`); diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 349b34d4..41a59d39 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -471,6 +471,8 @@ enum State { enum SubscriptionTier { STANDARD PRO + TEAM + ULTIMATE } enum Period { diff --git a/libraries/nestjs-libraries/src/database/prisma/subscriptions/pricing.ts b/libraries/nestjs-libraries/src/database/prisma/subscriptions/pricing.ts index cc8ac74c..67dc2f15 100644 --- a/libraries/nestjs-libraries/src/database/prisma/subscriptions/pricing.ts +++ b/libraries/nestjs-libraries/src/database/prisma/subscriptions/pricing.ts @@ -32,8 +32,8 @@ export const pricing: PricingInterface = { }, STANDARD: { current: 'STANDARD', - month_price: 30, - year_price: 288, + month_price: 29, + year_price: 278, channel: 5, posts_per_month: 400, image_generation_count: 20, @@ -44,11 +44,11 @@ export const pricing: PricingInterface = { import_from_channels: true, image_generator: false, }, - PRO: { - current: 'PRO', - month_price: 40, - year_price: 384, - channel: 8, + TEAM: { + current: 'TEAM', + month_price: 39, + year_price: 374, + channel: 10, posts_per_month: 1000000, image_generation_count: 100, community_features: true, @@ -58,4 +58,32 @@ export const pricing: PricingInterface = { import_from_channels: true, image_generator: true, }, + PRO: { + current: 'PRO', + month_price: 49, + year_price: 470, + channel: 30, + posts_per_month: 1000000, + image_generation_count: 300, + community_features: true, + team_members: true, + featured_by_gitroom: true, + ai: true, + import_from_channels: true, + image_generator: true, + }, + ULTIMATE: { + current: 'ULTIMATE', + month_price: 99, + year_price: 950, + channel: 100, + posts_per_month: 1000000, + image_generation_count: 500, + community_features: true, + team_members: true, + featured_by_gitroom: true, + ai: true, + import_from_channels: true, + image_generator: true, + }, }; diff --git a/libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.repository.ts b/libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.repository.ts index fb415a8f..04ccb431 100644 --- a/libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.repository.ts @@ -202,4 +202,15 @@ export class SubscriptionRepository { }, }); } + + setCustomerId(orgId: string, customerId: string) { + return this._organization.model.organization.update({ + where: { + id: orgId, + }, + data: { + paymentId: customerId, + }, + }); + } } diff --git a/libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.service.ts b/libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.service.ts index 839268cb..4f11c0f9 100644 --- a/libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/subscriptions/subscription.service.ts @@ -5,6 +5,7 @@ import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/in import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service'; import { Organization } from '@prisma/client'; import dayjs from 'dayjs'; +import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; @Injectable() export class SubscriptionService { @@ -164,7 +165,7 @@ export class SubscriptionService { const type = organization?.subscription?.subscriptionTier || 'FREE'; if (type === 'FREE') { - return {credits: 0}; + return { credits: 0 }; } // @ts-ignore @@ -176,10 +177,29 @@ export class SubscriptionService { const checkFromMonth = date.subtract(1, 'month'); const imageGenerationCount = pricing[type].image_generation_count; - const totalUse = await this._subscriptionRepository.getCreditsFrom(organization.id, checkFromMonth); + const totalUse = await this._subscriptionRepository.getCreditsFrom( + organization.id, + checkFromMonth + ); return { credits: imageGenerationCount - totalUse, - } + }; } + + async addSubscription(orgId: string, userId: string, subscription: any) { + await this._subscriptionRepository.setCustomerId(orgId, orgId); + return this.createOrUpdateSubscription( + makeId(5), + userId, + pricing[subscription].channel!, + subscription, + 'MONTHLY', + null, + undefined, + orgId + ); + } + + } diff --git a/libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts b/libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts index eed1b49d..7544d722 100644 --- a/libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/billing/billing.subscribe.dto.ts @@ -4,6 +4,6 @@ export class BillingSubscribeDto { @IsIn(['MONTHLY', 'YEARLY']) period: 'MONTHLY' | 'YEARLY'; - @IsIn(['STANDARD', 'PRO']) - billing: 'STANDARD' | 'PRO'; + @IsIn(['STANDARD', 'PRO', 'TEAM', 'ULTIMATE']) + billing: 'STANDARD' | 'PRO' | 'TEAM' | 'ULTIMATE'; } diff --git a/libraries/nestjs-libraries/src/services/stripe.service.ts b/libraries/nestjs-libraries/src/services/stripe.service.ts index 6791ea78..61aa6d65 100644 --- a/libraries/nestjs-libraries/src/services/stripe.service.ts +++ b/libraries/nestjs-libraries/src/services/stripe.service.ts @@ -155,6 +155,7 @@ export class StripeService { (p) => p?.recurring?.interval?.toLowerCase() === (body.period === 'MONTHLY' ? 'month' : 'year') && + p?.nickname === body.billing + ' ' + body.period && p?.unit_amount === (body.period === 'MONTHLY' ? priceData.month_price @@ -177,10 +178,14 @@ export class StripeService { const proration_date = Math.floor(Date.now() / 1000); - const currentUserSubscription = await stripe.subscriptions.list({ - customer, - status: 'active', - }); + const currentUserSubscription = { + data: ( + await stripe.subscriptions.list({ + customer, + status: 'all', + }) + ).data.filter((f) => f.status === 'active' || f.status === 'trialing'), + }; try { const price = await stripe.invoices.retrieveUpcoming({ @@ -210,10 +215,14 @@ export class StripeService { const id = makeId(10); const org = await this._organizationService.getOrgById(organizationId); const customer = await this.createOrGetCustomer(org!); - const currentUserSubscription = await stripe.subscriptions.list({ - customer, - status: 'active', - }); + const currentUserSubscription = { + data: ( + await stripe.subscriptions.list({ + customer, + status: 'all', + }) + ).data.filter((f) => f.status === 'active' || f.status === 'trialing'), + }; const { cancel_at } = await stripe.subscriptions.update( currentUserSubscription.data[0].id, @@ -261,9 +270,13 @@ export class StripeService { ) { const { url } = await stripe.checkout.sessions.create({ customer, - success_url: process.env['FRONTEND_URL'] + `/billing?check=${uniqueId}`, + return_url: process.env['FRONTEND_URL'] + `/billing`, + success_url: + process.env['FRONTEND_URL'] + + `/launches?onboarding=true&check=${uniqueId}`, mode: 'subscription', subscription_data: { + trial_period_days: 7, metadata: { service: 'gitroom', ...body, @@ -307,7 +320,7 @@ export class StripeService { }, card_payments: { requested: true, - } + }, }, tos_acceptance: { service_agreement: 'full', @@ -444,10 +457,14 @@ export class StripeService { }, })); - const currentUserSubscription = await stripe.subscriptions.list({ - customer, - status: 'active', - }); + const currentUserSubscription = { + data: ( + await stripe.subscriptions.list({ + customer, + status: 'all', + }) + ).data.filter((f) => f.status === 'active' || f.status === 'trialing'), + }; if (!currentUserSubscription.data.length) { return this.createCheckoutSession(id, customer, body, findPrice!.id); @@ -540,7 +557,9 @@ export class StripeService { await this._subscriptionService.createOrUpdateSubscription( makeId(10), organizationId, - getCurrentSubscription?.subscriptionTier === 'PRO' ? (getCurrentSubscription.totalChannels + 5) : findPricing.channel!, + getCurrentSubscription?.subscriptionTier === 'PRO' + ? getCurrentSubscription.totalChannels + 5 + : findPricing.channel!, nextPackage, 'MONTHLY', null, @@ -550,7 +569,6 @@ export class StripeService { return { success: true, }; - } catch (err) { console.log(err); return {