Cyberbiz 訂單每日銷售報表寄送程式
📖 簡介
此程式可自動從 Cyberbiz API 擷取前一日的訂單,進行統計後,透過 Gmail API 寄送每日銷售報表,並可選擇發送至 LINE 群組(目前程式中預設註解)。
🛠 功能特色
-
自動擷取訂單
-
使用 Cyberbiz API (
/v1/orders) 抓取昨日訂單(狀態:open)。 -
詳細訂單資料逐筆讀取並儲存。
-
-
訂單存檔
-
輸出 JSON 檔案,存放於
/root/cb_order/orders/cb_order_YYYYMMDD.json。
-
-
銷售分析
-
計算:
-
訂單數量
-
總銷售金額
-
各付款方式數量
-
商品銷售數量
-
贈品數量
-
-
-
Email 報表寄送
-
使用 Gmail API(OAuth 驗證)發送報表。
-
信件主旨:
-
📊 銷售報表 - YYYYMMDD
-
-
內文包含:
-
銷售日期
-
訂單數與付款方式分布
-
總銷售金額
-
商品銷售明細
-
贈品明細(若有)
-
-
-
LINE 群組推播(選用)
-
內建 LINE Bot API 發送功能(程式碼目前註解)。
-
支援文字簡訊推播至群組。
-
📂 系統檔案與設定
-
程式執行目錄
-
/root/cb_order
-
-
訂單存檔路徑
-
/root/cb_order/orders/cb_order_YYYYMMDD.json
-
-
Gmail 驗證
-
credentials.json:Gmail OAuth 憑證 -
token.json:登入後產生的存取權杖
-
-
設定參數
-
BASE_URL:Cyberbiz API 入口 -
TOKEN:Cyberbiz API Token -
TO_EMAILS:收件人列表 -
SENDER:寄件人 Gmail 帳號
-
📐 報表範例
📅 銷售日期:20250331
📦 訂單數量:18【信用卡:12、LINE Pay:5、貨到付款:1】
💰 總銷售金額:24500 元
🛒 銷售商品明細:
- 綜合禮盒: 8 件
- 招牌魚鬆: 6 件
🎁 贈品明細:
- 試吃包: 4 件
⚙️ 操作流程
-
設定
TOKEN與 Gmail 憑證 (credentials.json)。 -
執行程式:
python3 cb_daily_report.py -
程式會自動:
-
擷取昨日訂單
-
儲存 JSON 檔案
-
分析並產出統計
-
發送 Email 報表
-
(選用)推播至 LINE 群組
-
✅ 成果
-
每日自動寄送前一日的銷售報表
-
含訂單數量、付款方式、商品/贈品明細
-
檔案與統計紀錄完整保存
⚙️ 程式原始碼
#!/usr/bin/env python3
import os
import json
import base64
import requests
from datetime import datetime, timedelta
from collections import defaultdict
from email.message import EmailMessage
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
# === 切換目錄,避免 cron 找不到 json 憑證 ===
os.chdir('/root/cb_order')
# === 設定 ===
BASE_URL = "https://app-store-api.cyberbiz.io"
TOKEN = ""
TO_EMAILS = [
"wayne@lianhung.com.tw"
]
SENDER = "zfuntw@gmail.com"
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
# === 抓昨天的資料 ===
yesterday = datetime.now() - timedelta(days=1)
start_time = yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
end_time = yesterday.replace(hour=23, minute=59, second=59, microsecond=0)
start_time_str = start_time.strftime("%Y-%m-%d %H:%M:%S")
end_time_str = end_time.strftime("%Y-%m-%d %H:%M:%S")
report_date_str = yesterday.strftime("%Y%m%d")
# === 建立 orders/ 目錄與檔名 ===
os.makedirs("orders", exist_ok=True)
json_path = f"orders/cb_order_{report_date_str}.json"
# === 取得訂單清單 ===
params = {
"start_time": start_time_str,
"end_time": end_time_str,
"statuses": "open"
}
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Accept": "application/json"
}
response = requests.get(f"{BASE_URL}/v1/orders", headers=HEADERS, params=params)
if response.status_code != 200:
print("❌ 無法取得訂單清單")
print(response.text)
exit()
order_list = response.json()
# === 取得每筆訂單詳情 ===
all_order_details = []
for order in order_list:
order_id = order.get("id")
if not order_id:
continue
detail_res = requests.get(f"{BASE_URL}/v1/orders/{order_id}", headers=HEADERS)
if detail_res.status_code == 200:
all_order_details.append(detail_res.json())
print(f"✅ 已讀取訂單 {order_id}")
else:
print(f"⚠️ 無法讀取訂單 {order_id}")
# === 儲存 JSON 檔案 ===
with open(json_path, "w", encoding="utf-8") as f:
json.dump(all_order_details, f, ensure_ascii=False, indent=2)
# === 統計分析(付款方式 + 銷售商品 + 贈品)===
total_orders = len(all_order_details)
total_amount = 0
product_summary = defaultdict(int)
gift_summary = defaultdict(int)
payment_methods = defaultdict(int)
for order in all_order_details:
payment = order.get("payment_method", "未知方式")
payment_methods[payment] += 1
for item in order.get("line_items", []):
title = item.get("title", "未知商品")
qty = item.get("quantity", 0)
price = item.get("total_price_after_discounts", item.get("price", 0))
if price == 0:
gift_summary[title] += qty
else:
product_summary[title] += qty
total_amount += price
# === 組合付款方式統計字串 ===
payment_str = "、".join([f"{k}:{v}" for k, v in sorted(payment_methods.items(), key=lambda x: -x[1])])
# === 組合信件內容 ===
report_lines = [
f"📅 銷售日期:{report_date_str}",
f"📦 訂單數量:{total_orders}【{payment_str}】",
f"💰 總銷售金額:{total_amount:.0f} 元",
"",
"🛒 銷售商品明細:"
]
for title, qty in sorted(product_summary.items(), key=lambda x: -x[1]):
report_lines.append(f"- {title}: {qty} 件")
if gift_summary:
report_lines.append("")
report_lines.append("🎁 贈品明細:")
for title, qty in sorted(gift_summary.items(), key=lambda x: -x[1]):
report_lines.append(f"- {title}: {qty} 件")
email_body = "\n".join(report_lines)
# === Gmail API 驗證 ===
creds = None
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token.json', 'w') as token:
token.write(creds.to_json())
service = build('gmail', 'v1', credentials=creds)
# === 寄出 Email ===
message = EmailMessage()
message.set_content(email_body)
message['To'] = ", ".join(TO_EMAILS)
message['From'] = SENDER
message['Subject'] = f"📊 銷售報表 - {report_date_str}"
encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
send_message = service.users().messages().send(userId="me", body={"raw": encoded_message}).execute()
print(f"\n✅ 郵件已寄出!ID: {send_message['id']}")
# === 發送 LINE 群組訊息 ===
#from linebot import LineBotApi
#from linebot.models import TextSendMessage
#from dotenv import load_dotenv
# 載入環境變數(如果程式開頭已有則可略過)
#load_dotenv()
#print("DEBUG - LINE_CHANNEL_ACCESS_TOKEN:", os.getenv("LINE_CHANNEL_ACCESS_TOKEN"))
#print("DEBUG - LINE_GROUP_IDS:", os.getenv("LINE_GROUP_IDS"))
#LINE_TOKEN = os.getenv("LINE_CHANNEL_ACCESS_TOKEN")
#LINE_GROUP_IDS = os.getenv("LINE_GROUP_IDS", "").split(",")
#LINE_GROUP_IDS = [gid.strip() for gid in LINE_GROUP_IDS if gid.strip()]
#if LINE_TOKEN and LINE_GROUP_IDS:
# try:
# line_bot_api = LineBotApi(LINE_TOKEN)
# LINE 最多允許 500 字,過長會送不出去
# short_report = "\n".join(report_lines)
# max_len = 480
# if len(short_report) > max_len:
# short_report = short_report[:max_len] + "\n...(訊息過長略)"
#
# for gid in LINE_GROUP_IDS:
# try:
# line_bot_api.push_message(
# gid,
# TextSendMessage(text=short_report)
# )
# print(f"✅ 已發送 LINE 銷售報表給群組:{gid}")
# except Exception as e:
# print(f"❌ 發送 LINE 給 {gid} 失敗:{e}")
#
# except Exception as e:
# print(f"❌ 發送 LINE 失敗(初始化錯誤):{e}")
#else:
# print("⚠️ 未設定 LINE_CHANNEL_ACCESS_TOKEN 或 LINE_GROUP_IDS")