import re from random import randint from typing import Any, Dict, Optional import urllib import poplib from appPublic.log import debug from appPublic.dictObject import DictObject from appPublic.timeUtils import curDateString, timestampstr from email.parser import Parser from email.header import decode_header from email.utils import parseaddr from ..core import Gateway def guess_charset(msg): charset = msg.get_charset() if charset is None: content_type = msg.get('Content-Type', '').lower() pos = content_type.find('charset=') if pos >= 0: charset = content_type[pos + 8:].strip() return charset class EmailClient: def __init__(self, pop3_server, emailaddress, password): self.client = poplib.POP3(pop3_server) self.client.user(emailaddress) self.client.pass_(password) def stat(self): return self.client.stat() def mail_list(self): resp, mails, octets = self.client.list() def get_mail(self, index): resp, lines, octets = self.client.retr(index) debug(f'{resp=}, {octets=}') msg_content = b'\r\n'.join(lines).decode('utf-8') msg = Parser().parsestr(msg_content) mail = { "mailfrom": msg.get('From'), "mailto": msg.get('To'), "subject": msg.get('Subject'), "body": self.get_body(msg) } return DictObject(**mail) def get_body(self, msg): if msg.is_multipart(): parts = msg.get_payload() content = '' for part in parts: content += self.get_body(part) return content else: content_type = msg.get_content_type() content = msg.get_payload(decode=True) charset = guess_charset(msg) if charset: content = content.decode(charset) else: content = content.decode('utf-8') return content class TransferGateway(Gateway): def __init__(self, from_mail="", pop3server="", email="", password="", account_no=""): self.from_email = from_email self.pop3server = pop3server self.email = email self.password = password self.account_no = account_no self.running = False async def new_tcode(self, sor): tcode = self.gen_mailcode() sql = "select * from transfercode where tcode=${tcode}$ and status='0'" while True: recs = await sor.sqlExe(sql, {'tcode': tcode}) if len(recs) == 0: break tcode = self.gen_mailcode() return tcode async def create_payment(self, payload: Dict[str, Any]) -> str: """ 返回一个可以在 H5 里直接重定向的支付宝支付 URL """ ns = { "id": payload.out_trade_no, "customerid": payload.customerid, "amount": payload.amount, "curdate": curDateString(), "curtime": timestampstr(), "status": '0' } ns = DictObject(**ns) ns.account_no = self.account_no env = payload.request._run_ns db = DBPools() dbname = env.get_module_dbname('unipay') async with db.sqlorContext(dbname) as sor: sql = "select * from transfercode where tcode=${tcode}$ and status='0'" ns.tcoce = await self.new_tcode(sor) await sor.C('transfercode', ns.copy()) url = env.entire_url('transfer_info.ui') query_str = urllib.parse.urlencode(ns) return f'{url}?{query_str}' return None def get_transfer_data(self, mail): assert mail.mailfrom == '95555@message.cmbchina.com' assert mail.mailto == self.email assert mail.body.startswith('动账业务通知') ns = DictObject() match = re.search(r'交易金额\s*[::]?\s*(\d+(?:\.\d+)?)', mail.body) if match: ns.amount = float(match.group(1)) p = r'摘要[::]\s*(\d{7})(?!\d)' match = re.search(r'摘要[::]\s*(\d{7})(?!\d)', mail.body) if match: ns.code = match.group(1) assert ns.amount assert ns.code return ns def gen_mailcode(self): c = '1' for i in range(6): c += randint(0,9) return c def run(self): self.running = True while self.running: ec = EmailClient(self.pop3server, self.email, self.password) mail = ec.get_mail(1) async def query(self, out_trade_no: str) -> Dict[str, Any]: pass async def refund(self, *, out_trade_no: str, refund_amount: str, out_request_no: str) -> Dict[str, Any]: pass async def handle_notify(self, request) -> Dict[str, Any]: pass