googleChargeProdIds;
+}
diff --git a/accompany-base/accompany-payment/src/main/java/com/accompany/payment/google/AndroidPublisherHelper.java b/accompany-base/accompany-payment/src/main/java/com/accompany/payment/google/AndroidPublisherHelper.java
new file mode 100644
index 000000000..7f3dbf46d
--- /dev/null
+++ b/accompany-base/accompany-payment/src/main/java/com/accompany/payment/google/AndroidPublisherHelper.java
@@ -0,0 +1,107 @@
+package com.accompany.payment.google;
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
+import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.client.util.Preconditions;
+import com.google.api.client.util.Strings;
+import com.google.api.services.androidpublisher.AndroidPublisher;
+import com.google.api.services.androidpublisher.AndroidPublisherScopes;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.util.Assert;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to initialize the publisher APIs client library.
+ *
+ * Before making any calls to the API through the client library you need to
+ * call the {@link AndroidPublisherHelper#init(String, String, String, String)} method. This will run
+ * all precondition checks for for client id and secret setup properly in
+ * resources/client_secrets.json and authorize this client against the API.
+ *
+ */
+public class AndroidPublisherHelper {
+
+ private static final Log log = LogFactory.getLog(AndroidPublisherHelper.class);
+
+ /** Global instance of the JSON factory. */
+ private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
+
+ /** Global instance of the HTTP transport. */
+ private static HttpTransport HTTP_TRANSPORT;
+
+ private static volatile AndroidPublisher androidPublisher;
+
+ private static volatile String oldApplicationName;
+
+ private static volatile String oldCredentialJson;
+
+
+ public static AndroidPublisher init(String applicationName, String json) throws IOException, GeneralSecurityException {
+ if (needInit(applicationName, json)) {
+ synchronized (AndroidPublisherHelper.class) {
+ if (needInit(applicationName, json) || androidPublisher == null) {
+ log.info("start init AndroidPublisher");
+ Preconditions.checkArgument(!Strings.isNullOrEmpty(applicationName),
+ "applicationName cannot be null or empty!");
+
+ List scopes = new ArrayList<>();
+ scopes.add(AndroidPublisherScopes.ANDROIDPUBLISHER);
+ ByteArrayResource resource = new ByteArrayResource(json.getBytes());
+ GoogleCredential credential = GoogleCredential.fromStream(resource.getInputStream())
+ .createScoped(scopes);
+
+ newTrustedTransport();
+
+ //使用谷歌凭据和收据从谷歌获取购买信息
+ androidPublisher = new AndroidPublisher.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
+ .setApplicationName(applicationName)
+ .build();
+
+ oldApplicationName = applicationName;
+ oldCredentialJson = json;
+ }
+ }
+ }
+
+ return androidPublisher;
+ }
+
+ /**
+ * 判断是否需要初始化
+ * @param applicationName
+ * @param credentialJsonPath
+ * @return
+ */
+ private static boolean needInit(String applicationName, String credentialJsonPath) {
+ boolean firstInit = AndroidPublisherHelper.oldApplicationName == null || AndroidPublisherHelper.oldCredentialJson == null;
+
+ // json配置修改后,重新初始化
+ boolean configChanged = StringUtils.isNotBlank(AndroidPublisherHelper.oldCredentialJson)
+ && !AndroidPublisherHelper.oldCredentialJson.equals(credentialJsonPath);
+
+ return firstInit || configChanged;
+ }
+
+ private static void newTrustedTransport() throws GeneralSecurityException,
+ IOException {
+ if (null == HTTP_TRANSPORT) {
+ HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
+ }
+ }
+
+ public static AndroidPublisher getPublisher() {
+ Assert.notNull(androidPublisher, "should init before get bean");
+ return androidPublisher;
+ }
+
+}
diff --git a/accompany-base/accompany-payment/src/main/java/com/accompany/payment/google/GooglePlayBillingService.java b/accompany-base/accompany-payment/src/main/java/com/accompany/payment/google/GooglePlayBillingService.java
new file mode 100644
index 000000000..fb0b137a0
--- /dev/null
+++ b/accompany-base/accompany-payment/src/main/java/com/accompany/payment/google/GooglePlayBillingService.java
@@ -0,0 +1,200 @@
+package com.accompany.payment.google;
+
+import com.accompany.common.constant.Constant;
+import com.accompany.common.redis.RedisKey;
+import com.accompany.common.status.BusiStatus;
+import com.accompany.common.utils.DateTimeUtil;
+import com.accompany.common.utils.UUIDUitl;
+import com.accompany.core.exception.ServiceException;
+import com.accompany.core.model.Users;
+import com.accompany.core.service.SysConfService;
+import com.accompany.core.service.user.UsersBaseService;
+import com.accompany.payment.dto.AppInnerPayRecordDTO;
+import com.accompany.payment.dto.GooglePayLimitConfigDTO;
+import com.accompany.payment.model.ChargeProd;
+import com.accompany.payment.model.ChargeRecord;
+import com.accompany.payment.service.ChargeProdService;
+import com.accompany.payment.service.ChargeRecordService;
+import com.alibaba.fastjson.JSONObject;
+import com.google.api.services.androidpublisher.model.ProductPurchase;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * google 内购
+ */
+@Service
+@Slf4j
+public class GooglePlayBillingService {
+
+ @Autowired
+ private RedissonClient redissonClient;
+ @Autowired
+ private ChargeRecordService chargeRecordService;
+ @Autowired
+ private ChargeProdService chargeProdService;
+ @Autowired
+ private SysConfService sysConfService;
+ @Autowired
+ private UsersBaseService usersBaseService;
+
+ protected String getPayChannel() {
+ return Constant.ChargeChannel.google_play_billing;
+ }
+
+ public AppInnerPayRecordDTO placeOrder(Long uid, String chargeProdId, String clientIp, String deviceId) {
+ validMoneyLimit(uid, chargeProdId);
+
+ RLock lock = redissonClient.getLock(RedisKey.lock_apply_charge.getKey(uid.toString()));
+ try {
+ lock.tryLock(10, TimeUnit.SECONDS);
+
+ ChargeProd chargeProd = chargeProdService.getChargeProdById(chargeProdId);
+ if (chargeProd == null) {
+ log.error("充值产品不存在,prodId: {} ", chargeProdId);
+ throw new ServiceException(BusiStatus.CHARGE_PROD_NOT_EXIST);
+ }
+
+ String payChannel = getPayChannel();
+ log.info("用户 {} 内购充值,渠道: {}", uid, payChannel);
+ //保存充值记录
+ //1.创建订单号
+ //UUID不会重复,所以不需要判断是否生成重复的订单号
+ String chargeRecordId = UUIDUitl.get();
+
+ Users users = usersBaseService.getUsersByUid(uid);
+ //Integer region = users.getRegion() == null ? Constant.region.overseas : users.getRegion();
+
+ ChargeRecord chargeRecord = new ChargeRecord();
+ chargeRecord.setChargeRecordId(chargeRecordId);
+ chargeRecord.setChargeProdId(chargeProdId);
+ chargeRecord.setUid(uid);
+ //chargeRecord.setRegion(region.byteValue());
+ chargeRecord.setChannel(payChannel);
+ chargeRecord.setChargeStatus(Constant.ChargeRecordStatus.create);
+ // product中money单位为分
+ chargeRecord.setAmount(chargeProd.getMoney());
+ chargeRecord.setSubject(chargeProd.getProdName());
+ chargeRecord.setBody(chargeProd.getProdName());
+ chargeRecord.setClientIp(clientIp);
+
+ //写入数据库
+ chargeRecordService.insertChargeRecord(chargeRecord);
+ log.info("用户 {} 内购充值,本地订单号: {}", uid, chargeRecordId);
+
+ //订单创建成功返回订单号
+ AppInnerPayRecordDTO recordIdVo = new AppInnerPayRecordDTO();
+ recordIdVo.setRecordId(chargeRecordId);
+ return recordIdVo;
+
+ } catch (InterruptedException e) {
+ throw new ServiceException(BusiStatus.SERVERBUSY);
+ } finally {
+ if (lock.isLocked()){
+ lock.unlock();
+ }
+ }
+
+ }
+
+ private void validMoneyLimit(Long uid, String chargeProdId) {
+ GooglePayLimitConfigDTO config = getLimitConfig();
+ Date now = new Date();
+ Date beginTimeOfDay = DateTimeUtil.getBeginTimeOfDay(now);
+ Date endTimeOfDay = DateTimeUtil.getEndTimeOfDay(now);
+
+ ChargeProd chargeProd = chargeProdService.getChargeProdById(chargeProdId);
+ if (chargeProd == null) {
+ log.error("充值产品不存在,prodId: {} ", chargeProdId);
+ throw new ServiceException(BusiStatus.CHARGE_PROD_NOT_EXIST);
+ }
+
+ Long userChargeAmmount = chargeRecordService.getChargeUserAmountWithProdIds(Collections.singletonList(uid), config.getGoogleChargeProdIds(), beginTimeOfDay, endTimeOfDay);
+ log.info("{}在{}-{}时间段内使用google内购充值了{}", uid, DateTimeUtil.convertDate(beginTimeOfDay), DateTimeUtil.convertDate(endTimeOfDay), userChargeAmmount);
+ // 配置的单位是元,充值记录和产品的数据单位为分
+ if (config.getLimitEveryOneDaySumAmount() * 100 < (chargeProd.getMoney() + userChargeAmmount)) {
+ throw new ServiceException(config.getErrorTip());
+ }
+ Long allUserChargeAmount = chargeRecordService.getChargeUserAmountWithProdIds(null, config.getGoogleChargeProdIds(), beginTimeOfDay, endTimeOfDay);
+ log.info("全部用户{}-{}内使用google内购充值了{}", DateTimeUtil.convertDate(beginTimeOfDay), DateTimeUtil.convertDate(endTimeOfDay), allUserChargeAmount);
+ if (config.getLimitEveryDayAmount() * 100 < (chargeProd.getMoney() + allUserChargeAmount)) {
+ throw new ServiceException(config.getErrorTip());
+ }
+ }
+
+ private GooglePayLimitConfigDTO getLimitConfig() {
+ GooglePayLimitConfigDTO limitConfig = JSONObject.parseObject(sysConfService.getDefaultSysConfValueById(Constant.SysConfId.GOOGLE_PAY_LIMIT_CONFIG, "{}"), GooglePayLimitConfigDTO.class);
+ if (limitConfig.getLimitEveryDayAmount() == null) {
+ limitConfig.setLimitEveryDayAmount(9999999L);
+ }
+ if (limitConfig.getLimitEveryOneDaySumAmount() == null) {
+ limitConfig.setLimitEveryOneDaySumAmount(9999999L);
+ }
+ if (limitConfig.getGoogleChargeProdIds() == null) {
+ limitConfig.setGoogleChargeProdIds(Collections.emptyList());
+ }
+ if (StringUtils.isEmpty(limitConfig.getErrorTip())) {
+ limitConfig.setErrorTip("充值失败,请联系客服处理");
+ }
+ return limitConfig;
+ }
+
+ public ChargeRecord verifyOrder(String chargeRecordId, String packageName, String googlePlayProdId, String purchaseToken) {
+ String lockKey = RedisKey.lock_pay_callback_notify.getKey(chargeRecordId);
+ RLock lock = redissonClient.getLock(lockKey);
+ try {
+ lock.tryLock(5L, TimeUnit.SECONDS);
+
+ ChargeRecord chargeRecord = chargeRecordService.getChargeRecordById(chargeRecordId);
+ if (chargeRecord == null) {
+ log.error("[google play billing]充值记录不存在。chargeRecordId: {}", chargeRecordId);
+ throw new ServiceException(BusiStatus.RECORD_NOT_EXIST);
+ }
+ if (!Constant.ChargeRecordStatus.create.equals(chargeRecord.getChargeStatus()) && !Constant.ChargeRecordStatus.error.equals(chargeRecord.getChargeStatus())) {
+ log.info("[google play billing]订单状态不是创建或错误,不进行处理。chargeRecordId: {}", chargeRecordId);
+ throw new ServiceException(BusiStatus.RECORD_ALREADY_EXIST);
+ }
+
+ if (!chargeRecord.getChargeProdId().equalsIgnoreCase(googlePlayProdId)) {
+ log.error("[google play billing]记录中的产品id和待查询内购产品id不一致。ChargeProdId: {}, googlePlayProdId: {}", chargeRecord.getChargeProdId(), googlePlayProdId);
+ throw new ServiceException(BusiStatus.RECORD_NOT_EXIST);
+ }
+
+ ProductPurchase purchase = AndroidPublisherHelper.getPublisher().purchases().products().get(packageName, googlePlayProdId, purchaseToken).execute();
+ log.info("purchase: {}", JSONObject.toJSONString(purchase));
+ if (purchase == null) {
+ log.error("查询google购买记录返回为空。packageName: {}, prodId: {}, purchaseToken: {}", packageName, googlePlayProdId, purchaseToken);
+ throw new ServiceException(BusiStatus.RECORD_NOT_EXIST);
+ }
+
+ if (!Constant.GooglePurchaseState.PURCHASED.equals(purchase.getPurchaseState())) {
+ log.error("[google play billing]订单未完成支付。当前状态: {}", purchase.getPurchaseState());
+ throw new ServiceException(BusiStatus.RECORD_NOT_EXIST);
+ }
+
+ chargeRecord.setPingxxChargeId(purchase.getOrderId());
+
+ return chargeRecord;
+
+ } catch (Exception e) {
+ log.error("[google play billing]校验google内购失败", e);
+ if (e instanceof ServiceException) {
+ throw (ServiceException)e;
+ } else {
+ throw new ServiceException(BusiStatus.BUSIERROR);
+ }
+ } finally {
+ if (lock.isLocked()){
+ lock.unlock();
+ }
+ }
+ }
+
+}
diff --git a/accompany-base/accompany-payment/src/main/java/com/accompany/payment/mapper/ChargeRecordMapperMgr.java b/accompany-base/accompany-payment/src/main/java/com/accompany/payment/mapper/ChargeRecordMapperMgr.java
index 821d9ab27..245d2fe3b 100644
--- a/accompany-base/accompany-payment/src/main/java/com/accompany/payment/mapper/ChargeRecordMapperMgr.java
+++ b/accompany-base/accompany-payment/src/main/java/com/accompany/payment/mapper/ChargeRecordMapperMgr.java
@@ -66,4 +66,8 @@ public interface ChargeRecordMapperMgr {
Long getHistoryRechargeAmountByChannel(@Param("userId") long userId, @Param("channel") String channel);
+ Long getChargeUserAmountWithProdIds(@Param("list") List uids, @Param("prodIds") List prodIds,
+ @Param("startTime") Date startTime, @Param("endTime") Date endTime);
+
+
}
diff --git a/accompany-base/accompany-payment/src/main/java/com/accompany/payment/service/ChargeRecordService.java b/accompany-base/accompany-payment/src/main/java/com/accompany/payment/service/ChargeRecordService.java
index fc8073c18..7c3991d8e 100644
--- a/accompany-base/accompany-payment/src/main/java/com/accompany/payment/service/ChargeRecordService.java
+++ b/accompany-base/accompany-payment/src/main/java/com/accompany/payment/service/ChargeRecordService.java
@@ -173,4 +173,15 @@ public class ChargeRecordService extends BaseService {
jedisLockService.unlock(RedisKey.ios__pay_user_toatl_amount_lock.getKey(userIdStr), lockVal);
}
}
+
+ /**
+ * 获取当前时间段内用户充值金额
+ **/
+ public Long getChargeUserAmountWithProdIds(List uid, List prods, Date startDate, Date endDate) {
+ Long chargeUserAmountWithProdIds = chargeRecordMapperMgr.getChargeUserAmountWithProdIds(uid, prods, startDate, endDate);
+ if (chargeUserAmountWithProdIds == null) {
+ return 0L;
+ }
+ return chargeUserAmountWithProdIds;
+ }
}
diff --git a/accompany-base/accompany-payment/src/main/resources/mapper/ChargeRecordMapper.xml b/accompany-base/accompany-payment/src/main/resources/mapper/ChargeRecordMapper.xml
index c336597fb..e3bb202e0 100644
--- a/accompany-base/accompany-payment/src/main/resources/mapper/ChargeRecordMapper.xml
+++ b/accompany-base/accompany-payment/src/main/resources/mapper/ChargeRecordMapper.xml
@@ -145,7 +145,8 @@
#{wxPubOpenid,jdbcType=VARCHAR}, #{subject,jdbcType=VARCHAR}, #{body,jdbcType=VARCHAR},
#{extra,jdbcType=VARCHAR}, #{metadata,jdbcType=VARCHAR}, #{chargeDesc,jdbcType=VARCHAR},
#{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP})
-
+
+
insert into charge_record
diff --git a/accompany-base/accompany-payment/src/main/resources/mapper/ChargeRecordMapperMgr.xml b/accompany-base/accompany-payment/src/main/resources/mapper/ChargeRecordMapperMgr.xml
index 8cf50b7eb..bec76ba45 100644
--- a/accompany-base/accompany-payment/src/main/resources/mapper/ChargeRecordMapperMgr.xml
+++ b/accompany-base/accompany-payment/src/main/resources/mapper/ChargeRecordMapperMgr.xml
@@ -283,4 +283,19 @@
select IFNULL(sum(amount)/100,0) from charge_record WHERE buss_type in (0,4) and charge_status = 2 and uid = #{userId} and channel <> 'exchange' and channel = #{channel};
+
+
+ select sum(amount) from charge_record
+ where charge_status = 2
+
+ and uid in #{uid}
+
+ and charge_prod_id in #{prodId}
+
+ and create_time > #{startTime}
+
+
+ and create_time < #{endTime}
+
+
\ No newline at end of file
diff --git a/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/apppay/GooglePlayBillingChargeController.java b/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/apppay/GooglePlayBillingChargeController.java
new file mode 100644
index 000000000..53eb3190f
--- /dev/null
+++ b/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/apppay/GooglePlayBillingChargeController.java
@@ -0,0 +1,56 @@
+package com.accompany.business.controller.apppay;
+
+import com.accompany.business.service.ChargeService;
+import com.accompany.common.annotation.Authorization;
+import com.accompany.common.utils.IPUitls;
+import com.accompany.core.enumeration.BusinessStatusCodeEnum;
+import com.accompany.core.vo.BaseRequestVO;
+import com.accompany.core.vo.BaseResponseVO;
+import com.accompany.payment.dto.AppInnerPayRecordDTO;
+import com.accompany.payment.google.GooglePlayBillingService;
+import com.accompany.payment.model.ChargeProd;
+import com.accompany.payment.model.ChargeRecord;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Api(tags = {"google内购"}, value = "google内购")
+@RestController
+@RequestMapping("/googlePlayBilling")
+@Slf4j
+public class GooglePlayBillingChargeController {
+
+ @Autowired
+ private ChargeService chargeService;
+ @Autowired
+ private GooglePlayBillingService googlePlayBillingService;
+
+ @ApiOperation("google内购预下单")
+ @PostMapping("/placeOrder")
+ @Authorization
+ public BaseResponseVO placeOrder(String chargeProdId, HttpServletRequest request) {
+ BaseRequestVO baseRequestVO = new BaseRequestVO();
+ Long uid = baseRequestVO.getMyUserId();
+ String clientIp = IPUitls.getRealIpAddress(request);
+ String deviceId = baseRequestVO.getDeviceId();
+
+ AppInnerPayRecordDTO appInnerPayRecordDTO = googlePlayBillingService.placeOrder(uid, chargeProdId, clientIp, deviceId);
+
+ return new BaseResponseVO<>(BusinessStatusCodeEnum.SUCCESS, appInnerPayRecordDTO);
+ }
+
+ @ApiOperation("google内购订单校验")
+ @PostMapping("/verifyOrder")
+ @Authorization
+ public BaseResponseVO verifyOrder(String chargeRecordId, String packageName, String googlePlayProdId, String purchaseToken) {
+ ChargeRecord chargeRecord = googlePlayBillingService.verifyOrder(chargeRecordId, packageName, googlePlayProdId, purchaseToken);
+ chargeService.updateAppPayData(chargeRecord);
+ return new BaseResponseVO<>(BusinessStatusCodeEnum.SUCCESS, BusinessStatusCodeEnum.SUCCESS.getReasonPhrase(), purchaseToken);
+ }
+}
diff --git a/accompany-business/accompany-business-web/src/test/java/servicetest/GooglePlayBillingServiceTest.java b/accompany-business/accompany-business-web/src/test/java/servicetest/GooglePlayBillingServiceTest.java
new file mode 100644
index 000000000..ec6df0237
--- /dev/null
+++ b/accompany-business/accompany-business-web/src/test/java/servicetest/GooglePlayBillingServiceTest.java
@@ -0,0 +1,35 @@
+package servicetest;
+
+import com.accompany.payment.google.AndroidPublisherHelper;
+import com.accompany.payment.google.GooglePlayBillingService;
+import com.alibaba.fastjson.JSONObject;
+import com.google.api.services.androidpublisher.model.ProductPurchase;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.io.IOException;
+
+public class GooglePlayBillingServiceTest extends CommonTest {
+
+ @Autowired
+ private GooglePlayBillingService googlePlayBillingVerifyService;
+
+ @Test
+ public void verifyOrderTest() throws Exception {
+ String chargeRecordId = "d9e457c4e7bc4d918d1a869bc49003fd ";
+ String packageName = "com.vele.ananplay";
+ String prodId = "goods_cent_1499";
+ String token ="lefpcedapihlpjcmjgnombhd.AO-J1OyZHdP6rv5D1sqFUyWsk1QbBuMS1oV4aTuet7CtgeKLZe_S6yjpOdkLpSTRT123gIQ8LSR7mWcayJtXCizwmMis7tqnTg";
+ googlePlayBillingVerifyService.verifyOrder(chargeRecordId, packageName, prodId, token);
+ }
+
+ @Test
+ public void getGooglePurchaseTest() throws IOException {
+ String packageName = "com.vele.ananplay";
+ String prodId = "goods_cent_1499";
+ String token ="lefpcedapihlpjcmjgnombhd.AO-J1OyZHdP6rv5D1sqFUyWsk1QbBuMS1oV4aTuet7CtgeKLZe_S6yjpOdkLpSTRT123gIQ8LSR7mWcayJtXCizwmMis7tqnTg";
+ ProductPurchase purchase = AndroidPublisherHelper.getPublisher().purchases().products().get(packageName, prodId, token).execute();
+ System.out.println(JSONObject.toJSONString(purchase));
+ //System.out.println(JSONObject.parse(purchase.getObfuscatedExternalProfileId()).toString());
+ }
+}