-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathbilling.controller.js
More file actions
106 lines (98 loc) · 3.75 KB
/
billing.controller.js
File metadata and controls
106 lines (98 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* Module dependencies
*/
import { activeStatuses } from '../lib/constants.js';
import config from '../../../config/index.js';
import responses from '../../../lib/helpers/responses.js';
import BillingService from '../services/billing.service.js';
import BillingUsageService from '../services/billing.usage.service.js';
/**
* @desc Endpoint to create a Stripe Checkout session
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<void>}
*/
const checkout = async (req, res) => {
try {
const { priceId, successUrl, cancelUrl } = req.body;
const url = await BillingService.createCheckout(req.organization, priceId, successUrl, cancelUrl);
responses.success(res, 'checkout session created')({ url });
} catch (err) {
const status = err.message?.startsWith('Invalid') || err.message?.includes('not found') ? 422 : 502;
const title = status === 422 ? 'Unprocessable Entity' : 'Bad Gateway';
responses.error(res, status, title, 'Failed to create checkout session')(err);
}
};
/**
* @desc Endpoint to create a Stripe Customer Portal session
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<void>}
*/
const portal = async (req, res) => {
try {
const { returnUrl } = req.body;
const url = await BillingService.createPortalSession(req.organization, returnUrl);
responses.success(res, 'portal session created')({ url });
} catch (err) {
const status = err.message?.startsWith('Invalid') || err.message?.includes('not found') ? 422 : 502;
const title = status === 422 ? 'Unprocessable Entity' : 'Bad Gateway';
responses.error(res, status, title, 'Failed to create portal session')(err);
}
};
/**
* @desc Endpoint to get the subscription for the current organization
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<void>}
*/
const getSubscription = async (req, res) => {
try {
const subscription = await BillingService.getSubscription(req.organization._id);
responses.success(res, 'subscription')(subscription);
} catch (err) {
responses.error(res, 500, 'Internal Server Error', 'Failed to retrieve subscription')(err);
}
};
/**
* @desc Endpoint to get billing usage for the current organization
* @param {Object} req - Express request object
* @param {Object} res - Express response object
* @returns {Promise<void>}
*/
const getUsage = async (req, res) => {
try {
// Determine current plan via service layer
const subscription = await BillingService.getSubscription(req.organization._id);
const plan = (!subscription || !activeStatuses.includes(subscription.status)) ? 'free' : (subscription.plan || 'free');
// Get usage counters (includes month field)
const usage = await BillingUsageService.get(req.organization._id.toString());
// Flatten quotas config into { "resource.action": limit } format
// Normalize Infinity to null for JSON-safe serialization
const quotas = config.billing?.quotas;
let limits = {};
if (quotas?.[plan]) {
const planQuotas = quotas[plan];
for (const resource of Object.keys(planQuotas)) {
for (const action of Object.keys(planQuotas[resource])) {
const rawLimit = planQuotas[resource][action];
limits[`${resource}_${action}`] = Number.isFinite(rawLimit) ? rawLimit : null;
}
}
}
responses.success(res, 'billing usage')({
plan,
period: usage.month,
usage: usage.counters || {},
limits,
});
} catch (err) {
responses.error(res, 500, 'Internal Server Error', 'Failed to retrieve billing usage')(err);
}
};
export default {
checkout,
portal,
getSubscription,
getUsage,
};