| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- """微信消息通知服务
- 支持微信小程序模板消息推送
- 使用方式:
- from app.core.wechat_message import WechatMessageService
-
- # 发送订单状态通知
- WechatMessageService.send_order_status(
- openid="user_openid",
- order_no="ORD202603190001",
- status="已支付",
- amount=100.00
- )
-
- # 发送配送通知
- WechatMessageService.send_delivery_notice(
- openid="deliver_openid",
- task_no="DLV20260319001",
- address="深圳市南山区...",
- time="尽快取货"
- )
- """
- import json
- import logging
- from typing import Optional, Dict, Any
- from datetime import datetime
- import requests
- from app.core.config import settings
- logger = logging.getLogger(__name__)
- class WechatMessageService:
- """微信消息服务"""
- # 模板消息ID配置
- TEMPLATE_IDS = {
- "order_status": "ORDER_STATUS_TEMPLATE_ID", # 订单状态通知
- "delivery_notice": "DELIVERY_NOTICE_TEMPLATE_ID", # 配送通知
- "payment_success": "PAYMENT_SUCCESS_TEMPLATE_ID", # 支付成功通知
- "refund_result": "REFUND_RESULT_TEMPLATE_ID", # 退款结果通知
- "delivery_warning": "DELIVERY_WARNING_TEMPLATE_ID", # 配送时效预警
- }
- def __init__(self):
- self.appid = settings.WXPAY_MINI_APPID
- self.secret = settings.WXPAY_SECRET
- self.access_token = None
- self.token_expires_at = None
- def _get_access_token(self) -> Optional[str]:
- """获取Access Token"""
- if self.access_token and self.token_expires_at:
- if datetime.now() < self.token_expires_at:
- return self.access_token
- try:
- url = "https://api.weixin.qq.com/cgi-bin/token"
- params = {
- "grant_type": "client_credential",
- "appid": self.appid,
- "secret": self.secret
- }
- response = requests.get(url, params=params, timeout=10)
- result = response.json()
- if "access_token" in result:
- self.access_token = result["access_token"]
- expires_in = result.get("expires_in", 7200)
- self.token_expires_at = datetime.now().timestamp() + expires_in - 300
- return self.access_token
- else:
- logger.error(f"获取Access Token失败: {result}")
- return None
- except Exception as e:
- logger.error(f"请求Access Token异常: {str(e)}")
- return None
- def _send_template_message(self, openid: str, template_id: str, data: Dict, page: str = None, color: str = None) -> Dict:
- """发送模板消息
-
- Args:
- openid: 用户openid
- template_id: 模板ID
- data: 模板数据
- page: 跳转页面
- color: 字体颜色
-
- Returns:
- Dict: 发送结果
- """
- access_token = self._get_access_token()
- if not access_token:
- return {"success": False, "error": "获取access_token失败"}
- try:
- url = f"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={access_token}"
- payload = {
- "touser": openid,
- "template_id": template_id,
- "data": data
- }
- if page:
- payload["miniprogram"] = {
- "appid": self.appid,
- "pagepath": page
- }
- if color:
- payload["color"] = color
- response = requests.post(url, json=payload, timeout=10)
- result = response.json()
- if result.get("errcode") == 0:
- logger.info(f"模板消息发送成功: {openid}, msgid: {result.get('msgid')}")
- return {"success": True, "msgid": result.get("msgid")}
- else:
- logger.error(f"模板消息发送失败: {result}")
- return {"success": False, "error": result.get("errmsg", "发送失败")}
- except Exception as e:
- logger.error(f"发送模板消息异常: {str(e)}")
- return {"success": False, "error": str(e)}
- @classmethod
- def send_order_status(
- cls,
- openid: str,
- order_no: str,
- status: str,
- amount: float,
- remark: str = None
- ) -> Dict:
- """发送订单状态通知
-
- Args:
- openid: 用户openid
- order_no: 订单号
- status: 订单状态
- amount: 订单金额
- remark: 备注
- """
- template_id = cls.TEMPLATE_IDS.get("order_status")
- if not template_id:
- return {"success": False, "error": "未配置订单状态模板ID"}
- data = {
- "order_no": {"value": order_no, "color": "#173177"},
- "status": {"value": status, "color": "#07c160"},
- "amount": {"value": f"¥{amount:.2f}", "color": "#ff6600"},
- "remark": {"value": remark or "感谢您的使用", "color": "#888888"}
- }
- service = cls()
- return service._send_template_message(
- openid=openid,
- template_id=template_id,
- data=data,
- page="pages/client/order/detail/detail"
- )
- @classmethod
- def send_payment_success(
- cls,
- openid: str,
- order_no: str,
- amount: float,
- payment_method: str = "微信支付"
- ) -> Dict:
- """发送支付成功通知
-
- Args:
- openid: 用户openid
- order_no: 订单号
- amount: 支付金额
- payment_method: 支付方式
- """
- template_id = cls.TEMPLATE_IDS.get("payment_success")
- if not template_id:
- return {"success": False, "error": "未配置支付成功模板ID"}
- data = {
- "order_no": {"value": order_no, "color": "#173177"},
- "amount": {"value": f"¥{amount:.2f}", "color": "#ff6600"},
- "payment_method": {"value": payment_method, "color": "#07c160"},
- "time": {"value": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "color": "#888888"}
- }
- service = cls()
- return service._send_template_message(
- openid=openid,
- template_id=template_id,
- data=data,
- page="pages/client/order/detail/detail"
- )
- @classmethod
- def send_delivery_notice(
- cls,
- openid: str,
- task_no: str,
- supplier_name: str,
- address: str,
- remark: str = None
- ) -> Dict:
- """发送配送通知
-
- Args:
- openid: 配送员openid
- task_no: 配送单号
- supplier_name: 供应商名称
- address: 取货地址
- remark: 备注
- """
- template_id = cls.TEMPLATE_IDS.get("delivery_notice")
- if not template_id:
- return {"success": False, "error": "未配置配送通知模板ID"}
- data = {
- "task_no": {"value": task_no, "color": "#173177"},
- "supplier": {"value": supplier_name, "color": "#07c160"},
- "address": {"value": address, "color": "#173177"},
- "remark": {"value": remark or "请尽快前往取货", "color": "#ff6600"}
- }
- service = cls()
- return service._send_template_message(
- openid=openid,
- template_id=template_id,
- data=data,
- page="pages/deliver/task/detail"
- )
- @classmethod
- def send_delivery_warning(
- cls,
- openid: str,
- task_no: str,
- remaining_minutes: int,
- urgency_level: str = "紧急"
- ) -> Dict:
- """发送配送时效预警
-
- Args:
- openid: 配送员openid
- task_no: 配送单号
- remaining_minutes: 剩余分钟数
- urgency_level: 紧急程度
- """
- template_id = cls.TEMPLATE_IDS.get("delivery_warning")
- if not template_id:
- return {"success": False, "error": "未配置配送预警模板ID"}
- color = "#ff0000" if urgency_level == "紧急" else "#ff6600"
- data = {
- "task_no": {"value": task_no, "color": "#173177"},
- "remaining": {"value": f"剩余{remaining_minutes}分钟", "color": color},
- "level": {"value": urgency_level, "color": color},
- "time": {"value": datetime.now().strftime("%H:%M"), "color": "#888888"}
- }
- service = cls()
- return service._send_template_message(
- openid=openid,
- template_id=template_id,
- data=data,
- page="pages/deliver/task/detail"
- )
- @classmethod
- def send_refund_result(
- cls,
- openid: str,
- order_no: str,
- refund_amount: float,
- refund_time: str = None,
- reason: str = None
- ) -> Dict:
- """发送退款结果通知
-
- Args:
- openid: 用户openid
- order_no: 订单号
- refund_amount: 退款金额
- refund_time: 退款时间
- reason: 退款原因
- """
- template_id = cls.TEMPLATE_IDS.get("refund_result")
- if not template_id:
- return {"success": False, "error": "未配置退款结果模板ID"}
- data = {
- "order_no": {"value": order_no, "color": "#173177"},
- "amount": {"value": f"¥{refund_amount:.2f}", "color": "#ff6600"},
- "time": {"value": refund_time or datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "color": "#888888"},
- "reason": {"value": reason or "原路退回", "color": "#07c160"}
- }
- service = cls()
- return service._send_template_message(
- openid=openid,
- template_id=template_id,
- data=data,
- page="pages/client/refund/refund"
- )
- @classmethod
- def send_urge_notification(
- cls,
- deliver_openid: str,
- order_no: str,
- client_name: str,
- address: str
- ) -> Dict:
- """发送催单通知给配送员
-
- Args:
- deliver_openid: 配送员openid
- order_no: 订单号
- client_name: 客户名称
- address: 配送地址
- """
- template_id = cls.TEMPLATE_IDS.get("delivery_notice")
- if not template_id:
- return {"success": False, "error": "未配置配送通知模板ID"}
- data = {
- "task_no": {"value": order_no, "color": "#173177"},
- "supplier": {"value": f"客户:{client_name}", "color": "#07c160"},
- "address": {"value": address, "color": "#173177"},
- "remark": {"value": "⚠️ 客户催单,请尽快处理", "color": "#ff0000"}
- }
- service = cls()
- return service._send_template_message(
- openid=deliver_openid,
- template_id=template_id,
- data=data,
- page="pages/deliver/task/detail"
- )
- # 全局实例
- wechat_message = WechatMessageService()
|