Update app.py
Browse files
app.py
CHANGED
|
@@ -9,7 +9,7 @@ from bs4 import BeautifulSoup
|
|
| 9 |
from sqlalchemy import select
|
| 10 |
|
| 11 |
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks, UploadFile, File, Form
|
| 12 |
-
from fastapi.responses import JSONResponse, StreamingResponse
|
| 13 |
|
| 14 |
import openai
|
| 15 |
|
|
@@ -28,6 +28,11 @@ DATABASE_URL = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres.lgbnxply
|
|
| 28 |
NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "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")
|
| 29 |
openai.api_key = os.getenv("OPENAI_API_KEY", "your_openai_api_key")
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# --- Database Setup ---
|
| 32 |
Base = declarative_base()
|
| 33 |
|
|
@@ -78,7 +83,6 @@ async def init_db():
|
|
| 78 |
await conn.run_sync(Base.metadata.create_all)
|
| 79 |
|
| 80 |
# --- Global In-Memory Stores ---
|
| 81 |
-
# Instead of a plain dict for conversation context, we use a dedicated class below.
|
| 82 |
user_state = {} # e.g., { user_id: ConversationState }
|
| 83 |
conversation_context = {} # { user_id: [ { "timestamp": ..., "role": "user"/"bot", "message": ... }, ... ] }
|
| 84 |
proactive_timer = {}
|
|
@@ -166,6 +170,26 @@ def create_paystack_payment_link(email: str, amount: int, reference: str) -> dic
|
|
| 166 |
except Exception as e:
|
| 167 |
return {"status": False, "message": str(e)}
|
| 168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
# --- NVIDIA LLM Streaming Functions ---
|
| 170 |
def stream_text_completion(prompt: str):
|
| 171 |
from openai import OpenAI
|
|
@@ -316,6 +340,28 @@ def process_order_flow(user_id: str, message: str) -> str:
|
|
| 316 |
await session.commit()
|
| 317 |
asyncio.create_task(save_order())
|
| 318 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
email = "customer@example.com" # Placeholder; retrieve from profile if available
|
| 320 |
payment_data = create_paystack_payment_link(email, total_price * 100, order_id)
|
| 321 |
dish_name = state.data.get("dish", "")
|
|
@@ -580,22 +626,54 @@ async def process_voice(file: UploadFile = File(...)):
|
|
| 580 |
simulated_text = "Simulated speech-to-text conversion result."
|
| 581 |
return {"transcription": simulated_text}
|
| 582 |
|
| 583 |
-
|
|
|
|
| 584 |
async def payment_callback(request: Request):
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 591 |
)
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
|
| 600 |
if __name__ == "__main__":
|
| 601 |
import uvicorn
|
|
|
|
| 9 |
from sqlalchemy import select
|
| 10 |
|
| 11 |
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks, UploadFile, File, Form
|
| 12 |
+
from fastapi.responses import JSONResponse, StreamingResponse, RedirectResponse
|
| 13 |
|
| 14 |
import openai
|
| 15 |
|
|
|
|
| 28 |
NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "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")
|
| 29 |
openai.api_key = os.getenv("OPENAI_API_KEY", "your_openai_api_key")
|
| 30 |
|
| 31 |
+
# WhatsApp Business API credentials (Cloud API)
|
| 32 |
+
WHATSAPP_PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID", "505165592686490")
|
| 33 |
+
WHATSAPP_ACCESS_TOKEN = os.getenv("WHATSAPP_ACCESS_TOKEN", "EAANi3PCgUmQBO9HKRZA8afKesVL7trAlZAqsFlrebFZBEilHWKKBp4H8Xw2eb2GdRTUZCeOA9lsNmm6zFO19kFq7uJdTTZCq9HrJJoGgNvzc0iehNuLaVdHd8AhZConIoAwLY7MlaA1nc0OKJ0Lw5LhFvJN3ZCuJLP3NskPn42v6HPkFnbFdXeqfJx9iosk4sOQ")
|
| 34 |
+
MANAGEMENT_WHATSAPP_NUMBER = os.getenv("MANAGEMENT_WHATSAPP_NUMBER", "2348035105411") # e.g. "15551234567" (international format without prefix)
|
| 35 |
+
|
| 36 |
# --- Database Setup ---
|
| 37 |
Base = declarative_base()
|
| 38 |
|
|
|
|
| 83 |
await conn.run_sync(Base.metadata.create_all)
|
| 84 |
|
| 85 |
# --- Global In-Memory Stores ---
|
|
|
|
| 86 |
user_state = {} # e.g., { user_id: ConversationState }
|
| 87 |
conversation_context = {} # { user_id: [ { "timestamp": ..., "role": "user"/"bot", "message": ... }, ... ] }
|
| 88 |
proactive_timer = {}
|
|
|
|
| 170 |
except Exception as e:
|
| 171 |
return {"status": False, "message": str(e)}
|
| 172 |
|
| 173 |
+
# --- WhatsApp Business API Helper ---
|
| 174 |
+
def send_whatsapp_message(recipient: str, message_body: str) -> dict:
|
| 175 |
+
"""
|
| 176 |
+
Sends a WhatsApp text message using the WhatsApp Cloud API.
|
| 177 |
+
`recipient` should be in international format, e.g., "15551234567".
|
| 178 |
+
"""
|
| 179 |
+
url = f"https://graph.facebook.com/v15.0/{WHATSAPP_PHONE_NUMBER_ID}/messages"
|
| 180 |
+
headers = {
|
| 181 |
+
"Authorization": f"Bearer {WHATSAPP_ACCESS_TOKEN}",
|
| 182 |
+
"Content-Type": "application/json"
|
| 183 |
+
}
|
| 184 |
+
payload = {
|
| 185 |
+
"messaging_product": "whatsapp",
|
| 186 |
+
"to": recipient,
|
| 187 |
+
"type": "text",
|
| 188 |
+
"text": {"body": message_body}
|
| 189 |
+
}
|
| 190 |
+
response = requests.post(url, headers=headers, json=payload)
|
| 191 |
+
return response.json()
|
| 192 |
+
|
| 193 |
# --- NVIDIA LLM Streaming Functions ---
|
| 194 |
def stream_text_completion(prompt: str):
|
| 195 |
from openai import OpenAI
|
|
|
|
| 340 |
await session.commit()
|
| 341 |
asyncio.create_task(save_order())
|
| 342 |
|
| 343 |
+
# Notify management of the new order via WhatsApp
|
| 344 |
+
async def notify_management_order(order_details: dict):
|
| 345 |
+
message_body = (
|
| 346 |
+
f"New Order Received:\n"
|
| 347 |
+
f"Order ID: {order_details['order_id']}\n"
|
| 348 |
+
f"Dish: {order_details['dish']}\n"
|
| 349 |
+
f"Quantity: {order_details['quantity']}\n"
|
| 350 |
+
f"Total Price: {order_details['price']}\n"
|
| 351 |
+
f"Delivery Address: {order_details.get('address', 'Not Provided')}\n"
|
| 352 |
+
f"Status: Pending Payment"
|
| 353 |
+
)
|
| 354 |
+
await asyncio.to_thread(send_whatsapp_message, MANAGEMENT_WHATSAPP_NUMBER, message_body)
|
| 355 |
+
order_details = {
|
| 356 |
+
"order_id": order_id,
|
| 357 |
+
"dish": state.data["dish"],
|
| 358 |
+
"quantity": state.data["quantity"],
|
| 359 |
+
"price": state.data["price"],
|
| 360 |
+
"address": state.data.get("address", ""),
|
| 361 |
+
"status": "Pending Payment"
|
| 362 |
+
}
|
| 363 |
+
asyncio.create_task(notify_management_order(order_details))
|
| 364 |
+
|
| 365 |
email = "customer@example.com" # Placeholder; retrieve from profile if available
|
| 366 |
payment_data = create_paystack_payment_link(email, total_price * 100, order_id)
|
| 367 |
dish_name = state.data.get("dish", "")
|
|
|
|
| 626 |
simulated_text = "Simulated speech-to-text conversion result."
|
| 627 |
return {"transcription": simulated_text}
|
| 628 |
|
| 629 |
+
# --- Payment Callback Endpoint with Payment Tracking and Redirection ---
|
| 630 |
+
@app.api_route("/payment_callback", methods=["GET", "POST"])
|
| 631 |
async def payment_callback(request: Request):
|
| 632 |
+
# GET: User redirection after payment
|
| 633 |
+
if request.method == "GET":
|
| 634 |
+
params = request.query_params
|
| 635 |
+
order_id = params.get("reference")
|
| 636 |
+
status = params.get("status", "Paid")
|
| 637 |
+
if not order_id:
|
| 638 |
+
raise HTTPException(status_code=400, detail="Missing order reference in callback.")
|
| 639 |
+
async with async_session() as session:
|
| 640 |
+
result = await session.execute(
|
| 641 |
+
Order.__table__.select().where(Order.order_id == order_id)
|
| 642 |
+
)
|
| 643 |
+
order = result.scalar_one_or_none()
|
| 644 |
+
if order:
|
| 645 |
+
order.status = status
|
| 646 |
+
await session.commit()
|
| 647 |
+
else:
|
| 648 |
+
raise HTTPException(status_code=404, detail="Order not found.")
|
| 649 |
+
# Notify management via WhatsApp about the payment update
|
| 650 |
+
await asyncio.to_thread(send_whatsapp_message, MANAGEMENT_WHATSAPP_NUMBER,
|
| 651 |
+
f"Payment Update:\nOrder ID: {order_id} is now {status}."
|
| 652 |
)
|
| 653 |
+
# Redirect user back to the chat interface (adjust URL as needed)
|
| 654 |
+
redirect_url = f"https://yourdomain.com/chat?order_id={order_id}&status=success"
|
| 655 |
+
return RedirectResponse(url=redirect_url)
|
| 656 |
+
# POST: Server-to-server callback from Paystack
|
| 657 |
+
else:
|
| 658 |
+
data = await request.json()
|
| 659 |
+
order_id = data.get("reference")
|
| 660 |
+
new_status = data.get("status", "Paid")
|
| 661 |
+
if not order_id:
|
| 662 |
+
raise HTTPException(status_code=400, detail="Missing order reference in callback.")
|
| 663 |
+
async with async_session() as session:
|
| 664 |
+
result = await session.execute(
|
| 665 |
+
Order.__table__.select().where(Order.order_id == order_id)
|
| 666 |
+
)
|
| 667 |
+
order = result.scalar_one_or_none()
|
| 668 |
+
if order:
|
| 669 |
+
order.status = new_status
|
| 670 |
+
await session.commit()
|
| 671 |
+
await asyncio.to_thread(send_whatsapp_message, MANAGEMENT_WHATSAPP_NUMBER,
|
| 672 |
+
f"Payment Update:\nOrder ID: {order_id} is now {new_status}."
|
| 673 |
+
)
|
| 674 |
+
return JSONResponse(content={"message": "Order updated successfully."})
|
| 675 |
+
else:
|
| 676 |
+
raise HTTPException(status_code=404, detail="Order not found.")
|
| 677 |
|
| 678 |
if __name__ == "__main__":
|
| 679 |
import uvicorn
|