add pricing

This commit is contained in:
Emily
2024-06-05 15:40:51 +02:00
parent f7891a94cd
commit 854d6eb528
22 changed files with 435 additions and 294 deletions

66
shared/data/PREMIUM.ts Normal file
View File

@@ -0,0 +1,66 @@
export type PREMIUM_TAG = typeof PREMIUM_TAGS[number];
export const PREMIUM_TAGS = [
'FREE', 'PLAN_1', 'PLAN_2', 'PLAN_3', 'PLAN_99'
] as const;
export type PREMIUM_DATA = {
COUNT_LIMIT: number,
AI_MESSAGE_LIMIT: number,
PRICE: string,
ID: number
}
export const PREMIUM_PLAN: Record<PREMIUM_TAG, PREMIUM_DATA> = {
FREE: {
ID: 0,
COUNT_LIMIT: 3_000,
AI_MESSAGE_LIMIT: 10,
PRICE: 'price_1PNbHYB2lPUiVs9VZP32xglF'
},
PLAN_1: {
ID: 1,
COUNT_LIMIT: 150_000,
AI_MESSAGE_LIMIT: 100,
PRICE: 'price_1PNZjVB2lPUiVs9VrsTbJL04'
},
PLAN_2: {
ID: 2,
COUNT_LIMIT: 500_000,
AI_MESSAGE_LIMIT: 5_000,
PRICE: ''
},
PLAN_3: {
ID: 3,
COUNT_LIMIT: 2_000_000,
AI_MESSAGE_LIMIT: 10_000,
PRICE: ''
},
PLAN_99: {
ID: 99,
COUNT_LIMIT: 10_000_000,
AI_MESSAGE_LIMIT: 100_000,
PRICE: ''
}
}
export function getPlanFromTag(tag: PREMIUM_TAG) {
return PREMIUM_PLAN[tag];
}
export function getPlanFromId(id: number) {
for (const tag of PREMIUM_TAGS) {
const plan = getPlanFromTag(tag);
if (plan.ID === id) return plan;
}
}
export function getPlanFromPrice(price: string) {
for (const tag of PREMIUM_TAGS) {
const plan = getPlanFromTag(tag);
if (plan.PRICE === price) return plan;
}
}

View File

@@ -1,81 +0,0 @@
export const PREMIUM_PLANS = [
{ id: 0, tag: 'FREE', name: 'Free' },
{ id: 1, tag: 'PLAN_1', name: 'Premium 1' },
{ id: 2, tag: 'PLAN_2', name: 'Premium 2' },
{ id: 3, tag: 'PLAN_3', name: 'Premium 3' },
{ id: 99, tag: 'PLAN_99', name: 'Premium 99' },
] as const;
export function getPlanFromPremiumType(premium_type?: number) {
if (!premium_type) return PREMIUM_PLANS[0];
const plan = PREMIUM_PLANS.find(e => e.id === premium_type);
if (!plan) return PREMIUM_PLANS[0];
return plan;
}
export function getPlanFromPremiumTag(tag: PREMIUM_PLAN_TAG) {
const plan = PREMIUM_PLANS.find(e => e.tag === tag);
return plan;
}
export type PREMIUM_PLAN_TAG = typeof PREMIUM_PLANS[number]['tag'];
export type PROJECT_LIMIT = {
COUNT_LIMIT: number,
AI_MESSAGE_LIMIT: number,
}
export const PREMIUM_LIMITS: Record<PREMIUM_PLAN_TAG, PROJECT_LIMIT> = {
FREE: {
COUNT_LIMIT: 3_000,
AI_MESSAGE_LIMIT: 10
},
PLAN_1: {
COUNT_LIMIT: 150_000,
AI_MESSAGE_LIMIT: 100
},
PLAN_2: {
COUNT_LIMIT: 500_000,
AI_MESSAGE_LIMIT: 5_000
},
PLAN_3: {
COUNT_LIMIT: 2_000_000,
AI_MESSAGE_LIMIT: 10_000
},
PLAN_99: {
COUNT_LIMIT: 10_000_000,
AI_MESSAGE_LIMIT: 100_000
}
}
export type STRIPE_PLAN = {
price: string
}
export const STRIPE_PLANS: Record<PREMIUM_PLAN_TAG, STRIPE_PLAN> = {
FREE: {
price: 'price_1PNbHYB2lPUiVs9VZP32xglF'
},
PLAN_1: {
price: 'price_1PNZjVB2lPUiVs9VrsTbJL04'
},
PLAN_2: {
price: ''
},
PLAN_3: {
price: ''
},
PLAN_99: {
price: ''
}
}
export function getPlanTagFromStripePrice(price: string): PREMIUM_PLAN_TAG | undefined {
for (const plan of PREMIUM_PLANS.map(e => e.tag)) {
const stripePrice = STRIPE_PLANS[plan].price;
if (stripePrice === price) return plan;
}
}

View File

@@ -1,70 +0,0 @@
import { ProjectCountModel } from '../schema/ProjectsCounts';
import { ProjectModel } from '../schema/ProjectSchema';
import { LimitNotifyModel } from '../schema/broker/LimitNotifySchema';
import { PREMIUM_LIMITS, getPlanFromPremiumType } from '../data/PREMIUM_LIMITS';
import { MONTH } from '../utilts/TIME';
export async function getCurrentProjectCountId(project_id: string) {
const projectCount = await ProjectCountModel.findOne({ project_id }, { _id: 1 }, { sort: { billing_expire_at: -1 } });
return projectCount?._id.toString();
}
export async function getAllLimitsFromProjectId(project_id: string) {
const targetProject = await ProjectModel.findById(project_id, {
premium: 1, premium_type: 1, premium_expire_at: 1
});
if (!targetProject) return PREMIUM_LIMITS.FREE;
if (!targetProject.premium) return PREMIUM_LIMITS.FREE;
const plan = getPlanFromPremiumType(targetProject.premium_type);
return PREMIUM_LIMITS[plan.tag];
}
export async function checkProjectCount(project_id: string) {
const targetProject = await ProjectModel.findById(project_id, {
premium: 1, premium_type: 1, premium_expire_at: 1
});
if (!targetProject) return;
if (new Date(targetProject.premium_expire_at).getTime() < Date.now()) {
await ProjectModel.updateOne({ _id: project_id }, {
premium: false,
$unset: {
premium_type: 1,
premium_expire_at: 1
},
});
}
const limits = await getAllLimitsFromProjectId(project_id);
const projectCounts = await ProjectCountModel.findOne({ project_id }, {}, { sort: { billing_expire_at: -1 } });
const billingExpireAt = projectCounts ? new Date(projectCounts.billing_expire_at).getTime() : -1;
if (projectCounts && Date.now() < billingExpireAt) {
if (projectCounts.ai_limit) return projectCounts.toJSON();
projectCounts.ai_limit = limits.AI_MESSAGE_LIMIT;
const saved = await projectCounts.save();
return saved.toJSON();
}
const newProjectCounts = await ProjectCountModel.create({
project_id,
events: 0,
visits: 0,
limit: limits.COUNT_LIMIT,
ai_messages: 0,
ai_limit: limits.AI_MESSAGE_LIMIT,
billing_start_at: projectCounts ? billingExpireAt : Date.now(),
billing_expire_at: (projectCounts ? billingExpireAt : Date.now()) + MONTH
});
await LimitNotifyModel.updateOne({ project_id }, { limit1: false, limit2: false, limit3: false });
return newProjectCounts.toJSON();
}

View File

@@ -5,8 +5,9 @@ export type TProject = {
owner: Schema.Types.ObjectId,
name: string,
premium: boolean,
premium_type?: number,
customer_id?: string,
premium_type: number,
customer_id: string,
subscription_id: string,
premium_expire_at: Date,
created_at: Date
}
@@ -15,9 +16,10 @@ const ProjectSchema = new Schema<TProject>({
owner: { type: Types.ObjectId, index: 1 },
name: { type: String, required: true },
premium: { type: Boolean, default: false },
premium_type: { type: Number },
customer_id: { type: String },
premium_expire_at: { type: Date },
premium_type: { type: Number, default: 0 },
customer_id: { type: String, required: true },
subscription_id: { type: String, required: true },
premium_expire_at: { type: Date, required: true },
created_at: { type: Date, default: () => Date.now() },
})

View File

@@ -5,22 +5,12 @@ export type TProjectCount = {
project_id: Schema.Types.ObjectId,
events: number,
visits: number,
ai_messages: number,
limit: number,
ai_limit: number,
billing_expire_at: Date,
billing_start_at: Date,
}
const ProjectCountSchema = new Schema<TProjectCount>({
project_id: { type: Types.ObjectId, index: 1 },
events: { type: Number, required: true, default: 0 },
visits: { type: Number, required: true, default: 0 },
ai_messages: { type: Number, required: true, default: 0 },
limit: { type: Number, required: true },
ai_limit: { type: Number, required: true },
billing_start_at: { type: Date, required: true },
billing_expire_at: { type: Date, required: true },
});
export const ProjectCountModel = model<TProjectCount>('project_counts', ProjectCountSchema);

View File

@@ -0,0 +1,26 @@
import { model, Schema, Types } from 'mongoose';
export type TProjectLimit = {
_id: Schema.Types.ObjectId,
project_id: Schema.Types.ObjectId,
events: number,
visits: number,
ai_messages: number,
limit: number,
ai_limit: number,
billing_expire_at: Date,
billing_start_at: Date,
}
const ProjectLimitSchema = new Schema<TProjectLimit>({
project_id: { type: Types.ObjectId, index: true, unique: true },
events: { type: Number, required: true, default: 0 },
visits: { type: Number, required: true, default: 0 },
ai_messages: { type: Number, required: true, default: 0 },
limit: { type: Number, required: true },
ai_limit: { type: Number, required: true },
billing_start_at: { type: Date, required: true },
billing_expire_at: { type: Date, required: true },
});
export const ProjectLimitModel = model<TProjectLimit>('project_limits', ProjectLimitSchema);