Atomic Billing Mechanism
Database transaction-based atomic billing mechanism that ensures consistency between credit deductions and task execution, never losing credits and never charging twice.
💡 Core Promise: Automatic refunds for failed tasks, atomic guarantee for billing and execution, complete transaction audit logs.
1. What is Atomic Billing
1.1 Atomicity Concept
Atomicity is one of the ACID properties in databases, meaning a group of operations either all succeed or all fail, with no intermediate states.
Applied to Billing Scenarios:
- ✅ Billing success + Task execution success → User pays, receives service
- ✅ Billing failure → Task doesn't execute
- ✅ Task execution failure → Automatic refund (rollback billing)
- ❌ Never allowed: Billing success but task not executed (user loss)
- ❌ Never allowed: Task execution success but no billing (system loss)
1.2 Why Atomic Billing Is Needed
Problem Scenario 1: Network Failure
Traditional Approach: 1. Deduct 100 credits → Success 2. Call LLM API → Network timeout, failure 3. Result: User loses 100 credits but gets no service Atomic Billing Approach: 1. Start transaction 2. Deduct 100 credits (not committed yet) 3. Call LLM API → Failure 4. Transaction rollback, credits returned 5. Result: User credits intact ✅
Problem Scenario 2: System Crash
Traditional Approach: 1. Deduct 100 credits → Success 2. Start report generation → Server crashes 3. Result: User loses 100 credits, report not generated Atomic Billing Approach: 1. Start transaction 2. Deduct 100 credits (not committed yet) 3. Start report generation → Server crashes 4. Transaction auto-rollback (database guarantee) 5. Result: User credits intact ✅
Problem Scenario 3: Duplicate Billing
Traditional Approach: 1. User clicks "Generate Report" 2. Deduct 100 credits 3. Network delay, frontend timeout 4. User clicks again 5. Deduct 100 credits again 6. Result: Duplicate billing of 200 credits ❌ Atomic Billing Approach: 1. Check if there's an in-progress task 2. If yes → Reject duplicate submission 3. If no → Start transaction 4. Billing + Create task record (atomic operation) 5. Result: Avoid duplicate billing ✅
2. Technical Implementation of Atomic Billing
2.1 Database Transaction Mechanism
Tech Stack: PostgreSQL + Prisma ORM
Transaction Example:
[object Object], ,[object Object], ,[object Object], ,[object Object],(,[object Object],) { ,[object Object], ,[object Object], ,[object Object], prisma.$transaction(,[object Object], (tx) => { ,[object Object], ,[object Object], user = ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { ,[object Object],: userId } }) ,[object Object], (user.,[object Object], < estimatedCost) { ,[object Object], ,[object Object], ,[object Object],(,[object Object],) } ,[object Object], ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { ,[object Object],: userId }, ,[object Object],: { ,[object Object],: user.,[object Object], - estimatedCost } }) ,[object Object], ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { userId, ,[object Object],: -estimatedCost, ,[object Object],: ,[object Object],, ,[object Object],: ,[object Object], } }) ,[object Object], ,[object Object], report = ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { userId, templateId, ,[object Object],: ,[object Object], } }) ,[object Object], ,[object Object], report }) ,[object Object], ,[object Object], }hljs javascript
Key Points:
- ✅ All operations in same transaction (billing, record creation, status update)
- ✅ Any step failure rolls back entire transaction
- ✅ Only truly billed after commit (irreversible)
2.2 Automatic Refund Mechanism
Refund Flow:
- Task execution fails (Worker detects exception)
- Worker callback (notifies system of task failure)
- Query billing record (find corresponding credit transaction by task ID)
- Create refund transaction (add new record with )
amount > 0 - Update user credits ()
credits += refundAmount - Update task status ()
status = FAILED
Refund Example:
[object Object], ,[object Object], ,[object Object], ,[object Object],(,[object Object],) { ,[object Object], ,[object Object], prisma.$transaction(,[object Object], (tx) => { ,[object Object], ,[object Object], task = ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { ,[object Object],: taskId } }) ,[object Object], transaction = ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { ,[object Object],: ,[object Object],, ,[object Object],: taskId, ,[object Object],: { ,[object Object],: ,[object Object], } ,[object Object], } }) ,[object Object], ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { ,[object Object],: task.,[object Object],, ,[object Object],: ,[object Object],.,[object Object],(transaction.,[object Object],), ,[object Object], ,[object Object],: ,[object Object],, ,[object Object],: ,[object Object], } }) ,[object Object], ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { ,[object Object],: task.,[object Object], }, ,[object Object],: { ,[object Object],: { ,[object Object],: ,[object Object],.,[object Object],(transaction.,[object Object],) } } }) ,[object Object], ,[object Object], tx.,[object Object],.,[object Object],({ ,[object Object],: { ,[object Object],: taskId }, ,[object Object],: { ,[object Object],: ,[object Object],, ,[object Object],: reason } }) }) }hljs javascript
2.3 Duplicate Submission Protection
Protection Mechanisms:
- Idempotency Check: Check for same task in progress before submission
- Unique Constraints: Database-level prevention of duplicate inserts
- State Machine: Task status can only flow one way (PENDING → PROCESSING → SUCCESS/FAILED)
Check Example:
[object Object], ,[object Object], ,[object Object], ,[object Object],(,[object Object],) { ,[object Object], ,[object Object], existingTask = ,[object Object], prisma.,[object Object],.,[object Object],({ ,[object Object],: { userId, templateId, ,[object Object],: { ,[object Object],: [,[object Object],, ,[object Object],] } } }) ,[object Object], (existingTask) { ,[object Object], ,[object Object], ,[object Object],(,[object Object],) } ,[object Object], ,[object Object], ,[object Object], ,[object Object],(userId, templateId) }hljs javascript
PlaceholderAtomic billing flow diagram - showing complete flow from billing to task execution to refund
3. Comparison with Traditional Billing
| Feature | Traditional Billing | Atomic Billing |
|---|---|---|
| Billing-Execution Consistency | ❌ Not guaranteed | ✅ Transaction guaranteed |
| Refund on Task Failure | ❌ Manual application | ✅ Automatic refund |
| Duplicate Billing Risk | ⚠️ Risk exists | ✅ Idempotency check |
| Credit Loss Risk | ⚠️ Possible during system failure | ✅ Transaction rollback protection |
| Audit Trail | ⚠️ May be incomplete | ✅ Complete transaction records |
| User Trust | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
3.1 Real Case Comparisons
Case 1: Report Generation Failure
| Step | Traditional Billing | Atomic Billing |
|---|---|---|
| 1. Click "Generate Report" | Deduct 200 credits | Start transaction, temporarily deduct 200 credits |
| 2. Execute generation | LLM API fails | LLM API fails |
| 3. Handle failure | Mark task failed | Transaction rollback + Auto-refund |
| 4. User action | Submit refund request | No action needed (already refunded) |
| 5. Final result | Refund in 3-7 days | Immediate refund ✅ |
Case 2: Network Delay Duplicate Submission
| Step | Traditional Billing | Atomic Billing |
|---|---|---|
| 1. First click | Deduct 200 credits | Deduct 200 credits |
| 2. Network delay | Frontend timeout | Frontend timeout |
| 3. User clicks again | Deduct 200 credits again | Detect task in progress, reject |
| 4. Final result | Duplicate billing 400 credits ❌ | Only billed 200 credits ✅ |
4. Transaction Audit Logs
4.1 Complete Transaction Records
Record Contents:
- : Unique transaction ID
id - : User ID
userId - : Amount (positive=top-up/refund, negative=billing)
amount - : Transaction type (REPORT_GENERATION, CONTENT_COLLECTION, CHAT, REFUND, etc.)
type - : Transaction description
description - : Transaction time
createdAt - : Associated task ID, report ID, etc.
metadata
Query Example:
// User credit consumption details Credit Management → Consumption Details Sample records: ┌─────────────────────┬────────┬──────────────┬────────────────────────┐ │ Time │ Amount │ Type │ Description │ ├─────────────────────┼────────┼──────────────┼────────────────────────┤ │ 2025-10-27 10:00 │ -200 │ Report Gen │ Daily news summary │ │ 2025-10-27 10:05 │ +200 │ Refund │ Auto-refund task fail │ │ 2025-10-27 11:00 │ -50 │ Collection │ RSS feed execution │ │ 2025-10-27 12:00 │ -20 │ Ask │ Library Q&A │ │ 2025-10-27 14:00 │ +10000 │ Redeem Code │ Code: ABC123 │ └─────────────────────┴────────┴──────────────┴────────────────────────┘
4.2 Audit Tracking Capability
Supported Queries:
- Query by time range (e.g., "Last 7 days")
- Filter by transaction type (e.g., "Only show report generation")
- Filter by amount (e.g., "Only show billings" or "Only show refunds")
- Export as CSV (for financial audit)
Data Consistency Guarantee:
- ✅ Every billing has corresponding task record
- ✅ Failed tasks must have refund records
- ✅ Credit balance = Initial credits + Sum of all transaction amounts
PlaceholderTransaction audit log interface - showing complete transaction list and filtering features
5. User Protection Measures
5.1 Credit Security Promise
✅ Our Promise:
- Task failure, 100% automatic refund, no application needed
- System failure, credits never lost (database transaction protection)
- Duplicate submission, auto-rejected, avoid duplicate billing
- All transactions, fully recorded, query anytime
5.2 Insufficient Balance Protection
Check Mechanisms:
- Pre-submission Check: Frontend displays estimated cost, disables button when balance insufficient
- Transaction Check: Re-check balance before billing, rollback transaction if insufficient
- Concurrency Control: Use database row-level locks to avoid overspending
Example Scenarios:
User current balance: 100 credits Situation 1: Single Task - Estimated cost: 80 credits - Check result: Balance sufficient ✅ - Action: Allow submission Situation 2: Concurrent Tasks - User submits 2 tasks simultaneously, 60 credits each - Transaction A: Deduct 60 credits → Balance 40 → Submit success - Transaction B: Attempt to deduct 60 credits → Balance insufficient → Rollback - Result: Only 1 task succeeds, avoid overspending ✅ Situation 3: Actual Cost Exceeds Estimate - Estimated cost: 100 credits - Actual cost: 120 credits (LLM tokens exceed estimate) - Handling: Task fails + Refund 100 credits - Prompt: Please top up and retry
5.3 Dispute Resolution Mechanism
If you find abnormal billing:
- View "Consumption Details" (Credit Management → Consumption Details)
- Find abnormal transaction record
- View associated task logs (click transaction record for details)
- If confirmed abnormal, contact technical support (provide transaction ID)
- Technical support will verify based on audit logs and handle
Dispute Handling Timeline:
- ✅ System automatic refund: Real-time (immediate after task failure)
- ✅ Manual review refund: 1-3 business days
6. Technical Advantages Summary
| Technical Feature | Implementation | User Benefit |
|---|---|---|
| Transaction Atomicity | PostgreSQL transaction + Prisma ORM | Billing-execution consistency, never lose credits |
| Auto-Refund | Worker callback + Transaction handling | Task failure immediate refund, no waiting |
| Duplicate Submission Protection | Idempotency check + State machine | Avoid duplicate billing, protect funds |
| Audit Logs | Complete transaction record table | Query anytime, disputes traceable |
| Concurrency Control | Database row-level locks | Avoid overspending, accurate balance |
7. Frequently Asked Questions
Q1: How soon will credits be refunded after task failure?
A: Immediately. When Worker detects task failure, it automatically triggers refund process (usually completes within 1-3 seconds after task failure). You can see refund record in "Consumption Details".
Q2: If I repeatedly click "Generate Report", will I be billed twice?
A: No. The system checks if the same task is executing, and if so, rejects duplicate submission with prompt "Task already in progress".
Q3: What if estimated cost differs from actual cost?
A: Estimated cost is a reference value calculated from historical data. If actual cost exceeds estimate:
- Balance insufficient → Task fails + Refund pre-deducted credits
- Balance sufficient → Bill at actual cost
- Recommendation: Keep some balance buffer (e.g., 1.5x estimated cost)
Q4: Can I view all historical transactions?
A: Yes. Go to "Credit Management → Consumption Details" to view all transaction records, supports:
- Filter by time range (e.g., "Last 30 days")
- Filter by transaction type (e.g., "Only show refunds")
- Filter by amount (e.g., "Only show transactions > 100 credits")
- Export as CSV (for personal accounting)
Q5: Does atomic billing affect system performance?
A: Impact is minimal. Transaction overhead is typically 1-5ms, almost imperceptible to user experience. Our optimization measures:
- Use database connection pool (reduce connection overhead)
- Execute time-consuming tasks asynchronously (report generation, content collection)
- Reasonable transaction granularity (only lock necessary rows)
Next Steps
- System Reliability - Learn about failover and health check mechanisms
- URL Deduplication Technology - Learn about content deduplication mechanisms
- Credits & Logs - View credit management and consumption details