From 0326ab2875f36d8d06d7444741d9bb0d95172728 Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 13 Mar 2026 10:59:27 -0500 Subject: [PATCH] 'env' --- .env | 23 ++++++++++++++++++++++ .env.local | 17 ++++++++++++++++ .gitignore | 4 ++-- README.md | 14 +++++++++---- app/main.py | 20 +++++++++++++++++-- app/payment/zpay.py | 48 ++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 .env create mode 100644 .env.local diff --git a/.env b/.env new file mode 100644 index 0000000..e7c1515 --- /dev/null +++ b/.env @@ -0,0 +1,23 @@ +# ========== 生产环境配置 ========== + +BASE_URL=https://api.hackrobot.cn +PAYMENT_PROVIDER=zpay + +# ZPAY(请填入实际值) +ZPAY_PID=2025121809351743 +ZPAY_KEY=tpEi7wWIWI2kXiYVTpIG6j7it0mjVW89 + +# PocketBase(check-user、ensure-user 必需,否则报「服务配置错误」) +PB_URL=https://pocketbase.hackrobot.cn +PB_ADMIN_EMAIL=xiaoshuang.eric@gmail.com +PB_ADMIN_PASSWORD=Xiao4669805@ + +# Memos(nomadvip 支付成功后创建账号) +MEMOS_API=https://qun.hackrobot.cn/api/v1/users +MEMOS_TOKEN=eyJhbGciOiJIUzI1NiIsImtpZCI6InYxIiwidHlwIjoiSldUIn0.eyJuYW1lIjoiaGFja3JvYm90IiwiaXNzIjoibWVtb3MiLCJzdWIiOiIxIiwiYXVkIjpbInVzZXIuYWNjZXNzLXRva2VuIl0sImlhdCI6MTc1NzQwNTE0OX0.Idn7kBlxE-CoSOXwWuZ1tHGIRKHAIeDyXSafGS5OHsg + +# MinIO(可选) +MINIO_ENDPOINT=minioweb.hackrobot.cn +MINIO_PORT=443 +MINIO_BUCKET=hackrobot +MINIO_UPLOAD_PREFIX=joins diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..16b8160 --- /dev/null +++ b/.env.local @@ -0,0 +1,17 @@ +# 本地调试 - 端口 8007、zpay 渠道 +PORT=8007 +PAYMENT_PROVIDER=zpay +BASE_URL=https://api.hackrobot.cn + +# PocketBase(支付回调、nomadvip/digital/cnomadcna 共用) +PB_URL=https://pocketbase.hackrobot.cn +PB_ADMIN_EMAIL=xiaoshuang.eric@gmail.com +PB_ADMIN_PASSWORD=Xiao4669805@ + +# Memos(nomadvip 支付成功后创建账号) +MEMOS_API=https://qun.hackrobot.cn/api/v1/users +MEMOS_TOKEN=eyJhbGciOiJIUzI1NiIsImtpZCI6InYxIiwidHlwIjoiSldUIn0.eyJuYW1lIjoiaGFja3JvYm90IiwiaXNzIjoibWVtb3MiLCJzdWIiOiIxIiwiYXVkIjpbInVzZXIuYWNjZXNzLXRva2VuIl0sImlhdCI6MTc1NzQwNTE0OX0.Idn7kBlxE-CoSOXwWuZ1tHGIRKHAIeDyXSafGS5OHsg + +# ZPAY +ZPAY_PID=2025121809351743 +ZPAY_KEY=tpEi7wWIWI2kXiYVTpIG6j7it0mjVW89 diff --git a/.gitignore b/.gitignore index 9c39016..8dc3230 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,5 @@ __pycache__/ *.py[cod] venv/ .venv/ -.env -.env.local +# .env +# .env.local diff --git a/README.md b/README.md index 4b8228b..0bc21ca 100755 --- a/README.md +++ b/README.md @@ -84,13 +84,19 @@ PAYMENT_PROVIDER=zpay ## 线上「支付失败」排查 -本地正常、Ubuntu 部署后显示支付失败,常见原因: +本地正常、Ubuntu 部署后 api.hackrobot.cn 调用支付失败,常见原因: -1. **BASE_URL 配置错误**:必须为 ZPAY 可公网访问的地址。错误示例:`http://127.0.0.1:8700`、`http://localhost`。正确:`https://api.hackrobot.cn`(域名需解析到本机,nginx 反向代理 8700 端口)。 +1. **BASE_URL 配置错误**:必须为 ZPAY 可公网访问的地址。错误示例:`http://127.0.0.1:8700`、`http://localhost`。正确:`https://api.hackrobot.cn`(域名需解析到本机,nginx 反向代理对应端口)。 -2. **服务器无法访问 zpayz.cn**:防火墙、DNS 或网络限制。访问 `https://你的域名/health?check_zpay=1` 自检,若 `zpayz_reachable: false` 则需开放出站或检查网络。 +2. **服务器无法访问 zpayz.cn**:防火墙、DNS 或网络限制。访问 `https://api.hackrobot.cn/health?check_zpay=1` 自检: + - `zpayz_reachable: false` → 开放出站或检查网络 + - `zpayz_error` 含 "SSL" → 执行 `apt install ca-certificates` 后重启 -3. **启动时控制台有 BASE_URL 警告**:说明配置了内网地址,需修改 `.env` 中的 `BASE_URL` 为公网地址后重启。 +3. **client_ip 内网导致 ZPAY 风控拒单**:前端(digital/cnomadcna/nomadvip)通过 nginx 代理时,需配置 `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for`,否则 payjsapi 收到 127.0.0.1 会触发风控。已做兼容:内网 IP 时自动省略 clientip 参数。 + +4. **启动时控制台有 BASE_URL 警告**:说明配置了内网地址,需修改 `.env` 中的 `BASE_URL` 为公网地址后重启。 + +5. **查看详细错误**:支付失败时,payjsapi 会输出 `ZPAY create_order_api 失败` 日志,含 status/code/msg,便于定位。 ## Getting started diff --git a/app/main.py b/app/main.py index a24cdbb..8b3dc59 100755 --- a/app/main.py +++ b/app/main.py @@ -118,15 +118,31 @@ async def startup_event(): @app.get("/health") async def health(check_zpay: str = ""): - """健康检查。check_zpay=1 时额外检测 zpayz.cn 连通性""" + """健康检查。check_zpay=1 时额外检测 zpayz.cn 连通性(含 SSL、mapi 接口)""" out = {"status": "ok", "service": "payjsapi", "base_url": BASE_URL} if str(check_zpay).lower() in ("1", "true", "yes"): + # 1) 检测 zpayz.cn 首页 try: r = requests.get("https://zpayz.cn", timeout=5) out["zpayz_reachable"] = r.status_code in (200, 301, 302, 404) - except Exception as e: + except requests.exceptions.SSLError as e: + out["zpayz_reachable"] = False + out["zpayz_error"] = f"SSL 错误: {e}(Ubuntu 请执行: apt install ca-certificates)" + except requests.RequestException as e: out["zpayz_reachable"] = False out["zpayz_error"] = str(e) + # 2) 检测 mapi 接口(实际支付调用地址) + if out.get("zpayz_reachable"): + try: + r2 = requests.post("https://zpayz.cn/mapi.php", data={"pid": "test"}, timeout=5) + out["zpayz_mapi_reachable"] = True + out["zpayz_mapi_status"] = r2.status_code + except requests.exceptions.SSLError as e: + out["zpayz_mapi_reachable"] = False + out["zpayz_mapi_error"] = f"SSL: {e}" + except requests.RequestException as e: + out["zpayz_mapi_reachable"] = False + out["zpayz_mapi_error"] = str(e) return out diff --git a/app/payment/zpay.py b/app/payment/zpay.py index 6be5bc9..6978457 100644 --- a/app/payment/zpay.py +++ b/app/payment/zpay.py @@ -5,12 +5,27 @@ ZPAY 支付渠道实现 """ import hashlib import json +import logging from typing import Any, Dict, Optional import requests from .base import PaymentProvider +logger = logging.getLogger(__name__) + + +def _is_internal_ip(ip: Optional[str]) -> bool: + """判断是否为内网 IP,ZPAY 可能拒收导致 Ubuntu 部署失败""" + if not ip or not ip.strip(): + return True + ip = ip.strip() + if ip in ("127.0.0.1", "localhost", "::1"): + return True + if ip.startswith("10.") or ip.startswith("192.168.") or ip.startswith("172.16."): + return True + return False + class ZPayProvider(PaymentProvider): """ZPAY 支付(易支付兼容接口)""" @@ -64,8 +79,10 @@ class ZPayProvider(PaymentProvider): "money": total_fee_yuan, "return_url": return_url or notify_url, } - if client_ip: + if client_ip and not _is_internal_ip(client_ip): params["clientip"] = client_ip + elif client_ip and _is_internal_ip(client_ip): + logger.warning("ZPAY create_order 跳过内网 client_ip=%s", client_ip) if extra and extra.get("param"): params["param"] = str(extra["param"]) params["sign_type"] = "MD5" @@ -98,6 +115,14 @@ class ZPayProvider(PaymentProvider): 适用于需要二维码或 H5 跳转的场景 """ channel_type = "wxpay" if pay_type.lower() in ("wx", "wechat", "wxpay") else "alipay" + effective_ip = (client_ip or "").strip() or "127.0.0.1" + # 内网 IP(127.0.0.1/10.x/192.168.x)可能导致 ZPAY 风控拒单,Ubuntu 部署时常见 + if _is_internal_ip(effective_ip): + logger.warning( + "ZPAY create_order_api client_ip=%s 为内网地址,已省略 clientip 参数(避免风控拒单)", + effective_ip, + ) + effective_ip = "" # 不传 clientip,由 ZPAY 从连接获取 params = { "pid": self.pid, "type": channel_type, @@ -105,9 +130,10 @@ class ZPayProvider(PaymentProvider): "notify_url": notify_url, "name": name, "money": total_fee_yuan, - "clientip": client_ip or "127.0.0.1", "device": device, } + if effective_ip: + params["clientip"] = effective_ip if return_url: params["return_url"] = return_url if extra and extra.get("param"): @@ -117,7 +143,15 @@ class ZPayProvider(PaymentProvider): try: resp = requests.post(self.MAPI_URL, data=params, timeout=15) + except requests.exceptions.SSLError as e: + logger.error("ZPAY SSL 错误(Ubuntu 可能 CA 证书不全): %s", e, exc_info=True) + return { + "status": "error", + "provider": self.name, + "msg": f"连接 zpayz.cn SSL 失败: {e}。Ubuntu 请安装 ca-certificates: apt install ca-certificates", + } except requests.RequestException as e: + logger.error("ZPAY 连接失败: %s", e, exc_info=True) return { "status": "error", "provider": self.name, @@ -160,10 +194,18 @@ class ZPayProvider(PaymentProvider): "O_id": result.get("O_id"), "trade_no": result.get("trade_no"), } + err_msg = result.get("msg", resp.text) + logger.error( + "ZPAY create_order_api 失败 status=%s code=%s msg=%s resp_text=%s", + resp.status_code, + code, + err_msg, + resp.text[:500] if resp.text else "", + ) return { "status": "error", "provider": self.name, - "msg": result.get("msg", resp.text), + "msg": err_msg, } def verify_notify(self, data: Dict[str, Any]) -> bool: