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