Fred808 commited on
Commit
93221cc
·
verified ·
1 Parent(s): c4f8c65

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -182
app.py CHANGED
@@ -457,10 +457,31 @@ def send_email_notification(order_details):
457
  return None
458
 
459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  async def process_order_flow(user_id: str, message: str) -> str:
461
  """
462
- A unified approach to parse dish(es), quantity, phone, and address
463
- whether the user provides them all at once or in multiple steps.
464
  """
465
  state = user_state.get(user_id)
466
  if state and state.is_expired():
@@ -468,7 +489,7 @@ async def process_order_flow(user_id: str, message: str) -> str:
468
  del user_state[user_id]
469
  state = None
470
 
471
- # 1) If user says "menu" or "order", initialize the flow
472
  if message.lower() in ["order", "menu"]:
473
  state = ConversationState()
474
  state.flow = "order"
@@ -479,7 +500,6 @@ async def process_order_flow(user_id: str, message: str) -> str:
479
  return "Sure! What dish would you like to order?"
480
  return ""
481
 
482
- # If user says "order" but we have no state
483
  if not state and "order" in message.lower():
484
  state = ConversationState()
485
  state.flow = "order"
@@ -488,50 +508,33 @@ async def process_order_flow(user_id: str, message: str) -> str:
488
  user_state[user_id] = state
489
  return "Sure! What dish would you like to order?"
490
 
491
- # 2) If we aren't in the order flow yet, try to detect dish(es)
492
  if not state or state.flow != "order":
493
  matched = match_dishes(message)
494
  if matched:
495
- # Multiple candidate dishes
496
- if len(matched) > 1:
497
- state = ConversationState()
498
- state.flow = "order"
499
- state.update_last_active()
500
- user_state[user_id] = state
501
- state.data["candidate_dishes"] = matched
502
- dish_options = ", ".join(matched)
503
- return (f"We found multiple dishes in your request: {dish_options}. "
504
- "Please specify which one you'd like to order or type 'both' if you'd like all.")
505
- else:
506
- # Single dish
507
  found_dish = matched[0]
508
  state = ConversationState()
509
  state.flow = "order"
510
  state.update_last_active()
511
  user_state[user_id] = state
512
  state.data["dish"] = found_dish
513
- # Attempt to parse quantity, phone, address if user typed them all
514
- single_dish_parse = _parse_single_dish_line(message, found_dish)
515
- if single_dish_parse["quantity"]:
516
- state.data["quantity"] = single_dish_parse["quantity"]
517
  state.step = 3
518
  else:
519
- # If no quantity found, we still need quantity
520
  state.step = 2
521
-
522
- if single_dish_parse["phone"]:
523
- state.data["phone_number"] = single_dish_parse["phone"]
524
- if single_dish_parse["address"]:
525
- state.data["address"] = single_dish_parse["address"]
526
-
527
- # Now see how much info we have:
528
  if state.step == 2 and not state.data.get("quantity"):
529
- # We must ask for quantity
530
  return f"You selected {found_dish}. How many servings would you like?"
531
  elif state.step == 3:
532
- # We have a quantity. Check if we also have phone and address
533
  if state.data.get("phone_number") and state.data.get("address"):
534
- # We have everything needed to skip to step 5
535
  shipping_cost = calculate_shipping_cost(state.data["address"])
536
  state.data["shipping_cost"] = shipping_cost
537
  state.step = 5
@@ -542,40 +545,29 @@ async def process_order_flow(user_id: str, message: str) -> str:
542
  return "Thank you. Please provide your delivery address."
543
  else:
544
  return "Please provide your phone number and address."
 
 
 
 
 
 
 
 
 
 
 
 
545
  else:
546
- # No dish matched
547
  return "I couldn't identify the dish. Please type the dish name from our menu."
548
 
549
- # 3) If we have candidate dishes and user needs to clarify
550
- if state and state.flow == "order" and "candidate_dishes" in state.data:
551
- # If user typed "both" => they want all candidate dishes
552
- if message.strip().lower() in ["both", "all"]:
553
- state.data["dishes"] = state.data["candidate_dishes"]
554
- del state.data["candidate_dishes"]
555
- state.step = 2 # We'll parse "2 for Jollof Rice and 3 for Fried Rice"
556
- dishes_str = ", ".join(state.data["dishes"])
557
- return (f"You have selected: {dishes_str}. How many servings of each would you like? "
558
- "(For example, '2 for Jollof Rice and 3 for Fried Rice')")
559
- else:
560
- # Maybe the user typed a single dish out of the candidates
561
- for dish in state.data["candidate_dishes"]:
562
- if dish.lower() in message.lower():
563
- state.data["dish"] = dish
564
- del state.data["candidate_dishes"]
565
- state.step = 2
566
- return f"You selected {dish}. How many servings would you like?"
567
- # If still unclear
568
- dish_options = ", ".join(state.data["candidate_dishes"])
569
- return (f"Please specify which one you'd like to order from: {dish_options} "
570
- "(or type 'both' if you'd like to order all).")
571
-
572
- # 4) If step == 2 for multiple dishes => parse "2 for Jollof Rice and 3 for Fried Rice"
573
  if state and state.flow == "order" and state.step == 2:
 
574
  if "dishes" in state.data and len(state.data["dishes"]) > 1:
575
  pairs = re.findall(r'(\d+)\s*for\s*([a-zA-Z\s]+)', message, flags=re.IGNORECASE)
576
  if not pairs:
577
  return ("I'm sorry, I didn't understand the quantity details. "
578
- "Please specify like '2 for Jollof Rice and 3 for Fried Rice'.")
579
  order_quantities = {}
580
  for quantity, dish_text in pairs:
581
  dish_text = dish_text.strip().lower()
@@ -590,9 +582,8 @@ async def process_order_flow(user_id: str, message: str) -> str:
590
  "Please provide your phone number and delivery address.")
591
  else:
592
  return ("I'm sorry, I couldn't match those dishes. "
593
- "Please try something like '2 for Jollof Rice and 3 for Fried Rice'.")
594
-
595
- # Single-dish scenario at step 2 => user typed just a quantity
596
  numbers = re.findall(r'\d+', message)
597
  if not numbers:
598
  return "Please enter a valid number for the quantity (e.g., 1, 2, 3)."
@@ -602,10 +593,10 @@ async def process_order_flow(user_id: str, message: str) -> str:
602
  state.data["quantity"] = quantity
603
  state.step = 3
604
  dish = state.data.get("dish", "")
605
- return (f"Got it. {quantity} serving(s) of {dish}. "
606
  "Please provide your phone number and delivery address.")
607
 
608
- # 5) Step == 3 => parse phone & address for single or multiple dishes
609
  if state and state.flow == "order" and state.step == 3:
610
  phone_pattern = r'(\+?\d{10,15})'
611
  phone_match = re.search(phone_pattern, message)
@@ -619,133 +610,99 @@ async def process_order_flow(user_id: str, message: str) -> str:
619
  state.data["address"] = address
620
  asyncio.create_task(update_user_profile(user_id, phone_number, address))
621
  else:
622
- # Maybe the user typed an address only, or partial info
623
- # If the phone was previously stored, we just store the address
624
  if "phone_number" in state.data:
625
  state.data["address"] = message.strip()
626
  else:
627
- # If no phone number found yet, we need it
628
  return ("Please provide both your phone number and address. "
629
  "For example: '09162409591, 1, Iyana Isashi, Isashi, Lagos'.")
630
-
631
  if "address" not in state.data:
632
- # If we only found phone but no address, ask for address
633
  return "Thank you. Please provide your delivery address."
634
-
635
- # If we have phone + address, move to step 5 => ask extras
636
  shipping_cost = calculate_shipping_cost(state.data["address"])
637
  state.data["shipping_cost"] = shipping_cost
638
  state.step = 5
639
  return (f"Thanks! Your phone number is recorded as: {state.data['phone_number']}.\n"
640
  f"Your delivery address is: {state.data['address']}.\n"
641
  f"Your delivery cost is N{shipping_cost}. Would you like extras (yes/no)?")
642
-
643
- # ... existing code above ...
644
 
645
- # Step 5: Ask user about extras
646
  if state and state.flow == "order" and state.step == 5:
647
- # If user wants extras
648
  if message.lower() in ["yes", "y"]:
649
  state.step = 6
650
  return "Please list the extras you'd like (e.g., drinks, sides)."
651
-
652
- # If user doesn't want extras
653
  elif message.lower() in ["no", "n"]:
654
  state.data["extras"] = ""
655
  state.step = 7
656
- # Summarize the order so far
657
- # If you handled multiple dishes, you'd need to show all.
658
- # For single dish, just show dish + quantity:
659
- dish = state.data.get("dish", "")
660
- quantity = state.data.get("quantity", 1)
661
- phone = state.data.get("phone_number", "")
662
- address = state.data.get("address", "")
663
- shipping_cost = state.data.get("shipping_cost", 0)
664
- extras = "None"
665
-
666
- # If you used state.data["orders"] for multiple dishes,
667
- # you'd build a summary from that dictionary.
668
-
669
- price_per_serving = 1500
670
- total_price = (quantity * price_per_serving) + shipping_cost
671
- summary = (f"Order Summary:\n"
672
- f"Dish: {dish}\n"
673
- f"Quantity: {quantity}\n"
674
- f"Phone: {phone}\n"
675
- f"Address: {address}\n"
676
- f"Shipping Cost: N{shipping_cost}\n"
677
- f"Total Price: N{total_price}\n"
678
- f"Extras: {extras}\n"
679
- f"Confirm order? (yes/no)")
680
- return summary
681
-
682
  else:
683
- # If user typed something else at step 5
684
  return "Please respond with 'yes' or 'no' regarding extras."
685
 
686
- # Step 6: If user typed "yes" for extras, we prompt them for a list
687
  if state and state.flow == "order" and state.step == 6:
688
- # The user has just listed extras
689
  state.data["extras"] = message
690
  state.step = 7
691
- dish = state.data.get("dish", "")
692
- quantity = state.data.get("quantity", 1)
693
- phone = state.data.get("phone_number", "")
694
- address = state.data.get("address", "")
695
- shipping_cost = state.data.get("shipping_cost", 0)
696
- extras = state.data.get("extras", "None")
697
-
698
- price_per_serving = get_dish_price(state.data["dish"])
699
- total_price = (quantity * price_per_serving) + shipping_cost
700
- summary = (f"Order Summary:\n"
701
- f"Dish: {dish}\n"
702
- f"Quantity: {quantity}\n"
703
- f"Phone: {phone}\n"
704
- f"Address: {address}\n"
705
- f"Shipping Cost: N{shipping_cost}\n"
706
- f"Total Price: N{total_price}\n"
707
- f"Extras: {extras}\n"
708
- f"Confirm order? (yes/no)")
709
- return summary
710
-
711
-
712
- # --- Step 7: Order Confirmation and Payment Link Generation ---
713
  if state and state.flow == "order" and state.step == 7:
714
  if message.lower() in ["yes", "y"]:
715
  order_id = f"ORD-{int(time.time())}"
716
  state.data["order_id"] = order_id
717
-
718
- # Determine whether this is a single-dish order or multi-dish order.
719
  if "orders" in state.data:
720
- # Multi-dish order: state.data["orders"] is a dict mapping dish name to quantity.
721
  total_price = 0
722
  for dish, qty in state.data["orders"].items():
723
  total_price += get_dish_price(dish) * qty
 
724
  total_price += state.data.get("shipping_cost", 0)
725
- dish_summary = ", ".join([f"{dish} (x{qty})" for dish, qty in state.data["orders"].items()])
 
726
  else:
727
- # Single-dish order:
728
- if "dish" in state.data:
729
- dish_name = state.data["dish"]
730
- elif "dishes" in state.data and len(state.data["dishes"]) == 1:
731
- dish_name = state.data["dishes"][0]
732
- else:
733
- return "Error: No dish selected in your order. Please try ordering again."
734
- price_per_serving = get_dish_price(dish_name)
735
- quantity = state.data.get("quantity", 1)
736
- total_price = (quantity * price_per_serving) + state.data.get("shipping_cost", 0)
737
- dish_summary = dish_name
738
-
739
  state.data["price"] = str(total_price)
740
-
741
  async def save_order():
742
  async with async_session() as session:
743
- # For simplicity, we store the dish summary as a string.
744
  order = Order(
745
  order_id=order_id,
746
  user_id=user_id,
747
  dish=dish_summary,
748
- quantity=str(state.data.get("quantity", 1)),
749
  price=str(total_price),
750
  status="Pending Payment",
751
  delivery_address=state.data.get("address", "")
@@ -754,57 +711,36 @@ async def process_order_flow(user_id: str, message: str) -> str:
754
  await session.commit()
755
  asyncio.create_task(save_order())
756
  asyncio.create_task(log_order_tracking(order_id, "Order Placed", "Order placed and awaiting payment."))
757
-
758
- # Prepare order details for email (or any other notification)
759
- order_details = {
760
- "order_id": order_id,
761
- "dish": dish_summary,
762
- "quantity": state.data.get("quantity", 1),
763
- "price": state.data["price"],
764
- "address": state.data.get("address", ""),
765
- "phone_number": state.data.get("phone_number", ""),
766
- "extras": state.data.get("extras", "None")
767
- }
768
-
769
- # Send email notification to admins via SMTP
770
- admin_emails = os.getenv("ADMIN_EMAILS", "admin@example.com").split(",")
771
  email_subject = f"New Order Received: {order_id}"
772
  email_body = (
773
  f"New Order Received:\n"
774
  f"Order ID: {order_id}\n"
775
- f"Dish: {order_details['dish']}\n"
776
- f"Quantity: {order_details['quantity']}\n"
777
- f"Total Price: N{order_details['price']}\n"
778
- f"Phone: {order_details['phone_number']}\n"
779
- f"Delivery Address: {order_details['address']}\n"
780
- f"Extras: {order_details['extras']}\n"
781
  f"Status: Pending Payment"
782
  )
783
- # Call your SMTP email helper; note that send_email_notification is synchronous.
784
  email_sent = send_email_notification(admin_emails, email_subject, email_body)
785
  if email_sent:
786
  print("Email notification sent successfully.")
787
  else:
788
  print("Failed to send email notification.")
789
-
790
- # Generate Paystack payment link (amount in kobo)
791
- email_for_paystack = "customer@example.com" # Replace with actual customer email if available
792
  payment_data = create_paystack_payment_link(email_for_paystack, total_price * 100, order_id)
793
- # Use dish from either "dish" or join "dishes" list
794
- if "dish" in state.data:
795
- dish_display = state.data["dish"]
796
- elif "dishes" in state.data:
797
- dish_display = ", ".join(state.data["dishes"])
798
- else:
799
- dish_display = "Unknown Dish"
800
-
801
  state.reset()
802
  if user_id in user_state:
803
  del user_state[user_id]
804
-
805
  if payment_data.get("status"):
806
  payment_link = payment_data["data"]["authorization_url"]
807
- return (f"Thank you for your order of {state.data.get('quantity', 1)} serving(s) of {dish_display}! "
808
  f"Your Order ID is {order_id}.\n\n"
809
  "Please complete your payment using one of the following options:\n"
810
  f"1. Pay online via our Paystack link: {payment_link}\n"
@@ -825,9 +761,9 @@ async def process_order_flow(user_id: str, message: str) -> str:
825
  if user_id in user_state:
826
  del user_state[user_id]
827
  return "Order canceled. Let me know if you'd like to try again."
828
- return ""
829
-
830
 
 
 
831
 
832
 
833
  def _parse_single_dish_line(message: str, dish_name: str) -> dict:
 
457
  return None
458
 
459
 
460
+
461
+ def match_dishes(user_input: str, threshold: int = 70) -> list:
462
+ matched_dishes = []
463
+ user_input_lower = user_input.lower()
464
+ for item in menu_items:
465
+ dish_name = item["name"]
466
+ # Direct substring check
467
+ if dish_name.lower() in user_input_lower:
468
+ matched_dishes.append(dish_name)
469
+ else:
470
+ score = fuzz.token_sort_ratio(user_input_lower, dish_name.lower())
471
+ if score >= threshold:
472
+ matched_dishes.append(dish_name)
473
+ return list(set(matched_dishes))
474
+
475
+ def get_dish_price(dish: str) -> int:
476
+ for item in menu_items:
477
+ if item["name"].lower() == dish.lower():
478
+ return item["price"]
479
+ return 0
480
+
481
+
482
  async def process_order_flow(user_id: str, message: str) -> str:
483
  """
484
+ Processes order flow allowing an unlimited number of dishes.
 
485
  """
486
  state = user_state.get(user_id)
487
  if state and state.is_expired():
 
489
  del user_state[user_id]
490
  state = None
491
 
492
+ # 1) If user says "order" or "menu", initialize the order flow.
493
  if message.lower() in ["order", "menu"]:
494
  state = ConversationState()
495
  state.flow = "order"
 
500
  return "Sure! What dish would you like to order?"
501
  return ""
502
 
 
503
  if not state and "order" in message.lower():
504
  state = ConversationState()
505
  state.flow = "order"
 
508
  user_state[user_id] = state
509
  return "Sure! What dish would you like to order?"
510
 
511
+ # 2) If we are not already in the order flow, detect dish(es) from the message.
512
  if not state or state.flow != "order":
513
  matched = match_dishes(message)
514
  if matched:
515
+ if len(matched) == 1:
516
+ # Single dish: store in state.data["dish"]
 
 
 
 
 
 
 
 
 
 
517
  found_dish = matched[0]
518
  state = ConversationState()
519
  state.flow = "order"
520
  state.update_last_active()
521
  user_state[user_id] = state
522
  state.data["dish"] = found_dish
523
+ # Try to parse quantity, phone, address from the same message.
524
+ single_parse = _parse_single_dish_line(message, found_dish)
525
+ if single_parse["quantity"]:
526
+ state.data["quantity"] = single_parse["quantity"]
527
  state.step = 3
528
  else:
 
529
  state.step = 2
530
+ if single_parse["phone"]:
531
+ state.data["phone_number"] = single_parse["phone"]
532
+ if single_parse["address"]:
533
+ state.data["address"] = single_parse["address"]
 
 
 
534
  if state.step == 2 and not state.data.get("quantity"):
 
535
  return f"You selected {found_dish}. How many servings would you like?"
536
  elif state.step == 3:
 
537
  if state.data.get("phone_number") and state.data.get("address"):
 
538
  shipping_cost = calculate_shipping_cost(state.data["address"])
539
  state.data["shipping_cost"] = shipping_cost
540
  state.step = 5
 
545
  return "Thank you. Please provide your delivery address."
546
  else:
547
  return "Please provide your phone number and address."
548
+ else:
549
+ # Multiple dishes detected—allow unlimited dishes.
550
+ state = ConversationState()
551
+ state.flow = "order"
552
+ state.update_last_active()
553
+ user_state[user_id] = state
554
+ state.data["dishes"] = matched # Store all matched dishes
555
+ state.step = 2
556
+ dish_options = ", ".join(matched)
557
+ return (f"You have selected the following dishes: {dish_options}.\n"
558
+ "How many servings of each would you like? "
559
+ "For example: '2 for Jollof Rice, 3 for Chicken Wings, 1 for Fish'")
560
  else:
 
561
  return "I couldn't identify the dish. Please type the dish name from our menu."
562
 
563
+ # 3) If state exists and we're at step 2: parse quantity details.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  if state and state.flow == "order" and state.step == 2:
565
+ # For multi-dish orders, expect input like "2 for Jollof Rice, 3 for Chicken Wings, 1 for Fish"
566
  if "dishes" in state.data and len(state.data["dishes"]) > 1:
567
  pairs = re.findall(r'(\d+)\s*for\s*([a-zA-Z\s]+)', message, flags=re.IGNORECASE)
568
  if not pairs:
569
  return ("I'm sorry, I didn't understand the quantity details. "
570
+ "Please specify like '2 for Jollof Rice, 3 for Chicken Wings, 1 for Fish'.")
571
  order_quantities = {}
572
  for quantity, dish_text in pairs:
573
  dish_text = dish_text.strip().lower()
 
582
  "Please provide your phone number and delivery address.")
583
  else:
584
  return ("I'm sorry, I couldn't match those dishes. "
585
+ "Please try something like '2 for Jollof Rice, 3 for Chicken Wings, 1 for Fish'.")
586
+ # For single-dish order at step 2: user types just a quantity.
 
587
  numbers = re.findall(r'\d+', message)
588
  if not numbers:
589
  return "Please enter a valid number for the quantity (e.g., 1, 2, 3)."
 
593
  state.data["quantity"] = quantity
594
  state.step = 3
595
  dish = state.data.get("dish", "")
596
+ return (f"Got it. {quantity} serving(s) of {dish}.\n"
597
  "Please provide your phone number and delivery address.")
598
 
599
+ # 4) If state.step == 3: parse phone & address
600
  if state and state.flow == "order" and state.step == 3:
601
  phone_pattern = r'(\+?\d{10,15})'
602
  phone_match = re.search(phone_pattern, message)
 
610
  state.data["address"] = address
611
  asyncio.create_task(update_user_profile(user_id, phone_number, address))
612
  else:
 
 
613
  if "phone_number" in state.data:
614
  state.data["address"] = message.strip()
615
  else:
 
616
  return ("Please provide both your phone number and address. "
617
  "For example: '09162409591, 1, Iyana Isashi, Isashi, Lagos'.")
 
618
  if "address" not in state.data:
 
619
  return "Thank you. Please provide your delivery address."
 
 
620
  shipping_cost = calculate_shipping_cost(state.data["address"])
621
  state.data["shipping_cost"] = shipping_cost
622
  state.step = 5
623
  return (f"Thanks! Your phone number is recorded as: {state.data['phone_number']}.\n"
624
  f"Your delivery address is: {state.data['address']}.\n"
625
  f"Your delivery cost is N{shipping_cost}. Would you like extras (yes/no)?")
 
 
626
 
627
+ # 5) Step 5: Ask about extras.
628
  if state and state.flow == "order" and state.step == 5:
 
629
  if message.lower() in ["yes", "y"]:
630
  state.step = 6
631
  return "Please list the extras you'd like (e.g., drinks, sides)."
 
 
632
  elif message.lower() in ["no", "n"]:
633
  state.data["extras"] = ""
634
  state.step = 7
635
+ # Prepare order summary for confirmation.
636
+ if "orders" in state.data:
637
+ # Multi-dish order summary:
638
+ summary = "\n".join([f"{qty} serving(s) of {dish}" for dish, qty in state.data["orders"].items()])
639
+ quantity_total = sum(state.data["orders"].values())
640
+ dish_summary = ", ".join(state.data["orders"].keys())
641
+ else:
642
+ summary = f"{state.data.get('quantity', 1)} serving(s) of {state.data.get('dish', '')}"
643
+ quantity_total = state.data.get("quantity", 1)
644
+ dish_summary = state.data.get("dish", "")
645
+ price_per_serving = get_dish_price(state.data.get("dish", "")) if "dish" in state.data else 0
646
+ total_price = (quantity_total * price_per_serving) + state.data.get("shipping_cost", 0)
647
+ confirmation = (f"Order Summary:\nDish(es): {dish_summary}\nQuantity: {quantity_total}\n"
648
+ f"Phone: {state.data.get('phone_number', '')}\n"
649
+ f"Address: {state.data.get('address', '')}\n"
650
+ f"Shipping Cost: N{state.data.get('shipping_cost', 0)}\n"
651
+ f"Total Price: N{total_price}\n"
652
+ f"Extras: None\nConfirm order? (yes/no)")
653
+ return confirmation
 
 
 
 
 
 
 
654
  else:
 
655
  return "Please respond with 'yes' or 'no' regarding extras."
656
 
657
+ # 6) Step 6: Process extras list.
658
  if state and state.flow == "order" and state.step == 6:
 
659
  state.data["extras"] = message
660
  state.step = 7
661
+ if "orders" in state.data:
662
+ summary = "\n".join([f"{qty} serving(s) of {dish}" for dish, qty in state.data["orders"].items()])
663
+ quantity_total = sum(state.data["orders"].values())
664
+ dish_summary = ", ".join(state.data["orders"].keys())
665
+ else:
666
+ summary = f"{state.data.get('quantity', 1)} serving(s) of {state.data.get('dish', '')}"
667
+ quantity_total = state.data.get("quantity", 1)
668
+ dish_summary = state.data.get("dish", "")
669
+ price_per_serving = get_dish_price(state.data.get("dish", "")) if "dish" in state.data else 0
670
+ total_price = (quantity_total * price_per_serving) + state.data.get("shipping_cost", 0)
671
+ confirmation = (f"Order Summary:\nDish(es): {dish_summary}\nQuantity: {quantity_total}\n"
672
+ f"Phone: {state.data.get('phone_number', '')}\n"
673
+ f"Address: {state.data.get('address', '')}\n"
674
+ f"Shipping Cost: N{state.data.get('shipping_cost', 0)}\n"
675
+ f"Total Price: N{total_price}\n"
676
+ f"Extras: {state.data.get('extras', 'None')}\nConfirm order? (yes/no)")
677
+ return confirmation
678
+
679
+ # 7) Step 7: Order Confirmation and Payment Link Generation
 
 
 
680
  if state and state.flow == "order" and state.step == 7:
681
  if message.lower() in ["yes", "y"]:
682
  order_id = f"ORD-{int(time.time())}"
683
  state.data["order_id"] = order_id
684
+ # Handle both single-dish and multi-dish orders:
 
685
  if "orders" in state.data:
 
686
  total_price = 0
687
  for dish, qty in state.data["orders"].items():
688
  total_price += get_dish_price(dish) * qty
689
+ # Add shipping cost once.
690
  total_price += state.data.get("shipping_cost", 0)
691
+ dish_summary = ", ".join(state.data["orders"].keys())
692
+ quantity_total = sum(state.data["orders"].values())
693
  else:
694
+ dish_summary = state.data.get("dish", "")
695
+ quantity_total = state.data.get("quantity", 1)
696
+ total_price = (quantity_total * get_dish_price(dish_summary)) + state.data.get("shipping_cost", 0)
 
 
 
 
 
 
 
 
 
697
  state.data["price"] = str(total_price)
698
+
699
  async def save_order():
700
  async with async_session() as session:
 
701
  order = Order(
702
  order_id=order_id,
703
  user_id=user_id,
704
  dish=dish_summary,
705
+ quantity=str(quantity_total),
706
  price=str(total_price),
707
  status="Pending Payment",
708
  delivery_address=state.data.get("address", "")
 
711
  await session.commit()
712
  asyncio.create_task(save_order())
713
  asyncio.create_task(log_order_tracking(order_id, "Order Placed", "Order placed and awaiting payment."))
714
+
715
+ # Send email notification to admin via SMTP (using your SMTP integration)
716
+ admin_emails = os.getenv("ADMIN_EMAILS", "samyung05@gmail.com").split(",")
 
 
 
 
 
 
 
 
 
 
 
717
  email_subject = f"New Order Received: {order_id}"
718
  email_body = (
719
  f"New Order Received:\n"
720
  f"Order ID: {order_id}\n"
721
+ f"Dish(es): {dish_summary}\n"
722
+ f"Quantity: {quantity_total}\n"
723
+ f"Total Price: N{total_price}\n"
724
+ f"Phone: {state.data.get('phone_number', '')}\n"
725
+ f"Delivery Address: {state.data.get('address', 'Not Provided')}\n"
726
+ f"Extras: {state.data.get('extras', 'None')}\n"
727
  f"Status: Pending Payment"
728
  )
729
+ # Call your SMTP API or use a helper to send email:
730
  email_sent = send_email_notification(admin_emails, email_subject, email_body)
731
  if email_sent:
732
  print("Email notification sent successfully.")
733
  else:
734
  print("Failed to send email notification.")
735
+
736
+ email_for_paystack = "customer@example.com" # Replace with actual email if available
 
737
  payment_data = create_paystack_payment_link(email_for_paystack, total_price * 100, order_id)
 
 
 
 
 
 
 
 
738
  state.reset()
739
  if user_id in user_state:
740
  del user_state[user_id]
 
741
  if payment_data.get("status"):
742
  payment_link = payment_data["data"]["authorization_url"]
743
+ return (f"Thank you for your order of {quantity_total} serving(s) of {dish_summary}! "
744
  f"Your Order ID is {order_id}.\n\n"
745
  "Please complete your payment using one of the following options:\n"
746
  f"1. Pay online via our Paystack link: {payment_link}\n"
 
761
  if user_id in user_state:
762
  del user_state[user_id]
763
  return "Order canceled. Let me know if you'd like to try again."
 
 
764
 
765
+ # Final fallback
766
+ return ""
767
 
768
 
769
  def _parse_single_dish_line(message: str, dish_name: str) -> dict: