Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions WALLET_API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Wallet API Implementation

This document describes the wallet and transaction functionality implemented as per the task requirements.

## Features Implemented

### Models

#### Wallet Model
- `id`: UUID primary key
- `user_id`: Foreign key to User (with CASCADE delete)
- `balance`: Decimal with 2 decimal places precision (starts at 0.00)
- `currency`: Enum (USD, EUR, RUB)

#### Transaction Model
- `id`: UUID primary key
- `wallet_id`: Foreign key to Wallet (with CASCADE delete)
- `amount`: Decimal with 2 decimal places
- `type`: Enum ('credit', 'debit')
- `timestamp`: DateTime with UTC timezone
- `currency`: Enum (USD, EUR, RUB)

### Business Rules

#### Wallet Rules
- A user can have maximum 3 wallets
- Wallet balance starts at 0.0
- Arithmetic operations maintain 2 decimal place precision
- Each user can have only one wallet per currency

#### Transaction Rules
- Credit transactions add amount to wallet balance
- Debit transactions subtract amount from wallet balance
- Wallet balance cannot go negative (debit transactions are rejected if insufficient balance)
- Currency conversion between different wallet currencies is supported with hardcoded exchange rates
- 2% conversion fee applied for cross-currency transactions

### API Endpoints

#### Create Wallet
```http
POST /wallets
Content-Type: application/json

{
"currency": "USD"
}
```

**Response**: WalletPublic object with wallet details

#### Get User Wallets
```http
GET /wallets
```

**Response**: List of all wallets for the authenticated user

#### Get Wallet Details
```http
GET /wallets/{wallet_id}
```

**Response**: Wallet details including current balance

#### Create Transaction
```http
POST /wallets/{wallet_id}/transactions
Content-Type: application/json

{
"amount": 100.50,
"type": "credit",
"currency": "USD"
}
```

**Response**: Transaction details

#### Get Wallet Transactions
```http
GET /wallets/{wallet_id}/transactions?skip=0&limit=100
```

**Response**: List of transactions for the wallet, ordered by timestamp (newest first)

### Exchange Rates

The following hardcoded exchange rates are used for currency conversion:

- USD to EUR: 0.85
- USD to RUB: 75.00
- EUR to USD: 1.18
- EUR to RUB: 88.24
- RUB to USD: 0.013
- RUB to EUR: 0.011

### Error Handling

The API includes proper error handling for:
- Maximum wallet limit exceeded
- Duplicate currency wallets for same user
- Insufficient balance for debit transactions
- Unsupported currency conversions
- Wallet not found or access denied

### Database Migration

A database migration has been created to add the new Wallet and Transaction tables with proper foreign key constraints and indexes.

### Authentication

All endpoints require user authentication and only allow access to wallets owned by the authenticated user.
61 changes: 61 additions & 0 deletions backend/app/alembic/versions/add_wallet_and_transaction_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
add_wallet_and_transaction_models

Revision ID: f4a2b3c5d6e7
Revises: 9c0a54914c78
Create Date: 2025-09-15 12:00:00.000000

"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "f4a2b3c5d6e7"
down_revision: str | None = "9c0a54914c78"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"wallet",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("user_id", sa.Uuid(), nullable=False),
sa.Column("balance", sa.Numeric(precision=10, scale=2), nullable=False),
sa.Column(
"currency", sa.Enum("USD", "EUR", "RUB", name="currency"), nullable=False
),
sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f("ix_wallet_currency"), "wallet", ["currency"], unique=False)
op.create_table(
"transaction",
sa.Column("id", sa.Uuid(), nullable=False),
sa.Column("wallet_id", sa.Uuid(), nullable=False),
sa.Column("amount", sa.Numeric(precision=10, scale=2), nullable=False),
sa.Column(
"type", sa.Enum("credit", "debit", name="transactiontype"), nullable=False
),
sa.Column("timestamp", sa.DateTime(timezone=True), nullable=False),
sa.Column(
"currency", sa.Enum("USD", "EUR", "RUB", name="currency"), nullable=False
),
sa.ForeignKeyConstraint(["wallet_id"], ["wallet.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("transaction")
op.drop_index(op.f("ix_wallet_currency"), table_name="wallet")
op.drop_table("wallet")
op.execute("DROP TYPE IF EXISTS currency")
op.execute("DROP TYPE IF EXISTS transactiontype")
# ### end Alembic commands ###
3 changes: 2 additions & 1 deletion backend/app/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

from fastapi import APIRouter

from app.api.routes import items, login, misc, private, users
from app.api.routes import items, login, misc, private, users, wallets
from app.core.config import settings

api_router = APIRouter()
api_router.include_router(login.router)
api_router.include_router(users.router)
api_router.include_router(misc.router)
api_router.include_router(items.router)
api_router.include_router(wallets.router)


if settings.ENVIRONMENT == "local":
Expand Down
Loading