test
This commit is contained in:
@@ -9,6 +9,7 @@ from .services import router as services
|
||||
from .users import router as users
|
||||
from .exchange import router as exchange
|
||||
from .marketplace_offers import router as marketplace_offers
|
||||
from .payments import router as payments
|
||||
# from .registry import router as registry
|
||||
|
||||
__all__ = ["client", "miner", "admin", "marketplace", "explorer", "services", "users", "exchange", "marketplace_offers", "registry"]
|
||||
__all__ = ["client", "miner", "admin", "marketplace", "explorer", "services", "users", "exchange", "marketplace_offers", "payments", "registry"]
|
||||
|
||||
@@ -2,12 +2,15 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from ..deps import require_client_key
|
||||
from ..schemas import JobCreate, JobView, JobResult
|
||||
from ..schemas.payments import JobPaymentCreate, PaymentMethod
|
||||
from ..types import JobState
|
||||
from ..services import JobService
|
||||
from ..services.payments import PaymentService
|
||||
from ..storage import SessionDep
|
||||
|
||||
router = APIRouter(tags=["client"])
|
||||
|
||||
|
||||
@router.post("/jobs", response_model=JobView, status_code=status.HTTP_201_CREATED, summary="Submit a job")
|
||||
async def submit_job(
|
||||
req: JobCreate,
|
||||
@@ -16,6 +19,22 @@ async def submit_job(
|
||||
) -> JobView: # type: ignore[arg-type]
|
||||
service = JobService(session)
|
||||
job = service.create_job(client_id, req)
|
||||
|
||||
# Create payment if amount is specified
|
||||
if req.payment_amount and req.payment_amount > 0:
|
||||
payment_service = PaymentService(session)
|
||||
payment_create = JobPaymentCreate(
|
||||
job_id=job.id,
|
||||
amount=req.payment_amount,
|
||||
currency=req.payment_currency,
|
||||
payment_method=PaymentMethod.AITBC_TOKEN # Jobs use AITBC tokens
|
||||
)
|
||||
payment = await payment_service.create_payment(job.id, payment_create)
|
||||
job.payment_id = payment.id
|
||||
job.payment_status = payment.status.value
|
||||
session.commit()
|
||||
session.refresh(job)
|
||||
|
||||
return service.to_view(job)
|
||||
|
||||
|
||||
|
||||
171
apps/coordinator-api/src/app/routers/payments.py
Normal file
171
apps/coordinator-api/src/app/routers/payments.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""Payment router for job payments"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from typing import List
|
||||
|
||||
from ..deps import require_client_key
|
||||
from ..schemas.payments import (
|
||||
JobPaymentCreate,
|
||||
JobPaymentView,
|
||||
PaymentRequest,
|
||||
PaymentReceipt,
|
||||
EscrowRelease,
|
||||
RefundRequest
|
||||
)
|
||||
from ..services.payments import PaymentService
|
||||
from ..storage import SessionDep
|
||||
|
||||
router = APIRouter(tags=["payments"])
|
||||
|
||||
|
||||
@router.post("/payments", response_model=JobPaymentView, status_code=status.HTTP_201_CREATED, summary="Create payment for a job")
|
||||
async def create_payment(
|
||||
payment_data: JobPaymentCreate,
|
||||
session: SessionDep,
|
||||
client_id: str = Depends(require_client_key()),
|
||||
) -> JobPaymentView:
|
||||
"""Create a payment for a job"""
|
||||
|
||||
service = PaymentService(session)
|
||||
payment = await service.create_payment(payment_data.job_id, payment_data)
|
||||
|
||||
return service.to_view(payment)
|
||||
|
||||
|
||||
@router.get("/payments/{payment_id}", response_model=JobPaymentView, summary="Get payment details")
|
||||
async def get_payment(
|
||||
payment_id: str,
|
||||
session: SessionDep,
|
||||
client_id: str = Depends(require_client_key()),
|
||||
) -> JobPaymentView:
|
||||
"""Get payment details by ID"""
|
||||
|
||||
service = PaymentService(session)
|
||||
payment = service.get_payment(payment_id)
|
||||
|
||||
if not payment:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Payment not found"
|
||||
)
|
||||
|
||||
return service.to_view(payment)
|
||||
|
||||
|
||||
@router.get("/jobs/{job_id}/payment", response_model=JobPaymentView, summary="Get payment for a job")
|
||||
async def get_job_payment(
|
||||
job_id: str,
|
||||
session: SessionDep,
|
||||
client_id: str = Depends(require_client_key()),
|
||||
) -> JobPaymentView:
|
||||
"""Get payment information for a specific job"""
|
||||
|
||||
service = PaymentService(session)
|
||||
payment = service.get_job_payment(job_id)
|
||||
|
||||
if not payment:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Payment not found for this job"
|
||||
)
|
||||
|
||||
return service.to_view(payment)
|
||||
|
||||
|
||||
@router.post("/payments/{payment_id}/release", response_model=dict, summary="Release payment from escrow")
|
||||
async def release_payment(
|
||||
payment_id: str,
|
||||
release_data: EscrowRelease,
|
||||
session: SessionDep,
|
||||
client_id: str = Depends(require_client_key()),
|
||||
) -> dict:
|
||||
"""Release payment from escrow (for completed jobs)"""
|
||||
|
||||
service = PaymentService(session)
|
||||
|
||||
# Verify the payment belongs to the client's job
|
||||
payment = service.get_payment(payment_id)
|
||||
if not payment:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Payment not found"
|
||||
)
|
||||
|
||||
success = await service.release_payment(
|
||||
release_data.job_id,
|
||||
payment_id,
|
||||
release_data.reason
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Failed to release payment"
|
||||
)
|
||||
|
||||
return {"status": "released", "payment_id": payment_id}
|
||||
|
||||
|
||||
@router.post("/payments/{payment_id}/refund", response_model=dict, summary="Refund payment")
|
||||
async def refund_payment(
|
||||
payment_id: str,
|
||||
refund_data: RefundRequest,
|
||||
session: SessionDep,
|
||||
client_id: str = Depends(require_client_key()),
|
||||
) -> dict:
|
||||
"""Refund payment (for failed or cancelled jobs)"""
|
||||
|
||||
service = PaymentService(session)
|
||||
|
||||
# Verify the payment belongs to the client's job
|
||||
payment = service.get_payment(payment_id)
|
||||
if not payment:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Payment not found"
|
||||
)
|
||||
|
||||
success = await service.refund_payment(
|
||||
refund_data.job_id,
|
||||
payment_id,
|
||||
refund_data.reason
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Failed to refund payment"
|
||||
)
|
||||
|
||||
return {"status": "refunded", "payment_id": payment_id}
|
||||
|
||||
|
||||
@router.get("/payments/{payment_id}/receipt", response_model=PaymentReceipt, summary="Get payment receipt")
|
||||
async def get_payment_receipt(
|
||||
payment_id: str,
|
||||
session: SessionDep,
|
||||
client_id: str = Depends(require_client_key()),
|
||||
) -> PaymentReceipt:
|
||||
"""Get payment receipt with verification status"""
|
||||
|
||||
service = PaymentService(session)
|
||||
payment = service.get_payment(payment_id)
|
||||
|
||||
if not payment:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Payment not found"
|
||||
)
|
||||
|
||||
receipt = PaymentReceipt(
|
||||
payment_id=payment.id,
|
||||
job_id=payment.job_id,
|
||||
amount=float(payment.amount),
|
||||
currency=payment.currency,
|
||||
status=payment.status,
|
||||
transaction_hash=payment.transaction_hash,
|
||||
created_at=payment.created_at,
|
||||
verified_at=payment.released_at or payment.refunded_at
|
||||
)
|
||||
|
||||
return receipt
|
||||
Reference in New Issue
Block a user