2022-11-24 15:10:21 +00:00
|
|
|
import BadDataException from 'Common/Types/Exception/BadDataException';
|
|
|
|
import { JSONObject } from 'Common/Types/JSON';
|
2022-11-25 14:57:58 +00:00
|
|
|
import Permission, { UserPermission } from 'Common/Types/Permission';
|
2022-11-24 15:10:21 +00:00
|
|
|
import BillingInvoice from 'Model/Models/BillingInvoice';
|
|
|
|
import Project from 'Model/Models/Project';
|
2023-09-11 10:29:36 +00:00
|
|
|
import { IsBillingEnabled } from '../EnvironmentConfig';
|
2022-11-24 15:10:21 +00:00
|
|
|
import UserMiddleware from '../Middleware/UserAuthorization';
|
|
|
|
import BillingInvoiceService, {
|
|
|
|
Service as BillingInvoiceServiceType,
|
|
|
|
} from '../Services/BillingInvoiceService';
|
|
|
|
import BillingService, { Invoice } from '../Services/BillingService';
|
|
|
|
import ProjectService from '../Services/ProjectService';
|
|
|
|
import {
|
|
|
|
ExpressRequest,
|
|
|
|
ExpressResponse,
|
|
|
|
NextFunction,
|
|
|
|
} from '../Utils/Express';
|
|
|
|
import Response from '../Utils/Response';
|
|
|
|
import BaseAPI from './BaseAPI';
|
2023-05-11 11:58:49 +00:00
|
|
|
import SubscriptionStatus from 'Common/Types/Billing/SubscriptionStatus';
|
2023-11-14 14:21:26 +00:00
|
|
|
import BaseModel from 'Common/Models/BaseModel';
|
2022-11-24 15:10:21 +00:00
|
|
|
|
|
|
|
export default class UserAPI extends BaseAPI<
|
|
|
|
BillingInvoice,
|
|
|
|
BillingInvoiceServiceType
|
|
|
|
> {
|
|
|
|
public constructor() {
|
|
|
|
super(BillingInvoice, BillingInvoiceService);
|
|
|
|
|
|
|
|
this.router.post(
|
2022-12-20 07:13:36 +00:00
|
|
|
`${new this.entityType().getCrudApiPath()?.toString()}/pay`,
|
2022-11-24 15:10:21 +00:00
|
|
|
UserMiddleware.getUserMiddleware,
|
|
|
|
async (
|
|
|
|
req: ExpressRequest,
|
|
|
|
res: ExpressResponse,
|
|
|
|
next: NextFunction
|
|
|
|
) => {
|
|
|
|
try {
|
|
|
|
if (!IsBillingEnabled) {
|
|
|
|
throw new BadDataException(
|
2023-07-30 21:29:50 +00:00
|
|
|
'Billing is not enabled for this server'
|
2022-11-24 15:10:21 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (req.body['projectId']) {
|
|
|
|
throw new BadDataException(
|
|
|
|
'projectId is required in request body'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-11-25 14:57:58 +00:00
|
|
|
const userPermissions: Array<UserPermission> = (
|
2022-11-24 15:13:21 +00:00
|
|
|
await this.getPermissionsForTenant(req)
|
2022-11-25 14:57:58 +00:00
|
|
|
).filter((permission: UserPermission) => {
|
2022-11-24 15:10:21 +00:00
|
|
|
return (
|
2022-11-24 15:13:21 +00:00
|
|
|
permission.permission.toString() ===
|
2023-08-04 11:38:11 +00:00
|
|
|
Permission.ProjectOwner.toString() ||
|
2022-11-24 15:13:21 +00:00
|
|
|
permission.permission.toString() ===
|
2023-08-04 11:38:11 +00:00
|
|
|
Permission.CanEditInvoices.toString()
|
2022-11-24 15:10:21 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (userPermissions.length === 0) {
|
|
|
|
throw new BadDataException(
|
|
|
|
`You need ${Permission.ProjectOwner} or ${Permission.CanEditInvoices} permission to pay invoices.`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const project: Project | null =
|
|
|
|
await ProjectService.findOneById({
|
|
|
|
id: this.getTenantId(req)!,
|
|
|
|
props: {
|
|
|
|
isRoot: true,
|
|
|
|
},
|
|
|
|
select: {
|
|
|
|
_id: true,
|
|
|
|
paymentProviderCustomerId: true,
|
2022-11-24 15:13:21 +00:00
|
|
|
paymentProviderSubscriptionId: true,
|
2023-08-04 11:11:48 +00:00
|
|
|
paymentProviderMeteredSubscriptionId: true,
|
2022-11-24 15:10:21 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!project) {
|
|
|
|
throw new BadDataException('Project not found');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!project.paymentProviderCustomerId) {
|
|
|
|
throw new BadDataException(
|
|
|
|
'Payment Provider customer not found'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!project.paymentProviderSubscriptionId) {
|
|
|
|
throw new BadDataException(
|
|
|
|
'Payment Provider subscription not found'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const body: JSONObject = req.body;
|
|
|
|
|
2022-11-24 15:13:21 +00:00
|
|
|
const item: BillingInvoice =
|
2023-11-14 14:21:26 +00:00
|
|
|
BaseModel.fromJSON<BillingInvoice>(
|
2022-11-24 15:13:21 +00:00
|
|
|
body['data'] as JSONObject,
|
|
|
|
this.entityType
|
|
|
|
) as BillingInvoice;
|
2022-11-24 15:10:21 +00:00
|
|
|
|
|
|
|
if (!item.paymentProviderInvoiceId) {
|
2022-11-24 15:13:21 +00:00
|
|
|
throw new BadDataException('Invoice ID not found');
|
2022-11-24 15:10:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!item.paymentProviderCustomerId) {
|
2022-11-24 15:13:21 +00:00
|
|
|
throw new BadDataException('Customer ID not found');
|
2022-11-24 15:10:21 +00:00
|
|
|
}
|
|
|
|
|
2022-11-24 15:13:21 +00:00
|
|
|
const invoice: Invoice = await BillingService.payInvoice(
|
|
|
|
item.paymentProviderCustomerId!,
|
|
|
|
item.paymentProviderInvoiceId!
|
|
|
|
);
|
2022-11-24 15:10:21 +00:00
|
|
|
|
2022-11-24 15:13:21 +00:00
|
|
|
// save updated status.
|
2022-11-24 15:10:21 +00:00
|
|
|
|
|
|
|
await this.service.updateOneBy({
|
|
|
|
query: {
|
2022-11-24 15:13:21 +00:00
|
|
|
paymentProviderInvoiceId: invoice.id!,
|
2022-11-24 15:10:21 +00:00
|
|
|
},
|
|
|
|
props: {
|
|
|
|
isRoot: true,
|
2022-11-24 15:13:21 +00:00
|
|
|
ignoreHooks: true,
|
2022-11-24 15:10:21 +00:00
|
|
|
},
|
|
|
|
data: {
|
2022-11-24 15:13:21 +00:00
|
|
|
status: invoice.status,
|
|
|
|
},
|
|
|
|
});
|
2022-11-24 15:10:21 +00:00
|
|
|
|
2022-11-24 15:13:21 +00:00
|
|
|
// refresh subscription status.
|
2023-05-11 11:58:49 +00:00
|
|
|
const subscriptionState: SubscriptionStatus =
|
2022-11-24 15:13:21 +00:00
|
|
|
await BillingService.getSubscriptionStatus(
|
|
|
|
project.paymentProviderSubscriptionId as string
|
|
|
|
);
|
2022-11-24 15:10:21 +00:00
|
|
|
|
2023-08-04 11:11:48 +00:00
|
|
|
const meteredSubscriptionState: SubscriptionStatus =
|
|
|
|
await BillingService.getSubscriptionStatus(
|
2023-08-04 11:38:11 +00:00
|
|
|
project.paymentProviderMeteredSubscriptionId as string
|
2023-08-04 11:11:48 +00:00
|
|
|
);
|
|
|
|
|
2023-10-08 14:03:50 +00:00
|
|
|
// if subscription is cancelled, create a new subscription and update project.
|
|
|
|
|
|
|
|
if (
|
|
|
|
meteredSubscriptionState ===
|
|
|
|
SubscriptionStatus.Canceled ||
|
|
|
|
subscriptionState === SubscriptionStatus.Canceled
|
|
|
|
) {
|
|
|
|
await ProjectService.reactiveSubscription(project.id!);
|
|
|
|
}
|
|
|
|
|
2022-11-24 15:10:21 +00:00
|
|
|
await ProjectService.updateOneById({
|
|
|
|
id: project.id!,
|
|
|
|
data: {
|
2022-11-24 15:13:21 +00:00
|
|
|
paymentProviderSubscriptionStatus:
|
|
|
|
subscriptionState,
|
2023-08-04 11:38:11 +00:00
|
|
|
paymentProviderMeteredSubscriptionStatus:
|
|
|
|
meteredSubscriptionState,
|
2022-11-24 15:10:21 +00:00
|
|
|
},
|
|
|
|
props: {
|
|
|
|
isRoot: true,
|
2022-11-24 15:13:21 +00:00
|
|
|
ignoreHooks: true,
|
|
|
|
},
|
2022-11-24 15:10:21 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return Response.sendEmptyResponse(req, res);
|
|
|
|
} catch (err) {
|
|
|
|
next(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|