# Hook System Design for Django Wallet Utils

## Overview

The hook system enables developers to register custom logic that executes before and after wallet transactions (add/deduct/transfer). Hooks can intercept transactions and determine whether they should proceed or be rejected.

**Document Version**: 1.0  
**Status**: Design Proposal  
**Last Updated**: 2025-12-21

---

## Problem Statement

Current wallet system allows operations (add, deduct, transfer) without a way for developers to:

1. **Intercept transactions** before they execute
2. **Perform business logic validation** (e.g., "user is not suspended", "daily limit not exceeded")
3. **Make decisions** on whether the transaction should proceed
4. **React to transaction events** after they complete
5. **Provide context to subsequent hooks** via the hook context

This makes it difficult to implement features like:
- Transaction limits (daily, monthly)
- User suspension checks
- Fraud detection
- Compliance checks
- Audit logging
- Notification sending
- Reward calculations

---

## Design Proposal: Hook System

### Core Concept

A **hook** is a callable (function or class method) that:

1. Executes at a specific point in the transaction lifecycle
2. Can inspect transaction details
3. Can make decisions (proceed/reject)
4. Can modify context for subsequent hooks
5. Can perform side effects (logging, notification, etc.)

### Transaction Lifecycle

```
┌─────────────────────────────────────────────────────────────────┐
│  Transaction Initiated (add_point/deduct_point/transfer_point)  │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
                ┌────────────────────────┐
                │  PRE Hooks Execution   │
                │  (can reject/proceed)  │
                └────────────┬───────────┘
                             │
              ┌──────────────┴──────────────┐
              │ All PRE hooks               │
              │ returned True?              │
              └──────────────┬──────────────┘
                             │
           ┌─────────────────┼─────────────────┐
           │ NO              │ YES             │
           ▼                 ▼                 ▼
      ┌────────┐      ┌──────────────┐   ┌──────────────┐
      │ REJECT │      │ Execute      │   │ POST Hooks   │
      │        │      │ Transaction  │   │ (reactions)  │
      │ Return │      │              │   │              │
      │ Error  │      │ Create Txn   │   │ Always run   │
      │        │      │ Record       │   │ (even on     │
      │        │      │              │   │  error)      │
      │        │      └──────────────┘   │              │
      │        │             │           │              │
      │        │             ▼           └──────────────┘
      │        │      ┌──────────────┐         │
      │        │      │ Return Txn   │         │
      │        │      │ ID           │         ▼
      │        │      └──────────────┘  ┌─────────────┐
      │        │             │          │ Return      │
      │        └─────────────┼──────────│ Result      │
      │                      │          │ or Error    │
      └──────────────────────┼──────────└─────────────┘
                             │
                             ▼
                    ┌────────────────┐
                    │ End            │
                    └────────────────┘
```

### Hook Types

#### 1. **PRE Hooks** (`pre_*`)
- Execute **before** the transaction
- Can inspect transaction details
- **Can reject** the transaction (return `False` or raise exception)
- **Can modify** hook context for subsequent hooks
- **Cannot modify** the transaction parameters (immutable)
- If any PRE hook returns `False`, transaction is rejected

Example: `pre_add_point`, `pre_deduct_point`, `pre_transfer_point`

#### 2. **POST Hooks** (`post_*`)
- Execute **after** the transaction completes (whether successful or failed)
- Can inspect transaction result and error information
- **Cannot reject** the transaction (decision already made)
- **Cannot modify** the transaction result
- Used for reactions/side effects: logging, notifications, etc.
- Exceptions in POST hooks don't affect transaction success

Example: `post_add_point`, `post_deduct_point`, `post_transfer_point`

---

## API Design

### Hook Registration

```python
from wallet_utils.hooks import HookRegistry

# Get global hook registry
hooks = HookRegistry.get_instance()

# Register PRE hook with priority (lower number = higher priority)
def validate_user_not_suspended(hook_context):
    """
    Validate that user is not suspended.
    
    Args:
        hook_context: HookContext containing transaction details
    
    Returns:
        bool: True if transaction should proceed, False to reject
    """
    user = get_user(hook_context.user_id)
    if user.is_suspended:
        return False
    return True

hooks.register_pre_add_point(
    'validate_not_suspended', 
    validate_user_not_suspended,
    priority=10  # Lower number = higher priority (default: 100)
)

# Register POST hook
def send_notification(hook_context, result):
    """
    Send notification to user after transaction.
    
    Args:
        hook_context: HookContext containing transaction details
        result: Transaction result (transaction_id or None if failed)
    """
    if result:
        notify_user(
            hook_context.user_id,
            f"Transaction {result} completed"
        )

hooks.register_post_add_point('notify_user', send_notification, priority=100)

# Register hook for multiple transaction types
hooks.register_pre(
    ['add_point', 'deduct_point'], 
    'validate_not_suspended', 
    validate_user_not_suspended,
    priority=10
)
```

### Hook Context

```python
from wallet_utils.hooks import HookContext

class HookContext:
    """Context passed to hooks containing transaction details."""
    
    # Transaction details
    user_id: int
    point_type: str
    amount: Decimal
    remarks: str
    trans_type: int | None
    iid: int  # Initiator ID
    
    # Extra metadata
    operation_type: str  # 'add', 'deduct', 'transfer'
    timestamp: datetime
    
    # Transfer-specific
    from_user_id: int  # Transfer only
    to_user_id: int    # Transfer only
    from_point_type: str  # Transfer only
    to_point_type: str    # Transfer only
    
    # For storing data between PRE hooks
    metadata: dict  # Mutable dictionary for sharing data between hooks
```

### Hook Method Signatures

```python
# PRE Hook Signature
def pre_hook(hook_context: HookContext) -> bool:
    """
    Validate transaction before execution.
    
    Returns:
        True: Transaction should proceed
        False: Transaction should be rejected
        
    Raises:
        HookRejectionError: To reject with structured error message
    """
    pass

# POST Hook Signature
def post_hook(hook_context: HookContext, result: int | None, error: Exception | None) -> None:
    """
    React to transaction after execution.
    
    Args:
        hook_context: Transaction details
        result: Transaction ID if successful, None if failed
        error: Exception if failed, None if successful
        
    Note:
        - Exceptions raised in POST hooks don't affect transaction success
        - POST hook exceptions are captured and returned separately
        - Cannot reject transaction at this point
    """
    pass
```

---

## Implementation Considerations

### 1. **Transaction Atomicity & Reliability**

**Key Issue**: If a PRE hook rejects a transaction, the business logic has already confirmed the operation is valid. This creates a trust issue.

**Design Decision**: 
- PRE hooks should be **validation only** (checking preconditions)
- PRE hooks should be **fast** and **side-effect free**
- **Never** have a PRE hook whose result depends on external state that could change
- External state checks should happen in business logic, not hooks

**Best Practice Example**:
```python
# ✅ GOOD: Hook validates immutable preconditions
def validate_amount_positive(hook_context):
    return hook_context.amount > 0  # Immutable precondition

# ❌ BAD: Hook depends on external state
def validate_user_has_permission(hook_context):
    # Problem: Permission could be revoked between check and execution
    user = fetch_from_api(hook_context.user_id)  # External call!
    return user.has_permission
```

### 2. **Hook Execution Order & Priority**

**Decision**: 
- PRE hooks execute **by priority** (lower number = higher priority)
- Hooks with same priority execute in registration order
- First failing PRE hook stops execution chain
- POST hooks execute **by priority** (lower number = higher priority)
- All POST hooks execute regardless of errors (exceptions captured)

**Priority System**:
- Default priority: 100
- Range: 1-999 (1 = highest priority, 999 = lowest)
- Common priorities:
  - 10: Critical validation (security, permissions)
  - 50: Business rules (limits, quotas)
  - 100: Default (general validation)
  - 500: Optional checks (fraud detection)
  - 900: Cleanup/notifications

**Rationale**:
- Priority allows control over hook execution order
- Predictable execution order helps with debugging
- Fast-fail prevents unnecessary validation
- POST hooks always run for cleanup/notification

### 3. **Error Handling & Frontend Communication**

**PRE Hooks**:
- Can return `False` (generic rejection)
- Can raise `HookRejectionError` with structured error details
- Any other exception is treated as hook error (rejected with error message)

**POST Hooks**:
- Exceptions don't affect transaction success
- Exceptions are captured and returned in transaction result
- Hook errors reported separately for frontend rendering
- Transaction status remains successful with `post_hook_errors` field

**Error Structure for Frontend**:
```python
from wallet_utils.hooks import HookRejectionError

# PRE hook rejection with structured error
def validate_daily_limit(hook_context):
    if limit_exceeded:
        raise HookRejectionError(
            error_code="DAILY_LIMIT_EXCEEDED",  # ALL_CAPS_SNAKE_CASE for frontend
            message="Daily transaction limit exceeded",  # Human-readable
            details={  # Optional: extra context
                "current_amount": "500.00",
                "limit": "1000.00",
                "remaining": "500.00"
            }
        )
    return True

# Service returns structured error
# On PRE hook rejection:
# {
#     "success": False,
#     "error_code": "DAILY_LIMIT_EXCEEDED",
#     "error_message": "Daily transaction limit exceeded",
#     "error_details": {...}
# }

# POST hook errors don't affect transaction
def send_notification(ctx, result, error):
    if result:
        # If this raises an exception, transaction is still successful
        send_email(...)  # Could fail
        
# On POST hook error:
# {
#     "success": True,  # Transaction succeeded
#     "transaction_id": 12345,
#     "post_hook_errors": [
#         {
#             "hook_name": "send_notification",
#             "error_code": "NOTIFICATION_FAILED",
#             "error_message": "Failed to send email"
#         }
#     ]
# }
```

**Frontend Integration Guidelines**:
1. PRE hook errors prevent transaction → show error immediately
2. POST hook errors don't prevent transaction → show warning/info
3. Use `error_code` (ALL_CAPS) for i18n and conditional rendering
4. Use `error_message` as fallback for display
5. Use `error_details` for rich error presentation

### 4. **Performance Impact**

**Concern**: Hooks could slow down transactions.

**Mitigation**:
- ~~PRE hooks must complete in <100ms (configurable timeout)~~ **REMOVED**: No timeout enforcement
- PRE hooks should be designed to be fast (developer responsibility)
- Hook execution time is logged for monitoring and debugging
- POST hooks run asynchronously if possible
- Option to disable hooks in settings for performance

**Design Philosophy**: 
- Trust developers to write performant PRE hooks
- Log execution time for visibility
- Provide monitoring tools to identify slow hooks

```python
# In settings.py
WALLET_UTILS = {
    'HOOKS_ENABLED': True,  # Set to False to skip all hooks
    'HOOKS_LOG_EXECUTION_TIME': True,  # Log hook execution time
    'HOOKS_ASYNC_POST': True,  # Run POST hooks asynchronously
}

# Hook execution time is logged
# logger.info(f"Hook {hook_name} took {duration}ms")
```

### 5. **Hook Scope & Isolation**

**Concern**: Hooks modifying shared state could affect each other.

**Design**:
- `hook_context.metadata` is mutable but isolated per transaction
- Each transaction gets its own HookContext instance
- Hooks should avoid global state modifications
- Thread-safe design required for concurrent transactions

### 6. **Testing & Debugging**

**Built-in Support**:
- Hook registry can be cleared between tests
- Hooks can be mocked/patched
- Hook execution time tracked
- Hook call history available in debug mode

```python
from wallet_utils.hooks import HookRegistry

# In tests
def test_transaction_with_hook():
    hooks = HookRegistry.get_instance()
    
    # Clear all hooks
    hooks.clear_all()
    
    # Register test hook
    called = []
    def test_hook(ctx):
        called.append(ctx)
        return True
    
    hooks.register_pre_add_point('test', test_hook)
    
    # Verify hook was called
    service.add_point(...)
    assert len(called) == 1
```

---

## Design Issues & Best Practices

### Issue 1: Hook Rejection vs Exception

**Problem**: How should developers reject transactions? Return `False` or raise exception?

**Solution**:
- **Primary Method**: Return `False` (simple, efficient)
- **Detailed Method**: Raise `HookRejectionError` with custom message
- Both are valid; choose based on needs

```python
# Simple rejection
def validate_something(ctx):
    return ctx.user_id != 0

# Detailed rejection with message
def validate_something_detailed(ctx):
    if ctx.user_id == 0:
        raise HookRejectionError("System user cannot perform this operation")
    return True
```

### Issue 2: Hook Dependencies

**Problem**: What if one PRE hook depends on result from another?

**Solution**:
- Use `hook_context.metadata` to share data
- Design hooks to be independent when possible
- Document hook execution order in docstrings

```python
def hook_1(ctx):
    """Calculate compliance score."""
    ctx.metadata['compliance_score'] = calculate_score(ctx.user_id)
    return True

def hook_2(ctx):
    """Check compliance score from hook_1."""
    score = ctx.metadata.get('compliance_score')
    return score >= 80  # Hook 1 must run first!
```

### Issue 3: Hook Security

**Problem**: Hooks could access/modify sensitive data.

**Solution**:
- Make HookContext immutable (except metadata)
- Use Django permissions to control hook registration
- Audit hook registration in production
- Document what data hooks can access

```python
# Production deployment
WALLET_UTILS = {
    'HOOKS_AUDIT_LOG': True,  # Log all hook registration/execution
    'HOOKS_READONLY_CONTEXT': True,  # Prevent context modification
}
```

### Issue 4: Hook Performance & Resource Limits

**Problem**: Long-running hooks could slow down transactions.

**Solution**:
- ~~Implement timeout mechanism for PRE hooks~~ **REMOVED**: No timeout enforcement
- Document performance expectations (aim for < 100ms)
- Provide hook execution metrics and monitoring
- Option to disable slow hooks
- Developer responsibility to ensure hook performance

```python
# Settings
WALLET_UTILS = {
    'HOOKS_LOG_EXECUTION_TIME': True,  # Log all hook execution times
    'HOOKS_SLOW_THRESHOLD': 0.1,  # Warn if > 100ms
}

# Hook execution time is logged
# Warning logged if hook exceeds threshold
# Slow hooks can be identified and optimized or disabled
```

### Issue 5: Backwards Compatibility

**Problem**: Adding hooks shouldn't break existing code.

**Solution**:
- Hook system is opt-in (no hooks by default)
- Existing WalletService API unchanged
- No performance impact if no hooks registered
- Can migrate gradually

```python
# Existing code works unchanged
service.add_point(...)  # Works even with hook system

# New code can use hooks
hooks.register_pre_add_point('my_hook', my_validation)
service.add_point(...)  # Hook is called
```

---

## Implementation Plan

### Phase 1: Core Hook System

1. **Create `HookContext` class**
   - Define transaction context structure
   - Make immutable (except metadata)
   - Add serialization for logging

2. **Create `HookRegistry` singleton**
   - Global hook registration
   - PRE/POST hook storage
   - Hook lookup and execution

3. **Create `HookRejectionError` exception**
   - Custom rejection with message
   - Inherits from `WalletOperationError`

4. **Integrate with `WalletService`**
   - Call PRE hooks before operations
   - Call POST hooks after operations
   - Handle hook rejections

### Phase 2: Testing & Documentation

1. **Unit Tests**
   - Hook registration tests
   - Hook execution tests
   - Hook rejection tests
   - Error handling tests

2. **Integration Tests**
   - Hooks with actual transactions
   - Concurrent transactions with hooks
   - Hook execution order

3. **Documentation**
   - Hook system guide
   - API reference
   - Best practices
   - Examples

### Phase 3: Advanced Features (Optional)

1. **Async Hooks**
   - POST hooks run asynchronously
   - Celery/threading support

2. **Hook Middleware**
   - Chain multiple hooks
   - Hook pipelines

3. **Hook Metrics**
   - Track hook execution time
   - Monitor hook errors
   - Performance dashboards

---

## Example Use Cases

### 1. User Suspension Check

```python
from wallet_utils.hooks import HookRegistry, HookRejectionError

hooks = HookRegistry.get_instance()

def check_user_suspended(ctx):
    """Reject if user is suspended."""
    user = User.objects.get(id=ctx.user_id)
    if user.is_suspended:
        raise HookRejectionError(
            error_code="USER_SUSPENDED",
            message=f"User account is suspended",
            details={
                "user_id": ctx.user_id,
                "suspended_at": user.suspended_at.isoformat(),
                "reason": user.suspension_reason
            }
        )
    return True

hooks.register_pre(
    ['add_point', 'deduct_point', 'transfer_point'], 
    'check_suspended', 
    check_user_suspended,
    priority=10  # High priority - security check
)
```

### 2. Daily Transaction Limit

```python
from django.utils import timezone
from decimal import Decimal

def check_daily_limit(ctx):
    """Reject if user exceeds daily transaction limit."""
    today = timezone.now().date()
    
    # Get today's transaction total
    today_total = WalletTransaction.objects.filter(
        uid=ctx.user_id,
        wtype=ctx.point_type,
        type='d',  # Debit only
        cdate__date=today
    ).aggregate(total=Sum('amount'))['total'] or Decimal('0')
    
    daily_limit = Decimal('1000.00')
    remaining = daily_limit - today_total
    
    if today_total + ctx.amount > daily_limit:
        raise HookRejectionError(
            error_code="DAILY_LIMIT_EXCEEDED",
            message="Daily transaction limit exceeded",
            details={
                "current_total": str(today_total),
                "requested_amount": str(ctx.amount),
                "daily_limit": str(daily_limit),
                "remaining": str(remaining)
            }
        )
    
    return True

hooks.register_pre_deduct_point('daily_limit', check_daily_limit, priority=50)
```

### 3. Audit Logging (POST Hook)

```python
from django.utils import timezone

def log_transaction(ctx, result, error):
    """Log transaction to audit system."""
    AuditLog.objects.create(
        user_id=ctx.user_id,
        operation=ctx.operation_type,
        amount=ctx.amount,
        point_type=ctx.point_type,
        status='success' if result else 'failed',
        error_message=str(error) if error else None,
        timestamp=timezone.now(),
    )

hooks.register_post(
    ['add_point', 'deduct_point'], 
    'audit', 
    log_transaction,
    priority=100  # Default priority
)
```

### 4. Fraud Detection

```python
def detect_fraud(ctx):
    """Check for unusual transaction patterns."""
    # Get recent transactions
    recent = WalletTransaction.objects.filter(
        uid=ctx.user_id,
        cdate__gte=timezone.now() - timedelta(minutes=5)
    ).count()
    
    # Reject if more than 10 transactions in 5 minutes
    if recent > 10:
        raise HookRejectionError(
            error_code="FRAUD_DETECTED",
            message="Suspicious transaction activity detected",
            details={
                "recent_transaction_count": recent,
                "time_window_minutes": 5,
                "threshold": 10
            }
        )
    
    return True

hooks.register_pre(
    ['add_point', 'deduct_point'], 
    'fraud_check', 
    detect_fraud,
    priority=20  # High priority - security check
)
```

### 5. Notification (POST Hook with Error Handling)

```python
def notify_user(ctx, result, error):
    """Send notification after transaction."""
    try:
        if result:
            # Transaction succeeded
            send_email(
                to=ctx.user_id,
                subject='Transaction Completed',
                message=f"Transaction {result} for {ctx.amount} {ctx.point_type}"
            )
        else:
            # Transaction failed
            send_email(
                to=ctx.user_id,
                subject='Transaction Failed',
                message=f"Transaction failed: {error}"
            )
    except EmailServiceError as e:
        # POST hook error - won't affect transaction
        # But will be captured and returned to frontend
        raise HookRejectionError(
            error_code="NOTIFICATION_FAILED",
            message="Failed to send notification email",
            details={"reason": str(e)}
        )

hooks.register_post(
    ['add_point', 'deduct_point'], 
    'notify', 
    notify_user,
    priority=500  # Low priority - non-critical
)

# Result structure when POST hook fails:
# {
#     "success": True,  # Transaction still succeeded!
#     "transaction_id": 12345,
#     "post_hook_errors": [
#         {
#             "hook_name": "notify_user",
#             "error_code": "NOTIFICATION_FAILED",
#             "error_message": "Failed to send notification email",
#             "error_details": {"reason": "SMTP connection timeout"}
#         }
#     ]
# }
```
```

---

## Questions & Answers

### ✅ Resolved

1. **Hook Prioritization**: ~~Should hooks have priority/order control?~~ 
   - **RESOLVED**: Yes, use priority system (lower number = higher priority)

2. **PRE Hook Timeout**: ~~Should PRE hooks have timeout enforcement?~~
   - **RESOLVED**: No timeout enforcement, developer responsibility

3. **PRE Hook Error Messages**: ~~How should frontend render hook rejection errors?~~
   - **RESOLVED**: Use `error_code` (ALL_CAPS), `error_message`, `error_details` structure

4. **POST Hook Errors**: ~~How to handle POST hook errors without affecting UX?~~
   - **RESOLVED**: Return `post_hook_errors` separately, transaction status remains successful

### 🔄 Open Questions

1. **Hook Persistence**: Should hooks be stored in database or only in-memory? (Proposed: In-memory for now)

2. **Async POST Hooks**: Should POST hooks support async execution via Celery? (Proposed: Phase 3 enhancement)

3. **Hook Debugging**: Should we provide Django admin interface for hook management? (Proposed: Future enhancement)

4. **Hook Chaining**: Should we support conditional hook execution based on previous hook results? (Proposed: Use metadata for now)

5. **Backwards Compatibility**: Should existing code without hooks work unchanged? (Proposed: Yes, opt-in only)

6. **Error Code Registry**: Should we maintain a registry of all possible error codes? (Proposed: Document in code comments)

---

## Design Decision Summary

| Aspect | Decision | Rationale |
|--------|----------|-----------|
| Hook Types | PRE (validation) + POST (reaction) | Clear separation of concerns |
| Rejection | Return False or raise HookRejectionError | Flexibility with structured errors |
| Execution Order | **Priority-based** (lower = higher priority) | Control over execution order |
| Context | Immutable except metadata | Safety with flexibility |
| POST on Error | Always execute, capture exceptions | Ensures cleanup/notifications, no UX impact |
| Storage | In-memory only | Simpler, faster, no DB overhead |
| Async | Sync only (Phase 1) | Simpler, can add later |
| Timeout | **No timeout enforcement** | Developer responsibility, better performance |
| Error Structure | error_code, message, details | Frontend-friendly error rendering |
| POST Hook Errors | Return separately, don't fail transaction | Transaction succeeds, errors are informational |

---

## Recommendations

### Before Implementation

1. ✅ Validate hook system design with team
2. ✅ Identify most critical use cases
3. ✅ Determine performance requirements
4. ✅ Plan testing strategy

### Implementation Guidelines

1. **Start Simple**: Begin with PRE hooks, add POST hooks after testing
2. **Test Thoroughly**: Unit tests for hooks, integration tests with transactions
3. **Document Well**: Provide clear examples and best practices
4. **Monitor Performance**: Track hook execution time from day 1
5. **Plan for Future**: Design with async/persistence in mind

### Deployment Considerations

1. **Gradual Rollout**: Start with internal hooks only
2. **Monitoring**: Track hook errors and performance
3. **Fallback**: Option to disable hooks if issues arise
4. **Audit**: Log all hook registration in production

---

## Related Features

- **Transfer Feature**: Will use hooks for notifications
- **Transaction Types**: Hooks can filter by transaction type
- **Signals**: Hooks are internal; Django signals for external events
- **Middleware**: Could build hook middleware system in future

---

**Next Steps**: 
1. Get stakeholder feedback on design
2. Create hook system implementation PR
3. Add comprehensive tests
4. Update documentation with hook examples

---

## Appendix A: Error Code Design & Frontend Integration

### Error Code Naming Convention

**Format**: `ALL_CAPS_SNAKE_CASE`

**Categories**:
- **Validation**: `*_INVALID`, `*_REQUIRED`, `*_MISMATCH`
- **Limits**: `*_LIMIT_EXCEEDED`, `*_INSUFFICIENT`
- **Security**: `*_SUSPENDED`, `*_BLOCKED`, `*_UNAUTHORIZED`
- **Business Rules**: `*_NOT_ALLOWED`, `*_UNAVAILABLE`
- **External Services**: `*_FAILED`, `*_TIMEOUT`

### Common Error Codes

```python
# PRE Hook Error Codes (Transaction Preventing)
ERROR_CODES = {
    # User State
    "USER_SUSPENDED": "User account is suspended",
    "USER_BLOCKED": "User account is blocked",
    "USER_NOT_VERIFIED": "User account is not verified",
    
    # Transaction Limits
    "DAILY_LIMIT_EXCEEDED": "Daily transaction limit exceeded",
    "MONTHLY_LIMIT_EXCEEDED": "Monthly transaction limit exceeded",
    "TRANSACTION_TOO_LARGE": "Transaction amount exceeds maximum allowed",
    "TRANSACTION_TOO_SMALL": "Transaction amount below minimum required",
    
    # Balance & Funds
    "INSUFFICIENT_BALANCE": "Insufficient balance for transaction",
    "WALLET_NOT_FOUND": "Wallet not found for user",
    "POINT_TYPE_INVALID": "Invalid point type specified",
    
    # Security & Fraud
    "FRAUD_DETECTED": "Suspicious transaction activity detected",
    "TOO_MANY_REQUESTS": "Too many transaction attempts",
    "IP_BLOCKED": "Requests from this IP address are blocked",
    
    # Business Rules
    "OPERATION_NOT_ALLOWED": "This operation is not allowed",
    "TRANSFER_SELF_NOT_ALLOWED": "Cannot transfer to self",
    "POINT_TYPE_MISMATCH": "Point types do not match",
}

# POST Hook Error Codes (Non-Critical)
POST_ERROR_CODES = {
    "NOTIFICATION_FAILED": "Failed to send notification",
    "AUDIT_LOG_FAILED": "Failed to create audit log",
    "ANALYTICS_FAILED": "Failed to track analytics",
    "WEBHOOK_FAILED": "Failed to trigger webhook",
    "EMAIL_FAILED": "Failed to send email",
}
```

### Frontend Integration Example

```typescript
// TypeScript frontend types
interface TransactionResponse {
    success: boolean;
    transaction_id?: number;
    
    // PRE hook rejection
    error_code?: string;
    error_message?: string;
    error_details?: Record<string, any>;
    
    // POST hook errors (transaction succeeded but side effects failed)
    post_hook_errors?: Array<{
        hook_name: string;
        error_code: string;
        error_message: string;
        error_details?: Record<string, any>;
    }>;
}

// React component example
function TransactionResult({ response }: { response: TransactionResponse }) {
    if (!response.success) {
        // PRE hook rejection - transaction failed
        return (
            <ErrorAlert severity="error">
                <ErrorTitle>{getErrorTitle(response.error_code)}</ErrorTitle>
                <ErrorMessage>{response.error_message}</ErrorMessage>
                {response.error_details && (
                    <ErrorDetails>
                        {renderErrorDetails(response.error_code, response.error_details)}
                    </ErrorDetails>
                )}
            </ErrorAlert>
        );
    }
    
    // Transaction succeeded
    return (
        <div>
            <SuccessAlert>
                Transaction completed successfully!
                <TransactionId>ID: {response.transaction_id}</TransactionId>
            </SuccessAlert>
            
            {/* POST hook errors - show as warnings */}
            {response.post_hook_errors?.map((error, idx) => (
                <WarningAlert key={idx}>
                    <WarningTitle>Non-critical error</WarningTitle>
                    <WarningMessage>{error.error_message}</WarningMessage>
                    <small>This does not affect your transaction.</small>
                </WarningAlert>
            ))}
        </div>
    );
}

// Error rendering based on error code
function renderErrorDetails(errorCode: string, details: Record<string, any>) {
    switch (errorCode) {
        case 'DAILY_LIMIT_EXCEEDED':
            return (
                <>
                    <p>Current total: {details.current_total}</p>
                    <p>Daily limit: {details.daily_limit}</p>
                    <p>Remaining: {details.remaining}</p>
                    <p>Try again tomorrow or request a limit increase.</p>
                </>
            );
        
        case 'INSUFFICIENT_BALANCE':
            return (
                <>
                    <p>Your balance: {details.current_balance}</p>
                    <p>Required: {details.required_amount}</p>
                    <p>Please add funds to continue.</p>
                </>
            );
        
        case 'USER_SUSPENDED':
            return (
                <>
                    <p>Suspended on: {new Date(details.suspended_at).toLocaleDateString()}</p>
                    <p>Reason: {details.reason}</p>
                    <p>Contact support for assistance.</p>
                </>
            );
        
        default:
            return <pre>{JSON.stringify(details, null, 2)}</pre>;
    }
}

// I18n integration
function getErrorTitle(errorCode?: string): string {
    const titles = {
        'DAILY_LIMIT_EXCEEDED': t('errors.daily_limit_exceeded.title'),
        'USER_SUSPENDED': t('errors.user_suspended.title'),
        'FRAUD_DETECTED': t('errors.fraud_detected.title'),
        // ... more translations
    };
    
    return titles[errorCode] || t('errors.generic.title');
}
```

### Python Service Return Value

```python
# wallet_utils/service.py

class TransactionResult:
    """Result of a wallet transaction."""
    
    def __init__(
        self, 
        success: bool,
        transaction_id: int = None,
        error_code: str = None,
        error_message: str = None,
        error_details: dict = None,
        post_hook_errors: list = None
    ):
        self.success = success
        self.transaction_id = transaction_id
        self.error_code = error_code
        self.error_message = error_message
        self.error_details = error_details or {}
        self.post_hook_errors = post_hook_errors or []
    
    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        result = {'success': self.success}
        
        if self.success:
            result['transaction_id'] = self.transaction_id
            if self.post_hook_errors:
                result['post_hook_errors'] = self.post_hook_errors
        else:
            result['error_code'] = self.error_code
            result['error_message'] = self.error_message
            if self.error_details:
                result['error_details'] = self.error_details
        
        return result

# Usage in WalletService
class WalletService:
    def add_point(self, user_id, point_type, amount, **kwargs) -> TransactionResult:
        try:
            # Execute PRE hooks
            hook_context = self._build_hook_context('add', ...)
            self._execute_pre_hooks(hook_context)
            
            # Execute transaction
            txn_id = self._add_point_impl(...)
            
            # Execute POST hooks (capture errors)
            post_errors = self._execute_post_hooks(hook_context, txn_id, None)
            
            return TransactionResult(
                success=True,
                transaction_id=txn_id,
                post_hook_errors=post_errors
            )
            
        except HookRejectionError as e:
            # PRE hook rejection
            return TransactionResult(
                success=False,
                error_code=e.error_code,
                error_message=e.message,
                error_details=e.details
            )
        except Exception as e:
            # Other errors
            return TransactionResult(
                success=False,
                error_code='TRANSACTION_FAILED',
                error_message=str(e)
            )
```

### Best Practices Summary

1. **Error Codes**:
   - Use ALL_CAPS_SNAKE_CASE format
   - Be specific and descriptive
   - Group by category (validation, limits, security, etc.)

2. **Error Messages**:
   - Human-readable, concise
   - Don't expose internal implementation details
   - Safe for display to end users

3. **Error Details**:
   - Provide actionable information
   - Include relevant context (limits, amounts, dates)
   - Structure for easy frontend rendering

4. **Frontend Handling**:
   - PRE hook errors → Red error alerts (transaction failed)
   - POST hook errors → Yellow warning alerts (transaction succeeded)
   - Use error_code for conditional rendering and i18n
   - Provide helpful next steps to users

5. **POST Hook Error Philosophy**:
   - Transaction success is independent of POST hooks
   - POST hook errors are informational only
   - User should know their transaction succeeded
   - Optional: offer retry for failed POST hooks (e.g., resend notification)

---
