Skip to content

本地联调

更新时间:2026/8/23 17:04:45

支付系统与普通 Web 应用最大的不同:第三方支付网关要主动回调你的服务,且微信授权域名、JSAPI 安全域名、支付宝应用网关等都要求外网可达且已备案的固定地址。本地开发时机器没有公网 IP,回调进不来、手机也访问不到你本机的 H5,这就需要一套联调方案。

一键部署用户可跳过

一键部署 的服务器本身有公网地址,不存在本节问题。本页面向源码本地开发场景。

方案选型

形态前提手机访问服务端回调适用场景
局域网域名直连已备案域名 + 手机与电脑同一局域网手机侧前端链路:H5 收银页、微信内支付、网页授权
直连 + 回调穿透(推荐)再加一台公网服务器跑 frp日常联调:前端流量走局域网,只穿透回调
全量穿透公网服务器 + 已备案域名测试者不在局域网(异地演示),见附录

拆分依据是流量由谁发起:微信 JSAPI / 支付宝手机网站支付在手机侧的页面加载、下单接口、JSSDK 拉起支付、支付完成跳回,全部从手机发出——这部分走局域网直连即可;只有支付结果的异步回调来自微信/支付宝的服务器(公网发起),需要公网可达。

回调地址与前端域名是解耦的:异步通知 URL 是下单时传给通道的参数(来自通道配置),微信/支付宝服务器不关心手机上打开的支付页是哪个域名。所以推荐形态是「前端走局域网、只穿透回调」——99% 的流量留在局域网(手机快、穿透零压力),穿透通道上只有偶发的回调 POST。

方案一:局域网域名直连

原理

把已备案域名的子域名 A 记录直接解析到电脑的内网 IP。DNSPod 等服务商允许发布私网地址(192.168.x.x)解析,手机查到该记录后流量直达局域网内的电脑:

手机(同一 WiFi) → DNS 查询 local.daxpay.example.com = 192.168.1.100
              → 直达电脑 nginx(443) → 本地 dev server

手机不需要改 hosts、不需要装任何工具,微信里也能直接打开。

准备步骤

  1. 固定电脑内网 IP:路由器后台把电脑网卡绑定为静态 DHCP(如 192.168.1.100),避免 IP 漂移后解析失效;
  2. 添加 DNS 解析:DNS 服务商后台为已备案域名加一条子域名 A 记录指向该内网 IP(如 local.daxpay.example.com A 192.168.1.100);
  3. 签发 HTTPS 证书:微信/支付宝 H5 链路要求 https。用 acme.sh / certbot 的 DNS-01 验证签发 Let's Encrypt 证书——DNS-01 只需临时加一条 TXT 记录验证域名所有权,不要求域名从公网可达,天然适合解析到内网的场景:
bash
# acme.sh 示例(DNSPod;密钥在后台获取,export 一次即可)
acme.sh --issue --dns dns_dp -d local.daxpay.example.com --keylength ec-256

# 安装到 nginx 并挂自动续期(90 天到期自动续 + reload)
acme.sh --install-cert -d local.daxpay.example.com --ecc \
  --key-file       C:/Server/nginx/conf/ssl/local.daxpay.example.com-key.pem \
  --fullchain-file C:/Server/nginx/conf/ssl/local.daxpay.example.com.pem \
  --reloadcmd      "C:/Server/nginx/nginx.exe -s reload -p C:/Server/nginx"
  1. 放行防火墙:Windows 防火墙放行 443 入站:
powershell
netsh advfirewall firewall add rule name="nginx-https" dir=in action=allow protocol=tcp localport=443
  1. 手机连同一 WiFi,浏览器访问 https://local.daxpay.example.com 能打开即链路打通。

Nginx 配置

在电脑的 nginx 中新增 server 块。注意 client_max_body_size 与超时两项容易漏配(默认 1M / 60s,上传图片 413、长连接被掐):

nginx
http {
    # 全局补两条(已有配置常缺)
    client_max_body_size 50m;       # 默认 1M,管理端上传图片会被 413 拦截

    # 80 一律跳 https(微信里粘贴 http 链接也能正确进入)
    server {
        listen      80;
        server_name local.daxpay.example.com;
        return 301 https://$host$request_uri;
    }

    server {
        listen      443 ssl;
        http2       on;
        server_name local.daxpay.example.com;   # DNS 解析到内网 IP

        ssl_certificate     ssl/local.daxpay.example.com.pem;      # Let's Encrypt(DNS-01)
        ssl_certificate_key ssl/local.daxpay.example.com-key.pem;
        ssl_protocols       TLSv1.2 TLSv1.3;

        # 全部流量交给 H5 dev server,/api 由 Vite 的 proxy 转发到主应用
        location / {
            proxy_pass http://127.0.0.1:9500;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
            proxy_read_timeout 300s;      # 默认 60s,长轮询/SSE 场景防断

            # Vite HMR(WebSocket)
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
}

为什么不单独分流 /api

H5 的 dev server 已内置本地代理(dax-pay-h5/.env.developmentVITE_PROXY,把 /api 转发到 127.0.0.1:9999),nginx 把全部流量交给 9500 即可,/api 会被 Vite 转到主应用。需要绕开 Vite 直连后端时,再加 location /api/ { proxy_pass http://127.0.0.1:9999/; }

微信域名校验文件须在公网解析期间完成

公众号后台配置 JS 安全域名/网页授权域名时,微信服务器会从公网抓取域名根路径的 MP_verify_xxxx.txt 做归属校验——域名解析到内网 IP 时微信访问不到,校验永远通不过。正确时序:先把该子域名的 A 记录指向任一公网可达的服务器(如在已有公网服务器 nginx 上为它配一段静态文件路径,放好校验文件),完成微信后台校验;域名登记进白名单后,再把 DNS 切到内网 IP,已通过的校验不因后续解析切换而失效,联调期间公网侧也不再需要提供该文件。

劫持生产支付链接域名(进阶)

线上生成的支付链接指向生产的跳转域名(如 redirect.example.cn/cashier/xxx)。把该域名同样解析到内网 IP 并签发证书,nginx 反代到本地 H5 dev server,手机上打开的就是生产环境生成的支付链接、落到的却是本地代码——排查「线上支付链接打不开/白屏」类问题最有效的手段。配置与上面完全相同,只是 server_name 与证书换成生产跳转域名。

回调边界

局域网直连收不到微信/支付宝服务器的异步回调——它们从公网发起请求,解析出内网 IP 后无法连接。只调前端链路时无妨:支付结果靠订单查询兜底,H5 支付页轮询查单即可正常拿到结果。需要验证「回调是否到达、验签是否通过」时,叠加回调穿透即可,两者可随时叠加或拆除。

方案二:回调穿透(只穿透回调)

frp 只穿透主应用一个端口,本机 nginx 与前端 dev server 完全不出公网:

toml
# 本机 frpc —— 只穿主应用一条隧道
[[proxies]]
name = "daxpay-callback"
type = "tcp"
localIP = "127.0.0.1"
localPort = 9999            # 直连主应用, 不经过本机 nginx
remotePort = 18080          # 公网服务器上的端口

公网服务器 nginx 只做回调域名的 HTTPS 终结(微信 v3 回调强制 https,建议 443 标准端口):

nginx
server {
    listen      443 ssl;
    server_name cb.daxpay.example.com;
    ssl_certificate     ssl/cb.daxpay.example.com.pem;
    ssl_certificate_key ssl/cb.daxpay.example.com-key.pem;

    location /api/ {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_pass http://127.0.0.1:18080/;   # frps remotePort, 结尾斜杠剥掉 /api
    }
}

想进一步收窄穿透面,公网 nginx 的 location 可只放行通道回调路径前缀(如 /api/unipay/callback/),其余一律 404。

通配符证书一签两用

DNS-01 签一张 *.daxpay.example.com 通配符证书,本机 nginx(方案一的 local.daxpay.example.com)与公网 nginx(cb.daxpay.example.com)部署同一张,续期一次两处生效。

隧道断了回调会丢

微信/支付宝回调都有衰减重试(几分钟内多次),frp 隧道断掉期间刚好有支付,该笔回调会延迟或丢失。兜底:H5 页轮询查单拿结果、管理端「交易管理 → 通道回调记录」查看到达情况,必要时补单。只调前端不验回调时,frpc 停掉也完全不影响方案一。

配置归属

组合形态下唯一的易错点是「哪个配置点填哪个域名」。规则一句话:凡是手机会打开的都填局域网域名,只有通道回调地址填公网域名

配置点位置填写
支付网关前端地址(生成支付链接用)管理端「平台配置 → 端点配置」https://local.daxpay.example.com(局域网域名)
微信授权域名 / JSAPI 支付目录微信公众平台/商户后台局域网域名(要求已备案)
通道回调/通知地址管理端各通道配置https://cb.daxpay.example.com/api/...(公网域名,以通道配置页提示为准)
Web 管理端/H5 接口地址(生产构建时)dax-pay-ui/apps/*/env/.env.productiondax-pay-h5/.env.production局域网域名,统一 /api 前缀

只用方案一(未搭穿透)时,回调地址一栏按公网域名填好也没关系——回调到不了属预期行为,查单兜底,将来搭好穿透即自动生效。

开发期在本机浏览器直接访问 127.0.0.1:6999 调试时,前端 dev server 的本地代理已把 /api 转发到本机主应用,无需任何域名;只有手机访问、第三方回调、微信内支付这三类场景才必须走联调域名。

附录:全量穿透(异地测试)

测试者不在局域网时(如给异地同事演示),把本机 nginx 入口整体穿透出去,单域名按路径分发给各端。frp 客户端:

toml
serverAddr = "1.2.3.4"       # 你的公网服务器(frp 服务端)
serverPort = 7000
auth.method = 'token'
auth.token = 'your-token'

[[proxies]]
name = "daxpay-dev"
type = "tcp"
localIP = "127.0.0.1"
localPort = 18080            # 本机 nginx 监听端口(见下节)
remotePort = 18080           # 公网服务器暴露端口

公网侧再由服务器上的 nginx(或 frp 的 http 类型代理)把 dev.daxpay.example.com 的流量转发到该端口。本机 nginx 按路径分发(与 项目构建 - 静态部署 的转发规则一致,proxy_pass 结尾斜杠剥掉 /api 前缀):

nginx
server {
    listen 18080;
    server_name dev.daxpay.example.com;
    client_max_body_size 50m;

    # 支付网关接口 + 通道回调
    location /api/ {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://127.0.0.1:9999/;
    }

    # H5 端(手机调试用)
    location /h5/ {
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:9500/;
    }

    # Web 运营端
    location / {
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://127.0.0.1:6999/;
    }
}

H5 base 路径

本项目 H5 的 VITE_PUBLIC_PATH 默认为 /(根路径部署),按上面 /h5/ 子路径分发会导致资源 404。优先给 H5 单独分配一个子域名(即方案一的做法);坚持子路径方式则需同步调整 H5 的 base 路径。

ngrok / cpolar 开箱即用,一行命令把本机 18080 映射成临时域名,但临时域名每次变化,不适合填到微信/支付宝的域名白名单里,只适合临时演示。

常见问题

手机打不开域名? 依次排查:手机与电脑是否同一 WiFi、DNS 是否生效(微信内可能有 DNS 缓存,开关一次飞行模式再试)、Windows 防火墙是否放行 443、电脑上 curl -k https://127.0.0.1 是否正常。电脑 IP 变化(DHCP 续租)也会导致解析失效,回到准备步骤 1 绑定静态 IP。

回调收不到? 先看用的哪种形态:纯局域网直连收不到回调是预期行为,见上文「回调边界」;搭了回调穿透则依次排查 frp 隧道是否在线(frpc 日志)、公网域名外网是否可达(手机关 WiFi 用流量访问测试)、公网 nginx /api/ 转发是否生效、回调地址是否与通道配置一致。主应用侧可在管理端「交易管理 → 通道回调记录」中查看回调是否到达。

上传文件报 413? nginx 默认 client_max_body_size 仅 1M,在 http 或对应 server 块加大(如 50m)。注意本机与公网两侧 nginx 都要检查。

微信内 H5 白屏或授权失败? 多为授权域名未配置或域名不一致导致,确认公众平台后台配置的域名与实际访问域名完全一致(含子域名);微信授权域名要求已备案域名,临时域名/ngrok 域名过不了审。

证书过期? Let's Encrypt 证书 90 天有效,用 acme.sh --install-cert ... --reloadcmd "nginx -s reload" 挂自动续期,避免联调到一半突然失效。

基于 GNU LGPL v3.0 协议开源