Update app/gm_crate.py
Browse files- app/gm_crate.py +109 -59
app/gm_crate.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
# app/gm_crate.py
|
| 2 |
import os
|
| 3 |
import base64
|
|
|
|
| 4 |
from email.utils import formataddr
|
| 5 |
from email.message import EmailMessage
|
| 6 |
from google.oauth2.credentials import Credentials
|
|
@@ -10,6 +11,7 @@ from datetime import datetime
|
|
| 10 |
|
| 11 |
class GmailLogic:
|
| 12 |
def __init__(self):
|
|
|
|
| 13 |
self.client_id = os.getenv("GMAIL_CLIENT_ID")
|
| 14 |
self.client_secret = os.getenv("GMAIL_CLIENT_SECRET")
|
| 15 |
self.refresh_token = os.getenv("GMAIL_REFRESH_TOKEN")
|
|
@@ -17,6 +19,7 @@ class GmailLogic:
|
|
| 17 |
self.display_name = "Celeste Store"
|
| 18 |
|
| 19 |
def get_service(self):
|
|
|
|
| 20 |
creds = Credentials(
|
| 21 |
None,
|
| 22 |
refresh_token=self.refresh_token,
|
|
@@ -24,9 +27,15 @@ class GmailLogic:
|
|
| 24 |
client_id=self.client_id,
|
| 25 |
client_secret=self.client_secret,
|
| 26 |
)
|
| 27 |
-
creds.
|
|
|
|
| 28 |
return build('gmail', 'v1', credentials=creds)
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
async def send_order_email(
|
| 31 |
self,
|
| 32 |
to_email: str,
|
|
@@ -38,6 +47,7 @@ class GmailLogic:
|
|
| 38 |
total_price: str,
|
| 39 |
from_name: str = "Celeste Store"
|
| 40 |
):
|
|
|
|
| 41 |
try:
|
| 42 |
service = self.get_service()
|
| 43 |
now = datetime.now().strftime("%d/%m/%Y")
|
|
@@ -61,91 +71,131 @@ class GmailLogic:
|
|
| 61 |
<html lang="vi">
|
| 62 |
<head>
|
| 63 |
<meta charset="UTF-8">
|
| 64 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 65 |
<style>
|
| 66 |
-
body {{ margin:0; padding:0; font-family: -apple-system,
|
| 67 |
.container {{ max-width:580px; margin:20px auto; background:#ffffff; border-radius:12px; overflow:hidden; box-shadow:0 4px 20px rgba(0,0,0,0.05); }}
|
| 68 |
.header {{ background:#ffffff; padding:40px 30px 20px; text-align:center; border-bottom:1px solid #eee; }}
|
| 69 |
-
.header h1 {{ margin:0; font-size:26px; font-weight:500; color:#111; }}
|
| 70 |
-
.header p {{ margin:8px 0 0; color:#666; font-size:15px; }}
|
| 71 |
.content {{ padding:30px; }}
|
| 72 |
-
.greeting {{ font-size:17px; margin-bottom:25px; }}
|
| 73 |
.order-box {{ background:#f9fafb; padding:25px; border-radius:10px; margin:25px 0; }}
|
| 74 |
-
|
| 75 |
-
table {{ width:100%; border-collapse:collapse; font-size:15px; }}
|
| 76 |
-
td {{ padding:10px 0; border-bottom:1px solid #eee; }}
|
| 77 |
-
.label {{ width:40%; color:#555; font-weight:500; }}
|
| 78 |
-
.value {{ text-align:right; font-weight:500; }}
|
| 79 |
-
.total-row td {{ padding:15px 0 5px; font-size:18px; border:none; }}
|
| 80 |
-
.total-row .label {{ font-weight:600; color:#111; }}
|
| 81 |
.total-row .value {{ font-weight:700; color:#e91e63; font-size:20px; }}
|
| 82 |
-
.
|
| 83 |
-
.support {{ margin:30px 0 0; font-size:15px; color:#555; text-align:center; }}
|
| 84 |
-
.footer {{ background:#f8f9fa; padding:25px; text-align:center; font-size:13px; color:#888; border-top:1px solid #eee; }}
|
| 85 |
-
a {{ color:#e91e63; text-decoration:none; }}
|
| 86 |
</style>
|
| 87 |
</head>
|
| 88 |
<body>
|
| 89 |
<div class="container">
|
| 90 |
-
<div class="header">
|
| 91 |
-
<h1>ĐƠN HÀNG ĐÃ ĐẶT THÀNH CÔNG</h1>
|
| 92 |
-
<p>Cảm ơn bạn đã ủng hộ shop nha ♡</p>
|
| 93 |
-
</div>
|
| 94 |
-
|
| 95 |
<div class="content">
|
| 96 |
-
<p
|
| 97 |
-
<p>Chúng mình đã nhận được đơn hàng của bạn rồi! Đang kiểm tra và chuẩn bị gói hàng thật xinh xắn cho bạn đây~</p>
|
| 98 |
-
|
| 99 |
<div class="order-box">
|
| 100 |
-
<h2>Thông tin đơn hàng</h2>
|
| 101 |
<table>
|
| 102 |
-
<tr>
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
</tr>
|
| 106 |
-
<tr>
|
| 107 |
-
<td class="label">Ngày đặt</td>
|
| 108 |
-
<td class="value">{now}</td>
|
| 109 |
-
</tr>
|
| 110 |
-
<tr>
|
| 111 |
-
<td class="label">Trạng thái</td>
|
| 112 |
-
<td class="value"><strong style="color:#10b981;">{status}</strong></td>
|
| 113 |
-
</tr>
|
| 114 |
</table>
|
| 115 |
-
|
| 116 |
-
<h3 class="products">Sản phẩm</h3>
|
| 117 |
<table>
|
| 118 |
{product_rows_html}
|
| 119 |
-
<tr class="total-row">
|
| 120 |
-
<td class="label">Tổng tiền</td>
|
| 121 |
-
<td class="value">{total_price}</td>
|
| 122 |
-
</tr>
|
| 123 |
</table>
|
| 124 |
</div>
|
| 125 |
-
|
| 126 |
-
<p class="support">Nếu cần hỗ trợ gì thì cứ nhắn shop ngay nhé:<br>
|
| 127 |
-
Hotline: 0909 123 456 | Email: support@yourshop.vn</p>
|
| 128 |
-
|
| 129 |
-
<p style="text-align:center; margin-top:35px; color:#777; font-style:italic;">
|
| 130 |
-
Hẹn gặp bạn sớm với đơn hàng xinh xắn này nha~ 💕
|
| 131 |
-
</p>
|
| 132 |
-
</div>
|
| 133 |
-
|
| 134 |
-
<div class="footer">
|
| 135 |
-
<p>Celeste Store • Trà Vinh • Việt Nam</p>
|
| 136 |
-
<p>© 2026 — Gửi đến bạn những điều nhỏ bé nhưng ấm áp</p>
|
| 137 |
</div>
|
|
|
|
| 138 |
</div>
|
| 139 |
</body>
|
| 140 |
</html>
|
| 141 |
"""
|
| 142 |
-
|
| 143 |
message.set_content(f"Xác nhận đơn hàng #{order_id}")
|
| 144 |
message.add_alternative(html_content, subtype='html')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
| 147 |
service.users().messages().send(userId="me", body={'raw': raw_message}).execute()
|
| 148 |
|
| 149 |
-
return {"ok": True
|
| 150 |
except Exception as e:
|
| 151 |
-
return {"ok": False, "error": str(e)}
|
|
|
|
| 1 |
# app/gm_crate.py
|
| 2 |
import os
|
| 3 |
import base64
|
| 4 |
+
import uuid
|
| 5 |
from email.utils import formataddr
|
| 6 |
from email.message import EmailMessage
|
| 7 |
from google.oauth2.credentials import Credentials
|
|
|
|
| 11 |
|
| 12 |
class GmailLogic:
|
| 13 |
def __init__(self):
|
| 14 |
+
# Các thông tin này lấy từ biến môi trường (Environment Variables)
|
| 15 |
self.client_id = os.getenv("GMAIL_CLIENT_ID")
|
| 16 |
self.client_secret = os.getenv("GMAIL_CLIENT_SECRET")
|
| 17 |
self.refresh_token = os.getenv("GMAIL_REFRESH_TOKEN")
|
|
|
|
| 19 |
self.display_name = "Celeste Store"
|
| 20 |
|
| 21 |
def get_service(self):
|
| 22 |
+
"""Khởi tạo kết nối với Gmail API"""
|
| 23 |
creds = Credentials(
|
| 24 |
None,
|
| 25 |
refresh_token=self.refresh_token,
|
|
|
|
| 27 |
client_id=self.client_id,
|
| 28 |
client_secret=self.client_secret,
|
| 29 |
)
|
| 30 |
+
if not creds.valid:
|
| 31 |
+
creds.refresh(Request())
|
| 32 |
return build('gmail', 'v1', credentials=creds)
|
| 33 |
|
| 34 |
+
def generate_random_order_id(self):
|
| 35 |
+
"""Tạo mã đơn hàng ngẫu nhiên định dạng CS-2026-XXXX (4 ký tự cuối random)"""
|
| 36 |
+
random_suffix = uuid.uuid4().hex[:4].upper()
|
| 37 |
+
return f"CS-2026-{random_suffix}"
|
| 38 |
+
|
| 39 |
async def send_order_email(
|
| 40 |
self,
|
| 41 |
to_email: str,
|
|
|
|
| 47 |
total_price: str,
|
| 48 |
from_name: str = "Celeste Store"
|
| 49 |
):
|
| 50 |
+
"""Gửi email xác nhận khi khách vừa đặt hàng"""
|
| 51 |
try:
|
| 52 |
service = self.get_service()
|
| 53 |
now = datetime.now().strftime("%d/%m/%Y")
|
|
|
|
| 71 |
<html lang="vi">
|
| 72 |
<head>
|
| 73 |
<meta charset="UTF-8">
|
|
|
|
| 74 |
<style>
|
| 75 |
+
body {{ margin:0; padding:0; font-family: -apple-system, sans-serif; background:#f8f9fa; color:#333; }}
|
| 76 |
.container {{ max-width:580px; margin:20px auto; background:#ffffff; border-radius:12px; overflow:hidden; box-shadow:0 4px 20px rgba(0,0,0,0.05); }}
|
| 77 |
.header {{ background:#ffffff; padding:40px 30px 20px; text-align:center; border-bottom:1px solid #eee; }}
|
|
|
|
|
|
|
| 78 |
.content {{ padding:30px; }}
|
|
|
|
| 79 |
.order-box {{ background:#f9fafb; padding:25px; border-radius:10px; margin:25px 0; }}
|
| 80 |
+
table {{ width:100%; border-collapse:collapse; }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
.total-row .value {{ font-weight:700; color:#e91e63; font-size:20px; }}
|
| 82 |
+
.footer {{ background:#f8f9fa; padding:25px; text-align:center; font-size:13px; color:#888; }}
|
|
|
|
|
|
|
|
|
|
| 83 |
</style>
|
| 84 |
</head>
|
| 85 |
<body>
|
| 86 |
<div class="container">
|
| 87 |
+
<div class="header"><h1>ĐƠN HÀNG ĐÃ ĐẶT THÀNH CÔNG</h1><p>Cảm ơn bạn đã ủng hộ shop nha ♡</p></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
<div class="content">
|
| 89 |
+
<p>Chào <strong>{customer_name}</strong>, chúng mình đã nhận được đơn hàng của bạn!</p>
|
|
|
|
|
|
|
| 90 |
<div class="order-box">
|
|
|
|
| 91 |
<table>
|
| 92 |
+
<tr><td>Mã đơn hàng</td><td style="text-align:right"><strong>#{order_id}</strong></td></tr>
|
| 93 |
+
<tr><td>Ngày đặt</td><td style="text-align:right">{now}</td></tr>
|
| 94 |
+
<tr><td>Trạng thái</td><td style="text-align:right"><strong style="color:#10b981;">{status}</strong></td></tr>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
</table>
|
| 96 |
+
<h3 style="margin-top:20px;">Sản phẩm</h3>
|
|
|
|
| 97 |
<table>
|
| 98 |
{product_rows_html}
|
| 99 |
+
<tr class="total-row"><td><strong>Tổng tiền</strong></td><td style="text-align:right" class="value">{total_price}</td></tr>
|
|
|
|
|
|
|
|
|
|
| 100 |
</table>
|
| 101 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
</div>
|
| 103 |
+
<div class="footer"><p>Celeste Store • Trà Vinh • Việt Nam</p></div>
|
| 104 |
</div>
|
| 105 |
</body>
|
| 106 |
</html>
|
| 107 |
"""
|
|
|
|
| 108 |
message.set_content(f"Xác nhận đơn hàng #{order_id}")
|
| 109 |
message.add_alternative(html_content, subtype='html')
|
| 110 |
+
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
| 111 |
+
service.users().messages().send(userId="me", body={'raw': raw_message}).execute()
|
| 112 |
+
return {"ok": True, "order_id": order_id}
|
| 113 |
+
except Exception as e:
|
| 114 |
+
return {"ok": False, "error": str(e)}
|
| 115 |
|
| 116 |
+
async def send_complete_email(
|
| 117 |
+
self,
|
| 118 |
+
to_email: str,
|
| 119 |
+
customer_name: str,
|
| 120 |
+
order_id: str,
|
| 121 |
+
product_name: str,
|
| 122 |
+
price: str,
|
| 123 |
+
tk: str,
|
| 124 |
+
mk: str
|
| 125 |
+
):
|
| 126 |
+
"""Gửi email khi đơn hàng đã Hoàn Thành kèm thông tin tài khoản"""
|
| 127 |
+
try:
|
| 128 |
+
service = self.get_service()
|
| 129 |
+
date_now = datetime.now().strftime("%d/%m/%Y")
|
| 130 |
+
|
| 131 |
+
message = EmailMessage()
|
| 132 |
+
message['To'] = to_email
|
| 133 |
+
message['From'] = formataddr((self.display_name, self.user_email))
|
| 134 |
+
message['Subject'] = f"Đơn hàng #{order_id} đã hoàn thành"
|
| 135 |
+
|
| 136 |
+
html_content = f"""
|
| 137 |
+
<!DOCTYPE html>
|
| 138 |
+
<html lang="vi">
|
| 139 |
+
<head>
|
| 140 |
+
<meta charset="UTF-8">
|
| 141 |
+
<style>
|
| 142 |
+
body{{margin:0;padding:0;background:#f3f4f6;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial;color:#333}}
|
| 143 |
+
.container{{max-width:560px;margin:20px auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 6px 20px rgba(0,0,0,.05)}}
|
| 144 |
+
.header{{text-align:center;padding:35px 20px;border-bottom:1px solid #eee}}
|
| 145 |
+
.header h1{{margin:0;font-size:24px;color:#111}}
|
| 146 |
+
.content{{padding:30px}}
|
| 147 |
+
.order-box{{background:#f9fafb;border-radius:10px;padding:20px;border:1px solid #eee;margin-bottom:25px}}
|
| 148 |
+
table{{width:100%;border-collapse:collapse;font-size:14px}}
|
| 149 |
+
td{{padding:10px 0;border-bottom:1px solid #eee}}
|
| 150 |
+
.label{{color:#666}}
|
| 151 |
+
.value{{text-align:right;font-weight:500}}
|
| 152 |
+
.status{{color:#10b981;font-weight:600}}
|
| 153 |
+
.total{{font-size:18px;font-weight:700;color:#e91e63;border:none}}
|
| 154 |
+
.account-box{{background:#f5f6fa;border:1px solid #e5e7eb;border-radius:10px;padding:18px;margin-top:10px}}
|
| 155 |
+
.account-title{{font-size:14px;color:#666;font-weight:600;margin-bottom:10px}}
|
| 156 |
+
.footer{{text-align:center;background:#f3f4f6;padding:22px;font-size:13px;color:#888;border-top:1px solid #eee}}
|
| 157 |
+
</style>
|
| 158 |
+
</head>
|
| 159 |
+
<body>
|
| 160 |
+
<div class="container">
|
| 161 |
+
<div class="header">
|
| 162 |
+
<h1>ĐƠN HÀNG HOÀN THÀNH</h1>
|
| 163 |
+
<p>Cảm ơn bạn đã mua hàng tại shop ♡</p>
|
| 164 |
+
</div>
|
| 165 |
+
<div class="content">
|
| 166 |
+
<p>Chào <strong>{customer_name}</strong>,</p>
|
| 167 |
+
<p>Đơn hàng của bạn đã được xử lý thành công. Thông tin chi tiết bên dưới:</p>
|
| 168 |
+
<div class="order-box">
|
| 169 |
+
<table>
|
| 170 |
+
<tr><td class="label">Mã đơn hàng</td><td class="value"><strong>#{order_id}</strong></td></tr>
|
| 171 |
+
<tr><td class="label">Ngày</td><td class="value">{date_now}</td></tr>
|
| 172 |
+
<tr><td class="label">Sản phẩm</td><td class="value">{product_name}</td></tr>
|
| 173 |
+
<tr><td class="label">Trạng thái</td><td class="value status">Hoàn Thành</td></tr>
|
| 174 |
+
<tr><td class="label">Tổng tiền</td><td class="value total">{price}</td></tr>
|
| 175 |
+
</table>
|
| 176 |
+
</div>
|
| 177 |
+
<div class="account-box">
|
| 178 |
+
<div class="account-title">Thông tin tài khoản</div>
|
| 179 |
+
<table style="width:100%;">
|
| 180 |
+
<tr><td style="width:90px;color:#777;">Tài khoản</td><td style="font-weight:600;">{tk}</td></tr>
|
| 181 |
+
<tr><td style="color:#777;">Mật khẩu</td><td style="font-weight:600;">{mk}</td></tr>
|
| 182 |
+
</table>
|
| 183 |
+
<div style="margin-top:14px;padding:10px;background:#fff;border:1px dashed #ddd;border-radius:6px;font-family:monospace;font-size:13px;word-break:break-all">
|
| 184 |
+
{tk}:{mk}
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
<p style="font-size:13px;color:#777;margin-top:15px;">Lưu ý:<br>1. KHÔNG đăng nhập vào iCloud<br>2. KHÔNG thêm SĐT<br>3. KHÔNG đổi mật khẩu</p>
|
| 188 |
+
</div>
|
| 189 |
+
<div class="footer">Celeste Store • Trà Vinh • Việt Nam<br>© 2026 Celeste Store</div>
|
| 190 |
+
</div>
|
| 191 |
+
</body>
|
| 192 |
+
</html>
|
| 193 |
+
"""
|
| 194 |
+
message.set_content(f"Tài khoản đơn hàng #{order_id}")
|
| 195 |
+
message.add_alternative(html_content, subtype='html')
|
| 196 |
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
| 197 |
service.users().messages().send(userId="me", body={'raw': raw_message}).execute()
|
| 198 |
|
| 199 |
+
return {"ok": True}
|
| 200 |
except Exception as e:
|
| 201 |
+
return {"ok": False, "error": str(e)}
|