mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat: cancel/resume LS subscriptions
This commit is contained in:
@@ -5,7 +5,7 @@ import { PlanSubscription } from '@/system/models';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { ERRORS, IOrganizationSubscriptionCanceled } from './types';
|
||||
import { ERRORS, IOrganizationSubscriptionCancelled } from './types';
|
||||
|
||||
@Service()
|
||||
export class LemonCancelSubscription {
|
||||
@@ -18,12 +18,15 @@ export class LemonCancelSubscription {
|
||||
* @param {number} subscriptionId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async cancelSubscription(tenantId: number) {
|
||||
public async cancelSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
configureLemonSqueezy();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: 'main',
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
if (!subscription) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT);
|
||||
@@ -35,13 +38,10 @@ export class LemonCancelSubscription {
|
||||
if (cancelledSub.error) {
|
||||
throw new Error(cancelledSub.error.message);
|
||||
}
|
||||
await PlanSubscription.query().findById(subscriptionId).patch({
|
||||
canceledAt: new Date(),
|
||||
});
|
||||
// Triggers `onSubscriptionCanceled` event.
|
||||
// Triggers `onSubscriptionCancelled` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionCanceled,
|
||||
{ tenantId, subscriptionId } as IOrganizationSubscriptionCanceled
|
||||
events.subscription.onSubscriptionCancel,
|
||||
{ tenantId, subscriptionId } as IOrganizationSubscriptionCancelled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,25 +18,30 @@ export class LemonChangeSubscriptionPlan {
|
||||
* @param {number} newVariantId - New variant id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async changeSubscriptionPlan(tenantId: number, newVariantId: number) {
|
||||
public async changeSubscriptionPlan(
|
||||
tenantId: number,
|
||||
newVariantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
configureLemonSqueezy();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: 'main',
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
const lemonSubscriptionId = subscription.lemonSubscriptionId;
|
||||
|
||||
// Send request to Lemon Squeezy to change the subscription.
|
||||
const updatedSub = await updateSubscription(lemonSubscriptionId, {
|
||||
variantId: newVariantId,
|
||||
invoiceImmediately: true,
|
||||
});
|
||||
if (updatedSub.error) {
|
||||
throw new ServiceError('SOMETHING_WENT_WRONG');
|
||||
}
|
||||
// Triggers `onSubscriptionPlanChanged` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionPlanChanged,
|
||||
events.subscription.onSubscriptionPlanChange,
|
||||
{
|
||||
tenantId,
|
||||
lemonSubscriptionId,
|
||||
|
||||
@@ -14,15 +14,16 @@ export class LemonResumeSubscription {
|
||||
|
||||
/**
|
||||
* Resumes the main subscription of the given tenant.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} subscriptionSlug - Subscription slug by default main subscription.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async resumeSubscription(tenantId: number) {
|
||||
public async resumeSubscription(tenantId: number, subscriptionSlug: string = 'main') {
|
||||
configureLemonSqueezy();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: 'main',
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
if (!subscription) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT);
|
||||
@@ -33,15 +34,11 @@ export class LemonResumeSubscription {
|
||||
cancelled: false,
|
||||
});
|
||||
if (returnedSub.error) {
|
||||
throw new ServiceError('');
|
||||
throw new ServiceError(ٌٌُERRORS.SOMETHING_WENT_WRONG_WITH_LS);
|
||||
}
|
||||
// Update the subscription of the organization.
|
||||
await PlanSubscription.query().findById(subscriptionId).patch({
|
||||
canceledAt: null,
|
||||
});
|
||||
// Triggers `onSubscriptionCanceled` event.
|
||||
// Triggers `onSubscriptionResume` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionResumed,
|
||||
events.subscription.onSubscriptionResume,
|
||||
{ tenantId, subscriptionId } as IOrganizationSubscriptionResumed
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export class LemonSqueezyWebhooks {
|
||||
|
||||
const userId = eventBody.meta.custom_data?.user_id;
|
||||
const tenantId = eventBody.meta.custom_data?.tenant_id;
|
||||
const subscriptionSlug = 'main';
|
||||
|
||||
if (!webhookHasMeta(eventBody)) {
|
||||
throw new Error("Event body is missing the 'meta' property.");
|
||||
@@ -68,13 +69,13 @@ export class LemonSqueezyWebhooks {
|
||||
if (webhookEvent === 'subscription_payment_success') {
|
||||
await this.subscriptionService.markSubscriptionPaymentSucceed(
|
||||
tenantId,
|
||||
'main'
|
||||
subscriptionSlug
|
||||
);
|
||||
// Marks the main subscription payment as failed.
|
||||
} else if (webhookEvent === 'subscription_payment_failed') {
|
||||
await this.subscriptionService.markSubscriptionPaymentFailed(
|
||||
tenantId,
|
||||
'main'
|
||||
subscriptionSlug
|
||||
);
|
||||
}
|
||||
// Save subscription invoices; eventBody is a SubscriptionInvoice
|
||||
@@ -100,20 +101,25 @@ export class LemonSqueezyWebhooks {
|
||||
await this.subscriptionService.newSubscribtion(
|
||||
tenantId,
|
||||
plan.slug,
|
||||
'main',
|
||||
subscriptionSlug,
|
||||
{ lemonSqueezyId: subscriptionId }
|
||||
);
|
||||
// Cancel the given subscription of the organization.
|
||||
} else if (webhookEvent === 'subscription_cancelled') {
|
||||
await this.subscriptionService.cancelSubscription(
|
||||
tenantId,
|
||||
plan.slug
|
||||
subscriptionSlug
|
||||
);
|
||||
} else if (webhookEvent === 'subscription_plan_changed') {
|
||||
await this.subscriptionService.subscriptionPlanChanged(
|
||||
tenantId,
|
||||
plan.slug,
|
||||
'main'
|
||||
subscriptionSlug
|
||||
);
|
||||
} else if (webhookEvent === 'subscription_resumed') {
|
||||
await this.subscriptionService.resumeSubscription(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
}
|
||||
} else if (webhookEvent.startsWith('order_')) {
|
||||
|
||||
@@ -82,6 +82,48 @@ export class Subscription {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ALREADY_CANCELED);
|
||||
}
|
||||
await subscription.$query().patch({ canceledAt: new Date() });
|
||||
|
||||
// Triggers `onSubscriptionCancelled` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionCancelled,
|
||||
{
|
||||
tenantId,
|
||||
subscriptionSlug,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the given tenant subscription.
|
||||
* @param {number} tenantId
|
||||
* @param {string} subscriptionSlug - Subscription slug by deafult main subscription.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resumeSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
const tenant = await Tenant.query().findById(tenantId).throwIfNotFound();
|
||||
|
||||
const subscription = await PlanSubscription.query().findOne({
|
||||
tenantId,
|
||||
slug: subscriptionSlug,
|
||||
});
|
||||
// Throw error early if the subscription is not exist.
|
||||
if (!subscription) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_NOT_EXIST);
|
||||
}
|
||||
// Throw error early if the subscription is not cancelled.
|
||||
if (!subscription.canceled()) {
|
||||
throw new ServiceError(ERRORS.SUBSCRIPTION_ALREADY_ACTIVE);
|
||||
}
|
||||
await subscription.$query().patch({ canceledAt: null });
|
||||
|
||||
// Triggers `onSubscriptionResumed` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.subscription.onSubscriptionResumed,
|
||||
{ tenantId, subscriptionSlug }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,8 +20,14 @@ export class SubscriptionApplication {
|
||||
* @param {string} id
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public cancelSubscription(tenantId: number, id: string) {
|
||||
return this.cancelSubscriptionService.cancelSubscription(tenantId, id);
|
||||
public cancelSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
return this.cancelSubscriptionService.cancelSubscription(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,8 +35,14 @@ export class SubscriptionApplication {
|
||||
* @param {number} tenantId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public resumeSubscription(tenantId: number) {
|
||||
return this.resumeSubscriptionService.resumeSubscription(tenantId);
|
||||
public resumeSubscription(
|
||||
tenantId: number,
|
||||
subscriptionSlug: string = 'main'
|
||||
) {
|
||||
return this.resumeSubscriptionService.resumeSubscription(
|
||||
tenantId,
|
||||
subscriptionSlug
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
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 {
|
||||
|
||||
@@ -3,6 +3,8 @@ export const ERRORS = {
|
||||
'SUBSCRIPTION_ID_NOT_ASSOCIATED_TO_TENANT',
|
||||
SUBSCRIPTION_NOT_EXIST: 'SUBSCRIPTION_NOT_EXIST',
|
||||
SUBSCRIPTION_ALREADY_CANCELED: 'SUBSCRIPTION_ALREADY_CANCELED',
|
||||
SUBSCRIPTION_ALREADY_ACTIVE: 'SUBSCRIPTION_ALREADY_ACTIVE',
|
||||
SOMETHING_WENT_WRONG_WITH_LS: 'SOMETHING_WENT_WRONG_WITH_LS'
|
||||
};
|
||||
|
||||
export interface IOrganizationSubscriptionChanged {
|
||||
@@ -11,7 +13,7 @@ export interface IOrganizationSubscriptionChanged {
|
||||
newVariantId: number;
|
||||
}
|
||||
|
||||
export interface IOrganizationSubscriptionCanceled {
|
||||
export interface IOrganizationSubscriptionCancelled {
|
||||
tenantId: number;
|
||||
subscriptionId: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user