Stripe 收款告警:钱有风险时让手机响起来

把 Stripe 的 Webhook 按事件轻重分级送到手机:收款成功安静推送,争议交易直接来电——因为那个有回应期限。

更新于

目录

Stripe 什么都发邮件,也就等于什么都没通知你。一笔成功扣款、一次订阅续费失败、一个拒付争议,最后都变成同一个收件箱里长得一样的消息;等你翻到的时候,它们之间的区别已经没意义了。

解决办法不是发更多通知,而是让每类 Stripe 事件带上它真正该有的紧急程度:有人付款时安静推一条,续费失败时用时效性通知,而争议交易——那个自带回应期限的——直接打电话给你。

哪些 Stripe 事件真的需要你

不是每个事件都值得通知,值得通知的那些也不一样急。

Stripe 事件为什么重要紧急级别
payment_intent.succeeded有人付款了。知道就好,从来不急。普通
invoice.payment_failed续费失败。在订阅真正失效前还有一个重试窗口。时效性
charge.dispute.created拒付争议。Stripe 会给出一个由卡组织设定的回应期限,错过就等于默认败诉。来电
payment_intent.payment_failed一次性支付失败。量大时有价值,单看一条很吵。普通,或者干脆不看

值得一通电话的只有争议交易。其余的都可以等你自己拿起手机再说;而一个拖了一周才发现的拒付,那笔钱本来就留不住了。

第一步 —— 按紧急级别各建一个频道

在 Echobell 里建三个频道,因为紧急级别是按频道设置的:

  • Stripe · 收入 —— 普通通知,用于收款成功
  • Stripe · 扣款失败 —— 时效性通知
  • Stripe · 争议交易 —— 来电

分别从频道设置里复制 webhook URL,等下 Stripe 三个都要用。

顺手把每个频道的 仅 POST 打开。Stripe 一定是用 POST 发送的,开启后能避免链接预览和地址栏自动补全把频道误触发。

第二步 —— 在 Stripe 里添加端点

进入 Stripe 后台,Developers → Webhooks → Add endpoint,粘贴频道 URL,然后只勾选这个频道该处理的事件。

建议一个频道对应一个端点,而不是所有事件塞进一个端点。Stripe 允许按端点选择事件,等于把路由放在了 Stripe 那边,你的频道可以保持简单。

如果你更想只用一个端点,那就指向一个频道,然后用条件过滤:

type == "charge.dispute.created"

条件不需要写模板里那对 {{ }} 包裹。

第三步 —— 用模板把 payload 变成人话

Stripe 发过来的 JSON 大致长这样:

{
  "type": "charge.dispute.created",
  "data": {
    "object": {
      "amount": 4900,
      "currency": "usd",
      "reason": "fraudulent",
      "status": "warning_needs_response"
    }
  }
}

所以频道模板要伸进 data.object 里取值:

标题

争议交易:{{data.object.reason}}

正文

{{data.object.amount}} {{data.object.currency}} 被发起争议
状态:{{data.object.status}}

有个坑几乎人人都踩: Stripe 的金额单位是该货币的最小单位。4900 是 49.00 美元,不是 4900 美元。Echobell 模板会原样代入这个值,所以要么在发送前先做除法,要么把货币写在后面、心里明白它是「分」。如果这个数字必须一眼就对,就把除法放在 Stripe 和 Echobell 之间的那一层里——一个 Worker、一个自动化流程,或者你自己的接口。

收入频道用同样的思路,字更少:

标题

+{{data.object.amount}} {{data.object.currency}}

正文

{{data.object.description}}

第四步 —— 不用等真实付款就能测

Stripe CLI 可以把真实结构的事件打到你的端点上:

stripe trigger payment_intent.succeeded
stripe trigger charge.dispute.created

上线依赖之前先跑一遍。这也是最快能看清「某个你以为一定存在的字段是 null 时模板会渲染成什么样」的方式。

这套方案没解决的问题

在把它放到收入链路上之前,有两个限制需要知道。

Echobell 不校验 Stripe 的签名。 Stripe 会给每个 webhook 带上 Stripe-Signature 头,校验它需要端点密钥,而 Echobell 的 webhook 接口并不做这件事——也就是说,保护这个频道的只有 URL 本身。请把它当作密钥对待:保持仅 POST 开启,不要粘进公开仓库或者截图里。

你可以用条件加一层很弱的检查:

header["stripe-signature"] != ""

它能确认「有个东西发来了长得像 Stripe 的请求」,但确认不了「这就是 Stripe」。如果一条伪造的通知会造成真实损失(而不只是让你困惑一下),那就在前面放一个你自己的接口,在那里校验签名,再由它去调 Echobell。

这是告警链路,不是账本。 Stripe 会重试失败的 webhook 投递,但通知本身不构成发生过什么的记录。对账请以 Stripe 为准,不要拿通知历史当依据。

常见问题

Stripe 能直接打电话给我吗?

不能。Stripe 只发邮件和 webhook。要变成一通电话,得把 webhook 转给一个能打电话的服务。

争议交易的告警真能把我叫醒吗?

如果频道设成来电模式,能。它会以来电形式抵达,穿透专注模式和勿扰。详见让关键告警穿透 iOS 专注模式

合伙人能收到同样的告警吗?

可以。把频道分享出去,每个订阅者自己选通知类型。你可以负责接电话,同一个事件他那边只是一条安静的推送。

测试模式怎么办?

Stripe 测试模式的事件只会发给在测试模式下注册的端点。想看到测试流量就在两种模式下都注册频道 URL;也可以先只在测试模式下注册,等你信任这套设置了再切。

小结

三个频道、三个端点、每个一份模板。重点不是「你能收到 Stripe 的通知」——你本来就能。重点是一笔拒付不再和一张收据长得一模一样。

下载 iPhone 版 Echobell在 Google Play 获取,然后执行一次 stripe trigger charge.dispute.created,故意让手机响一回。


相关内容