Subscription Management
Subscription Management provides comprehensive subscription retrieval, advanced filtering, and administrative oversight functionality within the DashClicks billing system.
API Endpoints Overview
| Method | Endpoint | Description |
|---|---|---|
GET | /v1/admin/billing/subscriptions | Retrieve subscriptions with advanced filtering |
GET | /v1/admin/billing/filters | Get subscription filter counts and statistics |
Service Methods & Business Logic
Subscription Retrieval and Filtering
querySubs(filters, sort, pagination) - Comprehensive subscription retrieval
- Service Method:
billingService.querySubs(filters, sort, pagination) - Controller:
billingController.getSubscriptions - Route:
GET /v1/admin/billing/subscriptions - Parameters:
status- Array of status filters (active, past_due, canceled, etc.)products- Array of product type filterssearch- Search term across accounts and productspage,limit- Pagination parameterssort_by,order- Sorting configuration
- Status Filtering Logic:
- Supports multiple status combinations
- Special handling for product-specific status filtering
- Search Functionality: Multi-field search across:
- Partner account name and email
- Buyer account name and email
- Product name and tier information
- MongoDB Aggregation Pipeline:
- Lookup to
_accountsfor partner (seller) information - Lookup to
_accountsfor buyer information via metadata.account_id - Lookup to
_store.productsfor product details - Discount calculation with coupon information
- Lookup to
- Collections Used:
_store.subscriptions,_accounts,_store.products - Returns: Paginated subscriptions with partner, buyer, and product data
retrySub(subId) - Retry failed subscription payment
- Service Method:
billingService.retrySub(subId) - Controller:
billingController.retrySubscription - Route:
PUT /v1/admin/billing/subscriptions/:sub_id/retry - Process: Retrieves subscription and triggers Stripe payment retry
- Returns: Updated subscription status
cancelSub(subId, immediate) - Cancel subscription
- Service Method:
billingService.cancelSub(subId, immediate) - Controller:
billingController.cancelSubscription - Route:
DELETE /v1/admin/billing/subscriptions/:sub_id - Parameters:
subId- Subscription ID to cancelimmediate- Boolean for immediate vs period-end cancellation
- Process: Updates Stripe subscription cancellation
- Returns: Cancellation confirmation
resumeCancelSub(subId) - Resume canceled subscription
- Service Method:
billingService.resumeCancelSub(subId) - Controller:
billingController.resumeCancelSubscription - Route:
POST /v1/admin/billing/subscriptions/:sub_id/resume - Process: Resumes previously canceled subscription in Stripe
- Returns: Resumed subscription status
clearSub(subId) - Clear/delete subscription record
- Service Method:
billingService.clearSub(subId) - Controller:
billingController.clearSubscription - Route:
DELETE /v1/admin/billing/subscriptions/:sub_id/clear - Process: Removes subscription record from database
- Returns: Deletion confirmation
Filter Statistics Generation
getFilters(billingFilters) - Dynamic filter count calculation for administrative interface
- Generates real-time filter statistics for administrative subscription filtering interface
- Status Filter Counts: Calculates subscription counts for each status category:
- Real-time aggregation of subscription status distribution
- Dynamic count calculation based on current subscription state
- Filter-specific matching logic for accurate statistics
- Product Filter Counts: Calculates subscription counts by product type:
- Product-type based aggregation with metadata matching
- Active subscription filtering (excludes canceled/expired)
- Product category distribution statistics
- Dynamic Aggregation: Flexible filter count calculation:
- Configurable filter categories and values
- Efficient MongoDB aggregation for performance
- Null-safe count calculation with fallback values
- MongoDB Collections:
_store.subscriptions,_store.productswith aggregation - Returns: Updated filter objects with current count statistics
Technical Implementation Details
Advanced Subscription Filtering Pipeline
const buildSubscriptionFilter = filters => {
let statusMatch = { status: { $in: [] } };
// Status-based filtering logic
if (filters.status?.length) {
filters.status.forEach(status => {
switch (status) {
case 'active':
statusMatch.status.$in.push('active', 'trialing');
break;
case 'past_due':
statusMatch.status.$in.push('past_due');
break;
case 'canceled':
statusMatch.status.$in.push('canceled');
statusMatch.team_tasks_pending = true;
statusMatch['plan.metadata.product_type'] = {
$nin: ['listings', 'phone_number', 'site', 'software'],
};
break;
case 'cancels_on':
statusMatch.status.$in.push('active', 'trialing');
statusMatch.cancel_at_period_end = true;
break;
case 'unpaid':
statusMatch.status.$in.push('unpaid');
break;
}
});
} else {
// Default to active subscriptions
statusMatch.status = { $in: ['active', 'trialing', 'past_due'] };
}
return statusMatch;
};
Multi-Dimensional Search Implementation
const buildSearchFilter = searchTerm => {
if (!searchTerm) return {};
const searchMatch = {
$or: [
// ObjectId-based account matching
...(mongoose.isObjectIdOrHexString(searchTerm)
? [
{ 'partner._id': { $eq: new ObjectId(searchTerm) } },
{ 'buyer._id': { $eq: new ObjectId(searchTerm) } },
]
: []),
// Text-based search across multiple fields
{
'partner.name': {
$regex: searchTerm.trim(),
$options: 'i',
},
},
{
'buyer.name': {
$regex: searchTerm.trim(),
$options: 'i',
},
},
{
'plan.nickname': {
$regex: searchTerm.trim(),
$options: 'i',
},
},
{
'product.name': {
$regex: searchTerm.trim(),
$options: 'i',
},
},
],
};
return searchMatch;
};
Subscription Aggregation Pipeline
const subscriptionAggregationPipeline = (filters, sort, pagination) => {
const skip = (pagination.page - 1) * pagination.limit;
return [
// Initial status filtering
{ $match: buildSubscriptionFilter(filters) },
// Partner (seller) account lookup
{
$lookup: {
from: '_accounts',
localField: 'account',
foreignField: '_id',
as: 'partner',
},
},
{
$set: {
partner: { $arrayElemAt: ['$partner', 0] },
},
},
// Buyer account lookup via metadata
{
$lookup: {
from: '_accounts',
localField: 'metadata.account_id',
foreignField: '_id',
as: 'buyer',
},
},
{
$set: {
buyer: { $arrayElemAt: ['$buyer', 0] },
},
},
// Product information lookup
{
$lookup: {
from: '_store.products',
let: { stripe_id: '$plan.product' },
pipeline: [
{
$match: {
'metadata.v1_product_type': { $exists: false },
$expr: { $eq: ['$stripe_id', '$$stripe_id'] },
},
},
],
as: 'product',
},
},
{
$set: {
product: { $arrayElemAt: ['$product', 0] },
},
},
// V1 product compatibility lookup
{
$lookup: {
from: '_store.products',
let: { stripe_id: '$plan.product' },
pipeline: [
{
$match: {
'metadata.v1_product_type': { $exists: true },
$expr: {
$and: [
{ $eq: ['$stripe_id', '$$stripe_id'] },
{
$in: [
'$metadata.v1_product_type',
[
'facebook_ads',
'seo',
'content',
'google_ads',
'listings',
'social_posting',
],
],
},
],
},
},
},
],
as: 'v1products',
},
},
{
$set: {
v1products: { $arrayElemAt: ['$v1products', 0] },
},
},
// Search filtering
...(filters.search ? [{ $match: buildSearchFilter(filters.search) }] : []),
// Pagination and counting
{
$facet: {
paginatedResults: [
// Discount calculation
{
$addFields: {
discountedCost: {
$multiply: [
'$plan.amount',
{
$subtract: [
1,
{
$divide: [{ $ifNull: ['$discount.coupon.percent_off', 0] }, 100],
},
],
},
],
},
},
},
// Final projection
{
$project: {
partner: {
name: 1,
phone: 1,
email: 1,
account: 1,
},
buyer: {
name: 1,
phone: 1,
email: 1,
account: 1,
},
product: {
$cond: {
if: { $not: ['$product.name'] },
then: null,
else: {
name: '$product.name',
tier: '$plan.nickname',
interval: '$plan.interval',
interval_count: '$plan.interval_count',
amount: '$plan.amount',
amount_due: '$discountedCost',
images: '$product.images',
},
},
},
v1_products: {
$cond: {
if: { $not: ['$v1products.name'] },
then: null,
else: {
name: '$v1products.name',
tier: '$plan.nickname',
interval: '$plan.interval',
interval_count: '$plan.interval_count',
amount: '$plan.amount',
amount_due: '$discountedCost',
images: '$product.images',
},
},
},
status: 1,
team_tasks_pending: 1,
cancel_at_period_end: 1,
current_period_start: 1,
current_period_end: 1,
cancel_at: 1,
ended_at: 1,
subscription_id: {
stripe: '$stripe_id',
internal: '$_id',
},
renew_date: '$current_period_end',
created: 1,
},
},
// Sorting
{
$sort: sort.sort_by
? { [sort.sort_by]: sort.order === 'asc' ? 1 : -1 }
: { created: -1 },
},
// Pagination
{ $skip: skip || 0 },
{ $limit: pagination.limit },
],
totalCount: [{ $count: 'count' }],
},
},
];
};
API Response Formats
Subscription List Response
{
"success": true,
"message": "SUCCESS",
"data": [
{
"partner": {
"name": "DashClicks Agency",
"phone": "+1234567890",
"email": "agency@dashclicks.com",
"account": "partner_account_id"
},
"buyer": {
"name": "Client Business Inc",
"phone": "+1987654321",
"email": "client@business.com",
"account": "buyer_account_id"
},
"product": {
"name": "SEO Management",
"tier": "Professional",
"interval": "month",
"interval_count": 1,
"amount": 29999,
"amount_due": 25499,
"images": ["https://example.com/seo-product.jpg"]
},
"v1_products": null,
"status": "active",
"team_tasks_pending": false,
"cancel_at_period_end": false,
"current_period_start": "2024-10-01T00:00:00.000Z",
"current_period_end": "2024-11-01T00:00:00.000Z",
"cancel_at": null,
"ended_at": null,
"subscription_id": {
"stripe": "sub_stripe_id_123",
"internal": "subscription_internal_id"
},
"renew_date": "2024-11-01T00:00:00.000Z",
"created": "2024-10-01T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 150,
"pages": 15
}
}
Filter Counts Response
{
"success": true,
"message": "SUCCESS",
"data": {
"statuses": {
"active": 125,
"past_due": 8,
"canceled": 12,
"cancels_on": 5,
"unpaid": 3
},
"products": {
"seo": 45,
"google_ads": 32,
"facebook_ads": 28,
"content": 20,
"social_posting": 15,
"listings": 10
}
}
}
Query Parameters
Subscription Filtering Parameters
status(array) - Filter by subscription status: ['active', 'past_due', 'canceled', 'cancels_on', 'unpaid']products(array) - Filter by product type: ['seo', 'google_ads', 'facebook_ads', 'content', 'social_posting', 'listings']search(string) - Search across partner names, buyer names, product names, and account IDssort_by(string) - Sort field: 'created', 'current_period_end', 'status'order(string) - Sort order: 'asc' or 'desc' (default: 'desc')page(number) - Page number for pagination (default: 1)limit(number) - Items per page (default: 10, max: 100)
Available Status Values
active- Currently active subscriptions (includes trialing)past_due- Subscriptions with failed payment attemptscanceled- Fully canceled subscriptions with pending team taskscancels_on- Active subscriptions scheduled for period-end cancellationunpaid- Subscriptions with unpaid invoices
Available Product Types
seo- SEO management servicesgoogle_ads- Google Ads managementfacebook_ads- Facebook Ads managementcontent- Content creation servicessocial_posting- Social media posting serviceslistings- Business listings managementphone_number- Phone number servicessite- Website servicessoftware- Software products
Error Handling
Subscription Management Error Scenarios
- 400 Bad Request: Invalid filtering parameters or malformed query
- 500 Internal Server Error: Database aggregation failures or MongoDB connection issues
Empty Results Response
{
"success": true,
"message": "SUCCESS",
"data": [],
"pagination": {
"page": 1,
"limit": 10,
"total": 0,
"pages": 0
}
}
Usage Examples
Get All Active Subscriptions
GET /v1/admin/billing/subscriptions?status=active&limit=20
Authorization: Bearer {admin_token}
Search Subscriptions by Partner Name
GET /v1/admin/billing/subscriptions?search=DashClicks&page=1&limit=10
Authorization: Bearer {admin_token}
Filter by Multiple Statuses and Product Types
GET /v1/admin/billing/subscriptions?status=active,past_due&products=seo,google_ads&sort_by=created&order=desc
Authorization: Bearer {admin_token}
Get Canceled Subscriptions with Pending Tasks
GET /v1/admin/billing/subscriptions?status=canceled&limit=50
Authorization: Bearer {admin_token}
Get Filter Statistics
GET /v1/admin/billing/filters
Authorization: Bearer {admin_token}