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去请求谷歌看看有没有订单生成,有没有自动续订。
当你这样做的话,就会全面覆盖谷歌支付的处理啦。
但是!没错还有但是,当自动续订的时候万一,定时器跟实时开发者通知并发了怎么办???
所以你需要加锁,谁先抢到就谁处理,(处理了不是会保存订单信息吗?所以加了锁还要进行个判断,判断该订单有没有处理)这样就就可以啦!!!!
哈哈,说到这会不会觉得茅塞顿开了呢!!!哈哈
第一次写,写的不好,不详细的请见谅,,,
如果还是不懂的可以私信我哦,记住我就是会撩头发的程序员!!!!!