调用purchases.subscriptionsv2.get如何获取订阅关联的用户数据
核心结论
purchases.subscriptionsv2.get 接口的官方返回结构里不包含任何Firebase用户身份相关字段。Google Play和Firebase Auth是完全独立的服务,Play侧仅存储交易本身、关联Google Play账号、订阅状态这类订单维度数据,不会感知你应用内的用户体系,不存在可以直接从该接口提取Firebase用户ID的内置字段。
不要白费力气在响应结构里找现成的用户关联字段,Google Play从设计上就不会存储你应用侧自定义的用户体系数据,所有用户-订单的绑定逻辑必须在购买发起阶段由客户端主动传入
通用实现方案(覆盖实名登录/匿名登录场景)
利用Billing Library预留的开发者自定义账号字段做绑定,全流程逻辑如下:
- 客户端发起购买前,先获取当前Firebase用户实例:不管是已绑定登录凭证的正式用户,还是未登录的匿名用户,Firebase Auth都会生成稳定唯一的
uid,直接取FirebaseAuth.getInstance().currentUser.uid即可 - 构建Billing购买参数时,把拿到的Firebase UID传入
setObfuscatedAccountId方法,这个字段是Billing Library专门提供给开发者绑定自有用户体系的,会随订单永久存储在Google Play侧 - 云函数校验订单时,从
purchases.subscriptionsv2.get返回的响应中,读取subscriptionPurchase.obfuscatedExternalAccountId字段,就能拿到之前绑定的Firebase UID,直接用这个值做用户-订单的关联存储即可
客户端传参参考代码(Kotlin):
// 发起购买前构建参数 val currentUid = FirebaseAuth.getInstance().currentUser?.uid ?: error("Firebase Auth未初始化,禁止发起购买") val billingParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList( listOf( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(targetProduct) .setOfferToken(selectedOfferToken) .build() ) ) // 绑定当前用户Firebase UID到订单 .setObfuscatedAccountId(currentUid) .build() // 调用launchBillingFlow拉起支付
现有代码修正点
你贴的云函数代码存在几个会导致逻辑异常的问题,需要同步修正:
- 变量未定义:代码里直接使用了
data变量,但实际Pub/Sub推送的消息体挂载在message.json下,需要先声明const data = message.json再读取内部字段 - 变量名冲突:入参名是
message,接口校验通过后又定义了var message存储待写入数据,会导致上下文变量覆盖,建议重命名内部变量 - 校验逻辑不完整:仅判断HTTP状态码200不足以证明订阅有效,需要额外校验
paymentState === 1(已完成付款)、cancelReason为空(未主动取消/退款),避免把无效订单写入数据库 - 存储结构不合理:全量存储原始Pub/Sub消息冗余度太高,建议按用户UID作为文档ID存储,方便后续直接查询用户订阅状态
修正后的核心校验逻辑参考:
exports.insertFromPubSubTopic = functions.pubsub.topic('mytopic').onPublish(async(message, context) => { const payload = message.json; if (!payload.subscriptionNotification) { console.log('收到非订阅通知消息'); return firestore.collection('mp-billing-messages-error').add(payload) } try { await authClient.authorize(); const subscriptionRes = await playDeveloperApiClient.purchases.subscriptionsv2.get({ packageName: payload.packageName, token: payload.subscriptionNotification.purchaseToken }); if (subscriptionRes.status !== 200) { throw new Error(`订单查询失败,状态码:${subscriptionRes.status}`) } const purchase = subscriptionRes.data.subscriptionPurchase; const linkedUid = purchase.obfuscatedExternalAccountId; // 校验用户ID存在、订单状态有效 if (!linkedUid || purchase.paymentState !== 1 || purchase.cancelReason) { throw new Error('订单无效,未关联用户或状态异常') } // 按UID存储订阅记录,支持后续直接更新 return firestore.collection('billing-verified').doc(linkedUid).set({ uid: linkedUid, purchaseToken: payload.subscriptionNotification.purchaseToken, orderId: purchase.orderId, expiryTimeMillis: purchase.expiryTime, autoRenewing: purchase.autoRenewing, lastUpdatedAt: Date.now() }, { merge: true }) } catch (error) { console.error('订阅校验失败:', error); return { status: 500, message: "订阅校验失败,稍后重试" } } })
匿名用户场景注意事项
- 匿名用户只要初始化过Firebase Auth,生成的UID在应用本地未清除数据的前提下是永久稳定的,完全可以直接作为
obfuscatedAccountId传入,和正式登录用户的处理逻辑没有区别 - 如果需要支持用户换设备/卸载重装后保留订阅权益,可以在用户后续绑定手机号、邮箱或第三方登录凭证时,调用Firebase Auth的
linkWithCredential接口把匿名账号升级为正式账号,之前绑定的订阅记录会自动关联到升级后的账号 - 不要尝试提取Google账号相关信息做用户关联,Google Play不会向开发者暴露用户的Google账号明文信息,返回的账号标识是应用维度混淆的,无法跨服务匹配用户身份
内容的提问来源于stack exchange,提问作者Gregor Sotošek
相关产品推荐
相关产品推荐

