PAYATHON 2026

Android 应用内订阅购买后如何获取买家邮箱?

支付阿杰

明确结论

无法通过 Google Play Billing API、Google Play Developer API 或旧版 Google Checkout Notification History API 获取 Android 应用内订阅买家的 Google 账号邮箱。

购买结果中的 order_id、purchase_token 等字段用于验证和管理交易,并不会披露买家身份。Google Play 后台页面即使显示了部分订单信息,也不意味着这些信息会通过 API 提供。

可行的做法是让用户在应用内登录,或由用户主动填写并验证邮箱。购买完成后,再由服务端将应用账号与 purchase_token 关联。

为什么 billing address 不再包含邮箱

buyer-billing-address 来自早期 Google Checkout 的数据结构。Google Checkout 及其 Notification History API 已不属于当前的 Android 应用内购买体系,不能再用它们获取订阅用户资料。

在现代 Google Play Billing 中,付款信息与开发者维护的用户身份是分开的:

  • Google Play 处理付款、续订、退款和订阅状态。
  • 应用管理登录账号、邮箱和业务身份。
  • purchase_token 用于验证和跟踪购买,但不包含邮箱。
  • order_id 是交易标识,无法通过公开 API 反查买家邮箱。
  • Google Play Developer API 可以返回订阅状态、商品、续订和取消等信息,但不会返回买家的 Google 账号邮箱。

如果 XML 中不再有 email 节点,就不应继续解析其他结算地址字段,试图从中寻找替代信息。

推荐实现方式

1. 在购买前确认应用内用户身份

要求用户先登录应用账号,或者至少完成邮箱验证。购买成功后,将以下信息发送给服务端:

  • 应用内部的 userId
  • purchaseToken
  • productId
  • packageName

邮箱应保存在应用自己的用户系统中,不要尝试从 Google Play 订单中反查。

2. 在发起购买时传递混淆后的账号标识

Play Billing 支持通过 obfuscatedAccountId 传入应用账号的混淆标识。该值可以帮助后端关联购买和用户,也有助于 Google 进行欺诈检测。

不要直接传入邮箱、手机号或其他明文个人信息。可以使用带密钥的 HMAC 处理稳定的内部用户 ID:

import android.util.Base64
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

fun createObfuscatedAccountId(userId: String, secret: ByteArray): String {
    val mac = Mac.getInstance("HmacSHA256")
    mac.init(SecretKeySpec(secret, "HmacSHA256"))

    return Base64.encodeToString(
        mac.doFinal(userId.toByteArray(Charsets.UTF_8)),
        Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING
    )
}

发起订阅购买时设置该标识:

val accountIdentifiers =
    BillingFlowParams.ObfuscatedAccountIdParams.newBuilder()
        .setObfuscatedAccountId(createObfuscatedAccountId(user.id, secret))
        .build()

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .setObfuscatedAccountIdParams(accountIdentifiers)
    .build()

billingClient.launchBillingFlow(activity, billingFlowParams)

具体构造方式可能会随 Play Billing Library 的版本变化,应以当前使用版本的官方接口定义为准。原则是传递不可逆或无法直接识别用户的内部账号标识,而不是邮箱明文。

3. 购买成功后上传 purchase token

客户端收到购买结果后,将令牌发送给服务端:

override fun onPurchasesUpdated(
    billingResult: BillingResult,
    purchases: MutableList<Purchase>?
) {
    if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
        return
    }

    purchases.orEmpty().forEach { purchase ->
        if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
            api.registerPurchase(
                userId = currentUser.id,
                purchaseToken = purchase.purchaseToken,
                productIds = purchase.products
            )
        }
    }
}

服务端至少应保存类似的关联关系:

user_id
purchase_token
product_id
package_name
latest_order_id
subscription_status
created_at
updated_at

不要只把 order_id 用作订阅的长期主键。续订可能产生新的订单编号,而 purchase_token 更适合用于调用 Google Play Developer API 查询订阅状态。升级、降级或重新购买时,令牌也可能发生变化,因此服务端需要处理新旧购买之间的关系。

4. 由服务端验证订阅

服务端收到 purchaseToken 后,应调用 Google Play Developer API 进行验证,不要直接信任客户端上报的购买状态。例如,可以使用订阅查询接口:

GET https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/subscriptionsv2/tokens/{token}
Authorization: Bearer ACCESS_TOKEN

该接口可以确认订阅状态和续订信息,但不会返回买家邮箱。验证成功后,服务端再向已登录的应用用户发放订阅权益。

没有登录系统时怎么办

如果现有应用允许匿名购买,就无法只凭历史 order_id 或 purchase_token 稳定、合规地恢复买家邮箱。可以采用以下补救方式:

  1. 在应用内增加”绑定邮箱”或”创建账号”流程。
  2. 验证用户是否拥有该邮箱。
  3. 恢复并验证设备当前 Google Play 账号下的有效购买。
  4. 将验证成功的 purchaseToken 绑定到应用账号。
  5. 在服务端阻止同一有效订阅被多个账号重复绑定。

这个流程恢复的是设备当前能够访问的购买记录,并不是从订单中读取 Google 账号邮箱。

注意事项

  • 不要假设用户的应用登录邮箱与 Google Play 付款账号邮箱相同。
  • 不要根据 order_id、收据文本或结算地址推断用户身份。
  • 不要将邮箱明文传入 obfuscatedAccountId 或 obfuscatedProfileId。
  • 购买验证、令牌绑定和订阅权益发放应由服务端完成。
  • 服务端应处理退款、撤销、暂停、宽限期、账号保留和续订等状态变化。
  • 可以结合 Real-time Developer Notifications 更新订阅状态,但通知同样不会提供买家邮箱。
  • 收集邮箱时应说明用途,并遵守适用的隐私政策和数据保护要求。

Google Play 的购买 API 用来证明”这笔订阅有效”,应用自己的账号系统则用来确定”这笔订阅属于谁”。如果业务必须使用邮箱,应在应用内征得用户同意,并自行收集和验证。

备注:内容仅供参考。