Update app.py
Browse files
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 |
-
|
| 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 "
|
| 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
|
| 492 |
if not state or state.flow != "order":
|
| 493 |
matched = match_dishes(message)
|
| 494 |
if matched:
|
| 495 |
-
|
| 496 |
-
|
| 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 |
-
#
|
| 514 |
-
|
| 515 |
-
if
|
| 516 |
-
state.data["quantity"] =
|
| 517 |
state.step = 3
|
| 518 |
else:
|
| 519 |
-
# If no quantity found, we still need quantity
|
| 520 |
state.step = 2
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 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
|
| 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
|
| 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
|
| 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 |
-
#
|
| 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
|
| 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 |
-
#
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 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:
|
| 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 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 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(
|
|
|
|
| 726 |
else:
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 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(
|
| 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 |
-
#
|
| 759 |
-
|
| 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: {
|
| 776 |
-
f"Quantity: {
|
| 777 |
-
f"Total Price: N{
|
| 778 |
-
f"Phone: {
|
| 779 |
-
f"Delivery Address: {
|
| 780 |
-
f"Extras: {
|
| 781 |
f"Status: Pending Payment"
|
| 782 |
)
|
| 783 |
-
# Call your SMTP
|
| 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 |
-
#
|
| 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 {
|
| 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:
|