๐ฐ OneBalance Module
๐ Overviewโ
The OneBalance module is DashClicks' unified credit and usage tracking system, managing consumption-based billing for platform services like InstaSites, InstaReports, SMS, phone numbers, and API calls. It provides real-time balance tracking, automatic reloading, tiered pricing, and sub-account rebilling with custom markup.
File Path: internal/api/v1/onebalance/
Key Capabilitiesโ
- Unified Balance: Single credit balance for all platform services
- Auto-Reload: Automatic balance replenishment when threshold reached
- Usage Tracking: Real-time consumption logging per service
- Tiered Pricing: Dynamic pricing based on account tier and volume
- Sub-Account Rebilling: Parent accounts can markup services for sub-accounts
- Credit Limits: Per-service credit allocations (e.g., 1000 InstaSite credits/month)
- Billing Cycle Tracking: Monthly credit resets tied to subscription cycles
- Currency Conversion: Multi-currency support with real-time exchange rates
๏ฟฝ Directory Structureโ
onebalance/
โโโ ๐ index.js - Module router
โโโ ๐ README.md - Basic API documentation
โโโ ๐ controllers/
โ โโโ onebalance.js - Request handlers
โโโ ๐ services/
โ โโโ onebalance.js - Core business logic
โ โโโ analytics.js - Usage analytics
โ โโโ index.js - Service exports
โโโ ๐ routes/
โ โโโ index.js - Route definitions
โโโ ๐ validations/
โโโ (validation schemas)
๏ฟฝ๐๏ธ Collections Usedโ
onebalanceโ
- Purpose: Store account credit balance and configuration
- Model:
shared/models/onebalance.js - Key Fields:
account_id(ObjectId) - Owner accountbalance(Number) - Current credit balance (in cents)reload.enabled(Boolean) - Auto-reload feature togglereload.threshold(Number) - Balance level to trigger reload (cents)reload.amount(Number) - Amount to add on reload (cents)reload.payment_method(String) - Stripe payment method IDrebill(Object) - Sub-account pricing configurationcreated_at(Date)updated_at(Date)
Rebill Structure (Main accounts only):
{
rebill: {
sms: { multiplier: 1.5 }, // 1.5x markup on SMS
listing: { value: 5000 }, // $50.00 per listing
lighting_domain: { value: 1000 }, // $10.00 per domain
instasite: { credits: 1000 }, // 1000 credits per month
instareport: { multiplier: 1.2 },
phone: { multiplier: 1.3 },
inbound_call: { multiplier: 1.1 },
outbound_call: { multiplier: 1.1 }
}
}
onebalance.usage_logsโ
- Purpose: Track service consumption events
- Model:
shared/models/onebalance-usage_logs.js - Key Fields:
account_id(ObjectId) - Consumer accountevent(String) - Service type ('instasite', 'instareport', 'sms', 'phone', etc.)amount(Number) - Cost deducted (cents)status('success' | 'failed') - Transaction statusmetadata(Object) - Event-specific detailsbalance_before(Number) - Balance before deductionbalance_after(Number) - Balance after deductioncreated_at(Date) - Usage timestamp
configsโ
- Operations: Read (pricing configuration)
- Usage: Retrieve base service prices by account tier
- Key Document:
type: 'onebalance-base-prices'
Price Configuration Structure:
{
type: 'onebalance-base-prices',
account_pricing_type: 'pro' | 'plus' | 'platinum',
prices: {
sms: { type: 'dynamic', base_us_price: 150 }, // $0.015 per SMS
phone: { type: 'dynamic', base_us_price: 100 }, // $1.00 per month
listing: { type: 'fixed', amount: 2500 }, // $25.00
lighting_domain: { type: 'fixed', amount: 1000 }, // $10.00
instasite: { type: 'credit', credits: 1000 }, // 1000/month
instareport: { type: 'fixed', amount: 500 } // $5.00
}
}
_store.subscriptionsโ
- Operations: Read (billing cycle info for credit resets)
- Usage: Determine current billing period for monthly credit allocations
_accountsโ
- Operations: Read (account tier, parent account, pricing type)
- Usage: Determine pricing tier and rebilling relationships
๐ Data Flowโ
Auto-Reload Flowโ
sequenceDiagram
participant Service as Platform Service
participant Verify as verifyBalance()
participant OB as OneBalance
participant Stripe as Stripe API
participant Log as Usage Logs
Service->>Verify: Request service (e.g., send SMS)
Verify->>OB: Check current balance
OB-->>Verify: balance: $5.00
alt Balance > Service Cost
Verify->>Log: Deduct cost from balance
Log->>OB: Update balance
Verify-->>Service: Approved
Service->>Service: Execute service
else Balance < Service Cost
Verify->>OB: Check auto-reload enabled
OB-->>Verify: reload.enabled: true
Verify->>Stripe: Charge payment method
Stripe-->>Verify: Payment success
Verify->>OB: Add reload.amount to balance
Verify->>Log: Log reload transaction
Verify->>Log: Deduct service cost
Verify-->>Service: Approved (after reload)
Service->>Service: Execute service
else Insufficient Balance & No Auto-Reload
Verify-->>Service: Rejected (insufficient credits)
Service-->>User: Error: Insufficient balance
end
Sub-Account Rebilling Flowโ
flowchart TD
A[Sub-Account Requests Service] --> B[Get Base Price]
B --> C[Check Parent Account rebill Config]
C --> D{Rebill Type?}
D -->|Multiplier| E[Base Price ร Multiplier]
D -->|Fixed Value| F[Use Parent's Fixed Price]
E --> G[Calculate Final Cost]
F --> G
G --> H[Deduct from Sub-Account Balance]
H --> I{Balance Sufficient?}
I -->|Yes| J[Execute Service]
I -->|No| K[Trigger Auto-Reload or Reject]
J --> L[Log Usage]
K --> L
InstaSite Credit Tracking Flowโ
flowchart TD
A[Request InstaSite Generation] --> B[Get Current Billing Cycle]
B --> C[Count InstaSites Used This Cycle]
C --> D[Calculate Remaining Credits]
D --> E{Credits > 0?}
E -->|Yes| F[Decrement Credit Count]
E -->|No| G[Charge Per-Use Fee]
F --> H[Generate InstaSite]
G --> I{Balance Sufficient?}
I -->|Yes| H
I -->|No| J[Reject: Insufficient Credits]
H --> K[Log Usage Event]
K --> L[Return Success]
๐ง Core Functionsโ
get({ account_id, account, currency, main })โ
Retrieve OneBalance configuration and current balance for an account.
Parameters:
account_id(ObjectId) - Account IDaccount(Object) - Full account objectcurrency(String) - Desired currency for display (e.g., 'USD', 'EUR')main(Boolean) - Is main account (includes rebill config)
Returns:
{
account_id: ObjectId,
balance: 125.50, // Converted from cents to dollars
reload: {
enabled: true,
threshold: 50.00,
amount: 100.00,
payment_method: 'pm_xxx'
},
rebill: { // Main accounts only
listing: { value: 50.00 },
lighting_domain: { value: 10.00 },
instasite: { credits: 850 } // Remaining credits this cycle
}
}
Business Logic:
- Auto-Initialization: If no OneBalance record exists, creates one via
initOnebalance() - Currency Conversion: Converts cent-based storage to dollar display (รท 100)
- Main Account Features: Only main accounts receive
rebillconfiguration - InstaSite Credits: Calculates remaining credits for current billing cycle
update(account_id, data, currency, main)โ
Update OneBalance configuration (reload settings, rebill config).
Parameters:
account_id(ObjectId)data(Object) - Fields to updatecurrency(String)main(Boolean)
Updatable Fields:
{
reload: {
enabled: Boolean,
threshold: Number,
amount: Number,
payment_method: String
},
rebill: { // Main accounts only
sms: { multiplier: Number },
listing: { value: Number },
lighting_domain: { value: Number },
instasite: { credits: Number }
}
}
Process:
- Convert dollar amounts to cents (ร 100)
- Flatten nested object structure
- Update document in MongoDB
- Convert response back to dollars
costPerItem({ account_id, account, currency, user_id })โ
Calculate per-service pricing for an account, applying tier pricing and rebilling.
Returns:
{
sms: {
type: 'dynamic',
base_us_price: 0.015, // Per SMS
a2p_registration_pending: false
},
phone: {
type: 'dynamic',
base_us_price: 1.00 // Per month
},
listing: {
type: 'fixed',
amount: 50.00 // Per listing
},
lighting_domain: {
type: 'fixed',
amount: 10.00 // Per domain
},
instasite: {
type: 'credit',
credits: 850, // Remaining
credit_details: {
total: 1000,
used: 150,
remaining: 850
},
billing_cycle: {
start: Date,
end: Date
}
},
instareport: {
type: 'fixed',
amount: 5.00
}
}
Business Logic:
- Base Pricing: Retrieve from
configscollection by accountpricing_type(Pro/Plus/Platinum) - Dynamic Pricing: For SMS/phone, fetch real-time pricing from Twilio API
- Currency Conversion: Convert USD base prices to requested currency
- Sub-Account Markup: Apply parent account's
rebillmultipliers/values - InstaSite Credits: Calculate remaining credits for current billing cycle
- A2P Registration: Flag SMS pricing if Twilio A2P registration pending
Sub-Account Pricing Calculation:
// If parent has rebill.sms.multiplier = 1.5 and base price is $0.01:
final_price = base_price * multiplier; // $0.01 * 1.5 = $0.015
// If parent has rebill.listing.value = 5000 (cents):
final_price = value / 100; // 5000 / 100 = $50.00
getInstasiteCreditInfo({ account_id, account })โ
Calculate remaining InstaSite credits for current billing cycle.
Returns:
{
type: 'credit',
credits: 850, // Remaining credits
credit_details: {
total: 1000,
used: 150,
remaining: 850
},
billing_cycle: {
start: Date('2025-10-01'),
end: Date('2025-11-01')
}
}
Process:
- Find active subscription for account (or parent account)
- Extract current billing period (start/end dates)
- Get configured credit limit from
onebalance.rebill.instasite.credits(default: 1000) - Count InstaSite usage logs within billing cycle
- Calculate remaining credits:
total - used
Edge Cases:
- No Subscription: Returns null (no credit allocation)
- Tier Override: Checks for manual tier overrides via
tierOverride()function - Parent Account Lookup: Sub-accounts use parent's subscription and credit config
๐ Integration Pointsโ
Verification Utilityโ
File: shared/utilities/onebalance.js
Function: verifyBalance({ event, account, user_id, quantity })
Used by all platform services before execution:
// Example: Before sending SMS
await verifyBalance({
event: 'sms',
account: accountObject,
user_id: userId,
quantity: 0, // 0 = check only, 1+ = deduct
});
// Example: Before creating InstaSite
await verifyBalance({
event: 'instasite',
account: accountObject,
user_id: userId,
quantity: 0,
});
Services Using OneBalance:
- SMS Service (
utilities/sms.js) - Per-message charges - Phone Numbers (
utilities/twilio.js) - Monthly number rental - InstaSites (
instasites/) - Credit-based or per-use - InstaReports (
instareports/) - Per-report charges - Listings (
reviews/) - Business listing management - Lightning Domains (
funnels/) - Custom domain provisioning
Stripe Integrationโ
Auto-Reload Payments:
- Charges configured
reload.payment_methodwhen balance <reload.threshold - Uses Stripe Payment Intents API
- Logs successful/failed charges to
onebalance.usage_logs
Currency Conversionโ
Utility: CurrencyUtil from utilities/currency.js
const currUtil = new CurrencyUtil();
const converted = await currUtil.convert(
account_id,
amount, // USD amount
'USD', // From currency
'EUR', // To currency
);
// Returns: { amount: 0.92, rate: 0.92, currency: 'EUR' }
๐ Analytics Serviceโ
File: services/analytics.js
Provides usage reporting and cost breakdown:
- Usage by Service: SMS, InstaSites, InstaReports, etc.
- Cost by Date Range: Daily/weekly/monthly aggregations
- Top Consuming Sub-Accounts: For parent accounts
- Credit Utilization: Percentage of monthly credits used
- Reload History: Frequency and amounts
โ๏ธ Configurationโ
Environment Variablesโ
BASE_CURRENCY=USD
API_BASE_URL=http://localhost:5001
API_VERSION=v1
APP_SECRET=your_jwt_secret
Account Pricing Typesโ
const PRICING_TYPES = {
PRO: 'pro', // Base tier
PLUS: 'plus', // Mid tier
PLATINUM: 'platinum', // Premium tier
};
Each tier has different base pricing in configs collection.
โ ๏ธ Important Notesโ
- ๐ต Cent-Based Storage: All balances stored in cents (100 = $1.00) to avoid floating-point errors
- ๐ Credit Resets: InstaSite credits reset monthly based on subscription billing cycle
- ๐ Parent Control: Only parent accounts can configure rebilling for sub-accounts
- โก Real-Time Deduction: Balance deducted immediately before service execution
- ๐ Usage Logs: All transactions logged for audit and analytics
- ๐ Multi-Currency: Display in any currency, but stored in USD cents
- ๐จ A2P SMS: SMS pricing flagged if Twilio A2P registration incomplete
๐งช Edge Cases & Special Handlingโ
Case: Insufficient Balance During Service Executionโ
Condition: Balance becomes insufficient between verification and execution
Handling:
// Service uses atomic transaction
await session.withTransaction(async () => {
await verifyBalance(); // Re-verify within transaction
await executeService();
await logUsage();
});
Case: Credit Overage for InstaSitesโ
Condition: User exceeds monthly credit allocation
Handling:
- After credits exhausted, charge per-use fee from OneBalance
- Per-use fee typically higher than credit-based pricing
- User can purchase additional credit packs or use pay-per-use
Case: Sub-Account Without Parent Rebill Configโ
Condition: Parent account has no rebill configuration
Handling:
- Throws error: "Onebalance billing record not found"
- Sub-account cannot use services until parent configures rebilling
- Parent must set at least base multipliers (1.0x)
Case: Reload Payment Failureโ
Condition: Auto-reload payment method declined
Handling:
- Log failed reload attempt
- Send notification to account owner
- Retry payment 3 times over 24 hours
- After 3 failures, disable services requiring credits
- Account owner must manually add credits or update payment method
๐ Performance Considerationsโ
Indexing Requirementsโ
// onebalance collection
{ account_id: 1 }
// onebalance.usage_logs collection
{ account_id: 1, created_at: -1 }
{ account_id: 1, event: 1, created_at: -1 }
{ account_id: 1, event: 1, status: 1, created_at: -1 } // For credit calculations
Caching Strategyโ
- Balance Checks: Cache balance for 30 seconds (TTL cache)
- Pricing Config: Cache service prices for 5 minutes
- Credit Calculations: Cache InstaSite credits for current billing cycle (1 hour)
Optimization Patternsโ
- Batch Deductions: Group multiple SMS/email charges into single transaction
- Lazy Loading: Only fetch rebill config when needed (main accounts)
- Projection: Exclude unused fields in queries
Last Updated: 2025-10-08
Module Path:internal/api/v1/onebalance/
Primary Service:services/onebalance.js(300+ lines)