mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
fix billing.
This commit is contained in:
@@ -77,6 +77,8 @@ export default class BaseModel extends BaseEntity {
|
||||
public updateBillingPlan!: PlanSelect | null;
|
||||
public deleteBillingPlan!: PlanSelect | null;
|
||||
|
||||
public allowAccessIfSubscriptionIsUnpaid!: boolean;
|
||||
|
||||
|
||||
public currentUserCanAccessColumnBy!: string | null;
|
||||
public labelsColumn!: string | null;
|
||||
|
||||
@@ -6,7 +6,7 @@ export default class Route {
|
||||
}
|
||||
public set route(v: string) {
|
||||
const matchRouteCharacters: RegExp =
|
||||
/^[a-zA-Z\d\-!#$&'()*+,./:;=?@[\]]*$/;
|
||||
/^[a-zA-Z_\d\-!#$&'()*+,./:;=?@[\]]*$/;
|
||||
if (v && !matchRouteCharacters.test(v)) {
|
||||
throw new BadDataException(`Invalid route: ${v}`);
|
||||
}
|
||||
|
||||
@@ -177,4 +177,12 @@ export default class SubscriptionPlan {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static isUnpaid(subscriptionStatus: string): boolean {
|
||||
if (subscriptionStatus === "incomplete" || subscriptionStatus === "incomplete_expired" || subscriptionStatus === "past_due" || subscriptionStatus === "canceled" || subscriptionStatus === "unpaid") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export default () => {
|
||||
return (ctr: Function) => {
|
||||
ctr.prototype.allowAccessIfSubscriptionIsUnpaid = true;
|
||||
};
|
||||
};
|
||||
@@ -26,6 +26,7 @@ enum ColumnType {
|
||||
PositiveNumber = 'integer',
|
||||
BigPositiveNumber = 'bigint',
|
||||
SmallNumber = 'smallint',
|
||||
Decimal = 'decimal',
|
||||
Number = 'integer',
|
||||
BigNumber = 'bigint',
|
||||
Markdown = 'text',
|
||||
|
||||
@@ -19,4 +19,5 @@ export default interface DatabaseCommonInteractionProps {
|
||||
isMultiTenantRequest?: boolean | undefined;
|
||||
ignoreHooks?: boolean | undefined;
|
||||
currentPlan?: PlanSelect | undefined;
|
||||
isSubscriptionUnpaid?: boolean | undefined;
|
||||
}
|
||||
|
||||
@@ -183,6 +183,18 @@ enum Permission {
|
||||
CanDeleteIncidentPublicNote = 'CanDeleteIncidentPublicNote',
|
||||
CanReadIncidentPublicNote = 'CanReadIncidentPublicNote',
|
||||
|
||||
|
||||
CanCreateInvoices = 'CanCreateInvoices',
|
||||
CanEditInvoices = 'CanEditInvoices',
|
||||
CanDeleteInvoices = 'CanDeleteInvoices',
|
||||
CanReadInvoices = 'CanReadInvoices',
|
||||
|
||||
|
||||
CanCreateBillingPaymentMethod = 'CanCreateBillingPaymentMethod',
|
||||
CanEditBillingPaymentMethod = 'CanEditBillingPaymentMethod',
|
||||
CanDeleteBillingPaymentMethod = 'CanDeleteBillingPaymentMethod',
|
||||
CanReadBillingPaymentMethod = 'CanReadBillingPaymentMethod',
|
||||
|
||||
CanCreateProjectMonitor = 'CanCreateProjectMonitor',
|
||||
CanEditProjectMonitor = 'CanEditProjectMonitor',
|
||||
CanDeleteProjectMonitor = 'CanDeleteProjectMonitor',
|
||||
@@ -1108,6 +1120,75 @@ export class PermissionHelper {
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
permission: Permission.CanCreateInvoices,
|
||||
title: 'Can Create Invoices',
|
||||
description:
|
||||
'A user assigned this permission can create Invoices this project.',
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.CanDeleteInvoices,
|
||||
title: 'Can Delete Invoices',
|
||||
description:
|
||||
'A user assigned this permission can delete Invoices of this project.',
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.CanEditInvoices,
|
||||
title: 'Can Edit Invoices',
|
||||
description:
|
||||
'A user assigned this permission can edit Invoices of this project.',
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.CanReadInvoices,
|
||||
title: 'Can Read Invoices',
|
||||
description:
|
||||
'A user assigned this permission can read Invoices of this project.',
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
permission: Permission.CanCreateBillingPaymentMethod,
|
||||
title: 'Can Create Payment Method',
|
||||
description:
|
||||
'A user assigned this permission can create Payment Method this project.',
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.CanDeleteBillingPaymentMethod,
|
||||
title: 'Can Delete Payment Method',
|
||||
description:
|
||||
'A user assigned this permission can delete Payment Method of this project.',
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.CanEditBillingPaymentMethod,
|
||||
title: 'Can Edit Payment Method',
|
||||
description:
|
||||
'A user assigned this permission can edit Payment Method of this project.',
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
{
|
||||
permission: Permission.CanReadBillingPaymentMethod,
|
||||
title: 'Can Read Payment Method',
|
||||
description:
|
||||
'A user assigned this permission can read Payment Method of this project.',
|
||||
isAssignableToTenant: true,
|
||||
isAccessControlPermission: false,
|
||||
},
|
||||
|
||||
{
|
||||
permission: Permission.CanCreateProjectOnCallDuty,
|
||||
title: 'Can Create On-Call Duty',
|
||||
|
||||
@@ -216,7 +216,9 @@ export default class BaseAPI<
|
||||
}
|
||||
|
||||
if (IsBillingEnabled && props.tenantId) {
|
||||
props.currentPlan = await ProjectService.getCurrentPlan(props.tenantId) || undefined;
|
||||
const plan = await ProjectService.getCurrentPlan(props.tenantId!);
|
||||
props.currentPlan = plan.plan || undefined;
|
||||
props.isSubscriptionUnpaid = plan.isSubscriptionUnpaid;
|
||||
}
|
||||
|
||||
return props;
|
||||
|
||||
159
CommonServer/API/BillingInvoiceAPI.ts
Normal file
159
CommonServer/API/BillingInvoiceAPI.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import BaseModel from 'Common/Models/BaseModel';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import Permission from 'Common/Types/Permission';
|
||||
import BillingInvoice from 'Model/Models/BillingInvoice';
|
||||
import Project from 'Model/Models/Project';
|
||||
import { IsBillingEnabled } from '../Config';
|
||||
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';
|
||||
|
||||
export default class UserAPI extends BaseAPI<
|
||||
BillingInvoice,
|
||||
BillingInvoiceServiceType
|
||||
> {
|
||||
public constructor() {
|
||||
super(BillingInvoice, BillingInvoiceService);
|
||||
|
||||
this.router.post(
|
||||
`/${new this.entityType().getCrudApiPath()?.toString()}/pay`,
|
||||
UserMiddleware.getUserMiddleware,
|
||||
async (
|
||||
req: ExpressRequest,
|
||||
res: ExpressResponse,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
if (!IsBillingEnabled) {
|
||||
throw new BadDataException(
|
||||
'Billign is not enabled for this server'
|
||||
);
|
||||
}
|
||||
|
||||
if (req.body['projectId']) {
|
||||
throw new BadDataException(
|
||||
'projectId is required in request body'
|
||||
);
|
||||
}
|
||||
|
||||
const userPermissions = (await this.getPermissionsForTenant(
|
||||
req
|
||||
)).filter((permission) => {
|
||||
console.log(permission.permission);
|
||||
//FIX: Change "Project"
|
||||
return (
|
||||
permission.permission.toString() === Permission.ProjectOwner.toString() || permission.permission.toString() === Permission.CanEditInvoices.toString()
|
||||
);
|
||||
});
|
||||
|
||||
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,
|
||||
paymentProviderSubscriptionId: true
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
throw new BadDataException('Project not found');
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const item: BillingInvoice = BaseModel.fromJSON<BillingInvoice>(
|
||||
body['data'] as JSONObject,
|
||||
this.entityType
|
||||
) as BillingInvoice;
|
||||
|
||||
|
||||
if (!item.paymentProviderInvoiceId) {
|
||||
throw new BadDataException("Invoice ID not found");
|
||||
}
|
||||
|
||||
if (!item.paymentProviderCustomerId) {
|
||||
throw new BadDataException("Customer ID not found");
|
||||
}
|
||||
|
||||
const invoice: Invoice =
|
||||
await BillingService.payInvoice(
|
||||
item.paymentProviderCustomerId!,
|
||||
item.paymentProviderInvoiceId!,
|
||||
);
|
||||
|
||||
|
||||
// save updated status.
|
||||
|
||||
await this.service.updateOneBy({
|
||||
query: {
|
||||
paymentProviderInvoiceId: invoice.id!
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true
|
||||
},
|
||||
data: {
|
||||
status: invoice.status
|
||||
}
|
||||
})
|
||||
|
||||
// refresh subscription status.
|
||||
const subscriptionState = await BillingService.getSubscriptionStatus(project.paymentProviderSubscriptionId as string);
|
||||
|
||||
await ProjectService.updateOneById({
|
||||
id: project.id!,
|
||||
data: {
|
||||
paymentProviderSubscriptionStatus: subscriptionState
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true
|
||||
}
|
||||
});
|
||||
|
||||
return Response.sendEmptyResponse(req, res);
|
||||
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import Permission from 'Common/Types/Permission';
|
||||
import BillingPaymentMethod from 'Model/Models/BillingPaymentMethod';
|
||||
import Project from 'Model/Models/Project';
|
||||
import { IsBillingEnabled } from '../Config';
|
||||
@@ -50,7 +51,7 @@ export default class UserAPI extends BaseAPI<
|
||||
console.log(permission.permission);
|
||||
//FIX: Change "Project"
|
||||
return (
|
||||
permission.permission.toString() === 'ProjectOwner'
|
||||
permission.permission.toString() === Permission.ProjectOwner.toString() || permission.permission.toString() === Permission.CanCreateBillingPaymentMethod.toString()
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@ import ApiKey from 'Model/Models/ApiKey';
|
||||
import { LessThan } from 'typeorm';
|
||||
import OneUptimeDate from 'Common/Types/Date';
|
||||
import UserType from 'Common/Types/UserType';
|
||||
import { PlanSelect } from 'Common/Types/Billing/SubscriptionPlan';
|
||||
import { IsBillingEnabled } from '../Config';
|
||||
import ProjectService from '../Services/ProjectService';
|
||||
|
||||
export default class ProjectMiddleware {
|
||||
public static getProjectId(req: ExpressRequest): ObjectID | null {
|
||||
@@ -33,20 +30,6 @@ export default class ProjectMiddleware {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public static async getProjectPlan(req: ExpressRequest): Promise<PlanSelect | null> {
|
||||
if (!IsBillingEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const projectId = this.getProjectId(req);
|
||||
|
||||
if (!projectId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await ProjectService.getCurrentPlan(projectId);
|
||||
}
|
||||
|
||||
public static getApiKey(req: ExpressRequest): ObjectID | null {
|
||||
let apiKey: ObjectID | null = null;
|
||||
|
||||
|
||||
92
CommonServer/Services/BillingInvoiceService.ts
Normal file
92
CommonServer/Services/BillingInvoiceService.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
|
||||
import Model from 'Model/Models/BillingInvoice';
|
||||
import DatabaseService, { OnDelete, OnFind } from './DatabaseService';
|
||||
import FindBy from '../Types/Database/FindBy';
|
||||
import ProjectService from './ProjectService';
|
||||
import BadDataException from 'Common/Types/Exception/BadDataException';
|
||||
import Project from 'Model/Models/Project';
|
||||
import BillingService from './BillingService';
|
||||
import DeleteBy from '../Types/Database/DeleteBy';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
|
||||
export class Service extends DatabaseService<Model> {
|
||||
public constructor(postgresDatabase?: PostgresDatabase) {
|
||||
super(Model, postgresDatabase);
|
||||
}
|
||||
|
||||
protected override async onBeforeFind(
|
||||
findBy: FindBy<Model>
|
||||
): Promise<OnFind<Model>> {
|
||||
if (!findBy.props.tenantId) {
|
||||
throw new BadDataException('ProjectID not found.');
|
||||
}
|
||||
|
||||
const project: Project | null = await ProjectService.findOneById({
|
||||
id: findBy.props.tenantId!,
|
||||
props: {
|
||||
...findBy.props,
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
select: {
|
||||
_id: true,
|
||||
paymentProviderCustomerId: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
throw new BadDataException('Project not found');
|
||||
}
|
||||
|
||||
if (!project.paymentProviderCustomerId) {
|
||||
throw new BadDataException(
|
||||
'Payment provider customer id not found.'
|
||||
);
|
||||
}
|
||||
|
||||
const invoices = await BillingService.getInvoices(
|
||||
project.paymentProviderCustomerId
|
||||
);
|
||||
|
||||
await this.deleteBy({
|
||||
query: {
|
||||
projectId: findBy.props.tenantId!,
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
ignoreHooks: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const invoice of invoices) {
|
||||
const billingInvoice = new Model();
|
||||
|
||||
billingInvoice.projectId = project.id!;
|
||||
|
||||
billingInvoice.amount = invoice.amount;
|
||||
billingInvoice.downloadableLink = URL.fromString(invoice.downloadableLink);
|
||||
billingInvoice.currencyCode = invoice.currencyCode;
|
||||
billingInvoice.paymentProviderCustomerId = invoice.customerId || '';
|
||||
billingInvoice.paymentProviderSubscriptionId = invoice.subscriptionId || '';
|
||||
billingInvoice.status = invoice.status || '';
|
||||
billingInvoice.paymentProviderInvoiceId = invoice.id;
|
||||
|
||||
await this.create({
|
||||
data: billingInvoice,
|
||||
props: {
|
||||
isRoot: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return { findBy, carryForward: invoices };
|
||||
}
|
||||
|
||||
protected override async onBeforeDelete(
|
||||
_deleteBy: DeleteBy<Model>
|
||||
): Promise<OnDelete<Model>> {
|
||||
throw new BadDataException("Invoice should not be deleted.")
|
||||
}
|
||||
}
|
||||
|
||||
export default new Service();
|
||||
@@ -13,6 +13,16 @@ export interface PaymentMethod {
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
export interface Invoice {
|
||||
id: string;
|
||||
amount: number;
|
||||
currencyCode: string;
|
||||
subscriptionId?: string | undefined;
|
||||
status: string;
|
||||
downloadableLink: string;
|
||||
customerId: string | undefined;
|
||||
}
|
||||
|
||||
export class BillingService {
|
||||
private static stripe: Stripe = new Stripe(BillingPrivateKey, {
|
||||
apiVersion: '2022-08-01',
|
||||
@@ -335,6 +345,51 @@ export class BillingService {
|
||||
|
||||
return subscription.status;
|
||||
}
|
||||
|
||||
public static async getInvoices(customerId: string): Promise<Array<Invoice>> {
|
||||
const invoices = await this.stripe.invoices.list({
|
||||
customer: customerId,
|
||||
limit: 100,
|
||||
});
|
||||
|
||||
return invoices.data.map((invoice) => {
|
||||
return {
|
||||
id: invoice.id!,
|
||||
amount: invoice.amount_due,
|
||||
currencyCode: invoice.currency,
|
||||
subscriptionId: invoice.subscription?.toString() || undefined,
|
||||
status: invoice.status?.toString() || 'Unknown',
|
||||
downloadableLink: invoice.invoice_pdf?.toString() || '',
|
||||
customerId: invoice.customer?.toString() || ''
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public static async payInvoice(customerId: string, invoiceId: string): Promise<Invoice> {
|
||||
// after the invoice is paid, // please fetch subscription and check the status.
|
||||
const paymentMethods = await this.getPaymentMethods(customerId);
|
||||
|
||||
if (paymentMethods.length === 0) {
|
||||
throw new BadDataException("Payment Method not added. Please add a payment method.");
|
||||
}
|
||||
|
||||
const invoice = await this.stripe.invoices.pay(
|
||||
invoiceId, {
|
||||
payment_method: paymentMethods[0]?.id || ''
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
id: invoice.id!,
|
||||
amount: invoice.amount_due,
|
||||
currencyCode: invoice.currency,
|
||||
subscriptionId: invoice.subscription?.toString() || undefined,
|
||||
status: invoice.status?.toString() || 'Unknown',
|
||||
downloadableLink: invoice.invoice_pdf?.toString() || '',
|
||||
customerId: invoice.customer?.toString() || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BillingService;
|
||||
|
||||
@@ -146,7 +146,7 @@ export class Service extends DatabaseService<Model> {
|
||||
plan,
|
||||
project.paymentProviderSubscriptionSeats as number,
|
||||
plan.getYearlyPlanId() ===
|
||||
updateBy.data.paymentProviderPlanId,
|
||||
updateBy.data.paymentProviderPlanId,
|
||||
project.trialEndsAt
|
||||
);
|
||||
}
|
||||
@@ -429,7 +429,7 @@ export class Service extends DatabaseService<Model> {
|
||||
let ownerTeam: Team = new Team();
|
||||
ownerTeam.projectId = createdItem.id!;
|
||||
ownerTeam.name = 'Owners';
|
||||
ownerTeam.shouldHaveAtleastOneMember = true;
|
||||
ownerTeam.shouldHaveAtleastOneMember = true;
|
||||
ownerTeam.isPermissionsEditable = false;
|
||||
ownerTeam.isTeamEditable = false;
|
||||
ownerTeam.isTeamDeleteable = false;
|
||||
@@ -592,15 +592,16 @@ export class Service extends DatabaseService<Model> {
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
public async getCurrentPlan(projectId: ObjectID): Promise<PlanSelect | null> {
|
||||
public async getCurrentPlan(projectId: ObjectID): Promise<{ plan: PlanSelect | null, isSubscriptionUnpaid: boolean }> {
|
||||
if (!IsBillingEnabled) {
|
||||
return null;
|
||||
return { plan: null, isSubscriptionUnpaid: false };
|
||||
}
|
||||
|
||||
const project = await this.findOneById({
|
||||
id: projectId,
|
||||
select: {
|
||||
paymentProviderPlanId: true
|
||||
paymentProviderPlanId: true,
|
||||
paymentProviderSubscriptionStatus: true
|
||||
},
|
||||
props: {
|
||||
isRoot: true,
|
||||
@@ -616,7 +617,7 @@ export class Service extends DatabaseService<Model> {
|
||||
throw new BadDataException("Project does not have any plans");
|
||||
}
|
||||
|
||||
return SubscriptionPlan.getPlanSelect(project.paymentProviderPlanId);
|
||||
return { plan: SubscriptionPlan.getPlanSelect(project.paymentProviderPlanId), isSubscriptionUnpaid: SubscriptionPlan.isUnpaid(project.paymentProviderSubscriptionStatus || 'active')};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -964,6 +964,10 @@ export default class ModelPermission {
|
||||
|
||||
const model = new modelType();
|
||||
|
||||
if (props.isSubscriptionUnpaid && !model.allowAccessIfSubscriptionIsUnpaid) {
|
||||
throw new PaymentRequiredException("Your current subscription is in an unpaid state. Looks like your payment method failed. Please add a new payment method in Project Settings > Billing to proceed.")
|
||||
}
|
||||
|
||||
if (type === DatabaseRequestType.Create && model.createBillingPlan) {
|
||||
if (!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(model.createBillingPlan, props.currentPlan)) {
|
||||
throw new PaymentRequiredException("Please upgrade your plan to " + model.createBillingPlan + " to access this feature");
|
||||
|
||||
@@ -58,7 +58,8 @@ import {
|
||||
ExternalLink,
|
||||
Layers,
|
||||
Codesandbox,
|
||||
Star
|
||||
Star,
|
||||
ArrowDown
|
||||
} from 'react-feather';
|
||||
|
||||
export enum SizeProp {
|
||||
@@ -144,7 +145,8 @@ export enum IconProp {
|
||||
Clock,
|
||||
Invoice,
|
||||
Upgrade,
|
||||
Star
|
||||
Star,
|
||||
Download
|
||||
}
|
||||
|
||||
export interface ComponentProps {
|
||||
@@ -646,6 +648,14 @@ const Icon: FunctionComponent<ComponentProps> = ({
|
||||
color={color ? color.toString() : (undefined as any)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{icon === IconProp.Download && (
|
||||
<ArrowDown
|
||||
size={size}
|
||||
strokeWidth={thick ? thick : ''}
|
||||
color={color ? color.toString() : (undefined as any)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -338,9 +338,6 @@ const ModelTable: Function = <TBaseModel extends BaseModel>(
|
||||
};
|
||||
|
||||
const fetchItems: Function = async () => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
setError('');
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -1,17 +1,74 @@
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import { JSONObject } from 'Common/Types/JSON';
|
||||
import Button, { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
|
||||
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
|
||||
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
|
||||
import Page from 'CommonUI/src/Components/Page/Page';
|
||||
import React, { FunctionComponent, ReactElement } from 'react';
|
||||
import Navigation from 'CommonUI/src/Utils/Navigation';
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useState,
|
||||
} from 'react';
|
||||
import Text from 'Common/Types/Text';
|
||||
import PageMap from '../../Utils/PageMap';
|
||||
import RouteMap from '../../Utils/RouteMap';
|
||||
import PageComponentProps from '../PageComponentProps';
|
||||
import DashboardSideMenu from './SideMenu';
|
||||
import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert';
|
||||
import BillingInvoice from 'Model/Models/BillingInvoice';
|
||||
import FieldType from 'CommonUI/src/Components/Types/FieldType';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
import Pill from 'CommonUI/src/Components/Pill/Pill';
|
||||
import { Green, Yellow } from 'Common/Types/BrandColors';
|
||||
import { DASHBOARD_API_URL } from 'CommonUI/src/Config';
|
||||
import BaseAPI from 'CommonUI/src/Utils/API/API';
|
||||
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
|
||||
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
|
||||
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
|
||||
import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader';
|
||||
|
||||
export interface ComponentProps extends PageComponentProps {}
|
||||
export interface ComponentProps extends PageComponentProps { }
|
||||
|
||||
const Settings: FunctionComponent<ComponentProps> = (
|
||||
_props: ComponentProps
|
||||
props: ComponentProps
|
||||
): ReactElement => {
|
||||
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const payInvoice = async (customerId: string, invoiceId: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
await BaseAPI.post<JSONObject>(
|
||||
URL.fromString(DASHBOARD_API_URL.toString()).addRoute(
|
||||
`/billing-invoices/pay`
|
||||
),
|
||||
{
|
||||
data: {
|
||||
paymentProviderInvoiceId: invoiceId,
|
||||
paymentProviderCustomerId: customerId,
|
||||
}
|
||||
},
|
||||
ModelAPI.getCommonHeaders()
|
||||
);
|
||||
|
||||
Navigation.reload();
|
||||
} catch (err) {
|
||||
try {
|
||||
setError(
|
||||
(err as HTTPErrorResponse).message ||
|
||||
'Server Error. Please try again'
|
||||
);
|
||||
} catch (e) {
|
||||
setError('Server Error. Please try again');
|
||||
}
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Page
|
||||
title={'Project Settings'}
|
||||
@@ -31,11 +88,105 @@ const Settings: FunctionComponent<ComponentProps> = (
|
||||
]}
|
||||
sideMenu={<DashboardSideMenu />}
|
||||
>
|
||||
<Alert
|
||||
type={AlertType.DANGER}
|
||||
strongTitle="DANGER ZONE"
|
||||
title="Deleting your project will delete it permanently and there is no way to recover. "
|
||||
/>
|
||||
|
||||
|
||||
{isLoading ? <ComponentLoader /> : <></>}
|
||||
|
||||
{!isLoading ? <ModelTable<BillingInvoice>
|
||||
modelType={BillingInvoice}
|
||||
id="invoices-table"
|
||||
isDeleteable={false}
|
||||
isEditable={false}
|
||||
isCreateable={false}
|
||||
isViewable={false}
|
||||
cardProps={{
|
||||
icon: IconProp.File,
|
||||
title: 'Invoices',
|
||||
description:
|
||||
'Here is a list of invoices for this project.',
|
||||
}}
|
||||
noItemsMessage={'No invoices so far.'}
|
||||
query={{
|
||||
projectId: props.currentProject?._id,
|
||||
}}
|
||||
showRefreshButton={true}
|
||||
showFilterButton={false}
|
||||
selectMoreFields={{
|
||||
currencyCode: true,
|
||||
paymentProviderCustomerId: true
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
field: {
|
||||
paymentProviderInvoiceId: true,
|
||||
},
|
||||
title: 'Invoice ID',
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
amount: true,
|
||||
},
|
||||
title: 'Amount',
|
||||
type: FieldType.Text,
|
||||
isFilterable: true,
|
||||
getElement: (item: JSONObject) => {
|
||||
return <span>{`${(item['amount'] as number) / 100} ${item['currencyCode']?.toString().toUpperCase()}`}</span>
|
||||
}
|
||||
},
|
||||
{
|
||||
field: {
|
||||
status: true,
|
||||
},
|
||||
title: 'Invoice Status',
|
||||
type: FieldType.Text,
|
||||
isFilterable: true,
|
||||
getElement: (item: JSONObject) => {
|
||||
if (item['status'] === "paid") {
|
||||
return <Pill text={Text.uppercaseFirstLetter(item['status'] as string)} color={Green} />
|
||||
} else {
|
||||
return <Pill text={Text.uppercaseFirstLetter(item['status'] as string)} color={Yellow} />
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: {
|
||||
downloadableLink: true,
|
||||
},
|
||||
title: 'Actions',
|
||||
type: FieldType.Text,
|
||||
isFilterable: true,
|
||||
getElement: (item: JSONObject) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{item['downloadableLink'] ? <Button icon={IconProp.Download} onClick={() => {
|
||||
Navigation.navigate(item['downloadableLink'] as URL);
|
||||
}} title="Download" /> : <></>}
|
||||
|
||||
{item['status'] !== "paid" ? <Button icon={IconProp.Billing} onClick={() => {
|
||||
payInvoice(item['paymentProviderCustomerId'] as string, item['paymentProviderInvoiceId'] as string);
|
||||
}} title="Pay Invoice" /> : <></>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
]}
|
||||
/> : <></>}
|
||||
|
||||
|
||||
{error ? (
|
||||
<ConfirmModal
|
||||
title={`Error`}
|
||||
description={`${error}`}
|
||||
submitButtonText={'Close'}
|
||||
onSubmit={() => {
|
||||
setError('');
|
||||
}}
|
||||
submitButtonType={ButtonStyleType.NORMAL}
|
||||
/>
|
||||
): <></>}
|
||||
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -172,15 +172,15 @@ const DashboardSideMenu: FunctionComponent = (): ReactElement => {
|
||||
}}
|
||||
icon={IconProp.Billing}
|
||||
/>
|
||||
{/* <SideMenuItem
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: 'Invoices',
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SETTINGS_BILLING_INVOICES] as Route
|
||||
),
|
||||
}}
|
||||
icon={IconProp.File}
|
||||
/> */}
|
||||
icon={IconProp.TextFile}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@@ -13,6 +13,8 @@ import UserService, {
|
||||
|
||||
import BillingPaymentMethodAPI from 'CommonServer/API/BillingPaymentMethodAPI';
|
||||
|
||||
import BillingInvoiceAPI from 'CommonServer/API/BillingInvoiceAPI';
|
||||
|
||||
import Project from 'Model/Models/Project';
|
||||
import ProjectService, {
|
||||
Service as ProjectServiceType,
|
||||
@@ -390,6 +392,7 @@ app.use(
|
||||
|
||||
app.use(new StatusPageAPI().getRouter());
|
||||
app.use(new BillingPaymentMethodAPI().getRouter());
|
||||
app.use(new BillingInvoiceAPI().getRouter());
|
||||
|
||||
app.use(
|
||||
new BaseAPI<
|
||||
|
||||
246
Model/Models/BillingInvoice.ts
Normal file
246
Model/Models/BillingInvoice.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
||||
import BaseModel from 'Common/Models/BaseModel';
|
||||
import User from './User';
|
||||
import Project from './Project';
|
||||
import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint';
|
||||
import Route from 'Common/Types/API/Route';
|
||||
import TableColumnType from 'Common/Types/Database/TableColumnType';
|
||||
import TableColumn from 'Common/Types/Database/TableColumn';
|
||||
import ColumnType from 'Common/Types/Database/ColumnType';
|
||||
import ObjectID from 'Common/Types/ObjectID';
|
||||
import ColumnLength from 'Common/Types/Database/ColumnLength';
|
||||
import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl';
|
||||
import Permission from 'Common/Types/Permission';
|
||||
import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl';
|
||||
import TenantColumn from 'Common/Types/Database/TenantColumn';
|
||||
import SingularPluralName from 'Common/Types/Database/SingularPluralName';
|
||||
import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid';
|
||||
import URL from 'Common/Types/API/URL';
|
||||
|
||||
@AllowAccessIfSubscriptionIsUnpaid()
|
||||
@TenantColumn('projectId')
|
||||
@TableAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
delete: [],
|
||||
update: [],
|
||||
})
|
||||
@CrudApiEndpoint(new Route('/billing-invoices'))
|
||||
@SingularPluralName('Invoice', 'Invoices')
|
||||
@Entity({
|
||||
name: 'BillingInvoice',
|
||||
})
|
||||
export default class BillingInvoice extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: 'projectId',
|
||||
type: TableColumnType.Entity,
|
||||
modelType: Project,
|
||||
})
|
||||
@ManyToOne(
|
||||
(_type: string) => {
|
||||
return Project;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: 'CASCADE',
|
||||
orphanedRowAction: 'nullify',
|
||||
}
|
||||
)
|
||||
@JoinColumn({ name: 'projectId' })
|
||||
public project?: Project = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@TableColumn({
|
||||
type: TableColumnType.ObjectID,
|
||||
required: true,
|
||||
canReadOnPopulate: true,
|
||||
})
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: false,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public projectId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: 'createdByUserId',
|
||||
type: TableColumnType.Entity,
|
||||
modelType: User,
|
||||
})
|
||||
@ManyToOne(
|
||||
(_type: string) => {
|
||||
return User;
|
||||
},
|
||||
{
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: 'CASCADE',
|
||||
orphanedRowAction: 'nullify',
|
||||
}
|
||||
)
|
||||
@JoinColumn({ name: 'createdByUserId' })
|
||||
public createdByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ObjectID })
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public createdByUserId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
manyToOneRelationColumn: 'deletedByUserId',
|
||||
type: TableColumnType.ObjectID,
|
||||
})
|
||||
@ManyToOne(
|
||||
(_type: string) => {
|
||||
return User;
|
||||
},
|
||||
{
|
||||
cascade: false,
|
||||
eager: false,
|
||||
nullable: true,
|
||||
onDelete: 'CASCADE',
|
||||
orphanedRowAction: 'nullify',
|
||||
}
|
||||
)
|
||||
@JoinColumn({ name: 'deletedByUserId' })
|
||||
public deletedByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ObjectID })
|
||||
@Column({
|
||||
type: ColumnType.ObjectID,
|
||||
nullable: true,
|
||||
transformer: ObjectID.getDatabaseTransformer(),
|
||||
})
|
||||
public deletedByUserId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.Number })
|
||||
@Column({
|
||||
type: ColumnType.Decimal,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
})
|
||||
public amount?: number = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
})
|
||||
public currencyCode?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.LongURL })
|
||||
@Column({
|
||||
type: ColumnType.LongURL,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
transformer: URL.getDatabaseTransformer(),
|
||||
})
|
||||
public downloadableLink?: URL = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
})
|
||||
public status?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
})
|
||||
public paymentProviderCustomerId?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: true,
|
||||
unique: false,
|
||||
})
|
||||
public paymentProviderSubscriptionId?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadInvoices],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@Column({
|
||||
type: ColumnType.ShortText,
|
||||
length: ColumnLength.ShortText,
|
||||
nullable: false,
|
||||
unique: false,
|
||||
})
|
||||
public paymentProviderInvoiceId?: string = undefined;
|
||||
}
|
||||
@@ -14,13 +14,15 @@ import Permission from 'Common/Types/Permission';
|
||||
import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl';
|
||||
import TenantColumn from 'Common/Types/Database/TenantColumn';
|
||||
import SingularPluralName from 'Common/Types/Database/SingularPluralName';
|
||||
import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid';
|
||||
|
||||
@AllowAccessIfSubscriptionIsUnpaid()
|
||||
@TenantColumn('projectId')
|
||||
@TableAccessControl({
|
||||
create: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner],
|
||||
delete: [Permission.ProjectOwner],
|
||||
update: [Permission.ProjectOwner],
|
||||
create: [Permission.ProjectOwner, Permission.CanCreateBillingPaymentMethod],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
delete: [Permission.ProjectOwner, Permission.CanDeleteBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@CrudApiEndpoint(new Route('/billing-payment-methods'))
|
||||
@SingularPluralName('Payment Method', 'Payment Methods')
|
||||
@@ -29,8 +31,8 @@ import SingularPluralName from 'Common/Types/Database/SingularPluralName';
|
||||
})
|
||||
export default class BillingPaymentMethod extends BaseModel {
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner],
|
||||
create: [Permission.ProjectOwner, Permission.CanCreateBillingPaymentMethod],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@@ -53,8 +55,8 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
public project?: Project = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner],
|
||||
create: [Permission.ProjectOwner, Permission.CanCreateBillingPaymentMethod],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@Index()
|
||||
@@ -71,8 +73,8 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
public projectId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner],
|
||||
create: [Permission.ProjectOwner, Permission.CanCreateBillingPaymentMethod],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@@ -95,8 +97,8 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
public createdByUser?: User = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner],
|
||||
create: [Permission.ProjectOwner, Permission.CanCreateBillingPaymentMethod],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ObjectID })
|
||||
@@ -109,7 +111,7 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({
|
||||
@@ -133,7 +135,7 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ObjectID })
|
||||
@@ -145,8 +147,8 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
public deletedByUserId?: ObjectID = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner],
|
||||
create: [Permission.ProjectOwner, Permission.CanCreateBillingPaymentMethod],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@@ -160,7 +162,7 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@@ -174,7 +176,7 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [],
|
||||
read: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@@ -187,8 +189,8 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
public paymentProviderCustomerId?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner],
|
||||
create: [Permission.ProjectOwner, Permission.CanCreateBillingPaymentMethod],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.ShortText })
|
||||
@@ -201,8 +203,8 @@ export default class BillingPaymentMethod extends BaseModel {
|
||||
public last4Digits?: string = undefined;
|
||||
|
||||
@ColumnAccessControl({
|
||||
create: [Permission.ProjectOwner],
|
||||
read: [Permission.ProjectOwner],
|
||||
create: [Permission.ProjectOwner, Permission.CanCreateBillingPaymentMethod],
|
||||
read: [Permission.ProjectOwner, Permission.CanReadBillingPaymentMethod],
|
||||
update: [],
|
||||
})
|
||||
@TableColumn({ type: TableColumnType.Boolean })
|
||||
|
||||
@@ -57,6 +57,7 @@ import ProjectSmtpConfig from './ProjectSmtpConfig';
|
||||
import Domain from './Domain';
|
||||
|
||||
import File from './File';
|
||||
import BillingInvoice from './BillingInvoice';
|
||||
|
||||
export default [
|
||||
User,
|
||||
@@ -100,4 +101,5 @@ export default [
|
||||
ScheduledMaintenanceInternalNote,
|
||||
|
||||
BillingPaymentMethods,
|
||||
BillingInvoice
|
||||
];
|
||||
|
||||
@@ -11,12 +11,14 @@ import Route from 'Common/Types/API/Route';
|
||||
import TableColumnType from 'Common/Types/Database/TableColumnType';
|
||||
import SlugifyColumn from 'Common/Types/Database/SlugifyColumn';
|
||||
import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl';
|
||||
import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid';
|
||||
import Permission from 'Common/Types/Permission';
|
||||
import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl';
|
||||
import TenantColumn from 'Common/Types/Database/TenantColumn';
|
||||
import SingularPluralName from 'Common/Types/Database/SingularPluralName';
|
||||
import MultiTenentQueryAllowed from 'Common/Types/Database/MultiTenentQueryAllowed';
|
||||
|
||||
@AllowAccessIfSubscriptionIsUnpaid()
|
||||
@MultiTenentQueryAllowed(true)
|
||||
@TableAccessControl({
|
||||
create: [Permission.User],
|
||||
|
||||
@@ -20,7 +20,10 @@ import Permission from 'Common/Types/Permission';
|
||||
import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl';
|
||||
import CurrentUserCanAccessRecordBy from 'Common/Types/Database/CurrentUserCanAccessRecordBy';
|
||||
import SingularPluralName from 'Common/Types/Database/SingularPluralName';
|
||||
import AllowAccessIfSubscriptionIsUnpaid from 'Common/Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid';
|
||||
|
||||
|
||||
@AllowAccessIfSubscriptionIsUnpaid()
|
||||
@TableAccessControl({
|
||||
create: [Permission.Public],
|
||||
read: [
|
||||
|
||||
Reference in New Issue
Block a user