所有文章
Deep dive阅读需 7 分钟

作废购买通知让你的 Google Play 服务器在退款到账的那一刻撤销访问权限

Google Play 可以在一笔购买被退款、发生拒付或被作废的瞬间,向你的服务器推送一条作废购买通知。它携带 purchaseToken、orderId、productType 和 refundType,只意味着一件事,撤销访问权限。下面介绍如何解读它并接入它。

黄昏时分的开发者工作台,一部手机显示一笔已冲回的付款,展示一条 Google Play 作废购买通知抵达服务器

要点

  • 作废购买通知是一条 Real-time Developer Notification,Google Play 会在一笔购买被退款、发生拒付或以其他方式作废的那一刻,将它推送到你的 Cloud Pub/Sub 主题。它是一个推送信号,而不是你必须去轮询的东西。
  • 该通知恰好携带四个字段:`purchaseToken`、`orderId`、`productType` 和 `refundType`。这足以在你的数据库中找到确切的那笔购买,并撤销与之绑定的权益。
  • `productType` 为 `1` 表示作废的订阅,为 `2` 表示作废的一次性购买。`refundType` 为 `1` 表示全额退款,为 `2` 表示基于数量的部分退款,后者仅适用于多数量的一次性购买。
  • 作废购买通知意味着客户已经拿回了他们的钱。Google 自己的指引是撤销对相关内容的访问权限,因为买家不应再持有该权益。
  • 作废购买通知默认是关闭的,直到你把它打开。在 Play Console 的 Monetization setup 中,你可以选择订阅加上所有作废购买,或者在此基础上再加上一次性产品事件。两个选项都包含作废购买。
  • 作废购买通知不是那两个向你索取证据的退款流程之一。它是一则事后通知。唯一接受你输入的流程是 Apple 的 CONSUMPTION_REQUEST,有 12 小时的窗口期,以及 Google Play 通过 orders.reviewrefund 进行的拒付审查,有 24 小时的窗口期。
  • 从 August 3, 2026 起,Google 会把一笔拒付的购买价格加上银行手续费转嫁给开发者。作废购买通知往往是你的服务器最先得知一笔拒付作废已到账的途径,因此把它接入正是让你能够停止为一个你已不再收到付款的客户提供服务的关键。

Google Play 上的退款不必是你稍后在报表中才发现的事情。Google Play 可以在一笔购买被作废的瞬间向你的服务器推送一条作废购买通知,无论是买家自助申请了退款、支持人员批准了退款、银行强制发起了拒付,还是你自己在开启撤销标志的情况下为该订单退了款。这条消息很小,它指明了确切的那笔购买,并携带一条指令,收回权益,因为钱已经离开了。

作废购买通知究竟是什么

作废购买通知是 Google Play 的 Real-time Developer Notifications(即 RTDN)中的一种。RTDN 是一个推送通道。Google 会向一个归你所有的 Cloud Pub/Sub 主题发布一条消息,你的后端会在事件发生后的片刻之内收到它,而不是等到你下一次计划的轮询才发现。这正是作废购买通知相较于较旧的拉取方式的全部意义所在:你在退款发生时就知道,而不是几个小时之后。

拉取方式依然存在,也依然重要。Voided Purchases API 让你的服务器可以按你自己的节奏,索取在某个时间范围内被作废的购买列表。两者是互补的。通知会在一笔购买状态翻转的那一刻告诉你;API 则让你批量对账,并回填任何一条错过的消息本会遗漏的内容。

它搭载在一条 Real-time Developer Notification 内部

作废购买通知从不单独到达。它位于一个 DeveloperNotification 包装器内部,而该包装器作为一个单独的 base64 编码字符串,出现在 Pub/Sub 消息的 data 字段中。你的处理程序要先把该字符串解码为 JSON,才能读取任何内容。该包装器始终指明应用和事件时间,并且恰好包含 Google 定义的五种通知对象中的一种。它们是互斥的,因此一条携带 voidedPurchaseNotification 的消息不会同时携带订阅或一次性事件。

包装器字段它所包含的内容
version通知架构的版本,例如 1.0
packageName事件所属的应用,例如 com.acme.app
eventTimeMillis事件发生的时间,以自 epoch 起的毫秒数表示
五种通知对象之一oneTimeProductNotificationsubscriptionNotificationvoidedPurchaseNotificationpendingRefundReviewNotificationtestNotification。每条消息只出现一个

它携带的四个字段

去掉包装器,作废购买通知本身就是四个字段。这是刻意的精简。Google 的立场是,如果你需要的只是找到正确的那笔购买并调整权益,这四个就够了,你无需再回调任何 API 就能采取行动。

字段它是什么你如何使用它
purchaseToken购买该商品时交给设备的令牌你的主键。将它与你在授予权益时存储的购买记录匹配
orderId向买家显示的订单 id,例如 GS.0000-0000-0000用于支持和对账的可读第二键
productType被作废的商品是订阅还是一次性购买引导至正确的撤销路径
refundType该作废是全额退款还是基于数量的部分退款决定是撤销全部还是仅撤销已退款的数量

productType 告诉你什么被作废了

productType 是一个小整数,它决定采用你的哪一条撤销路径。订阅作废必须解除持续的访问权限;一次性购买作废只是移除单个权益。

`productType` 值常量含义
1PRODUCT_TYPE_SUBSCRIPTION一笔订阅购买被作废
2PRODUCT_TYPE_ONE_TIME一笔一次性购买被作废

refundType 告诉你退回了多少

refundType 区分一次干净的全额冲回和一次部分冲回。部分的情形很狭窄。它只在一笔多数量的一次性购买中,有部分(但非全部)数量被退款时才会出现。

`refundType` 值常量含义
1REFUND_TYPE_FULL_REFUND该购买被完全作废
2REFUND_TYPE_QUANTITY_BASED_PARTIAL_REFUND一笔多数量购买的一部分被作废

先把它打开,否则它永远不会到达

作废购买通知默认不会流转。你在 Play Console 中启用一次 RTDN,并将它指向一个你控制的 Pub/Sub 主题。这个开关位于 Monetize,然后 Monetization setup,在页面顶部的 Real-time developer notifications 部分。勾选 Enable real-time notifications,然后以 projects/{project_id}/topics/{topic_name} 的格式粘贴你完整的主题名称,并在信任它之前使用 Send Test Message 确认这条管道正常工作。

内容切换选项正是人们错过作废购买的地方。两个选项都包含它们,所以你无法在保留订阅的同时不小心排除退款。

  • 接收订阅和所有作废购买的通知。你会收到订阅事件和每一笔作废购买,但不会收到一次性产品购买事件。
  • 接收订阅和一次性产品的所有通知。你会收到上述内容,外加一次性产品事件,例如 ONE_TIME_PRODUCT_PURCHASEDONE_TIME_PRODUCT_CANCELED
一张正面朝下的信用卡放在一张纸质收据旁边,代表一笔作废的 Google Play 购买

它在金钱上让你付出什么代价

作废是一笔在通知到达你时就已经入账的损失。销售价款没了,你为服务那位客户所花的一切也没了。如果他们生成了图像、对你的模型发起了调用、占用了存储,或触发了向第三方的付款,那些成本是用真金白银支付的,不会随退款一起回来。通知无法挽回其中任何一项。它能做的是从此刻起止血,而这正是要迅速对它采取行动的全部理由。

在订阅上和在拒付上出血最严重。一笔你未能撤销的订阅,会让你月复一月地持续为一个不再付款的客户承担服务成本。而拒付是最昂贵的一种作废。从 August 3, 2026 起,Google 的文档指出,一笔拒付会把购买价格加上银行手续费转嫁给开发者。作废购买通知常常是你自己的系统最先得知一笔拒付已完成的地方,因此一个当场撤销的处理程序,正是防止一笔已损失的销售变成一笔已损失的销售外加数周免费服务的关键。

如何逐步处理一条作废购买通知

验证消息并去重

  • 确认这条 Pub/Sub 消息来自 Google 并指向你配置的主题,然后解码 base64 的 data 字段以获取 DeveloperNotification 的 JSON。
  • 使用 Pub/Sub 的 messageId 来丢弃重复项。Google 警告说同一条通知可能被投递不止一次,所以把重复投递当作正常情况,并让你的处理程序具有幂等性。
  • 只有在你已经安全地记录了消息之后才确认它,这样处理程序中途崩溃就不会丢失该事件。

查找该笔购买

  • purchaseToken 与你首次授予权益时存储的购买进行匹配。回退到 orderId 用于支持查询和手动对账。
  • 读取 productType 以选择订阅或一次性的撤销路径,读取 refundType 以在全额撤销和部分撤销之间做出决定。

撤销并记录

  • 移除权益。对于全额退款,切断对该商品的访问。对于基于数量的部分退款,按已退款的数量减少已授予的数量,其余保持不变。
  • 记下你做了什么以及何时做的,以 purchaseTokenorderId 为键。那条记录就是让你日后能回复一张支持工单的依据,也是让 Voided Purchases API 能够干净地与你自己的状态进行对账的依据。

它在其他退款信号中的位置

作废购买通知是一则通知,而不是一场谈判。它告诉你一个已经决定的结果。值得把它与那些容易混淆的信号放在一起看,因为其中只有一部分会在某个时刻征询你这一方的说法。

信号方向它是否接受你的输入
作废购买通知(RTDN)Google 推送到你的服务器否。它报告一次已经发生的作废
Voided Purchases API你的服务器从 Google 拉取否。它是一个只读的过往作废列表
pendingRefundReviewNotification(RTDN)Google 推送到你的服务器是,间接地。它标记一笔拒付,随后你在 24 小时内通过 orders.reviewrefund 进行申诉
Apple CONSUMPTION_REQUESTApple 向你的服务器询问是。你在 12 小时内以 Send Consumption Information 作答

要记住的这条原则很简单。在两家商店中,恰好有两个退款流程让开发者能够发声,Apple 的 CONSUMPTION_REQUEST 和 Google Play 的拒付审查。作废购买通知两者都不是。等它到达你时,决定已成定局,留在你手中的唯一一件事就是你撤销得有多快。

常见问题解答

在 Google Play 上,作废购买通知意味着什么?
它意味着一笔购买被退款、发生拒付或以其他方式被作废,客户已经拿回了他们的钱。Google 的指引是撤销对相关内容的访问权限,因为买家不应再持有该权益。该通知通过其 `purchaseToken` 和 `orderId` 指明确切的那笔购买。
一条 Google Play 作废购买通知包含哪些字段?
四个:`purchaseToken`、`orderId`、`productType` 和 `refundType`。`productType` 为 `1` 表示订阅,为 `2` 表示一次性购买。`refundType` 为 `1` 表示全额退款,为 `2` 表示对一笔多数量购买的基于数量的部分退款。
我如何启用作废购买通知?
在 Play Console 中,打开 Monetize,然后 Monetization setup,并在 Real-time developer notifications 部分勾选 Enable real-time notifications,然后输入你的 Cloud Pub/Sub 主题名称。两个内容选项,订阅加上所有作废购买,以及在此基础上再加上一次性产品事件,都包含作废购买。
作废购买通知与 Voided Purchases API 有什么区别?
通知是一个推送信号,在一笔购买被作废的那一刻通过 Cloud Pub/Sub 实时投递。Voided Purchases API 是一条拉取方式,你的服务器按其自己的节奏查询它,以列出某个时间范围内的作废。用通知来即时反应,用 API 来对账和回填。
作废购买通知能让我申诉退款吗?
不能。它是对一个已经做出的决定的事后通知。唯一接受你输入的 Google Play 流程是通过 `orders.reviewrefund` 进行的拒付审查,你有 24 小时来作答,而在 Apple 上则是带有 12 小时窗口期的 CONSUMPTION_REQUEST。

来源和延伸阅读

RefundHalt

App Store 和 Google Play 退款自动驾驶

继续阅读

下一项退款请求已经在路上。

读完又一封关于未能申辩退款的支持邮件所需的时间,足够您完成 RefundHalt 设置。