You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 17:18:24