2026-06-27 13:30:31 +08:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
"""支付 provider readiness doctor.
|
|
|
|
|
|
|
|
|
|
|
|
检查真实支付 acceptance 所需的全部前置条件是否就绪。
|
|
|
|
|
|
返回 ready / missing_env_vars / missing_files / notes。
|
|
|
|
|
|
|
|
|
|
|
|
用法:
|
|
|
|
|
|
python3 scripts/payment_readiness_doctor.py
|
|
|
|
|
|
python3 scripts/payment_readiness_doctor.py --json
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
REQUIRED_ENV_VARS = [
|
2026-06-28 12:11:43 +08:00
|
|
|
|
"GAOKAO_LLM_PROVIDER",
|
|
|
|
|
|
"GAOKAO_LLM_API_KEY",
|
|
|
|
|
|
"GAOKAO_LLM_BASE_URL",
|
|
|
|
|
|
"GAOKAO_LLM_MODEL",
|
2026-06-27 13:30:31 +08:00
|
|
|
|
"GAOKAO_PAYMENT_APP_ID",
|
|
|
|
|
|
"GAOKAO_PAYMENT_MERCHANT_ID",
|
|
|
|
|
|
"GAOKAO_PAYMENT_PRIVATE_KEY_PATH",
|
|
|
|
|
|
"GAOKAO_PAYMENT_ALIPAY_PUBLIC_KEY_PATH",
|
|
|
|
|
|
"GAOKAO_PAYMENT_NOTIFY_URL",
|
|
|
|
|
|
"GAOKAO_PAYMENT_RETURN_URL",
|
|
|
|
|
|
"GAOKAO_PAYMENT_WEBHOOK_SECRET",
|
|
|
|
|
|
"GAOKAO_ORDERS_FERNET_KEY",
|
|
|
|
|
|
"GAOKAO_JWT_SECRET",
|
|
|
|
|
|
"GAOKAO_PORTAL_TOKEN_SECRET",
|
|
|
|
|
|
"GAOKAO_ADMIN_PASS",
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
REQUIRED_FILE_ENV_VARS = [
|
|
|
|
|
|
("GAOKAO_PAYMENT_PRIVATE_KEY_PATH", "应用私钥文件"),
|
|
|
|
|
|
("GAOKAO_PAYMENT_ALIPAY_PUBLIC_KEY_PATH", "支付宝公钥文件"),
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
REQUIRED_URL_PREFIX = "https://"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check() -> dict:
|
|
|
|
|
|
missing_env = []
|
|
|
|
|
|
notes = []
|
|
|
|
|
|
|
|
|
|
|
|
for var in REQUIRED_ENV_VARS:
|
|
|
|
|
|
val = os.environ.get(var, "")
|
|
|
|
|
|
if not val:
|
|
|
|
|
|
missing_env.append(var)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查 URL 是否 HTTPS
|
|
|
|
|
|
notify_url = os.environ.get("GAOKAO_PAYMENT_NOTIFY_URL", "")
|
|
|
|
|
|
if notify_url and not notify_url.startswith(REQUIRED_URL_PREFIX):
|
|
|
|
|
|
notes.append(
|
|
|
|
|
|
f"GAOKAO_PAYMENT_NOTIFY_URL 应为 HTTPS(当前: {notify_url[:30]}...)"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return_url = os.environ.get("GAOKAO_PAYMENT_RETURN_URL", "")
|
|
|
|
|
|
if return_url and not return_url.startswith(REQUIRED_URL_PREFIX):
|
|
|
|
|
|
notes.append(
|
|
|
|
|
|
f"GAOKAO_PAYMENT_RETURN_URL 应为 HTTPS(当前: {return_url[:30]}...)"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# JWT secret 长度
|
|
|
|
|
|
jwt = os.environ.get("GAOKAO_JWT_SECRET", "")
|
|
|
|
|
|
if jwt and len(jwt) < 32:
|
|
|
|
|
|
notes.append(f"GAOKAO_JWT_SECRET 长度不足 32(当前 {len(jwt)})")
|
|
|
|
|
|
|
|
|
|
|
|
# Portal token secret 与 JWT 不同
|
|
|
|
|
|
portal = os.environ.get("GAOKAO_PORTAL_TOKEN_SECRET", "")
|
|
|
|
|
|
if jwt and portal and jwt == portal:
|
|
|
|
|
|
notes.append("GAOKAO_PORTAL_TOKEN_SECRET 与 GAOKAO_JWT_SECRET 相同,必须分离")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查密钥文件是否存在
|
|
|
|
|
|
missing_files = []
|
|
|
|
|
|
for var, desc in REQUIRED_FILE_ENV_VARS:
|
|
|
|
|
|
path = os.environ.get(var, "")
|
|
|
|
|
|
if path and not Path(path).is_file():
|
|
|
|
|
|
missing_files.append({"var": var, "path": path, "desc": desc})
|
|
|
|
|
|
|
|
|
|
|
|
ready = len(missing_env) == 0 and len(missing_files) == 0 and len(notes) == 0
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"ready": ready,
|
|
|
|
|
|
"missing_env_vars": missing_env,
|
|
|
|
|
|
"missing_files": missing_files,
|
|
|
|
|
|
"notes": notes,
|
|
|
|
|
|
"total_required": len(REQUIRED_ENV_VARS),
|
|
|
|
|
|
"total_satisfied": len(REQUIRED_ENV_VARS) - len(missing_env),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
|
as_json = "--json" in sys.argv
|
|
|
|
|
|
result = check()
|
|
|
|
|
|
|
|
|
|
|
|
if as_json:
|
|
|
|
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
|
|
|
|
else:
|
|
|
|
|
|
status = "READY" if result["ready"] else "NOT READY"
|
|
|
|
|
|
print(f"[payment-doctor] {status}")
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" env vars satisfied: {result['total_satisfied']}/{result['total_required']}"
|
|
|
|
|
|
)
|
|
|
|
|
|
if result["missing_env_vars"]:
|
|
|
|
|
|
print(f" missing env vars: {', '.join(result['missing_env_vars'])}")
|
|
|
|
|
|
if result["missing_files"]:
|
|
|
|
|
|
for f in result["missing_files"]:
|
|
|
|
|
|
print(f" missing file: {f['var']} → {f['path']} ({f['desc']})")
|
|
|
|
|
|
if result["notes"]:
|
|
|
|
|
|
for n in result["notes"]:
|
|
|
|
|
|
print(f" note: {n}")
|
|
|
|
|
|
if not result["ready"]:
|
|
|
|
|
|
print("\n 参考 .env.payment.example 获取完整配置模板")
|
|
|
|
|
|
print(" 参考 docs/PAYMENT_PROVIDER_ONBOARDING.md 获取接入步骤")
|
|
|
|
|
|
|
|
|
|
|
|
return 0 if result["ready"] else 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
sys.exit(main())
|