@intlayer/backend 3.2.2 → 3.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/controllers/stripe.controller.cjs +110 -5
- package/dist/cjs/controllers/stripe.controller.cjs.map +1 -1
- package/dist/cjs/index.cjs +9 -2
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/middlewares/request.middleware.cjs +0 -4
- package/dist/cjs/middlewares/request.middleware.cjs.map +1 -1
- package/dist/cjs/routes/stripe.routes.cjs +8 -2
- package/dist/cjs/routes/stripe.routes.cjs.map +1 -1
- package/dist/cjs/schemas/plans.schema.cjs +27 -5
- package/dist/cjs/schemas/plans.schema.cjs.map +1 -1
- package/dist/cjs/services/organization.service.cjs +5 -27
- package/dist/cjs/services/organization.service.cjs.map +1 -1
- package/dist/cjs/services/subscription.service.cjs +110 -116
- package/dist/cjs/services/subscription.service.cjs.map +1 -1
- package/dist/cjs/types/plan.types.cjs.map +1 -1
- package/dist/cjs/utils/errors/errorCodes.cjs +107 -3
- package/dist/cjs/utils/errors/errorCodes.cjs.map +1 -1
- package/dist/cjs/utils/plan.cjs +3 -3
- package/dist/cjs/utils/plan.cjs.map +1 -1
- package/dist/cjs/webhooks/stripe.webhook.cjs +102 -89
- package/dist/cjs/webhooks/stripe.webhook.cjs.map +1 -1
- package/dist/esm/controllers/stripe.controller.mjs +99 -5
- package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
- package/dist/esm/index.mjs +10 -3
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/middlewares/request.middleware.mjs +0 -4
- package/dist/esm/middlewares/request.middleware.mjs.map +1 -1
- package/dist/esm/routes/stripe.routes.mjs +12 -3
- package/dist/esm/routes/stripe.routes.mjs.map +1 -1
- package/dist/esm/schemas/plans.schema.mjs +27 -5
- package/dist/esm/schemas/plans.schema.mjs.map +1 -1
- package/dist/esm/services/organization.service.mjs +5 -25
- package/dist/esm/services/organization.service.mjs.map +1 -1
- package/dist/esm/services/subscription.service.mjs +101 -120
- package/dist/esm/services/subscription.service.mjs.map +1 -1
- package/dist/esm/utils/errors/errorCodes.mjs +107 -3
- package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
- package/dist/esm/utils/plan.mjs +3 -3
- package/dist/esm/utils/plan.mjs.map +1 -1
- package/dist/esm/webhooks/stripe.webhook.mjs +103 -90
- package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
- package/dist/types/controllers/stripe.controller.d.ts +14 -0
- package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/routes/stripe.routes.d.ts +6 -1
- package/dist/types/routes/stripe.routes.d.ts.map +1 -1
- package/dist/types/schemas/plans.schema.d.ts.map +1 -1
- package/dist/types/services/organization.service.d.ts +1 -8
- package/dist/types/services/organization.service.d.ts.map +1 -1
- package/dist/types/services/subscription.service.d.ts +5 -21
- package/dist/types/services/subscription.service.d.ts.map +1 -1
- package/dist/types/types/plan.types.d.ts +2 -1
- package/dist/types/types/plan.types.d.ts.map +1 -1
- package/dist/types/utils/errors/errorCodes.d.ts +104 -0
- package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
- package/dist/types/webhooks/stripe.webhook.d.ts +7 -2
- package/dist/types/webhooks/stripe.webhook.d.ts.map +1 -1
- package/package.json +10 -10
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,41 +17,86 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var stripe_controller_exports = {};
|
|
20
30
|
__export(stripe_controller_exports, {
|
|
31
|
+
cancelSubscription: () => cancelSubscription,
|
|
21
32
|
getSubscription: () => getSubscription
|
|
22
33
|
});
|
|
23
34
|
module.exports = __toCommonJS(stripe_controller_exports);
|
|
24
|
-
var
|
|
35
|
+
var subscriptionService = __toESM(require('./../services/subscription.service.cjs'), 1);
|
|
25
36
|
var import_errors = require('./../utils/errors/index.cjs');
|
|
37
|
+
var import_plan = require('./../utils/plan.cjs');
|
|
26
38
|
var import_responseData = require('./../utils/responseData.cjs');
|
|
39
|
+
var import_express_intlayer = require("express-intlayer");
|
|
27
40
|
var import_stripe = require("stripe");
|
|
41
|
+
const stripe = new import_stripe.Stripe(process.env.STRIPE_SECRET_KEY);
|
|
28
42
|
const getSubscription = async (req, res) => {
|
|
29
43
|
try {
|
|
30
|
-
const stripe = new import_stripe.Stripe(process.env.STRIPE_SECRET_KEY);
|
|
31
44
|
const { organization, user } = res.locals;
|
|
32
45
|
const { priceId } = req.body;
|
|
33
46
|
if (!organization) {
|
|
34
47
|
import_errors.ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_NOT_FOUND");
|
|
35
48
|
return;
|
|
36
49
|
}
|
|
37
|
-
|
|
50
|
+
if (!user) {
|
|
51
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_FOUND");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!organization.membersIds.map(String).includes(String(user._id))) {
|
|
55
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(
|
|
56
|
+
res,
|
|
57
|
+
"USER_NOT_ORGANIZATION_MEMBER"
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!organization.adminsIds.map(String).includes(String(user._id))) {
|
|
62
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(
|
|
63
|
+
res,
|
|
64
|
+
"USER_NOT_ORGANIZATION_ADMIN"
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const { period, type } = (0, import_plan.retrievePlanInformation)(priceId);
|
|
69
|
+
if (organization.plan?.subscriptionId || organization.plan?.type === type && organization.plan?.period === period) {
|
|
70
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(res, "ALREADY_SUBSCRIBED", {
|
|
71
|
+
organizationId: organization._id
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let customerId = organization.plan?.customerId;
|
|
38
76
|
if (!customerId) {
|
|
39
77
|
const customer = await stripe.customers.create({
|
|
40
|
-
metadata: {
|
|
78
|
+
metadata: {
|
|
79
|
+
organizationId: String(organization._id),
|
|
80
|
+
userId: String(user._id),
|
|
81
|
+
// Include the locale for potential localization
|
|
82
|
+
locale: res.locals.locale
|
|
83
|
+
}
|
|
41
84
|
});
|
|
42
85
|
customerId = customer.id;
|
|
43
|
-
await (0, import_organization.saveStripeCustomerId)(organization, customerId);
|
|
44
86
|
}
|
|
45
87
|
const subscription = await stripe.subscriptions.create({
|
|
46
88
|
customer: customerId,
|
|
89
|
+
// Associate the subscription with the customer
|
|
47
90
|
items: [{ price: priceId }],
|
|
91
|
+
// Set the price ID for the subscription
|
|
48
92
|
expand: ["latest_invoice.payment_intent"],
|
|
93
|
+
// Expand to get payment intent details
|
|
49
94
|
payment_settings: {
|
|
50
95
|
payment_method_types: ["card"]
|
|
96
|
+
// Specify payment method types
|
|
51
97
|
},
|
|
52
98
|
payment_behavior: "default_incomplete"
|
|
99
|
+
// Create the subscription in an incomplete state until payment is confirmed
|
|
53
100
|
});
|
|
54
101
|
if (!subscription) {
|
|
55
102
|
import_errors.ErrorHandler.handleGenericErrorResponse(
|
|
@@ -66,8 +113,10 @@ const getSubscription = async (req, res) => {
|
|
|
66
113
|
const responseData = (0, import_responseData.formatResponse)({
|
|
67
114
|
data: {
|
|
68
115
|
subscriptionId: subscription.id,
|
|
116
|
+
// Retrieve the client secret from the payment intent to complete payment on the client side
|
|
69
117
|
clientSecret: subscription.latest_invoice.payment_intent?.client_secret ?? "",
|
|
70
118
|
status: subscription.status
|
|
119
|
+
// Subscription status (e.g., 'incomplete', 'active')
|
|
71
120
|
}
|
|
72
121
|
});
|
|
73
122
|
res.json(responseData);
|
|
@@ -77,8 +126,64 @@ const getSubscription = async (req, res) => {
|
|
|
77
126
|
return;
|
|
78
127
|
}
|
|
79
128
|
};
|
|
129
|
+
const cancelSubscription = async (_req, res) => {
|
|
130
|
+
try {
|
|
131
|
+
const { organization, user } = res.locals;
|
|
132
|
+
if (!organization) {
|
|
133
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(res, "ORGANIZATION_NOT_FOUND");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!user) {
|
|
137
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_FOUND");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!organization.adminsIds.map(String).includes(String(user._id))) {
|
|
141
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(
|
|
142
|
+
res,
|
|
143
|
+
"USER_NOT_ORGANIZATION_ADMIN"
|
|
144
|
+
);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!organization.plan?.subscriptionId) {
|
|
148
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(
|
|
149
|
+
res,
|
|
150
|
+
"ORGANIZATION_PLAN_NOT_FOUND"
|
|
151
|
+
);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
await stripe.subscriptions.cancel(organization.plan.subscriptionId);
|
|
155
|
+
const plan = await subscriptionService.cancelSubscription(
|
|
156
|
+
organization.plan.subscriptionId,
|
|
157
|
+
String(organization._id)
|
|
158
|
+
);
|
|
159
|
+
if (!plan) {
|
|
160
|
+
import_errors.ErrorHandler.handleGenericErrorResponse(
|
|
161
|
+
res,
|
|
162
|
+
"ORGANIZATION_PLAN_NOT_FOUND"
|
|
163
|
+
);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const formattedPlan = (0, import_responseData.formatResponse)({
|
|
167
|
+
message: (0, import_express_intlayer.t)({
|
|
168
|
+
en: "Subscription cancelled successfully",
|
|
169
|
+
fr: "Souscription annul\xE9e avec succ\xE8s",
|
|
170
|
+
es: "Suscripci\xF3n cancelada con \xE9xito"
|
|
171
|
+
}),
|
|
172
|
+
description: (0, import_express_intlayer.t)({
|
|
173
|
+
en: "Your subscription has been cancelled successfully",
|
|
174
|
+
fr: "Votre souscription a \xE9t\xE9 annul\xE9e avec succ\xE8s",
|
|
175
|
+
es: "Su suscripci\xF3n ha sido cancelada con \xE9xito"
|
|
176
|
+
}),
|
|
177
|
+
data: plan
|
|
178
|
+
});
|
|
179
|
+
res.json(formattedPlan);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
import_errors.ErrorHandler.handleAppErrorResponse(res, error);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
80
184
|
// Annotate the CommonJS export names for ESM import in node:
|
|
81
185
|
0 && (module.exports = {
|
|
186
|
+
cancelSubscription,
|
|
82
187
|
getSubscription
|
|
83
188
|
});
|
|
84
189
|
//# sourceMappingURL=stripe.controller.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/controllers/stripe.controller.ts"],"sourcesContent":["import { ResponseWithInformation } from '@middlewares/sessionAuth.middleware';\nimport { saveStripeCustomerId } from '@services/organization.service';\nimport { ErrorHandler, AppError } from '@utils/errors';\nimport { formatResponse, ResponseData } from '@utils/responseData';\nimport { Request } from 'express';\nimport { Stripe } from 'stripe';\n\nexport type GetCheckoutSessionBody = {\n organizationId: string;\n priceId: string;\n};\n\ntype CheckoutSessionData = {\n subscriptionId: string;\n clientSecret: string;\n status: Stripe.Subscription.Status;\n};\n\nexport type GetCheckoutSessionResult = ResponseData<CheckoutSessionData>;\n\nexport const getSubscription = async (\n req: Request<undefined, undefined, GetCheckoutSessionBody>,\n res: ResponseWithInformation<GetCheckoutSessionResult>\n): Promise<void> => {\n try {\n const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n const { organization, user } = res.locals;\n const { priceId } = req.body;\n\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND');\n return;\n }\n\n // Fetch or create a Stripe customer for the organization\n let { customerId } = organization.plan;\n\n if (!customerId) {\n const customer = await stripe.customers.create({\n metadata: { organizationId: String(organization._id) },\n });\n customerId = customer.id;\n await saveStripeCustomerId(organization, customerId);\n }\n\n const subscription = await stripe.subscriptions.create({\n customer: customerId,\n items: [{ price: priceId }],\n expand: ['latest_invoice.payment_intent'],\n payment_settings: {\n payment_method_types: ['card'],\n },\n payment_behavior: 'default_incomplete',\n });\n\n if (!subscription) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n return;\n }\n\n const responseData = formatResponse<CheckoutSessionData>({\n data: {\n subscriptionId: subscription.id,\n clientSecret:\n (\n (subscription.latest_invoice as Stripe.Invoice)\n .payment_intent as Stripe.PaymentIntent\n )?.client_secret ?? '',\n status: subscription.status,\n },\n });\n\n res.json(responseData);\n\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,0BAAqC;AACrC,oBAAuC;AACvC,0BAA6C;AAE7C,oBAAuB;AAehB,MAAM,kBAAkB,OAC7B,KACA,QACkB;AAClB,MAAI;AACF,UAAM,SAAS,IAAI,qBAAO,QAAQ,IAAI,iBAAkB;AACxD,UAAM,EAAE,cAAc,KAAK,IAAI,IAAI;AACnC,UAAM,EAAE,QAAQ,IAAI,IAAI;AAExB,QAAI,CAAC,cAAc;AACjB,iCAAa,2BAA2B,KAAK,wBAAwB;AACrE;AAAA,IACF;AAGA,QAAI,EAAE,WAAW,IAAI,aAAa;AAElC,QAAI,CAAC,YAAY;AACf,YAAM,WAAW,MAAM,OAAO,UAAU,OAAO;AAAA,QAC7C,UAAU,EAAE,gBAAgB,OAAO,aAAa,GAAG,EAAE;AAAA,MACvD,CAAC;AACD,mBAAa,SAAS;AACtB,gBAAM,0CAAqB,cAAc,UAAU;AAAA,IACrD;AAEA,UAAM,eAAe,MAAM,OAAO,cAAc,OAAO;AAAA,MACrD,UAAU;AAAA,MACV,OAAO,CAAC,EAAE,OAAO,QAAQ,CAAC;AAAA,MAC1B,QAAQ,CAAC,+BAA+B;AAAA,MACxC,kBAAkB;AAAA,QAChB,sBAAsB,CAAC,MAAM;AAAA,MAC/B;AAAA,MACA,kBAAkB;AAAA,IACpB,CAAC;AAED,QAAI,CAAC,cAAc;AACjB,iCAAa;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA;AAAA,IACF;AAEA,UAAM,mBAAe,oCAAoC;AAAA,MACvD,MAAM;AAAA,QACJ,gBAAgB,aAAa;AAAA,QAC7B,cAEK,aAAa,eACX,gBACF,iBAAiB;AAAA,QACtB,QAAQ,aAAa;AAAA,MACvB;AAAA,IACF,CAAC;AAED,QAAI,KAAK,YAAY;AAErB;AAAA,EACF,SAAS,OAAO;AACd,+BAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/controllers/stripe.controller.ts"],"sourcesContent":["import { ResponseWithInformation } from '@middlewares/sessionAuth.middleware';\nimport * as subscriptionService from '@services/subscription.service';\nimport { ErrorHandler, AppError } from '@utils/errors';\nimport { retrievePlanInformation } from '@utils/plan';\nimport { formatResponse, ResponseData } from '@utils/responseData';\nimport { Request } from 'express';\nimport { t } from 'express-intlayer';\nimport { Locales } from 'intlayer';\nimport { Stripe } from 'stripe';\nimport type { Organization } from '@/types/organization.types';\n\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n\nexport type GetCheckoutSessionBody = {\n organizationId: string;\n priceId: string;\n};\n\ntype CheckoutSessionData = {\n subscriptionId: string;\n clientSecret: string;\n status: Stripe.Subscription.Status;\n};\n\nexport type GetCheckoutSessionResult = ResponseData<CheckoutSessionData>;\n\n/**\n * Handles subscription creation or update with Stripe and returns a ClientSecret.\n * @param req - Express request object.\n * @param res - Express response object.\n */\nexport const getSubscription = async (\n req: Request<undefined, undefined, GetCheckoutSessionBody>,\n res: ResponseWithInformation<GetCheckoutSessionResult>\n): Promise<void> => {\n try {\n // Extract organization and user from response locals (set by authentication middleware)\n const { organization, user } = res.locals;\n // Get the price ID (Stripe Price ID) from the request body\n const { priceId } = req.body;\n\n // Validate that the organization exists\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND');\n return;\n }\n\n // Validate that the user exists\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n // Ensure the user is a member of the organization\n if (!organization.membersIds.map(String).includes(String(user._id))) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'USER_NOT_ORGANIZATION_MEMBER'\n );\n return;\n }\n\n // Ensure the user is an admin of the organization\n if (!organization.adminsIds.map(String).includes(String(user._id))) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'USER_NOT_ORGANIZATION_ADMIN'\n );\n return;\n }\n\n const { period, type } = retrievePlanInformation(priceId);\n\n if (\n organization.plan?.subscriptionId ||\n (organization.plan?.type === type && organization.plan?.period === period)\n ) {\n ErrorHandler.handleGenericErrorResponse(res, 'ALREADY_SUBSCRIBED', {\n organizationId: organization._id,\n });\n return;\n }\n\n // Attempt to retrieve the Stripe customer ID from the organization's plan\n let customerId = organization.plan?.customerId;\n\n if (!customerId) {\n // If no customer ID exists, create a new Stripe customer for the organization\n const customer = await stripe.customers.create({\n metadata: {\n organizationId: String(organization._id),\n userId: String(user._id),\n // Include the locale for potential localization\n locale: (res.locals as unknown as { locale: Locales }).locale,\n },\n });\n customerId = customer.id;\n }\n\n // If no subscription exists, create a new one\n const subscription = await stripe.subscriptions.create({\n customer: customerId, // Associate the subscription with the customer\n items: [{ price: priceId }], // Set the price ID for the subscription\n expand: ['latest_invoice.payment_intent'], // Expand to get payment intent details\n payment_settings: {\n payment_method_types: ['card'], // Specify payment method types\n },\n payment_behavior: 'default_incomplete', // Create the subscription in an incomplete state until payment is confirmed\n });\n\n // Handle subscription creation failure\n if (!subscription) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'SUBSCRIPTION_CREATION_FAILED',\n {\n user,\n organization,\n priceId,\n }\n );\n return;\n }\n\n // Prepare the response data with subscription details\n const responseData = formatResponse<CheckoutSessionData>({\n data: {\n subscriptionId: subscription.id,\n // Retrieve the client secret from the payment intent to complete payment on the client side\n clientSecret:\n (\n (subscription.latest_invoice as Stripe.Invoice)\n .payment_intent as Stripe.PaymentIntent\n )?.client_secret ?? '',\n status: subscription.status, // Subscription status (e.g., 'incomplete', 'active')\n },\n });\n\n // Send the response back to the client\n res.json(responseData);\n\n return;\n } catch (error) {\n // Handle any errors that occur during the process\n\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\ntype CancelSubscriptionData = Organization['plan'];\n\ntype CancelSubscriptionResult = ResponseData<CancelSubscriptionData>;\n\n/**\n * Cancels a subscription for an organization.\n * @param _req - Express request object.\n * @param res - Express response object.\n */\nexport const cancelSubscription = async (\n _req: Request,\n res: ResponseWithInformation<CancelSubscriptionResult>\n): Promise<void> => {\n try {\n // Extract the organization and user from the response locals\n // These are typically set by authentication middleware earlier in the request pipeline\n const { organization, user } = res.locals;\n\n // Validate that the organization exists\n if (!organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'ORGANIZATION_NOT_FOUND');\n return;\n }\n\n // Validate that the user exists\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_FOUND');\n return;\n }\n\n // Check if the user is an admin of the organization\n if (!organization.adminsIds.map(String).includes(String(user._id))) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'USER_NOT_ORGANIZATION_ADMIN'\n );\n return;\n }\n\n // Check if the organization has an active subscription to cancel\n if (!organization.plan?.subscriptionId) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n return;\n }\n\n // Cancel the subscription on Stripe immediately using the subscription ID\n await stripe.subscriptions.cancel(organization.plan.subscriptionId);\n\n // Update the organization's plan in the database to reflect the cancellation\n const plan = await subscriptionService.cancelSubscription(\n organization.plan.subscriptionId,\n String(organization._id)\n );\n\n // If the plan could not be updated in the database, handle the error\n if (!plan) {\n ErrorHandler.handleGenericErrorResponse(\n res,\n 'ORGANIZATION_PLAN_NOT_FOUND'\n );\n return;\n }\n\n // Prepare a formatted response with a success message and the updated plan data\n const formattedPlan = formatResponse<CancelSubscriptionData>({\n message: t({\n en: 'Subscription cancelled successfully',\n fr: 'Souscription annulée avec succès',\n es: 'Suscripción cancelada con éxito',\n }),\n description: t({\n en: 'Your subscription has been cancelled successfully',\n fr: 'Votre souscription a été annulée avec succès',\n es: 'Su suscripción ha sido cancelada con éxito',\n }),\n data: plan!,\n });\n\n // Send the response back to the client\n res.json(formattedPlan);\n } catch (error) {\n // Handle any errors that occur during the cancellation process\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n }\n};\n"],"mappings":"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","names":[]}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -63,7 +63,11 @@ app.use((0, import_cookie_parser.default)());
|
|
|
63
63
|
app.use((0, import_express_intlayer.intlayer)());
|
|
64
64
|
const isDev = env === "development";
|
|
65
65
|
(0, import_connectDB.connectDB)();
|
|
66
|
-
app.post(
|
|
66
|
+
app.post(
|
|
67
|
+
"/webhook/stripe",
|
|
68
|
+
import_express.default.raw({ type: "application/json" }),
|
|
69
|
+
import_stripe2.stripeWebhook
|
|
70
|
+
);
|
|
67
71
|
app.use((0, import_compression.default)());
|
|
68
72
|
app.use(import_express.default.json({ limit: "50mb" }));
|
|
69
73
|
app.use(import_express.default.urlencoded({ extended: true }));
|
|
@@ -76,7 +80,10 @@ const corsOptions = {
|
|
|
76
80
|
"Content-Type",
|
|
77
81
|
"credentials",
|
|
78
82
|
"cache-control",
|
|
79
|
-
"Access-Control-Allow-Origin"
|
|
83
|
+
"Access-Control-Allow-Origin",
|
|
84
|
+
"private-state-token-redemption",
|
|
85
|
+
"private-state-token-issuance",
|
|
86
|
+
"browsing-topics"
|
|
80
87
|
],
|
|
81
88
|
exposedHeaders: [""],
|
|
82
89
|
preflightContinue: false,
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["/* eslint-disable import/order */\n\n// Libraries\nimport compression from 'compression';\nimport cookieParser from 'cookie-parser';\nimport cors, { type CorsOptions } from 'cors';\nimport dotenv from 'dotenv';\nimport express, {
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["/* eslint-disable import/order */\n\n// Libraries\nimport compression from 'compression';\nimport cookieParser from 'cookie-parser';\nimport cors, { type CorsOptions } from 'cors';\nimport dotenv from 'dotenv';\nimport express, { type Express } from 'express';\nimport { intlayer, t } from 'express-intlayer';\nimport helmet from 'helmet';\n\n// Middlewares\nimport {\n attachOAuthInstance,\n authenticateOAuth2,\n RequestWithOAuth2Information,\n} from '@middlewares/oAuth2.middleware';\nimport { logAPIRequestURL } from '@middlewares/request.middleware';\nimport {\n checkUser,\n checkOrganization,\n checkProject,\n checkAdmin,\n ResponseWithInformation,\n} from '@middlewares/sessionAuth.middleware';\n\n// Routes\nimport { dictionaryRouter } from '@routes/dictionary.routes';\nimport { organizationRouter } from '@routes/organization.routes';\nimport { projectRouter } from '@routes/project.routes';\nimport { sessionAuthRouter } from '@routes/sessionAuth.routes';\nimport { userRouter } from '@routes/user.routes';\nimport { stripeRouter } from '@routes/stripe.routes';\n\n// Webhooks\nimport { stripeWebhook } from '@webhooks/stripe.webhook';\n\n// Controllers\nimport { getOAuth2Token } from '@controllers/oAuth2.controller';\nimport {\n getSessionInformation,\n setCSRFToken,\n} from '@controllers/sessionAuth.controller';\n\n// Utils\nimport { doubleCsrfProtection } from '@utils/CSRF';\nimport { connectDB } from '@utils/mongoDB/connectDB';\n\n// Logger\nimport { logger } from './logger';\n\nconst app: Express = express();\n\napp.disable('x-powered-by'); // Disabled to prevent attackers from knowing that the app is running Express\napp.use(helmet());\n\n// Environment variables\nconst env = app.get('env');\n\nlogger.info(`run as ${env}`);\n\ndotenv.config({ path: ['.env', `.env.${env}`] });\n\n// Parse incoming requests with cookies\napp.use(cookieParser());\n\n// Load internationalization request handler\napp.use(intlayer());\n\nconst isDev = env === 'development';\n\n// Connect to MongoDB\nconnectDB();\n\n// Stripe\napp.post(\n '/webhook/stripe',\n express.raw({ type: 'application/json' }),\n stripeWebhook\n);\n\n// Compress all HTTP responses\napp.use(compression());\n\n// Parse incoming requests with JSON payloads\napp.use(express.json({ limit: '50mb' }));\n\n// Parse incoming requests with urlencoded payloads\napp.use(express.urlencoded({ extended: true }));\n\n// CORS\nconst whitelist: string[] = [process.env.CLIENT_URL!];\nconst corsOptions: CorsOptions = {\n origin: whitelist,\n credentials: true,\n allowedHeaders: [\n 'authorization',\n 'Content-Type',\n 'credentials',\n 'cache-control',\n 'Access-Control-Allow-Origin',\n 'private-state-token-redemption',\n 'private-state-token-issuance',\n 'browsing-topics',\n ],\n\n exposedHeaders: [''],\n preflightContinue: false,\n methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',\n};\napp.use(cors(corsOptions));\nlogger.info('url whitelist : ', whitelist.join(', '));\n\n// Liveness check\napp.get('/', (_req, res) => {\n res.send(\n t({\n en: 'Ok - locale: en',\n fr: 'Ok - locale: fr',\n es: 'Ok - locale: es',\n })\n );\n});\n\n// middleware - jwt & session auth\napp.use(/(.*)/, checkUser);\napp.use(/(.*)/, checkOrganization);\napp.use(/(.*)/, checkProject);\napp.use(/(.*)/, checkAdmin);\n\n// debug\nif (isDev) {\n app.use(logAPIRequestURL);\n}\n\n// Sessions\napp.get('/session', getSessionInformation);\napp.use('/api/auth', sessionAuthRouter);\n\n// CSRF\napp.get('/csrf-token', setCSRFToken);\n\n// oAuth2\napp.use(/(.*)/, attachOAuthInstance);\napp.post('/oauth2/token', getOAuth2Token); // Route to get the token\napp.use(/(.*)/, (req, res, next) => {\n // If the request is not already authenticated check the oAuth2 token\n if (!res.locals.authType) {\n return authenticateOAuth2(\n req as RequestWithOAuth2Information,\n res as ResponseWithInformation,\n next\n );\n }\n next();\n});\n\n// CSRF protection\napp.use(/(.*)/, (req, res, next) => {\n // If the request is authenticated using the session auth check the CSRF token\n if (res.locals.authType === 'session') {\n return doubleCsrfProtection(req, res, next);\n }\n next();\n});\n\n// Routes\napp.use('/api/user', userRouter);\napp.use('/api/organization', organizationRouter);\napp.use('/api/project', projectRouter);\napp.use('/api/dictionary', dictionaryRouter);\napp.use('/api/stripe', stripeRouter);\n\n// Server\napp.listen(process.env.PORT, () => {\n logger.info(`Listening on port ${process.env.PORT}`);\n});\n\n// Export tu use as serverless function\nexport default app;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,yBAAwB;AACxB,2BAAyB;AACzB,kBAAuC;AACvC,oBAAmB;AACnB,qBAAsC;AACtC,8BAA4B;AAC5B,oBAAmB;AAGnB,oBAIO;AACP,qBAAiC;AACjC,yBAMO;AAGP,wBAAiC;AACjC,0BAAmC;AACnC,qBAA8B;AAC9B,IAAAA,sBAAkC;AAClC,kBAA2B;AAC3B,oBAA6B;AAG7B,IAAAC,iBAA8B;AAG9B,IAAAC,iBAA+B;AAC/B,IAAAF,sBAGO;AAGP,kBAAqC;AACrC,uBAA0B;AAG1B,oBAAuB;AAEvB,MAAM,UAAe,eAAAG,SAAQ;AAE7B,IAAI,QAAQ,cAAc;AAC1B,IAAI,QAAI,cAAAC,SAAO,CAAC;AAGhB,MAAM,MAAM,IAAI,IAAI,KAAK;AAEzB,qBAAO,KAAK,UAAU,GAAG,EAAE;AAE3B,cAAAC,QAAO,OAAO,EAAE,MAAM,CAAC,QAAQ,QAAQ,GAAG,EAAE,EAAE,CAAC;AAG/C,IAAI,QAAI,qBAAAC,SAAa,CAAC;AAGtB,IAAI,QAAI,kCAAS,CAAC;AAElB,MAAM,QAAQ,QAAQ;AAAA,IAGtB,4BAAU;AAGV,IAAI;AAAA,EACF;AAAA,EACA,eAAAH,QAAQ,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAAA,EACxC;AACF;AAGA,IAAI,QAAI,mBAAAI,SAAY,CAAC;AAGrB,IAAI,IAAI,eAAAJ,QAAQ,KAAK,EAAE,OAAO,OAAO,CAAC,CAAC;AAGvC,IAAI,IAAI,eAAAA,QAAQ,WAAW,EAAE,UAAU,KAAK,CAAC,CAAC;AAG9C,MAAM,YAAsB,CAAC,QAAQ,IAAI,UAAW;AACpD,MAAM,cAA2B;AAAA,EAC/B,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,gBAAgB;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EAEA,gBAAgB,CAAC,EAAE;AAAA,EACnB,mBAAmB;AAAA,EACnB,SAAS;AACX;AACA,IAAI,QAAI,YAAAK,SAAK,WAAW,CAAC;AACzB,qBAAO,KAAK,oBAAoB,UAAU,KAAK,IAAI,CAAC;AAGpD,IAAI,IAAI,KAAK,CAAC,MAAM,QAAQ;AAC1B,MAAI;AAAA,QACF,2BAAE;AAAA,MACA,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IACN,CAAC;AAAA,EACH;AACF,CAAC;AAGD,IAAI,IAAI,QAAQ,4BAAS;AACzB,IAAI,IAAI,QAAQ,oCAAiB;AACjC,IAAI,IAAI,QAAQ,+BAAY;AAC5B,IAAI,IAAI,QAAQ,6BAAU;AAG1B,IAAI,OAAO;AACT,MAAI,IAAI,+BAAgB;AAC1B;AAGA,IAAI,IAAI,YAAY,yCAAqB;AACzC,IAAI,IAAI,aAAa,qCAAiB;AAGtC,IAAI,IAAI,eAAe,gCAAY;AAGnC,IAAI,IAAI,QAAQ,iCAAmB;AACnC,IAAI,KAAK,iBAAiB,6BAAc;AACxC,IAAI,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS;AAElC,MAAI,CAAC,IAAI,OAAO,UAAU;AACxB,eAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,OAAK;AACP,CAAC;AAGD,IAAI,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS;AAElC,MAAI,IAAI,OAAO,aAAa,WAAW;AACrC,eAAO,kCAAqB,KAAK,KAAK,IAAI;AAAA,EAC5C;AACA,OAAK;AACP,CAAC;AAGD,IAAI,IAAI,aAAa,sBAAU;AAC/B,IAAI,IAAI,qBAAqB,sCAAkB;AAC/C,IAAI,IAAI,gBAAgB,4BAAa;AACrC,IAAI,IAAI,mBAAmB,kCAAgB;AAC3C,IAAI,IAAI,eAAe,0BAAY;AAGnC,IAAI,OAAO,QAAQ,IAAI,MAAM,MAAM;AACjC,uBAAO,KAAK,qBAAqB,QAAQ,IAAI,IAAI,EAAE;AACrD,CAAC;AAGD,IAAO,cAAQ;","names":["import_sessionAuth","import_stripe","import_oAuth2","express","helmet","dotenv","cookieParser","compression","cors"]}
|
|
@@ -21,7 +21,6 @@ __export(request_middleware_exports, {
|
|
|
21
21
|
logAPIRequestURL: () => logAPIRequestURL
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(request_middleware_exports);
|
|
24
|
-
var import_logger = require('./../logger/index.cjs');
|
|
25
24
|
const logAPIRequestURL = (req, res, next) => {
|
|
26
25
|
const queryDetails = {
|
|
27
26
|
params: req.params,
|
|
@@ -29,9 +28,6 @@ const logAPIRequestURL = (req, res, next) => {
|
|
|
29
28
|
body: req.body,
|
|
30
29
|
locals: res.locals
|
|
31
30
|
};
|
|
32
|
-
import_logger.logger.info(
|
|
33
|
-
`API Request - ${req.method} - ${req.originalUrl} - ${JSON.stringify(queryDetails, null, 2)}`
|
|
34
|
-
);
|
|
35
31
|
next();
|
|
36
32
|
};
|
|
37
33
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/middlewares/request.middleware.ts"],"sourcesContent":["import { logger } from '@logger';\nimport type { Request, NextFunction } from 'express';\nimport type { ResponseWithInformation } from './sessionAuth.middleware';\n\nexport const logAPIRequestURL = (\n req: Request,\n res: ResponseWithInformation,\n next: NextFunction\n): void => {\n const queryDetails = {\n params: req.params,\n query: req.query,\n body: req.body,\n locals: res.locals,\n };\n\n logger.info(\n
|
|
1
|
+
{"version":3,"sources":["../../../src/middlewares/request.middleware.ts"],"sourcesContent":["import { logger } from '@logger';\nimport type { Request, NextFunction } from 'express';\nimport type { ResponseWithInformation } from './sessionAuth.middleware';\n\nexport const logAPIRequestURL = (\n req: Request,\n res: ResponseWithInformation,\n next: NextFunction\n): void => {\n const queryDetails = {\n params: req.params,\n query: req.query,\n body: req.body,\n locals: res.locals,\n };\n\n // logger.info(\n // `API Request - ${req.method} - ${req.originalUrl} - ${JSON.stringify(queryDetails, null, 2)}`\n // );\n\n next();\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,mBAAmB,CAC9B,KACA,KACA,SACS;AACT,QAAM,eAAe;AAAA,IACnB,QAAQ,IAAI;AAAA,IACZ,OAAO,IAAI;AAAA,IACX,MAAM,IAAI;AAAA,IACV,QAAQ,IAAI;AAAA,EACd;AAMA,OAAK;AACP;","names":[]}
|
|
@@ -27,13 +27,19 @@ var import_express = require("express");
|
|
|
27
27
|
const stripeRouter = (0, import_express.Router)();
|
|
28
28
|
const baseURL = `${process.env.BACKEND_URL}/api/stipe`;
|
|
29
29
|
const stripeRoutes = {
|
|
30
|
-
|
|
30
|
+
createSubscription: {
|
|
31
31
|
urlModel: "/create-subscription",
|
|
32
32
|
url: `${baseURL}/create-subscription`,
|
|
33
33
|
method: "POST"
|
|
34
|
+
},
|
|
35
|
+
cancelSubscription: {
|
|
36
|
+
urlModel: "/cancel-subscription",
|
|
37
|
+
url: `${baseURL}/cancel-subscription`,
|
|
38
|
+
method: "POST"
|
|
34
39
|
}
|
|
35
40
|
};
|
|
36
|
-
stripeRouter.post(stripeRoutes.
|
|
41
|
+
stripeRouter.post(stripeRoutes.createSubscription.urlModel, import_stripe.getSubscription);
|
|
42
|
+
stripeRouter.post(stripeRoutes.cancelSubscription.urlModel, import_stripe.cancelSubscription);
|
|
37
43
|
// Annotate the CommonJS export names for ESM import in node:
|
|
38
44
|
0 && (module.exports = {
|
|
39
45
|
stripeRouter,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/routes/stripe.routes.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../../src/routes/stripe.routes.ts"],"sourcesContent":["import {\n cancelSubscription,\n getSubscription,\n} from '@controllers/stripe.controller';\nimport { Router } from 'express';\nimport { Routes } from '@/types/Routes';\n\nexport const stripeRouter: Router = Router();\n\nconst baseURL = `${process.env.BACKEND_URL}/api/stipe`;\n\nexport const stripeRoutes = {\n createSubscription: {\n urlModel: '/create-subscription',\n url: `${baseURL}/create-subscription`,\n method: 'POST',\n },\n cancelSubscription: {\n urlModel: '/cancel-subscription',\n url: `${baseURL}/cancel-subscription`,\n method: 'POST',\n },\n} satisfies Routes;\n\nstripeRouter.post(stripeRoutes.createSubscription.urlModel, getSubscription);\n\nstripeRouter.post(stripeRoutes.cancelSubscription.urlModel, cancelSubscription);\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAGO;AACP,qBAAuB;AAGhB,MAAM,mBAAuB,uBAAO;AAE3C,MAAM,UAAU,GAAG,QAAQ,IAAI,WAAW;AAEnC,MAAM,eAAe;AAAA,EAC1B,oBAAoB;AAAA,IAClB,UAAU;AAAA,IACV,KAAK,GAAG,OAAO;AAAA,IACf,QAAQ;AAAA,EACV;AAAA,EACA,oBAAoB;AAAA,IAClB,UAAU;AAAA,IACV,KAAK,GAAG,OAAO;AAAA,IACf,QAAQ;AAAA,EACV;AACF;AAEA,aAAa,KAAK,aAAa,mBAAmB,UAAU,6BAAe;AAE3E,aAAa,KAAK,aAAa,mBAAmB,UAAU,gCAAkB;","names":[]}
|
|
@@ -27,22 +27,44 @@ const planSchema = new import_mongoose.Schema(
|
|
|
27
27
|
type: {
|
|
28
28
|
type: String,
|
|
29
29
|
required: true,
|
|
30
|
-
enum: ["
|
|
31
|
-
|
|
30
|
+
enum: ["PREMIUM", "ENTERPRISE"]
|
|
31
|
+
},
|
|
32
|
+
period: {
|
|
33
|
+
type: String,
|
|
34
|
+
required: true,
|
|
35
|
+
enum: ["MONTHLY", "YEARLY"],
|
|
36
|
+
default: "MONTHLY"
|
|
32
37
|
},
|
|
33
38
|
creatorId: {
|
|
34
39
|
type: import_mongoose.Schema.Types.ObjectId,
|
|
35
40
|
ref: "User",
|
|
36
41
|
required: true
|
|
37
42
|
},
|
|
43
|
+
subscriptionId: {
|
|
44
|
+
type: String,
|
|
45
|
+
required: true
|
|
46
|
+
},
|
|
47
|
+
customerId: {
|
|
48
|
+
type: String,
|
|
49
|
+
required: true
|
|
50
|
+
},
|
|
38
51
|
priceId: {
|
|
39
|
-
type: String
|
|
52
|
+
type: String,
|
|
53
|
+
required: true
|
|
40
54
|
},
|
|
41
55
|
status: {
|
|
42
56
|
type: String,
|
|
43
57
|
required: true,
|
|
44
|
-
enum: [
|
|
45
|
-
|
|
58
|
+
enum: [
|
|
59
|
+
"active",
|
|
60
|
+
"canceled",
|
|
61
|
+
"past_due",
|
|
62
|
+
"unpaid",
|
|
63
|
+
"incomplete",
|
|
64
|
+
"incomplete_expired",
|
|
65
|
+
"paused",
|
|
66
|
+
"trialing"
|
|
67
|
+
]
|
|
46
68
|
}
|
|
47
69
|
},
|
|
48
70
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/schemas/plans.schema.ts"],"sourcesContent":["import { Schema } from 'mongoose';\nimport { Plan } from '@/types/plan.types';\n\nexport const planSchema = new Schema<Plan>(\n {\n type: {\n type: String,\n required: true,\n enum: ['
|
|
1
|
+
{"version":3,"sources":["../../../src/schemas/plans.schema.ts"],"sourcesContent":["import { Schema } from 'mongoose';\nimport { Plan } from '@/types/plan.types';\n\nexport const planSchema = new Schema<Plan>(\n {\n type: {\n type: String,\n required: true,\n enum: ['PREMIUM', 'ENTERPRISE'],\n },\n period: {\n type: String,\n required: true,\n enum: ['MONTHLY', 'YEARLY'],\n default: 'MONTHLY',\n },\n creatorId: {\n type: Schema.Types.ObjectId,\n ref: 'User',\n required: true,\n },\n subscriptionId: {\n type: String,\n required: true,\n },\n customerId: {\n type: String,\n required: true,\n },\n priceId: {\n type: String,\n required: true,\n },\n status: {\n type: String,\n required: true,\n enum: [\n 'active',\n 'canceled',\n 'past_due',\n 'unpaid',\n 'incomplete',\n 'incomplete_expired',\n 'paused',\n 'trialing',\n ],\n },\n },\n {\n timestamps: true,\n }\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAuB;AAGhB,MAAM,aAAa,IAAI;AAAA,EAC5B;AAAA,IACE,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM,CAAC,WAAW,YAAY;AAAA,IAChC;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM,CAAC,WAAW,QAAQ;AAAA,MAC1B,SAAS;AAAA,IACX;AAAA,IACA,WAAW;AAAA,MACT,MAAM,uBAAO,MAAM;AAAA,MACnB,KAAK;AAAA,MACL,UAAU;AAAA,IACZ;AAAA,IACA,gBAAgB;AAAA,MACd,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,YAAY;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,YAAY;AAAA,EACd;AACF;","names":[]}
|
|
@@ -22,10 +22,8 @@ __export(organization_service_exports, {
|
|
|
22
22
|
createOrganization: () => createOrganization,
|
|
23
23
|
deleteOrganizationById: () => deleteOrganizationById,
|
|
24
24
|
findOrganizations: () => findOrganizations,
|
|
25
|
-
getOrganizationByCustomerId: () => getOrganizationByCustomerId,
|
|
26
25
|
getOrganizationById: () => getOrganizationById,
|
|
27
26
|
getOrganizationsByOwner: () => getOrganizationsByOwner,
|
|
28
|
-
saveStripeCustomerId: () => saveStripeCustomerId,
|
|
29
27
|
updateOrganizationById: () => updateOrganizationById,
|
|
30
28
|
updatePlan: () => updatePlan
|
|
31
29
|
});
|
|
@@ -66,11 +64,6 @@ const createOrganization = async (organization, userId) => {
|
|
|
66
64
|
creatorId: userId,
|
|
67
65
|
membersIds: [userId],
|
|
68
66
|
adminsIds: [userId],
|
|
69
|
-
plan: {
|
|
70
|
-
name: "FREE",
|
|
71
|
-
statue: "ACTIVE",
|
|
72
|
-
creatorId: userId
|
|
73
|
-
},
|
|
74
67
|
...organization
|
|
75
68
|
});
|
|
76
69
|
return result;
|
|
@@ -103,19 +96,14 @@ const deleteOrganizationById = async (organizationId) => {
|
|
|
103
96
|
}
|
|
104
97
|
return organization;
|
|
105
98
|
};
|
|
106
|
-
const saveStripeCustomerId = async (organization, customerId) => {
|
|
107
|
-
if (!organization) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
await import_organization.OrganizationModel.updateOne(
|
|
111
|
-
{ _id: organization._id },
|
|
112
|
-
{ $set: { plan: { customerId } } }
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
99
|
const updatePlan = async (organization, plan) => {
|
|
100
|
+
let prevPlan = organization.plan ?? {};
|
|
101
|
+
if (typeof prevPlan?.toObject === "function") {
|
|
102
|
+
prevPlan = prevPlan.toObject();
|
|
103
|
+
}
|
|
116
104
|
const updateOrganizationResult = await import_organization.OrganizationModel.updateOne(
|
|
117
105
|
{ _id: organization._id },
|
|
118
|
-
{ $set: { plan: { ...
|
|
106
|
+
{ $set: { plan: { ...prevPlan, ...plan } } },
|
|
119
107
|
{ new: true }
|
|
120
108
|
);
|
|
121
109
|
if (updateOrganizationResult.matchedCount === 0) {
|
|
@@ -126,24 +114,14 @@ const updatePlan = async (organization, plan) => {
|
|
|
126
114
|
const updatedOrganization = await getOrganizationById(organization._id);
|
|
127
115
|
return updatedOrganization;
|
|
128
116
|
};
|
|
129
|
-
const getOrganizationByCustomerId = async (customerId) => {
|
|
130
|
-
const organization = await import_organization.OrganizationModel.findOne({
|
|
131
|
-
plan: {
|
|
132
|
-
customerId
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
return organization;
|
|
136
|
-
};
|
|
137
117
|
// Annotate the CommonJS export names for ESM import in node:
|
|
138
118
|
0 && (module.exports = {
|
|
139
119
|
countOrganizations,
|
|
140
120
|
createOrganization,
|
|
141
121
|
deleteOrganizationById,
|
|
142
122
|
findOrganizations,
|
|
143
|
-
getOrganizationByCustomerId,
|
|
144
123
|
getOrganizationById,
|
|
145
124
|
getOrganizationsByOwner,
|
|
146
|
-
saveStripeCustomerId,
|
|
147
125
|
updateOrganizationById,
|
|
148
126
|
updatePlan
|
|
149
127
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/organization.service.ts"],"sourcesContent":["import { OrganizationModel } from '@models/organization.model';\nimport { GenericError } from '@utils/errors';\nimport type { OrganizationFilters } from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n type OrganizationFields,\n validateOrganization,\n} from '@utils/validation/validateOrganization';\nimport type { ObjectId } from 'mongoose';\nimport type {\n Organization,\n OrganizationCreationData,\n OrganizationDocument,\n} from '@/types/organization.types';\nimport type { Plan } from '@/types/plan.types';\n\n/**\n * Finds organizations based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of organizations matching the filters.\n */\nexport const findOrganizations = async (\n filters: OrganizationFilters,\n skip: number,\n limit: number\n): Promise<OrganizationDocument[]> => {\n return await OrganizationModel.find(filters).skip(skip).limit(limit);\n};\n\n/**\n * Finds an organization by its ID.\n * @param organizationId - The ID of the organization to find.\n * @returns The organization matching the ID.\n */\nexport const getOrganizationById = async (\n organizationId: ObjectId | string\n): Promise<OrganizationDocument> => {\n const organization = await OrganizationModel.findById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', { organizationId });\n }\n\n return organization;\n};\n\n/**\n * Retrieves an organization by its owner.\n * @param userId - The ID of the user to find the organization.\n * @returns The organizations matching the user ID.\n */\nexport const getOrganizationsByOwner = async (\n userId: string | ObjectId\n): Promise<OrganizationDocument[] | null> => {\n const organization = await OrganizationModel.find({\n creatorId: userId,\n });\n\n return organization;\n};\n\n/**\n * Counts the total number of organizations that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of organizations.\n */\nexport const countOrganizations = async (\n filters: OrganizationFilters\n): Promise<number> => {\n const result = await OrganizationModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('ORGANIZATION_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new organization in the database.\n * @param organization - The organization data to create.\n * @returns The created organization.\n */\nexport const createOrganization = async (\n organization: OrganizationCreationData,\n userId: string | ObjectId\n): Promise<OrganizationDocument> => {\n const errors = validateOrganization(organization, ['name']);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('ORGANIZATION_INVALID_FIELDS', { errors });\n }\n\n try {\n const result = await OrganizationModel.create({\n creatorId: userId,\n membersIds: [userId],\n adminsIds: [userId],\n
|
|
1
|
+
{"version":3,"sources":["../../../src/services/organization.service.ts"],"sourcesContent":["import { OrganizationModel } from '@models/organization.model';\nimport { GenericError } from '@utils/errors';\nimport type { OrganizationFilters } from '@utils/filtersAndPagination/getOrganizationFiltersAndPagination';\nimport {\n type OrganizationFields,\n validateOrganization,\n} from '@utils/validation/validateOrganization';\nimport type { ObjectId } from 'mongoose';\nimport type {\n Organization,\n OrganizationCreationData,\n OrganizationDocument,\n} from '@/types/organization.types';\nimport type { Plan, PlanDocument } from '@/types/plan.types';\n\n/**\n * Finds organizations based on filters and pagination options.\n * @param filters - MongoDB filter query.\n * @param skip - Number of documents to skip.\n * @param limit - Number of documents to limit.\n * @returns List of organizations matching the filters.\n */\nexport const findOrganizations = async (\n filters: OrganizationFilters,\n skip: number,\n limit: number\n): Promise<OrganizationDocument[]> => {\n return await OrganizationModel.find(filters).skip(skip).limit(limit);\n};\n\n/**\n * Finds an organization by its ID.\n * @param organizationId - The ID of the organization to find.\n * @returns The organization matching the ID.\n */\nexport const getOrganizationById = async (\n organizationId: ObjectId | string\n): Promise<OrganizationDocument> => {\n const organization = await OrganizationModel.findById(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', { organizationId });\n }\n\n return organization;\n};\n\n/**\n * Retrieves an organization by its owner.\n * @param userId - The ID of the user to find the organization.\n * @returns The organizations matching the user ID.\n */\nexport const getOrganizationsByOwner = async (\n userId: string | ObjectId\n): Promise<OrganizationDocument[] | null> => {\n const organization = await OrganizationModel.find({\n creatorId: userId,\n });\n\n return organization;\n};\n\n/**\n * Counts the total number of organizations that match the filters.\n * @param filters - MongoDB filter query.\n * @returns Total number of organizations.\n */\nexport const countOrganizations = async (\n filters: OrganizationFilters\n): Promise<number> => {\n const result = await OrganizationModel.countDocuments(filters);\n\n if (typeof result === 'undefined') {\n throw new GenericError('ORGANIZATION_COUNT_FAILED', { filters });\n }\n\n return result;\n};\n\n/**\n * Creates a new organization in the database.\n * @param organization - The organization data to create.\n * @returns The created organization.\n */\nexport const createOrganization = async (\n organization: OrganizationCreationData,\n userId: string | ObjectId\n): Promise<OrganizationDocument> => {\n const errors = validateOrganization(organization, ['name']);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('ORGANIZATION_INVALID_FIELDS', { errors });\n }\n\n try {\n const result = await OrganizationModel.create({\n creatorId: userId,\n membersIds: [userId],\n adminsIds: [userId],\n ...organization,\n });\n\n return result;\n } catch (error) {\n throw new GenericError('ORGANIZATION_CREATION_FAILED', { error });\n }\n};\n\n/**\n * Updates an existing organization in the database by its ID.\n * @param organizationId - The ID of the organization to update.\n * @param organization - The updated organization data.\n * @returns The updated organization.\n */\nexport const updateOrganizationById = async (\n organizationId: ObjectId | string,\n organization: Partial<Organization>\n): Promise<OrganizationDocument> => {\n const updatedKeys = Object.keys(organization) as OrganizationFields;\n const errors = validateOrganization(organization, updatedKeys);\n\n if (Object.keys(errors).length > 0) {\n throw new GenericError('ORGANIZATION_INVALID_FIELDS', {\n organizationId,\n errors,\n });\n }\n\n const result = await OrganizationModel.updateOne(\n { _id: organizationId },\n organization\n );\n\n if (result.matchedCount === 0) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', { organizationId });\n }\n\n return await getOrganizationById(organizationId);\n};\n\n/**\n * Deletes an organization from the database by its ID.\n * @param organizationId - The ID of the organization to delete.\n * @returns The result of the deletion operation.\n */\nexport const deleteOrganizationById = async (\n organizationId: ObjectId | string\n): Promise<OrganizationDocument> => {\n const organization =\n await OrganizationModel.findByIdAndDelete(organizationId);\n\n if (!organization) {\n throw new GenericError('ORGANIZATION_NOT_FOUND', { organizationId });\n }\n\n return organization;\n};\n\n/**\n * Updates an existing plan in the database by its ID.\n * @param planId - The ID of the plan to update.\n * @param plan - The updated plan data.\n * @returns The updated plan.\n */\nexport const updatePlan = async (\n organization: Organization | OrganizationDocument,\n plan: Partial<Plan>\n): Promise<OrganizationDocument | null> => {\n let prevPlan = organization.plan ?? {};\n\n if (typeof (prevPlan as PlanDocument)?.toObject === 'function') {\n prevPlan = (prevPlan as PlanDocument).toObject();\n }\n\n const updateOrganizationResult = await OrganizationModel.updateOne(\n { _id: organization._id },\n { $set: { plan: { ...prevPlan, ...plan } } },\n { new: true }\n );\n\n if (updateOrganizationResult.matchedCount === 0) {\n throw new GenericError('ORGANIZATION_UPDATE_FAILED', {\n organizationId: organization._id,\n });\n }\n\n const updatedOrganization = await getOrganizationById(organization._id);\n\n return updatedOrganization;\n};\n"],"mappings":"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","names":[]}
|