为每一笔 Google Play 购买打上混淆账户 id 标记,否则拒付到来时你将无从追溯
Google Play 允许你为每一笔购买盖上一个稳定的哈希 id,并在争议发生时把它读回给你。设置好它,一次拒付审查就能精确对应到你必须上报其使用情况的那个用户。跳过它,你就只能在 24 小时的时限内拿着一个孤零零的订单 id 去猜。

要点
- 混淆账户 id 是你用 setObfuscatedAccountId 附加到 Google Play 购买上的一串字符。Google Play 会把它与订单一起存储,并在之后以 obfuscatedExternalAccountId 的形式返回,因此一笔购买可以追溯回你系统中发起它的那个用户。
- 用 Google 自己的话说,这个字段让 Google Play 能够检测异常活动,例如短时间内同一账户上有许多设备发起购买。设置它会在交易完成之前,在购买那一刻为 Google 自己的欺诈筛查提供数据。
- 该标识符限制为 64 个字符,且不得以明文携带个人信息。Google 表示,在此字段中存储电子邮件等 PII 会导致购买被拦截,并建议改用单向哈希或加密。
- 当一次银行拒付需要你审查时,Google Play 会发送一条 PendingRefundReviewNotification,它指明的是一个订单,而不是一个人。混淆账户 id 就是那把连接键,把该订单映射回你必须上报其使用情况的用户记录。
- 回应一次争议意味着在 24 小时内调用 orders.reviewrefund,携带 refundPreference、一个 sampleContentProvided 标志,以及像 consumptionPercentageMilliunits 和 consumptionUsageEvents 这样的消耗证据。只有先知道订单属于哪个用户,你才能构建这些证据。
- 对于在 2026 年 8 月 3 日或之后下单的 Google Play 订单,一次败诉的拒付会向开发者收取购买价格减去 Google 服务费,再加上银行的拒付手续费。一次因为无法识别订单而无法回应的争议,如今成了一项直接成本,而不仅仅是一笔流失的销售。
- 对每一笔购买都设置该 id,而不只是订阅,并在服务器端把它读回。在客户端它来自 Purchase.getAccountIdentifiers,在你的后端它是购买记录上的 obfuscatedExternalAccountId 字段。
一次 Google Play 拒付审查出现时只报出一个订单和一个购买令牌。它不会告诉你客户是谁。如果你从未把自己的标识符盖到那笔购买上,你现在就得在 24 小时的时限下,拿着一个孤零零的订单 id 去和你的用户表做匹配,还得用你可能根本找不到的使用证据来回应。混淆账户 id 就是解药。它是一串你在结账时附加的短字符,Google Play 会把它与购买一起存储,之后再交还给你,这样每一笔订单都能追溯到发起它的那个确切用户。下面说清楚这个字段是什么、它为何决定了你到底能不能回应一次争议,以及在一次败诉拒付已经变成一张账单的今天,跳过它的代价是什么。
混淆账户 id 究竟是什么
混淆账户 id 是当客户购买东西时,你传入 Google Play 计费流程的一个可选字符串。你在 BillingFlowParams 构建器上用 setObfuscatedAccountId 设置它,Google 会把它与购买一起存储。它不是客户的姓名,不是他们的电子邮件,也不是他们的 Google 账户。它是你自己对你自己用户的标识符,以一种 Google 可以保存却无从得知此人是谁的形式写成。
它是你在结账时设置的一串字符,而不是一个姓名
用 Google 的话说,setObfuscatedAccountId 指定一个可选的混淆字符串,它与你应用中购买者的用户账户唯一关联。“混淆”这个词是有实际作用的。Google 不想要你的原始用户 id,也不想要任何能识别此人的东西。它想要的是一个稳定的令牌,与你这边的一个用户一一对应,仅此而已。该字段限制为 64 个字符,足够容纳一个哈希,装不下太多别的东西。
Google 首先为它自己的欺诈筛查读取它
在对你有用之前,这个字段先为 Google 做一件事。计费文档说,Google Play 可以使用这个值来检测异常活动,例如短时间内同一账户上有许多设备发起购买,并且 Google 使用这些数据来检测可疑行为,并在某些类型的欺诈交易完成之前将其拦截。所以设置它的第一份回报在上游,体现在更干净的购买上,以及更少那些日后变成作废和争议的欺诈购买上。Google 把混淆账户 id 和 Voided Purchases API 一并列为它两大核心反滥用工具,是有原因的。
为什么在一次拒付审查到来时它至关重要
一次你能预见的退款很好办。难办的是银行拒付,因为它不是从你的客户跟你交谈开始的。它从银行开始,Google Play 把它作为一次带着倒计时的审查转交给你。
争议指明的是一个订单,而不是一个人
当一位客户就一笔扣款向他们的银行发起争议,而 Google 需要你的意见时,Google Play 会发送一条 PendingRefundReviewNotification。那条消息标识的是订单。它不携带你的用户 id,因为 Google 从来就没有你的用户 id。它只有你盖到购买上的那点东西。如果那是空的,你现在就得拿着一个孤零零的订单 id 和购买令牌去反查你自己的记录,寄望于你在购买时记录了那个令牌,也寄望于匹配没有歧义。如果你设置了混淆账户 id,那笔购买就携带你自己的哈希,你一次查询就能找到用户,然后转去构建证据,而不是四处搜寻身份。
orders.reviewrefund 到底向你要什么
回应争议意味着在 24 小时内调用 orders.reviewrefund 方法。Google 记录你的第一次调用并忽略其余,所以第一个回答就是唯一的回答。以下是它想要的字段,而每一个证据字段都假定你已经知道订单属于哪个用户。
| 字段 | 是否必填 | 它携带什么 |
|---|---|---|
| pendingRefundToken | 是 | 来自你正在回应的那条 PendingRefundReviewNotification 的令牌 |
| refundPreference | 是 | APPROVE、DECLINE 或 NEUTRAL,你对 Play 是否应退款的建议 |
| sampleContentProvided | 是 | 你是否在购买前提供了免费样品、试用或该功能的说明 |
| consumptionPercentageMilliunits | 可选 | 客户消耗了购买内容的多少,0 到 100,000 milliunits |
| consumptionUsageEvents | 可选 | 一系列事件,每一个都是用户消耗或使用其所购内容的一次实例 |

跳过它究竟要付出什么代价
在 Google Play 历史的大部分时间里,一次你无法辩护的拒付不过是一笔流失的销售,耸耸肩就过去了。这一点变了。对于在 2026 年 8 月 3 日或之后下单的订单,一次败诉的拒付会向开发者收取购买价格减去 Google 服务费,再加上银行的拒付手续费。那次你无法回应的争议如今是一个账目项。
走一遍一笔订单。一位客户就一笔 $9.99 的购买向他们的银行发起争议。Google Play 发来审查,你有 24 小时。如果你为这笔购买打了标记,你就能找到用户,看到他们已经消耗了所购内容的大部分,然后以 DECLINE 偏好加上消耗证据来回应 reviewrefund,给了 Google 一个真实的案情去抗辩一次不正当的争议。如果你没打标记,你要么来不及识别订单,要么什么都没有地去回应,争议在没有你这一方声音的情况下被裁决,而对一笔 8 月 3 日之后的订单,你要退还 $9.99 减去 Google 的费用,外加一笔固定的银行拒付手续费,它往往落在接近 $20 的水平。在一笔小额销售上,仅这笔固定费用就可能大于你净得的金额。
- 流失的收益:你在这笔销售中的净份额,被反转。
- 银行的拒付手续费:一笔由卡组织设定的固定成本,对在 2026 年 8 月 3 日之后下单的订单额外收取,而一次普通退款从不携带它。
- 白花的开销:该账户已经用掉的计算、第三方 API 调用和存储,无论你能否回应,都没了。
- 你看不见的模式:没有一个稳定的账户 id,你也无法察觉是同一个用户在一次又一次地发起争议,于是惯犯式的滥用就读作互不相关的一次性损失。
如何设置它才不会让购买被拦截
两条规则几乎覆盖了各团队在这个字段上会犯的所有错误。给 id 做哈希,并且到处都设置它。
对你的用户 id 做哈希,绝不发送 PII
不要在这个字段里放电子邮件、电话号码或任何原始的个人细节。Google 明确表示,以明文存储电子邮件等 PII 会导致购买被拦截,并建议用单向哈希或加密来生成该值。干净的做法是对你的内部用户 id 做单向哈希,每次都以相同的方式计算,这样同一个用户始终产生相同的 64 个字符的字符串。也不要用此人的 Google 账户 id 或你的开发者 id。这个值应当只对你的系统有意义。
对每一笔购买都设置它,并在你的服务器上把它读回
把这个 id 附加到每一个计费流程,一次性商品和订阅都一样,这样没有一笔购买是没有标签的。购买之后,在两个地方把它读回。在客户端,Purchase.getAccountIdentifiers 返回一个对象,其 getObfuscatedAccountId 给出你设置的那串字符。在你的后端,服务器端的购买记录以 obfuscatedExternalAccountId 字段携带它,而应当信任的是服务器这一份,因为争议到达的是你的服务器,而不是设备。
当一个账户有多个档案时使用 setObfuscatedProfileId
如果你的应用允许一个账户拥有多个档案,比如一个流媒体家庭或一个有多个角色的游戏,那么也一并设置 setObfuscatedProfileId。它是同一类经过哈希、64 个字符、无 PII 的字符串,作用域限定到发起购买的那个档案。Google 指出,设置档案 id 时也要求传入账户 id,所以两个都发。结果是一次争议不仅映射到账户,还映射到花了这笔钱的那个确切档案。
iOS 上的对应做法,一句话说清
App Store 在另一个名字下有着同样的思路。在 iOS 上你为一笔购买附加一个 appAccountToken,一个 UUID,它会在交易上返回,也会在客户申请退款时 Apple 发送的 CONSUMPTION_REQUEST 上返回。问题的形状在两个商店上是一模一样的。争议或退款流程引用的是一笔交易,而你自己的标识符就是把它连回一个你能上报其使用情况的用户的东西。
| 细节 | Google Play | App Store |
|---|---|---|
| 你设置的字段 | 通过 setObfuscatedAccountId 设置的混淆账户 id | appAccountToken |
| 格式 | 哈希字符串,64 个字符,无 PII | UUID |
| 它在哪里返回 | 购买上的 obfuscatedExternalAccountId | 交易上的 appAccountToken |
| 它供给的时窗 | orders.reviewrefund,24 小时 | CONSUMPTION_REQUEST,12 小时 |
| 你上报什么 | 消耗百分比和使用事件 | Apple 的消耗字段 |
这些都不难做。它很容易被跳过,因为你写结账代码的那天不是拒付到来的那天,而跳过它的代价在那之前一直是隐形的。RefundHalt 在两个商店上设置并跟踪账户标识符,保持购买到用户的链路,让一次争议总能落到一个真实的客户身上,并在各自的时窗内用销售时记录下来的消耗证据去回应 Google Play 的 orders.reviewrefund 和 Apple 的 CONSUMPTION_REQUEST。那 24 小时的倒计时,不是你才发现自己说不出是谁买了这个东西的时刻。
常见问题解答
- Google Play 计费中的混淆账户 id 是什么?
- 它是你用 setObfuscatedAccountId 附加到一笔购买上的一个可选字符串,与你应用中购买者的用户账户唯一关联。Google Play 把它与订单一起存储,用它来检测异常活动,比如许多设备在同一账户上购买,并在之后以 obfuscatedExternalAccountId 的形式返回给你,这样你就能把一笔购买连回某个特定用户。
- 我可以把用户的电子邮件或 id 放进混淆账户 id 字段吗?
- 不可以。Google 表示,在此字段中以明文存储电子邮件等个人身份信息会导致购买被拦截。请用单向哈希或加密来生成该值,保持在 64 个字符以内,也不要使用此人的 Google 账户 id 或你的开发者 id。
- 混淆账户 id 对 Google Play 拒付有什么帮助?
- 一次拒付审查,也就是 PendingRefundReviewNotification,指明的是订单,而不是你的用户。混淆账户 id 就是那把连接键,把该订单映射到正确的用户记录,这样你就能在 24 小时内用真实的消耗证据回应 orders.reviewrefund,而不必去猜订单属于哪个客户。
- 我应该在订阅上设置混淆账户 id,还是只在一次性购买上设置?
- 对每一笔购买都设置它,一次性商品和订阅都要。任何没有标签的购买,都是当一次争议或作废到来时你无法追溯到某个用户的购买,而争议可以落在任何订单类型上。
- 混淆账户 id 和混淆档案 id 有什么区别?
- 账户 id 把一笔购买映射到你应用中的一个用户账户。档案 id 把它映射到该账户内的一个特定档案,用于一个账户拥有多个档案或角色的应用。两者都是经过哈希、64 个字符、无 PII 的字符串,而且 Google 指出,设置档案 id 时也要求传入账户 id。
来源和延伸阅读
- Android Developers: Fight fraud and abuse (Play Billing)
- Android Developers: BillingFlowParams.Builder (setObfuscatedAccountId, setObfuscatedProfileId)
- Android Developers: AccountIdentifiers (getObfuscatedAccountId)
- Android Developers: Help Google dispute chargebacks
- Google Play Developer API: Method orders.reviewrefund
- Play Console Help: Chargeback cost responsibility update (August 3, 2026)
- Apple Developer: Handling refund notifications (CONSUMPTION_REQUEST, appAccountToken)
RefundHalt
App Store 和 Google Play 退款自动驾驶
继续阅读
银行账单上一笔无法识别的费用会变成拒付,而拒付给你带来的损失比退款更大
当客户无法辨别你的应用向他们收取了什么费用时,他们会打电话给银行而不是联系你,于是这场纠纷就变成了拒付。Apple 把所有费用都显示为 apple.com/bill,并且不让你更改任何内容。Google Play 允许你设置账单显示名称。以下是各自的代价以及你能掌控的部分。
Apple 可以撤销它已经批准的退款,而你的服务器若忽略这种已撤销的退款,就会把付了钱的客户挡在门外
当 App Store 撤销它已经批准的退款时,它期望你的服务器恢复你此前收回的访问权限。以下说明退款、退款被拒和退款撤销这些通知在 App Store 和 Google Play 上如何运作,以及当你忽略它们时每一种各会付出什么代价。