feat: hold money
This commit is contained in:
parent
5acdd88e53
commit
3e00e15e35
|
|
@ -24,10 +24,10 @@ export class BillingController {
|
|||
@Param('id') body: string
|
||||
) {
|
||||
return {
|
||||
exists: !!(await this._subscriptionService.checkSubscription(
|
||||
status: await this._stripeService.checkSubscription(
|
||||
org.id,
|
||||
body
|
||||
)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ export class BillingController {
|
|||
@Req() req: Request
|
||||
) {
|
||||
const uniqueId = req?.cookies?.track;
|
||||
return this._stripeService.subscribe(uniqueId, org.id, user.id, body);
|
||||
return this._stripeService.subscribe(uniqueId, org.id, user.id, body, org.allowTrial);
|
||||
}
|
||||
|
||||
@Get('/portal')
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export class UsersController {
|
|||
isLifetime: !!organization?.subscription?.isLifetime,
|
||||
admin: !!user.isSuperAdmin,
|
||||
impersonate: !!req.cookies.impersonate,
|
||||
allowTrial: organization?.allowTrial,
|
||||
// @ts-ignore
|
||||
publicApi: organization?.users[0]?.role === 'SUPERADMIN' || organization?.users[0]?.role === 'ADMIN'
|
||||
? organization?.apiKey
|
||||
|
|
|
|||
|
|
@ -481,7 +481,7 @@ export const MainBillingComponent: FC<{
|
|||
.format('D MMM, YYYY')}`
|
||||
: 'Cancel subscription'
|
||||
: // @ts-ignore
|
||||
user?.tier === 'FREE' || user?.tier?.current === 'FREE'
|
||||
(user?.tier === 'FREE' || user?.tier?.current === 'FREE') && user.allowTrial
|
||||
? 'Start 7 days free trial'
|
||||
: 'Purchase'}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import { FC, useCallback, useEffect, useState } from 'react';
|
||||
import Loading from 'react-loading';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
|
||||
export const CheckPayment: FC<{ check: string; mutate: () => void }> = (props) => {
|
||||
const [showLoader, setShowLoader] = useState(true);
|
||||
const fetch = useFetch();
|
||||
const toaster = useToaster();
|
||||
|
||||
const checkSubscription = useCallback(async () => {
|
||||
const {status} = await (await fetch('/billing/check/' + props.check)).json();
|
||||
if (status === 0) {
|
||||
await timer(1000);
|
||||
return checkSubscription();
|
||||
}
|
||||
|
||||
if (status === 1) {
|
||||
toaster.show(
|
||||
'We could not validate your payment method, please try again',
|
||||
'warning'
|
||||
);
|
||||
setShowLoader(false);
|
||||
}
|
||||
|
||||
if (status === 2) {
|
||||
setShowLoader(false);
|
||||
props.mutate();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
checkSubscription();
|
||||
}, []);
|
||||
|
||||
if (showLoader) {
|
||||
return (
|
||||
<div className="fixed bg-black/40 w-full h-full flex justify-center items-center z-[400]">
|
||||
<div>
|
||||
<Loading type="spin" color="#612AD5" height={250} width={250} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import { ReactNode, useCallback, useEffect } from 'react';
|
||||
import { Title } from '@gitroom/frontend/components/layout/title';
|
||||
import { ContextWrapper } from '@gitroom/frontend/components/layout/user.context';
|
||||
import { TopMenu } from '@gitroom/frontend/components/layout/top.menu';
|
||||
|
|
@ -8,7 +8,7 @@ import { MantineWrapper } from '@gitroom/react/helpers/mantine.wrapper';
|
|||
import { ToolTip } from '@gitroom/frontend/components/layout/top.tip';
|
||||
import { ShowMediaBoxModal } from '@gitroom/frontend/components/media/media.component';
|
||||
import Image from 'next/image';
|
||||
import { Toaster } from '@gitroom/react/toaster/toaster';
|
||||
import { Toaster, useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { ShowPostSelector } from '@gitroom/frontend/components/post-url-selector/post.url.selector';
|
||||
import { OrganizationSelector } from '@gitroom/frontend/components/layout/organization.selector';
|
||||
import NotificationComponent from '@gitroom/frontend/components/notifications/notification.component';
|
||||
|
|
@ -37,6 +37,8 @@ const ModeComponent = dynamic(
|
|||
);
|
||||
|
||||
import { extend } from 'dayjs';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { CheckPayment } from '@gitroom/frontend/components/layout/check.payment';
|
||||
|
||||
extend(utc);
|
||||
extend(weekOfYear);
|
||||
|
|
@ -47,11 +49,12 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
|
|||
const fetch = useFetch();
|
||||
const { isGeneral } = useVariables();
|
||||
const { backendUrl, billingEnabled } = useVariables();
|
||||
const searchParams = useSearchParams();
|
||||
const load = useCallback(async (path: string) => {
|
||||
return await (await fetch(path)).json();
|
||||
}, []);
|
||||
|
||||
const { data: user } = useSWR('/user/self', load, {
|
||||
const { data: user, mutate } = useSWR('/user/self', load, {
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
revalidateIfStale: false,
|
||||
|
|
@ -69,6 +72,12 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
|
|||
>
|
||||
<ContextWrapper user={user}>
|
||||
<MantineWrapper>
|
||||
{user.tier === 'FREE' && searchParams.get('check') && (
|
||||
<CheckPayment
|
||||
check={searchParams.get('check')!}
|
||||
mutate={mutate}
|
||||
/>
|
||||
)}
|
||||
<ToolTip />
|
||||
<ShowMediaBoxModal />
|
||||
<ShowLinkedinCompany />
|
||||
|
|
@ -132,7 +141,10 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
|
|||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div id = "systray-buttons" className="flex items-center justify-self-end gap-[8px] order-2 md:order-3">
|
||||
<div
|
||||
id="systray-buttons"
|
||||
className="flex items-center justify-self-end gap-[8px] order-2 md:order-3"
|
||||
>
|
||||
<ModeComponent />
|
||||
<SettingsComponent />
|
||||
<NotificationComponent />
|
||||
|
|
@ -150,59 +162,61 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
|
|||
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>
|
||||
{user?.allowTrial && (
|
||||
<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>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 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>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 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>Cancel anytime, hassle-free</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<BillingComponent />
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const UserContext = createContext<
|
|||
totalChannels: number;
|
||||
isLifetime?: boolean;
|
||||
impersonate: boolean;
|
||||
allowTrial: boolean;
|
||||
})
|
||||
>(undefined);
|
||||
|
||||
|
|
|
|||
|
|
@ -131,11 +131,11 @@ module.exports = {
|
|||
'100%': { overflow: 'hidden' },
|
||||
},
|
||||
fadeDown: {
|
||||
'0%': { opacity: 0, transform: 'translateY(-30px)' },
|
||||
'10%': { opacity: 1, transform: 'translateY(0)' },
|
||||
'85%': { opacity: 1, transform: 'translateY(0)' },
|
||||
'90%': { opacity: 1, transform: 'translateY(10px)' },
|
||||
'100%': { opacity: 0, transform: 'translateY(-30px)' },
|
||||
'0%': { opacity: 0, marginTop: -30},
|
||||
'10%': { opacity: 1, marginTop: 0 },
|
||||
'85%': { opacity: 1, marginTop: 0 },
|
||||
'90%': { opacity: 1, marginTop: 10 },
|
||||
'100%': { opacity: 0, marginTop: -30 },
|
||||
},
|
||||
normalFadeDown: {
|
||||
'0%': { opacity: 0, transform: 'translateY(-30px)' },
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ export class OrganizationRepository {
|
|||
data: {
|
||||
name: body.company,
|
||||
apiKey: AuthService.fixedEncryption(makeId(20)),
|
||||
allowTrial: true,
|
||||
users: {
|
||||
create: {
|
||||
role: Role.SUPERADMIN,
|
||||
|
|
@ -246,6 +247,14 @@ export class OrganizationRepository {
|
|||
});
|
||||
}
|
||||
|
||||
getOrgByCustomerId(customerId: string) {
|
||||
return this._organization.model.organization.findFirst({
|
||||
where: {
|
||||
paymentId: customerId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getTeam(orgId: string) {
|
||||
return this._organization.model.organization.findUnique({
|
||||
where: {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ export class OrganizationService {
|
|||
return this._organizationRepository.getTeam(orgId);
|
||||
}
|
||||
|
||||
getOrgByCustomerId(customerId: string) {
|
||||
return this._organizationRepository.getOrgByCustomerId(customerId);
|
||||
}
|
||||
|
||||
async inviteTeamMember(orgId: string, body: AddTeamMemberDto) {
|
||||
const timeLimit = dayjs().add(1, 'hour').format('YYYY-MM-DD HH:mm:ss');
|
||||
const id = makeId(5);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ model Organization {
|
|||
Integration Integration[]
|
||||
post Post[] @relation("organization")
|
||||
submittedPost Post[] @relation("submittedForOrg")
|
||||
allowTrial Boolean @default(false)
|
||||
Comments Comments[]
|
||||
notifications Notifications[]
|
||||
buyerOrganization MessagesGroup[]
|
||||
|
|
|
|||
|
|
@ -64,6 +64,17 @@ export class SubscriptionRepository {
|
|||
});
|
||||
}
|
||||
|
||||
getCustomerIdByOrgId(organizationId: string) {
|
||||
return this._organization.model.organization.findFirst({
|
||||
where: {
|
||||
id: organizationId,
|
||||
},
|
||||
select: {
|
||||
paymentId: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
checkSubscription(organizationId: string, subscriptionId: string) {
|
||||
return this._subscription.model.subscription.findFirst({
|
||||
where: {
|
||||
|
|
@ -158,6 +169,15 @@ export class SubscriptionRepository {
|
|||
},
|
||||
});
|
||||
|
||||
await this._organization.model.organization.update({
|
||||
where: {
|
||||
id: findOrg.id,
|
||||
},
|
||||
data: {
|
||||
allowTrial: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (code) {
|
||||
await this._usedCodes.model.usedCodes.create({
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/o
|
|||
import { Organization } from '@prisma/client';
|
||||
import dayjs from 'dayjs';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';
|
||||
|
||||
@Injectable()
|
||||
export class SubscriptionService {
|
||||
constructor(
|
||||
private readonly _subscriptionRepository: SubscriptionRepository,
|
||||
private readonly _integrationService: IntegrationService,
|
||||
private readonly _organizationService: OrganizationService
|
||||
private readonly _organizationService: OrganizationService,
|
||||
) {}
|
||||
|
||||
getSubscriptionByOrganizationId(organizationId: string) {
|
||||
|
|
@ -55,8 +56,8 @@ export class SubscriptionService {
|
|||
);
|
||||
}
|
||||
|
||||
checkSubscription(organizationId: string, subscriptionId: string) {
|
||||
return this._subscriptionRepository.checkSubscription(
|
||||
async checkSubscription(organizationId: string, subscriptionId: string) {
|
||||
return await this._subscriptionRepository.checkSubscription(
|
||||
organizationId,
|
||||
subscriptionId
|
||||
);
|
||||
|
|
@ -197,9 +198,7 @@ export class SubscriptionService {
|
|||
'MONTHLY',
|
||||
null,
|
||||
undefined,
|
||||
orgId
|
||||
orgId
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,20 +45,79 @@ export class StripeService {
|
|||
);
|
||||
}
|
||||
|
||||
createSubscription(event: Stripe.CustomerSubscriptionCreatedEvent) {
|
||||
async checkValidCard(
|
||||
event:
|
||||
| Stripe.CustomerSubscriptionCreatedEvent
|
||||
| Stripe.CustomerSubscriptionUpdatedEvent
|
||||
) {
|
||||
if (event.data.object.status === 'incomplete') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const getOrgFromCustomer = await this._organizationService.getOrgByCustomerId(event.data.object.customer as string);
|
||||
|
||||
if (!getOrgFromCustomer?.allowTrial) {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log('Checking card');
|
||||
|
||||
const paymentMethods = await stripe.paymentMethods.list({
|
||||
customer: event.data.object.customer as string,
|
||||
});
|
||||
|
||||
// find the last one created
|
||||
const latestMethod = paymentMethods.data.reduce((prev, current) => {
|
||||
if (prev.created < current.created) {
|
||||
return current;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: 100,
|
||||
currency: 'usd',
|
||||
payment_method: latestMethod.id,
|
||||
customer: event.data.object.customer as string,
|
||||
automatic_payment_methods: {
|
||||
allow_redirects: 'never',
|
||||
enabled: true,
|
||||
},
|
||||
capture_method: 'manual', // Authorize without capturing
|
||||
confirm: true, // Confirm the PaymentIntent
|
||||
});
|
||||
|
||||
if (paymentIntent.status !== 'requires_capture') {
|
||||
console.error('Cant charge');
|
||||
await stripe.paymentMethods.detach(paymentMethods.data[0].id);
|
||||
await stripe.subscriptions.cancel(event.data.object.id as string);
|
||||
return false;
|
||||
}
|
||||
|
||||
await stripe.paymentIntents.cancel(paymentIntent.id as string);
|
||||
return true;
|
||||
}
|
||||
|
||||
async createSubscription(event: Stripe.CustomerSubscriptionCreatedEvent) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const {
|
||||
id,
|
||||
uniqueId,
|
||||
billing,
|
||||
period,
|
||||
}: {
|
||||
billing: 'STANDARD' | 'PRO';
|
||||
period: 'MONTHLY' | 'YEARLY';
|
||||
id: string;
|
||||
uniqueId: string;
|
||||
} = event.data.object.metadata;
|
||||
|
||||
const check = await this.checkValidCard(event);
|
||||
if (!check) {
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
return this._subscriptionService.createOrUpdateSubscription(
|
||||
id,
|
||||
uniqueId,
|
||||
event.data.object.customer as string,
|
||||
pricing[billing].channel!,
|
||||
billing,
|
||||
|
|
@ -66,20 +125,26 @@ export class StripeService {
|
|||
event.data.object.cancel_at
|
||||
);
|
||||
}
|
||||
updateSubscription(event: Stripe.CustomerSubscriptionUpdatedEvent) {
|
||||
async updateSubscription(event: Stripe.CustomerSubscriptionUpdatedEvent) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const {
|
||||
id,
|
||||
uniqueId,
|
||||
billing,
|
||||
period,
|
||||
}: {
|
||||
billing: 'STANDARD' | 'PRO';
|
||||
period: 'MONTHLY' | 'YEARLY';
|
||||
id: string;
|
||||
uniqueId: string;
|
||||
} = event.data.object.metadata;
|
||||
|
||||
const check = await this.checkValidCard(event);
|
||||
if (!check) {
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
return this._subscriptionService.createOrUpdateSubscription(
|
||||
id,
|
||||
uniqueId,
|
||||
event.data.object.customer as string,
|
||||
pricing[billing].channel!,
|
||||
billing,
|
||||
|
|
@ -218,6 +283,15 @@ export class StripeService {
|
|||
}
|
||||
}
|
||||
|
||||
async getCustomerSubscriptions(organizationId: string) {
|
||||
const org = (await this._organizationService.getOrgById(organizationId))!;
|
||||
const customer = org.paymentId;
|
||||
return stripe.subscriptions.list({
|
||||
customer: customer!,
|
||||
status: 'all',
|
||||
});
|
||||
}
|
||||
|
||||
async setToCancel(organizationId: string) {
|
||||
const id = makeId(10);
|
||||
const org = await this._organizationService.getOrgById(organizationId);
|
||||
|
|
@ -228,7 +302,7 @@ export class StripeService {
|
|||
customer,
|
||||
status: 'all',
|
||||
})
|
||||
).data,
|
||||
).data.filter((f) => f.status !== 'canceled'),
|
||||
};
|
||||
|
||||
const { cancel_at } = await stripe.subscriptions.update(
|
||||
|
|
@ -275,7 +349,8 @@ export class StripeService {
|
|||
customer: string,
|
||||
body: BillingSubscribeDto,
|
||||
price: string,
|
||||
userId: string
|
||||
userId: string,
|
||||
allowTrial: boolean
|
||||
) {
|
||||
const isUtm = body.utm ? `&utm_source=${body.utm}` : '';
|
||||
const { url } = await stripe.checkout.sessions.create({
|
||||
|
|
@ -286,7 +361,7 @@ export class StripeService {
|
|||
`/launches?onboarding=true&check=${uniqueId}${isUtm}`,
|
||||
mode: 'subscription',
|
||||
subscription_data: {
|
||||
trial_period_days: 7,
|
||||
...(allowTrial ? { trial_period_days: 7 } : {}),
|
||||
metadata: {
|
||||
service: 'gitroom',
|
||||
...body,
|
||||
|
|
@ -370,6 +445,34 @@ export class StripeService {
|
|||
return accountLink.url;
|
||||
}
|
||||
|
||||
async checkSubscription(organizationId: string, subscriptionId: string) {
|
||||
const orgValue = await this._subscriptionService.checkSubscription(
|
||||
organizationId,
|
||||
subscriptionId
|
||||
);
|
||||
|
||||
if (orgValue) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
const getCustomerSubscriptions = await this.getCustomerSubscriptions(
|
||||
organizationId
|
||||
);
|
||||
if (getCustomerSubscriptions.data.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (
|
||||
getCustomerSubscriptions.data.find(
|
||||
(p) => p.metadata.uniqueId === subscriptionId
|
||||
)?.canceled_at
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async payAccountStepOne(
|
||||
userId: string,
|
||||
organization: Organization,
|
||||
|
|
@ -431,7 +534,8 @@ export class StripeService {
|
|||
uniqueId: string,
|
||||
organizationId: string,
|
||||
userId: string,
|
||||
body: BillingSubscribeDto
|
||||
body: BillingSubscribeDto,
|
||||
allowTrial: boolean
|
||||
) {
|
||||
const id = makeId(10);
|
||||
const priceData = pricing[body.billing];
|
||||
|
|
@ -481,6 +585,21 @@ export class StripeService {
|
|||
},
|
||||
}));
|
||||
|
||||
const getCurrentSubscriptions =
|
||||
await this._subscriptionService.getSubscription(organizationId);
|
||||
|
||||
if (!getCurrentSubscriptions) {
|
||||
return this.createCheckoutSession(
|
||||
uniqueId,
|
||||
id,
|
||||
customer,
|
||||
body,
|
||||
findPrice!.id,
|
||||
userId,
|
||||
allowTrial
|
||||
);
|
||||
}
|
||||
|
||||
const currentUserSubscription = {
|
||||
data: (
|
||||
await stripe.subscriptions.list({
|
||||
|
|
@ -490,17 +609,6 @@ export class StripeService {
|
|||
).data.filter((f) => f.status === 'active' || f.status === 'trialing'),
|
||||
};
|
||||
|
||||
if (!currentUserSubscription.data.length) {
|
||||
return this.createCheckoutSession(
|
||||
uniqueId,
|
||||
id,
|
||||
customer,
|
||||
body,
|
||||
findPrice!.id,
|
||||
userId
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await stripe.subscriptions.update(currentUserSubscription.data[0].id, {
|
||||
cancel_at_period_end: false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue