diff --git a/json/transfercode.json b/json/transfercode.json new file mode 100644 index 0000000..b08e411 --- /dev/null +++ b/json/transfercode.json @@ -0,0 +1,15 @@ +{ + "tblname": "transfercode", + "title": "转账码", + "params": { + "sortby": "id", + "browserfields": { + "exclouded": ["id"], + "alters": { + } + }, + "editexclouded": [ + "id" + ] + } +} diff --git a/models/transfercode.xlsx b/models/transfercode.xlsx new file mode 100644 index 0000000..15201c5 Binary files /dev/null and b/models/transfercode.xlsx differ diff --git a/unipay/init.py b/unipay/init.py index 50c997b..6c2bf46 100644 --- a/unipay/init.py +++ b/unipay/init.py @@ -8,6 +8,12 @@ from .payfee import get_pay_fee, sor_get_pay_fee, get_paychannels, get_pay_feera # 从 env 或配置载入 provider conf(这里只示例) CONF = { + "transfer":{ + "pop3server": os.getenv("POP3SERVER", ""), + "mail": os.getenv("MAIL", ""), + "password": os.getenv("PASSWORD", "") + "from_mail": os.getenv("FROM_MAIL", "") + } "wechat": { "mchid": os.getenv("WXP_MCHID",""), "appid": os.getenv("WXP_APPID", ""), @@ -132,6 +138,7 @@ async def payment_notify(request, callback, params_kw=None): # callback url= "/unipay/notify/{provider}" def load_unipay(): + PROVIDERS["transfer"] = get_provider("transfer", CONF["transfer"]), PROVIDERS["wechat"] = get_provider("wechat", CONF["wechat"]), PROVIDERS["paypal"] = get_provider("paypal", CONF["paypal"]), PROVIDERS["alipay"] = get_provider("alipay", CONF["alipay"]), diff --git a/unipay/notify.py b/unipay/notify.py index 96ba22c..9202df2 100644 --- a/unipay/notify.py +++ b/unipay/notify.py @@ -1,5 +1,7 @@ # unipay/notify.py from typing import Dict +from appPublic.log import exception +from .providers.transfer import TransferGateway from .providers.wechat import WechatGateway from .providers.paypal import PaypalGateway from .providers.alipay import AlipayGateway @@ -12,12 +14,15 @@ def get_provider_channel(name:str): "wechat":"0", "paypal":"1", "alipay":"2", - "stripe":"3" + "stripe":"3", + "transfer":"4" } return channels.get(name, '9') def get_provider(name: str, conf: Dict): try: + if name == "transfer": + return TransferGateway(**conf) if name == "wechat": return WechatGateway(**conf) if name == "paypal": @@ -26,7 +31,8 @@ def get_provider(name: str, conf: Dict): return AlipayGateway(**conf) if name == "stripe": return StripeGateway(**conf) - except: + except Exception as e: + exception(f'get_proveder() error {name=}, {conf=}, {e}') return None raise ValueError("unknown provider") diff --git a/unipay/providers/transfer.py b/unipay/providers/transfer.py new file mode 100644 index 0000000..729523e --- /dev/null +++ b/unipay/providers/transfer.py @@ -0,0 +1,114 @@ +import re +from random import randint +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 TransferPay(Gateway): + def __init__(self, from_mail="", pop3server="", email="", password=""): + self.from_email = from_email + self.pop3server = pop3server + self.email = email + self.password = password + self.running = False + + async def create_payment(self, payload: Dict[str, Any]) -> str: + """ + 返回一个可以在 H5 里直接重定向的支付宝支付 URL + """ + ns = { + "id": payload["out_trade_no"], + "customerid": payload['customerid'], + "amount": payload["amount"], + "tcode": self.gen_mailcode(), + "curdate": curDateString(), + "curtime": timestampstr(), + "status": '0' + } + return + + 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 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) +