PAYATHON 2026

如何通过 Google Developer API 获取订阅的历史付款列表?

支付老李

结论

仅凭 purchaseToken 和 subscriptionId,无法通过 Google Play Developer API 一次获取某个订阅的全部历史付款记录及每期对应的 orderId。

订阅查询接口提供的是当前订阅状态和最近一次相关订单的信息,并非付款历史列表。服务账号只决定调用 API 时使用的身份和权限,不会扩大接口返回的数据范围。

如果业务需要准确记录每次续订的 orderId,应在订阅生命周期内接收 Real-time Developer Notifications(RTDN)。每次收到续订等通知后,重新查询订阅状态,并将订单信息持久化。对于此前没有保存的数据,只能通过 Google Play Console 的订单记录或财务报告进行核对,无法使用订阅查询接口补回完整历史。给订单号追加 ..0、..1 等后缀来推算历史订单,也不能作为可靠的账务方案。

为什么查询接口不能返回历史付款

旧版接口:

GET /androidpublisher/v2/applications/{packageName}/purchases/subscriptions/{subscriptionId}/tokens/{token}

该接口返回一个订阅状态对象。即使响应中存在 orderId,它也只是本次查询返回的订单标识,响应里没有可供遍历的历史订单数组。

实际开发应使用当前 Android Publisher API 提供的接口,例如:

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

purchases.subscriptionsv2.get 的响应可能包含 latestOrderId。这个字段表示最近的订单,并不包含全部续订订单。响应中的 lineItems 用于描述当前订阅项目及其状态,同样不能作为完整的付款流水。

以下信息也无法用于查询完整的历史付款:

  • linkedPurchaseToken:用于关联升级、降级、重新订阅等操作引起的 token 替换关系,不是付款历史列表。
  • RTDN:通知通常不会直接携带完整订单数据,只会提示订阅状态发生了变化。
  • purchases.voidedpurchases.list:用于查询退款、撤销或拒付等作废购买,不包含所有成功付款记录。
  • 服务账号:负责服务器身份认证,不会改变接口的数据范围。

推荐实现方式

可以按以下流程处理:

  1. App 完成购买后,将 purchaseToken、商品 ID、用户 ID 等信息发送到服务器。
  2. 服务器调用 purchases.subscriptionsv2.get 校验订阅。
  3. 保存响应中的 latestOrderId、到期时间、订阅状态和原始响应摘要。
  4. 配置 Google Cloud Pub/Sub 和 RTDN。
  5. 收到续订、取消、暂停、恢复或撤销通知后,再次查询对应的 purchaseToken。
  6. 如果 latestOrderId 与数据库中最近保存的订单不同,则新增一条订单记录。
  7. 使用 orderId 或其他合适的业务组合键进行幂等处理,避免重复通知产生重复记录。

典型的数据结构可以包含:

user_id
package_name
product_id
purchase_token
order_id
event_type
expiry_time
subscription_state
first_seen_at
last_verified_at
raw_response

purchaseToken 应视为敏感凭据,不要将其写入客户端日志、公开日志或分析平台。

服务账号查询示例

下面的 Python 示例使用服务账号获取访问令牌,再查询订阅的当前状态。它只能读取本次响应中的订单信息,无法列出此前的全部付款记录。

from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account

PACKAGE_NAME = "com.example.app"
PURCHASE_TOKEN = "purchase-token-from-client"

credentials = service_account.Credentials.from_service_account_file(
    "service-account.json",
    scopes=["https://www.googleapis.com/auth/androidpublisher"],
)

session = AuthorizedSession(credentials)

url = (
    "https://androidpublisher.googleapis.com/androidpublisher/v3/"
    f"applications/{PACKAGE_NAME}/purchases/subscriptionsv2/"
    f"tokens/{PURCHASE_TOKEN}"
)

response = session.get(url, timeout=30)
response.raise_for_status()

subscription = response.json()

latest_order_id = subscription.get("latestOrderId")
subscription_state = subscription.get("subscriptionState")
line_items = subscription.get("lineItems", [])

print("latestOrderId:", latest_order_id)
print("subscriptionState:", subscription_state)
print("lineItems:", line_items)

查询完成后,应立即持久化新订单,例如:

def save_if_new(subscription, repository):
    order_id = subscription.get("latestOrderId")

    if not order_id:
        return

    if repository.exists_by_order_id(order_id):
        return

    repository.insert({
        "order_id": order_id,
        "subscription_state": subscription.get("subscriptionState"),
        "line_items": subscription.get("lineItems", []),
    })

实际使用时,还需配合事务或数据库唯一约束处理并发。RTDN 可能重复发送,也可能延迟或乱序到达,因此不能假设每条通知只会收到一次。

已经错过的历史订单怎么办

如果系统此前没有保存每次续订结果,无法仅凭 purchaseToken 恢复完整的 orderId 列表。可以从以下来源查找:

  • Google Play Console 中的订单管理记录;
  • Google Play 提供的财务报告或收入报告;
  • 业务系统中已有的 RTDN、服务器日志、发票或支付对账数据;
  • 用户提供的 Google Play 收据或订单记录。

财务报告通常按结算周期生成,其字段、生成时间和所需权限都与实时订阅查询接口不同,需要单独设计导入及对账流程。它也不是一个可以按 purchaseToken 实时查询全部历史订单的 API。

注意事项

  • 不要通过修改订单号后缀来构造历史 orderId。即使部分连续续订的订单看起来符合这种格式,也不能将推算结果用于支付确认或作为账务凭证。
  • purchaseToken 可能在重新订阅、升级、降级或其他订阅替换操作后发生变化,因此需要保存 linkedPurchaseToken 关系。
  • 不要只检查到期时间。判断权益时,还应结合 subscriptionState、自动续订状态、取消原因和具体的 lineItems。
  • 必须在 Google Play Console 中为服务账号授予相应应用以及订单或订阅权限。仅配置 Google Cloud IAM 权限通常不够。
  • 订单归档应从首次购买开始。订阅查询接口适合校验当前状态,不能代替历史账本。

备注:内容仅供参考。