订单卡在 PENDING、跳到 CANCELLED、或者用户在 Cryptomus 支付页付款成功后余额没到——这一页给出按顺序的真实诊断流程。所有结论都来自 monkeytheme.com 2026-08-08 的生产 webhook 日志和 payment_orders 表结构。
如果一笔 Cryptomus USDT 充值看起来卡住,按下面四步走完再开工单。每一步都有可以直接验证的结论。
登录后打开 /dashboard/orders,找到订单 id。如果这一行根本不存在,说明创建订单的请求根本没到后端——通常是因为用户还没登录就点了充值,或者同一个 key 在多浏览器里被复用。
如果行存在,看 status、paid_at 和 expires_at:
| status | 通常含义 | 下一步 |
|---|---|---|
| PENDING | 用户打开了 pay_url 但还没完成链上转账,或者 webhook 还没到。 | 等 30 分钟内复查。Cryptomus 会重试 webhook。 |
| COMPLETED | webhook 验证通过,余额已到账,订单关闭。 | 不用动。如果余额没动,把订单 id 发工单。 |
| CANCELLED | 用户没付款就关了支付页,或者订单超时。 | 重新创建一笔。老单不可恢复。 |
| EXPIRED | 超过 ORDER_TIMEOUT_MINUTES(默认 30)还没有 webhook。 | 重新创建。如果反复出现,说明上游 webhook 在丢失。 |
| FAILED | webhook 到了,但上游报失败。 | 把订单 id、paid_at 和 Cryptomus invoice id 发工单。 |
monkeytheme.com 的前端 bundle(providerConfig-*.js)里写死的 notifyUrl 只有一条:
cryptomus:"/api/v1/payment/webhook/cryptomus"
其他都是错的。2026-08-08 截取的生产日志里,七天内一共出现过四条 webhook 路径,其中三条是噪声:
| 观察到的路径 | 状态码 | 来源 |
|---|---|---|
| /api/v1/payment/webhook/cryptomus | 真实 webhook 200,探测 400 | 官方前端 |
| /api/v1/payment/cryptomus/webhook | 404 | 探测(路径顺序换位) |
| /api/v1/payment/webhooks/cryptomus | 404 | 探测(多了一个 s) |
| /api/cryptomus/webhook | 404 | 探测(没有 /v1) |
官方路径上的 400 全部是 cryptomus webhook missing sign,并且 bodyLen: 2,说明 body 就是 {}。这不是真实充值,是扫描器或客户端配错了。真实的 Cryptomus webhook body 一定带 JSON sign 字段和订单 payload。
Cryptomus 托管页需要达到指定确认数后才会显示付款成功。在那之前 Cryptomus 不会 POST webhook。如果用户付款前关掉了标签页,订单会一直 PENDING,直到 expires_at 超时。
可以让用户从托管页 URL 里复制 Cryptomus invoice id(形如 pay_url?...&orderid=...,或者跳转后页面上显示的订单 id),到 Cryptomus 商户后台的 Payments 里直接核对。
链上支付成功后,托管页会跳到 /payment/result。前端用 GET /api/v1/payment/orders/my 轮询几秒钟,等 COMPLETED 状态出现。如果用户等不及关掉了那个标签页,余额其实已经被 webhook 加上了,只是 UI 一直显示 PENDING,下次刷新就好了。
处理:刷新订单页就行。不要因为弹窗关得早就说 webhook 坏了。
USDT 充值从 /purchase 到余额到账,在 monkeytheme.com 上实际走的是这五步:
第 3 步是服务端配置错误会让已付款订单悄悄丢失的唯一位置。第 4 步和第 3 步在同一个 webhook handler 里跑,第 3 步返 200,第 4 步就跑完了。如果你看到第 3 步是 400,第 4 步根本没启动,用户当然没收到余额。
下面是一行真实的 monkeytheme.com webhook 日志,来源是 journalctl -u sub2api --since "7 days ago",2026-08-08 截取。
INFO middleware/logger.go:82 http request completed service=sub2api env=production component=http request_id=f55564df-948e-4847-8a17-d72285534880 path=/api/v1/payment/webhook/cryptomus method=POST status_code=400 latency_ms=7 client_ip=65.49.151.58 protocol=HTTP/1.1
同一笔交易、紧挨着的上一行:
ERROR slog logger/slog_handler.go:53 [Payment Webhook] verify failed provider=cryptomus error="cryptomus webhook missing sign" method=POST bodyLen=2
两点结论:
missing sign + bodyLen: 2 表示 body 是 {}。这是探测,不是真实 Cryptomus 发票。payment_orders 更新,而不是 verify-failed 错误。/api/cryptomus/webhook、/api/auth/google、/api/v1/settings/public 等无关端点。如果你看到的日志也是这种形态,那是探测,不是用户。上面四步都跑过之后再开工单。一次回复就能定位的四个证据:
/dashboard/orders 里的数字)。journalctl -u sub2api --since "5 minutes ago" --no-pager | grep -E "orderid=YOUR_ORDER_ID|payment/webhook/cryptomus|verify failed"不要发钱包地址截图,不要发账号密码,不要贴 API key。订单 id + invoice id 就够查到这一行。
/api/v1/payment/webhook/cryptomus。其他路径如 /api/v1/payment/cryptomus/webhook、/api/v1/payment/webhooks/cryptomus(多一个 s)和 /api/cryptomus/webhook 都没有注册路由,全部返回 404,即使生产日志里能看到请求落到这些路径上。
说明 Cryptomus POST 的请求体没有带 sign 字段,或者 body 是空的。2026-08-02 的生产日志里,失败的请求全部是 bodyLen=2(字面意义就是 {})。真实的 Cryptomus webhook 请求体总会带一个 JSON sign 字段,所以空 body 不是真实充值,而是探测、客户端配置错或代理把 body 剥掉了。
不一定是。webhook 路径只是流程的一半。Cryptomus 第一次失败会重试 webhook,商户后台也可以手动重发。另一半是 returnUrl:用户付完款会被跳回 /payment/result,前端会轮询订单状态。如果用户还没等回跳就关掉标签页,订单可能一直 PENDING 直到超时(默认 30 分钟,由 ORDER_TIMEOUT_MINUTES 控制)。
这一页只覆盖付款后:webhook 有没有到、订单有没有进 COMPLETED、余额有没有到账。它不教选钱包、选网络、估 gas。这些选择有各自的判断和风险,上游改得比文档快,直接链到 Cryptomus 文档比抄数字靠谱。