mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
feat: auto subscribe to free plan once signup on community version.
This commit is contained in:
@@ -201,4 +201,13 @@ module.exports = {
|
|||||||
storeId: process.env.LEMONSQUEEZY_STORE_ID,
|
storeId: process.env.LEMONSQUEEZY_STORE_ID,
|
||||||
webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
|
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
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ import { SaleReceiptMarkClosedOnMailSentSubcriber } from '@/services/Sales/Recei
|
|||||||
import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent';
|
import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/subscribers/SaleEstimateMarkApprovedOnMailSent';
|
||||||
import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/subscribers/DeleteCashflowTransactionOnUncategorize';
|
import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/subscribers/DeleteCashflowTransactionOnUncategorize';
|
||||||
import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete';
|
import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete';
|
||||||
|
import { SubscribeFreeOnSignupCommunity } from '@/services/Subscription/events/SubscribeFreeOnSignupCommunity';
|
||||||
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
return new EventPublisher();
|
return new EventPublisher();
|
||||||
@@ -219,5 +221,7 @@ export const susbcribers = () => {
|
|||||||
// Cashflow
|
// Cashflow
|
||||||
DeleteCashflowTransactionOnUncategorize,
|
DeleteCashflowTransactionOnUncategorize,
|
||||||
PreventDeleteTransactionOnDelete,
|
PreventDeleteTransactionOnDelete,
|
||||||
|
|
||||||
|
SubscribeFreeOnSignupCommunity
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,9 +96,7 @@ export class LemonSqueezyWebhooks {
|
|||||||
if (webhookEvent === 'subscription_created') {
|
if (webhookEvent === 'subscription_created') {
|
||||||
await this.subscriptionService.newSubscribtion(
|
await this.subscriptionService.newSubscribtion(
|
||||||
tenantId,
|
tenantId,
|
||||||
'pro-yearly',
|
'early-adaptor',
|
||||||
'year',
|
|
||||||
1
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,17 @@ export class Subscription {
|
|||||||
public async newSubscribtion(
|
public async newSubscribtion(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
planSlug: string,
|
planSlug: string,
|
||||||
invoiceInterval: string,
|
|
||||||
invoicePeriod: number,
|
|
||||||
subscriptionSlug: string = 'main'
|
subscriptionSlug: string = 'main'
|
||||||
) {
|
) {
|
||||||
const tenant = await Tenant.query().findById(tenantId).throwIfNotFound();
|
const tenant = await Tenant.query().findById(tenantId).throwIfNotFound();
|
||||||
const plan = await Plan.query().findOne('slug', planSlug).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
|
const subscription = await tenant
|
||||||
.$relatedQuery('subscriptions')
|
.$relatedQuery('subscriptions')
|
||||||
.modify('subscriptionBySlug', subscriptionSlug)
|
.modify('subscriptionBySlug', subscriptionSlug)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import moment from 'moment';
|
import moment, { type unitOfTime } from 'moment';
|
||||||
|
|
||||||
export default class SubscriptionPeriod {
|
export default class SubscriptionPeriod {
|
||||||
start: Date;
|
private start: Date;
|
||||||
end: Date;
|
private end: Date;
|
||||||
interval: string;
|
private interval: string;
|
||||||
count: number;
|
private count: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
@@ -12,7 +12,11 @@ export default class SubscriptionPeriod {
|
|||||||
* @param {number} count -
|
* @param {number} count -
|
||||||
* @param {Date} start -
|
* @param {Date} start -
|
||||||
*/
|
*/
|
||||||
constructor(interval: string = 'month', count: number, start?: Date) {
|
constructor(
|
||||||
|
interval: unitOfTime.DurationConstructor = 'month',
|
||||||
|
count: number,
|
||||||
|
start?: Date
|
||||||
|
) {
|
||||||
this.interval = interval;
|
this.interval = interval;
|
||||||
this.count = count;
|
this.count = count;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
@@ -20,7 +24,11 @@ export default class SubscriptionPeriod {
|
|||||||
if (!start) {
|
if (!start) {
|
||||||
this.start = moment().toDate();
|
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() {
|
getStartDate() {
|
||||||
@@ -36,6 +44,6 @@ export default class SubscriptionPeriod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getIntervalCount() {
|
getIntervalCount() {
|
||||||
return this.interval;
|
return this.count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -211,7 +211,7 @@ export default class Tenant extends BaseModel {
|
|||||||
static newSubscription(
|
static newSubscription(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
planId: number,
|
planId: number,
|
||||||
invoiceInterval: string,
|
invoiceInterval: 'month' | 'year',
|
||||||
invoicePeriod: number,
|
invoicePeriod: number,
|
||||||
subscriptionSlug: string
|
subscriptionSlug: string
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,65 +1,25 @@
|
|||||||
|
|
||||||
exports.seed = (knex) => {
|
exports.seed = (knex) => {
|
||||||
// Deletes ALL existing entries
|
// Deletes ALL existing entries
|
||||||
return knex('subscription_plans').del()
|
return knex('subscription_plans')
|
||||||
|
.del()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
return knex('subscription_plans').insert([
|
return knex('subscription_plans').insert([
|
||||||
{
|
{
|
||||||
name: 'Essentials',
|
name: 'Free',
|
||||||
slug: 'essentials-monthly',
|
slug: 'free',
|
||||||
price: 100,
|
price: 0,
|
||||||
active: true,
|
active: true,
|
||||||
currency: 'LYD',
|
currency: 'USD',
|
||||||
trial_period: 7,
|
|
||||||
trial_interval: 'days',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Essentials',
|
name: 'Early Adaptor',
|
||||||
slug: 'essentials-yearly',
|
slug: 'early-adaptor',
|
||||||
price: 1200,
|
price: 29,
|
||||||
active: true,
|
active: true,
|
||||||
currency: 'LYD',
|
currency: 'USD',
|
||||||
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',
|
|
||||||
invoice_period: 12,
|
invoice_period: 12,
|
||||||
invoice_interval: 'month',
|
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,
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import SetupDialogs from './SetupDialogs';
|
|
||||||
import SetupWizardContent from './SetupWizardContent';
|
import SetupWizardContent from './SetupWizardContent';
|
||||||
|
|
||||||
import withOrganization from '@/containers/Organization/withOrganization';
|
import withOrganization from '@/containers/Organization/withOrganization';
|
||||||
@@ -30,7 +29,6 @@ function SetupRightSection({
|
|||||||
return (
|
return (
|
||||||
<section className={'setup-page__right-section'}>
|
<section className={'setup-page__right-section'}>
|
||||||
<SetupWizardContent stepId={setupStepId} stepIndex={setupStepIndex} />
|
<SetupWizardContent stepId={setupStepId} stepIndex={setupStepIndex} />
|
||||||
<SetupDialogs />
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function SubscriptionPricing({
|
|||||||
useGetLemonSqueezyCheckout();
|
useGetLemonSqueezyCheckout();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
getLemonCheckout({ variantId: '337977' })
|
getLemonCheckout({ variantId: '338516' })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const checkoutUrl = res.data.data.attributes.url;
|
const checkoutUrl = res.data.data.attributes.url;
|
||||||
window.LemonSqueezy.Url.Open(checkoutUrl);
|
window.LemonSqueezy.Url.Open(checkoutUrl);
|
||||||
|
|||||||
Reference in New Issue
Block a user