Change / cancel plan though stripe customer dashboard

This commit is contained in:
Vins 2024-04-08 15:31:16 +02:00
parent df00b78d80
commit d3b7692823
5 changed files with 145 additions and 45 deletions

View File

@ -1,7 +1,7 @@
import authHandler from "@App/middlewares/AuthHandler";
// import roleHandler from "@App/middlewares/RolesHandler";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Controller, Get, Post } from "@ControllerPattern/index";
import { Controller, Get, Post, Put } from "@ControllerPattern/index";
import StripeService from "@Services/common/StripeService/StripeService";
import { validateOrReject } from "class-validator";
import { Request, Response } from "express";
@ -40,6 +40,33 @@ export default class StripeController extends ApiController {
}
}
@Put("/api/v1/admin/stripe/:uid")
protected async createStripeSubscriptionUpdateCheckout(req: Request, response: Response) {
try {
const uid = req.params["uid"];
if (!uid) {
this.httpBadRequest(response, "No uid provided");
return;
}
const officeId: string = req.body.user.office_Id;
//add office id to request body
req.body.office = { uid: officeId };
//init Subscription resource with request body values
const subscriptionEntity = Subscription.hydrate<Subscription>(req.body, { strategy: "excludeAll" });
await validateOrReject(subscriptionEntity, { groups: ["updateSubscription"], forbidUnknownValues: false });
const stripeSession = await this.stripeService.createCheckoutSessionUpdate(uid, subscriptionEntity);
this.httpCreated(response, stripeSession);
} catch (error) {
this.httpInternalError(response, error);
return;
}
}
@Get("/api/v1/admin/stripe/:uid", [authHandler])
protected async getClientPortalSession(req: Request, response: Response) {
try {

View File

@ -86,7 +86,6 @@ export default class SubscriptionsRepository extends BaseRepository {
* @description : update given subscription
*/
public async update(uid: string, subscription: Subscription): Promise<Subscriptions> {
if(subscription.type === "STANDARD")
{
const updateArgs: Prisma.SubscriptionsUpdateArgs = {
@ -98,16 +97,6 @@ export default class SubscriptionsRepository extends BaseRepository {
type: ESubscriptionType.STANDARD,
status: subscription.status as ESubscriptionStatus,
nb_seats: subscription.nb_seats!,
seats: {
deleteMany: {},
create: subscription.seats!.map(seat => ({
user: {
connect: {
uid: seat.user!.uid,
},
},
})),
}
},
};
return this.model.update(updateArgs);
@ -132,5 +121,16 @@ export default class SubscriptionsRepository extends BaseRepository {
}
}
/**
* @description : Delete a subscription
*/
public async delete(uid: string) {
return this.model.delete({
where: {
uid: uid,
},
});
}
}

View File

@ -1,3 +1,4 @@
import { BackendVariables } from "@Common/config/variables/Variables";
import ApiController from "@Common/system/controller-pattern/ApiController";
import { Controller, Post } from "@ControllerPattern/index";
import SubscriptionsService from "@Services/admin/SubscriptionsService/SubscriptionsService.ts";
@ -10,7 +11,7 @@ import { Service } from "typedi";
@Controller()
@Service()
export default class StripeWebhooks extends ApiController {
constructor(private stripeService: StripeService, private subscriptionsService: SubscriptionsService) {
constructor(private stripeService: StripeService, private subscriptionsService: SubscriptionsService, private backendVariables: BackendVariables) {
super();
}
@ -20,22 +21,32 @@ export default class StripeWebhooks extends ApiController {
@Post("/api/v1/webhooks/stripe")
protected async post(req: Request, response: Response) {
try {
// const sig = req.headers["stripe-signature"];
// const endpointSecret = "whsec_c4088876914bc166ff5c39253207f84900820b67f7bba3b2669c0ff392cbc838";
// const stripe = this.stripeService.getClient();
// let event: Stripe.Event;
// if (!sig || !endpointSecret) {
// throw new Error("Signature verification failed");
// }
// event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
const event = req.body;
switch (event.type) {
case "invoice.payment_succeeded":
if (event.data.object.billing_reason !== "subscription_update") break;
const stripeSubscription = await this.stripeService.getClient().subscriptions.retrieve(event.data.object.subscription);
const existingSubscription = await this.subscriptionsService.get({where : {stripe_subscription_id : stripeSubscription.id}});
if(!existingSubscription[0]) break;
const subscriptionUpdate: any = {};
subscriptionUpdate.start_date = new Date(stripeSubscription.current_period_start * 1000);
subscriptionUpdate.end_date = new Date(stripeSubscription.current_period_end * 1000);
subscriptionUpdate.nb_seats = stripeSubscription.items.data[0]?.quantity;
subscriptionUpdate.type = stripeSubscription.items.data[0]?.price?.id === this.backendVariables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID ? "STANDARD" : "UNLIMITED";
const subscriptionEntityUpdate = Subscription.hydrate<Subscription>(subscriptionUpdate);
await validateOrReject(subscriptionEntityUpdate, { groups: ["updateSubscription"], forbidUnknownValues: false });
await this.subscriptionsService.update(existingSubscription[0].uid ,subscriptionEntityUpdate);
case "checkout.session.completed":
if (event.data.object.status !== "complete") break;
const subscription = JSON.parse(event.data.object.metadata.subscription);
subscription.stripe_subscription_id = event.data.object.subscription;
const subscriptionInfo = await this.stripeService
@ -48,9 +59,13 @@ export default class StripeWebhooks extends ApiController {
const subscriptionEntity = Subscription.hydrate<Subscription>(subscription);
await validateOrReject(subscriptionEntity, { groups: ["createSubscription"], forbidUnknownValues: false });
await this.subscriptionsService.create(subscriptionEntity);
break;
case "customer.subscription.deleted":
const subscriptionToDelete = await this.subscriptionsService.get({where : {stripe_subscription_id : event.data.object.id}});
if(!subscriptionToDelete[0]) break;
await this.subscriptionsService.delete(subscriptionToDelete[0].uid);
default:
break;
}

View File

@ -42,4 +42,12 @@ export default class SubscriptionsService extends BaseService {
public async update(uid: string, subscriptionEntity: Subscription): Promise<Subscriptions> {
return this.subscriptionsRepository.update(uid, subscriptionEntity);
}
/**
* @description : Delete a subscription
* @throws {Error} If subscription cannot be deleted
*/
public async delete(uid: string) {
return this.subscriptionsRepository.delete(uid);
}
}

View File

@ -15,26 +15,76 @@ export default class StripeService {
}
public async createCheckoutSession(subscription: Subscription) {
const priceId =
subscription.type === "STANDARD"
? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID
: this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID;
return this.client.checkout.sessions.create({
mode: "subscription",
payment_method_types: ["card", "paypal"],
billing_address_collection: "auto",
line_items: [
{
price: priceId,
quantity: subscription.type === "STANDARD" ? subscription.nb_seats : 1,
const priceId = subscription.type === "STANDARD" ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID;
return this.client.checkout.sessions.create({
mode: "subscription",
payment_method_types: ["card", "paypal"],
billing_address_collection: "auto",
line_items: [
{
price: priceId,
quantity: subscription.type === "STANDARD" ? subscription.nb_seats : 1,
},
],
success_url: this.variables.APP_HOST + "/subscription/success",
cancel_url: this.variables.APP_HOST + "/subscription/error",
metadata: {
subscription: JSON.stringify(subscription),
},
],
success_url: this.variables.APP_HOST + "/subscription/success",
cancel_url: this.variables.APP_HOST + "/subscription/error",
metadata: {
subscription: JSON.stringify(subscription),
},
});
});
}
public async createCheckoutSessionUpdate(uid: string, subscription: Subscription) {
// return this.client.checkout.sessions.create({
// mode: "payment",
// payment_method_types: ["card", "paypal"],
// billing_address_collection: "auto",
// success_url: this.variables.APP_HOST + "/subscription/success",
// cancel_url: this.variables.APP_HOST + "/subscription/error",
// metadata: {
// subscription: JSON.stringify(subscription),
// },
// });
// const priceId =
// subscription.type === "STANDARD"
// ? this.variables.STRIPE_STANDARD_SUBSCRIPTION_PRICE_ID
// : this.variables.STRIPE_UNLIMITED_SUBSCRIPTION_PRICE_ID;
// return this.client.checkout.sessions.create({
// mode: "subscription",
// payment_method_types: ["card", "paypal"],
// billing_address_collection: "auto",
// line_items: [
// {
// price: priceId,
// quantity: subscription.type === "STANDARD" ? subscription.nb_seats : 1,
// },
// ],
// success_url: this.variables.APP_HOST + "/subscription/success",
// cancel_url: this.variables.APP_HOST + "/subscription/error",
// metadata: {
// subscription: JSON.stringify(subscription),
// },
// });
// const subscriptions = await this.client.subscriptions.retrieve(uid);
// const itemId = subscriptions.items.data[0]?.id;
// return await this.client.subscriptions.update(uid, {
// items: [
// {
// id: itemId,
// price: priceId,
// quantity: subscription.nb_seats,
// },
// ],
// });
}
public async createClientPortalSession(subscriptionId: string) {