Skip to main content

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 ID
  • sub_account_id (ObjectId, optional) - Filter by specific client
  • search (String, optional) - Multi-field search term
  • skip (Number) - Pagination offset
  • limit (Number) - Page size
  • status (Array, optional) - Filter by subscription statuses
  • products (Array, optional) - Filter by product names
  • sortBy (String, optional) - Sort field name
  • sortOrder (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 update
  • updatedData (Object) - Fields to update
  • accountId (ObjectId) - Seller account validation

Returns: Updated order document

Business Logic:

  1. Validate order exists and belongs to seller account
  2. Update order with findOneAndUpdate
  3. Find users to notify (specialist, manager, account users, admins)
  4. Emit new_activity socket 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 ID
  • accountId (ObjectId) - Account ID for authorization
  • dashboardPreferences (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:

  1. Feedback Lookup:
const feedback = await SubFeedBack.findOne(
{ subscription: order.subscription._id },
{ _id: 0, reason: 1, feedback: 1 },
);
  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;
}
  1. 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,
});
  1. 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;
}
  1. 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 list
  • getOrderStatus utility - Timeline calculation
  • socketEmit - Activity notifications
  • notFound error - 404 handling
  • catchAsync - Error handling

External Services​

  • Socket.IO - new_activity event 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 $dateDiff with month unit
  • 🔐 Dashboard Scoping: Respects allow_client_dashboard and 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

Last Updated: 2025-10-08
Service Files: services/subscriptions.service.js, controllers/subscriptions.controller.js
Primary Functions: 3 service functions (getSubscriptions, updateOrder, getOrder)

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM