fix billing issues (#11160)
- first commit : fix quantity update on Enterprise plan - second commit : fix user with soft deleted workspace trying to recreate another workspace closes https://github.com/twentyhq/core-team-issues/issues/634
This commit is contained in:
@ -17,7 +17,6 @@ import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-p
|
|||||||
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
|
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
|
||||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||||
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
|
||||||
import { BillingPlanKey } from 'src/engine/core-modules/billing/enums/billing-plan-key.enum';
|
|
||||||
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
|
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
|
||||||
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
|
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
|
||||||
import { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service';
|
import { BillingPlanService } from 'src/engine/core-modules/billing/services/billing-plan.service';
|
||||||
@ -63,26 +62,29 @@ export class BillingSubscriptionService {
|
|||||||
{ workspaceId },
|
{ workspaceId },
|
||||||
);
|
);
|
||||||
|
|
||||||
const getStripeProductId = (
|
const planKey = getPlanKeyFromSubscription(billingSubscription);
|
||||||
await this.billingPlanService.getPlanBaseProduct(BillingPlanKey.PRO)
|
|
||||||
)?.stripeProductId;
|
|
||||||
|
|
||||||
if (!getStripeProductId) {
|
const baseProduct =
|
||||||
|
await this.billingPlanService.getPlanBaseProduct(planKey);
|
||||||
|
|
||||||
|
if (!baseProduct) {
|
||||||
throw new BillingException(
|
throw new BillingException(
|
||||||
'Base product not found',
|
'Base product not found',
|
||||||
BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND,
|
BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stripeProductId = baseProduct.stripeProductId;
|
||||||
|
|
||||||
const billingSubscriptionItem =
|
const billingSubscriptionItem =
|
||||||
billingSubscription.billingSubscriptionItems.filter(
|
billingSubscription.billingSubscriptionItems.filter(
|
||||||
(billingSubscriptionItem) =>
|
(billingSubscriptionItem) =>
|
||||||
billingSubscriptionItem.stripeProductId === getStripeProductId,
|
billingSubscriptionItem.stripeProductId === stripeProductId,
|
||||||
)?.[0];
|
)?.[0];
|
||||||
|
|
||||||
if (!billingSubscriptionItem) {
|
if (!billingSubscriptionItem) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Cannot find billingSubscriptionItem for product ${getStripeProductId} for workspace ${workspaceId}`,
|
`Cannot find billingSubscriptionItem for product ${stripeProductId} for workspace ${workspaceId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@ describe('WorkspaceService', () => {
|
|||||||
let workspaceCacheStorageService: WorkspaceCacheStorageService;
|
let workspaceCacheStorageService: WorkspaceCacheStorageService;
|
||||||
let messageQueueService: MessageQueueService;
|
let messageQueueService: MessageQueueService;
|
||||||
let customDomainService: CustomDomainService;
|
let customDomainService: CustomDomainService;
|
||||||
|
let billingSubscriptionService: BillingSubscriptionService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -61,6 +62,18 @@ describe('WorkspaceService', () => {
|
|||||||
softDelete: jest.fn(),
|
softDelete: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: BillingService,
|
||||||
|
useValue: {
|
||||||
|
isBillingEnabled: jest.fn().mockReturnValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: BillingSubscriptionService,
|
||||||
|
useValue: {
|
||||||
|
deleteSubscriptions: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
...[
|
...[
|
||||||
WorkspaceManagerService,
|
WorkspaceManagerService,
|
||||||
WorkspaceManagerService,
|
WorkspaceManagerService,
|
||||||
@ -68,8 +81,6 @@ describe('WorkspaceService', () => {
|
|||||||
UserService,
|
UserService,
|
||||||
DomainManagerService,
|
DomainManagerService,
|
||||||
CustomDomainService,
|
CustomDomainService,
|
||||||
BillingSubscriptionService,
|
|
||||||
BillingService,
|
|
||||||
EnvironmentService,
|
EnvironmentService,
|
||||||
EmailService,
|
EmailService,
|
||||||
OnboardingService,
|
OnboardingService,
|
||||||
@ -115,6 +126,9 @@ describe('WorkspaceService', () => {
|
|||||||
);
|
);
|
||||||
customDomainService = module.get<CustomDomainService>(CustomDomainService);
|
customDomainService = module.get<CustomDomainService>(CustomDomainService);
|
||||||
customDomainService.deleteCustomHostnameByHostnameSilently = jest.fn();
|
customDomainService.deleteCustomHostnameByHostnameSilently = jest.fn();
|
||||||
|
billingSubscriptionService = module.get<BillingSubscriptionService>(
|
||||||
|
BillingSubscriptionService,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -220,8 +234,11 @@ describe('WorkspaceService', () => {
|
|||||||
.spyOn(workspaceRepository, 'findOne')
|
.spyOn(workspaceRepository, 'findOne')
|
||||||
.mockResolvedValue(mockWorkspace);
|
.mockResolvedValue(mockWorkspace);
|
||||||
jest.spyOn(userWorkspaceRepository, 'find').mockResolvedValue([]);
|
jest.spyOn(userWorkspaceRepository, 'find').mockResolvedValue([]);
|
||||||
|
|
||||||
await service.deleteWorkspace(mockWorkspace.id, true);
|
await service.deleteWorkspace(mockWorkspace.id, true);
|
||||||
|
|
||||||
|
expect(billingSubscriptionService.deleteSubscriptions).toHaveBeenCalled;
|
||||||
|
|
||||||
expect(workspaceRepository.softDelete).toHaveBeenCalledWith({
|
expect(workspaceRepository.softDelete).toHaveBeenCalledWith({
|
||||||
id: mockWorkspace.id,
|
id: mockWorkspace.id,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -334,6 +334,10 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (softDelete) {
|
if (softDelete) {
|
||||||
|
if (this.billingService.isBillingEnabled()) {
|
||||||
|
await this.billingSubscriptionService.deleteSubscriptions(workspace.id);
|
||||||
|
}
|
||||||
|
|
||||||
await this.workspaceRepository.softDelete({ id });
|
await this.workspaceRepository.softDelete({ id });
|
||||||
|
|
||||||
this.logger.log(`workspace ${id} soft deleted`);
|
this.logger.log(`workspace ${id} soft deleted`);
|
||||||
|
|||||||
Reference in New Issue
Block a user