From 9233503cd7994a67756ec42d8f60fa96d4b1b455 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sat, 11 Jul 2026 12:10:00 +0800 Subject: [PATCH] =?UTF-8?q?deploy(pay):=20pangolin1=20=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E8=84=9A=E6=89=8B=E6=9E=B6(systemd/config/env/deploy,crypto+ne?= =?UTF-8?q?zha=20=E5=8F=8C=E6=B8=A0=E9=81=93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/pangolin1/README.md | 98 ++++++++++++++++++++++++++++++++ deploy/pangolin1/config.yaml | 66 +++++++++++++++++++++ deploy/pangolin1/deploy.sh | 95 +++++++++++++++++++++++++++++++ deploy/pangolin1/pay-v2.service | 57 +++++++++++++++++++ deploy/pangolin1/pay.env.example | 51 +++++++++++++++++ 5 files changed, 367 insertions(+) create mode 100644 deploy/pangolin1/README.md create mode 100644 deploy/pangolin1/config.yaml create mode 100755 deploy/pangolin1/deploy.sh create mode 100644 deploy/pangolin1/pay-v2.service create mode 100644 deploy/pangolin1/pay.env.example diff --git a/deploy/pangolin1/README.md b/deploy/pangolin1/README.md new file mode 100644 index 0000000..00aa865 --- /dev/null +++ b/deploy/pangolin1/README.md @@ -0,0 +1,98 @@ +# pay v2 — pangolin1 部署脚手架 + +pangolin1(`103.119.13.48`,Debian 12,root,内存 ~960Mi)上单独跑一份 `payd` +实例(`pay-v2`),只走 **crypto(USDT-TRC20)+ 哪吒(nezha,CNY)双渠道**,MOCK 关。 +与阿里云那份 `pay`(仓库根 `deploy.sh`,支付宝+微信)彻底分开:不同 systemd +服务名(`pay-v2`)、不同端口(`8090`,旧 USDT watcher 已下线释放)、不同二进制/数据/ +配置路径(`/opt/pay-v2` `/var/lib/pay-v2` `/etc/pay-v2`)、不同系统用户(`payv2`)。 + +## 文件清单 + +| 文件 | 用途 | +|---|---| +| `pay-v2.service` | systemd unit,装到 pangolin1 的 `/etc/systemd/system/pay-v2.service` | +| `pay.env.example` | env 模板,人工填真值后存到 pangolin1 的 `/etc/pay-v2/pay.env`(**不进 git**) | +| `config.yaml` | 非密钥配置源,`deploy.sh` 首次部署会拷到 `/etc/pay-v2/config.yaml` | +| `deploy.sh` | 部署脚本(交叉编译 + 上传 + 装 unit/config + 重启 + 探活回滚) | + +## 部署步骤 + +1. **先手工准备凭证**(pangolin1 上,或本机 `rbw get` 取值后 `scp`/粘贴): + ```bash + ssh pangolin1 'mkdir -p /etc/pay-v2' + # 把 pay.env.example 拷一份改名 pay.env,逐项填真值,再传上去: + scp pay.env pangolin1:/etc/pay-v2/pay.env + ssh pangolin1 'chmod 640 /etc/pay-v2/pay.env' + ``` + 需要人工填的 env(全部走 Bitwarden,建议条目命名见括号): + - `PANGOLIN_CRYPTO_1_ADDRESS` —— TRC20 收款地址(Bitwarden:`pay-v2 pangolin1 crypto-1`) + - `TRONGRID_API_KEY` —— TronGrid API key(同上条目,或单独 `pay-v2 trongrid-key`) + - `PANGOLIN_NEZHA_1_PID` —— 哪吒商户号(Bitwarden:`pay-v2 pangolin1 nezha-1`) + - `PANGOLIN_NEZHA_1_PRIVATE_KEY` —— 商户 RSA 私钥 PEM(同上条目) + - `PANGOLIN_NEZHA_1_PLATFORM_PUBLIC_KEY` —— 哪吒平台 RSA 公钥 PEM(同上条目) + - `BIZ_PANGOLIN_SECRET` —— 与 pangolin-server 的 `PAY_BIZ_SECRET` 同值 + (Bitwarden:`pay-v2 BIZ_PANGOLIN_SECRET`,两边必须手工同步同一个值) + +2. **改 `config.yaml` 里的占位域名**(部署前,本地改好再让 `deploy.sh` 上传首个版本): + - `biz.pangolin.callback_url` 的 `` 换成真实域名。 + +3. **跑部署脚本**(pay 仓根目录,本机执行): + ```bash + ./deploy/pangolin1/deploy.sh + ``` + 首次会:建 `payv2` 系统用户 → 建 `/opt/pay-v2` `/var/lib/pay-v2` `/etc/pay-v2` → + 装 unit → 拷 `config.yaml`(仅当远端还没有时)→ 传二进制 → `enable --now` → + 探活 `curl 127.0.0.1:8090/health`。之后重跑只刷新二进制,`config.yaml`/`pay.env` + 都不会被覆盖(改配置要单独 `scp` 再 `systemctl restart pay-v2`)。 + +4. **验证**: + ```bash + ssh pangolin1 'systemctl status pay-v2 --no-pager' + ssh pangolin1 'journalctl -u pay-v2 -n 50 --no-pager' + ssh pangolin1 'curl -s 127.0.0.1:8090/health' + ``` + +## seed 生产套餐(占位命令,按需调整价格/名称) + +pay 目前套餐通过代码里的 `seed()`/`seedWechat()` 之类函数在启动时 upsert,或者用 +管理接口/直接写库。crypto 结算币种是 USDT,nezha 结算币种是 CNY——**两个渠道各需 +一套独立的 `ProductPrice`**(同一 `Product` 下按币种分别挂价,不能共用一条同金额 +不同币种硬凑)。生产 seed 前先确认落地方式(建代码里的 seed 函数,还是走管理接口), +以下只是占位: + +```bash +# 占位:crypto(USDT)套餐价 —— 具体走哪个接口/表待定,先别直接照抄执行 +# INSERT INTO product_prices (product_id, currency, amount, ...) VALUES (..., 'USDT', ...); + +# 占位:nezha(CNY)套餐价 +# INSERT INTO product_prices (product_id, currency, amount, ...) VALUES (..., 'CNY', ...); +``` + +## 渠道说明 + +- **crypto**:`method` 名为 `crypto`。地址 watch-only,**payd 不做归集**——收到的 + USDT 一直留在 `PANGOLIN_CRYPTO_1_ADDRESS` 这个地址上,不会自动转出。定期手动 + 归集/转出是运营动作,不在 payd 职责内。 +- **nezha**:`method` 名预期为 `nezha`(哪吒 provider 正在另一分支开发,尚未合并 + 进 `internal/providerbuild/registry.go` 的 `BuildRegistry`)。本仓 `config.yaml` + 已经把 `pangolin-nezha-1` 账户和 `credential_env_prefix` 结构定好,**但在 + provider 代码合并前,该渠道不会真正被注册**(`BuildRegistry` 只认识 + `crypto`/`alipay`/`stripe`/`fake`,未知 channel 的 enabled 账户会被静默忽略, + 不会 fatal,只是收不到这个渠道)。provider 分支合并后需确认: + - env 变量名是否确实是 `PID` / `PRIVATE_KEY` / `PLATFORM_PUBLIC_KEY`(本文档 + 按任务描述推导,以 provider 代码实际 `accts.Credential(accountID, "KEY")` + 调用为准); + - PEM 私钥/公钥在 `pay.env` 里是单行原始换行还是 `\n` 转义,需和 provider 的 + 解析方式对齐(`pay.env.example` 里留了两种可能的说明)。 + +## 已知易错点 + +- `QUERY_SYNC_ENABLED` **不是** `QUERYSYNC_ENABLED`——viper 的 `"." → "_"` + 替换对 `query_sync.enabled` 展开成 `QUERY_SYNC_ENABLED`(带下划线),已用本地 + viper 用例验证过。`pay.env.example` 里已经用对的名字。 +- **绝对不要**在 `pay.env` 或 `config.yaml` 里设置 `MOCK_CHANNEL_ENABLED=true` / + `mock_channel_enabled: true`——会注册 `fake` 渠道并挂载 + `/api/v2/dev/orders/:order_no/mark-paid` 这类联调专用端点,生产环境绝不能开。 +- 阿里云那份 `pay`(仓库根 `deploy.sh`)服务名是 `pay`、端口 `8080`、路径 + `/opt/pay`;这份是 `pay-v2` / `8090` / `/opt/pay-v2`,两者互不干扰,**别把 + 两个 `deploy.sh` 搞混执行**。 diff --git a/deploy/pangolin1/config.yaml b/deploy/pangolin1/config.yaml new file mode 100644 index 0000000..0a65244 --- /dev/null +++ b/deploy/pangolin1/config.yaml @@ -0,0 +1,66 @@ +# pay v2 — pangolin1 部署配置模板。 +# +# 部署时原样拷到 /etc/pay-v2/config.yaml(deploy.sh 首次部署会拷;之后重跑不覆盖, +# 改配置需手动 scp 这个文件上去再 systemctl restart pay-v2)。 +# +# ⚠️ 密钥/凭证一律不写在这里!全部走 /etc/pay-v2/pay.env(见同目录 pay.env.example), +# 由 config.go 的 viper AutomaticEnv("." → "_" 转大写)或 accounts.Registry 按 +# credential_env_prefix 在运行时从环境变量读取。 + +server: + port: "8090" + mode: "release" + # 拼 notify_url / return_url 用(alipay/wechat 才用得到;本部署这两渠道都禁用, + # 留默认占位即可,不影响 crypto/nezha)。 + base_url: "http://127.0.0.1:8090" + +database: + driver: "sqlite" + dsn: "/var/lib/pay-v2/pay.db" + +# 本部署刻意只留 crypto + nezha 两个账户;alipay_sandbox / wechat 不写段落即用 +# config.go 默认值(enabled: false),mock_channel_enabled 同理不写即默认 false +# ——不要在这里加 mock_channel_enabled: true。 + +# accounts:多账户路由注册表(v2)。凭证一律不写这里,只声明 credential_env_prefix, +# 真值由 internal/accounts.Registry 运行时从 `_` 环境变量读取。 +accounts: + # crypto:USDT-TRC20,watch-only(不持私钥)。地址/TronGrid key 走 + # PANGOLIN_CRYPTO_1_ADDRESS / PANGOLIN_CRYPTO_1_TRONGRID_KEY(或全局 TRONGRID_API_KEY)。 + - account_id: "pangolin-crypto-1" + channel: "crypto" + enabled: true + weight: 1 + region: "global" + subject: "pangolin USDT 收款(TRC20)" + credential_env_prefix: "PANGOLIN_CRYPTO_1" + + # nezha:哪吒渠道,CNY 结算。provider 正在另一分支开发,这里先把账户/env 前缀 + # 结构定好;商户号/密钥走 PANGOLIN_NEZHA_1_PID / PANGOLIN_NEZHA_1_PRIVATE_KEY / + # PANGOLIN_NEZHA_1_PLATFORM_PUBLIC_KEY。provider 未合并前 providerbuild.BuildRegistry + # 不认识 "nezha" 这个 channel,该账户会被跳过(不会 fatal),只是渠道暂不生效。 + - account_id: "pangolin-nezha-1" + channel: "nezha" + enabled: true + weight: 1 + region: "cn" + subject: "pangolin CNY 收款(哪吒)" + credential_env_prefix: "PANGOLIN_NEZHA_1" + +# 业务对接:pangolin-server 的 webhook 接收器。secret 留空,走环境变量 +# BIZ_PANGOLIN_SECRET(须与 pangolin-server 侧 PAY_BIZ_SECRET 同值)。 +biz: + pangolin: + callback_url: "https:///v1/webhook/pay" # ⚠️ 部署时替换成真实域名 + secret: "" + +# 兜底主动查单(防异步回调丢失)。 +query_sync: + enabled: true + interval_sec: 30 + max_age_min: 30 + +# 后台守护/对账(过期清理/用量刷新/查单对账/已付抽查/退款自愈/crypto 孤儿扫描)。 +# 其余间隔字段沿用 config.go 默认值,量不大不必在此覆盖。 +reconcile: + enabled: true diff --git a/deploy/pangolin1/deploy.sh b/deploy/pangolin1/deploy.sh new file mode 100755 index 0000000..6fb4c3b --- /dev/null +++ b/deploy/pangolin1/deploy.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# pay v2 → pangolin1 专用部署脚本:交叉编译 → 传 pangolin1 → 首次装 +# 用户/目录/systemd unit/config.yaml → 备份旧二进制 → daemon-reload + enable --now → +# 探活(失败自动回滚)。幂等:重复跑只刷新二进制,不覆盖 /etc/pay-v2/pay.env 或已存在的 +# /etc/pay-v2/config.yaml。 +# +# 用法(pay 仓根目录执行): +# ./deploy/pangolin1/deploy.sh +# +# 前提: +# - ~/.ssh/config 有 pangolin1 别名(密钥登录);pangolin1 上以 root 身份操作 +# (systemctl / 装 unit / 建系统用户都需要)。 +# - 首次部署前,必须已在 pangolin1 上手工创建 /etc/pay-v2/pay.env +# (参照 deploy/pangolin1/pay.env.example 填真值,真值一律走 Bitwarden/rbw)。 +# 本脚本刻意不生成/不覆盖这个文件——密钥不能经这条流水线过一遍。 +# +# shellcheck disable=SC2029 # 下面 ssh 命令里的 $HOST/$DIR/$SVC 等都是本脚本的本地 +# 变量,故意要在客户端展开后再传给远端 shell(远端命令里没有任何"远端才存在的变量"), +# 这正是想要的行为,不是需要转义的 bug。 +set -euo pipefail + +HOST=pangolin1 +SVC=pay-v2 +SVC_USER=payv2 +DIR=/opt/pay-v2 +DATA_DIR=/var/lib/pay-v2 +ETC_DIR=/etc/pay-v2 +BIN=/tmp/pay-v2d-release + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +HERE="$REPO_ROOT/deploy/pangolin1" + +log() { printf '\033[1;32m[deploy-pangolin1]\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m[deploy-pangolin1]\033[0m %s\n' "$*" >&2; } + +echo "[1/6] 交叉编译 linux/amd64 …" +( + cd "$REPO_ROOT" + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o "$BIN" . +) + +echo "[2/6] 首次初始化(幂等):系统用户 + 目录 …" +ssh "$HOST" " + set -e + id -u '$SVC_USER' >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin '$SVC_USER' + mkdir -p '$DIR' '$DATA_DIR' '$ETC_DIR' + chown -R '$SVC_USER:$SVC_USER' '$DATA_DIR' +" + +echo "[3/6] 装 systemd unit + config.yaml(不覆盖已存在的 config.yaml/pay.env)…" +scp -q "$HERE/pay-v2.service" "$HOST:/etc/systemd/system/pay-v2.service" +if ssh "$HOST" "[ -f '$ETC_DIR/config.yaml' ]"; then + log "$ETC_DIR/config.yaml 已存在,跳过覆盖(改配置需手动: scp $HERE/config.yaml $HOST:$ETC_DIR/config.yaml)" +else + scp -q "$HERE/config.yaml" "$HOST:$ETC_DIR/config.yaml" + log "已写入首个 $ETC_DIR/config.yaml" +fi +if ! ssh "$HOST" "[ -f '$ETC_DIR/pay.env' ]"; then + warn "$ETC_DIR/pay.env 不存在!请先在 $HOST 上手工创建(参照 $HERE/pay.env.example,真值走 Bitwarden)," + warn "否则 systemctl start 会因缺 env 报错或以空凭证跑起来。继续执行,但服务大概率起不来。" +fi +ssh "$HOST" " + [ -f '$ETC_DIR/pay.env' ] && { chown root:'$SVC_USER' '$ETC_DIR/pay.env'; chmod 640 '$ETC_DIR/pay.env'; } || true + chown root:root '$ETC_DIR/config.yaml' 2>/dev/null || true + chmod 644 '$ETC_DIR/config.yaml' 2>/dev/null || true +" + +echo "[4/6] 上传二进制 + 备份旧版 + 切换 …" +scp -q "$BIN" "$HOST:$DIR/payd-new" +ssh "$HOST" " + set -e + [ -f '$DIR/payd' ] && cp '$DIR/payd' '$DIR/payd.bak' || true + mv '$DIR/payd-new' '$DIR/payd' + chmod +x '$DIR/payd' + chown '$SVC_USER:$SVC_USER' '$DIR/payd' +" + +echo "[5/6] daemon-reload + enable --now …" +ssh "$HOST" "systemctl daemon-reload && systemctl enable --now $SVC && systemctl restart $SVC" + +echo "[6/6] 探活 …" +if ssh "$HOST" "curl -fsS --retry 8 --retry-delay 1 --retry-connrefused -o /dev/null -w 'health %{http_code}\n' http://127.0.0.1:8090/health"; then + echo "✅ 发布完成(上一版已备份在 $DIR/payd.bak,若存在)" +else + echo "❌ 探活失败,自动回滚 …" + ssh "$HOST" " + if [ -f '$DIR/payd.bak' ]; then + cp '$DIR/payd.bak' '$DIR/payd' && systemctl restart '$SVC' + else + systemctl stop '$SVC' + fi + " + echo "↩️ 已回滚/停止。请 ssh $HOST 'journalctl -u $SVC -n 100 --no-pager' 排查后重试。" + exit 1 +fi diff --git a/deploy/pangolin1/pay-v2.service b/deploy/pangolin1/pay-v2.service new file mode 100644 index 0000000..28e1141 --- /dev/null +++ b/deploy/pangolin1/pay-v2.service @@ -0,0 +1,57 @@ +[Unit] +Description=pay v2 收款服务(pangolin1 专用实例;crypto USDT + 哪吒(nezha) CNY 双渠道,MOCK 关) +Documentation=https://github.com/wangjia/pay +After=network-online.target +Wants=network-online.target +# 崩溃后不放弃自愈,但避免瞬时死循环耗尽资源(呼应 pangolin-agent.service 的自愈风格, +# 这里用有限重启而非 Restart=always,因 payd 不是"必须永不停"的网络自愈进程)。 +# 注:StartLimitIntervalSec=/StartLimitBurst= 是 [Unit] 段指令(现代 systemd, +# 与 pangolin-agent.service 的用法一致),不是 [Service] 段——放错段会被忽略。 +StartLimitIntervalSec=60 +StartLimitBurst=5 + +[Service] +Type=simple +User=payv2 +Group=payv2 +WorkingDirectory=/var/lib/pay-v2 +EnvironmentFile=/etc/pay-v2/pay.env +ExecStart=/opt/pay-v2/payd +Restart=on-failure +RestartSec=3 + +# ── 加固 ───────────────────────────────────────────────────────────────── +# ProtectSystem=strict 把整个文件系统挂只读,仅 ReadWritePaths 列出的目录可写 +# (sqlite 库文件 + 未来任何本地状态都落 /var/lib/pay-v2)。/etc/pay-v2 下的 +# config.yaml / pay.env 只读挂载即可,不需要写权限。 +# 仍需正常出网:连 TronGrid(crypto 查链)、哪吒网关(nezha 下单/查单/回调验签)、 +# 回调 pangolin-server 的 /v1/webhook/pay——因此不设 PrivateNetwork=true, +# 也不把 RestrictAddressFamilies 收窄到只剩 AF_UNIX(仍留 AF_INET/AF_INET6)。 +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/var/lib/pay-v2 +PrivateTmp=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectKernelLogs=true +ProtectControlGroups=true +ProtectClock=true +ProtectHostname=true +ProtectProc=invisible +RestrictSUIDSGID=true +RestrictRealtime=true +RestrictNamespaces=true +RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX +LockPersonality=true +RemoveIPC=true +# payd 是纯 Go 静态二进制(CGO_ENABLED=0 交叉编译,sqlite 走 modernc 纯 Go 实现, +# 无 cgo/JIT),历史上 MemoryDenyWriteExecute=true 对这类构建安全;若启动后 +# journalctl 看到 SIGSYS/mmap 相关崩溃,先注释这一行排查。 +MemoryDenyWriteExecute=true +CapabilityBoundingSet= +AmbientCapabilities= +UMask=0027 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/pangolin1/pay.env.example b/deploy/pangolin1/pay.env.example new file mode 100644 index 0000000..685eab6 --- /dev/null +++ b/deploy/pangolin1/pay.env.example @@ -0,0 +1,51 @@ +# pay v2 — pangolin1 部署 env 模板(/etc/pay-v2/pay.env)。 +# +# ⚠️ 本文件全是占位符,禁止填真值后提交进 git!真值一律经 Bitwarden → rbw 在 +# pangolin1 上手工写入 /etc/pay-v2/pay.env(权限建议 640, root:payv2)。 +# 部署脚本 deploy.sh 不会创建/覆盖这个文件——首次部署需手工准备好它。 +# +# viper 的 env 覆盖规则是 "." → "_" 再转大写(见 config/config.go SetEnvKeyReplacer), +# 下面每个变量名后注释了它覆盖的是 config.yaml 的哪个字段。 + +# ── 基础服务 ──────────────────────────────────────────────────────────────── +SERVER_PORT=8090 # server.port(pangolin1 上 8090,旧 USDT watcher 已下线释放) +SERVER_MODE=release # server.mode(release 关 gin 调试日志) +DATABASE_DRIVER=sqlite # database.driver +DATABASE_DSN=/var/lib/pay-v2/pay.db # database.dsn(sqlite 文件路径,ExecStart 前需目录存在且属主可写) + +# ── crypto 渠道(USDT-TRC20,watch-only,不持私钥)── Bitwarden 条目建议命名 +# "pay-v2 pangolin1 crypto-1" ───────────────────────────────────────────── +# 收款地址(TRC20,仅地址,不涉及私钥/助记词): +PANGOLIN_CRYPTO_1_ADDRESS=<占位-TRC20收款地址-走Bitwarden> +# TronGrid API key:优先用账户级 _TRONGRID_KEY,缺省回退全局 TRONGRID_API_KEY +# (internal/provider/crypto/crypto.go::apiKey)。单账户场景填全局这个即可, +# 多账户各自限流时再加 PANGOLIN_CRYPTO_1_TRONGRID_KEY 覆盖。 +TRONGRID_API_KEY=<占位-TronGrid-APIKey-走Bitwarden> +# PANGOLIN_CRYPTO_1_TRONGRID_KEY=<可选-账户专属TronGrid-Key-走Bitwarden> + +# ── 哪吒(nezha)渠道(CNY 结算)── Bitwarden 条目建议命名 "pay-v2 pangolin1 nezha-1" ── +# provider 正在另一分支开发,以下三项是其将读取的凭证 env(命名按 +# CredentialEnvPrefix=PANGOLIN_NEZHA_1 推导,若合并后实际 key 名不同,以那边为准再改)。 +PANGOLIN_NEZHA_1_PID=<占位-哪吒商户号-走Bitwarden> +# 商户 RSA 私钥(PEM)。若 env 单行存不了原始换行,推荐把 PEM 内的真实换行替换成 +# 字面 "\n" 再存一行(需 provider 侧对应做 strings.ReplaceAll(v, `\n`, "\n") 解码; +# nezha provider 合并后核对其实际解析方式,当前仅占位)。 +PANGOLIN_NEZHA_1_PRIVATE_KEY=<占位-商户RSA私钥PEM-走Bitwarden> +# 哪吒平台 RSA 公钥(PEM,验证哪吒回调签名用),同上单行转义方式。 +PANGOLIN_NEZHA_1_PLATFORM_PUBLIC_KEY=<占位-平台RSA公钥PEM-走Bitwarden> + +# ── 业务对接(pangolin)── Bitwarden 条目建议命名 "pay-v2 BIZ_PANGOLIN_SECRET" ── +# HMAC 共享密钥,须与 pangolin-server 那侧 PAY_BIZ_SECRET 同值(见 +# server/internal/httpapi 对 /v1/webhook/pay 的验签)。 +BIZ_PANGOLIN_SECRET=<占位-与pangolin-server-PAY_BIZ_SECRET同值-走Bitwarden> + +# ── 后台守护 ──────────────────────────────────────────────────────────────── +RECONCILE_ENABLED=true # reconcile.enabled(过期清理/查单对账/退款自愈/crypto孤儿扫描) +QUERY_SYNC_ENABLED=true # query_sync.enabled ⚠️ 注意是 QUERY_SYNC(带下划线), + # 不是 QUERYSYNC——viper 的 "." → "_" 替换对 query_sync.enabled + # 展开成 QUERY_SYNC_ENABLED(已用本地 viper 用例验证过)。 + +# ── MOCK 渠道:pangolin1 是真实收款环境,禁止开 ───────────────────────────── +# 不要在这里设置 MOCK_CHANNEL_ENABLED(留空/不写即默认 false)。一旦误设为 true 会 +# 注册 fake 渠道 + 挂载 /api/v2/dev/orders/:order_no/mark-paid 这类联调专用端点。 +# MOCK_CHANNEL_ENABLED=true # ← 禁止在生产取消这行注释