9.2 KiB
微信 Native 扫码充值
电脑账单页面生成支付二维码,用户用手机微信扫码,支付确认后金额进入当前组织钱包。支持固定档位和自定义金额;组织管理员可以发起和查看本组织的支付订单,普通成员继续共用组织额度。超级管理员可在计费页查看所有组织的充值订单、按订单发起微信原路退款,也保留原有人工上账入口。
微信商户侧准备
- 公司商户号已开通 Native 支付。小程序支付权限不能替代 Native 权限。
- 已认证的小程序或服务号 AppID 已绑定此商户号。已有小程序 AppID 可用,网页无需跳转小程序,也不需要用户微信登录。
- 商户经营资料、网站和实际 AI 服务相符,并完成适用的审核及网站运营手续。
- 取得商户 API 证书序列号、商户私钥、32 字节 APIv3 密钥,以及微信支付公钥和公钥 ID。这里使用微信支付公钥模式。
参考:Native 接入准备、开发参数。
部署配置
- ACK 发布的新 Go 镜像开启了启动前迁移,会自动应用尚未执行的
0007_wechat_native_payments.sql和0008_wechat_refunds.sql,并通过DATABASE_APP_ROLE给应用账号授权。构建镜像前运行npm run db:bundle并提交同步后的 Go 迁移包;不要修改已执行的历史迁移。其他部署方式可手工运行npm run db:migrate,或按部署说明配置同一启动迁移流程。 - 在 Go 后端 配置下表变量;配置项不进入前端静态构建或网页设置接口。私钥文件来自微信商户平台的商户 API 证书生成流程,格式为 PKCS#8
-----BEGIN PRIVATE KEY-----。 - 将公开网站地址
ZHINIAN_PUBLIC_BASE_URL(或已设置的NEXT_PUBLIC_APP_URL)配置为正确的 HTTPS 网站地址。回调路径由已有/api反向代理转发给 Go,公网微信服务器应能访问,且该路径不应被网站登录网关、验证码或 WAF 挑战页拦截。 - 配置完整后设置
WECHAT_PAY_ENABLED=true并重启 Go。支付开启时强制要求 PostgreSQL 持久化和有效的强制登录配置;缺少密钥会启动失败。默认关闭时不展示充值入口。
| 变量 | 含义 |
|---|---|
WECHAT_PAY_MCH_ID |
普通商户号 |
WECHAT_PAY_APP_ID |
已认证且绑定商户号的小程序/服务号 AppID |
WECHAT_PAY_NOTIFY_URL |
https://正式域名/api/payments/wechat/notify |
WECHAT_PAY_MERCHANT_SERIAL |
商户 API 证书序列号 |
WECHAT_PAY_PRIVATE_KEY_FILE |
商户 PKCS#8 私钥 PEM 文件路径 |
WECHAT_PAY_API_V3_KEY |
32 字节 APIv3 密钥,用于回调解密 |
WECHAT_PAY_PUBLIC_KEY_ID |
微信支付公钥 ID,通常以 PUB_KEY_ID_ 开头 |
WECHAT_PAY_PUBLIC_KEY_FILE |
微信支付公钥 PEM 文件路径 |
WECHAT_PAY_MIN_AMOUNT_FEN |
自定义充值最低金额,默认 100(1 元) |
WECHAT_PAY_MAX_AMOUNT_FEN |
自定义充值最高金额,默认 1000000(10000 元),不得超过此上限 |
WECHAT_PAY_PRESET_AMOUNTS_FEN |
固定档位,默认 10000,50000,100000(100、500、1000 元) |
文件变量也支持对应的 WECHAT_PAY_PRIVATE_KEY_PEM / WECHAT_PAY_PUBLIC_KEY_PEM,同时配置时文件优先。金额均为整数分,最多配置 8 个不重复且在上下限内的固定档位。
ACK 的 secrets.example.yaml 已提供 zhinian-go-wechat-pay 模板,只有 Go 工作负载加载它。模板保留占位符;实际 Secret 通过部署环境注入。Docker Compose 可通过已有 .env.local 注入 PEM 字符串(换行可写作 \n),或在 Go 容器挂载只读密钥文件并填写容器内路径。若同时配置两个公开网站地址,NEXT_PUBLIC_APP_URL 优先,需确保它不是旧的 localhost 地址。
微信退款通知使用 https://正式域名/api/payments/wechat/refund-notify。后端从已配置的 WECHAT_PAY_NOTIFY_URL 派生同一 HTTPS 域名,无需新增环境变量。两个回调路径都需由公网 /api 路由转发给 Go。
订单与到账
- 服务端从会话确定组织和发起人,校验整数金额及范围。每次充值尝试带
requestKey,重试返回同一订单;更换金额应创建新尝试。 - 默认支付有效期 15 分钟。二维码在浏览器本地生成,不向第三方二维码服务传递支付链接。
- 回调通过官方 SDK 验签、检查时间戳并解密,然后核对商户、AppID、订单、币种、金额和支付状态。
- 支付成功状态、交易号和钱包账本在同一 PostgreSQL 事务提交。重复回调、页面查单和后台补单并发到达仍只入账一次。
- 每 30 秒后台扫描待支付订单,结合微信查单补齐漏通知。超时订单查询后向微信关单;回调晚到仍可确认有效付款。网络失败保留待确认状态继续重试。
- 钱包流水的充值类型为
recharge,发起人在支付订单和账本 metadata 里记录。财务可使用本地订单号及微信交易号与微信商户平台核对。
原路退款与余额
只有超级管理员可在「计费 → 微信充值」查看跨组织订单并发起退款;组织管理员和普通成员不能发起或读取后台退款接口。订单列表可按组织筛选,展示已退款、退款中、可退金额。仅已支付且属于当前商户号的订单可退,单次可退一部分或整笔剩余可退金额,累计不得超过原支付金额。退款原因必填,去除首尾空白后最多 80 个 UTF-8 字节。
创建退款时,系统先为该笔订单保留可退额度,并以 requestKey 保证重复提交仍指向同一笔退款。微信受理或退款处理中不会立即扣除组织余额;只有经微信退款查询或签名通知确认成功,才在同一数据库事务中写入成功状态并从组织钱包扣款。该笔扣款允许钱包变为负数,避免已消费充值金额阻止真实原路退款;后续计费仍按原余额规则处理。退款账本流水使用独立的 recharge_refund 类型,不与生成任务失败返还的退款流水混用。
退款状态包括 pending、processing、success、closed、abnormal、rejected。微信明确拒绝申请时,记录 rejected 和可读原因,释放可退额度;网络超时、限流或服务端错误仍保留待确认,沿用同一微信商户退款单号查询及重试。已关闭退款也会释放预留额度,可重新申请;异常退款继续占用额度,需核对微信商户平台及后台查询结果,避免重复退款。后台定期查询处理中订单,修复遗漏回调。退款通知无需用户登录,但必须经过微信官方签名验证、解密,以及商户号、交易号、金额和订单核对。关闭充值开关后仍可查看历史订单和退款记录,不能新建退款;待处理退款的通知和对账需保持微信支付配置开启。
API
| 接口 | 用途 |
|---|---|
GET /api/payments/options |
登录后读取开启状态、权限、固定金额和上下限 |
GET /api/payments/orders |
组织管理员读取本组织最近 100 个订单 |
POST /api/payments/orders |
{ "amountFen": 10000, "requestKey": "客户端生成的UUID" } 创建或重试订单 |
GET /api/payments/orders/{id} |
本组织订单状态;适时向微信补查 |
POST /api/payments/wechat/notify |
微信签名通知,成功持久化后返回 204;不依赖用户 Cookie |
GET /api/payments/admin/orders?organizationId=&limit=50&offset=0 |
超管分页查看全部组织订单;组织筛选可选,包含可退与已退金额 |
GET /api/payments/admin/orders/{id}/refunds |
超管查看指定订单的退款记录 |
POST /api/payments/admin/orders/{id}/refunds |
超管发起部分或剩余金额退款;请求体 { "amountFen": 5000, "requestKey": "客户端生成的UUID", "reason": "客户申请" } |
GET /api/payments/admin/refunds/{id} |
超管查询单笔退款状态 |
POST /api/payments/wechat/refund-notify |
微信退款签名通知;不依赖用户 Cookie |
上线联调
在测试商户或受控正式环境中先核对配置和回调可达性,再由业务负责人发起一笔小额支付,确认网页成功状态、组织余额、唯一充值流水及微信商户平台交易号一致。必要时可暂时把最低金额设为 1 分,并同时调整固定档位。检查重复回调不重复加钱、关闭网页后仍自动到账、刷新后能恢复待支付订单以及二维码失效后可新建订单。退款需另行用受控小额订单核对微信成功状态、退款通知、组织余额只扣一次和 recharge_refund 流水;切勿为测试而对真实客户订单发起退款。
此版本提供收款充值、查询、关单、自动补单和超管原路退款;尚无普通用户自助退款或微信账单下载。正式收款前仍应确定退款审批与财务核对流程。若绕过平台直接在微信商户平台退款,平台余额不会自动同步,需要人工核对并处理账本。现有生成任务失败退回平台余额的逻辑继续独立运行。
真实商户权限、证书和线上微信回调需要部署后联调,本地模拟测试不能确认这些外部条件。