从零到一:谷歌支付(充值+订阅)后端集成与上线验证实战

张开发
2026/4/16 21:05:22 15 分钟阅读

分享文章

从零到一:谷歌支付(充值+订阅)后端集成与上线验证实战
1. 谷歌支付后端集成全景图第一次接触谷歌支付后端集成时我被官方文档里密密麻麻的流程图和API参数吓得不轻。但实际走完全流程后发现核心环节就像组装乐高积木——只要把服务账号创建、Pub/Sub配置、订单验证、实时通知处理这几个关键模块正确拼接就能搭建出稳定的支付系统。这里分享我踩过坑后总结的最佳实践。谷歌支付与其他支付平台最大的不同在于其双重验证机制前端返回的支付凭证必须经过后端二次验证同时还要处理谷歌服务器主动推送的实时开发者通知RTDN。这种设计虽然增加了复杂度但能有效防止伪造支付请求。我在项目初期就遇到过测试环境验证通过但正式环境总报错的情况后来发现是服务账号权限配置遗漏导致的。2. 服务账号创建与权限配置2.1 创建服务账号的隐藏陷阱在Google Cloud控制台创建服务账号时新手常犯两个致命错误直接使用默认的编辑器角色Editor这会导致权限过大忘记给服务账号添加Google Play Android Developer权限正确的做法是# 创建最小权限角色 gcloud iam roles create GooglePayVerifier \ --projectyour-project-id \ --titleGoogle Pay Verifier \ --permissionsandroidpublisher.purchases.get,androidpublisher.subscriptions.get然后通过控制台https://console.cloud.google.com/iam-admin/serviceaccounts创建专属服务账号并绑定这个自定义角色。记得下载JSON密钥文件时要像保护数据库密码一样保管它——我有次不小心把密钥提交到GitHub仓库不得不紧急轮换凭证。2.2 密钥轮换的自动化方案生产环境中建议实现密钥自动轮换机制。我在Spring Boot项目中是这么配置的Bean RefreshScope public AndroidPublisher androidPublisher( Value(${google.credential.path}) String credentialPath) throws Exception { GoogleCredential credential GoogleCredential .fromStream(new FileInputStream(credentialPath)) .createScoped(Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER)); return new AndroidPublisher.Builder( GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), credential) .setApplicationName(packageName) .build(); }配合配置中心的RefreshScope注解更新密钥文件后无需重启服务。曾经有次密钥泄露事件我们就是用这套方案在5分钟内完成全集群密钥更新全程零停机。3. Pub/Sub配置的魔鬼细节3.1 主题与订阅的生死时速创建Pub/Sub主题时https://console.cloud.google.com/cloudpubsub/topic/list必须记得添加特殊服务账号google-play-developer-notificationssystem.gserviceaccount.com这个账号需要pubsub.publisher权限否则谷歌服务器无法推送通知。我有次凌晨三点被报警叫醒就是因为漏配了这个权限导致所有实时通知丢失。建议用Terraform固化配置resource google_pubsub_topic_iam_member play_notification_publisher { topic google_pubsub_topic.play_notifications.name role roles/pubsub.publisher member serviceAccount:google-play-developer-notificationssystem.gserviceaccount.com }3.2 订阅的重试策略玄机创建订阅时要特别注意确认期限ackDeadline和重试策略。默认的10秒确认时间对于支付验证可能太短建议调整为gcloud pubsub subscriptions create your-subscription \ --topicyour-topic \ --ack-deadline30 \ --expiration-periodnever \ --message-retention-duration7d我们在处理高峰流量时曾因消息堆积导致确认超时Pub/Sub不断重发消息。最终解决方案是增加确认超时到30秒实现幂等处理逻辑添加死信队列处理永不过期的消息4. 后端SDK集成的坑位指南4.1 依赖管理的版本陷阱引入AndroidPublisher SDK时要特别注意传递依赖冲突。推荐锁定以下版本dependency groupIdcom.google.apis/groupId artifactIdgoogle-api-services-androidpublisher/artifactId versionv3-rev20231012-2.0.0/version /dependency dependency groupIdcom.google.http-client/groupId artifactIdgoogle-http-client-jackson2/artifactId version1.42.3/version /dependency我曾因为没指定http-client版本导致线上环境出现奇怪的JSON解析错误。现在每次升级依赖前都会用mvn dependency:tree仔细检查冲突。4.2 自动配置的优雅实现Spring Boot的自动配置可以极大简化初始化过程。这是我的生产级配置模板Configuration Slf4j public class GooglePayAutoConfig { Bean ConditionalOnMissingBean public AndroidPublisher androidPublisher( Value(${google.package.name}) String packageName, Value(${google.credential.location}) Resource credentialFile) { try { ListString scopes List.of(AndroidPublisherScopes.ANDROIDPUBLISHER); GoogleCredential credential GoogleCredential .fromStream(credentialFile.getInputStream()) .createScoped(scopes); return new AndroidPublisher.Builder( GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), credential) .setApplicationName(packageName) .build(); } catch (Exception e) { log.error(Google支付初始化失败, e); throw new RuntimeException(e); } } }关键点在于使用ConditionalOnMissingBean避免重复创建以及通过Resource抽象实现多环境适配本地文件/配置中心/云存储。5. 订单验证的实战代码5.1 一次性商品验证逻辑处理应用内购买一次性商品时要特别注意消费状态consumptionState的检查public boolean verifyOneTimePurchase(String packageName, String productId, String purchaseToken) { ProductPurchase purchase androidPublisher.purchases() .products() .get(packageName, productId, purchaseToken) .execute(); return purchase.getPurchaseState() 0 // 0表示已购买 purchase.getConsumptionState() 0; // 0表示未消耗 }常见错误是只检查purchaseState而忽略consumptionState导致重复发货。我们为此专门设计了防重表CREATE TABLE google_purchase_tokens ( token VARCHAR(128) PRIMARY KEY, user_id BIGINT NOT NULL, product_id VARCHAR(64) NOT NULL, consumed BOOLEAN DEFAULT false, consumed_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );5.2 订阅验证的复杂场景订阅验证比一次性商品复杂得多需要处理续期、升降级等场景。这是我们的核心验证逻辑public SubscriptionVerifyResult verifySubscription(String packageName, String token) { SubscriptionPurchaseV2 sub androidPublisher.purchases() .subscriptionsv2() .get(packageName, token) .execute(); SubscriptionVerifyResult result new SubscriptionVerifyResult(); result.setValid(sub.getAcknowledgementState() 1); // 0待确认 1已确认 SubscriptionPurchaseLineItem latest sub.getLineItems().get(0); result.setStartTime(parseGoogleTime(latest.getStartTime())); result.setExpiryTime(parseGoogleTime(latest.getExpiryTime())); // 处理关联购买升降级场景 if (sub.getLinkedPurchaseToken() ! null) { result.setLinkedPurchaseToken(sub.getLinkedPurchaseToken()); } return result; }特别注意谷歌订阅订单号有特殊规则。首次购买是GPA.1234-5678-9012续订会变成GPA.1234-5678-9012..0、GPA.1234-5678-9012..1。降级购买时订单号会完全变化但会保留linkedPurchaseToken指向原订单。6. 实时开发者通知(RTDN)处理6.1 消息解析的正确姿势谷歌推送的RTDN消息需要先base64解码public DeveloperNotification parseNotification(String messageData) { byte[] decoded Base64.getDecoder().decode(messageData); return objectMapper.readValue(decoded, DeveloperNotification.class); }消息体中的eventTimeMillis是毫秒级时间戳建议转换为UTC时间Instant instant Instant.ofEpochMilli(notification.getEventTimeMillis()); LocalDateTime eventTime LocalDateTime.ofInstant(instant, ZoneOffset.UTC);6.2 处理订阅状态变更订阅状态变更是最复杂的部分需要处理以下通知类型switch (notification.getSubscriptionNotification().getType()) { case 1: // SUBSCRIPTION_RECOVERED handleRecovery(userId, token); break; case 2: // SUBSCRIPTION_RENEWED handleRenewal(userId, token); break; case 3: // SUBSCRIPTION_CANCELED handleCancellation(userId, token); break; case 4: // SUBSCRIPTION_PURCHASED handleNewSubscription(userId, token); break; // 其他类型处理... }特别注意谷歌会在订阅到期前7天发送警告通知类型7。我们利用这个特性实现了续费提醒功能使续订率提升了15%。7. 上线验证的终极 checklist7.1 测试环境验证要点使用android.test.purchased等保留产品ID进行测试验证所有错误场景无效签名已消费的订单伪造的购买凭证模拟网络中断时的重试机制7.2 生产环境灰度方案我们采用的渐进式上线策略先对1%用户开放谷歌支付监控以下核心指标验证API成功率通知处理延迟订单状态一致性全量前进行最终检查# 验证服务账号权限 gcloud auth list # 检查Pub/Sub订阅状态 gcloud pubsub subscriptions describe your-subscription记得在Google Play控制台配置应用内消息当验证失败时可以给用户友好的提示而不是技术错误。这个细节让我们客服工单减少了30%。

更多文章