# LLD: Withdrawal Flow

# Withdrawal Request Flow (Angrerett)

## Purpose

Implements the user's right of withdrawal (angrerett) as required by Norwegian consumer protection law (angrerettloven). Users can submit a withdrawal request to cancel their account or service agreement within the statutory cooling-off period.

## Sequence Diagram

```mermaid
sequenceDiagram
    participant U as User (App)
    participant API as Drop API
    participant Auth as Auth Middleware
    participant DB as PostgreSQL
    participant Audit as Audit Log

    U->>API: POST /withdrawal { reason, comment }
    API->>Auth: Validate JWT token
    Auth-->>API: user context

    alt Invalid JSON body
        API-->>U: 400 bad_request
    end

    API->>API: Sanitize reason (max 100 chars)
    API->>API: Sanitize comment (max 1000 chars)
    API->>API: Validate reason against VALID_REASONS

    alt Invalid reason
        API-->>U: 400 validation_error
    end

    API->>DB: INSERT INTO withdrawal_requests (id, user_id, reason, comment)
    DB-->>API: OK

    API->>Audit: Log WITHDRAWAL_REQUEST action
    Audit->>DB: INSERT INTO audit_log

    API-->>U: 201 { success: true, id }
```

## Database Schema

### withdrawal_requests table

| Column     | Type | Constraints                                                          |
|------------|------|----------------------------------------------------------------------|
| id         | TEXT | PRIMARY KEY (prefix: `wr_`)                                         |
| user_id    | TEXT | NOT NULL, REFERENCES users(id)                                      |
| reason     | TEXT | Nullable                                                             |
| comment    | TEXT | Nullable                                                             |
| status     | TEXT | DEFAULT 'pending', CHECK IN ('pending','processing','completed','rejected') |
| created_at | TIMESTAMPTZ | DEFAULT NOW()                                                   |

Index: `idx_withdrawal_requests_user` on `user_id`.

## Valid Withdrawal Reasons

| Value             | Description                                    |
|-------------------|------------------------------------------------|
| `not_needed`      | User no longer needs the service               |
| `alternative`     | User found an alternative service              |
| `not_satisfied`   | User is not satisfied with the service         |
| `other`           | Other reason (details in comment field)        |
| `""` (empty)      | No reason provided                             |

## Request Processing

1. **Authentication** -- request must include a valid JWT token (authMiddleware).
2. **Input validation** -- reason is checked against the allowlist; both reason and comment are sanitized via `sanitizeText` with length limits.
3. **Record creation** -- a new `withdrawal_requests` row is inserted with status `pending`.
4. **Audit logging** -- an audit log entry is created with action `WITHDRAWAL_REQUEST`, including the reason and the requester's IP address.

## Status Lifecycle

```
pending --> processing --> completed
                      \-> rejected
```

- **pending** -- initial state after user submits request.
- **processing** -- staff/admin has begun reviewing the request.
- **completed** -- withdrawal has been executed, account closed or service cancelled.
- **rejected** -- request was denied (e.g., outside cooling-off period, regulatory hold).

## Error States

| Scenario                  | HTTP Status | Error Code         |
|---------------------------|-------------|--------------------|
| Missing/invalid JWT       | 401         | unauthorized       |
| Malformed JSON body       | 400         | bad_request        |
| Invalid reason value      | 400         | validation_error   |
| Database write failure    | 500         | internal_error     |

## Edge Cases

- **Duplicate requests** -- no uniqueness constraint on user_id; a user can submit multiple withdrawal requests. Business logic should handle deduplication at the review stage.
- **Already deleted user** -- the foreign key on user_id ensures the user must exist. If the user record has `deleted_at` set, the auth middleware should reject the request before it reaches this route.
- **AML retention** -- even after withdrawal is completed, transaction records and AML-related data must be retained for 5 years per hvitvaskingsloven. The data retention cron (`/cron/retention`) handles anonymization after the retention period expires.

## Cross-References

- **Angrerettloven** -- Norwegian Act on the Right of Withdrawal (consumer protection).
- **Data retention** -- See `src/drop-api/src/routes/cron.ts` retention endpoint and `docs/architecture/lld/flow-kyc-aml.md` for AML retention requirements.
- **Audit logging** -- See `src/drop-api/src/lib/audit.ts` for audit log implementation.