Google Play Developer API 未返回 emailAddress 字段
结论
“Subscribe with Google”不是 Android 应用中的 Google Play Billing 订阅流程,而是 Google 为特定内容出版商提供的一项独立订阅服务。
Android App 通过 Google Play Billing 完成普通订阅后,purchases.subscriptions.get 通常不会返回 emailAddress。这是一个条件字段,只有购买确实来自 “Subscribe with Google” 时才可能出现。
为什么没有 emailAddress
Google Play Developer API 用于验证和管理购买状态,不会提供普通 Play Billing 订单所对应的 Google 账号邮箱。即使用户已经登录 Google Play,开发者也无法通过 purchaseToken、orderId 或该接口反查其 Google 账号邮箱。
文档中的这句话:
Only present for purchases made with ‘Subscribe with Google’.
意思是,只有通过名为 “Subscribe with Google” 的特定产品流程完成购买,响应中才可能包含该字段。这里的名称并不是对所有 Google 订阅购买的统称。
你给出的响应是正常的 Google Play Billing 订阅响应。缺少 emailAddress 不表示请求参数、API 权限或订阅配置有误,增加 OAuth scope 也不会让该字段出现。
推荐的解决方式
如果业务需要把订阅关联到具体用户,应通过自己的账号系统建立关联,不要依赖 Google Play 返回邮箱。
可以采用以下流程:
- 用户先登录应用。
- 后端生成一个不含个人信息的内部账号标识。
- 发起购买时,通过
setObfuscatedAccountId()将该标识传给 Google Play。 - 购买成功后,将
purchaseToken、商品 ID 和内部用户 ID 发送到后端。 - 后端调用 Google Play Developer API 验证购买,并保存订阅与用户的对应关系。
如果用户尚未注册,可以先保存 purchaseToken,等用户登录或注册后再完成绑定。
Android 端示例
不要将邮箱地址直接传给 setObfuscatedAccountId(),应使用稳定且不可逆的散列值,或者专门生成的随机标识。
val productDetailsParams =
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(offerToken)
.build()
val billingFlowParams =
BillingFlowParams.newBuilder()
.setProductDetailsParamsList(listOf(productDetailsParams))
.setObfuscatedAccountId(obfuscatedAccountId)
.build()
billingClient.launchBillingFlow(activity, billingFlowParams)
购买成功后,将购买凭据提交给后端:
data class VerifySubscriptionRequest(
val productId: String,
val purchaseToken: String
)
val request = VerifySubscriptionRequest(
productId = purchase.products.first(),
purchaseToken = purchase.purchaseToken
)
后端可以保存类似下面的关联关系:
internal_user_id
purchase_token
product_id
order_id
subscription_status
expiry_time
邮箱则由应用自己的用户系统管理:
internal_user_id -> email_address
internal_user_id -> purchase_token
这样一来,即使用户修改邮箱、续订、升级订阅或更换设备,仍然可以通过内部用户 ID 管理订阅归属。
后端验证示例
下面是调用旧版订阅查询接口的概念性示例:
const result = await androidPublisher.purchases.subscriptions.get({
packageName: "com.example.app",
subscriptionId: productId,
token: purchaseToken
});
const subscription = result.data;
if (subscription.paymentState === 1) {
// 根据 purchaseToken 查找并更新内部用户的订阅状态
}
实际项目应优先使用 Google 当前推荐的订阅查询接口,并根据接口返回的到期时间、确认状态、取消状态和订阅项目状态作出判断。无论使用哪个版本的订阅查询接口,都不要假设普通 Play Billing 订单会返回买家的 Google 邮箱。
注意事项
- 缺少
emailAddress是预期行为,不是字段解析错误。 - 不要尝试通过
orderId或purchaseToken推导或查询用户邮箱。 - 不要将邮箱、姓名或其他个人信息直接用作
obfuscatedAccountId。 purchaseToken是敏感的购买凭据,只应通过 HTTPS 传输并保存在后端。- 不要只依赖客户端上报的购买状态,最终状态应由后端通过 Google Play Developer API 验证。
- 对于未登录购买,需要设计恢复购买和账号绑定流程,避免将同一个订阅错误地关联到多个账号。
- 如果业务必须获取邮箱,应在应用内征得用户同意后让用户提供,或通过自己的登录系统获取,不要依赖 Google Play 订单接口。
普通 Android Play Billing 订阅并不属于文档所说的 “Subscribe with Google”,因此当前响应没有 emailAddress 是正常的。正确的处理方式是在购买发生时,将 purchaseToken 与应用自己的用户账号可靠地关联起来。
备注:内容仅供参考。
