Google Pay JAVA后端处理

前言:最近接了个需求,关于谷歌支付的处理流程。觉得有必要记录下来,在网上也找了很多资料,不 全。怎么个不全法呢?

*第一:很多人用的方法就是使用谷歌的publicKey来校验订单,然而使用publicKey去验证只是返回一个布尔值,验证购买成功或失败。拿不到订单信息,怎么处理?

*第二:文档的更新,google后台的更新,当时官方文档没有更新。所以如果第一次配置谷歌后台的话,会有很多坑。

好了废话不多说,进入正题吧。关于谷歌支付整体的流程:
①前端下单
②后台产生预订单
③前端调用谷歌进行支付
④支付成功谷歌返回凭证
⑤前端调用后台接口
⑥后台拿到凭证调用谷歌进行校验
⑦校验成功改变订单状态(最后就是处理成功后需要做的业务啦)

1、上面只是说了正常的校验。万一调用callback接口失败了,需要下一次调用,但是拿不到订单了(因为是下单的时候返回的订单号)。所以callback需要做一个补单操作!!!
2、用户要是在谷歌商店对商品的改动,比如说对订阅包的改动,进行一个升降级操作,此时需要让谷歌通知后端商品信息变更啦!所以谷歌推出实时开发者通知!

好了接下来出教程吧!!
一、在google后台创建应用,配置信息。具体配置可参照官网(详情步骤网上也是一找一大堆)
参考此博客:https://www.cnblogs.com/kevin-zou/p/4533091.html
我用的是v3版本

接下来是代码:
商品我们有两种类型:消耗跟订阅!

//请求谷歌api,获取SubscriptionPurchase对象try {ClassPathResource classPathResource = new ClassPathResource(googleP12);InputStream input = classPathResource.getInputStream();HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();PrivateKey privateKey = SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(),input, //文件流"notasecret", "privatekey", "notasecret");GoogleCredential credential = new GoogleCredential.Builder().setTransport(transport).setJsonFactory(JacksonFactory.getDefaultInstance()).setServiceAccountId(serviceAccountEmail) // 替换掉serviceAccountEmail.setServiceAccountScopes(AndroidPublisherScopes.all()).setServiceAccountPrivateKey(privateKey).build();AndroidPublisher publisher = new AndroidPublisher.Builder(transport,JacksonFactory.getDefaultInstance(), credential).build();AndroidPublisher.Purchases.Subscriptions subscriptions = publisher.purchases().subscriptions();AndroidPublisher.Purchases.Subscriptions.Get subscription = subscriptions.get(packageName, subscriptionId, purchaseToken);purchase = subscription.execute();} catch (Exception e) {logger.info("[请求谷歌api出错了]");e.printStackTrace();}

以上就是请求谷歌产生订单的逻辑,获取的SubscriptionPurchase对象(此对象为订阅类型)。
如果为消耗:

 ClassPathResource classPathResource = new ClassPathResource(googleP12);InputStream input = classPathResource.getInputStream();logger.info(">>>destFilePath4"+classPathResource);HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();PrivateKey privateKey = SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(),input, //文件流"notasecret", "privatekey", "notasecret");GoogleCredential credential = new GoogleCredential.Builder().setTransport(transport).setJsonFactory(JacksonFactory.getDefaultInstance()).setServiceAccountId(serviceAccountEmail) // 替换掉serviceAccountEmail.setServiceAccountScopes(AndroidPublisherScopes.all()).setServiceAccountPrivateKey(privateKey).build();AndroidPublisher publisher = new AndroidPublisher.Builder(transport,JacksonFactory.getDefaultInstance(), credential).build();AndroidPublisher.Purchases.Products products = publisher.purchases().products();AndroidPublisher.Purchases.Products.Get product = products.get(body.getString("googlePackageName"), productId, purchaseToken);logger.info("【正在向GOOGLE消耗型商品 API发请求,请稍后...】");purchase = product.execute();logger.info(">>>purchase"+purchase.toString());

其实请求的逻辑是一样的,返回的对象不一样而已。获取ProductPurchase对象。
介绍请求参数:
googleP12:谷歌应用生成的P12文件,放在类路径下;
serviceAccountEmail:谷歌应用生成的serviceAccount;
packageName:谷歌应用的packageName;
productId:谷歌的产品Id;
purchaseToken:购买凭证;
前三个写死,后两个需要前端携带。
详情请参考官网:https://developers.google.com/android-publisher/api-ref/purchases/subscriptions(需要外网)没有外网的同学我直接给你copy出来啦!!!

然后根据Purchase的状态走相应的逻辑啦!

如果商品一昧的为消耗型类型的,那就结束啦!
但是订阅类型时,有很多功能:
自动续订、退订、升降级 这时需要怎么做?前端是不知道这些变化的。后台也不知道。所以需要谷歌通知。
详情请参照官方文档:https://developer.android.com/google/play/billing/realtime_developer_notifications.html#setup_cloud_pubsub
其实就是Pub/Sub模式订阅消费。
详情配置:https://blog.csdn.net/diaomeng11/article/details/103238253(其实就是拷贝官网)
配置好订阅的之后,该配置消费了

URL填写通知接口的全路径

 @RequestMapping(value = "/googleNotify")public String googleNotify(@RequestBody(required = false) byte[] body,HttpServletRequest request, HttpServletResponse response) {logger.info("【正在进入google play 实时开发通知入口,请稍后.......】");String paramStr = null;long time = System.currentTimeMillis() + 1000 * 60;String redisLock = String.format(RedisKeyUtil.REDIS_GOOGLE_PAY_TIME_LOCK);boolean isLock = redisUtils.lock(redisLock, String.valueOf(time));if(isLock) {try {//获取返回的内容,是一个字节数组paramStr = new String(body, "utf-8");//回调具体内容见下方logger.debug("googleNotify params :{}", paramStr);googleService.googleNotify(paramStr);} catch (Exception e) {e.printStackTrace();}finally {//释放锁redisUtils.unlock(redisLock, String.valueOf(time));}}return null;}
@Override@Transactional(rollbackFor = Exception.class)public void googleNotify(String paramStr) {if (StringUtils.isNotBlank(paramStr)){JSONObject paramJson = new JSONObject();try {paramJson = JSONObject.parseObject(URLDecoder.decode(paramStr,"utf-8"));/*** paramJson数据格式如下* "message": {*         "data": "ewogICAgInZlcnNpb24iOiAiMS4wIiwKICAgICJwYWNrYWdlTmFtZSI6ICLljIXlkI0iLAogICAgImV2ZW50VGltZU1pbGxpcyI6ICLml7bpl7TmiLMo5q+r56eSKSIsCiAgICAic3Vic2NyaXB0aW9uTm90aWZpY2F0aW9uIjogewogICAgICAgICJ2ZXJzaW9uIjogIjEuMCIsCiAgICAgICAgIm5vdGlmaWNhdGlvblR5cGUiOiA0LAogICAgICAgICJwdXJjaGFzZVRva2VuIjogIuaUr+S7mOS7pOeJjCIsCiAgICAgICAgInN1YnNjcmlwdGlvbklkIjogIuiuoumYheeahOWVhuWTgWlkIgogICAgfQp9",*         "messageId": "消息id",*         "message_id": "消息id",*         "publishTime": "2019-11-14T03:58:43.608Z",*         "publish_time": "2019-11-14T03:58:43.608Z"*     },*/JSONObject msgJson = paramJson.getJSONObject("message");String data = msgJson.getString("data");logger.debug("googleNotify data :{}",paramStr);//data 是由base64加密,解密即可String developerNotificationStr = new String(Base64.getDecoder().decode(data), "UTF-8");JSONObject developerNotificationJson = JSONObject.parseObject(developerNotificationStr);/*** developerNotificationJson数据格式如下*  {*      "version":"1.0",*       "packageName":"包名",*       "eventTimeMillis":"时间戳(毫秒)",*       "subscriptionNotification":{*          "version":"1.0",*          "notificationType":4,*          "purchaseToken":"支付令牌",*          "subscriptionId":"订阅的商品id"*      }*  }*/String packageName = developerNotificationJson.getString("packageName");JSONObject subscriptionNotificationJson = developerNotificationJson.getJSONObject("subscriptionNotification");logger.info("订阅后的收到的通知信息"+subscriptionNotificationJson.toJSONString());String purchaseToken = subscriptionNotificationJson.getString("purchaseToken");String subscriptionId = subscriptionNotificationJson.getString("subscriptionId");/*** notificationType    int* 通知的类型。它可以具有以下值:* (1) SUBSCRIPTION_RECOVERED - 从帐号保留状态恢复了订阅。* (2) SUBSCRIPTION_RENEWED - 续订了处于活动状态的订阅。* (3) SUBSCRIPTION_CANCELED - 自愿或非自愿地取消了订阅。如果是自愿取消,在用户取消时发送。* (4) SUBSCRIPTION_PURCHASED - 购买了新的订阅。* (5) SUBSCRIPTION_ON_HOLD - 订阅已进入帐号保留状态(如已启用)。* (6) SUBSCRIPTION_IN_GRACE_PERIOD - 订阅已进入宽限期(如已启用)。* (7) SUBSCRIPTION_RESTARTED - 用户已通过“Play”>“帐号”>“订阅”重新激活其订阅(需要选择使用订阅恢复功能)。* (8) SUBSCRIPTION_PRICE_CHANGE_CONFIRMED - 用户已成功确认订阅价格变动。* (9) SUBSCRIPTION_DEFERRED - 订阅的续订时间点已延期。* (10) SUBSCRIPTION_PAUSED - 订阅已暂停。* (11) SUBSCRIPTION_PAUSE_SCHEDULE_CHANGED - 订阅暂停计划已更改。* (12) SUBSCRIPTION_REVOKED - 用户在有效时间结束前已撤消订阅。* (13) SUBSCRIPTION_EXPIRED - 订阅已过期。*/int notificationType = subscriptionNotificationJson.getIntValue("notificationType");SubscriptionPurchase purchase = googleNotifyGetGoogleOrder(notificationType,packageName,subscriptionId,purchaseToken);logger.info("实时开发通知SubscriptionPurchase"+purchase.toString());if(!purchase.isEmpty()){/** 回调后对应的购买流程 **/if(4 == notificationType) {         //SUBSCRIPTION_PURCHASED - 购买了新的订阅。logger.info("【购买了新订阅 不做处理】----"+purchase.toString());//需要查出是哪个用户,查出哪个产品}else if(3 == notificationType) {   //SUBSCRIPTION_CANCELED - 自愿或非自愿地取消了订阅。如果是自愿取消,在用户取消时发送。logger.info("【取消了订阅】----"+purchase.toString());//取消了自动订阅改变状态}else if(12 == notificationType) {  //SUBSCRIPTION_REVOKED - 用户在有效时间结束前已撤消订阅。logger.info("【撤销了订阅】----"+purchase.toString());}else if(2 == notificationType) {   //SUBSCRIPTION_RENEWED - 续订了处于活动状态的订阅。  其实就是续订了logger.info("【续订了订阅】----"+purchase.toString());//根据现在请求谷歌的googleOrderId获取续订包的订单,获取用户跟productId//续订的谷歌订单是有规则的,比如你第一次购买产生的订单是GPA.123-456-789,那第一次续订的产生的订单号是GPA.123-456-789..0,第二次GPA.123-456-789..1,依次类推。//续订这里还有一个坑,当订阅包降级购买的时候,谷歌并不是通知你购买了新订阅包而是续订,比如你购买的高级订阅包是GPA.123-456-789,当你切换低级的订阅包的时候订单号为GPA.111-222-333..0,你会发现订单号变了,这时候就难办了,拿不到之前用户的信息(谷歌返回的订单参数是没有项目里面用户信息的),经过联调发现,他们两个订单的linkedPurchaseToken是一样的,可以参考上图中参数的介绍//当降级和自动续订的时候都会走这个通知,所以里面有两个逻辑,需要自己去判断,这两者的不同上面都写的很清楚了。}else if(7 == notificationType) {   //SUBSCRIPTION_RESTARTED - 用户已通过“Play”>“帐号”>“订阅”重新激活其订阅(需要选择使用订阅恢复功能)logger.info("【重新注册了订阅】----"+purchase.toString());//用户在谷歌后台重新激活的话,purchaseToken是不变的,我们只需通过Token找出之前的记录更改状态,过期时间是不变的}else if(6 == notificationType) {  //订阅已进入宽限期(如已启用)。logger.info("【订阅已进入宽限期】----"+purchase.toString());}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}}}

以下为请求谷歌获取订单信息:

  /*** 访问谷歌api获取订阅订单* @param notificationType* @param packageName* @param subscriptionId* @param purchaseToken* @return*/private SubscriptionPurchase googleNotifyGetGoogleOrder(int notificationType, String packageName, String subscriptionId, String purchaseToken){SubscriptionPurchase purchase = new SubscriptionPurchase();if(notificationType > 0) {//请求谷歌api,获取SubscriptionPurchase对象try {ClassPathResource classPathResource = new ClassPathResource(googleP12);InputStream input = classPathResource.getInputStream();HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();PrivateKey privateKey = SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(),input, //文件流"notasecret", "privatekey", "notasecret");GoogleCredential credential = new GoogleCredential.Builder().setTransport(transport).setJsonFactory(JacksonFactory.getDefaultInstance()).setServiceAccountId(serviceAccountEmail) // 替换掉serviceAccountEmail.setServiceAccountScopes(AndroidPublisherScopes.all()).setServiceAccountPrivateKey(privateKey).build();AndroidPublisher publisher = new AndroidPublisher.Builder(transport,JacksonFactory.getDefaultInstance(), credential).build();AndroidPublisher.Purchases.Subscriptions subscriptions = publisher.purchases().subscriptions();AndroidPublisher.Purchases.Subscriptions.Get subscription = subscriptions.get(packageName, subscriptionId, purchaseToken);purchase = subscription.execute();} catch (Exception e) {logger.info("[请求谷歌api出错了]");e.printStackTrace();}}return purchase;}

看到这里伙伴们是不是很清楚了呢?

我在项目中是写了三种方法去处理的!
⒈一个正常回调也就是callback,但他只能处理购买时的回调只针对于升级。如果是降级,高级包要在失效之后才能切换到你购买的低级包。所以,高级包在有效期内你去做降级操作,谷歌是没有产生订单的,需要等高级订阅包失效。
⒉实时开发者通知,对续订,退订,升降级操作的处理。(注意升级购买的时候实时开发者通知不处理,全都交由callback处理)
⒊定时器开发,因为每一次对谷歌订单的处理你都需要保存谷歌订单的信息,定时器会查询你所保存的购买凭证也就是linkedPurchaseToken去请求谷歌看看有没有订单生成,有没有自动续订。

当你这样做的话,就会全面覆盖谷歌支付的处理啦。
但是!没错还有但是,当自动续订的时候万一,定时器跟实时开发者通知并发了怎么办???
所以你需要加锁,谁先抢到就谁处理,(处理了不是会保存订单信息吗?所以加了锁还要进行个判断,判断该订单有没有处理)这样就就可以啦!!!!

哈哈,说到这会不会觉得茅塞顿开了呢!!!哈哈
第一次写,写的不好,不详细的请见谅,,,
如果还是不懂的可以私信我哦,记住我就是会撩头发的程序员!!!!!

关于Google Pay JAVA后端处理相关推荐

  1. Java后端WebSocket的Tomcat实现

    转自: http://blog.chenzuhuang.com/archive/28.html http://www.cnblogs.com/xdp-gacl/p/5193279.html 一.Web ...

  2. Java后端WebSocket的Tomcat实现(转载)

    一.WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通 ...

  3. Android端+java后端+servlet+MySQL的型男塑造平台【前后端源代码+论文+答辩ppt】

    活动地址:毕业季·进击的技术er 目录 前言 第一章 绪论 1.1 背景和意义 1.2 国内外研究现状 1.3 论文研究目标与内容 1.4.减肥瘦身相关概念与计算方式介绍 第二章 需求分析 2.1 平 ...

  4. golang版google pay 支付验证

    今天接google pay的支付,但是找了好多都是PHP或java的,并没找到go版本的.而官方文档说明也不详细. 最终参考一些代码之后完成go版本的验证,以下是代码实现 const publicKe ...

  5. Google Pay接入

    1.由于业务需求,准备接入Google Pay,一开始本人接到这个需求的时候,就开始到Google Pay官网以及Google.百度上搜索如何接入Google Pay,也是有发现一些文章.在我处理了一 ...

  6. 完整流程Google Pay 接入

    前一段时间由于项目需求,产品需要接入Google Pay SDK,然后....大家都懂的...各种搜索,出现的文章要么就是很久以前的,要么就是各种问题,经过一番"泥里打滚"后,还是 ...

  7. Android 接入google pay

    文章目录 google pay google play Billing 支持的一次性产品 商品购买流程 google pay 实现流程 1. 添加依赖 2. 连接到Google Play 3. 查询商 ...

  8. 4000字干货长文!从校招和社招的角度说说如何准备大厂Java后端面试?

    我觉得每一个技术人的梦想大概就是能够找一份大厂的 offer,我觉得这很正常,这并不是我们的饭后谈资而是每个技术人的追求.像阿里.腾讯.美团.字节跳动.京东等等大厂的技术氛围还是要明显优于一些创业型公 ...

  9. Java 后端技术清单 2023版

    后端开发需要掌握的知识,从入门到精通之路 JAVA后端技术清单 文章目录 JAVA后端技术清单 1. 开发环境 2. 基础知识 4. 编码规范及技术博客 5. 动⼿实践 1. 开发环境 JDK: 版本 ...

最新文章

  1. Logistic Regression(逻辑回归) +python3.6(pycharm)实现
  2. Spark SQL之External DataSource外部数据源(二)源代码分析
  3. LAMP环境安装1之php编译报错
  4. QT示例:基于TCP 点对多Socket通讯(server,clients)
  5. Spark流编程指引(三)-------------------------------------初始化StreamingContext
  6. Python批量提取PowerPoint文件中所有幻灯片标题和备注文本
  7. 阿正喜欢的演讲系列连载(一):大法官约翰·罗伯茨-我祝你不幸(I Wish You Bad Luck)
  8. Fortify 5.1漏洞整改方案
  9. 关于打开网页FLASH显示叉叉问题
  10. Thingworx自定义扩展开发(二)- Widget Demo Coding
  11. 读取XML文档解析信息
  12. 工科学术论文书写攻略
  13. GICv3软件overview手册之虚拟化
  14. Eclipse下用JFS和Tomcat防问SQLServer数据库的以下配置
  15. JVM系列之:MAT工具使用教程
  16. 为什么华为的工程师也要去考公务员???
  17. Python/Pandas Categorical对象labels和levels属性已弃用,改为codes和categories
  18. 江苏大学计算机学院在校生,江苏大学计算机科学与通信工程学院
  19. V2G背景201707051926
  20. vivo手机哪款性价比高质量好 2022

热门文章

  1. al输入标题自动写作
  2. 头条校招(今日头条2017秋招真题)
  3. 系统可打开最大文件数过小,导致CHECK_NRPE: Error - Could not complete SSL handshake
  4. 【Python for Everybody(Python Data Structures)】Week 4 | Chapter 8 题目汇总
  5. 机器学习--K近邻算法(KNN)(2)
  6. 牛客网 赛码在线编程中数据读取问题
  7. 关于最新版mumu模拟器(2.2.16)安装xposed框架
  8. 【GitHub或GitLab rejected】error: failed to push some refs to,Updates were rejected...
  9. 什么输入法对计算机英语,电脑怎么把英文输入法设置为默认输入法
  10. Minor GC、Young GC、Old GC、Major GC、Mixed GC、Full GC都是什么?