from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import Optional, Dict from supabase import create_client, Client import os import requests import uuid app = FastAPI(title="Cié Cié Backend API") # 🌟 解決 CORS (跨域) 問題:允許您的 GitHub Pages 前端呼叫這台主機 app.add_middleware( CORSMiddleware, allow_origins=["*"], # 正式上線可改為 ["https://ciecietaipei.github.io"] allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 讀取環境變數 (請在 Hugging Face Settings 中設定) SUPABASE_URL = os.getenv("SUPABASE_URL", "") # ⚠️ 注意:這裡必須使用 service_role key,才能無視 RLS 直接查核黑名單與寫入! SUPABASE_KEY = os.getenv("SUPABASE_KEY", "") LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN", "") BOSS_LINE_ID = os.getenv("BOSS_LINE_ID", "") # 老闆的 LINE User ID # 初始化 Supabase supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) if SUPABASE_URL else None # 定義前端傳來的資料結構 (Payload) class OrderPayload(BaseModel): service_type: str name: str tel: str date: str time: str line_id: Optional[str] = "" pax: int = 2 cart: Dict[str, int] = {} deposit_required: int = 0 total_amount: int = 0 @app.get("/") def read_root(): return {"status": "online", "message": "Cié Cié FastAPI is running."} @app.post("/api/submit_booking") async def submit_booking(payload: OrderPayload): if not supabase: raise HTTPException(status_code=500, detail="資料庫未設定") # ========================================== # 🕵️‍♂️ 階段 1:查核 No-Show 黑名單 # ========================================== is_noshow = False try: # 用電話號碼去資料庫找有沒有 No-Show 紀錄 res = supabase.table("bookings").select("id").eq("tel", payload.tel).eq("status", "No-Show").execute() is_noshow = len(res.data) > 0 except Exception as e: print("查詢黑名單失敗:", e) # 決定最終訂金:如果是黑名單,原本不用付訂金的也強制收 $1000 final_deposit = payload.deposit_required if is_noshow and final_deposit == 0: final_deposit = 1000 # ========================================== # 💳 階段 2:金流分流處理 (需要收訂金 vs 不用收訂金) # ========================================== # 狀況 A:需要付款 (產生 LINE Pay 連結) if final_deposit > 0: order_id = f"ORDER-{uuid.uuid4().hex[:8].upper()}" # ⚠️ 這裡未來會串接真實的 LINE Pay API,目前先回傳模擬的結帳網址 mock_payment_url = f"https://sandbox-web-pay.line.me/web/payment/wait?transactionReserveId=mock&orderId={order_id}" # 我們不先把資料寫入資料庫,而是等他「付款成功」的 Webhook 再寫入 return { "status": "require_payment", "message": "訂單需支付訂金", "is_noshow_penalty": is_noshow, # 讓前端知道是不是因為被懲罰才要付錢 "deposit_amount": final_deposit, "payment_url": mock_payment_url, "order_id": order_id } # 狀況 B:不需付款 (直接寫入資料庫並完成訂位) booking_data = { "name": payload.name, "tel": payload.tel, "date": payload.date, "time": payload.time, "pax": payload.pax, "email": "", # 可由前端擴充 "user_id": payload.line_id, "status": "待處理", "remarks": f"類型: {'外帶' if payload.service_type == 'takeout' else '內用'}\n餐點內容: {payload.cart}" } try: supabase.table("bookings").insert(booking_data).execute() # 🔔 通知老闆有新訂位 notify_boss(payload.name, payload.tel, payload.date, payload.time, payload.pax) return { "status": "success", "message": "訂位已成功建立!" } except Exception as e: raise HTTPException(status_code=500, detail=f"寫入資料庫失敗: {str(e)}") def notify_boss(name, tel, date, time, pax): """發送 LINE 通知給老闆 (需設定環境變數)""" if not LINE_ACCESS_TOKEN or not BOSS_LINE_ID: return msg = f"🔔 【新訂位通知】\n姓名:{name}\n電話:{tel}\n時間:{date} {time}\n人數:{pax}位" headers = {"Authorization": f"Bearer {LINE_ACCESS_TOKEN}"} payload = {"to": BOSS_LINE_ID, "messages": [{"type": "text", "text": msg}]} try: requests.post("https://api.line.me/v2/bot/message/push", headers=headers, json=payload) except: pass