如何通过 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:用于查询退款、撤销或拒付等作废购买,不包含所有成功付款记录。- 服务账号:负责服务器身份认证,不会改变接口的数据范围。
推荐实现方式
可以按以下流程处理:
- App 完成购买后,将
purchaseToken、商品 ID、用户 ID 等信息发送到服务器。 - 服务器调用
purchases.subscriptionsv2.get校验订阅。 - 保存响应中的
latestOrderId、到期时间、订阅状态和原始响应摘要。 - 配置 Google Cloud Pub/Sub 和 RTDN。
- 收到续订、取消、暂停、恢复或撤销通知后,再次查询对应的
purchaseToken。 - 如果
latestOrderId与数据库中最近保存的订单不同,则新增一条订单记录。 - 使用
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 权限通常不够。
- 订单归档应从首次购买开始。订阅查询接口适合校验当前状态,不能代替历史账本。
备注:内容仅供参考。
