DeepLearning101 commited on
Commit
b4479dc
·
verified ·
1 Parent(s): 1b7678c

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +102 -26
main.py CHANGED
@@ -6,13 +6,18 @@ from supabase import create_client, Client
6
  import os
7
  import requests
8
  import uuid
 
 
 
 
 
9
 
10
  app = FastAPI(title="Cié Cié Backend API")
11
 
12
  # 🌟 解決 CORS (跨域) 問題:允許您的 GitHub Pages 前端呼叫這台主機
13
  app.add_middleware(
14
  CORSMiddleware,
15
- allow_origins=["*"], # 正式上線可改為 ["https://ciecietaipei.github.io"]
16
  allow_credentials=True,
17
  allow_methods=["*"],
18
  allow_headers=["*"],
@@ -20,11 +25,17 @@ app.add_middleware(
20
 
21
  # 讀取環境變數 (請在 Hugging Face Settings 中設定)
22
  SUPABASE_URL = os.getenv("SUPABASE_URL", "")
23
- # ⚠️ 注意:這裡必須使用 service_role key,才能無視 RLS 直接查核黑名單與寫入!
24
- SUPABASE_KEY = os.getenv("SUPABASE_KEY", "")
25
-
26
  LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN", "")
27
- BOSS_LINE_ID = os.getenv("BOSS_LINE_ID", "") # 老闆的 LINE User ID
 
 
 
 
 
 
 
 
28
 
29
  # 初始化 Supabase
30
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) if SUPABASE_URL else None
@@ -71,22 +82,92 @@ async def submit_booking(payload: OrderPayload):
71
  # 💳 階段 2:金流分流處理 (需要收訂金 vs 不用收訂金)
72
  # ==========================================
73
 
74
- # 狀況 A:需要付款 (產生 LINE Pay 連結)
75
  if final_deposit > 0:
76
  order_id = f"ORDER-{uuid.uuid4().hex[:8].upper()}"
77
 
78
- # ⚠️ 這裡未來會串接真實的 LINE Pay API,目前先回傳模擬結帳網址
79
- mock_payment_url = f"https://sandbox-web-pay.line.me/web/payment/wait?transactionReserveId=mock&orderId={order_id}"
80
-
81
- # 我們不先把資料寫入資料庫,而是等他「付款成功」的 Webhook 再寫入
82
- return {
83
- "status": "require_payment",
84
- "message": "訂單需支付訂金",
85
- "is_noshow_penalty": is_noshow, # 讓前端知道是不是因為被懲罰才要付錢
86
- "deposit_amount": final_deposit,
87
- "payment_url": mock_payment_url,
88
- "order_id": order_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  # 狀況 B:不需付款 (直接寫入資料庫並完成訂位)
92
  booking_data = {
@@ -95,27 +176,22 @@ async def submit_booking(payload: OrderPayload):
95
  "date": payload.date,
96
  "time": payload.time,
97
  "pax": payload.pax,
98
- "email": "", # 可由前端擴充
99
  "user_id": payload.line_id,
100
  "status": "待處理",
101
- "remarks": f"類型: {'外帶' if payload.service_type == 'takeout' else '內用'}\n餐點內容: {payload.cart}"
102
  }
103
 
104
  try:
105
  supabase.table("bookings").insert(booking_data).execute()
106
-
107
  # 🔔 通知老闆有新訂位
108
  notify_boss(payload.name, payload.tel, payload.date, payload.time, payload.pax)
109
-
110
- return {
111
- "status": "success",
112
- "message": "訂位已成功建立!"
113
- }
114
  except Exception as e:
115
  raise HTTPException(status_code=500, detail=f"寫入資料庫失敗: {str(e)}")
116
 
117
  def notify_boss(name, tel, date, time, pax):
118
- """發送 LINE 通知給老闆 (需設定環境變數)"""
119
  if not LINE_ACCESS_TOKEN or not BOSS_LINE_ID:
120
  return
121
  msg = f"🔔 【新訂位通知】\n姓名:{name}\n電話:{tel}\n時間:{date} {time}\n人數:{pax}位"
 
6
  import os
7
  import requests
8
  import uuid
9
+ import json
10
+ import base64
11
+ import hashlib
12
+ import hmac
13
+ import time
14
 
15
  app = FastAPI(title="Cié Cié Backend API")
16
 
17
  # 🌟 解決 CORS (跨域) 問題:允許您的 GitHub Pages 前端呼叫這台主機
18
  app.add_middleware(
19
  CORSMiddleware,
20
+ allow_origins=["*"],
21
  allow_credentials=True,
22
  allow_methods=["*"],
23
  allow_headers=["*"],
 
25
 
26
  # 讀取環境變數 (請在 Hugging Face Settings 中設定)
27
  SUPABASE_URL = os.getenv("SUPABASE_URL", "")
28
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY", "") # 必須使用 service_role key
 
 
29
  LINE_ACCESS_TOKEN = os.getenv("LINE_ACCESS_TOKEN", "")
30
+ BOSS_LINE_ID = os.getenv("BOSS_LINE_ID", "")
31
+
32
+ # 🌟 LINE Pay 金鑰設定 🌟
33
+ LINE_PAY_CHANNEL_ID = os.getenv("LINE_PAY_CHANNEL_ID", "")
34
+ LINE_PAY_CHANNEL_SECRET = os.getenv("LINE_PAY_CHANNEL_SECRET", "")
35
+ LINE_PAY_BASE_URL = "https://sandbox-api-pay.line.me" # 這是沙盒(測試)環境的專屬網址
36
+
37
+ # 結帳完成後要跳轉回哪個網頁?(設定回您的官網)
38
+ RETURN_URL = "https://ciecietaipei.github.io/index.html"
39
 
40
  # 初始化 Supabase
41
  supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) if SUPABASE_URL else None
 
82
  # 💳 階段 2:金流分流處理 (需要收訂金 vs 不用收訂金)
83
  # ==========================================
84
 
85
+ # 狀況 A:需要付款 (產生真實 LINE Pay 連結)
86
  if final_deposit > 0:
87
  order_id = f"ORDER-{uuid.uuid4().hex[:8].upper()}"
88
 
89
+ # 1. 準備發送給 LINE Pay 的訂單資料 (JSON)
90
+ request_body = {
91
+ "amount": final_deposit,
92
+ "currency": "TWD",
93
+ "orderId": order_id,
94
+ "packages": [
95
+ {
96
+ "id": "pkg_1",
97
+ "amount": final_deposit,
98
+ "name": "Cié Cié Taipei 預約/點餐",
99
+ "products": [
100
+ {
101
+ "name": "餐飲訂金與預付金",
102
+ "quantity": 1,
103
+ "price": final_deposit
104
+ }
105
+ ]
106
+ }
107
+ ],
108
+ "redirectUrls": {
109
+ "confirmUrl": RETURN_URL, # 客人付款完,LINE Pay 會把他導回您的首頁
110
+ "cancelUrl": RETURN_URL
111
+ }
112
+ }
113
+
114
+ # 2. 核心大魔王:生成 HMAC-SHA256 簽章
115
+ uri = "/v3/payments/request"
116
+ nonce = str(uuid.uuid4())
117
+ body_str = json.dumps(request_body)
118
+ message = LINE_PAY_CHANNEL_SECRET + uri + body_str + nonce
119
+
120
+ # 執行加密
121
+ signature = base64.b64encode(
122
+ hmac.new(
123
+ LINE_PAY_CHANNEL_SECRET.encode(),
124
+ message.encode(),
125
+ hashlib.sha256
126
+ ).digest()
127
+ ).decode()
128
+
129
+ # 3. 發送請求給 LINE Pay 總部
130
+ headers = {
131
+ "Content-Type": "application/json",
132
+ "X-LINE-ChannelId": LINE_PAY_CHANNEL_ID,
133
+ "X-LINE-Authorization-Nonce": nonce,
134
+ "X-LINE-Authorization": signature
135
  }
136
+
137
+ try:
138
+ line_pay_res = requests.post(
139
+ f"{LINE_PAY_BASE_URL}{uri}",
140
+ headers=headers,
141
+ data=body_str
142
+ )
143
+ res_data = line_pay_res.json()
144
+
145
+ # 如果 LINE Pay 成功受理,它會回傳一組專屬網址給我們
146
+ if res_data.get("returnCode") == "0000":
147
+ payment_url = res_data["info"]["paymentUrl"]["web"]
148
+
149
+ # 這裡我們先把訂單狀態記為 "待付款",寫入資料庫
150
+ booking_data = {
151
+ "name": payload.name, "tel": payload.tel, "date": payload.date,
152
+ "time": payload.time, "pax": payload.pax, "user_id": payload.line_id,
153
+ "status": "待付款", # 特別標記為待付款
154
+ "remarks": f"類型: {'外帶' if payload.service_type == 'takeout' else '內用'}\n餐點: {payload.cart}\n訂單號: {order_id}"
155
+ }
156
+ supabase.table("bookings").insert(booking_data).execute()
157
+
158
+ return {
159
+ "status": "require_payment",
160
+ "message": "訂單需支付訂金",
161
+ "is_noshow_penalty": is_noshow,
162
+ "deposit_amount": final_deposit,
163
+ "payment_url": payment_url, # 這是真實會跳轉去刷卡的網址!
164
+ "order_id": order_id
165
+ }
166
+ else:
167
+ raise HTTPException(status_code=500, detail=f"LINE Pay 錯誤: {res_data.get('returnMessage')}")
168
+
169
+ except Exception as e:
170
+ raise HTTPException(status_code=500, detail=f"金流連線失敗: {str(e)}")
171
 
172
  # 狀況 B:不需付款 (直接寫入資料庫並完成訂位)
173
  booking_data = {
 
176
  "date": payload.date,
177
  "time": payload.time,
178
  "pax": payload.pax,
179
+ "email": "",
180
  "user_id": payload.line_id,
181
  "status": "待處理",
182
+ "remarks": f"類型: {'外帶' if payload.service_type == 'takeout' else '內用'}\n餐點: {payload.cart}"
183
  }
184
 
185
  try:
186
  supabase.table("bookings").insert(booking_data).execute()
 
187
  # 🔔 通知老闆有新訂位
188
  notify_boss(payload.name, payload.tel, payload.date, payload.time, payload.pax)
189
+ return { "status": "success", "message": "訂位已成功建立!" }
 
 
 
 
190
  except Exception as e:
191
  raise HTTPException(status_code=500, detail=f"寫入資料庫失敗: {str(e)}")
192
 
193
  def notify_boss(name, tel, date, time, pax):
194
+ """發送 LINE 通知給老闆"""
195
  if not LINE_ACCESS_TOKEN or not BOSS_LINE_ID:
196
  return
197
  msg = f"🔔 【新訂位通知】\n姓名:{name}\n電話:{tel}\n時間:{date} {time}\n人數:{pax}位"