diff options
author | 2024-09-13 19:12:07 +0100 | |
---|---|---|
committer | 2024-09-13 19:12:07 +0100 | |
commit | da8fdd31bafbd6c16049c8cd40b55200432f0b14 (patch) | |
tree | 2100935c158f04b0e42ab55c4773bc4a94278119 | |
parent | Add the printful store ID to the authorized client as a header (diff) |
Add an endpoint to submit an order to printful
-rw-r--r-- | thallium-backend/src/dto/__init__.py | 5 | ||||
-rw-r--r-- | thallium-backend/src/dto/orders.py | 66 | ||||
-rw-r--r-- | thallium-backend/src/routes/__init__.py | 2 | ||||
-rw-r--r-- | thallium-backend/src/routes/orders.py | 38 |
4 files changed, 111 insertions, 0 deletions
diff --git a/thallium-backend/src/dto/__init__.py b/thallium-backend/src/dto/__init__.py index e38135e..17907e2 100644 --- a/thallium-backend/src/dto/__init__.py +++ b/thallium-backend/src/dto/__init__.py @@ -1,4 +1,5 @@ from .login import JWTClaim, PasswordReset, UserClaim, UserLogin, VoucherClaim, VoucherLogin +from .orders import Order, OrderCreate, OrderItem, OrderRecipient from .templates import Template, TemplateWithVariant, Variant from .users import User, UserPermission from .vouchers import Voucher @@ -6,6 +7,10 @@ from .vouchers import Voucher __all__ = ( "LoginData", "JWTClaim", + "OrderCreate", + "Order", + "OrderItem", + "OrderRecipient", "User", "UserPermission", "Voucher", diff --git a/thallium-backend/src/dto/orders.py b/thallium-backend/src/dto/orders.py new file mode 100644 index 0000000..a3a046e --- /dev/null +++ b/thallium-backend/src/dto/orders.py @@ -0,0 +1,66 @@ +from decimal import Decimal + +from pydantic import BaseModel + + +class OrderRecipient(BaseModel): + """Information about the recipient of the order.""" + + name: str + company: str | None = None + address1: str + address2: str + city: str + state_code: str | None = None + state_name: str | None = None + country_code: str + country_name: str + zip: str + phone: str + email: str + tax_number: str | None = None + + +class OrderItem(BaseModel): + """Information about the items in the order.""" + + product_template_id: int + variant_id: int + + +class OrderCreate(BaseModel): + """Data required to create an order.""" + + recipient: OrderRecipient + items: list[OrderItem] + + def as_printful_payload(self) -> dict: + """Return this order in the format used by Printful's API.""" + return { + "recipient": self.recipient.model_dump(), + "items": [item.model_dump() for item in self.items], + } + + +class OrderCosts(BaseModel): + """All costs associated with an order.""" + + currency: str + subtotal: Decimal + discount: Decimal + shipping: Decimal + digitization: Decimal + additional_fee: Decimal + fulfillment_fee: Decimal + retail_delivery_fee: Decimal + tax: Decimal + vat: Decimal + total: Decimal + + +class Order(OrderCreate): + """The order as returned by printful.""" + + id: int + status: str + costs: OrderCosts diff --git a/thallium-backend/src/routes/__init__.py b/thallium-backend/src/routes/__init__.py index 0671816..a1fd732 100644 --- a/thallium-backend/src/routes/__init__.py +++ b/thallium-backend/src/routes/__init__.py @@ -3,6 +3,7 @@ from fastapi import APIRouter from src.routes.admin import router as admin_router from src.routes.debug import router as debug_router from src.routes.login import router as login_router +from src.routes.orders import router as order_router from src.routes.templates import router as template_router from src.routes.vouchers import router as voucher_router from src.settings import CONFIG @@ -10,6 +11,7 @@ from src.settings import CONFIG top_level_router = APIRouter() top_level_router.include_router(admin_router) top_level_router.include_router(login_router) +top_level_router.include_router(order_router) top_level_router.include_router(template_router) top_level_router.include_router(voucher_router) if CONFIG.debug: diff --git a/thallium-backend/src/routes/orders.py b/thallium-backend/src/routes/orders.py new file mode 100644 index 0000000..c5126a8 --- /dev/null +++ b/thallium-backend/src/routes/orders.py @@ -0,0 +1,38 @@ +import logging + +from fastapi import APIRouter, Depends, HTTPException, Request +from sqlalchemy import select + +from src.auth import TokenAuth +from src.dto import Order, OrderCreate, Voucher +from src.orm import Voucher as DBVoucher +from src.settings import DBSession, PrintfulClient + +router = APIRouter(prefix="/orders", tags=["Orders"], dependencies=[Depends(TokenAuth(allow_vouchers=True))]) + +log = logging.getLogger(__name__) + + [email protected]("/") +async def create_order(request: Request, db: DBSession, client: PrintfulClient, order: OrderCreate) -> Order | None: + """ + Create the order in printful and deduct the order cost from the voucher. + + If the voucher does not have enough funds, the order is cancelled. + """ + resp = await client.post("/orders", json=order.as_printful_payload(), params={"confirm": False}) + resp.raise_for_status() + submitted_order = Order.model_validate(resp.json()["result"]) + + voucher: Voucher = request.state.voucher + stmt = select(DBVoucher).where(DBVoucher.id == voucher.id).with_for_update() + db_voucher = await db.scalar(stmt) + if submitted_order.costs.total > db_voucher.balance: + await client.delete(f"/orders/{submitted_order.id}") + raise HTTPException( + status_code=400, + detail=f"Order totals {submitted_order.costs.total}, only {db_voucher.balance} remaining on voucher.", + ) + + db_voucher.balance = db_voucher.balance - submitted_order.costs.total + return submitted_order |