2026-09-29 · 本文所有代码都在一台真实跑着的站点上验证过(Cloudflare 命名隧道 + 本地 Python 服务 + epay 类网关)。
很多云主机(尤其入门款)默认把全部入站端口挡掉。判断方法很直接:在另一台网络环境上 curl 你的公网 IP。
curl -m 5 -s -o /dev/null -w "%{http_code}\n" http://<你的公网IP>:8080
# 全端口超时 = 防火墙挡了入站,加 DNS 记录没用
注意:加一条 A 记录(哪怕套 Cloudflare 橙云)不能解决这个问题 —— Cloudflare 回源时还是打到那个被挡的 IP,只会返回 5xx。真正的解法是出站隧道:主机主动连出去,流量走隧道回来。
免费随机域名每次重启都会变,而 notify_url(支付回调地址)一旦写死就失效 —— 客户付了钱,你这边一条订单都收不到。正确做法:
cloudflared tunnel login # 打开输出的授权链接,选域名 cloudflared tunnel create myshop # 记下输出的 UUID # DNS 里加 CNAME: shop.example.com -> <UUID>.cfargotunnel.com (橙云代理) cloudflared tunnel run --url http://localhost:8080 myshop
重连时复用同一个 UUID,CNAME 和回调地址永远不用改。
epay 系列网关的规则:参数按 key 排序 → 剔除空值与 sign/sign_type → 拼成 k=v&k=v → 末尾追加上商户 KEY → MD5。
import hashlib, urllib.parse
def sign(params, key):
p = {k: v for k, v in params.items()
if v not in ("", None) and k not in ("sign", "sign_type")}
s = "&".join(f"{k}={p[k]}" for k in sorted(p)) + key
return hashlib.md5(s.encode("utf-8")).hexdigest()
params = {"pid": "1001", "type": "alipay", "out_trade_no": "MEOW001",
"notify_url": "https://shop.example.com/notify",
"return_url": "https://shop.example.com/paid.html",
"name": "文案代做", "money": "9.90", "sitename": "喵窝"}
params["sign"] = sign(params, "你的商户KEY")
params["sign_type"] = "MD5"
上线前先用服务端跑一次:POST 到网关的 submit.php,返回 HTML 里带 sign 隐藏字段就说明 PID+KEY+MD5 规则对了。这一步能省掉后面几小时的排查。
客户在聊天框报的单号一律不可信。只有网关 notify 回来的、验签通过的记录才记账:
def handle_notify(q): # q 是回调的 query 参数
got = q.get("sign", [""])[0]
if sign(q, KEY) != got:
return log("验签=失败"), "success"
if q.get("trade_status", [""])[0] != "TRADE_SUCCESS":
return log("状态未成功"), "success"
log(f"订单={q['out_trade_no'][0]} 金额={q['money'][0]} 验签=通过")
return "success" # 必须原样返回 success,否则网关会一直重发
两个坑:① 返回体不是 success 会触发无限重发;② 统计收入要解析显式的金额字段(金额=¥X),绝不能正则去抓一行里的第一个数字 —— 订单号是数字,会把收入放大几个数量级。
客户常报短单号,所以查询要支持前缀匹配并要求唯一命中,精确匹配会把真实付款的人误判成骗子。
交付能力没就绪时,把支付按钮 disable 掉并挂暂停横幅。能在收钱的页面上出现编造的客服联系方式,那不是占位符,是欺诈。虚拟物品页面上统一写「交付后不退款;未收到或未达承诺免费重做/补发」,落地页、支付成功页、客服话术三处措辞要一致。
notify_url/return_url