|
@@ -0,0 +1,1002 @@
|
|
|
|
|
+package com.fs.live.service.impl;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.core.util.NumberUtil;
|
|
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
|
|
+import com.alibaba.fastjson.TypeReference;
|
|
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
|
|
+import com.fs.common.core.redis.service.StockDeductService;
|
|
|
|
|
+import com.fs.common.exception.CustomException;
|
|
|
|
|
+import com.fs.common.utils.ServletUtils;
|
|
|
|
|
+import com.fs.common.utils.SnowflakeUtil;
|
|
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
|
|
+import com.fs.common.utils.ip.IpUtils;
|
|
|
|
|
+import com.fs.config.cloud.CloudHostProper;
|
|
|
|
|
+import com.fs.course.domain.FsCoursePlaySourceConfig;
|
|
|
|
|
+import com.fs.course.mapper.FsCoursePlaySourceConfigMapper;
|
|
|
|
|
+import com.fs.his.domain.FsPayConfig;
|
|
|
|
|
+import com.fs.his.domain.MerchantAppConfig;
|
|
|
|
|
+import com.fs.his.mapper.MerchantAppConfigMapper;
|
|
|
|
|
+import com.fs.hisStore.config.StoreConfig;
|
|
|
|
|
+import com.fs.hisStore.constants.StoreConstants;
|
|
|
|
|
+import com.fs.hisStore.domain.*;
|
|
|
|
|
+import com.fs.hisStore.dto.FsStoreCartDTO;
|
|
|
|
|
+import com.fs.hisStore.dto.TemplateDTO;
|
|
|
|
|
+import com.fs.hisStore.enums.OrderInfoEnum;
|
|
|
|
|
+import com.fs.hisStore.enums.OrderLogEnum;
|
|
|
|
|
+import com.fs.hisStore.enums.ShippingTempEnum;
|
|
|
|
|
+import com.fs.hisStore.mapper.FsStoreOrderItemScrmMapper;
|
|
|
|
|
+import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
|
|
|
|
|
+import com.fs.hisStore.mapper.FsStorePaymentScrmMapper;
|
|
|
|
|
+import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
|
|
|
|
|
+import com.fs.hisStore.service.IFsCityScrmService;
|
|
|
|
|
+import com.fs.hisStore.service.IFsStoreOrderScrmService;
|
|
|
|
|
+import com.fs.hisStore.service.IFsStoreOrderStatusScrmService;
|
|
|
|
|
+import com.fs.hisStore.service.IFsStoreProductPurchaseLimitScrmService;
|
|
|
|
|
+import com.fs.hisStore.service.IFsStoreProductScrmService;
|
|
|
|
|
+import com.fs.hisStore.service.IFsUserScrmService;
|
|
|
|
|
+import com.fs.huifuPay.domain.HuiFuCreateOrder;
|
|
|
|
|
+import com.fs.huifuPay.domain.HuifuCreateOrderResult;
|
|
|
|
|
+import com.fs.huifuPay.sdk.opps.core.utils.HuiFuUtils;
|
|
|
|
|
+import com.fs.huifuPay.service.HuiFuService;
|
|
|
|
|
+import com.fs.live.constant.LiveOrderOptConstants;
|
|
|
|
|
+import com.fs.live.domain.LiveCouponUser;
|
|
|
|
|
+import com.fs.live.domain.LiveGoods;
|
|
|
|
|
+import com.fs.live.domain.LiveOrder;
|
|
|
|
|
+import com.fs.live.domain.LiveUserFirstEntry;
|
|
|
|
|
+import com.fs.live.mapper.LiveGoodsMapper;
|
|
|
|
|
+import com.fs.live.param.LiveOrderPayParam;
|
|
|
|
|
+import com.fs.live.service.ILiveCouponUserService;
|
|
|
|
|
+import com.fs.live.service.ILiveOrderOptService;
|
|
|
|
|
+import com.fs.live.service.ILiveService;
|
|
|
|
|
+import com.fs.live.service.ILiveUserFirstEntryService;
|
|
|
|
|
+import com.fs.live.vo.LiveGoodsUploadMqVo;
|
|
|
|
|
+import com.fs.system.service.ISysConfigService;
|
|
|
|
|
+import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
|
|
|
|
|
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
|
|
|
|
|
+import com.github.binarywang.wxpay.config.WxPayConfig;
|
|
|
|
|
+import com.github.binarywang.wxpay.exception.WxPayException;
|
|
|
|
|
+import com.github.binarywang.wxpay.service.WxPayService;
|
|
|
|
|
+import lombok.Data;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
|
|
|
|
+import org.redisson.api.RLock;
|
|
|
|
|
+import org.redisson.api.RedissonClient;
|
|
|
|
|
+import org.springframework.aop.framework.AopContext;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
+
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
|
|
+import java.util.concurrent.ExecutionException;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+import static com.fs.hisStore.constants.UserAppsLockConstant.LOCK_KEY_PAY;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 直播订单优化版实现
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class LiveOrderOptServiceImpl implements ILiveOrderOptService {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private RedisCache redisCache;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private RedissonClient redissonClient;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private ISysConfigService configService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private IFsStoreProductScrmService fsStoreProductService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FsStoreProductAttrValueScrmMapper fsStoreProductAttrValueMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private LiveGoodsMapper liveGoodsMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private ILiveService liveService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private ILiveUserFirstEntryService liveUserFirstEntryService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private IFsStoreProductPurchaseLimitScrmService purchaseLimitService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private StockDeductService stockDeductService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private CloudHostProper cloudHostProper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private RocketMQTemplate rocketMQTemplate;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FsStoreOrderScrmMapper fsStoreOrderScrmMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FsStoreOrderItemScrmMapper fsStoreOrderItemScrmMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private IFsStoreOrderStatusScrmService orderStatusService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private ILiveCouponUserService liveCouponUserService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private IFsStoreOrderScrmService fsStoreOrderScrmService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private IFsUserScrmService userService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FsStorePaymentScrmMapper fsStorePaymentScrmMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private FsCoursePlaySourceConfigMapper fsCoursePlaySourceConfigMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private MerchantAppConfigMapper merchantAppConfigMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private HuiFuService huiFuService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private WxPayService wxPayService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private IFsCityScrmService fsCityService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private com.fs.hisStore.service.IFsShippingTemplatesScrmService shippingTemplatesService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private com.fs.hisStore.service.IFsShippingTemplatesRegionScrmService shippingTemplatesRegionService;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Throwable.class)
|
|
|
|
|
+ public R createStoreOrder(LiveOrder param) {
|
|
|
|
|
+ return doCreateStoreOrder(param, false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Throwable.class)
|
|
|
|
|
+ public R createStoreOrderTest(LiveOrder param) {
|
|
|
|
|
+ return doCreateStoreOrder(param, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R doCreateStoreOrder(LiveOrder liveOrder, boolean testMode) {
|
|
|
|
|
+ long start = System.currentTimeMillis();
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!testMode) {
|
|
|
|
|
+ String orderKey = redisCache.getCacheObject("orderKey:" + liveOrder.getOrderKey());
|
|
|
|
|
+ if (StringUtils.isEmpty(orderKey)) {
|
|
|
|
|
+ return R.error("订单已过期");
|
|
|
|
|
+ }
|
|
|
|
|
+ String creatingKey = LiveOrderOptConstants.LOCK_ORDER_KEY_CREATING + liveOrder.getOrderKey();
|
|
|
|
|
+ if (!redisCache.setIfAbsent(creatingKey, "1", 30, TimeUnit.SECONDS)) {
|
|
|
|
|
+ return R.error("订单正在创建中,请勿重复提交");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ R validateResult = validateCreateParam(liveOrder);
|
|
|
|
|
+ if (validateResult != null) {
|
|
|
|
|
+ return validateResult;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Long userId = Long.parseLong(liveOrder.getUserId());
|
|
|
|
|
+ Integer purchaseNum = Integer.parseInt(liveOrder.getTotalNum());
|
|
|
|
|
+
|
|
|
|
|
+ LiveGoods goods = getLiveGoodsCached(liveOrder.getLiveId(), liveOrder.getProductId());
|
|
|
|
|
+ if (goods == null) {
|
|
|
|
|
+ return R.error("当前商品不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!testMode) {
|
|
|
|
|
+ R stockResult = deductStockIfNeeded(liveOrder, goods);
|
|
|
|
|
+ if (stockResult != null) {
|
|
|
|
|
+ return stockResult;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (liveService.selectLiveByLiveId(liveOrder.getLiveId()) == null) {
|
|
|
|
|
+ return R.error("当前直播不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreRedisProductById(liveOrder.getProductId());
|
|
|
|
|
+ if (fsStoreProduct == null) {
|
|
|
|
|
+ return R.error("商品不存在,购买失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (fsStoreProduct.getIsShow() == 0 || goods.getStatus() == 0) {
|
|
|
|
|
+ return R.error("商品已下架,购买失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!"1".equals(fsStoreProduct.getIsAudit())) {
|
|
|
|
|
+ return R.error("商品已下架,购买失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FsStoreProductAttrValueScrm attrValue = null;
|
|
|
|
|
+ if (liveOrder.getAttrValueId() != null) {
|
|
|
|
|
+ attrValue = getProductAttrValueCached(liveOrder.getAttrValueId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ checkPurchaseLimit(userId, fsStoreProduct, purchaseNum);
|
|
|
|
|
+
|
|
|
|
|
+ if (!testMode) {
|
|
|
|
|
+ publishStockUpdate(goods.getGoodsId(), purchaseNum);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FsStoreOrderScrm storeOrder = buildStoreOrder(liveOrder, fsStoreProduct, attrValue, goods, testMode);
|
|
|
|
|
+ LiveCouponUser couponToUse = resolveCoupon(liveOrder, userId, storeOrder);
|
|
|
|
|
+
|
|
|
|
|
+ Integer flag = fsStoreOrderScrmMapper.insertFsStoreOrder(storeOrder);
|
|
|
|
|
+ if (flag == null || flag == 0) {
|
|
|
|
|
+ return R.error("订单创建失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (couponToUse != null) {
|
|
|
|
|
+ couponToUse.setStatus(1);
|
|
|
|
|
+ couponToUse.setUseTime(new Date());
|
|
|
|
|
+ liveCouponUserService.updateLiveCouponUser(couponToUse);
|
|
|
|
|
+ redisCache.deleteObject(LiveOrderOptConstants.CACHE_LIVE_COUPON_USER + couponToUse.getId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ insertOrderItem(storeOrder, fsStoreProduct, attrValue, liveOrder.getTotalNum());
|
|
|
|
|
+ orderStatusService.create(storeOrder.getId(), OrderLogEnum.CREATE_ORDER.getValue(),
|
|
|
|
|
+ OrderLogEnum.CREATE_ORDER.getDesc());
|
|
|
|
|
+
|
|
|
|
|
+ StoreConfig config = getStoreConfigCached();
|
|
|
|
|
+ String redisKey = StoreConstants.REDIS_ORDER_OUTTIME_UNPAY + storeOrder.getId();
|
|
|
|
|
+ if (config != null && config.getUnPayTime() != null && config.getUnPayTime() > 0) {
|
|
|
|
|
+ redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!testMode) {
|
|
|
|
|
+ redisCache.deleteObject("orderKey:" + liveOrder.getOrderKey());
|
|
|
|
|
+ redisCache.deleteObject(LiveOrderOptConstants.LOCK_ORDER_KEY_CREATING + liveOrder.getOrderKey());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
|
|
+ calendar.setTime(storeOrder.getCreateTime());
|
|
|
|
|
+ if (config != null && config.getUnPayTime() != null) {
|
|
|
|
|
+ calendar.add(Calendar.MINUTE, config.getUnPayTime());
|
|
|
|
|
+ }
|
|
|
|
|
+ String payLimitTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime());
|
|
|
|
|
+
|
|
|
|
|
+ log.info("[createOpt] testMode={} orderId={} cost={}ms", testMode, storeOrder.getId(),
|
|
|
|
|
+ System.currentTimeMillis() - start);
|
|
|
|
|
+ return R.ok("下单成功").put("order", storeOrder).put("payLimitTime", payLimitTime);
|
|
|
|
|
+ } catch (CustomException e) {
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("[createOpt] 订单创建失败 testMode={}", testMode, e);
|
|
|
|
|
+ return R.error("订单创建失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R handleStoreOrderPay(LiveOrderPayParam param) {
|
|
|
|
|
+ return doPay(param, false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public R handleStoreOrderPayTest(LiveOrderPayParam param) {
|
|
|
|
|
+ return doPay(param, true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R doPay(LiveOrderPayParam param, boolean testMode) {
|
|
|
|
|
+ long start = System.currentTimeMillis();
|
|
|
|
|
+ Long orderId = param.getOrderId();
|
|
|
|
|
+ RLock lock = redissonClient.getLock(String.format(LOCK_KEY_PAY, orderId));
|
|
|
|
|
+ try {
|
|
|
|
|
+ boolean locked = lock.tryLock(500, 30000, TimeUnit.MILLISECONDS);
|
|
|
|
|
+ if (!locked) {
|
|
|
|
|
+ return R.error("订单正在处理中,请勿重复提交");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FsStoreOrderScrm order = fsStoreOrderScrmService.selectFsStoreOrderById(orderId);
|
|
|
|
|
+ if (order == null) {
|
|
|
|
|
+ return R.error("订单不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (order.getStatus() != null && order.getStatus() != OrderInfoEnum.STATUS_0.getValue()) {
|
|
|
|
|
+ return R.error("当前订单已支付");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (testMode && !isTestOrder(order)) {
|
|
|
|
|
+ return R.error("测试支付仅支持测试订单(order_create_type=99)");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String payingFlag = redisCache.getCacheObject("isPaying:" + orderId);
|
|
|
|
|
+ if (StringUtils.isNotEmpty(payingFlag) && payingFlag.equals(order.getId().toString())) {
|
|
|
|
|
+ return R.error("正在支付中...");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ R preCheck = checkExistingPayments(orderId);
|
|
|
|
|
+ if (preCheck != null) {
|
|
|
|
|
+ return preCheck;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FsUserScrm user = getUserCached(order.getUserId());
|
|
|
|
|
+ if (user == null) {
|
|
|
|
|
+ return R.error("用户OPENID不存在");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ applyPayTypeAmount(order, param);
|
|
|
|
|
+ fsStoreOrderScrmService.updateFsStoreOrder(order);
|
|
|
|
|
+
|
|
|
|
|
+ if (testMode) {
|
|
|
|
|
+ R testResult = createTestPayment(order, user, param);
|
|
|
|
|
+ log.info("[payTest] orderId={} cost={}ms", orderId, System.currentTimeMillis() - start);
|
|
|
|
|
+ return testResult;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ R payResult = invokeThirdPartyPay(order, user, param);
|
|
|
|
|
+ log.info("[payOpt] orderId={} cost={}ms", orderId, System.currentTimeMillis() - start);
|
|
|
|
|
+ return payResult;
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
|
|
+ return R.error("支付处理被中断,请稍后重试");
|
|
|
|
|
+ } catch (CustomException e) {
|
|
|
|
|
+ redisCache.deleteObject("isPaying:" + orderId);
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ redisCache.deleteObject("isPaying:" + orderId);
|
|
|
|
|
+ log.error("[payOpt] 支付异常 orderId={}", orderId, e);
|
|
|
|
|
+ return R.error("支付失败:" + e.getMessage());
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (lock.isHeldByCurrentThread()) {
|
|
|
|
|
+ lock.unlock();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R validateCreateParam(LiveOrder liveOrder) {
|
|
|
|
|
+ if (liveOrder.getLiveId() == null) return R.error("直播ID不能为空");
|
|
|
|
|
+ if (liveOrder.getProductId() == null) return R.error("购物商品ID不能为空");
|
|
|
|
|
+ if (liveOrder.getUserName() == null) return R.error("用户名不能为空");
|
|
|
|
|
+ if (liveOrder.getUserPhone() == null) return R.error("用户手机号不能为空");
|
|
|
|
|
+ if (liveOrder.getUserAddress() == null) return R.error("用户地址不能为空");
|
|
|
|
|
+ if (liveOrder.getTotalNum() == null) return R.error("商品数量不能为空");
|
|
|
|
|
+ if (StringUtils.isEmpty(liveOrder.getTotalNum())) {
|
|
|
|
|
+ liveOrder.setTotalNum("1");
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R deductStockIfNeeded(LiveOrder liveOrder, LiveGoods goods) {
|
|
|
|
|
+ com.fs.hisStore.config.StoreConfig config = getHisStoreConfigCached();
|
|
|
|
|
+ if (config == null || !Boolean.TRUE.equals(config.getCheckStock())) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ CompletableFuture<Boolean> future = stockDeductService.deductStockAsync(
|
|
|
|
|
+ liveOrder.getProductId(), liveOrder.getLiveId(),
|
|
|
|
|
+ Integer.parseInt(liveOrder.getTotalNum()), Long.parseLong(liveOrder.getUserId()));
|
|
|
|
|
+ if (!future.get()) {
|
|
|
|
|
+ return R.error("抱歉,这款商品已被抢光,暂时无库存~");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
|
|
+ return R.error("订单创建失败:" + e.getMessage());
|
|
|
|
|
+ } catch (ExecutionException e) {
|
|
|
|
|
+ return R.error("订单创建失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void publishStockUpdate(Long goodsId, Integer purchaseNum) {
|
|
|
|
|
+ LiveGoodsUploadMqVo vo = LiveGoodsUploadMqVo.builder().goodsId(goodsId).goodsNum(purchaseNum).build();
|
|
|
|
|
+ try {
|
|
|
|
|
+ if ("北京卓美".equals(cloudHostProper.getCompanyName())) {
|
|
|
|
|
+ rocketMQTemplate.syncSend("live-goods-upload", JSON.toJSONString(vo));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ liveGoodsMapper.updateStock(goodsId, purchaseNum);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("更新库存失败 vo={}", vo, e);
|
|
|
|
|
+ throw new CustomException("库存更新失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private FsStoreOrderScrm buildStoreOrder(LiveOrder liveOrder, FsStoreProductScrm fsStoreProduct,
|
|
|
|
|
+ FsStoreProductAttrValueScrm attrValue, LiveGoods goods, boolean testMode) {
|
|
|
|
|
+ FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
|
|
|
|
|
+ storeOrder.setUserId(Long.parseLong(liveOrder.getUserId()));
|
|
|
|
|
+ storeOrder.setRealName(liveOrder.getUserName());
|
|
|
|
|
+ storeOrder.setUserPhone(liveOrder.getUserPhone());
|
|
|
|
|
+ storeOrder.setUserAddress(liveOrder.getUserAddress());
|
|
|
|
|
+ storeOrder.setTotalNum(Long.parseLong(liveOrder.getTotalNum()));
|
|
|
|
|
+ storeOrder.setStoreId(liveOrder.getStoreId());
|
|
|
|
|
+ storeOrder.setMark(liveOrder.getRemark());
|
|
|
|
|
+ storeOrder.setRemark(String.valueOf(liveOrder.getLiveId()));
|
|
|
|
|
+
|
|
|
|
|
+ if (testMode) {
|
|
|
|
|
+ storeOrder.setOrderCreateType(LiveOrderOptConstants.ORDER_CREATE_TYPE_LIVE_TEST);
|
|
|
|
|
+ storeOrder.setMark(LiveOrderOptConstants.TEST_ORDER_MARK);
|
|
|
|
|
+ } else if (liveOrder.getOrderCreateType() != null) {
|
|
|
|
|
+ storeOrder.setOrderCreateType(liveOrder.getOrderCreateType());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String storeHouseCode = resolveStoreHouseCode(fsStoreProduct.getProductId());
|
|
|
|
|
+ storeOrder.setStoreHouseCode(storeHouseCode);
|
|
|
|
|
+
|
|
|
|
|
+ LiveUserFirstEntry firstEntry = getFirstEntryCached(liveOrder.getLiveId(), Long.parseLong(liveOrder.getUserId()));
|
|
|
|
|
+ if (ObjectUtil.isNotEmpty(firstEntry)) {
|
|
|
|
|
+ storeOrder.setCompanyId(firstEntry.getCompanyId());
|
|
|
|
|
+ storeOrder.setCompanyUserId(firstEntry.getCompanyUserId());
|
|
|
|
|
+ storeOrder.setTuiUserId(firstEntry.getCompanyUserId());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String orderSn = SnowflakeUtil.nextIdStr();
|
|
|
|
|
+ storeOrder.setOrderCode(orderSn);
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal totalPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
|
|
|
|
|
+ BigDecimal payPrice = totalPrice;
|
|
|
|
|
+ if (attrValue != null) {
|
|
|
|
|
+ payPrice = attrValue.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal deliveryMoney = BigDecimal.ZERO;
|
|
|
|
|
+ if (liveOrder.getCityId() != null) {
|
|
|
|
|
+ deliveryMoney = handleDeliveryMoneyCached(liveOrder.getCityId(), fsStoreProduct,
|
|
|
|
|
+ liveOrder.getTotalNum(), attrValue);
|
|
|
|
|
+ if (deliveryMoney.compareTo(BigDecimal.valueOf(-1)) == 0) {
|
|
|
|
|
+ throw new CustomException("偏远地区暂不可购买");
|
|
|
|
|
+ }
|
|
|
|
|
+ payPrice = payPrice.add(deliveryMoney);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal discountMoney = BigDecimal.ZERO;
|
|
|
|
|
+ if (liveOrder.getCouponUserId() != null) {
|
|
|
|
|
+ LiveCouponUser couponUser = getCouponUserCached(liveOrder.getCouponUserId());
|
|
|
|
|
+ if (couponUser != null && couponUser.getStatus() == 0
|
|
|
|
|
+ && couponUser.getUserId() != null
|
|
|
|
|
+ && couponUser.getUserId().toString().equals(liveOrder.getUserId())) {
|
|
|
|
|
+ if (couponUser.getUseMinPrice().compareTo(payPrice) < 1) {
|
|
|
|
|
+ discountMoney = couponUser.getCouponPrice();
|
|
|
|
|
+ storeOrder.setCouponId(couponUser.getId());
|
|
|
|
|
+ storeOrder.setCouponPrice(couponUser.getCouponPrice());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ storeOrder.setTotalPrice(totalPrice);
|
|
|
|
|
+ storeOrder.setTotalPostage(deliveryMoney);
|
|
|
|
|
+ storeOrder.setPayPostage(deliveryMoney);
|
|
|
|
|
+ storeOrder.setPayDelivery(deliveryMoney);
|
|
|
|
|
+ storeOrder.setCouponPrice(discountMoney);
|
|
|
|
|
+ storeOrder.setDeductionPrice(BigDecimal.ZERO);
|
|
|
|
|
+ storeOrder.setPaid(0);
|
|
|
|
|
+ storeOrder.setPayType(StringUtils.isEmpty(liveOrder.getPayType()) ? "1" : liveOrder.getPayType());
|
|
|
|
|
+ storeOrder.setUseIntegral(BigDecimal.ZERO);
|
|
|
|
|
+ storeOrder.setBackIntegral(BigDecimal.ZERO);
|
|
|
|
|
+ storeOrder.setGainIntegral(BigDecimal.ZERO);
|
|
|
|
|
+ storeOrder.setCost(BigDecimal.ZERO);
|
|
|
|
|
+ storeOrder.setIsChannel(1);
|
|
|
|
|
+ storeOrder.setShippingType(1);
|
|
|
|
|
+ storeOrder.setCreateTime(new Date());
|
|
|
|
|
+ storeOrder.setIsPrescribe(0);
|
|
|
|
|
+ storeOrder.setOrderType(2);
|
|
|
|
|
+ storeOrder.setLiveId(liveOrder.getLiveId());
|
|
|
|
|
+ storeOrder.setStatus(0);
|
|
|
|
|
+
|
|
|
|
|
+ StoreConfig config = getStoreConfigCached();
|
|
|
|
|
+ if (config != null && config.getServiceFee() != null) {
|
|
|
|
|
+ storeOrder.setServiceFee(config.getServiceFee());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal finalPay = payPrice.subtract(discountMoney);
|
|
|
|
|
+ storeOrder.setPayPrice(finalPay);
|
|
|
|
|
+ storeOrder.setPayMoney(finalPay);
|
|
|
|
|
+ return storeOrder;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private LiveCouponUser resolveCoupon(LiveOrder liveOrder, Long userId, FsStoreOrderScrm storeOrder) {
|
|
|
|
|
+ if (liveOrder.getCouponUserId() == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ LiveCouponUser couponUser = getCouponUserCached(liveOrder.getCouponUserId());
|
|
|
|
|
+ if (couponUser == null || couponUser.getStatus() != 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!couponUser.getUserId().equals(userId)) {
|
|
|
|
|
+ throw new CustomException("非法操作");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (storeOrder.getCouponId() == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return couponUser;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void insertOrderItem(FsStoreOrderScrm storeOrder, FsStoreProductScrm fsStoreProduct,
|
|
|
|
|
+ FsStoreProductAttrValueScrm attrValue, String totalNum) {
|
|
|
|
|
+ FsStoreCartDTO cartDTO = new FsStoreCartDTO();
|
|
|
|
|
+ cartDTO.setProductId(fsStoreProduct.getProductId());
|
|
|
|
|
+ cartDTO.setPrice(attrValue != null ? attrValue.getPrice() : fsStoreProduct.getPrice());
|
|
|
|
|
+ cartDTO.setSku(attrValue != null && attrValue.getSku() != null ? attrValue.getSku() : "");
|
|
|
|
|
+ cartDTO.setProductName(fsStoreProduct.getProductName());
|
|
|
|
|
+ cartDTO.setNum(Integer.parseInt(totalNum));
|
|
|
|
|
+ cartDTO.setImage(fsStoreProduct.getImage());
|
|
|
|
|
+ if (attrValue != null) {
|
|
|
|
|
+ cartDTO.setBarCode(attrValue.getBarCode());
|
|
|
|
|
+ cartDTO.setGroupBarCode(attrValue.getGroupBarCode());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ FsStoreOrderItemScrm orderItem = new FsStoreOrderItemScrm();
|
|
|
|
|
+ orderItem.setOrderId(storeOrder.getId());
|
|
|
|
|
+ orderItem.setOrderCode(storeOrder.getOrderCode());
|
|
|
|
|
+ orderItem.setProductId(fsStoreProduct.getProductId());
|
|
|
|
|
+ orderItem.setProductAttrValueId(attrValue != null ? attrValue.getId() : null);
|
|
|
|
|
+ orderItem.setJsonInfo(JSONUtil.toJsonStr(cartDTO));
|
|
|
|
|
+ orderItem.setNum(Integer.parseInt(totalNum));
|
|
|
|
|
+ orderItem.setIsAfterSales(0);
|
|
|
|
|
+ orderItem.setIsPrescribe(0);
|
|
|
|
|
+
|
|
|
|
|
+ List<FsStoreOrderItemScrm> items = Collections.singletonList(orderItem);
|
|
|
|
|
+ storeOrder.setItemJson(JSONUtil.toJsonStr(items));
|
|
|
|
|
+ fsStoreOrderItemScrmMapper.insertFsStoreOrderItem(orderItem);
|
|
|
|
|
+ fsStoreOrderScrmMapper.updateFsStoreOrder(storeOrder);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R checkExistingPayments(Long orderId) {
|
|
|
|
|
+ List<FsStorePaymentScrm> paymentList = fsStorePaymentScrmMapper.selectFsStorePaymentByOrderId(orderId);
|
|
|
|
|
+ if (paymentList == null || paymentList.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (FsStorePaymentScrm payment : paymentList) {
|
|
|
|
|
+ if (payment.getStatus() != null && payment.getStatus() == 1) {
|
|
|
|
|
+ return R.error("当前订单已支付");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void applyPayTypeAmount(FsStoreOrderScrm order, LiveOrderPayParam param) {
|
|
|
|
|
+ if (order.getIsEditMoney() != null && order.getIsEditMoney() == 1) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ com.fs.store.config.StoreConfig storeConfig = getStoreConfigForPayCached();
|
|
|
|
|
+ if (param.getPayType().equals(1)) {
|
|
|
|
|
+ order.setPayType("1");
|
|
|
|
|
+ order.setPayMoney(order.getPayPrice());
|
|
|
|
|
+ if (!"广州郑多燕".equals(cloudHostProper.getCompanyName())) {
|
|
|
|
|
+ order.setPayDelivery(BigDecimal.ZERO);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ order.setPayType(order.getPayPrice().compareTo(order.getTotalPrice()) == 0 ? "1" : "5");
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (param.getPayType().equals(2)) {
|
|
|
|
|
+ order.setPayType("2");
|
|
|
|
|
+ BigDecimal payMoney = order.getPayPrice().multiply(new BigDecimal(storeConfig.getPayRate()))
|
|
|
|
|
+ .divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);
|
|
|
|
|
+ if (payMoney.compareTo(new BigDecimal("0.01")) < 0) {
|
|
|
|
|
+ throw new CustomException("物流代收计算支付金额为0,不允许选择物流代收");
|
|
|
|
|
+ }
|
|
|
|
|
+ order.setPayDelivery(order.getPayPrice().subtract(payMoney));
|
|
|
|
|
+ order.setPayMoney(payMoney);
|
|
|
|
|
+ } else if (param.getPayType().equals(3)) {
|
|
|
|
|
+ order.setPayType("3");
|
|
|
|
|
+ BigDecimal amount = redisCache.getCacheObject("orderAmount:" + order.getId());
|
|
|
|
|
+ BigDecimal payMoney = amount != null ? amount : BigDecimal.ZERO;
|
|
|
|
|
+ BigDecimal payPostage = order.getPayPostage();
|
|
|
|
|
+ if (payPostage == null || payPostage.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ payPostage = storeConfig.getPayPostage() != null ? storeConfig.getPayPostage() : BigDecimal.ZERO;
|
|
|
|
|
+ order.setPayPrice(order.getPayPrice().add(payPostage));
|
|
|
|
|
+ }
|
|
|
|
|
+ order.setPayPostage(payPostage);
|
|
|
|
|
+ payMoney = payMoney.add(payPostage);
|
|
|
|
|
+ order.setPayMoney(payMoney);
|
|
|
|
|
+ order.setPayDelivery(order.getPayPrice().subtract(payMoney));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R createTestPayment(FsStoreOrderScrm order, FsUserScrm user, LiveOrderPayParam param) {
|
|
|
|
|
+ String payCode = SnowflakeUtil.nextIdStr();
|
|
|
|
|
+ FsStorePaymentScrm storePayment = buildBasePayment(order, user, param, payCode);
|
|
|
|
|
+ storePayment.setRemark(LiveOrderOptConstants.TEST_PAYMENT_REMARK);
|
|
|
|
|
+ storePayment.setTradeNo("TEST-" + payCode);
|
|
|
|
|
+ fsStorePaymentScrmMapper.insertFsStorePayment(storePayment);
|
|
|
|
|
+ redisCache.setCacheObject("isPaying:" + order.getId(), order.getId().toString(), 1, TimeUnit.MINUTES);
|
|
|
|
|
+ Map<String, Object> mockPay = new HashMap<>();
|
|
|
|
|
+ mockPay.put("testMode", true);
|
|
|
|
|
+ mockPay.put("paymentId", storePayment.getPaymentId());
|
|
|
|
|
+ mockPay.put("payCode", payCode);
|
|
|
|
|
+ return R.ok().put("payType", param.getPayType()).put("result", mockPay).put("isTest", true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R invokeThirdPartyPay(FsStoreOrderScrm order, FsUserScrm user, LiveOrderPayParam param) {
|
|
|
|
|
+ if (StringUtils.isBlank(param.getAppId())) {
|
|
|
|
|
+ return R.error("appId不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!isOnlinePayType(order.getPayType()) && order.getPayMoney().compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ if ("3".equals(order.getPayType())) {
|
|
|
|
|
+ IFsStoreOrderScrmService proxy = (IFsStoreOrderScrmService) AopContext.currentProxy();
|
|
|
|
|
+ proxy.payConfirm(2, order.getId(), null, null, null, null);
|
|
|
|
|
+ return R.ok().put("payType", param.getPayType());
|
|
|
|
|
+ }
|
|
|
|
|
+ return R.error("支付金额异常");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (order.getPayMoney().compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ return R.error("支付金额异常");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ MiniAppPayBundle bundle = getMiniAppPayBundleCached(param.getAppId());
|
|
|
|
|
+ String payCode = SnowflakeUtil.nextIdStr();
|
|
|
|
|
+ FsStorePaymentScrm storePayment = buildBasePayment(order, user, param, payCode);
|
|
|
|
|
+ storePayment.setRemark("直播订单支付");
|
|
|
|
|
+ storePayment.setPayMode(bundle.getMerchantAppConfig().getMerchantType());
|
|
|
|
|
+ storePayment.setMerConfigId(bundle.getMerchantAppConfig().getId());
|
|
|
|
|
+ storePayment.setAppId(bundle.getPlayConfig().getAppid() == null ? "" : bundle.getPlayConfig().getAppid());
|
|
|
|
|
+ fsStorePaymentScrmMapper.insertFsStorePayment(storePayment);
|
|
|
|
|
+
|
|
|
|
|
+ MerchantAppConfig merchantAppConfig = bundle.getMerchantAppConfig();
|
|
|
|
|
+ if ("hf".equals(merchantAppConfig.getMerchantType())) {
|
|
|
|
|
+ return invokeHuiFuPay(order, user, param, storePayment, bundle.getPayConfig());
|
|
|
|
|
+ }
|
|
|
|
|
+ if ("wx".equals(merchantAppConfig.getMerchantType())) {
|
|
|
|
|
+ return invokeWxPay(order, user, param, storePayment, bundle);
|
|
|
|
|
+ }
|
|
|
|
|
+ return R.error("不支持的支付方式");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R invokeHuiFuPay(FsStoreOrderScrm order, FsUserScrm user, LiveOrderPayParam param,
|
|
|
|
|
+ FsStorePaymentScrm storePayment, FsPayConfig fsPayConfig) {
|
|
|
|
|
+ HuiFuCreateOrder o = new HuiFuCreateOrder();
|
|
|
|
|
+ o.setTradeType("T_MINIAPP");
|
|
|
|
|
+ o.setOpenid(user.getMaOpenId());
|
|
|
|
|
+ o.setReqSeqId("live-" + storePayment.getPayCode());
|
|
|
|
|
+ o.setTransAmt(storePayment.getPayMoney().toString());
|
|
|
|
|
+ o.setGoodsDesc("直播订单支付");
|
|
|
|
|
+ o.setAppId(param.getAppId());
|
|
|
|
|
+ try {
|
|
|
|
|
+ HuiFuUtils.doDiv(o, order.getCompanyId(), storePayment.getMerConfigId());
|
|
|
|
|
+ HuiFuUtils.saveDivItem(o, order.getOrderCode(), storePayment.getPayCode());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("分账出错 orderId={}", order.getId(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ HuifuCreateOrderResult result = huiFuService.createOrder(o);
|
|
|
|
|
+ if (result.getResp_code() != null
|
|
|
|
|
+ && ("00000000".equals(result.getResp_code()) || "00000100".equals(result.getResp_code()))) {
|
|
|
|
|
+ FsStorePaymentScrm mt = new FsStorePaymentScrm();
|
|
|
|
|
+ mt.setPaymentId(storePayment.getPaymentId());
|
|
|
|
|
+ mt.setTradeNo(result.getHf_seq_id());
|
|
|
|
|
+ mt.setAppId(param.getAppId());
|
|
|
|
|
+ mt.setBusinessCode(order.getOrderCode());
|
|
|
|
|
+ fsStorePaymentScrmMapper.updateFsStorePayment(mt);
|
|
|
|
|
+ redisCache.setCacheObject("isPaying:" + order.getId(), order.getId().toString(), 1, TimeUnit.MINUTES);
|
|
|
|
|
+ Map<String, Object> resultMap = JSON.parseObject(result.getPay_info(), new TypeReference<Map<String, Object>>() {});
|
|
|
|
|
+ String pkg = (String) resultMap.get("package");
|
|
|
|
|
+ resultMap.put("packageValue", pkg);
|
|
|
|
|
+ return R.ok().put("payType", param.getPayType()).put("result", resultMap);
|
|
|
|
|
+ }
|
|
|
|
|
+ fsStorePaymentScrmMapper.deleteFsStorePaymentById(storePayment.getPaymentId());
|
|
|
|
|
+ return R.error(result.getResp_desc());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private R invokeWxPay(FsStoreOrderScrm order, FsUserScrm user, LiveOrderPayParam param,
|
|
|
|
|
+ FsStorePaymentScrm storePayment, MiniAppPayBundle bundle) {
|
|
|
|
|
+ FsPayConfig fsPayConfig = bundle.getPayConfig();
|
|
|
|
|
+ WxPayConfig payConfig = new WxPayConfig();
|
|
|
|
|
+ payConfig.setAppId(bundle.getPlayConfig().getAppid());
|
|
|
|
|
+ payConfig.setMchId(fsPayConfig.getWxMchId());
|
|
|
|
|
+ payConfig.setMchKey(fsPayConfig.getWxMchKey());
|
|
|
|
|
+ payConfig.setKeyPath(fsPayConfig.getKeyPath());
|
|
|
|
|
+ payConfig.setNotifyUrl(fsPayConfig.getNotifyUrlScrm());
|
|
|
|
|
+ wxPayService.setConfig(payConfig);
|
|
|
|
|
+ WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
|
|
|
|
|
+ orderRequest.setOpenid(user.getMaOpenId());
|
|
|
|
|
+ orderRequest.setBody("直播订单支付");
|
|
|
|
|
+ orderRequest.setOutTradeNo("live-" + storePayment.getPayCode());
|
|
|
|
|
+ orderRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(storePayment.getPayMoney().toString()));
|
|
|
|
|
+ orderRequest.setTradeType("JSAPI");
|
|
|
|
|
+ orderRequest.setSpbillCreateIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
|
|
|
|
|
+ try {
|
|
|
|
|
+ WxPayMpOrderResult orderResult = wxPayService.createOrder(orderRequest);
|
|
|
|
|
+ redisCache.setCacheObject("isPaying:" + order.getId(), order.getId().toString(), 1, TimeUnit.MINUTES);
|
|
|
|
|
+ return R.ok().put("result", orderResult).put("type", "wx").put("isPay", 0).put("payType", param.getPayType());
|
|
|
|
|
+ } catch (WxPayException e) {
|
|
|
|
|
+ fsStorePaymentScrmMapper.deleteFsStorePaymentById(storePayment.getPaymentId());
|
|
|
|
|
+ throw new CustomException("支付失败" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private FsStorePaymentScrm buildBasePayment(FsStoreOrderScrm order, FsUserScrm user,
|
|
|
|
|
+ LiveOrderPayParam param, String payCode) {
|
|
|
|
|
+ FsStorePaymentScrm storePayment = new FsStorePaymentScrm();
|
|
|
|
|
+ storePayment.setCompanyId(order.getCompanyId());
|
|
|
|
|
+ storePayment.setCompanyUserId(order.getCompanyUserId());
|
|
|
|
|
+ storePayment.setStatus(0);
|
|
|
|
|
+ storePayment.setPayCode(payCode);
|
|
|
|
|
+ storePayment.setPayMoney(order.getPayMoney());
|
|
|
|
|
+ storePayment.setCreateTime(new Date());
|
|
|
|
|
+ storePayment.setPayTypeCode("weixin");
|
|
|
|
|
+ storePayment.setBusinessType(2);
|
|
|
|
|
+ storePayment.setOpenId(user.getMaOpenId());
|
|
|
|
|
+ storePayment.setUserId(user.getUserId());
|
|
|
|
|
+ storePayment.setBusinessOrderId(order.getId().toString());
|
|
|
|
|
+ storePayment.setOrderId(order.getId());
|
|
|
|
|
+ storePayment.setBusinessCode(order.getOrderCode());
|
|
|
|
|
+ return storePayment;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isOnlinePayType(String payType) {
|
|
|
|
|
+ return "1".equals(payType) || "2".equals(payType) || "3".equals(payType) || "5".equals(payType);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isTestOrder(FsStoreOrderScrm order) {
|
|
|
|
|
+ return order.getOrderCreateType() != null
|
|
|
|
|
+ && order.getOrderCreateType() == LiveOrderOptConstants.ORDER_CREATE_TYPE_LIVE_TEST;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String resolveStoreHouseCode(Long productId) {
|
|
|
|
|
+ if (productId != null && (productId.equals(3168L) || productId.equals(3184L) || productId.equals(3185L))) {
|
|
|
|
|
+ return "YDSP001";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "CQDS001";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void checkPurchaseLimit(Long userId, FsStoreProductScrm product, Integer num) {
|
|
|
|
|
+ if (product.getSinglePurchaseLimit() != null && product.getSinglePurchaseLimit() > 0
|
|
|
|
|
+ && num > product.getSinglePurchaseLimit()) {
|
|
|
|
|
+ throw new CustomException("该商品单次最多购买" + product.getSinglePurchaseLimit() + "件");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(
|
|
|
|
|
+ product.getProductId(), userId);
|
|
|
|
|
+ int purchasedNum = purchaseLimit != null ? purchaseLimit.getNum() : 0;
|
|
|
|
|
+ if (purchasedNum + num > product.getPurchaseLimit()) {
|
|
|
|
|
+ throw new CustomException("该商品限购" + product.getPurchaseLimit() + "件,您已购买"
|
|
|
|
|
+ + (purchasedNum + num) + "件,无法继续购买");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ---------- 缓存 ----------
|
|
|
|
|
+
|
|
|
|
|
+ private LiveGoods getLiveGoodsCached(Long liveId, Long productId) {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_LIVE_GOODS + liveId + ":" + productId;
|
|
|
|
|
+ LiveGoods cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ LiveGoods goods = liveGoodsMapper.selectLiveGoodsByProductId(liveId, productId);
|
|
|
|
|
+ if (goods != null) {
|
|
|
|
|
+ redisCache.setCacheObject(key, goods, 60, TimeUnit.SECONDS);
|
|
|
|
|
+ }
|
|
|
|
|
+ return goods;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private LiveUserFirstEntry getFirstEntryCached(Long liveId, Long userId) {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_LIVE_FIRST_ENTRY + liveId + ":" + userId;
|
|
|
|
|
+ LiveUserFirstEntry cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ LiveUserFirstEntry entry = liveUserFirstEntryService.selectEntityByLiveIdUserId(liveId, userId);
|
|
|
|
|
+ if (entry != null) {
|
|
|
|
|
+ redisCache.setCacheObject(key, entry, 10, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ return entry;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private com.fs.hisStore.config.StoreConfig getHisStoreConfigCached() {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_SYS_CONFIG + "his.store";
|
|
|
|
|
+ com.fs.hisStore.config.StoreConfig cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ String json = configService.selectConfigByKey("his.store");
|
|
|
|
|
+ if (StringUtils.isEmpty(json)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ com.fs.hisStore.config.StoreConfig config = JSON.parseObject(json, com.fs.hisStore.config.StoreConfig.class);
|
|
|
|
|
+ if (config != null) {
|
|
|
|
|
+ redisCache.setCacheObject(key, config, 10, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ return config;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private StoreConfig getStoreConfigCached() {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_SYS_CONFIG + "store.config";
|
|
|
|
|
+ StoreConfig cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ String json = configService.selectConfigByKey("store.config");
|
|
|
|
|
+ if (StringUtils.isEmpty(json)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
|
|
|
|
|
+ if (config != null) {
|
|
|
|
|
+ redisCache.setCacheObject(key, config, 10, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ return config;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private com.fs.store.config.StoreConfig getStoreConfigForPayCached() {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_SYS_CONFIG + "his.store.pay";
|
|
|
|
|
+ com.fs.store.config.StoreConfig cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ String config = configService.selectConfigByKey("his.store");
|
|
|
|
|
+ com.fs.store.config.StoreConfig storeConfig = JSONUtil.toBean(config, com.fs.store.config.StoreConfig.class);
|
|
|
|
|
+ if (storeConfig != null) {
|
|
|
|
|
+ redisCache.setCacheObject(key, storeConfig, 10, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ return storeConfig;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private FsUserScrm getUserCached(Long userId) {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_SYS_CONFIG + "user:pay:" + userId;
|
|
|
|
|
+ FsUserScrm cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ FsUserScrm user = userService.selectFsUserById(userId);
|
|
|
|
|
+ if (user != null) {
|
|
|
|
|
+ redisCache.setCacheObject(key, user, 10, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ return user;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private MiniAppPayBundle getMiniAppPayBundleCached(String appId) {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_MINIAPP_PAY + appId;
|
|
|
|
|
+ MiniAppPayBundle cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ FsCoursePlaySourceConfig playConfig = fsCoursePlaySourceConfigMapper.selectCoursePlaySourceConfigByAppId(appId);
|
|
|
|
|
+ if (playConfig == null) {
|
|
|
|
|
+ throw new CustomException("未找到appId对应的小程序配置: " + appId);
|
|
|
|
|
+ }
|
|
|
|
|
+ Long merchantConfigId = playConfig.getMerchantConfigId();
|
|
|
|
|
+ if (merchantConfigId == null || merchantConfigId <= 0) {
|
|
|
|
|
+ throw new CustomException("小程序没有配置商户信息");
|
|
|
|
|
+ }
|
|
|
|
|
+ MerchantAppConfig merchantAppConfig = merchantAppConfigMapper.selectMerchantAppConfigById(merchantConfigId);
|
|
|
|
|
+ FsPayConfig payConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
|
|
|
|
|
+ MiniAppPayBundle bundle = new MiniAppPayBundle();
|
|
|
|
|
+ bundle.setPlayConfig(playConfig);
|
|
|
|
|
+ bundle.setMerchantAppConfig(merchantAppConfig);
|
|
|
|
|
+ bundle.setPayConfig(payConfig);
|
|
|
|
|
+ redisCache.setCacheObject(key, bundle, 30, TimeUnit.MINUTES);
|
|
|
|
|
+ return bundle;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private FsStoreProductAttrValueScrm getProductAttrValueCached(Long attrValueId) {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_PRODUCT_ATTR_ID + attrValueId;
|
|
|
|
|
+ FsStoreProductAttrValueScrm cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ FsStoreProductAttrValueScrm attrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(attrValueId);
|
|
|
|
|
+ if (attrValue != null) {
|
|
|
|
|
+ redisCache.setCacheObject(key, attrValue, 10, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ return attrValue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private LiveCouponUser getCouponUserCached(Long couponUserId) {
|
|
|
|
|
+ String key = LiveOrderOptConstants.CACHE_LIVE_COUPON_USER + couponUserId;
|
|
|
|
|
+ LiveCouponUser cached = redisCache.getCacheObject(key);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ LiveCouponUser couponUser = liveCouponUserService.selectLiveCouponUserById(couponUserId);
|
|
|
|
|
+ if (couponUser != null) {
|
|
|
|
|
+ redisCache.setCacheObject(key, couponUser, 5, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ return couponUser;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private BigDecimal handleDeliveryMoneyCached(Long cityId, FsStoreProductScrm fsStoreProduct,
|
|
|
|
|
+ String totalNumSize, FsStoreProductAttrValueScrm knownAttrValue) {
|
|
|
|
|
+ BigDecimal storePostage = BigDecimal.ZERO;
|
|
|
|
|
+ BigDecimal badCode = BigDecimal.valueOf(-1);
|
|
|
|
|
+ if (ObjectUtil.isNull(fsStoreProduct.getTempId())) {
|
|
|
|
|
+ return storePostage;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<String> citys = resolveShippingMatchCityIdsCached(cityId);
|
|
|
|
|
+ String ids = String.valueOf(fsStoreProduct.getTempId());
|
|
|
|
|
+ List<FsShippingTemplatesScrm> shippingTemplatesList = getShippingTemplatesCached(ids);
|
|
|
|
|
+ String cityIds = String.join(",", citys);
|
|
|
|
|
+ List<FsShippingTemplatesRegionScrm> shippingTemplatesRegionList = getShippingRegionsCached(ids, cityIds);
|
|
|
|
|
+ if (shippingTemplatesList != null && !shippingTemplatesList.isEmpty()
|
|
|
|
|
+ && (shippingTemplatesRegionList == null || shippingTemplatesRegionList.isEmpty())) {
|
|
|
|
|
+ return badCode;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<Long, Integer> shippingTemplatesMap = shippingTemplatesList.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(FsShippingTemplatesScrm::getId, FsShippingTemplatesScrm::getType));
|
|
|
|
|
+ Map<Long, FsShippingTemplatesRegionScrm> shippingTemplatesRegionMap = shippingTemplatesRegionList.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(FsShippingTemplatesRegionScrm::getTempId, r -> r, (a, b) -> b));
|
|
|
|
|
+ Long tempId = Long.valueOf(fsStoreProduct.getTempId());
|
|
|
|
|
+ Integer templateType = shippingTemplatesMap.get(tempId);
|
|
|
|
|
+ FsStoreProductAttrValueScrm productAttrValue = knownAttrValue != null
|
|
|
|
|
+ ? knownAttrValue : getFirstProductAttrValueCached(fsStoreProduct.getProductId());
|
|
|
|
|
+ if (productAttrValue == null) {
|
|
|
|
|
+ return storePostage;
|
|
|
|
|
+ }
|
|
|
|
|
+ Integer totalNum = Integer.valueOf(totalNumSize);
|
|
|
|
|
+ double num;
|
|
|
|
|
+ if (ShippingTempEnum.TYPE_1.getValue().equals(templateType)) {
|
|
|
|
|
+ num = totalNum.doubleValue();
|
|
|
|
|
+ } else if (ShippingTempEnum.TYPE_2.getValue().equals(templateType)) {
|
|
|
|
|
+ num = NumberUtil.mul(totalNum, productAttrValue.getWeight()).doubleValue();
|
|
|
|
|
+ } else if (ShippingTempEnum.TYPE_3.getValue().equals(templateType)) {
|
|
|
|
|
+ num = NumberUtil.mul(totalNum, productAttrValue.getVolume()).doubleValue();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ num = totalNum.doubleValue();
|
|
|
|
|
+ }
|
|
|
|
|
+ FsShippingTemplatesRegionScrm region = shippingTemplatesRegionMap.get(tempId);
|
|
|
|
|
+ if (region == null) {
|
|
|
|
|
+ return badCode;
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal price = NumberUtil.round(NumberUtil.mul(totalNum, fsStoreProduct.getPrice()), 2);
|
|
|
|
|
+ TemplateDTO templateDTO = TemplateDTO.builder()
|
|
|
|
|
+ .number(num).price(price)
|
|
|
|
|
+ .first(region.getFirst().doubleValue()).firstPrice(region.getFirstPrice())
|
|
|
|
|
+ .continues(region.getContinues().doubleValue()).continuePrice(region.getContinuePrice())
|
|
|
|
|
+ .tempId(tempId).cityId(cityId.toString()).build();
|
|
|
|
|
+ if (Double.compare(templateDTO.getNumber(), templateDTO.getFirst()) <= 0) {
|
|
|
|
|
+ return NumberUtil.round(NumberUtil.add(storePostage, templateDTO.getFirstPrice()), 2);
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal firstPrice = NumberUtil.add(storePostage, templateDTO.getFirstPrice());
|
|
|
|
|
+ if (templateDTO.getContinues() <= 0) {
|
|
|
|
|
+ return firstPrice;
|
|
|
|
|
+ }
|
|
|
|
|
+ double average = Math.ceil(NumberUtil.div(NumberUtil.sub(templateDTO.getNumber(), templateDTO.getFirst()),
|
|
|
|
|
+ templateDTO.getContinues().doubleValue()));
|
|
|
|
|
+ return NumberUtil.add(firstPrice, NumberUtil.mul(average, templateDTO.getContinuePrice()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<String> resolveShippingMatchCityIdsCached(Long cityId) {
|
|
|
|
|
+ String cacheKey = LiveOrderOptConstants.CACHE_CITY_SHIPPING_CHAIN + cityId;
|
|
|
|
|
+ List<String> cached = redisCache.getCacheObject(cacheKey);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<String> cityIds = fsCityService.resolveShippingMatchCityIds(String.valueOf(cityId));
|
|
|
|
|
+ redisCache.setCacheObject(cacheKey, cityIds, 24, TimeUnit.HOURS);
|
|
|
|
|
+ return cityIds;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<FsShippingTemplatesScrm> getShippingTemplatesCached(String ids) {
|
|
|
|
|
+ String cacheKey = LiveOrderOptConstants.CACHE_SHIPPING_TEMPLATE + ids;
|
|
|
|
|
+ List<FsShippingTemplatesScrm> cached = redisCache.getCacheObject(cacheKey);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<FsShippingTemplatesScrm> templates = shippingTemplatesService.selectFsShippingTemplatesByIds(ids);
|
|
|
|
|
+ if (templates != null) {
|
|
|
|
|
+ redisCache.setCacheObject(cacheKey, templates, 1, TimeUnit.HOURS);
|
|
|
|
|
+ }
|
|
|
|
|
+ return templates;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<FsShippingTemplatesRegionScrm> getShippingRegionsCached(String tempIds, String cityIds) {
|
|
|
|
|
+ String cacheKey = LiveOrderOptConstants.CACHE_SHIPPING_REGION + tempIds + ":" + cityIds;
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<FsShippingTemplatesRegionScrm> cached = redisCache.getCacheObject(cacheKey);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ return cached;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ redisCache.deleteObject(cacheKey);
|
|
|
|
|
+ }
|
|
|
|
|
+ List<FsShippingTemplatesRegionScrm> regions = shippingTemplatesRegionService
|
|
|
|
|
+ .selectFsShippingTemplatesRegionListByTempIdsAndCityIds(tempIds, cityIds);
|
|
|
|
|
+ if (regions != null) {
|
|
|
|
|
+ redisCache.setCacheObject(cacheKey, regions, 1, TimeUnit.HOURS);
|
|
|
|
|
+ }
|
|
|
|
|
+ return regions;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private FsStoreProductAttrValueScrm getFirstProductAttrValueCached(Long productId) {
|
|
|
|
|
+ String cacheKey = LiveOrderOptConstants.CACHE_PRODUCT_ATTR_LIST + productId;
|
|
|
|
|
+ List<FsStoreProductAttrValueScrm> cached = redisCache.getCacheObject(cacheKey);
|
|
|
|
|
+ if (cached == null) {
|
|
|
|
|
+ cached = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueByProductId(productId);
|
|
|
|
|
+ if (cached != null) {
|
|
|
|
|
+ redisCache.setCacheObject(cacheKey, cached, 10, TimeUnit.MINUTES);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (cached == null || cached.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return cached.get(0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Data
|
|
|
|
|
+ private static class MiniAppPayBundle implements java.io.Serializable {
|
|
|
|
|
+ private FsCoursePlaySourceConfig playConfig;
|
|
|
|
|
+ private MerchantAppConfig merchantAppConfig;
|
|
|
|
|
+ private FsPayConfig payConfig;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|