feat(orders): integrate Pally payment and order management

This commit is contained in:
2026-07-31 13:42:14 +07:00
parent 2547bcfcba
commit 8fbd739697
20 changed files with 661 additions and 36 deletions

0
core/auth/__init__.py Normal file
View File

30
core/auth/jwt.py Normal file
View File

@@ -0,0 +1,30 @@
from datetime import UTC, datetime
from fastapi import HTTPException
from pydantic import ValidationError
from sqlalchemy.ext.asyncio import AsyncSession
from core.secrets import decode_jwt
from repositories.users import UserRepository
from schemas.dto import AuthContext
from schemas.jwt import JWTPayload
async def authorize(token: str, session: AsyncSession, service: str | None = None) -> AuthContext:
content = decode_jwt(token)
try:
payload = JWTPayload.model_validate(content)
except ValidationError:
raise HTTPException(status_code=401, detail="Invalid credentials") from None
if payload.exp < datetime.now(UTC).timestamp():
raise HTTPException(status_code=401, detail="Access token expired")
repo = UserRepository(session)
user = await repo.get_user_by_id(int(payload.sub))
if not user:
raise HTTPException(status_code=401, detail="User not found")
return AuthContext(user, auth_method="jwt", service=service)

View File

@@ -1,38 +1,25 @@
from datetime import UTC, datetime
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2AuthorizationCodeBearer
from pydantic import ValidationError
from fastapi import Depends, HTTPException, Request
from sqlalchemy.ext.asyncio import AsyncSession
from core.secrets import decode_jwt
from db.models import User
from config import cfg
from core.auth import jwt
from db.session import get_db
from repositories.users import UserRepository
from schemas.jwt import JWTPayload
oauth_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl="/auth/login", tokenUrl="/auth/refresh"
)
from external.pally import PallyClient
from schemas.dto import AuthContext
async def get_current_user(
session: AsyncSession = Depends(get_db), token: str = Depends(oauth_scheme)
) -> User | None:
content = decode_jwt(token)
async def get_auth_context(
request: Request, session: AsyncSession = Depends(get_db)
) -> AuthContext | None:
auth = request.headers.get("Authorization")
try:
payload = JWTPayload.model_validate(content)
except ValidationError:
raise HTTPException(status_code=401, detail="Invalid credentials") from None
if not auth:
raise HTTPException(status_code=403, detail="No authorization provided.")
if payload.exp < datetime.now(UTC).timestamp():
raise HTTPException(status_code=401, detail="Access token expired")
if auth.startswith("Bearer"):
token = auth.removeprefix("Bearer ").strip()
return await jwt.authorize(token, session)
repo = UserRepository(session)
user = await repo.get_user_by_id(int(payload.sub))
if not user:
raise HTTPException(status_code=401, detail="User not found")
return user
def get_pally_client() -> PallyClient:
return PallyClient(api_token=cfg.pally_token, payer_pays_commission=True)

View File

@@ -32,12 +32,12 @@ def verify_password(hashed_password: str, plain_password: str) -> bool:
def generate_jwt(payload: dict[str, Any]) -> str:
return jwt.encode(payload, cfg.private_key, "RS256")
return jwt.encode(payload, cfg.private_key, "EdDSA")
def decode_jwt(token: str) -> dict[str, Any] | None:
try:
return jwt.decode(token, cfg.public_key, "RS256")
return jwt.decode(token, cfg.public_key, "EdDSA")
except jwt.ExpiredSignatureError:
return