feat: auto subscribe to free plan once signup on community version.

This commit is contained in:
Ahmed Bouhuolia
2024-04-16 20:57:05 +02:00
parent 9b5f1a36ab
commit 9d3f1541eb
11 changed files with 84 additions and 81 deletions

View File

@@ -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
),
};

View File

@@ -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
];
};

View File

@@ -96,9 +96,7 @@ export class LemonSqueezyWebhooks {
if (webhookEvent === 'subscription_created') {
await this.subscriptionService.newSubscribtion(
tenantId,
'pro-yearly',
'year',
1
'early-adaptor',
);
}
}

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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<void>}
*/
private async subscribeFreeOnSigupCommunity({
signupDTO,
tenant,
user,
}: IAuthSignedUpEventPayload) {
if (config.hostedOnBigcapitalCloud) return null;
await this.subscriptionService.newSubscribtion(tenant.id, 'free');
}
}

View File

@@ -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
) {

View File

@@ -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,
},
]);
});

View File

@@ -1,14 +0,0 @@
// @ts-nocheck
import React from 'react';
import PaymentViaVoucherDialog from '@/containers/Dialogs/PaymentViaVoucherDialog';
/**
* Setup dialogs.
*/
export default function SetupDialogs() {
return (
<div class="setup-dialogs">
<PaymentViaVoucherDialog dialogName={'payment-via-voucher'} />
</div>
)
}

View File

@@ -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 (
<section className={'setup-page__right-section'}>
<SetupWizardContent stepId={setupStepId} stepIndex={setupStepIndex} />
<SetupDialogs />
</section>
);
}

View File

@@ -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);