Subscriptions
Overview
The Subscriptions service manages subscription listing, order management, and cost calculations for managed services, providing comprehensive subscription data with discount handling, status prioritization, and feedback collection.
Source Files:
- Service:
internal/api/v1/projects/services/subscriptions.service.js - Controller:
internal/api/v1/projects/controllers/subscriptions.controller.js
Key Capabilities:
- List subscriptions with advanced search and sorting
- Calculate costs with discount handling
- Retrieve detailed order information
- Update orders with activity notifications
- Priority-based status ordering
- Subscription feedback management
- Client dashboard scoping
Collections Used
_store.orders
Operations: Read, Update
Usage: Primary subscription/order data source
Key Fields:
{
_id: ObjectId,
seller_account: ObjectId,
buyer_account: ObjectId,
subscription: ObjectId,
status: String,
metadata: {
product_name: String,
price_name: String,
product_type: String,
images: [String]
},
acc_price: { monthly_cost: Number },
assigned_user_ids: [ObjectId],
created_at: Date
}
_store.subscriptions
Operations: Read (lookup)
Usage: Subscription details, pricing, discounts
_store.subscription-feedback
Operations: Read
Usage: Cancellation feedback and reasons
_accounts
Operations: Read (lookup)
Usage: Buyer account information
_users
Operations: Read (lookup)
Usage: Assigned user enrichment
Business Logic & Functions
Service Layer
getSubscriptions({ accountId, sub_account_id, search, skip, limit, status, products, sortBy, sortOrder, dashboardPreferences })
Purpose: Retrieves paginated subscription list with advanced filtering, search, and sorting.
Parameters:
accountId(ObjectId) - Seller/agency account IDsub_account_id(ObjectId, optional) - Filter by specific clientsearch(String, optional) - Multi-field search termskip(Number) - Pagination offsetlimit(Number) - Page sizestatus(Array, optional) - Filter by subscription statusesproducts(Array, optional) - Filter by product namessortBy(String, optional) - Sort field namesortOrder(String, optional) - 'asc' or 'desc'dashboardPreferences(Object, optional) - Client dashboard settings
Returns: { data: [], total: Number }
Search Implementation:
// Multi-field regex search
if (search) {
searchRegex = { $regex: search, $options: 'i' };
$or: [
{ 'account.name': searchRegex },
{ 'metadata.product_name': searchRegex },
{ 'subscription.stripe_id': searchRegex },
];
// ObjectId search if valid
if (mongoose.Types.ObjectId.isValid(search)) {
$or.unshift(
{ _id: searchObjectId },
{ 'account._id': searchObjectId },
{ 'subscription._id': searchObjectId },
);
}
}
Sort Field Mapping:
const sortMap = {
type: 'type',
status: 'subscription.status',
created_at: 'subscription.created_at',
price: 'acc_price',
cost: 'subscription.plan.amount',
product_name: 'metadata.product_name',
};
Status Priority Ordering:
{
$addFields: {
statusOrder: {
$switch: {
branches: [
{ case: { $eq: ['$subscription.status', 'past_due'] }, then: 1 },
{ case: { $eq: ['$subscription.status', 'active'] }, then: 2 },
{ case: { $eq: ['$subscription.status', 'canceled'] }, then: 3 }
],
default: 4
}
}
}
}
Priority: past_due > active > canceled > other
Cost Calculations:
// Original monthly amount
original_amount = plan.amount / interval_count
// Discounted amount
if (discount exists) {
discounted_amount = (plan.amount × percent_off / 100) / interval_count
}
// Final amount
amount = discount ? (original_amount - discounted_amount) : original_amount
Months Calculation:
months = dateDiff(
start: subscription.created,
end: canceled_at ?? NOW,
unit: 'month'
)
Response Structure:
{
data: [{
account: { name, id },
created_at: Date,
canceled_at: Date,
cancel_at: Date,
status: String,
subscription_id: ObjectId,
metadata: Object,
cost: {
amount: Number,
interval_count: Number,
currency: String,
my_cost: Number,
original_amount: Number,
discounted_amount: Number,
percent_off: Number
},
assigned_users: [{ id, name, email, image }],
product_name: String,
price_name: String,
months: Number,
image: String
}],
total: Number
}
updateOrder({ orderid, updatedData, accountId })
Purpose: Updates an order and notifies relevant users via socket.
Parameters:
orderid(ObjectId) - Order ID to updateupdatedData(Object) - Fields to updateaccountId(ObjectId) - Seller account validation
Returns: Updated order document
Business Logic:
- Validate order exists and belongs to seller account
- Update order with
findOneAndUpdate - Find users to notify (specialist, manager, account users, admins)
- Emit
new_activitysocket event
Socket Notification Recipients:
{
$or: [
{ _id: updatedOrder.specialist },
{ _id: updatedOrder.manager },
{ account: updatedOrder.seller_account },
{ 'dashclicks.projects.role': 'admin' }
],
active: true
}
getOrder({ orderid, accountId, dashboardPreferences })
Purpose: Retrieves detailed order with full population, feedback, timeline, and cost calculations.
Parameters:
orderid(ObjectId) - Order IDaccountId(ObjectId) - Account ID for authorizationdashboardPreferences(Object, optional) - Client dashboard settings
Returns: Fully populated order object
Population Chain:
.populate({
path: 'seller_account',
select: 'business',
populate: { path: 'business', select: '_id name email phone address social website' }
})
.populate({
path: 'buyer_account',
select: 'business',
populate: { path: 'business', select: '_id name email phone address social website' }
})
.populate({ path: 'subscription' })
.populate({
path: 'tasks',
populate: { path: 'invoice' }
})
.populate({
path: 'manager',
select: '_id name email phone image availability metadata.meeting_link timezone'
})
Enrichment Steps:
- Feedback Lookup:
const feedback = await SubFeedBack.findOne(
{ subscription: order.subscription._id },
{ _id: 0, reason: 1, feedback: 1 },
);
- Main POC (Client Dashboard Only):
if (dashboardPreferences?.allow_client_dashboard || dashboardPreferences?.sso) {
const mainPoc = await User.find({
_id: { $in: dashboardPreferences.main_poc },
}).select('_id name email phone image metadata.meeting_link');
order.main_poc = mainPoc;
}
- Timeline Calculation:
timeline = await getOrderStatus({
productType: order.subscription.plan.metadata.product_type,
onboardingStatus: currentOnboardingStatus.status,
launched: isLaunched,
subMetadata: order.subscription.metadata,
subscriptionStatus: order.subscription.status,
});
- Date Scoping (Client Dashboard):
if (!dashboardPreferences.scopes.includes('activity.start_dates')) {
delete order.created_at;
delete order.subscription.created_at;
delete order.subscription.current_period_start;
}
- Cost Calculation (Agency Only):
if (order.subscription.discount) {
const intervalCount = order.subscription.plan.interval_count || 1;
const planAmount = order.subscription.plan.amount || 0;
const originalAmount = intervalCount !== 0 ? planAmount / intervalCount : 0;
const percentOff = order.subscription.discount.coupon.percent_off || 0;
const discountValue = (planAmount × percentOff) / 100;
const discountedAmount = discountValue / intervalCount;
const finalAmount = discountedAmount > 0 ? originalAmount - discountedAmount : originalAmount;
order.cost = {
amount: finalAmount,
interval_count: intervalCount,
currency: order.subscription.plan.currency,
my_cost: order.acc_price,
original_amount: originalAmount,
discounted_amount: discountedAmount,
percent_off: percentOff
};
}
Client Dashboard vs Agency Response:
- Client Dashboard: Includes main_poc, timeline, respects date scopes, NO cost calculations
- Agency: Full data including cost breakdown with discount calculations
Data Flow Diagram
flowchart TD
A[GET Subscriptions] --> B[Apply Search Filters]
B --> C[Apply Status/Product Filters]
C --> D[Lookup Subscriptions]
D --> E[Calculate Status Priority]
E --> F[Apply Sorting]
F --> G[Lookup Accounts]
G --> H[Calculate Costs & Discounts]
H --> I[Calculate Months Active]
I --> J[Facet: Data + Total]
J --> K[Lookup Assigned Users]
K --> L[Return Paginated Results]
M[GET Single Order] --> N{Dashboard or Agency?}
N -->|Dashboard| O[Filter by parent_account & sub_account_ids]
N -->|Agency| P[Filter by seller or buyer account]
O --> Q[Populate Relationships]
P --> Q
Q --> R[Fetch Feedback]
R --> S{Client Dashboard?}
S -->|Yes| T[Fetch Main POC]
S -->|No| U[Calculate Costs]
T --> V[Calculate Timeline]
U --> V
V --> W[Apply Date Scopes]
W --> X[Return Order]
Integration Points
Internal Dependencies
MANAGED_SUBSCRIPTIONS- Service type listgetOrderStatusutility - Timeline calculationsocketEmit- Activity notificationsnotFounderror - 404 handlingcatchAsync- Error handling
External Services
- Socket.IO -
new_activityevent for order updates - Stripe - Subscription data via
_store.subscriptions
Important Notes
- 🔍 Multi-Field Search: Searches across account name, product name, stripe_id, and ObjectIds
- 📊 Status Priority: past_due prioritized over active over canceled
- 💰 Cost in Cents: All monetary values stored in cents
- 🔢 Discount Formula:
(plan.amount × percent_off / 100) / interval_count - 📅 Month Calculation: Uses MongoDB
$dateDiffwith month unit - 🔐 Dashboard Scoping: Respects
allow_client_dashboardand scope permissions - 🔔 Real-time Updates: Socket events on order updates
- 🎯 Default Sort: Priority by status, then creation date descending
- 📝 Feedback: Fetched separately from
_store.subscription-feedback - ⏱️ Timeline: Calculated based on product type and onboarding status
Related Documentation
- Dashboard - Dashboard preferences with main_poc
- Analytics - Revenue calculations from subscriptions
- Onboardings - Onboarding workflow and timeline
- Projects Module Overview - Parent module
Last Updated: 2025-10-08
Service Files:services/subscriptions.service.js,controllers/subscriptions.controller.js
Primary Functions: 3 service functions (getSubscriptions, updateOrder, getOrder)