π― Overview Analytics
π Overviewβ
internal/api/v1/billing/services/overview.service.js centralises every KPI that feeds the billing overview screens. It aggregates MongoDB collections populated by Stripe webhooks, reconciles them with live Stripe lookups, and normalises the values into consistent currency/period windows. The service expects pagination and date range inputs from controllers and returns presentation-ready DTOs with pre-filled gaps for graph continuity.
File Path: internal/api/v1/billing/services/overview.service.js
ποΈ Collections Usedβ
π Full Schema: See Database Collections Documentation
billing.subscriptionsβ
- Operations: Read via aggregation pipelines.
- Model:
shared/models/billing-subscription.js - Usage Context: Counts new vs renewing subscriptions and drives product-level cohort metrics.
billing.chargesβ
- Operations: Read via aggregation pipelines.
- Model:
shared/models/billing-charge.js - Usage Context: Revenue KPIs, growth calculations, and risk analysis.
billing.disputesβ
- Operations: Read via aggregation pipelines.
- Model:
shared/models/billing-dispute.js - Usage Context: Offsets net revenue and tracks dispute counts/costs.
billing.refundsβ
- Operations: Read via aggregation pipelines.
- Model:
shared/models/billing-refund.js - Usage Context: Deducts refund totals from net volume analytics.
billing.customersβ
- Operations: Read via aggregation pipelines.
- Model:
shared/models/billing-customer.js - Usage Context: Identifies new customers/subscribers within a period.
billing.productsβ
- Operations:
findOne/updateOnewrites when cache misses occur, otherwise read. - Model:
shared/models/billing-product.js - Usage Context: Enriches subscription-level analytics with human-friendly product names.
π Data Flowβ
flowchart TD
A[Controller payload] --> B[overview.service]
B --> C{Metric type}
C -->|Revenue| D[Mongo Aggregations on billing.charges]
C -->|Subscriptions| E[Mongo Aggregations on billing.subscriptions]
C -->|Disputes/Refunds| F[Mongo Aggregations]
C -->|Stripe Lookups| G[Stripe SDK via stripeConfig]
D & E & F --> H[fillMissingDateData]
G --> I[Optional cache update (billing.products)]
H --> J[DTO assembly]
I --> J
J --> K[Controller Response]
classDef mongo fill:#f5f7ff,stroke:#4a65ff;
classDef external fill:#e8fff4,stroke:#00a86b;
class D,E,F mongo;
class G external;
π§ Business Logic & Functionsβ
salesOverview({ startDate, endDate, stripeBilling })β
Purpose: Calculates revenue breakdown for new vs renewing subscriptions within a reporting window.
Parameters:
startDate/endDate(String optional) β ISO date strings; defaults to trailing 30 days when omitted.stripeBilling(Object) β Includesstripe_user_idfor connected-account scoping.
Returns: Promise<{ currency, new, renewing, total, by_date: Array<{ date, total }> }>
by_dateis gap-filled to ensure chart continuity even on zero-revenue days.
Business Logic Flow:
- Normalise the date window to
momentobjects; fallback to last 30 days. - Derive the comparative period (
secondSetStart,secondSetEnd) to calculate renewals. - Run three aggregations on
billing.subscriptions:- New activations with
createdwithin the range. - Renewals where
current_period_startcrosses into the range. - Daily totals grouped by converted UTC dates.
- New activations with
- Convert cents to dollars and sum totals.
- Call
fillMissingDateDatato ensure theby_datearray contains every day between start and end. - Return the composed summary including a
totalproperty (new + renewing).
Key Business Rules:
- Aggregations always filter by
stripe_user_idfrom the connected account. - Revenue uses the first price item in
items.data; multi-price subscriptions inherit that unit amount.
Error Handling:
- Relies on
Promise.alland lets aggregation errors bubble up; controller wraps.
Example Usage:
const summary = await overviewService.salesOverview({
startDate: '2025-09-01',
endDate: '2025-09-30',
stripeBilling,
});
Side Effects: None (read-only).
salesByProduct({ limit = 25, next, stripeBilling })β
Purpose: Ranks products by sales count and revenue, enriching missing Stripe metadata.
Parameters:
limit(Number) β Page size for product groups.next(ObjectId string) β Mongo_idcursor for pagination.stripeBilling(Object) β Connected account context.
Returns: Promise<{ sales_by_product: Array, has_more, next }>
Business Logic Flow:
- Aggregate active subscriptions grouped by
items.data.price.product. - Sort descending by
_idand facet intodataandtotalbuckets to support pagination metadata. - For each grouped product:
- Attempt to fetch cached metadata from
billing.products. - Fallback to live
stripe.products.retrievecall and upsert the cache. - Record unit amounts and currencies per price tier.
- Attempt to fetch cached metadata from
- Determine
has_moreand next cursor using aggregated total vs limit.
Key Business Rules:
- Uses
$ltpagination on_idfor deterministic paging. - Caches Stripe product names to minimise API calls.
Error Handling:
- Stripe errors are caught and logged (
logger.error) but processing continues for other products.
Example Usage:
const page = await overviewService.salesByProduct({ limit: 10, stripeBilling });
Side Effects:
- β οΈ Upserts
billing.productswhen a product name is missing locally. - β οΈ Makes external Stripe API calls.
activeSubscriptionsByProduct({ limit = 25, next, stripeBilling })β
Purpose: Mirrors salesByProduct but only counts subscriptions with status: 'active'.
Differences:
- Adds
{ status: 'active' }to the aggregation match. - Returns payload under
active_subscriptions_by_product.
Error Handling:
- Surrounds the entire method with
try/catch; logs and rethrows errors to controller.
payouts({ firstPayoutId, lastPayoutId, limit = 25, stripeBilling })β
Purpose: Lists Stripe payouts for a connected account with pagination cursors.
Parameters:
firstPayoutId/lastPayoutId(String) β Stripe cursor IDs for reverse/forward pagination.limit(Number) β Page size.stripeBillingβ Connected account context.
Returns: Promise<{ payouts, has_more, first_payout_id, last_payout_id }> with currency-normalised amounts.
Business Logic Flow:
- Retrieve payouts via
stripe.payouts.list, expanding destinations for bank metadata. - Map each payout to DTO with dollars, bank name, and card last4.
- Derive pagination state (
has_more,first_payout_id,last_payout_id).
Error Handling:
- Logs Stripe failures (payout fetch) and rethrows to controller.
Side Effects:
- β οΈ External Stripe API call.
grossVolume({ startDate, endDate, stripeBilling })β
Purpose: Calculates gross charge volume for the selected period and prior window, including day-level series and growth rate.
Business Logic Flow:
- Normalise the window and compute comparative period.
- Build aggregation pipelines on
billing.chargesfor:- Total volume & count for current period.
- Daily breakdown for current period.
- Run equivalent pipelines for the previous period.
- Convert cents to dollars, format into chart-friendly arrays, and compute growth rate with zero-division guards.
- Fill missing dates for both
currentandpreviousranges usingfillMissingDateData.
Key Business Rules:
- Growth rate rounds to one decimal and defaults to
0when not finite. - Uses timezone offset derived from
startDateso daily buckets align with the accountβs locale.
Example Usage:
const gross = await overviewService.grossVolume({ startDate, endDate, stripeBilling });
console.log(gross.current.total); // "12345.67"
Side Effects: None.
netVolume({ startDate, endDate, stripeBilling })β
Purpose: Produces net revenue by subtracting disputes and refunds from gross totals.
Business Logic Flow:
- Reuse charge aggregation structure but sum
balance_transaction.net. - Aggregate disputes and refunds separately, multiplying dispute totals by
-1for offsets. - Merge day-level data to subtract disputes/refunds from charge totals per day.
- Compute current vs previous totals and growth rate, guarding against zero denominators.
- Gap-fill daily arrays via
fillMissingDateData.
Key Business Rules:
- When previous net total is
<= 0and current is positive, growth defaults to100. - Sorted daily arrays ensure chart order even when dispute days insert new entries.
Side Effects: None.
successfulPayments({ startDate, endDate, stripeBilling })β
Purpose: Counts paid charges for current vs prior period.
Returns: Totals and daily counts (not amounts), useful for conversion metrics.
Business Rules:
- Mirror logic of
grossVolumebut sumcountnot amounts.
disputeCount({ startDate, endDate, stripeBilling })β
Purpose: Tallies Stripe disputes and their net losses for comparison periods.
Business Rules:
- Aggregates
billing.disputes, summing the reduced balance transactions and multiplying by-1. - Growth rate follows successful payments pattern.
spendPerCustomer({ startDate, endDate, stripeBilling })β
Purpose: Calculates average spend per unique customer.
Business Logic Flow:
- Aggregate charges to count distinct
customerIDs and total amount. - Divide amount by customer count (guarding against zero) to compute average.
- Provide day-level averages by dividing per-group amounts by per-group unique customers.
Edge Cases:
- When no customers exist, totals default to
0to avoidNaN.
newCustomers({ startDate, endDate, stripeBilling })β
Purpose: Counts customers created in the period.
Business Logic Flow:
- Match
billing.customersbycreatedtimestamp window. - Sum totals and produce daily counts.
Special Handling:
- Uses
fillMissingDateDatafor chart consistency.
newSubscribers({ startDate, endDate, stripeBilling })β
Purpose: Counts unique customers who started subscriptions within the period.
Business Logic Flow:
- Match
billing.subscriptionsby created date. $lookupintobilling.customersto ensure the customer was also created during the same window.- Count distinct subscribers and produce daily counts.
Key Business Rules:
- Ensures the subscriber is new to the ecosystem (customer and subscription created in window).
highRiskPayments({ startDate, endDate, stripeBilling })β
Purpose: Surfaces charges flagged by Stripe Radar as highest or elevated risk.
Business Logic Flow:
- Filter
billing.chargesonoutcome.risk_levelvalues. - Aggregate totals and daily counts similar to disputes.
Use Cases:
- Alerts risk teams for manual review thresholds.
π Integration Pointsβ
Internal Dependenciesβ
../utils/stripe-configβ Instantiates Stripe SDK per connected account.../utils(fillMissingDateData) β Injects zero-value days between gaps.../utils/stripe-config+BillingProductβ Hydrates product metadata when caching misses.shared/utilities/loggerβ Logs Stripe lookup failures during product cache fills.
External Servicesβ
- Stripe API
stripe.products.retrievestripe.payouts.list- Only invoked when local caches require enrichment or when listing payouts.
π§ͺ Edge Cases & Special Handlingβ
Sparse Data Windowsβ
- Condition: No documents within the date range.
- Handling: Aggregations return empty arrays; service defaults totals to
0andfillMissingDateDataoutputs day entries with zero values.
Timezone Offsetsβ
- Condition: Start date includes timezone offset.
- Handling: Converts offset to seconds to align
$toDateconversions, preventing shifted day buckets.
Stripe Cache Missesβ
- Condition:
billing.productslacks a product referenced by subscription data. - Handling: Fetch from Stripe, upsert cache, continue without blocking remaining products.
β οΈ Important Notesβ
- π¨ Large Aggregations: All pipelines project only required fields but still scan significant data sets. Keep indexes on
stripe_user_idand date fields in sync. - π‘ Pagination:
salesByProductandactiveSubscriptionsByProductrely on_idcursor paging; controllers must pass the lastidreturned. - π Growth Rate Division: Growth metrics protect against division by zero yet can still spike to large values when prior period is tinyβsurface as-is for transparency.