diff --git a/packages/server/src/config/index.ts b/packages/server/src/config/index.ts index 175056d7a..5f3d610fc 100644 --- a/packages/server/src/config/index.ts +++ b/packages/server/src/config/index.ts @@ -201,4 +201,13 @@ module.exports = { storeId: process.env.LEMONSQUEEZY_STORE_ID, webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET, }, + + /** + * Bigcapital (Cloud). + * NOTE: DO NOT CHANGE THIS OPTION OR ADD THIS ENV VAR. + */ + hostedOnBigcapitalCloud: parseBoolean( + process.env.HOSTED_ON_BIGCAPITAL_CLOUD, + false + ), }; diff --git a/packages/server/src/loaders/eventEmitter.ts b/packages/server/src/loaders/eventEmitter.ts index 720a4143b..91d814f8a 100644 --- a/packages/server/src/loaders/eventEmitter.ts +++ b/packages/server/src/loaders/eventEmitter.ts @@ -90,6 +90,8 @@ import { SaleReceiptMarkClosedOnMailSentSubcriber } from '@/services/Sales/Recei import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent'; import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/subscribers/DeleteCashflowTransactionOnUncategorize'; import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete'; +import { SubscribeFreeOnSignupCommunity } from '@/services/Subscription/events/SubscribeFreeOnSignupCommunity'; + export default () => { return new EventPublisher(); @@ -219,5 +221,7 @@ export const susbcribers = () => { // Cashflow DeleteCashflowTransactionOnUncategorize, PreventDeleteTransactionOnDelete, + + SubscribeFreeOnSignupCommunity ]; }; diff --git a/packages/server/src/services/Subscription/LemonSqueezyWebhooks.ts b/packages/server/src/services/Subscription/LemonSqueezyWebhooks.ts index ff21c1871..904a92687 100644 --- a/packages/server/src/services/Subscription/LemonSqueezyWebhooks.ts +++ b/packages/server/src/services/Subscription/LemonSqueezyWebhooks.ts @@ -96,9 +96,7 @@ export class LemonSqueezyWebhooks { if (webhookEvent === 'subscription_created') { await this.subscriptionService.newSubscribtion( tenantId, - 'pro-yearly', - 'year', - 1 + 'early-adaptor', ); } } diff --git a/packages/server/src/services/Subscription/Subscription.ts b/packages/server/src/services/Subscription/Subscription.ts index 6832f7127..39f1d5c57 100644 --- a/packages/server/src/services/Subscription/Subscription.ts +++ b/packages/server/src/services/Subscription/Subscription.ts @@ -15,13 +15,17 @@ export class Subscription { public async newSubscribtion( tenantId: number, planSlug: string, - invoiceInterval: string, - invoicePeriod: number, subscriptionSlug: string = 'main' ) { const tenant = await Tenant.query().findById(tenantId).throwIfNotFound(); const plan = await Plan.query().findOne('slug', planSlug).throwIfNotFound(); + const isFree = plan.price === 0; + + // Take the invoice interval and period from the given plan. + const invoiceInterval = plan.invoiceInternal; + const invoicePeriod = isFree ? Infinity : plan.invoicePeriod; + const subscription = await tenant .$relatedQuery('subscriptions') .modify('subscriptionBySlug', subscriptionSlug) diff --git a/packages/server/src/services/Subscription/SubscriptionPeriod.ts b/packages/server/src/services/Subscription/SubscriptionPeriod.ts index 2deb6b073..442036b43 100644 --- a/packages/server/src/services/Subscription/SubscriptionPeriod.ts +++ b/packages/server/src/services/Subscription/SubscriptionPeriod.ts @@ -1,10 +1,10 @@ -import moment from 'moment'; +import moment, { type unitOfTime } from 'moment'; export default class SubscriptionPeriod { - start: Date; - end: Date; - interval: string; - count: number; + private start: Date; + private end: Date; + private interval: string; + private count: number; /** * Constructor method. @@ -12,7 +12,11 @@ export default class SubscriptionPeriod { * @param {number} count - * @param {Date} start - */ - constructor(interval: string = 'month', count: number, start?: Date) { + constructor( + interval: unitOfTime.DurationConstructor = 'month', + count: number, + start?: Date + ) { this.interval = interval; this.count = count; this.start = start; @@ -20,7 +24,11 @@ export default class SubscriptionPeriod { if (!start) { this.start = moment().toDate(); } - this.end = moment(start).add(count, interval).toDate(); + if (count === Infinity) { + this.end = null; + } else { + this.end = moment(start).add(count, interval).toDate(); + } } getStartDate() { @@ -36,6 +44,6 @@ export default class SubscriptionPeriod { } getIntervalCount() { - return this.interval; + return this.count; } } diff --git a/packages/server/src/services/Subscription/events/SubscribeFreeOnSignupCommunity.tsx b/packages/server/src/services/Subscription/events/SubscribeFreeOnSignupCommunity.tsx new file mode 100644 index 000000000..1649b0a7e --- /dev/null +++ b/packages/server/src/services/Subscription/events/SubscribeFreeOnSignupCommunity.tsx @@ -0,0 +1,36 @@ +import { IAuthSignedUpEventPayload } from '@/interfaces'; +import events from '@/subscribers/events'; +import config from '@/config'; +import { Subscription } from '../Subscription'; +import { Inject, Service } from 'typedi'; + +@Service() +export class SubscribeFreeOnSignupCommunity { + @Inject() + private subscriptionService: Subscription; + + /** + * Attaches events with handlers. + */ + public attach = (bus) => { + bus.subscribe( + events.auth.signUp, + this.subscribeFreeOnSigupCommunity.bind(this) + ); + }; + + /** + * Creates a new free subscription once the user signup if the app is self-hosted. + * @param {IAuthSignedUpEventPayload} + * @returns {Promise} + */ + private async subscribeFreeOnSigupCommunity({ + signupDTO, + tenant, + user, + }: IAuthSignedUpEventPayload) { + if (config.hostedOnBigcapitalCloud) return null; + + await this.subscriptionService.newSubscribtion(tenant.id, 'free'); + } +} diff --git a/packages/server/src/system/models/Tenant.ts b/packages/server/src/system/models/Tenant.ts index b715adc8c..7d5d13613 100644 --- a/packages/server/src/system/models/Tenant.ts +++ b/packages/server/src/system/models/Tenant.ts @@ -211,7 +211,7 @@ export default class Tenant extends BaseModel { static newSubscription( tenantId: number, planId: number, - invoiceInterval: string, + invoiceInterval: 'month' | 'year', invoicePeriod: number, subscriptionSlug: string ) { diff --git a/packages/server/src/system/seeds/seed_subscriptions_plans.js b/packages/server/src/system/seeds/seed_subscriptions_plans.js index 0e69b94db..b810510bc 100644 --- a/packages/server/src/system/seeds/seed_subscriptions_plans.js +++ b/packages/server/src/system/seeds/seed_subscriptions_plans.js @@ -1,65 +1,25 @@ - exports.seed = (knex) => { // Deletes ALL existing entries - return knex('subscription_plans').del() + return knex('subscription_plans') + .del() .then(() => { // Inserts seed entries return knex('subscription_plans').insert([ { - name: 'Essentials', - slug: 'essentials-monthly', - price: 100, + name: 'Free', + slug: 'free', + price: 0, active: true, - currency: 'LYD', - trial_period: 7, - trial_interval: 'days', + currency: 'USD', }, { - name: 'Essentials', - slug: 'essentials-yearly', - price: 1200, + name: 'Early Adaptor', + slug: 'early-adaptor', + price: 29, active: true, - currency: 'LYD', - trial_period: 12, - trial_interval: 'months', - }, - { - name: 'Pro', - slug: 'pro-monthly', - price: 200, - active: true, - currency: 'LYD', - trial_period: 1, - trial_interval: 'months', - }, - { - name: 'Pro', - slug: 'pro-yearly', - price: 500, - active: true, - currency: 'LYD', + currency: 'USD', invoice_period: 12, invoice_interval: 'month', - index: 2, - }, - { - name: 'Plus', - slug: 'plus-monthly', - price: 200, - active: true, - currency: 'LYD', - trial_period: 1, - trial_interval: 'months', - }, - { - name: 'Plus', - slug: 'plus-yearly', - price: 500, - active: true, - currency: 'LYD', - invoice_period: 12, - invoice_interval: 'month', - index: 2, }, ]); }); diff --git a/packages/webapp/src/containers/Setup/SetupDialogs.tsx b/packages/webapp/src/containers/Setup/SetupDialogs.tsx deleted file mode 100644 index 1e3c8d754..000000000 --- a/packages/webapp/src/containers/Setup/SetupDialogs.tsx +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-nocheck -import React from 'react'; -import PaymentViaVoucherDialog from '@/containers/Dialogs/PaymentViaVoucherDialog'; - -/** - * Setup dialogs. - */ -export default function SetupDialogs() { - return ( -
- -
- ) -} \ No newline at end of file diff --git a/packages/webapp/src/containers/Setup/SetupRightSection.tsx b/packages/webapp/src/containers/Setup/SetupRightSection.tsx index 1219c8a58..8d9415e82 100644 --- a/packages/webapp/src/containers/Setup/SetupRightSection.tsx +++ b/packages/webapp/src/containers/Setup/SetupRightSection.tsx @@ -1,7 +1,6 @@ // @ts-nocheck import React from 'react'; -import SetupDialogs from './SetupDialogs'; import SetupWizardContent from './SetupWizardContent'; import withOrganization from '@/containers/Organization/withOrganization'; @@ -30,7 +29,6 @@ function SetupRightSection({ return (
-
); } diff --git a/packages/webapp/src/containers/Setup/SetupSubscription/SubscriptionPlan.tsx b/packages/webapp/src/containers/Setup/SetupSubscription/SubscriptionPlan.tsx index 403ce8465..9f5976bf3 100644 --- a/packages/webapp/src/containers/Setup/SetupSubscription/SubscriptionPlan.tsx +++ b/packages/webapp/src/containers/Setup/SetupSubscription/SubscriptionPlan.tsx @@ -26,7 +26,7 @@ function SubscriptionPricing({ useGetLemonSqueezyCheckout(); const handleClick = () => { - getLemonCheckout({ variantId: '337977' }) + getLemonCheckout({ variantId: '338516' }) .then((res) => { const checkoutUrl = res.data.data.attributes.url; window.LemonSqueezy.Url.Open(checkoutUrl);