payment plan settings
parent
d2489b3093
commit
5321476433
|
@ -261,6 +261,139 @@ export async function StripeWebhook(req: Request, res: Response) {
|
|||
return res.status(400).send(`Webhook Error: ${error}`);
|
||||
}
|
||||
|
||||
let userId;
|
||||
let customerId;
|
||||
let user;
|
||||
|
||||
switch (event.type) {
|
||||
case "checkout.session.completed":
|
||||
userId = (event.data.object as Stripe.Checkout.Session)?.metadata
|
||||
?.userId;
|
||||
|
||||
if (userId === undefined) {
|
||||
logger.error(
|
||||
"StripeWebhook: checkout.session.completed: userId undefined"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// delete user pending payment
|
||||
|
||||
const pendingPayment = await UserPendingPayment.findOne({
|
||||
where: {
|
||||
payment_session_id: (event.data.object as Stripe.Checkout.Session)
|
||||
.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (pendingPayment !== null) {
|
||||
await pendingPayment.destroy();
|
||||
|
||||
let user = await User.findOne({
|
||||
where: {
|
||||
user_id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user !== null && user.state === ACCOUNT_STATE.INIT_PAYMENT) {
|
||||
// update user state
|
||||
await User.update(
|
||||
{
|
||||
state: ACCOUNT_STATE.ACTIVE,
|
||||
stripe_customer_id: (
|
||||
event.data.object as Stripe.Checkout.Session
|
||||
).customer as string,
|
||||
},
|
||||
{
|
||||
where: {
|
||||
user_id: userId,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
userLogger.info(
|
||||
userId as string,
|
||||
"StripeWebhook: user state updated to ACTIVE"
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "checkout.session.expired":
|
||||
logger.info(
|
||||
`StripeWebhook: checkout.session.expired: ${event.data.object.id}`
|
||||
);
|
||||
|
||||
break;
|
||||
case "billing_portal.session.created":
|
||||
customerId = event.data.object.customer as string;
|
||||
|
||||
user = await User.findOne({
|
||||
where: {
|
||||
stripe_customer_id: customerId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
logger.error(
|
||||
`StripeWebhook: customer.subscription.updated: user not found for customerId: ${customerId}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
userLogger.info(
|
||||
user.user_id as string,
|
||||
`StripeWebhook: billing_portal.session.created: customerId: ${customerId}`
|
||||
);
|
||||
break;
|
||||
case "customer.subscription.updated":
|
||||
const subscription = event.data.object as Stripe.Subscription;
|
||||
|
||||
const status = subscription.status;
|
||||
const trialEnd = subscription.trial_end as number | null;
|
||||
const canceledAt = subscription.canceled_at;
|
||||
|
||||
customerId = subscription.customer as string;
|
||||
|
||||
logger.info(
|
||||
`StripeWebhook: customer.subscription.updated: ${subscription.id} status: ${status} trialEnd: ${trialEnd} canceledAt: ${canceledAt} customerId: ${customerId}`
|
||||
);
|
||||
|
||||
user = await User.findOne({
|
||||
where: {
|
||||
stripe_customer_id: customerId,
|
||||
},
|
||||
});
|
||||
|
||||
if (user === null) {
|
||||
logger.error(
|
||||
`StripeWebhook: customer.subscription.updated: user not found for customerId: ${customerId}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
let updateData: any = {
|
||||
payment_plan_status: status,
|
||||
payment_plan_trial_end: trialEnd !== null ? trialEnd : null,
|
||||
payment_plan_canceled_at: canceledAt !== null ? canceledAt : null,
|
||||
};
|
||||
|
||||
logger.info(
|
||||
`StripeWebhook: customer.subscription.updated: updating user: ${
|
||||
user.user_id
|
||||
} ${JSON.stringify(updateData)}`
|
||||
);
|
||||
|
||||
await User.update(updateData, {
|
||||
where: {
|
||||
stripe_customer_id: customerId,
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
logger.warn(`StripeWebhook: Ignoring unknown event: ${event.type}`);
|
||||
break;
|
||||
}
|
||||
/*
|
||||
if (event.type === "checkout.session.completed") {
|
||||
const userId = (event.data.object as Stripe.Checkout.Session)?.metadata
|
||||
?.userId;
|
||||
|
@ -307,11 +440,11 @@ export async function StripeWebhook(req: Request, res: Response) {
|
|||
logger.info(`webhook checkout.session.expired: ${event.data.object.id}`);
|
||||
} else {
|
||||
logger.warn(`Ignoring unknown event: ${event.type}`);
|
||||
}
|
||||
} */
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
logger.error("StripeWebhook", error as string);
|
||||
logger.error("StripeWebhook err:", error as string);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
CALENDAR_MIN_EARLIEST_BOOKING_TIME,
|
||||
EMAIL_VERIFICATION_STATE,
|
||||
PAYMENT_PLAN_SETTINGS,
|
||||
PAYMENT_PLAN_STATUS,
|
||||
Roles,
|
||||
USER_ANALYTICS_ENABLED_DEFAULT,
|
||||
} from "../utils/constants";
|
||||
|
@ -175,6 +176,7 @@ export async function SignUp(req: Request, res: Response) {
|
|||
analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT,
|
||||
state: ACCOUNT_STATE.INIT_PAYMENT,
|
||||
payment_plan: paymentPlan,
|
||||
payment_plan_status: PAYMENT_PLAN_STATUS.TRAILING,
|
||||
});
|
||||
|
||||
const checkoutSessionUrl = await CreateCheckoutSession(
|
||||
|
@ -449,6 +451,9 @@ export async function GetUser(req: Request, res: Response) {
|
|||
"language",
|
||||
"analytics_enabled",
|
||||
"payment_plan",
|
||||
"payment_plan_status",
|
||||
"payment_plan_trial_end",
|
||||
"payment_plan_canceled_at",
|
||||
"created_at",
|
||||
],
|
||||
});
|
||||
|
@ -476,10 +481,14 @@ export async function GetUser(req: Request, res: Response) {
|
|||
language: user.language,
|
||||
analytics_enabled: user.analytics_enabled,
|
||||
payment_plan: user.payment_plan,
|
||||
payment_plan_status: user.payment_plan_status,
|
||||
payment_plan_trial_end: user.payment_plan_trial_end,
|
||||
payment_plan_canceled_at: user.payment_plan_canceled_at,
|
||||
},
|
||||
stores: stores,
|
||||
// only temporary until we have a proper permissions system
|
||||
permissions: [] as string[],
|
||||
paymentPlanSettings: PAYMENT_PLAN_SETTINGS[user.payment_plan],
|
||||
};
|
||||
|
||||
// if user is not a store master, then check if user is a worker
|
||||
|
@ -512,6 +521,16 @@ export async function GetUser(req: Request, res: Response) {
|
|||
);
|
||||
}
|
||||
|
||||
// send error if user has invalid payment plan
|
||||
if (!isPaymentPlanValid(user.payment_plan)) {
|
||||
userLogger.error(
|
||||
session.user_id,
|
||||
`User has invalid payment plan: ${
|
||||
user.payment_plan
|
||||
}. Possible highest payment plan is ${PAYMENT_PLAN_SETTINGS.length - 1}`
|
||||
);
|
||||
}
|
||||
|
||||
// update user session last_used
|
||||
|
||||
Session.update(
|
||||
|
|
|
@ -18,6 +18,9 @@ interface UserAttributes {
|
|||
google_account_picture?: string;
|
||||
analytics_enabled: boolean;
|
||||
payment_plan: number;
|
||||
payment_plan_status?: string;
|
||||
payment_plan_trial_end?: number;
|
||||
payment_plan_canceled_at?: number;
|
||||
stripe_customer_id?: string;
|
||||
}
|
||||
|
||||
|
@ -37,6 +40,9 @@ class User extends Model<UserAttributes> implements UserAttributes {
|
|||
declare google_account_picture: string;
|
||||
declare analytics_enabled: boolean;
|
||||
declare payment_plan: number;
|
||||
declare payment_plan_status: string;
|
||||
declare payment_plan_trial_end: number;
|
||||
declare payment_plan_canceled_at: number;
|
||||
declare stripe_customer_id: string;
|
||||
declare created_at: Date;
|
||||
}
|
||||
|
@ -95,7 +101,7 @@ User.init(
|
|||
},
|
||||
google_account_picture: {
|
||||
type: DataTypes.STRING(1050),
|
||||
allowNull: true, // varchar(1050) needs to be set manually
|
||||
allowNull: true,
|
||||
},
|
||||
analytics_enabled: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
@ -105,6 +111,18 @@ User.init(
|
|||
type: DataTypes.TINYINT,
|
||||
allowNull: false,
|
||||
},
|
||||
payment_plan_status: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
payment_plan_trial_end: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
payment_plan_canceled_at: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
stripe_customer_id: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
|
|
|
@ -89,27 +89,25 @@ export const ACCOUNT_EXPORT_URL = `${DASHBOARD_URL}/api/v1/user/profile/export/`
|
|||
export const TERMIN_PLANNER_URL = process.env.TERMIN_PLANNER_URL;
|
||||
|
||||
export enum PAYMENT_PLAN {
|
||||
DEMO = 0,
|
||||
BASIC = 1,
|
||||
PREMIUM = 2,
|
||||
BASIC = 0,
|
||||
PREMIUM = 1,
|
||||
}
|
||||
|
||||
export const PAYMENT_PLAN_SETTINGS = [
|
||||
{
|
||||
// demo
|
||||
maxEmployees: 5,
|
||||
calendarMaxFutureBookingDays: 14,
|
||||
},
|
||||
{
|
||||
// basic
|
||||
id: "basic",
|
||||
id: "basic", // used in the backend for identifiying the stripe pricing product
|
||||
name: "Basic", // used in the frontend
|
||||
maxEmployees: 15,
|
||||
calendarMaxFutureBookingDays: 60,
|
||||
},
|
||||
{
|
||||
// premium
|
||||
id: "premium",
|
||||
id: "premium", // used in the backend for identifiying the stripe pricing product
|
||||
name: "Premium", // used in the frontend
|
||||
maxEmployees: 20,
|
||||
calendarMaxFutureBookingDays: 90,
|
||||
},
|
||||
];
|
||||
|
||||
export enum PAYMENT_PLAN_STATUS {
|
||||
TRAILING = "trailing",
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ export function isCalendarMinEarliestBookingTimeValid(
|
|||
|
||||
export function isPaymentPlanValid(paymentPlan: number) {
|
||||
return (
|
||||
paymentPlan !== PAYMENT_PLAN.DEMO && paymentPlan <= PAYMENT_PLAN.PREMIUM
|
||||
paymentPlan >= PAYMENT_PLAN.BASIC && paymentPlan <= PAYMENT_PLAN.PREMIUM
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue