# init.py import os import traceback from appPublic.log import debug,exception from ahserver.configuredServer import add_startup from ahserver.serverenv import ServerEnv from .notify import get_provider, get_provider_channel from .paylog import PaymentLog, unipay_accounting from .payfee import get_pay_fee, sor_get_pay_fee, get_paychannels, get_pay_feerate # ────────────────────────────────────────────── # 延迟加载:只存储 env key 名称,不在此处打开文件 # 避免 import 阶段因文件缺失或 env 未设置而崩溃 # ────────────────────────────────────────────── CONF_ENV = { "transfer": { "pop3server": "POP3SERVER", "email": "MAIL", "password": "PASSWORD", "from_mail": "FROM_MAIL", "account_no": "ACCOUNT_NO" }, "wechat": { "mchid": "WXP_MCHID", "appid": "WXP_APPID", "cert_serial_no": "WXP_SERIAL", "private_key_pem_file": "WXP_PRIVKEY", "api_v3_key": "WXP_API_V3_KEY" }, "paypal": { "client_id": "PP_ID", "client_secret": "PP_SECRET", "sandbox": True # 静态值 }, "alipay": { "app_id": "ALIPAY_APPID", "app_private_key_pem_file": "ALIPAY_PRIV", "alipay_public_key_pem_file": "ALIPAY_PUB" }, "stripe": { "api_key": "STRIPE_KEY" } } PROVIDERS = {} def _build_provider_conf(provider_name: str) -> dict: """从环境变量构造 provider 配置。打开文件、处理默认值。 如果关键环境变量缺失或文件打不开,抛异常由调用方处理。""" conf_def = CONF_ENV.get(provider_name, {}) conf = {} for key, env_name in conf_def.items(): if isinstance(env_name, bool): conf[key] = env_name continue val = os.getenv(env_name, "") conf[key] = val # 特殊处理:读取密钥文件 if provider_name == "wechat": privkey_path = conf.get("private_key_pem_file", "") if not privkey_path: raise FileNotFoundError(f"环境变量 WXP_PRIVKEY 未设置") with open(privkey_path, "rb") as f: conf["private_key_pem"] = f.read() del conf["private_key_pem_file"] elif provider_name == "alipay": priv_path = conf.get("app_private_key_pem_file", "") pub_path = conf.get("alipay_public_key_pem_file", "") if not priv_path: raise FileNotFoundError(f"环境变量 ALIPAY_PRIV 未设置") if not pub_path: raise FileNotFoundError(f"环境变量 ALIPAY_PUB 未设置") with open(priv_path, "rb") as f: conf["app_private_key_pem"] = f.read() with open(pub_path, "rb") as f: conf["alipay_public_key_pem"] = f.read() del conf["app_private_key_pem_file"] del conf["alipay_public_key_pem_file"] return conf # ────────────────────────────────────────────── # 业务函数(不变) # ────────────────────────────────────────────── # 下单接口(统一) async def create_payment(request, params_kw=None): env = request._run_ns if params_kw is None: params_kw = request.params_kw data = params_kw data.request = request provider = data.get("provider") if provider not in PROVIDERS: debug(f'{provider=} is not a valid payment channel') return {"error":"unknown provider"} try: if PROVIDERS[provider] is None: e = Exception(f'{provider} cannot pay') exception(f'{e}') raise e notify_url = env.entire_url(f'notify/{provider}') pl = PaymentLog(request._run_ns) feerate = await get_pay_fee(provider, data.amount) fee = feerate * data.amount channel = get_provider_channel(provider) userid = await env.get_user() orgid = await env.get_userorgid() client_ip = request['client_ip'] # userid, customerid, channel, payment_name, amount, client_ip, currency='CNY' payment_name = data.payment_name or "充值" amount = data.amount currency = data.currency plog = await pl.new_log(userid, orgid, provider, amount, feerate, client_ip, currency=currency) if plog: data.out_trade_no = plog.id data.customerid = orgid data.userid = userid data.payment_name = payment_name data.notify_url = notify_url data.client_ip = client_ip res = await PROVIDERS[provider].create_payment(data) debug(f'{provider=} instance return {res}') return res raise Exception('write payment_log error') except Exception as e: exception(f'create_payment():{params_kw=}, {e}') raise e # 查询 async def query_payment(request, params_kw=None): if params_kw is None: params_kw = request.params_kw data = params_kw provider = data.get("provider") if provider not in PROVIDERS: return {"error":"unknown provider"} try: if PROVIDERS[provider] is None: e = Exception(f'{provider} cannot pay') exception(f'{e}') raise e res = await PROVIDERS[provider].query(data) return res except Exception as e: exception(f'query_payment():{params_kw}, {e}') raise e # 退款 async def refund_payment(request, params_kw=None): if params_kw is None: params_kw = request.params_kw data = await get_refundable_plog(request, params_kw.id) if data is None: return None if params_kw.amount >= data.total_amount: return None provider = data.channelid if provider not in PROVIDERS: return {"error":"unknown provider"} try: plog = await new_refund_log(request, data.amount, data.id) if PROVIDERS[provider] is None: e = Exception(f'{provider} cannot pay') exception(f'{e}') raise e plog.out_trade_no = plog.origin_id plog.out_request_no = plog.id plog.total_amount = params_kw.amount plog.refund_amount = plog.total_amount plog.notify_url = request.env.entire_url(f'notify/{provider}') res = await PROVIDERS[provider].refund(plog) return res except Exception as e: exception(f'query_payment():{params_kw}, {e}') raise e # 微信支付回调入口 async def wechat_notify(request): debug("wechat notify called .......") Response = ServerEnv().Response provider = 'wechat' if PROVIDERS[provider] is None: e = Exception(f'{provider} cannot pay') exception(f'{e}') return Response(text='{"code":"SUCCESS","message":"OK"}', content_type='application/json') data = None try: data = await PROVIDERS[provider].handle_notify(request) except Exception as e: exception(f'{e}') return Response(text='{"code":"SUCCESS","message":"OK"}', content_type='application/json') if data is None: return Response(text='{"code":"SUCCESS","message":"OK"}', content_type='application/json') debug(f'{data=}') try: await unipay_accounting(request, data.data) except Exception as e: exception(f'{e}') return Response(text='{"code":"SUCCESS","message":"OK"}', content_type='application/json') # 支付宝回调入口 async def alipay_notify(request): debug("alipay notify called .......") Response = ServerEnv().Response provider = 'alipay' if PROVIDERS[provider] is None: e = Exception(f'{provider} cannot pay') exception(f'{e}') return Response(text='success', status=200) data = None try: data = await PROVIDERS[provider].handle_notify(request) except Exception as e: e = Exception(f'{provider} cannot pay') exception(f'{e}') return Response(text='success', status=200) debug(f'{data=}') try: await unipay_accounting(request, data.data) except Exception as e: exception(f'{e}') return Response(text='success', status=200) async def setup_callback_path(app): app.router.add_post('/unipay/notify/wechat', wechat_notify) app.router.add_post('/unipay/notify/alipay', alipay_notify) # callback url= "/unipay/notify/{provider}" def load_unipay(): """注册各支付渠道到 PROVIDERS。初始化失败的渠道设为 None(disabled)。""" for name in ("transfer", "wechat", "paypal", "alipay", "stripe"): try: conf = _build_provider_conf(name) PROVIDERS[name] = get_provider(name, conf) if PROVIDERS[name] is not None: print(f"[unipay] {name} 初始化成功") else: print(f"[unipay] {name} 初始化返回 None(渠道 disabled)") except Exception as e: PROVIDERS[name] = None print(f"[unipay] {name} 初始化失败,已禁用: {e}", flush=True) env = ServerEnv() env.get_paychannels = get_paychannels env.get_pay_feerate = get_pay_feerate env.create_payment = create_payment env.query_payment = query_payment env.refund_payment = refund_payment env.get_pay_fee = get_pay_fee env.sor_get_pay_fee = sor_get_pay_fee env.PaymentLog = PaymentLog add_startup(setup_callback_path)