Skip to content

Commit

Permalink
Adjust orders and account UI
Browse files Browse the repository at this point in the history
  • Loading branch information
Winston-Hsiao committed Nov 15, 2024
1 parent a37943c commit f900442
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 121 deletions.
22 changes: 20 additions & 2 deletions frontend/src/components/orders/OrderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ const orderStatuses = [
"being_assembled",
"shipped",
"delivered",
"preorder_placed",
"cancelled",
"refunded",
"failed",
];

const activeStatuses = [
"preorder_placed",
"processing",
"in_development",
"being_assembled",
Expand Down Expand Up @@ -67,7 +69,7 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
return "text-gray-600";
};

const unitPrice = order.amount / order.quantity;
const unitPrice = order.price_amount / order.quantity;

const handleOrderUpdate = (updatedOrder: Order) => {
setOrderWithProduct((prev) => ({ ...prev, order: updatedOrder }));
Expand All @@ -87,6 +89,22 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
</span>
</p>

{order.status === "preorder_placed" && order.preorder_deposit_amount && (
<div className="mb-4 p-3 bg-blue-50 text-blue-800 rounded-md">
<p className="font-medium">
Pre-order Deposit: {formatPrice(order.preorder_deposit_amount)}
</p>
{order.preorder_release_date && (
<p className="text-sm mt-1">
Expected Release:{" "}
{new Date(
order.preorder_release_date * 1000,
).toLocaleDateString()}
</p>
)}
</div>
)}

<div className="text-sm sm:text-base text-gray-11 flex flex-col mb-4">
<p>Order ID: {order.id}</p>
<div className="mb-2">
Expand Down Expand Up @@ -116,7 +134,7 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
<p className="text-gray-12">Quantity: {order.quantity}</p>
<p>
<span className="text-gray-12 font-medium">
{formatPrice(order.amount)}
{formatPrice(order.price_amount)}
</span>{" "}
<span className="font-light">
= {formatPrice(unitPrice)} x {order.quantity}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/pages/Orders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const OrdersPage: React.FC = () => {
<div className="mb-6">
<h1 className="text-3xl font-bold">Your Orders</h1>
<p className="text-gray-8">
You can view the status of your past and current orders here.
You can view the status of your orders here.
</p>
</div>
{isLoading || loadingOrders ? (
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/pages/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export const RenderProfile = (props: RenderProfileProps) => {
You are set up to sell robots on K-Scale.
</p>
) : (
<p>You must complete seller onboarding to sell robots</p>
<p>Complete seller onboarding to sell robots on K-Scale</p>
)}
</div>
<div className="flex sm:flex-row flex-col gap-2 mb-8">
Expand Down Expand Up @@ -345,23 +345,23 @@ export const RenderProfile = (props: RenderProfileProps) => {
<Button
variant="outline"
asChild
className={`text-xs sm:text-sm px-2 sm:px-4 hover:bg-gray-11 ${value === "own" ? "border" : ""}`}
className={`text-xs sm:text-sm px-2 sm:px-4 hover:bg-gray-11 ${value === "own" ? "border text-gray-12" : ""}`}
>
<TabsTrigger
value="own"
className="data-[state=active]:bg-gray-3 text-gray-12"
className="data-[state=active]:bg-gray-3"
>
Your Robot Listings
</TabsTrigger>
</Button>
<Button
variant="outline"
asChild
className={`text-xs sm:text-sm px-2 sm:px-4 hover:bg-gray-11 ${value === "upvoted" ? "border-0 bg-transparent hover:bg-transparent" : ""}`}
className={`text-xs sm:text-sm px-2 sm:px-4 hover:bg-gray-11 ${value === "upvoted" ? "border text-gray-12" : ""}`}
>
<TabsTrigger
value="upvoted"
className="data-[state=active]:bg-gray-3 text-gray-12"
className="data-[state=active]:bg-gray-3"
>
Upvoted Robots
</TabsTrigger>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/gen/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,8 @@ export interface components {
stripe_payment_method_id?: string | null;
/** Stripe Payment Intent Id */
stripe_payment_intent_id?: string | null;
/** Preorder Release Date */
preorder_release_date?: number | null;
/** Preorder Deposit Amount */
preorder_deposit_amount?: number | null;
/** Stripe Preorder Deposit Id */
Expand Down
3 changes: 3 additions & 0 deletions store/app/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ class Order(StoreBaseModel):
stripe_customer_id: str | None = None
stripe_payment_method_id: str | None = None
stripe_payment_intent_id: str | None = None
preorder_release_date: int | None = None
preorder_deposit_amount: int | None = None
stripe_preorder_deposit_id: str | None = None
stripe_refund_id: str | None = None
Expand Down Expand Up @@ -627,6 +628,7 @@ def create(
stripe_customer_id: str | None = None,
stripe_payment_method_id: str | None = None,
stripe_payment_intent_id: str | None = None,
preorder_release_date: int | None = None,
preorder_deposit_amount: int | None = None,
stripe_preorder_deposit_id: str | None = None,
stripe_refund_id: str | None = None,
Expand Down Expand Up @@ -656,6 +658,7 @@ def create(
stripe_customer_id=stripe_customer_id,
stripe_payment_method_id=stripe_payment_method_id,
stripe_payment_intent_id=stripe_payment_intent_id,
preorder_release_date=preorder_release_date,
preorder_deposit_amount=preorder_deposit_amount,
stripe_preorder_deposit_id=stripe_preorder_deposit_id,
stripe_refund_id=stripe_refund_id,
Expand Down
208 changes: 95 additions & 113 deletions store/app/routers/stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,94 +166,101 @@ async def stripe_connect_webhook(request: Request, crud: Crud = Depends(Crud.get
elif event["type"] == "checkout.session.completed":
session = event["data"]["object"]

# Handle setup completion for preorders
if session["mode"] == "setup":
try:
seller_connect_account_id = session["metadata"].get("seller_connect_account_id")
if not seller_connect_account_id:
raise ValueError("Missing seller connect account ID")
# Use the connected account ID when retrieving the session
try:
complete_session = stripe.checkout.Session.retrieve(session["id"], stripe_account=connected_account_id)
# Handle setup completion for preorders
if complete_session["mode"] == "setup":
try:
seller_connect_account_id = complete_session["metadata"].get("seller_connect_account_id")
if not seller_connect_account_id:
raise ValueError("Missing seller connect account ID")

# Get the customer ID from the session
connected_customer_id = complete_session["customer"]

# Check for existing payment method
existing_payment_method_id = complete_session["metadata"].get("existing_payment_method_id")

if existing_payment_method_id:
# Use existing payment method
payment_method_id = existing_payment_method_id
else:
# Retrieve SetupIntent with expanded payment method
setup_intent = stripe.SetupIntent.retrieve(
complete_session["setup_intent"],
expand=["payment_method"],
stripe_account=seller_connect_account_id,
)

# Get the customer ID from the session
connected_customer_id = session["customer"]
if not setup_intent.payment_method or isinstance(setup_intent.payment_method, str):
raise ValueError("Invalid payment method")

# Check for existing payment method
existing_payment_method_id = session["metadata"].get("existing_payment_method_id")
# Attach the payment method to the customer
stripe.PaymentMethod.attach(
setup_intent.payment_method.id,
customer=connected_customer_id,
stripe_account=seller_connect_account_id,
)

if existing_payment_method_id:
# Use existing payment method
payment_method_id = existing_payment_method_id
else:
# Retrieve SetupIntent with expanded payment method
setup_intent = stripe.SetupIntent.retrieve(
session["setup_intent"],
expand=["payment_method"],
stripe_account=seller_connect_account_id,
)
# Set as default payment method for the customer
stripe.Customer.modify(
connected_customer_id,
invoice_settings={"default_payment_method": setup_intent.payment_method.id},
stripe_account=seller_connect_account_id,
)

if not setup_intent.payment_method or isinstance(setup_intent.payment_method, str):
raise ValueError("Invalid payment method")
payment_method_id = setup_intent.payment_method.id

# Create order for preorder
order_data = {
"user_id": complete_session["client_reference_id"],
"user_email": complete_session["customer_details"]["email"],
"stripe_checkout_session_id": complete_session["id"],
"stripe_payment_intent_id": None,
"amount": int(complete_session["metadata"]["price_amount"]),
"currency": "usd",
"status": "preorder_placed",
"quantity": 1,
"stripe_product_id": complete_session["metadata"].get("stripe_product_id"),
"stripe_customer_id": connected_customer_id,
"stripe_payment_method_id": payment_method_id,
"stripe_connect_account_id": seller_connect_account_id,
"preorder_release_date": complete_session["metadata"].get("preorder_release_date"),
}

# Attach the payment method to the customer
stripe.PaymentMethod.attach(
setup_intent.payment_method.id,
customer=connected_customer_id,
stripe_account=seller_connect_account_id,
)
# Add shipping details if available
if shipping_details := complete_session.get("shipping_details"):
shipping_address = shipping_details.get("address", {})
order_data.update(
{
"shipping_name": shipping_details.get("name"),
"shipping_address_line1": shipping_address.get("line1"),
"shipping_address_line2": shipping_address.get("line2"),
"shipping_city": shipping_address.get("city"),
"shipping_state": shipping_address.get("state"),
"shipping_postal_code": shipping_address.get("postal_code"),
"shipping_country": shipping_address.get("country"),
}
)

# Set as default payment method for the customer
stripe.Customer.modify(
await crud.create_order(order_data)
logger.info(
"Successfully processed preorder setup for customer %s with payment method %s",
connected_customer_id,
invoice_settings={"default_payment_method": setup_intent.payment_method.id},
stripe_account=seller_connect_account_id,
)

payment_method_id = setup_intent.payment_method.id

# Create order for preorder
order_data = {
"user_id": session["client_reference_id"],
"user_email": session["customer_details"]["email"],
"stripe_checkout_session_id": session["id"],
"stripe_payment_intent_id": None,
"amount": int(session["metadata"]["price_amount"]),
"currency": "usd",
"status": "preorder_placed",
"quantity": 1,
"stripe_product_id": session["metadata"].get("stripe_product_id"),
"stripe_customer_id": connected_customer_id,
"stripe_payment_method_id": payment_method_id,
"stripe_connect_account_id": seller_connect_account_id,
}

# Add shipping details if available
if shipping_details := session.get("shipping_details"):
shipping_address = shipping_details.get("address", {})
order_data.update(
{
"shipping_name": shipping_details.get("name"),
"shipping_address_line1": shipping_address.get("line1"),
"shipping_address_line2": shipping_address.get("line2"),
"shipping_city": shipping_address.get("city"),
"shipping_state": shipping_address.get("state"),
"shipping_postal_code": shipping_address.get("postal_code"),
"shipping_country": shipping_address.get("country"),
}
payment_method_id,
)

await crud.create_order(order_data)
logger.info(
"Successfully processed preorder setup for customer %s with payment method %s",
connected_customer_id,
payment_method_id,
)

except Exception as e:
logger.error("Error processing preorder webhook: %s", str(e))
raise
except Exception as e:
logger.error("Error processing preorder webhook: %s", str(e))
raise

else:
# Handle regular checkout completion
await handle_checkout_session_completed(session, crud)
else:
# Handle regular checkout completion with the complete session
await handle_checkout_session_completed(complete_session, crud)
except Exception as e:
logger.error("Error retrieving complete session: %s", str(e))
raise

return {"status": "success"}
except Exception as e:
Expand All @@ -264,17 +271,24 @@ async def stripe_connect_webhook(request: Request, crud: Crud = Depends(Crud.get
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
seller_connect_account_id = session.get("metadata", {}).get("seller_connect_account_id")
if seller_connect_account_id:
session = stripe.checkout.Session.retrieve(session["id"], stripe_account=seller_connect_account_id)

shipping_details = session.get("shipping_details", {})
shipping_address = shipping_details.get("address", {})

# Get the line items to extract the quantity
line_items = stripe.checkout.Session.list_line_items(session["id"])
line_items = stripe.checkout.Session.list_line_items(session["id"], stripe_account=seller_connect_account_id)
quantity = line_items.data[0].quantity if line_items.data else 1

# Get payment intent if it exists
payment_intent = None
if session.get("payment_intent"):
payment_intent = stripe.PaymentIntent.retrieve(session["payment_intent"])
payment_intent = stripe.PaymentIntent.retrieve(
session["payment_intent"], stripe_account=seller_connect_account_id
)

# Create the order
is_preorder = session["metadata"].get("is_preorder") == "true"
Expand All @@ -292,6 +306,7 @@ async def handle_checkout_session_completed(session: Dict[str, Any], crud: Crud)
"status": "preorder_placed" if is_preorder else "processing",
"quantity": quantity,
"preorder_deposit_amount": session["amount_total"] if is_preorder else None,
"preorder_release_date": session["metadata"].get("preorder_release_date") if is_preorder else None,
"stripe_price_id": session["metadata"].get("full_price_id") if is_preorder else None,
"shipping_name": shipping_details.get("name"),
"shipping_address_line1": shipping_address.get("line1"),
Expand Down Expand Up @@ -321,39 +336,6 @@ async def handle_checkout_session_completed(session: Dict[str, Any], crud: Crud)
raise


async def fulfill_order(
session: Dict[str, Any],
crud: Annotated[Crud, Depends(Crud.get)],
) -> None:
user_id = session.get("client_reference_id")
if not user_id:
logger.warning("No user_id found for session: %s", session["id"])
return

user = await crud.get_user(user_id)
if not user:
logger.warning("User not found for id: %s", user_id)
return

order_data = {
"user_id": user_id,
"stripe_checkout_session_id": session["id"],
"stripe_payment_intent_id": session["payment_intent"],
"amount": session["amount_total"],
"currency": session["currency"],
"status": "processing",
"stripe_product_id": session["metadata"].get("stripe_product_id"),
"user_email": session["metadata"].get("user_email"),
}

try:
await crud.create_order(order_data)
logger.info("Order fulfilled for session: %s and user: %s", session["id"], user_id)
except Exception as e:
logger.error("Error creating order: %s", str(e))
# You might want to add some error handling here, such as retrying or notifying an admin


async def notify_payment_failed(session: Dict[str, Any]) -> None:
logger.warning("Payment failed for session: %s", session["id"])

Expand Down

0 comments on commit f900442

Please sign in to comment.