Skip to content

Commit

Permalink
Optimize order fetching and always get with product
Browse files Browse the repository at this point in the history
  • Loading branch information
Winston-Hsiao committed Nov 18, 2024
1 parent ce916e7 commit cb2586d
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 67 deletions.
4 changes: 1 addition & 3 deletions frontend/src/components/pages/Orders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ const OrdersPage: React.FC = () => {
if (isAuthenticated && currentUser) {
setLoadingOrders(true);
try {
const { data, error } = await api.client.GET("/orders/me", {
params: { query: { include_products: true } },
});
const { data, error } = await api.client.GET("/orders/me");

if (error) {
const apiError = error as ApiError;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/stripe/CheckoutButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const CheckoutButton: React.FC<Props> = ({

try {
const { data, error } = await auth.client.POST(
"/stripe/create-checkout-session",
"/stripe/checkout-session",
{
body: {
listing_id: listingId,
Expand Down
64 changes: 43 additions & 21 deletions frontend/src/gen/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ export interface paths {
patch?: never;
trace?: never;
};
"/stripe/create-checkout-session": {
"/stripe/checkout-session": {
parameters: {
query?: never;
header?: never;
Expand All @@ -910,7 +910,7 @@ export interface paths {
get?: never;
put?: never;
/** Create Checkout Session */
post: operations["create_checkout_session_stripe_create_checkout_session_post"];
post: operations["create_checkout_session_stripe_checkout_session_post"];
delete?: never;
options?: never;
head?: never;
Expand Down Expand Up @@ -1354,6 +1354,15 @@ export interface components {
/** Order Id */
order_id?: string | null;
};
/** DeleteTestAccountsResponse */
DeleteTestAccountsResponse: {
/** Success */
success: boolean;
/** Deleted Accounts */
deleted_accounts: string[];
/** Count */
count: number;
};
/** DeleteTokenResponse */
DeleteTokenResponse: {
/** Message */
Expand Down Expand Up @@ -1745,6 +1754,13 @@ export interface components {
order: components["schemas"]["Order"];
product: components["schemas"]["ProductInfo"] | null;
};
/** ProcessPreorderResponse */
ProcessPreorderResponse: {
/** Status */
status: string;
/** Checkout Session */
checkout_session: Record<string, never> | null;
};
/** ProductInfo */
ProductInfo: {
/** Id */
Expand All @@ -1759,6 +1775,25 @@ export interface components {
metadata: {
[key: string]: string;
};
/** Active */
active: boolean;
};
/** ProductResponse */
ProductResponse: {
/** Id */
id: string;
/** Name */
name: string;
/** Description */
description: string | null;
/** Images */
images: string[];
/** Metadata */
metadata: {
[key: string]: string;
};
/** Active */
active: boolean;
};
/** PublicUserInfoResponseItem */
PublicUserInfoResponseItem: {
Expand Down Expand Up @@ -3264,9 +3299,7 @@ export interface operations {
};
get_order_orders__order_id__get: {
parameters: {
query?: {
include_product?: boolean;
};
query?: never;
header?: never;
path: {
order_id: string;
Expand Down Expand Up @@ -3297,9 +3330,7 @@ export interface operations {
};
get_user_orders_orders_me_get: {
parameters: {
query?: {
include_products?: boolean;
};
query?: never;
header?: never;
path?: never;
cookie?: never;
Expand All @@ -3315,15 +3346,6 @@ export interface operations {
"application/json": components["schemas"]["OrderWithProduct"][];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_order_shipping_address_orders__order_id__shipping_address_put: {
Expand Down Expand Up @@ -3650,7 +3672,7 @@ export interface operations {
};
};
};
create_checkout_session_stripe_create_checkout_session_post: {
create_checkout_session_stripe_checkout_session_post: {
parameters: {
query?: never;
header?: never;
Expand Down Expand Up @@ -3700,7 +3722,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"application/json": Record<string, never>;
"application/json": components["schemas"]["ProductResponse"];
};
};
/** @description Validation Error */
Expand Down Expand Up @@ -3784,7 +3806,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"application/json": Record<string, never>;
"application/json": components["schemas"]["DeleteTestAccountsResponse"];
};
};
};
Expand All @@ -3806,7 +3828,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"application/json": Record<string, never>;
"application/json": components["schemas"]["ProcessPreorderResponse"];
};
};
/** @description Validation Error */
Expand Down
77 changes: 42 additions & 35 deletions store/app/routers/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class ProductInfo(BaseModel):
description: str | None
images: list[str]
metadata: dict[str, str]
active: bool


class OrderWithProduct(BaseModel):
Expand All @@ -36,51 +37,57 @@ class OrderWithProduct(BaseModel):
@router.get("/{order_id}", response_model=OrderWithProduct)
async def get_order(
order_id: str,
include_product: bool = False,
user: User = Depends(get_session_user_with_read_permission),
crud: Crud = Depends(Crud.get),
) -> Order | OrderWithProduct:
order = await crud.get_order(order_id)
if order is None or order.user_id != user.id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Order not found")

if not include_product:
return order

if order.stripe_product_id is None:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Order has no associated product")
crud: Crud = Depends(),
) -> OrderWithProduct:
async with crud:
order = await crud.get_order(order_id)
if not order or order.user_id != user.id:
raise HTTPException(status_code=404, detail="Order not found")

# Get product info from Stripe
product = await stripe.get_product(order.stripe_product_id, crud)

# Convert ProductResponse to ProductInfo
product_info = ProductInfo(
id=product.id,
name=product.name,
description=product.description,
images=product.images,
metadata=product.metadata,
active=product.active,
)

product = await stripe.get_product(order.stripe_product_id, crud)
return OrderWithProduct(order=order, product=ProductInfo(**product))
return OrderWithProduct(order=order, product=product_info)


@router.get("/me", response_model=list[OrderWithProduct])
async def get_user_orders(
include_products: bool = False,
user: User = Depends(get_session_user_with_read_permission),
crud: Crud = Depends(Crud.get),
crud: Crud = Depends(),
) -> list[OrderWithProduct]:
async def get_product_info(order: Order) -> OrderWithProduct:
if not order.stripe_product_id:
return OrderWithProduct(order=order, product=None)

try:
product = await stripe.get_product(order.stripe_product_id, crud)
product_info = ProductInfo(
id=product.id,
name=product.name,
description=product.description,
images=product.images,
metadata=product.metadata,
active=product.active,
)
return OrderWithProduct(order=order, product=product_info)
except Exception as e:
logger.error("Error processing order", extra={"order_id": order.id, "error": str(e), "user_id": user.id})
return OrderWithProduct(order=order, product=None)

try:
orders = await crud.get_orders_by_user_id(user.id)

if not include_products:
return [OrderWithProduct(order=order, product=None) for order in orders]

orders_with_products = []
for order in orders:
try:
if order.stripe_product_id is None:
orders_with_products.append(OrderWithProduct(order=order, product=None))
continue
product = await stripe.get_product(order.stripe_product_id, crud)
orders_with_products.append(OrderWithProduct(order=order, product=ProductInfo(**product)))
except Exception as e:
logger.error(
"Error processing order", extra={"order_id": order.id, "error": str(e), "user_id": user.id}
)
orders_with_products.append(OrderWithProduct(order=order, product=None))
continue
return orders_with_products
return [await get_product_info(order) for order in orders]
except ItemNotFoundError:
return []
except Exception as e:
Expand Down
33 changes: 26 additions & 7 deletions store/app/routers/stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import time
from datetime import datetime
from enum import Enum
from typing import Annotated, Literal
from typing import Annotated, Any, Literal

import stripe
from fastapi import APIRouter, Body, Depends, HTTPException, Request, status
Expand Down Expand Up @@ -159,6 +159,7 @@ async def stripe_connect_webhook(request: Request, crud: Crud = Depends(Crud.get
is_fully_onboarded = bool(
account.get("details_submitted")
and account.get("payouts_enabled")
and account.get("charges_enabled")
and capabilities.get("card_payments") == "active"
and capabilities.get("transfers") == "active"
)
Expand Down Expand Up @@ -209,7 +210,7 @@ async def stripe_connect_webhook(request: Request, crud: Crud = Depends(Crud.get
raise HTTPException(status_code=500, detail=str(e))


async def handle_checkout_session_completed(session: stripe.checkout.Session, crud: Crud) -> None:
async def handle_checkout_session_completed(session: dict[str, Any], crud: Crud) -> None:
logger.info("Processing checkout session: %s", session["id"])
try:
# Retrieve full session details from the connected account
Expand Down Expand Up @@ -295,7 +296,7 @@ class CreateCheckoutSessionResponse(BaseModel):
stripe_connect_account_id: str


@router.post("/create-checkout-session", response_model=CreateCheckoutSessionResponse)
@router.post("/checkout-session", response_model=CreateCheckoutSessionResponse)
async def create_checkout_session(
request: CreateCheckoutSessionRequest,
user: User = Depends(get_session_user_with_read_permission),
Expand Down Expand Up @@ -436,6 +437,7 @@ async def create_checkout_session(
"application_fee_amount": application_fee,
},
"stripe_account": seller.stripe_connect.account_id,
"metadata": {**metadata, "stripe_price_id": listing.stripe_price_id},
}
)

Expand All @@ -452,8 +454,17 @@ async def create_checkout_session(
raise HTTPException(status_code=400, detail=str(e))


@router.get("/get-product/{product_id}")
async def get_product(product_id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> stripe.Product:
class ProductResponse(BaseModel):
id: str
name: str
description: str | None
images: list[str]
metadata: dict[str, str]
active: bool


@router.get("/get-product/{product_id}", response_model=ProductResponse)
async def get_product(product_id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> ProductResponse:
try:
# First get the listing by stripe_product_id
listing = await crud.get_listing_by_stripe_product_id(product_id)
Expand All @@ -468,7 +479,15 @@ async def get_product(product_id: str, crud: Annotated[Crud, Depends(Crud.get)])
# Retrieve the product using the seller's connected account
product = stripe.Product.retrieve(product_id, stripe_account=seller.stripe_connect.account_id)

return product
# Convert Stripe Product to our ProductResponse model
return ProductResponse(
id=product.id,
name=product.name,
description=product.description,
images=product.images,
metadata=product.metadata,
active=product.active,
)
except stripe.StripeError as e:
logger.error(f"Stripe error retrieving product: {str(e)}")
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
Expand Down Expand Up @@ -662,7 +681,7 @@ async def create_listing_product(

class ProcessPreorderResponse(BaseModel):
status: str
checkout_session: stripe.checkout.Session | None
checkout_session: dict[str, Any] | None


@router.post("/process-preorder/{order_id}", response_model=ProcessPreorderResponse)
Expand Down

0 comments on commit cb2586d

Please sign in to comment.