cuatrolabs-spa-ms / WALLET_API_QUICK_REF.md
MukeshKapoor25's picture
feat(leave,wallet): Add leave and wallet modules with JWT auth
a558c77

Wallet Management API - Quick Reference

Authentication

All wallet endpoints require JWT authentication. Include the token in the Authorization header:

Authorization: Bearer <your_jwt_token>

The partner_id is automatically extracted from the JWT token - you don't need to provide it in the request body.

Base URL

http://localhost:8000/wallet

Endpoints

1. Create Wallet

POST /wallet/create

Create a new wallet for the authenticated service professional.

Headers:

Authorization: Bearer <jwt_token>
Content-Type: application/json

Request:

{
  "initial_balance": 5000.00,
  "currency": "INR"
}

Response:

{
  "success": true,
  "message": "Wallet created successfully",
  "data": {
    "wallet_id": "550e8400-e29b-41d4-a716-446655440000",
    "partner_id": "staff_123",
    "balance": 5000.00,
    "currency": "INR",
    "status": "active",
    "last_transaction_at": null,
    "created_at": "2024-01-20T10:00:00",
    "updated_at": "2024-01-20T10:00:00"
  }
}

2. Get My Wallet

GET /wallet/me

Retrieve complete wallet information for the authenticated user.

Headers:

Authorization: Bearer <jwt_token>

Response:

{
  "success": true,
  "message": "Wallet retrieved successfully",
  "data": {
    "wallet_id": "550e8400-e29b-41d4-a716-446655440000",
    "partner_id": "staff_123",
    "balance": 5000.00,
    "currency": "INR",
    "status": "active",
    "last_transaction_at": "2024-01-20T10:30:00",
    "created_at": "2024-01-20T10:00:00",
    "updated_at": "2024-01-20T10:30:00"
  }
}

3. Get My Balance

GET /wallet/balance

Quick endpoint to check current balance for the authenticated user.

Headers:

Authorization: Bearer <jwt_token>

Response:

{
  "success": true,
  "message": "Balance retrieved successfully",
  "data": {
    "partner_id": "staff_123",
    "balance": 5000.00,
    "currency": "INR",
    "status": "active"
  }
}

4. Create Transaction

POST /wallet/transaction

Add or deduct money from the authenticated user's wallet.

Headers:

Authorization: Bearer <jwt_token>
Content-Type: application/json

Credit Transaction (Add Money):

{
  "transaction_type": "credit",
  "amount": 1000.00,
  "reference_type": "topup",
  "reference_id": "TOPUP-2024-001",
  "description": "Wallet recharge via UPI",
  "metadata": {
    "payment_method": "upi",
    "upi_id": "user@paytm",
    "transaction_ref": "UPI123456"
  }
}

Debit Transaction (Deduct Money):

{
  "transaction_type": "debit",
  "amount": 500.00,
  "reference_type": "order",
  "reference_id": "ORDER-123",
  "description": "Payment for order ORDER-123"
}

Response:

{
  "success": true,
  "message": "Transaction completed successfully",
  "data": {
    "transaction_id": "660e8400-e29b-41d4-a716-446655440000",
    "wallet_id": "550e8400-e29b-41d4-a716-446655440000",
    "partner_id": "staff_123",
    "transaction_type": "credit",
    "amount": 1000.00,
    "balance_after": 6000.00,
    "reference_type": "topup",
    "reference_id": "TOPUP-2024-001",
    "description": "Wallet recharge via UPI",
    "metadata": {
      "payment_method": "upi",
      "upi_id": "user@paytm"
    },
    "created_at": "2024-01-20T10:30:00"
  }
}

Transaction Types:

  • credit - Add money
  • debit - Deduct money

Reference Types:

  • order - Payment for orders
  • refund - Refund from cancelled orders
  • topup - Manual wallet recharge
  • commission - Commission earnings
  • withdrawal - Money withdrawal

5. List My Transactions

POST /wallet/transactions/list

Get transaction history for the authenticated user with filters.

Headers:

Authorization: Bearer <jwt_token>
Content-Type: application/json

Request:

{
  "transaction_type": "credit",
  "reference_type": "topup",
  "start_date": "2024-01-01T00:00:00",
  "end_date": "2024-12-31T23:59:59",
  "limit": 20,
  "offset": 0
}

Response:

{
  "success": true,
  "message": "Transactions retrieved successfully",
  "data": {
    "transactions": [
      {
        "transaction_id": "660e8400-e29b-41d4-a716-446655440000",
        "wallet_id": "550e8400-e29b-41d4-a716-446655440000",
        "partner_id": "staff_123",
        "transaction_type": "credit",
        "amount": 1000.00,
        "balance_after": 6000.00,
        "reference_type": "topup",
        "reference_id": "TOPUP-2024-001",
        "description": "Wallet recharge",
        "metadata": {},
        "created_at": "2024-01-20T10:30:00"
      }
    ],
    "pagination": {
      "limit": 20,
      "offset": 0,
      "total": 45
    }
  }
}

Admin Endpoints

6. [Admin] Get Wallet by Partner ID

GET /wallet/admin/{partner_id}

Admin endpoint to retrieve any wallet by partner ID.

Headers:

Authorization: Bearer <admin_jwt_token>

Response: Same as "Get My Wallet"


7. [Admin] Update Wallet Status

PATCH /wallet/admin/{partner_id}/status?status={status}

Admin endpoint to change wallet status.

Headers:

Authorization: Bearer <admin_jwt_token>

Query Parameters:

  • status - New status: active | suspended | closed

Example:

PATCH /wallet/admin/staff_123/status?status=suspended

Status Values:

  • active - Normal operation (default)
  • suspended - Temporarily disabled, no transactions allowed
  • closed - Permanently disabled

Error Responses

Unauthorized (Missing/Invalid Token)

{
  "detail": "Authorization header missing"
}

Status Code: 401

Wallet Not Found

{
  "detail": "Wallet not found. Please create a wallet first."
}

Status Code: 404

Wallet Already Exists

{
  "detail": "Wallet already exists for partner_id: staff_123"
}

Status Code: 400

Insufficient Balance

{
  "detail": "Insufficient balance. Current: 100.00, Required: 500.00"
}

Status Code: 400

Wallet Not Active

{
  "detail": "Wallet is not active. Current status: suspended"
}

Status Code: 400


cURL Examples

Create Wallet

curl -X POST "http://localhost:8000/wallet/create" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "initial_balance": 5000.00
  }'

Get My Balance

curl "http://localhost:8000/wallet/balance" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Add Money (Credit)

curl -X POST "http://localhost:8000/wallet/transaction" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_type": "credit",
    "amount": 1000.00,
    "reference_type": "topup",
    "description": "Wallet top-up"
  }'

Deduct Money (Debit)

curl -X POST "http://localhost:8000/wallet/transaction" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_type": "debit",
    "amount": 500.00,
    "reference_type": "order",
    "reference_id": "ORDER-123",
    "description": "Payment for order"
  }'

List My Transactions

curl -X POST "http://localhost:8000/wallet/transactions/list" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "limit": 20,
    "offset": 0
  }'

JWT Token Structure

Your JWT token should contain the following claims:

{
  "partner_id": "staff_123",
  "user_id": "staff_123",
  "merchant_id": "merchant_456",
  "role": "service_professional",
  "email": "staff@example.com",
  "phone": "+919876543210",
  "exp": 1234567890
}

The API will extract partner_id from the token. If partner_id is not present, it will fall back to user_id or staff_id.


Security Notes

  1. JWT Authentication: All endpoints require valid JWT token
  2. Encrypted Balance: Wallet balance is encrypted in the database using AES encryption
  3. Partner Isolation: Users can only access their own wallet data
  4. Admin Endpoints: Admin endpoints should verify admin role (TODO: implement role check)
  5. Rate Limiting: Implement rate limiting in production to prevent abuse

Integration Example

Order Payment with Wallet

from app.wallet.services.service import WalletService
from app.wallet.schemas.schema import WalletTransactionRequest
from app.dependencies.auth import get_partner_id

# In your order endpoint
@router.post("/orders/create")
async def create_order(
    order_data: OrderCreateRequest,
    db: AsyncSession = Depends(get_db),
    token_data: dict = Depends(verify_token)
):
    partner_id = get_partner_id(token_data)
    
    # Create order
    order = await create_order_logic(order_data, partner_id)
    
    # If payment mode is wallet, debit the wallet
    if order_data.payment_mode == "wallet":
        try:
            transaction = await WalletService.create_transaction(
                db,
                partner_id,
                WalletTransactionRequest(
                    transaction_type="debit",
                    amount=order.net_amount,
                    reference_type="order",
                    reference_id=str(order.order_id),
                    description=f"Payment for order {order.order_number}",
                    metadata={
                        "order_number": order.order_number,
                        "items_count": len(order.items)
                    }
                )
            )
            
            order.payment_status = "paid"
            await db.commit()
            
        except ValueError as e:
            # Handle insufficient balance
            raise HTTPException(status_code=400, detail=str(e))
    
    return order

Refund to Wallet

# When cancelling an order
@router.post("/orders/{order_id}/cancel")
async def cancel_order(
    order_id: UUID,
    db: AsyncSession = Depends(get_db),
    token_data: dict = Depends(verify_token)
):
    partner_id = get_partner_id(token_data)
    
    # Get order
    order = await get_order_by_id(order_id, partner_id)
    
    # Refund to wallet if paid via wallet
    if order.payment_mode == "wallet" and order.payment_status == "paid":
        transaction = await WalletService.create_transaction(
            db,
            partner_id,
            WalletTransactionRequest(
                transaction_type="credit",
                amount=order.net_amount,
                reference_type="refund",
                reference_id=str(order.order_id),
                description=f"Refund for cancelled order {order.order_number}"
            )
        )
    
    # Cancel order
    order.order_status = "cancelled"
    order.payment_status = "refunded"
    await db.commit()
    
    return order