跳到正文

账单关了,事件却不是 invoice.paid

2026年9月15日 2 分钟读完 Claude C. Claude C.
三摞薄片对着一条橙色虚线量高低,第一摞差一截,第二摞齐平,第三摞高出去一块——Paymos 账单多付少付插图

要点速览

买家多付了,超出的部分跟着一起入账,账单关成已付,发出来的事件却是 invoice.paid_over。 代码里把事件类型跟单个字符串比,这笔钱就漏掉了。少付先撞项目容差:上限 2%,每格 0.1 个百分点,新建项目停在 0.1%。过了这条线,允许多笔付款的账单为剩下的金额继续挂 着,只收一笔的直接关成少付。还有一种钱到了地址却不付账单,它连事件都没有,也不 是少付。

一张 50 USDT 的账单,到账 49.6。没人操作失误,转账已确认,链上也没有可以申诉的撤销。后面的事在买家打开收银台之前就定好了:多出来的部分照样入账,账单关成已付;少掉的部分有两种结局,项目设置替你挑了哪一种。两种都不稀奇。交易所把自己的提币手续费从转账金额里扣走,报价带着小数尾巴,买家凑单的时候还会自己抹掉几分。

这篇讲金额这一侧。怎么开一张账单在账单全流程,带全部流转的状态机在支付流程。下面只回答三件事:钱进来多少,订单该不该发,你的服务器听见了什么。

多出来的那部分去了哪

一分不扣,全额进余额。

到账多少就入账多少,多的那截跟着一起进来,账单关成已付。服务费按实际收到的金额算,不按你开价的金额算,所以多出来的那截也照常带上这道费率。费率本身在价格页

分岔在事件名上。金额正好停在账单要的那个数,报 invoice.paid。越过去了,报 invoice.paid_over,事件目录给它的说明是「支付超过应付金额;实收全额入账」。你的系统拿到的全部区别,就是这个名字。

退不退是另一件事,而且要你自己发一笔转账。没有东西会自己往回走。这更像生意上的判断:几分钱的尾巴不值得一笔交易,多打了一个零的买家会比你先找上门。

账单已付,订单为什么还挂着

因为处理器只认一个字符串。

事件是到了的。webhook 订阅按类目走,类目里的单个名字不能单独勾选,所以订了账单类目的端点,八个事件全收,invoice.paid_over 就在里面。它到了,处理器拿类型跟 invoice.paid 一比,对不上,扔进不关心的那一堆。

这种失败天生不出声。控制台里账单是已付,余额上钱是到的,订单挂着,哪儿都没报错。它要过几天才浮上来,形式是买家问货在哪——而且专挑那批付多了的买家。

所以判断条件写成集合,不写成等号:

// 两个事件都表示账单已经关了。区别只在有没有多出来的钱。
$type = $event['event_type'];

if ($type === 'invoice.paid' || $type === 'invoice.paid_over') {
    fulfil($event['data']['invoice_id']);
}

然后把两个数字在自己库里分开存。应付是一个数,实收是另一个数。账单上这两个字段叫 payment.expectedpayment.paid,旁边还有 payment.remaining,是买家还差多少,它不会变成负数,所以多付的那截要自己拿实收减应付。

少多少还算付清

项目上有条线。付款没够到它,也不会有人来问你。

这个开关在控制台里叫少付容差。上限 2%,滑块一格 0.1 个百分点,今天新建的项目就停在第一格,0.1%。拉到 0 是严格模式,产品自己的说法是「只有足额或超额支付才会确认」。单张账单改不动它。创建账单的请求里没有容差字段,项目写多少,它下面每张账单就拿多少。

容差之内,账单照常完成,报 invoice.paid,和任何一张付清的账单没有区别。你结算到账的那个数,差的部分没有人补上。

有一处细节决定了事后还能不能补救。判一张账单用的是它创建那一刻项目上的容差,这个值当场抄进账单,跟它一辈子。滑块往哪边拉都只影响你接下来开的账单,动不了此刻还挂在收银台上的那些。

买家为什么老是少付那么一点

最常被怀疑的是网络费,而它恰好不是。以太坊上的 gas 用 ETH 付,不用代币付,这笔钱从发送方钱包自己的币里出。代币金额是整数过去的。买家从自己控制的钱包发 50 USDT,到的就是 50 USDT。

缺口几乎都来自交易所账户。Binance.US 在自己的帮助中心里把这件事写得很直白:网络费和平台手续费都「are deducted from your withdrawal amount before it reaches your destination wallet」。买家照着账单金额填,交易所从这个数里减掉自己的手续费再发,你的账单就正好短了那一笔手续费。

容差按百分比设,缺口却不照百分比来,错位就在这儿。固定金额的扣款在小账单上咬掉一大口,在大账单上几乎看不见,没有哪个百分比能同时罩住两头。0.1% 对一张 50 单位的账单是 0.05 个单位,小数尾巴的量级,离交易所手续费还差得远。容差照着尾巴设就够。交易所那种情况要当成另一件事办,在收银台或者订单确认信里,提前跟买家讲清楚。

少付之后,账单有两条路

allow_multiple_payments 挑走其中一条。它在创建账单时定死,API 默认给 true

允许多笔付款时,一笔少付什么都关不掉。账单转成 underpaid_waiting,为剩下的金额继续挂着,收银台替买家把减法做完。二维码和每条钱包链接都按还欠的金额重画,这个数由服务端算好发下来,浏览器只负责挑显示哪一个。Telegram 那张付款卡片上,复制金额的按钮会改名成复制剩余金额,复制走的是差额。

关掉多笔付款,一次少付就是终点。账单关成 underpaid,报 invoice.underpaid

还挂着的那些由时钟收尾。付款窗口关闭时账单还没够到那条线,它按少付落定。已经检测到、正在确认的转账会直接挡住过期,账单不能从正在路上的钱底下抽走。

少付的账单照样给你钱

给,而且这是对账时最别扭的一段。

每笔转账确认了就入账,服务费按这笔转账自己的金额收。所以账单还开着,钱已经在余额上了。最后关成 underpaid 的账单,会留给你一笔真钱,对着一张始终没关掉的订单。

事件流比钱安静。invoice.underpaid_waiting 在进入这个状态时发一次。第二笔仍然不够的转账会再动一次余额,却不再产生事件,因为状态没变,而 webhook 是按状态变化入队的。拿事件流搭出来的客服界面,会把两次到账显示成一次。要看累计,去读账单。

剩下的是没人能替你做的决定:照发、发一部分、找买家补差额,还是把钱退回去。退回去和多付那种情况一样,是从你自己余额发出的一笔普通转账。

钱进了余额,却不算付款

账单会收不够。还有一种钱,根本不是冲着某张账单来的。

余额页把它单列成一块,叫账单之外的入账,每条带着原因、金额和服务费。原因一共五种,常碰上的是这几条:钱到得太晚,账单早就关了,代币不是这张账单要的那一种,或者那个地址后面压根没挂账单。形状不一样,结果一样,都是扣掉正常服务费后计入余额,账单原样不动,不标已付,也不标少付。

它也没有事件。不是换了个事件名,是一条都没有,这类入账在 webhook 流里没有任何东西承载。少付有状态可读,有事件可订阅。这一类只有余额页上的一行、一封邮件、控制台铃铛里的一条,以及绑定了联系人时的一条 Telegram 消息。服务器那边听不到。

差别决定了账对不上的时候该往哪儿看。少付是一张要追的订单。账单之外的入账是一笔没有订单在等的钱,只有余额会告诉你它来过。

上线之前,先在沙盒里把结局跑一遍

沙盒有付款模拟器,它发出的生命周期 webhook 和真实付款完全一样。一条签名请求,就能把一张已确认的沙盒账单推到某个结局:

POST /v1/sandbox/invoices/inv_7Qk2.../simulate-payment
Content-Type: application/json

{ "stage": "overpaid" }

overpaid 按应付多付 10%,underpay 只付 40%,每个数字都由服务端从账单推出来,客户端一个金额都不发。几次调用是累加的,所以连着两次 underpay 是把 80% 打在同一张账单上,它仍然不够。

第一笔真付款之前,有三趟值得跑:

  1. 对一张履约流程正盯着的账单跑 overpaid。订单要发得出去,你自己的记录里实收要高于应付。
  2. 对一张允许多笔付款的账单跑 underpay。订单不能发,客服看的那个界面要显示还差多少,而不是原来的总额。
  3. 对一张创建时把 allow_multiple_payments 设成 false 的账单跑 underpay。订单不能发,账单要关掉。

容差那条分支这套办法测不到。没有哪个阶段能模拟「只差一点点」的付款,它只能去项目设置里读。别的结局,一条签名请求跑一个。

到账金额落在哪一档,账单就怎么关(2026年9月)
到账金额账单怎么处理你收到的事件
超过账单金额关成已付,多的部分一起入账invoice.paid_over
少的部分在项目容差之内关成已付invoice.paid
少付,这张账单允许多笔付款为剩下的金额继续挂着invoice.underpaid_waiting
少付,这张账单只收一笔关成少付invoice.underpaid
付款窗口关闭时仍然不够关成少付invoice.underpaid
地址对,后面却没有在等的账单一动不动没有

常见问题

买家多付了,钱会退回去吗?

不会自动退。到账多少就入账多少,多出来的一起进余额,账单关成已付。要退回 去,那是从你自己余额发出的一笔普通转账。

invoice.paid_over 和 invoice.paid 是两个事件吗?

是,集成最常栽在这一处。订了账单类目的端点两个都会收到,但处理器如果拿类型 去跟 invoice.paid 这个字符串比对,多付的那笔就被跳过,订单一直挂着。

少付容差是什么,在哪里设?

一笔付款可以比账单少多少还算付清。设置在项目上,0% 到 2%,每格 0.1 个百分 点,新建项目从 0.1% 开始。设成 0 是严格模式,付款必须等于或者大于账单金额。

同一张账单能付第二次吗?

创建时 allow_multiple_payments 为 true 就可以,API 默认就是 true。一笔少付 会把账单留在 underpaid_waiting,后续转账继续算在同一张账单上,直到付款窗口 关闭。

少付的账单,钱会进余额吗?

会。每笔转账确认了就入账,服务费按这笔转账自己的金额收。所以一张少付的账单 会让你手上握着真钱,对着一张没关掉的订单。

账单过期之后才到的钱去哪了?

不付这张账单。它扣掉正常服务费后计入余额,账单原样不动,而且不发 webhook。 能看见它的地方是余额页、邮件和控制台铃铛。

什么时候不该用少付容差

  • 如果每张账单都来自买家自己的钱包、用的就是你报价的那种代币,金额会整数到账, 任何大于 0 的容差都是一道常年开着的折扣。设成 0,严格对。
  • 如果一张订单的毛利比你正在考虑的容差还薄,那个滑块就是给每个凑整往下抹的买家 的减价。把尾巴吃在看得见的地方。
  • 如果你反复看到的缺口是交易所那笔固定手续费,百分比盖不匀:大账单盖住了,小账 单还漏着。那件事要跟买家谈,不是拉滑块。
  • 如果卖的是不可分割的一件东西,一份授权、一个席位、一张票,让账单为第二笔付款 继续挂着,多半只会造出一批半付的订单交给客服去追。这类账单创建时把多笔付款关 掉。

参考来源

  1. 1. Paymos 文档 — 支付流程 (accessed 2026-09-15)
  2. 2. Paymos 文档 — 获取账单 (accessed 2026-09-15)
  3. 3. Paymos 文档 — 模拟账单付款 (accessed 2026-09-15)
  4. 4. ethereum.org — Gas and fees (accessed 2026-09-15)
  5. 5. Binance.US Help Center — Understanding network fees vs. exchange fees (accessed 2026-09-15)

最近复核:2026年9月15日

#多付#少付#加密货币账单#对账#webhook
分享