Google Play 内购实现

本文写于 2024-1-26

接上一次苹果内购记录-CSDN博客,我又接了一把 Google Play 内购。

Google Play 内购有啥好说的?一般人都不用。

只能说时代变了,没想到啊没想到,Google Play 这个浓眉大眼的的家伙也叛变了。

付款 - Play 管理中心帮助 (google.com)

你看,真叛变了吧?

1. 准备事项

  1. Google Play 账号
  2. Google Cloud 账号
  3. 付款信息
  4. 提供一个账号给开发
  5. 配置内购产品信息:名称和价格这些属性

嘿嘿,以上这些事情统统交给产品。如果没有产品,那就交给老板吧。

如果他们不愿意,和他们说的时候带上这个

3. 开搞前缕缕思路

思路和苹果内购应该是差不多的,毕竟苹果内购"珠玉在前"嘛。

和苹果内购一样,这里最关键的一步是:【完成交易】。如果这一步出问题,那就是用户给钱了,但没给用户发服务。和苹果一脉相承的掉单问题。老规矩:

第一步:穿上西装站直 第二步:弯腰90度 第三步:虔诚地说出:su mi ma sen 第四步:请不要着急,如果没收到权益,3天后自动给您退款

Google Play 这波是被掉单搞怕了,哈哈。

言归正传:

第一步发现问题:

  • 用户写邮件来对线了
  • 每天查一下 Google Play 后台交易成功的数量和后台数据库交易成功的数量能不能对上
  • 客户端和服务端加上日志

第二步排查问题:

  • 用户找你的时候要他提供订单号,然后可以通过Google Play 后台查询到这笔订单的信息
  • Google Play 后台数据和服务后台数据对不上的时候赶紧查日志去

第三步解决问题:

  • 客户端做另外的掉单处理,比如说轮训查还有没有交易是没完成的,然后重新提交服务端
  • 手动给用户补发
  • 退款(Google Play 后台可以退,终于不用被别人掐住脖子了)

4. 差不多了,开搞

官方整合文档:Google Play 结算系统 | Google Play's billing system | Android Developers

4.1 准备

  • 创建一个 Google Cloud Project,并启用相关API和服务
  • Google Play 应用关联 Google Cloud Project
  • Google Cloud 创建ServiceAccount服务账号
  • Google Play 添加服务账号,并分配权限
  • Google Cloud Pub/Sub创建主题以及订阅
  • Google Play 启用实时通知,并配置Google Cloud 中创建的主题
  1. 创建Google Cloud Project,并启用相关API和服务
  1. Google Play 中关联 Google Cloud Project
  1. Google Cloud 创建ServiceAccount服务账号

创建完账号,把密钥也创建好,下载到本地

  1. Google Play 添加服务账号,并分配权限

把创建好的账号填进来

这一步完成,其实就可以使用服务账号的密钥信息调用 Google Play API了。下面只是为了接收Google Play 通知的时候用的,不用的可以不看,当然我建议你看看,不然用户退款的时候,我估计老板会找你。

Google Play 把产品购买分成两种类型:

  • 一次性购买
  • 订阅

对于一次性购买来说,实时通知只是为了接收退款的回调,购买成功是没有回调的,喵的,文档里面写了有事件,实际又没发。这个破问题了困扰了我一天,最后社区问到的答案。

RTDN messages are not sent to Topic when using Test licences - Google Play Developer Community(*...+#@>此处省略1800个字)

对于订阅来说,不说了。我没做订阅,你自己去看吧。

订阅生命周期 | Google Play's billing system | Android Developers

  1. Google Cloud Pub/Sub创建主题以及订阅

给Google Play 固定的服务账号加权限,注意这个账号是一个固定的,不是刚刚创建的哪个服务账号 google-play-developer-notifications@system.gserviceaccount.com

  1. Google Play 启用实时通知,并配置Google Cloud 中创建的主题

点击发送测试通知,测试一下。然后在 Pub/Sub 中看看能不能拉取到消息

没问题之后,把订阅的类型改成推送,这样就能通过http发送到我们的服务了。

重试策略看需要调整,感觉退避算法合理点。这个自己选择,也就是出问题你自己背锅的意思。

4.2 交易凭证验证

准备就绪,正片上代码。

  1. 依赖导入
xml 复制代码
<dependency>  
    <groupId>com.google.auth</groupId>  
    <artifactId>google-auth-library-oauth2-http</artifactId>  
    <version>1.19.0</version>  
</dependency>  
<dependency>  
    <groupId>com.google.apis</groupId>  
    <artifactId>google-api-services-androidpublisher</artifactId>  
    <version>v3-rev20231115-2.0.0</version>  
</dependency>
  1. 配置和配置类
properties 复制代码
google-play.packageName=com.xxx.xxx
# 这个json就是刚刚创建服务账号密钥的时候下载的
google-play.serviceAccountJson=xxxxx.json
java 复制代码
@Data  
@Configuration  
@ConfigurationProperties(prefix = "google-play")  
public class GooglePlayConfig {  
    private String packageName;  
    private String serviceAccountJson;  
  
    @Bean  
    public GoogleCredentials googleCredentials() throws IOException {  
	    // 懒得搞配置文件了,直接丢resources读进来
        return GoogleCredentials.fromStream(new ClassPathResource(serviceAccountJson).getInputStream())  
                .createScoped(AndroidPublisherScopes.ANDROIDPUBLISHER);  
    }  
  
    @Bean  
    public AndroidPublisher androidPublisher(GoogleCredentials credentials) throws IOException, GeneralSecurityException {  
        return new AndroidPublisher.Builder(  
                GoogleNetHttpTransport.newTrustedTransport(),  
                GsonFactory.getDefaultInstance(),  
                new HttpCredentialsAdapter(credentials)  
        ).setApplicationName(packageName).build();  
    }  
}
  1. 验签逻辑,其实就是根据凭证查下这笔单在不在
java 复制代码
@Component  
@Slf4j  
public class GooglePlayComponent {  
    @Resource  
    private GooglePlayConfig googlePlayConfig;
    @Resource  
    private AndroidPublisher androidPublisher;// 注入进来咔咔用  
  
    public ProductPurchase productPurchase(String sku, String purchaseToken) {  
        try {  
            return androidPublisher  
                    .purchases().products()  
                    .get(googlePlayConfig.getPackageName(), sku, purchaseToken)  
                    .execute();  
        } catch (IOException e) {  
            log.error("failed to query product purchase. {}", purchaseToken, e);  
            ServiceException.throwInternalServerEx("failed to query product purchase:" + purchaseToken);  
            return null;  
        }  
    }  
}

4.3 接收回调

java 复制代码
@PostMapping("/google_play_webhook")
public Object googlePlayWebhook(@RequestBody String body) {
    log.info("Google play subscription webhook: {}", body);

    if (StringUtils.isBlank(body)) {
        log.warn("Google play subscription webhook body is EMPTY");
        return ResponseEntity.status(400).body("Empty body");
    }

    DeveloperNotification developerNotification;
    try {
        developerNotification = JacksonUtils.parseJson(body, DeveloperNotification.class);
    } catch (Exception e) {
        log.error("failed to parse body. {}", body, e);
        return ResponseEntity.status(400).body(e.getMessage());
    }
	// TODO 处理过程自己写去

    return ResponseEntity.ok().body("OK");
}

Ref

官方集成文档:做好准备 | Google Play's billing system | Android Developers

回调的一些Bean代码:blog.csdn.net/jack2350536...

一次性购买无法收到回调问题: support.google.com/googleplay/...

相关推荐
源代码•宸1 分钟前
Leetcode—1929. 数组串联&&Q1. 数组串联【简单】
经验分享·后端·算法·leetcode·go
smileNicky40 分钟前
SpringBoot系列之集成Pulsar教程
java·spring boot·后端
小翰子_1 小时前
Spring Boot整合Sharding-JDBC实现日志表按月按周分表实战
java·spring boot·后端
踏浪无痕2 小时前
SQLInsight:从JDBC底层到API调用的零侵入SQL监控方案
数据库·后端·开源
superman超哥3 小时前
Rust HashSet与BTreeSet的实现细节:集合类型的底层逻辑
开发语言·后端·rust·编程语言·rust hashset·rust btreeset·集合类型
superman超哥4 小时前
Rust String与&str的内部实现差异:所有权与借用的典型案例
开发语言·后端·rust·rust string·string与str·内部实现·所有权与借用
愈努力俞幸运4 小时前
rust安装
开发语言·后端·rust
踏浪无痕4 小时前
JobFlow 负载感知调度:把任务分给最闲的机器
后端·架构·开源
UrbanJazzerati4 小时前
Python自动化统计工具实战:Python批量分析Salesforce DML操作与错误处理
后端·面试
我爱娃哈哈4 小时前
SpringBoot + Seata + Nacos:分布式事务落地实战,订单-库存一致性全解析
spring boot·分布式·后端