Files
gitlab-instance-0a899031_pa…/app/main.py
2026-03-13 05:15:00 -05:00

169 lines
5.4 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
支付 API 主入口
支持多支付渠道XorPay、ZPAY通过 PAYMENT_PROVIDER 环境变量切换
路由模块化,按项目拆分:
- nomadvip: /nomadvip/* - 固定 ZPAY
- digital: /digital/* - 使用 PAYMENT_PROVIDER
- cnomadcna: /cnomadcna/* - 使用 PAYMENT_PROVIDER
- nomadlms: /nomadlms/* - 使用 PAYMENT_PROVIDER
- legacy: /payh5、/zpay_notify 等(向后兼容)
- common: /submit_meetup_application、/create_user
本地调试BASE_URL 需为 ZPAY 可访问地址(如 ngrok
线上部署BASE_URL=https://api.hackrobot.cn必须为 ZPAY 可公网访问的地址)
"""
import logging
import sys
import requests
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from .config import BASE_URL
from .routers import (
nomadvip_router,
digital_router,
cnomadcna_router,
nomadlms_router,
legacy_router,
common_router,
meetup_router,
)
app = FastAPI(
title="PayJS API",
description="nomadvip / digital / cnomadcna / nomadlms 支付接口",
version="2.0",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
class _SafeStderrProxy:
def write(self, message: str) -> int:
try:
return sys.stderr.write(message)
except (OSError, ValueError):
return 0
def flush(self) -> None:
try:
sys.stderr.flush()
except (OSError, ValueError):
pass
def _safe_log(level: int, message: str, *args, **kwargs) -> None:
try:
logging.log(level, message, *args, **kwargs)
except Exception:
pass
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""捕获未处理异常,记录日志后返回 500HTTPException 交由 FastAPI 默认处理)"""
if isinstance(exc, HTTPException):
raise exc
_safe_log(logging.ERROR, "未处理异常: %s - %s", request.url.path, exc, exc_info=True)
return JSONResponse(status_code=500, content={"detail": str(exc)})
logging.raiseExceptions = False
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[logging.StreamHandler(_SafeStderrProxy())],
force=True,
)
def _safe_stderr_write(message: str) -> None:
try:
sys.stderr.write(message)
sys.stderr.flush()
except (OSError, ValueError):
pass
def _should_trace_request(path: str) -> bool:
return any(
segment in path for segment in ("/nomadvip", "/digital", "/cnomadcna", "/nomadlms")
)
# 启动时校验 BASE_URL线上部署必须为公网可访问地址否则 ZPAY 无法回调)
def _check_base_url():
url = (BASE_URL or "").strip().lower()
if not url:
logging.warning("BASE_URL 未设置,将使用默认值。线上部署请务必设置 BASE_URL 为公网地址")
return
if "localhost" in url or "127.0.0.1" in url or url.startswith("http://192.168.") or url.startswith("http://10."):
logging.warning(
"BASE_URL=%s 疑似内网地址ZPAY 无法回调!线上部署请设置 BASE_URL 为公网地址,如 https://api.hackrobot.cn",
BASE_URL,
)
@app.on_event("startup")
async def startup_event():
_check_base_url()
@app.get("/health")
async def health(check_zpay: str = ""):
"""健康检查。check_zpay=1 时额外检测 zpayz.cn 连通性"""
out = {"status": "ok", "service": "payjsapi", "base_url": BASE_URL}
if str(check_zpay).lower() in ("1", "true", "yes"):
try:
r = requests.get("https://zpayz.cn", timeout=5)
out["zpayz_reachable"] = r.status_code in (200, 301, 302, 404)
except Exception as e:
out["zpayz_reachable"] = False
out["zpayz_error"] = str(e)
return out
# 请求日志中间件:每个请求都打印到 stderr确保终端可见
@app.middleware("http")
async def log_requests(request, call_next):
path = request.url.path
if _should_trace_request(path):
_safe_stderr_write(f"[payjsapi] >>> {request.method} {path}\n")
resp = await call_next(request)
if _should_trace_request(path):
_safe_stderr_write(f"[payjsapi] <<< {request.method} {path} {resp.status_code}\n")
return resp
# 挂载路由
app.include_router(nomadvip_router)
app.include_router(digital_router)
app.include_router(cnomadcna_router)
app.include_router(nomadlms_router)
app.include_router(legacy_router)
app.include_router(common_router)
app.include_router(meetup_router)
@app.get("/")
async def root():
"""健康检查"""
return {
"status": "ok",
"service": "payjsapi",
"routes": {
"nomadvip": "/nomadvip/payh5, /nomadvip/payh5/redirect, /nomadvip/check_vip, /nomadvip/check_vip_by_email, /nomadvip/order_status, /nomadvip/zpay_order_status, /nomadvip/zpay_notify, /nomadvip/xorpay_notify",
"digital": "/digital/payh5, /digital/payh5/redirect, /digital/zpay_order_status, /digital/zpay_notify, /digital/xorpay_notify",
"cnomadcna": "/cnomadcna/payh5, ...",
"nomadlms": "/nomadlms/payh5, ...",
"legacy": "/payh5, /payh5/redirect, /zpay_notify, /xorpay_notify, /zpay_order_status",
"common": "/submit_meetup_application, /create_user",
},
}