Appearance
Overview
The Pocketbook billing system uses batch invoicing instead of real-time per-transaction charging. Usage events are recorded immediately for tracking purposes, but customers are invoiced on their billing cycle (bi-weekly or monthly).
This approach provides:
- Better cash flow management for customers
- Reduced Stripe fees (fewer transactions)
- Consolidated invoicing for easier accounting
- Flexible billing cycles (bi-weekly, monthly, quarterly, annual)
How It Works
1. Usage Recording (Real-Time)
When a customer creates a certificate, voucher, or performs a bulk mint:
typescript
// Usage event created immediately
const usageEvent = await BillingService.recordUsage(
userId,
'certificate_minted',
quantity,
'certificate',
metadata
);
// Event stored in database but NOT charged immediately
// totalCost is calculated based on tiered pricing
// Event status: includedInInvoiceId = null, invoicedAt = null2. Invoice Generation (Batch Processing)
The AutomatedInvoicingService runs periodically (daily via cron) to generate invoices for customers whose billing periods have ended:
typescript
// Automated billing cycle processing
const result = await AutomatedInvoicingService.processBillingCycle();
// For each customer ready for billing:
// 1. Collect all uninvoiced usage events from billing period
// 2. Calculate total cost with tax
// 3. Create Stripe invoice with line items
// 4. Mark all events as invoiced (includedInInvoiceId set)
// 5. Reset billing period for next cycle3. Usage Event Lifecycle
[Created] → [Pending Invoice] → [Invoiced] → [Paid]
↓ ↓ ↓ ↓
New Waiting for Invoice Payment
Event billing cycle generated received
to endEvent Fields:
includedInInvoiceId: Stripe invoice ID (null until invoiced)invoicedAt: Timestamp when included in invoicereportedToStripe: Legacy compatibility (set to true when invoiced)
Billing Cycles
Supported Cycles
| Cycle | Duration | Description |
|---|---|---|
biweekly | 14 days | Invoice every 2 weeks |
monthly | 1 month | Invoice at month end (default) |
quarterly | 3 months | Invoice every quarter |
annual | 12 months | Invoice once per year |
Setting Billing Cycle
For New Customers:
typescript
await CustomerBilling.create({
userId: 'user_123',
stripeCustomerId: 'cus_abc',
billingEmail: 'customer@example.com',
billingCycle: 'biweekly', // or 'monthly', 'quarterly', 'annual'
// ... other fields
});For Existing Customers:
javascript
db.customerbilling.updateOne(
{ userId: "user_123" },
{ $set: { billingCycle: "biweekly" } }
)Invoice Structure
Each invoice includes:
Line Items
Certificate Usage
- Description: "Certificate Usage (X certificates) - MM/DD/YYYY to MM/DD/YYYY"
- Amount: Based on tiered pricing
- Metadata: certificateCount, userId, billingPeriod
Voucher Usage
- Description: "Voucher Lifecycle (X created / Y deleted)"
- Amount: Based on net voucher creation
- Metadata: voucherCounts, userId, billingPeriod
API Usage (if applicable)
- Description: "API Usage (X calls)"
- Amount: Based on API call pricing
- Metadata: apiCallCount, userId, billingPeriod
Sales Tax
- Description: "Sales Tax (Texas 6.25%)"
- Amount: 6.25% of subtotal
- Metadata: taxRate, taxType
Invoice Metadata
typescript
{
userId: 'user_123',
enterpriseId: 'ent_456',
billingPeriodStart: '01/01/2025',
billingPeriodEnd: '01/31/2025',
certificateCount: '150',
subtotal: '97.50',
taxAmount: '6.09',
totalCost: '103.59',
paymentTerms: 'immediate',
billingCycle: 'monthly'
}Payment Terms
| Term | Due Date | Collection Method |
|---|---|---|
immediate | On receipt | Auto-charge payment method |
net30 | 30 days | Send invoice email |
net60 | 60 days | Send invoice email |
net90 | 90 days | Send invoice email |
Custom Contract Pricing
Customers with customContractPricing.enabled = true follow the same batch billing flow but:
- ✅ Still have usage events recorded
- ✅ Still receive invoices at billing cycle
- ❌ NOT auto-charged (manual payment expected)
- 📧 Invoice sent via email for manual processing
Automated Invoicing
Cron Schedule
The AutomatedInvoicingService should run daily at 2:00 AM UTC:
javascript
// Cron expression: "0 2 * * *"
// Runs: Every day at 2:00 AM UTC
cron.schedule('0 2 * * *', async () => {
console.log('Starting automated billing cycle...');
const result = await AutomatedInvoicingService.processBillingCycle();
console.log(`Billing cycle complete: ${result.successful} invoices generated`);
});Manual Trigger
typescript
// Trigger billing cycle manually (admin only)
const result = await AutomatedInvoicingService.processBillingCycle();
console.log({
processed: result.processed, // Total customers checked
successful: result.successful, // Invoices created
failed: result.failed, // Errors encountered
totalAmount: result.totalAmount // Total $ invoiced
});Querying Usage Events
Get Uninvoiced Events
typescript
// All uninvoiced events across all customers
const uninvoiced = await UsageEvent.find({
includedInInvoiceId: { $exists: false }
});
// Uninvoiced events for specific customer
const customerUninvoiced = await UsageEvent.find({
userId: 'user_123',
includedInInvoiceId: { $exists: false }
});Get Events by Invoice
typescript
// All events included in a specific invoice
const invoiceEvents = await UsageEvent.find({
includedInInvoiceId: 'in_abc123'
});
// Calculate invoice total from events
const total = invoiceEvents.reduce((sum, event) => sum + event.totalCost, 0);Get Events by Period
typescript
// All events in a billing period (invoiced or not)
const periodEvents = await UsageEvent.find({
userId: 'user_123',
timestamp: {
$gte: new Date('2025-01-01'),
$lt: new Date('2025-02-01')
}
});Migration from Real-Time Billing
Before (Per-Usage Charging)
typescript
// OLD: Immediate Stripe charge
if (isCertificate && totalCost > 0 && !isCustomContractCustomer) {
MeteredBillingService.reportUsageToStripe(usageEvent).catch(error => {
console.error(`Failed to report usage event to Stripe:`, error);
});
}After (Batch Billing)
typescript
// NEW: Record event, invoice later
if (totalCost > 0) {
console.log(`Usage recorded, invoice will be generated at billing cycle (${billingCycle})`);
}
// AutomatedInvoicingService handles charging at billing cycle endBenefits
For Customers
✅ Predictable billing - One invoice per cycle instead of dozens ✅ Better budgeting - Know monthly/bi-weekly costs in advance ✅ Easier accounting - Single invoice to reconcile ✅ Flexible payment - Choose immediate or net30/60/90 terms
For Platform
✅ Lower Stripe fees - Fewer transactions = lower per-transaction fees ✅ Reduced API calls - Batch processing instead of per-event calls ✅ Better analytics - Period-based reporting ✅ Scalability - Handles high-volume customers efficiently
Monitoring
Dashboard Metrics
- Uninvoiced usage events (total and per customer)
- Upcoming invoices (next billing cycle)
- Failed invoice generation attempts
- Revenue recognized vs. invoiced
Logs to Monitor
typescript
// Successful invoice generation
[AutomatedInvoicing] Successfully generated invoice for customer user_123: $103.59
// Events marked as invoiced
[AutomatedInvoicing] Marked usage events as invoiced for customer user_123, invoice in_abc123
// Batch completion
[AutomatedInvoicing] Billing cycle completed: 42 successful, 0 failed, total $4,350.82Troubleshooting
Issue: Customer Has Uninvoiced Events Past Billing Period
Cause: Billing cycle didn't run or failed Solution:
typescript
// Manually generate invoice for customer
const customer = await CustomerBilling.findByUserId('user_123');
const result = await AutomatedInvoicingService.generateInvoiceForCustomer(customer);Issue: Events Invoiced Twice
Protection: Events with includedInInvoiceId are skipped during billing Check:
typescript
const duplicates = await UsageEvent.aggregate([
{ $match: { includedInInvoiceId: { $exists: true } } },
{ $group: { _id: '$includedInInvoiceId', count: { $sum: 1 } } },
{ $match: { count: { $gt: 1 } } }
]);Issue: Billing Cycle Not Running
Check Cron:
bash
# Verify cron job is active
crontab -l | grep "billing"
# Check server logs for cron execution
tail -f /var/log/billing-cron.logAPI Endpoints
Get Customer Usage (Current Period)
http
GET /api/billing/usage
Authorization: Bearer <jwt>
Response:
{
"certificatesThisPeriod": 150,
"vouchersCreatedThisPeriod": 25,
"currentPeriodCost": 97.50,
"billingCycle": "monthly",
"billingPeriodEnd": "2025-01-31T23:59:59.999Z",
"uninvoicedEvents": 175
}Get Upcoming Invoice Preview
http
GET /api/billing/upcoming-invoice
Authorization: Bearer <jwt>
Response:
{
"estimatedTotal": 103.59,
"subtotal": 97.50,
"tax": 6.09,
"certificateCount": 150,
"voucherCount": 25,
"dueDate": "2025-02-01",
"billingCycle": "monthly"
}Related Documentation
Support
For issues with batch billing:
- Check AutomatedInvoicingService logs
- Verify cron job is running daily
- Check for uninvoiced events past billing period
- Review customer billing cycle settings
- Contact billing team for manual invoice generation if needed
