Coverage for polar/payout/endpoints.py: 49%
58 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 15:52 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-05 15:52 +0000
1from fastapi import Depends, Query 1a
2from fastapi.responses import StreamingResponse 1a
3from pydantic import UUID4 1a
5from polar.account.service import account as account_service 1a
6from polar.exceptions import ResourceNotFound 1a
7from polar.kit.db.postgres import AsyncSessionMaker 1a
8from polar.kit.pagination import ListResource, PaginationParamsQuery 1a
9from polar.kit.schemas import MultipleQueryFilter 1a
10from polar.locker import Locker, get_locker 1a
11from polar.models import Payout 1a
12from polar.models.payout import PayoutStatus 1a
13from polar.openapi import APITag 1a
14from polar.postgres import AsyncSession, get_db_session, get_db_sessionmaker 1a
15from polar.routing import APIRouter 1a
17from . import auth as payouts_auth 1a
18from . import sorting 1a
19from .schemas import Payout as PayoutSchema 1a
20from .schemas import PayoutCreate, PayoutEstimate, PayoutGenerateInvoice, PayoutInvoice 1a
21from .service import InsufficientBalance, UnderReviewAccount 1a
22from .service import payout as payout_service 1a
24router = APIRouter(prefix="/payouts", tags=["payouts", APITag.private]) 1a
27@router.get("/", response_model=ListResource[PayoutSchema]) 1a
28async def list( 1a
29 auth_subject: payouts_auth.PayoutsRead,
30 pagination: PaginationParamsQuery,
31 sorting: sorting.ListSorting,
32 account_id: MultipleQueryFilter[UUID4] | None = Query(
33 None, title="Account ID Filter", description="Filter by account ID."
34 ),
35 status: MultipleQueryFilter[PayoutStatus] | None = Query(
36 None, title="Status Filter", description="Filter by payout status."
37 ),
38 session: AsyncSession = Depends(get_db_session),
39) -> ListResource[PayoutSchema]:
40 """List payouts."""
41 results, count = await payout_service.list(
42 session,
43 auth_subject,
44 account_id=account_id,
45 status=status,
46 pagination=pagination,
47 sorting=sorting,
48 )
50 return ListResource.from_paginated_results(
51 [PayoutSchema.model_validate(result) for result in results], count, pagination
52 )
55@router.get( 1a
56 "/estimate",
57 response_model=PayoutEstimate,
58 responses={
59 200: {
60 "description": "Payout estimate computed successfully.",
61 },
62 400: {
63 "description": "The balance is insufficient to create a payout.",
64 "model": InsufficientBalance.schema(),
65 },
66 403: {
67 "description": "The account is under review or not ready.",
68 "model": UnderReviewAccount.schema(),
69 },
70 404: {"description": "Account not found.", "model": ResourceNotFound.schema()},
71 },
72)
73async def get_estimate( 1a
74 auth_subject: payouts_auth.PayoutsRead,
75 account_id: UUID4,
76 session: AsyncSession = Depends(get_db_session),
77) -> PayoutEstimate:
78 account = await account_service.get(session, auth_subject, account_id)
79 if account is None:
80 raise ResourceNotFound()
82 return await payout_service.estimate(session, account=account)
85@router.post("/", response_model=PayoutSchema, status_code=201) 1a
86async def create( 1a
87 auth_subject: payouts_auth.PayoutsWrite,
88 payout_create: PayoutCreate,
89 session: AsyncSession = Depends(get_db_session),
90 locker: Locker = Depends(get_locker),
91) -> Payout:
92 account_id = payout_create.account_id
93 account = await account_service.get(session, auth_subject, account_id)
94 if account is None:
95 raise ResourceNotFound()
97 return await payout_service.create(session, locker, account=account)
100@router.get("/{id}/csv") 1a
101async def get_csv( 1a
102 id: UUID4,
103 auth_subject: payouts_auth.PayoutsRead,
104 session: AsyncSession = Depends(get_db_session),
105 sessionmaker: AsyncSessionMaker = Depends(get_db_sessionmaker),
106) -> StreamingResponse:
107 payout = await payout_service.get(session, auth_subject, id)
109 if payout is None:
110 raise ResourceNotFound()
112 content = payout_service.get_csv(session, sessionmaker, payout)
113 filename = f"polar-payout-{payout.created_at.isoformat()}.csv"
115 return StreamingResponse(
116 content,
117 media_type="text/csv",
118 headers={"Content-Disposition": f"attachment; filename={filename}"},
119 )
122@router.post("/{id}/invoice", status_code=202) 1a
123async def generate_invoice( 1a
124 id: UUID4,
125 payout_generate_invoice: PayoutGenerateInvoice,
126 auth_subject: payouts_auth.PayoutsWrite,
127 session: AsyncSession = Depends(get_db_session),
128) -> None:
129 """Trigger generation of an order's invoice."""
130 payout = await payout_service.get(session, auth_subject, id)
132 if payout is None:
133 raise ResourceNotFound()
135 await payout_service.trigger_invoice_generation(
136 session, payout, payout_generate_invoice
137 )
140@router.get("/{id}/invoice") 1a
141async def invoice( 1a
142 id: UUID4,
143 auth_subject: payouts_auth.PayoutsRead,
144 session: AsyncSession = Depends(get_db_session),
145) -> PayoutInvoice:
146 """Get an order's invoice data."""
147 payout = await payout_service.get(session, auth_subject, id)
149 if payout is None:
150 raise ResourceNotFound()
152 return await payout_service.get_invoice(payout)