Merge pull request #1040 from automatisch/usage-data
feat: add subscription update and cancel logics
This commit is contained in:
@@ -12,6 +12,10 @@ export default async (request: IRequest, response: Response) => {
|
|||||||
|
|
||||||
if (request.body.alert_name === 'subscription_created') {
|
if (request.body.alert_name === 'subscription_created') {
|
||||||
await Billing.webhooks.handleSubscriptionCreated(request);
|
await Billing.webhooks.handleSubscriptionCreated(request);
|
||||||
|
} else if (request.body.alert_name === 'subscription_updated') {
|
||||||
|
await Billing.webhooks.handleSubscriptionUpdated(request);
|
||||||
|
} else if (request.body.alert_name === 'subscription_cancelled') {
|
||||||
|
await Billing.webhooks.handleSubscriptionCancelled(request);
|
||||||
} else if (request.body.alert_name === 'subscription_payment_succeeded') {
|
} else if (request.body.alert_name === 'subscription_payment_succeeded') {
|
||||||
await Billing.webhooks.handleSubscriptionPaymentSucceeded(request);
|
await Billing.webhooks.handleSubscriptionPaymentSucceeded(request);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
import appConfig from '../../config/app';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!appConfig.isCloud) return;
|
||||||
|
|
||||||
|
return knex.schema.table('usage_data', (table) => {
|
||||||
|
table.uuid('subscription_id').references('id').inTable('subscriptions');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (!appConfig.isCloud) return;
|
||||||
|
|
||||||
|
return knex.schema.table('usage_data', (table) => {
|
||||||
|
table.dropColumn('subscription_id');
|
||||||
|
});
|
||||||
|
}
|
@@ -13,7 +13,7 @@ const getBillingAndUsage = async (
|
|||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const persistedSubscription = await context.currentUser.$relatedQuery(
|
const persistedSubscription = await context.currentUser.$relatedQuery(
|
||||||
'subscription'
|
'currentSubscription'
|
||||||
);
|
);
|
||||||
|
|
||||||
const subscription = persistedSubscription
|
const subscription = persistedSubscription
|
||||||
@@ -39,8 +39,8 @@ const paidSubscription = (subscription: Subscription): TSubscription => {
|
|||||||
title: currentPlan.limit,
|
title: currentPlan.limit,
|
||||||
action: {
|
action: {
|
||||||
type: 'link',
|
type: 'link',
|
||||||
text: 'Change plan',
|
text: 'Cancel plan',
|
||||||
src: '/settings/billing/change-plan',
|
src: subscription.cancelUrl,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nextBillAmount: {
|
nextBillAmount: {
|
||||||
|
@@ -6,7 +6,7 @@ const getInvoices = async (
|
|||||||
_params: unknown,
|
_params: unknown,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const subscription = await context.currentUser.$relatedQuery('subscription');
|
const subscription = await context.currentUser.$relatedQuery('currentSubscription');
|
||||||
|
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
return;
|
return;
|
||||||
|
19
packages/backend/src/graphql/queries/get-trial-status.ee.ts
Normal file
19
packages/backend/src/graphql/queries/get-trial-status.ee.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import appConfig from '../../config/app';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
|
const getTrialStatus = async (
|
||||||
|
_parent: unknown,
|
||||||
|
_params: unknown,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
if (!appConfig.isCloud) return;
|
||||||
|
|
||||||
|
const inTrial = await context.currentUser.inTrial();
|
||||||
|
if (!inTrial) return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
expireAt: context.currentUser.trialExpiryDate,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getTrialStatus;
|
@@ -1,6 +1,7 @@
|
|||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
|
// TODO: remove as getBillingAndUsageData query has been introduced
|
||||||
const getUsageData = async (
|
const getUsageData = async (
|
||||||
_parent: unknown,
|
_parent: unknown,
|
||||||
_params: unknown,
|
_params: unknown,
|
||||||
@@ -9,18 +10,20 @@ const getUsageData = async (
|
|||||||
if (!appConfig.isCloud) return;
|
if (!appConfig.isCloud) return;
|
||||||
|
|
||||||
const usageData = await context.currentUser
|
const usageData = await context.currentUser
|
||||||
.$relatedQuery('usageData')
|
.$relatedQuery('currentUsageData')
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const paymentPlan = await context.currentUser
|
const subscription = await usageData
|
||||||
.$relatedQuery('paymentPlan')
|
.$relatedQuery('subscription')
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const plan = subscription.plan;
|
||||||
|
|
||||||
const computedUsageData = {
|
const computedUsageData = {
|
||||||
name: paymentPlan.name,
|
name: plan.name,
|
||||||
allowedTaskCount: paymentPlan.taskCount,
|
allowedTaskCount: plan.quota,
|
||||||
consumedTaskCount: usageData.consumedTaskCount,
|
consumedTaskCount: usageData.consumedTaskCount,
|
||||||
remainingTaskCount: paymentPlan.taskCount - usageData.consumedTaskCount,
|
remainingTaskCount: plan.quota - usageData.consumedTaskCount,
|
||||||
nextResetAt: usageData.nextResetAt,
|
nextResetAt: usageData.nextResetAt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ import getPaddleInfo from './queries/get-paddle-info.ee';
|
|||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
||||||
import getInvoices from './queries/get-invoices.ee';
|
import getInvoices from './queries/get-invoices.ee';
|
||||||
import getAutomatischInfo from './queries/get-automatisch-info';
|
import getAutomatischInfo from './queries/get-automatisch-info';
|
||||||
|
import getTrialStatus from './queries/get-trial-status.ee';
|
||||||
import healthcheck from './queries/healthcheck';
|
import healthcheck from './queries/healthcheck';
|
||||||
|
|
||||||
const queryResolvers = {
|
const queryResolvers = {
|
||||||
@@ -39,6 +40,7 @@ const queryResolvers = {
|
|||||||
getBillingAndUsage,
|
getBillingAndUsage,
|
||||||
getInvoices,
|
getInvoices,
|
||||||
getAutomatischInfo,
|
getAutomatischInfo,
|
||||||
|
getTrialStatus,
|
||||||
healthcheck,
|
healthcheck,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -40,6 +40,7 @@ type Query {
|
|||||||
getBillingAndUsage: GetBillingAndUsage
|
getBillingAndUsage: GetBillingAndUsage
|
||||||
getInvoices: [Invoice]
|
getInvoices: [Invoice]
|
||||||
getAutomatischInfo: GetAutomatischInfo
|
getAutomatischInfo: GetAutomatischInfo
|
||||||
|
getTrialStatus: GetTrialStatus
|
||||||
healthcheck: AppHealth
|
healthcheck: AppHealth
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,6 +476,10 @@ type GetAutomatischInfo {
|
|||||||
isCloud: Boolean
|
isCloud: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetTrialStatus {
|
||||||
|
expireAt: String
|
||||||
|
}
|
||||||
|
|
||||||
type GetBillingAndUsage {
|
type GetBillingAndUsage {
|
||||||
subscription: Subscription
|
subscription: Subscription
|
||||||
usage: Usage
|
usage: Usage
|
||||||
|
@@ -24,6 +24,7 @@ const getSubscription = async (subscriptionId: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getInvoices = async (subscriptionId: number) => {
|
const getInvoices = async (subscriptionId: number) => {
|
||||||
|
// TODO: iterate over previous subscriptions and include their invoices
|
||||||
const data = {
|
const data = {
|
||||||
vendor_id: appConfig.paddleVendorId,
|
vendor_id: appConfig.paddleVendorId,
|
||||||
vendor_auth_code: appConfig.paddleVendorAuthCode,
|
vendor_auth_code: appConfig.paddleVendorAuthCode,
|
||||||
|
@@ -2,15 +2,14 @@ const plans = [
|
|||||||
{
|
{
|
||||||
name: '10k - monthly',
|
name: '10k - monthly',
|
||||||
limit: '10,000',
|
limit: '10,000',
|
||||||
|
quota: 10000,
|
||||||
price: '€20',
|
price: '€20',
|
||||||
productId: '47384',
|
productId: '47384',
|
||||||
},
|
}
|
||||||
{
|
|
||||||
name: '30k - monthly',
|
|
||||||
limit: '30,000',
|
|
||||||
price: '€50',
|
|
||||||
productId: '47419',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function getPlanById(id: string) {
|
||||||
|
return plans.find((plan) => plan.productId === id);
|
||||||
|
}
|
||||||
|
|
||||||
export default plans;
|
export default plans;
|
||||||
|
@@ -3,7 +3,30 @@ import Subscription from '../../models/subscription.ee';
|
|||||||
import Billing from './index.ee';
|
import Billing from './index.ee';
|
||||||
|
|
||||||
const handleSubscriptionCreated = async (request: IRequest) => {
|
const handleSubscriptionCreated = async (request: IRequest) => {
|
||||||
await Subscription.query().insertAndFetch(formatSubscription(request));
|
const subscription = await Subscription.query().insertAndFetch(formatSubscription(request));
|
||||||
|
await subscription.$relatedQuery('usageData').insert(formatUsageData(request));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubscriptionUpdated = async (request: IRequest) => {
|
||||||
|
await Subscription
|
||||||
|
.query()
|
||||||
|
.findOne({
|
||||||
|
paddle_subscription_id: request.body.subscription_id,
|
||||||
|
})
|
||||||
|
.patch(formatSubscription(request));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubscriptionCancelled = async (request: IRequest) => {
|
||||||
|
const subscription = await Subscription
|
||||||
|
.query()
|
||||||
|
.findOne({
|
||||||
|
paddle_subscription_id: request.body.subscription_id,
|
||||||
|
})
|
||||||
|
.patchAndFetch(formatSubscription(request));
|
||||||
|
|
||||||
|
if (request.body.status === 'deleted') {
|
||||||
|
await subscription.$query().delete();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubscriptionPaymentSucceeded = async (request: IRequest) => {
|
const handleSubscriptionPaymentSucceeded = async (request: IRequest) => {
|
||||||
@@ -22,6 +45,8 @@ const handleSubscriptionPaymentSucceeded = async (request: IRequest) => {
|
|||||||
nextBillDate: remoteSubscription.next_payment.date,
|
nextBillDate: remoteSubscription.next_payment.date,
|
||||||
lastBillDate: remoteSubscription.last_payment.date,
|
lastBillDate: remoteSubscription.last_payment.date,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await subscription.$relatedQuery('usageData').insert(formatUsageData(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatSubscription = (request: IRequest) => {
|
const formatSubscription = (request: IRequest) => {
|
||||||
@@ -37,8 +62,18 @@ const formatSubscription = (request: IRequest) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatUsageData = (request: IRequest) => {
|
||||||
|
return {
|
||||||
|
userId: JSON.parse(request.body.passthrough).id,
|
||||||
|
consumedTaskCount: 0,
|
||||||
|
nextResetAt: request.body.next_bill_date,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const webhooks = {
|
const webhooks = {
|
||||||
handleSubscriptionCreated,
|
handleSubscriptionCreated,
|
||||||
|
handleSubscriptionUpdated,
|
||||||
|
handleSubscriptionCancelled,
|
||||||
handleSubscriptionPaymentSucceeded,
|
handleSubscriptionPaymentSucceeded,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -67,7 +67,7 @@ class ExecutionStep extends Base {
|
|||||||
if (!execution.testRun && !this.isFailed) {
|
if (!execution.testRun && !this.isFailed) {
|
||||||
const flow = await execution.$relatedQuery('flow');
|
const flow = await execution.$relatedQuery('flow');
|
||||||
const user = await flow.$relatedQuery('user');
|
const user = await flow.$relatedQuery('user');
|
||||||
const usageData = await user.$relatedQuery('usageData');
|
const usageData = await user.$relatedQuery('currentUsageData');
|
||||||
|
|
||||||
await usageData.increaseConsumedTaskCountByOne();
|
await usageData.increaseConsumedTaskCountByOne();
|
||||||
}
|
}
|
||||||
|
@@ -136,7 +136,7 @@ class Flow extends Base {
|
|||||||
if (!appConfig.isCloud) return;
|
if (!appConfig.isCloud) return;
|
||||||
|
|
||||||
const user = await this.$relatedQuery('user');
|
const user = await this.$relatedQuery('user');
|
||||||
const usageData = await user.$relatedQuery('usageData');
|
const usageData = await user.$relatedQuery('currentUsageData');
|
||||||
|
|
||||||
const hasExceeded = await usageData.checkIfLimitExceeded();
|
const hasExceeded = await usageData.checkIfLimitExceeded();
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
import Base from './base';
|
import Base from './base';
|
||||||
import User from './user';
|
import User from './user';
|
||||||
|
import UsageData from './usage-data.ee';
|
||||||
|
import { getPlanById } from '../helpers/billing/plans.ee';
|
||||||
|
|
||||||
class Subscription extends Base {
|
class Subscription extends Base {
|
||||||
id!: string;
|
id!: string;
|
||||||
@@ -12,6 +14,8 @@ class Subscription extends Base {
|
|||||||
nextBillAmount!: string;
|
nextBillAmount!: string;
|
||||||
nextBillDate!: string;
|
nextBillDate!: string;
|
||||||
lastBillDate?: string;
|
lastBillDate?: string;
|
||||||
|
usageData?: UsageData[];
|
||||||
|
currentUsageData?: UsageData;
|
||||||
|
|
||||||
static tableName = 'subscriptions';
|
static tableName = 'subscriptions';
|
||||||
|
|
||||||
@@ -51,7 +55,31 @@ class Subscription extends Base {
|
|||||||
to: 'users.id',
|
to: 'users.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
usageData: {
|
||||||
|
relation: Base.HasManyRelation,
|
||||||
|
modelClass: UsageData,
|
||||||
|
join: {
|
||||||
|
from: 'subscriptions.id',
|
||||||
|
to: 'usage_data.subscription_id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currentUsageData: {
|
||||||
|
relation: Base.HasOneRelation,
|
||||||
|
modelClass: UsageData,
|
||||||
|
join: {
|
||||||
|
from: 'subscriptions.id',
|
||||||
|
to: 'usage_data.subscription_id',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
get plan() {
|
||||||
|
return getPlanById(this.paddlePlanId);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isActive() {
|
||||||
|
return this.status === 'active' || this.status === 'past_due';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Subscription;
|
export default Subscription;
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
import { raw } from 'objection';
|
import { raw } from 'objection';
|
||||||
import Base from './base';
|
import Base from './base';
|
||||||
import User from './user';
|
import User from './user';
|
||||||
import PaymentPlan from './payment-plan.ee';
|
import Subscription from './subscription.ee';
|
||||||
|
import { getPlanById } from '../helpers/billing/plans.ee';
|
||||||
|
|
||||||
class UsageData extends Base {
|
class UsageData extends Base {
|
||||||
id!: string;
|
id!: string;
|
||||||
userId!: string;
|
userId!: string;
|
||||||
|
subscriptionId?: string;
|
||||||
consumedTaskCount!: number;
|
consumedTaskCount!: number;
|
||||||
nextResetAt!: string;
|
nextResetAt!: string;
|
||||||
paymentPlan?: PaymentPlan;
|
subscription?: Subscription;
|
||||||
|
user?: User;
|
||||||
|
|
||||||
static tableName = 'usage_data';
|
static tableName = 'usage_data';
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ class UsageData extends Base {
|
|||||||
properties: {
|
properties: {
|
||||||
id: { type: 'string', format: 'uuid' },
|
id: { type: 'string', format: 'uuid' },
|
||||||
userId: { type: 'string', format: 'uuid' },
|
userId: { type: 'string', format: 'uuid' },
|
||||||
|
subscriptionId: { type: 'string', format: 'uuid' },
|
||||||
consumedTaskCount: { type: 'integer' },
|
consumedTaskCount: { type: 'integer' },
|
||||||
nextResetAt: { type: 'string' },
|
nextResetAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
@@ -33,24 +37,38 @@ class UsageData extends Base {
|
|||||||
to: 'users.id',
|
to: 'users.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
paymentPlan: {
|
subscription: {
|
||||||
relation: Base.BelongsToOneRelation,
|
relation: Base.BelongsToOneRelation,
|
||||||
modelClass: PaymentPlan,
|
modelClass: Subscription,
|
||||||
join: {
|
join: {
|
||||||
from: 'usage_data.user_id',
|
from: 'usage_data.subscription_id',
|
||||||
to: 'payment_plans.user_id',
|
to: 'subscriptions.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async checkIfLimitExceeded() {
|
async checkIfLimitExceeded() {
|
||||||
const paymentPlan = await this.$relatedQuery('paymentPlan');
|
const user = await this.$relatedQuery('user');
|
||||||
|
|
||||||
return this.consumedTaskCount >= paymentPlan.taskCount;
|
if (await user.inTrial()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = await this.$relatedQuery('subscription');
|
||||||
|
|
||||||
|
if (!subscription.isActive) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = subscription.plan;
|
||||||
|
|
||||||
|
return this.consumedTaskCount >= plan.quota;
|
||||||
}
|
}
|
||||||
|
|
||||||
async increaseConsumedTaskCountByOne() {
|
async increaseConsumedTaskCountByOne() {
|
||||||
return await this.$query().patch({ consumedTaskCount: raw('consumed_task_count + 1') });
|
return await this.$query().patch({
|
||||||
|
consumedTaskCount: raw('consumed_task_count + 1'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
import { QueryContext, ModelOptions } from 'objection';
|
import { QueryContext, ModelOptions } from 'objection';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import crypto from 'crypto';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import Base from './base';
|
import Base from './base';
|
||||||
|
import ExtendedQueryBuilder from './query-builder';
|
||||||
import Connection from './connection';
|
import Connection from './connection';
|
||||||
import Flow from './flow';
|
import Flow from './flow';
|
||||||
import Step from './step';
|
import Step from './step';
|
||||||
import Execution from './execution';
|
import Execution from './execution';
|
||||||
import ExecutionStep from './execution-step';
|
|
||||||
import bcrypt from 'bcrypt';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import PaymentPlan from './payment-plan.ee';
|
|
||||||
import UsageData from './usage-data.ee';
|
import UsageData from './usage-data.ee';
|
||||||
import Subscription from './subscription.ee';
|
import Subscription from './subscription.ee';
|
||||||
|
|
||||||
@@ -26,9 +25,10 @@ class User extends Base {
|
|||||||
flows?: Flow[];
|
flows?: Flow[];
|
||||||
steps?: Step[];
|
steps?: Step[];
|
||||||
executions?: Execution[];
|
executions?: Execution[];
|
||||||
paymentPlan?: PaymentPlan;
|
usageData?: UsageData[];
|
||||||
usageData?: UsageData;
|
currentUsageData?: UsageData;
|
||||||
subscription?: Subscription;
|
subscriptions?: Subscription[];
|
||||||
|
currentSubscription?: Subscription;
|
||||||
|
|
||||||
static tableName = 'users';
|
static tableName = 'users';
|
||||||
|
|
||||||
@@ -86,29 +86,43 @@ class User extends Base {
|
|||||||
to: 'executions.flow_id',
|
to: 'executions.flow_id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
paymentPlan: {
|
|
||||||
relation: Base.HasOneRelation,
|
|
||||||
modelClass: PaymentPlan,
|
|
||||||
join: {
|
|
||||||
from: 'payment_plans.user_id',
|
|
||||||
to: 'users.id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
usageData: {
|
usageData: {
|
||||||
relation: Base.HasOneRelation,
|
relation: Base.HasManyRelation,
|
||||||
modelClass: UsageData,
|
modelClass: UsageData,
|
||||||
join: {
|
join: {
|
||||||
from: 'usage_data.user_id',
|
from: 'usage_data.user_id',
|
||||||
to: 'users.id',
|
to: 'users.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
subscription: {
|
currentUsageData: {
|
||||||
|
relation: Base.HasOneRelation,
|
||||||
|
modelClass: UsageData,
|
||||||
|
join: {
|
||||||
|
from: 'usage_data.user_id',
|
||||||
|
to: 'users.id',
|
||||||
|
},
|
||||||
|
filter(builder: ExtendedQueryBuilder<UsageData>) {
|
||||||
|
builder.orderBy('created_at', 'desc').first();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subscriptions: {
|
||||||
|
relation: Base.HasManyRelation,
|
||||||
|
modelClass: Subscription,
|
||||||
|
join: {
|
||||||
|
from: 'subscriptions.user_id',
|
||||||
|
to: 'users.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currentSubscription: {
|
||||||
relation: Base.HasOneRelation,
|
relation: Base.HasOneRelation,
|
||||||
modelClass: Subscription,
|
modelClass: Subscription,
|
||||||
join: {
|
join: {
|
||||||
from: 'subscriptions.user_id',
|
from: 'subscriptions.user_id',
|
||||||
to: 'users.id',
|
to: 'users.id',
|
||||||
},
|
},
|
||||||
|
filter(builder: ExtendedQueryBuilder<Subscription>) {
|
||||||
|
builder.orderBy('created_at', 'desc').first();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,6 +165,29 @@ class User extends Base {
|
|||||||
this.trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
|
this.trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async inTrial() {
|
||||||
|
if (!appConfig.isCloud) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.trialExpiryDate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = await this.$relatedQuery('currentSubscription');
|
||||||
|
|
||||||
|
if (subscription?.isActive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiryDate = DateTime.fromJSDate(
|
||||||
|
this.trialExpiryDate as unknown as Date
|
||||||
|
);
|
||||||
|
const now = DateTime.now();
|
||||||
|
|
||||||
|
return now < expiryDate;
|
||||||
|
}
|
||||||
|
|
||||||
async $beforeInsert(queryContext: QueryContext) {
|
async $beforeInsert(queryContext: QueryContext) {
|
||||||
await super.$beforeInsert(queryContext);
|
await super.$beforeInsert(queryContext);
|
||||||
await this.generateHash();
|
await this.generateHash();
|
||||||
@@ -167,6 +204,18 @@ class User extends Base {
|
|||||||
await this.generateHash();
|
await this.generateHash();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async $afterInsert(queryContext: QueryContext) {
|
||||||
|
await super.$afterInsert(queryContext);
|
||||||
|
|
||||||
|
if (appConfig.isCloud) {
|
||||||
|
await this.$relatedQuery('usageData').insert({
|
||||||
|
userId: this.id,
|
||||||
|
consumedTaskCount: 0,
|
||||||
|
nextResetAt: DateTime.now().plus({ days: 30 }).toISODate(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
@@ -6,13 +6,13 @@ const router = Router();
|
|||||||
|
|
||||||
const exposeError =
|
const exposeError =
|
||||||
(handler: RequestHandler) =>
|
(handler: RequestHandler) =>
|
||||||
async (req: IRequest, res: Response, next: NextFunction) => {
|
async (req: IRequest, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
await handler(req, res, next);
|
await handler(req, res, next);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
router.post('/webhooks', exposeError(webhooksHandler));
|
router.post('/webhooks', exposeError(webhooksHandler));
|
||||||
|
|
||||||
|
@@ -191,7 +191,8 @@ export default function UsageDataInformation() {
|
|||||||
<Divider sx={{ mt: 2 }} />
|
<Divider sx={{ mt: 2 }} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button
|
{/* free plan has `null` status so that we can show the upgrade button */}
|
||||||
|
{billingAndUsageData?.subscription?.status === null && <Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to={URLS.SETTINGS_PLAN_UPGRADE}
|
to={URLS.SETTINGS_PLAN_UPGRADE}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -199,7 +200,7 @@ export default function UsageDataInformation() {
|
|||||||
sx={{ mt: 2, alignSelf: 'flex-end' }}
|
sx={{ mt: 2, alignSelf: 'flex-end' }}
|
||||||
>
|
>
|
||||||
{formatMessage('usageDataInformation.upgrade')}
|
{formatMessage('usageDataInformation.upgrade')}
|
||||||
</Button>
|
</Button>}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
Reference in New Issue
Block a user