Skip to main content

OneBalance Main Service

๐Ÿ“– Overviewโ€‹

The OneBalance service manages the unified credit balance system for all DashClicks consumption-based services. It handles:

  • Balance retrieval and updates (cent-based storage with dollar display)
  • Auto-reload configuration with Stripe payment methods
  • Per-service pricing with tier-based logic
  • Rebilling configuration for main accounts with sub-account markup
  • InstaSite credit tracking tied to billing cycles
  • Multi-currency pricing conversion

๐Ÿ—„๏ธ Database Collectionsโ€‹

๐Ÿ“š Full Schema: See Database Collections Documentation

onebalanceโ€‹

  • Operations: Read/Write for balance and configuration management
  • Model: shared/models/onebalance.js
  • Usage: Stores account credit balance (cents), auto-reload settings, and rebilling configuration

onebalance.usage_logsโ€‹

  • Operations: Read for InstaSite usage calculation
  • Model: shared/models/onebalance-usage_logs.js
  • Usage: Count InstaSite usage within billing cycle for credit tracking

configsโ€‹

  • Operations: Read for base pricing by account tier
  • Model: shared/models/config.js
  • Usage: Retrieve service pricing by account_pricing_type (pro, plus, platinum)

_store.subscriptionsโ€‹

  • Operations: Read for billing cycle dates
  • Model: shared/models/store-subscription.js
  • Usage: Determine current billing period for InstaSite monthly credit resets

_accountsโ€‹

  • Operations: Read for account tier, parent relationships, pricing type
  • Model: shared/models/account.js
  • Usage: Determine pricing tier and rebilling relationships

๐Ÿ”„ Data Flowโ€‹

sequenceDiagram
participant Client
participant Service
participant DB as Database
participant Currency as Currency Util

Client->>Service: get(account_id)
Service->>DB: Find OneBalance

alt OneBalance not found
Service->>Service: initOnebalance()
Service->>DB: Create default OneBalance
end

Service->>Service: Convert cents โ†’ dollars (รท100)

alt Main Account
Service->>DB: Get Store Subscription
Service->>DB: Count InstaSite usage
Service->>Service: Calculate remaining credits
end

Service->>Currency: Convert USD โ†’ account currency
Service-->>Client: OneBalance with converted values

๐ŸŽฏ API Endpointsโ€‹

GET /v1/onebalanceโ€‹

Purpose: Retrieve OneBalance configuration and current balance

Authentication: Required (JWT)

Response:

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60a7f8d5e4b0d8f3a4c5e1b2",
"account_id": "60a7f8d5e4b0d8f3a4c5e1b1",
"balance": 150.5,
"reload": {
"enabled": true,
"threshold": 20.0,
"amount": 100.0,
"payment_method": "pm_xxx"
},
"rebill": {
"sms": {
"enabled": true,
"multiplier": 1.2
},
"email": {
"enabled": true,
"multiplier": 1.1
},
"listing": {
"enabled": true,
"value": 5.0,
"multiplier": null
},
"lighting_domain": {
"enabled": true,
"value": null,
"multiplier": 1.5
},
"instasite": {
"enabled": true,
"credits": 850
}
},
"created_at": "2024-10-01T10:00:00.000Z",
"updated_at": "2024-10-08T14:30:00.000Z"
}
}

Response Fields:

FieldTypeDescription
balanceNumberCurrent balance in dollars (converted from cents)
reload.enabledBooleanWhether auto-reload is enabled
reload.thresholdNumberBalance threshold for auto-reload (dollars)
reload.amountNumberAmount to reload (dollars)
reload.payment_methodStringStripe payment method ID
rebillObjectRebilling configuration (main accounts only)
rebill.{service}.enabledBooleanWhether rebilling is enabled for service
rebill.{service}.valueNumberFixed rebill price (if set)
rebill.{service}.multiplierNumberMultiplier on base price (if set)
rebill.instasite.creditsNumberRemaining credits this billing cycle

PUT /v1/onebalanceโ€‹

Purpose: Update OneBalance configuration (reload settings, rebilling)

Authentication: Required (JWT)

Request Body:

{
"reload": {
"enabled": true,
"threshold": 25.0,
"amount": 200.0,
"payment_method": "pm_newmethod123"
},
"rebill": {
"listing": {
"enabled": true,
"value": 6.0
},
"sms": {
"enabled": true,
"multiplier": 1.3
},
"instasite": {
"enabled": true,
"credits": 1500
}
}
}

Validation:

  • Threshold must be > 0
  • Amount must be > 0
  • Multiplier must be >= 1
  • Sub-accounts cannot update rebill settings

Response: Same structure as GET endpoint with updated values


GET /v1/onebalance/cost-per-itemโ€‹

Purpose: Get current pricing for all OneBalance services

Authentication: Required (JWT)

Response:

{
"success": true,
"message": "SUCCESS",
"data": {
"sms": {
"type": "dynamic",
"base_us_price": 0.0075,
"a2p_registration_pending": false
},
"email": {
"type": "fixed",
"amount": 0.001
},
"phone": {
"type": "dynamic",
"base_us_price": 1.0
},
"listing": {
"type": "fixed",
"amount": 50.0
},
"lighting_domain": {
"type": "fixed",
"amount": 10.0
},
"instasite": {
"type": "credit",
"credits": 850,
"credit_details": {
"total": 1000,
"used": 150,
"remaining": 850
},
"billing_cycle": {
"start": "2024-10-01T00:00:00.000Z",
"end": "2024-11-01T00:00:00.000Z"
}
},
"instareport": {
"type": "fixed",
"amount": 5.0
}
}
}

Pricing Types:

TypeDescriptionExamples
dynamicPrice fetched from external APISMS, Phone, Calls
fixedStatic price per itemEmail, Listing, Domain
creditMonthly credit allocationInstaSite

๐Ÿ”ง Service Methodsโ€‹

get({ account_id, account, currency, main, account_pricing_type })โ€‹

Purpose: Retrieves OneBalance configuration and current balance for an account. Auto-initializes if not exists. Converts cent-based storage to dollar display with currency conversion.

Parameters:

  • account_id (ObjectId) - Account identifier
  • account (Object) - Full account document
  • currency (String, optional) - Target currency for display (default: USD)
  • main (Boolean) - Whether account is main account
  • account_pricing_type (String, optional) - Pricing tier override

Returns: Promise<Object> - OneBalance document with converted values

Business Logic Flow:

  1. Query OneBalance

    • Find by account_id
    • If not found: call initOnebalance() to create default
  2. Validate Record

    • Throw notFound if initialization fails
  3. Convert Cent Storage to Dollars

    • balance รท 100
    • reload.threshold รท 100
    • reload.amount รท 100
  4. Main Account Processing

    • Convert rebill values: listing.value รท 100, lighting_domain.value รท 100
    • Calculate InstaSite credits: call getInstasiteCreditInfo()
    • Set rebill.instasite.credits to remaining credits
  5. Sub-Account Processing

    • Delete rebill object entirely (not applicable)
  6. Apply Currency Conversion (if not USD)

    • Convert all monetary values to target currency
  7. Return OneBalance

Key Business Rules:

  • All monetary values stored in cents (prevents floating-point errors)
  • Auto-initialization ensures record always exists
  • Main accounts receive rebilling configuration
  • Sub-accounts never see rebill settings
  • InstaSite credits calculated per billing cycle

Error Handling:

  • notFound(404): OneBalance initialization failed

Example Usage:

const balance = await onebalanceService.get({
account_id: req.account_id,
account: req.account,
currency: 'USD',
main: true,
});
// Returns: { balance: 125.50, reload: {...}, rebill: {...} }

Side Effects:

  • โš ๏ธ May create OneBalance record if not exists
  • โš ๏ธ Queries store subscription for billing cycle
  • โš ๏ธ Counts usage logs for credit calculation

update(account_id, data, currency, main)โ€‹

Purpose: Updates OneBalance configuration including reload settings and rebilling multipliers/values. Converts dollar inputs to cent storage.

Parameters:

  • account_id (ObjectId) - Account identifier
  • data (Object) - Update fields (nested structure)
  • currency (String, optional) - Display currency
  • main (Boolean) - Whether account is main account

Returns: Promise<Object> - Updated OneBalance with converted values

Business Logic Flow:

  1. Convert Dollars to Cents (if present)

    • reload.amount ร— 100
    • reload.threshold ร— 100
    • rebill.listing.value ร— 100
    • rebill.lighting_domain.value ร— 100
  2. Flatten Nested Object

    • Convert { reload: { amount: 10000 } } โ†’ { 'reload.amount': 10000 }
    • Uses flattenObject() utility
  3. Update Database

    • findOneAndUpdate({ account_id }, flattened, { new: true })
  4. Convert Response Back to Dollars

    • balance รท 100
    • reload.threshold รท 100
    • reload.amount รท 100
    • Main only: rebill.listing.value รท 100, rebill.lighting_domain.value รท 100
  5. Filter by Account Type

    • Main: Return all fields including rebill
    • Sub: Delete rebill object
  6. Return Updated Record

Updatable Fields:

{
reload: {
enabled: Boolean,
threshold: Number, // Dollars (will be ร— 100)
amount: Number, // Dollars (will be ร— 100)
payment_method: String
},
rebill: { // Main only
listing: { value: Number }, // Dollars (will be ร— 100)
lighting_domain: { value: Number },
instasite: { credits: Number }, // Raw number, not converted
sms: { multiplier: Number },
email: { multiplier: Number }
}
}

Key Business Rules:

  • Only monetary values converted (not multipliers or credits)
  • Partial updates supported (only send changed fields)
  • Sub-accounts cannot update rebill settings
  • Nested updates flattened for MongoDB dot notation

Example Usage:

const updated = await onebalanceService.update(
account_id,
{
reload: { enabled: true, threshold: 25.0, amount: 200.0 },
},
'USD',
true,
);

costPerItem({ account_id, account, currency, user_id })โ€‹

Purpose: Calculates per-service pricing for account, applying tier-based pricing, parent rebilling markup, and InstaSite credit information.

Parameters:

  • account_id (ObjectId) - Account identifier
  • account (Object) - Full account document with pricing_type, main, parent_account
  • currency (String, optional) - Target currency
  • user_id (ObjectId) - User ID for dynamic pricing requests

Returns: Promise<Object> - Service pricing map

Business Logic Flow:

  1. Fetch Base Pricing Config

    • Query configs collection
    • Match: type: 'onebalance-base-prices' AND account_pricing_type: pricing_type
    • Throw notFound if config missing
  2. Handle InstaSite Credits

    • Call getInstasiteCreditInfo({ account_id, account })
    • Replace prices['instasite'] with credit details
    • Skip conversion (credit-based, not monetary)
  3. Convert Base Prices to Account Currency

    • For each service (skip instasite):
      • Dynamic pricing: Convert base_us_price / 100 from USD to currency
      • Fixed pricing: Convert amount / 100 from USD to currency
    • Uses CurrencyUtil.convert()
  4. Apply Sub-Account Rebilling (if not main)

    • Find parent OneBalance
    • Throw notFound if no rebill config
    • For each service (skip instasite):
      • Dynamic: base_us_price ร— rebill[service].multiplier
      • Fixed with value override: Use rebill[service].value / 100 (converted)
      • Fixed with multiplier: amount ร— rebill[service].multiplier
  5. Add A2P Registration Flag

    • If Twilio A2P not verified: prices['sms'].a2p_registration_pending = true
  6. Return Pricing Map

Pricing Calculation Examples:

// Dynamic pricing with sub-account markup
Base Price: $0.0075 (from config)
Parent Rebill Multiplier: 1.3
Sub-Account Price: $0.0075 ร— 1.3 = $0.00975

// Fixed pricing with value override
Base Price: $25.00
Parent Rebill Value: $50.00 (overrides base)
Sub-Account Price: $50.00

Key Business Rules:

  • Base pricing retrieved by account pricing_type (pro/plus/platinum)
  • Sub-accounts inherit parent pricing with markup
  • InstaSite excluded for sub-accounts (main account feature)
  • Rebill can use multiplier (percentage) OR value (fixed override)
  • A2P flag warns about SMS pricing limitations
  • All prices converted to account's display currency

Error Handling:

  • notFound(404): Price config not found for tier
  • notFound(404): Parent OneBalance missing rebill configuration

Side Effects:

  • โš ๏ธ Queries store subscription for billing cycle
  • โš ๏ธ Counts usage logs for InstaSite credits
  • โš ๏ธ May call external Twilio API for dynamic pricing

getInstasiteCreditInfo({ account_id, account })โ€‹

Purpose: Calculates remaining InstaSite credits for current billing cycle. Handles both subscription-based and tier-override billing.

Parameters:

  • account_id (ObjectId) - Account identifier
  • account (Object) - Account document with main, parent_account, tier_override

Returns: Promise<Object|null> - Credit information or null if no billing

Business Logic Flow:

  1. Find Active Subscription

    • Query StoreSubscription:
      • Account: account_id OR parent_account (for sub-accounts)
      • Plan: metadata.software = 'true' AND metadata.product_type = 'software'
      • Status: active, trialing, or past_due
    • Sort by updated_at descending (get latest)
  2. Check Tier Override

    • Call tierOverride(account_id, tier_override)
    • If tier override exists but no plan returned: return null
    • If tier override missing and no subscription: return null
  3. Extract Billing Period

    • Tier Override: Use start_date and renewal (unix timestamps)
    • Subscription: Use current_period_start and current_period_end
    • Convert unix timestamps to Date objects (ร— 1000)
  4. Get Configured Credit Limit

    • Query OneBalance for main account or parent
    • Extract rebill.instasite.credits
    • Default: 1000 credits if not configured
  5. Count Usage in Billing Cycle

    • Query OnebalanceLogs:
      • account_id: account OR parent
      • event: 'instasite'
      • status: 'success'
      • created_at: Between currentPeriodStart and currentPeriodEnd
    • Use countDocuments() for efficiency
  6. Calculate Remaining Credits

    • remaining = initialCredits - instasiteUsage
  7. Return Credit Object

Key Business Rules:

  • Credits reset monthly based on subscription billing cycle
  • Sub-accounts use parent's subscription and credit allocation
  • Tier overrides take precedence over subscriptions
  • Only successful InstaSite generations counted
  • Default allocation: 1000 credits/month
  • Main accounts only (sub-accounts don't get this info)

Edge Cases:

  • No Subscription: Returns null (no credit allocation)
  • Tier Override Without Plan: Returns null
  • Negative Credits: Possible if configured limit reduced mid-cycle
  • Parent Account Lookup: Sub-accounts must use parent's billing

Example Usage:

const creditInfo = await onebalanceService.getInstasiteCreditInfo({
account_id: req.account_id,
account: req.account,
});
// Returns: { credits: 850, credit_details: {...}, billing_cycle: {...} }

๐Ÿ’ฐ Pricing Hierarchyโ€‹

Main Account Pricingโ€‹

Base Provider Price (e.g., Twilio)
โ†“
Platform Markup (1.05x)
โ†“
Price Charged to Main Account

Sub-Account Pricingโ€‹

Base Provider Price
โ†“
Platform Markup (1.05x)
โ†“
Parent Rebill Multiplier (1.2x)
โ†“
Price Charged to Sub-Account

Example SMS Calculation:

// Twilio charges: $0.0075 per SMS
// Platform markup: 1.05
// Main account pays: $0.0075 ร— 1.05 = $0.007875

// Parent sets rebill multiplier: 1.2
// Sub-account pays: $0.007875 ร— 1.2 = $0.00945
// Parent profit: $0.00945 - $0.007875 = $0.001575 per SMS

๐Ÿ’ณ Auto-Reload Systemโ€‹

Configurationโ€‹

reload: {
enabled: true,
threshold: 20.00, // Trigger when balance < $20
amount: 100.00, // Reload $100
payment_method: "pm_..." // Saved payment method
}

Trigger Logicโ€‹

Checked during verifyBalance() before each transaction:

if (onebalance.balance < onebalance.reload.threshold && onebalance.reload.enabled) {
// Queue auto-reload job
await chargePaymentMethod({
amount: onebalance.reload.amount,
payment_method: onebalance.reload.payment_method,
});

// Add to balance
onebalance.balance += onebalance.reload.amount;
}

Failure Handlingโ€‹

  • Email notification sent to account owner
  • Balance remains unchanged
  • Transaction proceeds if balance sufficient
  • Transaction fails if balance insufficient

๐Ÿข Rebilling Configurationโ€‹

Main Account Controlsโ€‹

rebill: {
sms: {
enabled: true,
multiplier: 1.2 // Charge sub-accounts 1.2x
},
email: {
enabled: true,
multiplier: 1.15
},
listing: {
enabled: true,
value: 15.00, // Fixed price to sub-accounts
multiplier: null // Ignored if value set
},
instasite: {
enabled: true,
credits: 10 // Monthly credits per sub-account
}
}

Rebilling Rulesโ€‹

  • Multiplier-based: Used for dynamic pricing (SMS, email, calls)
  • Value-based: Used for static pricing (listings, domains)
  • Credits: Used for InstaSites (monthly allocation)

Disable Rebilling:

rebill.sms.enabled = false; // Sub-accounts cannot use SMS

๐Ÿ’ฐ Balance Managementโ€‹

Balance Storageโ€‹

  • Database: Stored as cents (integers)
  • API: Returned as dollars (floats)

Conversion:

// Reading
balance_dollars = balance_cents / 100;

// Writing
balance_cents = balance_dollars * 100;

Balance Updatesโ€‹

Debit (usage):

await OnebalanceLogs.create({
account_id,
event: 'sms',
cost: 8, // 8 cents
type: 'debit',
status: 'success',
});
// Balance reduced automatically via queue

Credit (reload, refund):

await OnebalanceLogs.create({
account_id,
event: 'refill',
cost: 10000, // $100 = 10000 cents
type: 'credit',
status: 'success',
});

๐Ÿ”’ Security & Validationโ€‹

Authorizationโ€‹

  • Main accounts: Full access to rebill configuration
  • Sub-accounts: Cannot view/edit rebill settings
  • Each account can only access their own OneBalance

Input Validationโ€‹

// Threshold must be positive
if (body.reload.threshold <= 0) {
throw badRequest('Threshold must be greater than 0');
}

// Multiplier must be >= 1 (cannot sell below cost)
if (body.rebill.sms.multiplier < 1) {
throw badRequest('Multiplier must be >= 1');
}

// Payment method required if auto-reload enabled
if (body.reload.enabled && !body.reload.payment_method) {
throw badRequest('Payment method required for auto-reload');
}

๐Ÿ”— Integration Pointsโ€‹

External Servicesโ€‹

  1. Twilio Pricing API

    • Endpoint: /v1/e/twilio/pricing/{type}?country=US
    • Types: sms, phone, call
    • Status: Currently unused (base prices used instead)
  2. Currency Utility

    • Class: CurrencyUtil from shared/utilities/currency.js
    • Method: convert(account_id, amount, from_currency, to_currency)
  3. Stripe Integration

    • Payment method storage
    • Auto-reload charge processing

Internal Dependenciesโ€‹

  • shared/utilities/onebalance.js - initOnebalance() for auto-creation
  • shared/utilities/credits.js - tierOverride() for manual tier overrides
  • shared/utilities/index.js - flattenObject() for nested updates
  • shared/utilities/catch-errors.js - Error helpers (notFound, badRequest)
  • shared/utilities/constants.js - DEFAULT_PRICING_TYPE

๐Ÿงช Edge Cases & Special Handlingโ€‹

OneBalance Auto-Initializationโ€‹

Condition: First access for new account
Handling: Call initOnebalance() to create default configuration
Result: Seamless experience, always returns valid record

InstaSite Credits After Config Changeโ€‹

Condition: Admin reduces credit allocation mid-cycle
Handling: Calculation may show negative credits
Display: Frontend should show 0 if negative

Sub-Account Without Parent Rebillโ€‹

Condition: Parent account hasn't configured rebilling
Handling: Throw notFound('Onebalance billing record not found')
Prevention: Parent must set rebill config before sub-account usage

Currency Conversion Failureโ€‹

Condition: Currency service unavailable
Handling: Currently no error handling (will throw)
Improvement: Should fall back to USD

A2P Registration Pendingโ€‹

Condition: Twilio A2P campaign not verified
Handling: Add a2p_registration_pending: true flag to SMS pricing
Display: Frontend shows warning about SMS limitations


โš ๏ธ Important Notesโ€‹

  • ๐Ÿ’ต Cent-Based Storage: All monetary values stored in cents (ร— 100) to prevent floating-point errors
  • ๐Ÿ”„ Conversion Pattern: Input dollars โ†’ convert to cents โ†’ store โ†’ retrieve cents โ†’ convert to dollars
  • ๐ŸŽฏ Main vs Sub: Main accounts get rebill config, sub-accounts get pricing with markup
  • ๐Ÿ“Š InstaSite Credits: Reset monthly based on subscription billing cycle
  • ๐ŸŒ Currency Support: All prices converted to account's display currency
  • ๐Ÿ” Tier-Based Pricing: Different base prices for pro/plus/platinum tiers
  • ๐Ÿ“ˆ Dynamic Pricing: Twilio integration exists but unused (base prices used)
  • ๐Ÿ”„ Billing Cycle: Tied to Store subscription, not calendar month
  • โš ๏ธ No Transaction: Updates not wrapped in transactions (potential inconsistency)
  • ๐Ÿšจ Missing Validation: No checks for negative balance or invalid multipliers

  • Parent Module: OneBalance Module
  • Related Service: Analytics Service
  • Controller: internal/api/v1/onebalance/controllers/onebalance.js
  • Routes: internal/api/v1/onebalance/routes/index.js
  • Utilities:
    • OneBalance Verification (link removed - file does not exist)
    • Currency Conversion (link removed - file does not exist)
    • Credits Management (link removed - file does not exist)
๐Ÿ’ฌ

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