package com.izouma.nineth.service; import cn.hutool.core.collection.CollUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alipay.api.AlipayClient; import com.alipay.api.request.AlipayTradeWapPayRequest; import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult; import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; import com.google.common.base.Splitter; import com.huifu.adapay.core.exception.BaseAdaPayException; import com.huifu.adapay.model.AdapayCommon; import com.huifu.adapay.model.Payment; import com.izouma.nineth.config.*; import com.izouma.nineth.domain.Collection; import com.izouma.nineth.domain.*; import com.izouma.nineth.dto.MarketSettlement; import com.izouma.nineth.dto.PageQuery; import com.izouma.nineth.dto.UserBankCard; import com.izouma.nineth.enums.*; import com.izouma.nineth.event.*; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.*; import com.izouma.nineth.security.Authority; import com.izouma.nineth.service.sms.SmsService; import com.izouma.nineth.utils.*; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket; import io.github.bucket4j.BucketConfiguration; import io.github.bucket4j.Refill; import io.github.bucket4j.distributed.proxy.ProxyManager; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.EncoderException; import org.apache.commons.codec.net.URLCodec; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.junit.Test; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.event.EventListener; import org.springframework.core.env.Environment; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.BoundValueOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import javax.persistence.criteria.Join; import java.io.OutputStream; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @Service @Slf4j public class OrderService { private final OrderRepo orderRepo; private final CollectionRepo collectionRepo; private final UserAddressRepo userAddressRepo; private final UserRepo userRepo; private final Environment env; private final AlipayClient alipayClient; private final AlipayProperties alipayProperties; private final WxPayService wxPayService; private final WxPayProperties wxPayProperties; private final AssetService assetService; private final SysConfigService sysConfigService; private final AssetRepo assetRepo; private final UserCouponRepo userCouponRepo; private final CollectionService collectionService; private final CommissionRecordRepo commissionRecordRepo; private final AdapayProperties adapayProperties; private final GeneralProperties generalProperties; private final RocketMQTemplate rocketMQTemplate; private final RedisTemplate redisTemplate; private final SnowflakeIdWorker snowflakeIdWorker; private final SmsService smsService; private final ErrorOrderRepo errorOrderRepo; private final ShowCollectionRepo showCollectionRepo; private final ShowroomService showroomService; private final CollectionPrivilegeRepo collectionPrivilegeRepo; private final UserBankCardRepo userBankCardRepo; private final CacheService cacheService; private final UserPropertyRepo userPropertyRepo; private final UserBalanceService userBalanceService; private final ProxyManager buckets; private final HeatInfoRepo heatInfoRepo; private final ShowroomRepo showroomRepo; public OrderService(OrderRepo orderRepo, CollectionRepo collectionRepo, UserAddressRepo userAddressRepo, UserRepo userRepo, Environment env, AlipayClient alipayClient, AlipayProperties alipayProperties, WxPayService wxPayService, WxPayProperties wxPayProperties, AssetService assetService, SysConfigService sysConfigService, AssetRepo assetRepo, UserCouponRepo userCouponRepo, CollectionService collectionService, CommissionRecordRepo commissionRecordRepo, AdapayProperties adapayProperties, GeneralProperties generalProperties, RocketMQTemplate rocketMQTemplate, RedisTemplate redisTemplate, SnowflakeIdWorker snowflakeIdWorker, SmsService smsService, ErrorOrderRepo errorOrderRepo, ShowCollectionRepo showCollectionRepo, ShowroomService showroomService, CollectionPrivilegeRepo collectionPrivilegeRepo, UserBankCardRepo userBankCardRepo, CacheService cacheService, UserPropertyRepo userPropertyRepo, UserBalanceService userBalanceService, ProxyManager proxyManager, HeatInfoRepo heatInfoRepo, ShowroomRepo showroomRepo) { this.orderRepo = orderRepo; this.collectionRepo = collectionRepo; this.userAddressRepo = userAddressRepo; this.userRepo = userRepo; this.env = env; this.alipayClient = alipayClient; this.alipayProperties = alipayProperties; this.wxPayService = wxPayService; this.wxPayProperties = wxPayProperties; this.assetService = assetService; this.sysConfigService = sysConfigService; this.assetRepo = assetRepo; this.userCouponRepo = userCouponRepo; this.collectionService = collectionService; this.commissionRecordRepo = commissionRecordRepo; this.adapayProperties = adapayProperties; this.generalProperties = generalProperties; this.rocketMQTemplate = rocketMQTemplate; this.redisTemplate = redisTemplate; this.snowflakeIdWorker = snowflakeIdWorker; this.smsService = smsService; this.errorOrderRepo = errorOrderRepo; this.showCollectionRepo = showCollectionRepo; this.showroomService = showroomService; this.collectionPrivilegeRepo = collectionPrivilegeRepo; this.userBankCardRepo = userBankCardRepo; this.cacheService = cacheService; this.userPropertyRepo = userPropertyRepo; this.userBalanceService = userBalanceService; this.buckets = proxyManager; this.heatInfoRepo = heatInfoRepo; this.showroomRepo = showroomRepo; } public Page all(PageQuery pageQuery) { Page all = orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery)); List content = all.getContent(); content.forEach(order -> { Long userId = orderRepo.selectUserId(order.getAssetId()); order.setSellerId(userId); }); return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery)); } public String mqCreate(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor, String sign, boolean vip, boolean safeFlag, Long showroomId) { String qs = null; try { qs = AESEncryptUtil.decrypt(sign); } catch (Exception e) { throw new BusinessException("签名错误"); } final Map map = Splitter.on('&').trimResults().withKeyValueSeparator('=').split(qs); if (Math.abs(MapUtils.getLong(map, "ts") - System.currentTimeMillis()) > 90000) { throw new BusinessException("签名已过期"); } if (redisTemplate.opsForValue().get(RedisKeys.BLACK_LIST + userId) != null) { throw new BusinessException("频繁操作,请稍后再试"); } BoundValueOperations ops = redisTemplate.boundValueOps(RedisKeys.LIMIT_USER + userId); ops.setIfAbsent(0, Duration.ofSeconds(10)); long val = Optional.ofNullable(ops.increment()).orElse(0L); if (val > 5) { if (val > 10) { redisTemplate.opsForValue().set(RedisKeys.BLACK_LIST + userId, 1, Duration.ofSeconds(60 * 10)); } throw new BusinessException("频繁操作,请稍后再试"); } limitReq(collectionId); Integer stock = collectionService.getStock(collectionId); if (stock == null || stock <= 0) { throw new BusinessException("藏品已售罄", ErrorCode.SOLD_OUT); } Long id = snowflakeIdWorker.nextId(); redisTemplate.opsForValue().set("safeFlag::" + id, safeFlag, 10, TimeUnit.MINUTES); SendResult result = rocketMQTemplate.syncSend(generalProperties.getCreateOrderTopic(), new CreateOrderEvent(id, userId, collectionId, qty, addressId, userCouponId, invitor, vip, showroomId), 100000); log.info("发送订单到队列: {}, userId={}, result={}", id, userId, result); return String.valueOf(id); } public void limitReq(Long collectionId) { Bucket bucket = buckets.builder().build("limit::" + collectionId, () -> (BucketConfiguration.builder() .addLimit(Bandwidth.classic(3000, Refill.intervally(3000, Duration.ofSeconds(3000)))) .build())); if (!bucket.tryConsume(1)) { throw new BusinessException("前方拥堵,请稍后再试"); } } public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor, Long id, boolean vip, Long showroomId) { long t = System.currentTimeMillis(); qty = 1; int stock = Optional.ofNullable(collectionService.decreaseStock(collectionId, qty)) .map(Math::toIntExact) .orElseThrow(new BusinessException("很遗憾,藏品已售罄", ErrorCode.SOLD_OUT)); int usePoint = 0; // 创建订单出错后需要回滚库存,所以需要try-catch try { if (stock < 0) { throw new BusinessException("很遗憾,藏品已售罄", ErrorCode.SOLD_OUT); } Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在")); if (collection.getAssetId() != null && collection.getAssetId().equals(778359L)) { throw new BusinessException("很遗憾,藏品已售罄", ErrorCode.SOLD_OUT); } if (collection.getSource() == CollectionSource.TRANSFER) { Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("藏品不存在")); if (Objects.equals(asset.getUserId(), userId)) { throw new BusinessException("不能购买自己的藏品"); } if (asset.getStatus() != AssetStatus.NORMAL) { throw new BusinessException("藏品已下架"); } if (asset.getLockTo() != null && asset.getLockTo().isAfter(LocalDateTime.now())) { throw new BusinessException("此藏品已锁仓"); } } User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在")); UserCoupon coupon = null; if (collection.isCouponPayment()) { if (userCouponId == null) { throw new BusinessException("必须使用优惠券支付"); } coupon = userCouponRepo.findById(userCouponId).orElseThrow(new BusinessException("兑换券不存在")); if (!coupon.getUserId().equals(userId)) { throw new BusinessException("兑换券不属于您"); } if (coupon.isUsed()) { throw new BusinessException("该兑换券已使用"); } if (coupon.isLimited() && !coupon.getCollectionIds().contains(collectionId)) { throw new BusinessException("该兑换券不可用"); } } if (collection.isScheduleSale()) { if (collection.getStartTime().isAfter(LocalDateTime.now())) { throw new BusinessException("当前还未开售"); } } if (!collection.isOnShelf()) { if (!collection.isScanCode()) { throw new BusinessException("藏品已下架"); } } if (!collection.isSalable()) { throw new BusinessException("该藏品当前不可购买"); } if (collection.getMaxCount() > 0) { int userMax = userPropertyRepo.findById(userId) .map(UserProperty::getMaxCount) .orElse(collection.getMaxCount()); int count; if (StringUtils.isNotBlank(collection.getCountId())) { count = orderRepo.countByUserIdAndCountIdAndStatusIn(userId, collection.getCountId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING)); } else { count = orderRepo.countByUserIdAndCollectionIdAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING)); } if (count >= userMax) { throw new BusinessException("限购" + userMax + "件"); } } User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); //设置了最低消费 if (ObjectUtils.isNotEmpty(collection.getMinimumCharge()) && collection.getMinimumCharge() .compareTo(BigDecimal.ZERO) > 0) { if (!user.isCanSale()) { throw new BusinessException("绿洲石不足"); } } //查询是否有拉新任务,只算官方购买 if (collection.getSource() != CollectionSource.TRANSFER && collection.getAssignment() > 0) { //延迟销售 if (!vip && collection.getTimeDelay()) { if (collection.getSaleTime().isAfter(LocalDateTime.now())) { throw new BusinessException("当前还未开售"); } } if (vip) { if (user.getVipPurchase() < 1) { throw new BusinessException("非vip!"); } } else { if (user.getVipPoint() < 1) { throw new BusinessException("没有购买名额"); } usePoint = 1; } } UserAddress userAddress = null; if (addressId != null) { userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在")); } BigDecimal gasFee = sysConfigService.getBigDecimal("gas_fee"); Order order = Order.builder() .id(Optional.ofNullable(id).orElse(snowflakeIdWorker.nextId())) .userId(userId) .collectionId(collectionId) .name(collection.getName()) .pic(collection.getPic()) .detail(collection.getDetail()) .properties(collection.getProperties()) .category(collection.getCategory()) .canResale(collection.isCanResale()) .royalties(collection.getRoyalties()) .serviceCharge(collection.getServiceCharge()) .type(collection.getType()) .source(collection.getSource()) .minterId(collection.getMinterId()) .minter(minter.getNickname()) .minterAvatar(minter.getAvatar()) .qty(qty) .price(collection.getPrice()) .gasPrice(gasFee) .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(qty)).add(gasFee)) .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null)) .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null)) .address(Optional.ofNullable(userAddress).map(u -> u.getProvinceName() + " " + u.getCityName() + " " + u.getDistrictName() + " " + u .getAddress()) .orElse(null)) .status(OrderStatus.NOT_PAID) .assetId(collection.getAssetId()) .couponId(userCouponId) .invitor(invitor) .countId(collection.getCountId()) .vip(vip) .vipPoint(usePoint) .build(); if (coupon != null) { coupon.setUsed(true); coupon.setUseTime(LocalDateTime.now()); if (coupon.isNeedGas()) { order.setTotalPrice(order.getGasPrice()); } else { order.setTotalPrice(BigDecimal.ZERO); } userCouponRepo.save(coupon); } if (collection.getSource() == CollectionSource.TRANSFER) { Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("资产不存在")); asset.setStatus(AssetStatus.TRADING); assetRepo.save(asset); collectionRepo.setOnShelf(collectionId, false); //拥有指定藏品降税 order.setRoyalties(assetService.getRoyalties(minter.getId(), collection.getRoyalties(), userId)); } order = orderRepo.save(order); if (order.getTotalPrice().compareTo(BigDecimal.ZERO) == 0) { notifyOrder(order.getId(), PayMethod.WEIXIN, null); } // if (usePoint > 0) { // // 扣除积分 // userRepo.addVipPoint(userId, -usePoint); // cacheService.clearUserMy(userId); // } if (ObjectUtils.isNotEmpty(showroomId)) { //通过展厅的购买数量 heatInfoRepo.save(HeatInfo.builder() .showroomId(showroomId) .userId(userId) .type(HeatType.BUY) .value(0) .orderId(order.getId()) .build()); } rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), collectionId, 10000); log.info("订单创建完成, id={}, {}ms", order.getId(), System.currentTimeMillis() - t); return order; } catch (Exception e) { collectionService.increaseStock(collectionId, qty); // if (usePoint > 0) { // // 扣除积分 // userRepo.addVipPoint(userId, usePoint); // cacheService.clearUserMy(userId); // log.info("订单失败加积分用户ID:{}, 积分:{}", userId, usePoint); // } // if (vip) { // collectionService.decreaseQuota(collectionId, 1); // log.info("订单失败加藏品额度CollectionId:{}", collectionId); // } throw e; } } public Object checkLimit(Long collectionId, Long userId) { Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在")); int count = 0; AtomicInteger userMax = new AtomicInteger(); userPropertyRepo.findById(userId).ifPresent(userProperty -> userMax.set(userProperty.getMaxCount())); if (collection.getMaxCount() > 0) { if (StringUtils.isNotBlank(collection.getCountId())) { count = orderRepo.countByUserIdAndCountIdAndStatusIn(userId, collection.getCountId(), Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING)); } else { count = orderRepo.countByUserIdAndCollectionIdAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING)); } } Map map = new HashMap<>(); map.put("limit", userMax.get() > 0 ? userMax.get() : collection.getMaxCount()); map.put("count", count); return map; } public void payOrderAlipay(Long id, Model model) { try { Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } JSONObject bizContent = new JSONObject(); bizContent.put("notifyUrl", alipayProperties.getNotifyUrl()); bizContent.put("returnUrl", alipayProperties.getReturnUrl()); bizContent.put("out_trade_no", String.valueOf(snowflakeIdWorker.nextId())); bizContent.put("total_amount", order.getTotalPrice().stripTrailingZeros().toPlainString()); bizContent.put("disable_pay_channels", "pcredit,creditCard"); if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) { // 测试环境设为1分 bizContent.put("total_amount", "0.01"); } bizContent.put("subject", order.getName()); bizContent.put("product_code", "QUICK_WAP_PAY"); JSONObject body = new JSONObject(); body.put("action", "payOrder"); body.put("userId", order.getUserId()); body.put("orderId", order.getId()); bizContent.put("body", body.toJSONString()); AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest(); alipayRequest.setReturnUrl(alipayProperties.getReturnUrl()); alipayRequest.setNotifyUrl(alipayProperties.getNotifyUrl()); alipayRequest.setBizContent(JSON.toJSONString(bizContent)); String form = alipayClient.pageExecute(alipayRequest).getBody(); model.addAttribute("form", form); } catch (BusinessException err) { model.addAttribute("errMsg", err.getError()); } catch (Exception e) { model.addAttribute("errMsg", e.getMessage()); } } public Object payOrderWeixin(Long id, String tradeType, String openId) throws WxPayException, EncoderException { Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); request.setBody(order.getName()); request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId())); request.setTotalFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue()); if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) { // 测试环境设为1分 // request.setTotalFee(1); } request.setSpbillCreateIp("180.102.110.170"); request.setNotifyUrl(wxPayProperties.getNotifyUrl()); request.setTradeType(tradeType); request.setOpenid(openId); request.setSignType("MD5"); JSONObject body = new JSONObject(); body.put("action", "payOrder"); body.put("userId", order.getUserId()); body.put("orderId", order.getId()); request.setAttach(body.toJSONString()); if (WxPayConstants.TradeType.MWEB.equals(tradeType)) { WxPayMwebOrderResult result = wxPayService.createOrder(request); return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl()); } else if (WxPayConstants.TradeType.JSAPI.equals(tradeType)) { return wxPayService.createOrder(request); } throw new BusinessException("不支持此付款方式"); } @Cacheable(value = "adapay", key = "#id+'_'+#payChannel") public Object payAdapay(Long id, String payChannel, String openId) throws BaseAdaPayException { List aliChannels = Arrays.asList("alipay", "alipay_qr", "alipay_wap"); List wxChannels = Arrays.asList("wx_pub", "wx_lite"); if (!aliChannels.contains(payChannel) && !wxChannels.contains(payChannel)) { throw new BusinessException("不支持此渠道"); } Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在")); if (SecurityUtils.getAuthenticatedUser() != null && !SecurityUtils.getAuthenticatedUser().getId().equals(order.getUserId())) { log.error("payAdapay userId错误 requestUserId={} orderUserId={}", SecurityUtils.getAuthenticatedUser().getId(), order.getUserId()); } Collection collection = collectionRepo.findById(order.getCollectionId()) .orElseThrow(new BusinessException("藏品不存在")); User invitor = null; if (order.getInvitor() != null) { invitor = userRepo.findById(order.getInvitor()).orElse(null); } if (invitor != null && StringUtils.isBlank(invitor.getSettleAccountId())) { invitor = null; } if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } Map paymentParams = new HashMap<>(); paymentParams.put("order_no", String.valueOf(snowflakeIdWorker.nextId())); paymentParams.put("pay_amt", order.getTotalPrice().setScale(2, RoundingMode.HALF_UP).toPlainString()); paymentParams.put("app_id", adapayProperties.getAppId()); paymentParams.put("pay_channel", payChannel); paymentParams.put("goods_title", collection.getName()); paymentParams.put("goods_desc", collection.getName()); paymentParams.put("time_expire", DateTimeFormatter.ofPattern("yyyyMMddHHmmss") .format(LocalDateTime.now().plusMinutes(3))); paymentParams.put("notify_url", adapayProperties.getNotifyUrl() + "/order/" + adapayProperties .getMerchant() + "/" + order.getId()); List> divMembers = new ArrayList<>(); BigDecimal totalAmount = order.getTotalPrice().subtract(order.getGasPrice()); BigDecimal restAmount = order.getTotalPrice().multiply(BigDecimal.valueOf(1)); if (collection.getSource().equals(CollectionSource.TRANSFER)) { Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("无记录")); User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("拥有者用户不存在")); if (collection.getServiceCharge() + collection.getRoyalties() > 0) { // 扣除手续费、服务费、GAS费 restAmount = divMoney(totalAmount, restAmount, divMembers, owner.getMemberId(), 100 - (collection.getServiceCharge() + collection.getRoyalties()), false); } restAmount = divMoney(restAmount, divMembers, "0", restAmount, true); } else { if (invitor != null && invitor.getShareRatio() != null && invitor.getShareRatio().compareTo(BigDecimal.ZERO) > 0) { restAmount = divMoney(totalAmount, restAmount, divMembers, invitor.getMemberId(), invitor.getShareRatio().intValue(), false); } restAmount = divMoney(restAmount, divMembers, "0", restAmount, true); } if (restAmount.compareTo(BigDecimal.ZERO) != 0) { log.error("分账出错 {}", JSON.toJSONString(divMembers, SerializerFeature.PrettyFormat)); throw new BusinessException("分账出错"); } if (divMembers.size() > 1) { paymentParams.put("div_members", divMembers); } Map expend = new HashMap<>(); paymentParams.put("expend", expend); if ("wx_pub".equals(payChannel)) { if (StringUtils.isBlank(openId)) { throw new BusinessException("缺少openId"); } expend.put("open_id", openId); expend.put("limit_pay", "1"); } Map response; if ("wx_lite".equals(payChannel)) { paymentParams.put("adapay_func_code", "wxpay.createOrder"); paymentParams.put("callback_url", generalProperties.getHost() + "/9th/orders"); response = AdapayCommon.requestAdapayUits(paymentParams); log.info("createOrderResponse {}", JSON.toJSONString(response, SerializerFeature.PrettyFormat)); } else { response = Payment.create(paymentParams); log.info("createOrderResponse {}", JSON.toJSONString(response, SerializerFeature.PrettyFormat)); AdapayService.checkSuccess(response); // 保存adapay的订单id,用于后续取消订单时的查询 BoundSetOperations ops = redisTemplate.boundSetOps(RedisKeys.PAY_RECORD + order.getId()); ops.add(adapayProperties.getMerchant() + "#" + MapUtils.getString(response, "id")); ops.expire(7, TimeUnit.DAYS); } switch (payChannel) { case "alipay_wap": case "alipay": return MapUtils.getString(MapUtils.getMap(response, "expend"), "pay_info"); case "alipay_qr": return MapUtils.getString(MapUtils.getMap(response, "expend"), "qrcode_url"); case "wx_pub": JSONObject payParams = JSON .parseObject(MapUtils.getString(MapUtils.getMap(response, "expend"), "pay_info")); payParams.put("timestamp", payParams.get("timeStamp")); payParams.remove("timeStamp"); return payParams; default: return MapUtils.getMap(response, "expend"); } } public static BigDecimal divMoney(BigDecimal totalAmount, BigDecimal restAmount, List> divMembers, String memberId, double ratio, boolean feeFlag) { if (ratio == -1 || (ratio > 0 && ratio < 100)) { BigDecimal divAmount = ratio == -1 ? restAmount : totalAmount.multiply(BigDecimal.valueOf(ratio)) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); Map divMem = new HashMap<>(); divMem.put("member_id", memberId); divMem.put("amount", divAmount.toPlainString()); divMem.put("fee_flag", feeFlag ? "Y" : "N"); divMembers.add(divMem); return restAmount.subtract(divAmount); } else { throw new BusinessException("分账比例错误"); } } public static BigDecimal divMoney(BigDecimal restAmount, List> divMembers, String memberId, BigDecimal divAmount, boolean feeFlag) { if (divAmount.compareTo(BigDecimal.ZERO) > 0) { Map divMem = new HashMap<>(); divMem.put("member_id", memberId); divMem.put("amount", divAmount.toPlainString()); divMem.put("fee_flag", feeFlag ? "Y" : "N"); divMembers.add(divMem); } return restAmount.subtract(divAmount); } public void notifyOrder(Long orderId, PayMethod payMethod, String transactionId) { log.info("订单回调 orderId: {}, payMethod: {}, transactionId: {}", orderId, payMethod, transactionId); // 取消订单与订单回调不能同时进行,需要抢锁 if (!getOrderLock(orderId)) { log.info("订单回调失败 orderId: {} redis锁定, 重新发送到队列", orderId); rocketMQTemplate.syncSend(generalProperties.getOrderNotifyTopic(), new OrderNotifyEvent(orderId, payMethod, transactionId, System.currentTimeMillis())); return; } try { Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在")); Collection collection = collectionRepo.findDetailById(order.getCollectionId()) .orElseThrow(new BusinessException("藏品不存在")); User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("用户不存在")); if (order.getStatus() == OrderStatus.NOT_PAID) { order.setStatus(OrderStatus.PROCESSING); order.setPayTime(LocalDateTime.now()); order.setTransactionId(transactionId); order.setPayMethod(payMethod); if (order.getType() == CollectionType.BLIND_BOX) { log.info("开始盲盒抽卡 orderId: {}, collectionId: {}", orderId, collection.getId()); BlindBoxItem winItem = collectionService.draw(order.getUserId(), collection.getId()); log.info("抽卡成功 orderId: {}, collectionId: {}, winCollectionId: {}", orderId, collection .getId(), winItem.getCollectionId()); order.setWinCollectionId(winItem.getCollectionId()); orderRepo.save(order); //藏品其他信息/是否vip CollectionPrivilege collectionPrivilege = collectionPrivilegeRepo .findByCollectionId(order.getCollectionId()); if (ObjectUtils.isNotEmpty(collectionPrivilege)) { if (collectionPrivilege.isVip()) { //更新vip信息 userRepo.updateVipPurchase(order.getUserId(), 1); } } assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售", winItem.getTotal() > 1 ? collectionService.getNextNumber(winItem.getCollectionId()) : null, collection.getHoldDays(), false); } else { if (collection.getSource() == CollectionSource.TRANSFER) { orderRepo.save(order); Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null); boolean safeFlag = Objects .equals(true, redisTemplate.opsForValue().get("safeFlag::" + orderId)); assetService.transfer(asset, order.getPrice(), user, TransferReason.TRANSFER, order.getId(), safeFlag); order.setStatus(OrderStatus.FINISH); orderRepo.save(order); collectionRepo.delete(collection); // 如果展厅有此藏品 showCollectionRepo.deleteAllByCollectionId(order.getCollectionId()); // 发送短信提醒用户转让成功 if (asset != null && asset.getUserId() != null) { smsService.sellOut(userRepo.findPhoneById(asset.getUserId())); } userBalanceService.realtimeSettleOrder(order); } else { orderRepo.save(order); //藏品其他信息/是否vip CollectionPrivilege collectionPrivilege = collectionPrivilegeRepo .findByCollectionId(order.getCollectionId()); if (ObjectUtils.isNotEmpty(collectionPrivilege)) { if (collectionPrivilege.isVip()) { //更新vip信息 userRepo.updateVipPurchase(order.getUserId(), 1); } } Asset asset = assetService.createAsset(collection, user, order.getId(), order.getPrice(), "出售", collectionService.getNextNumber(collection), false); if (collection.getType() == CollectionType.SHOWROOM) { showroomService.save(asset); } } } commission(order); if (collection.getAssetId() == null) { collectionService.increaseSale(order.getCollectionId(), order.getQty()); } //通过展厅购买加热力值 List heatInfos = heatInfoRepo .findByUserIdAndOrderIdAndType(order.getUserId(), orderId, HeatType.BUY); if (CollUtil.isNotEmpty(heatInfos)) { HeatInfo heatInfo = heatInfos.get(0); int weight = sysConfigService.getInt("heat_buy_weight"); heatInfo.setValue(weight); heatInfoRepo.save(heatInfo); showroomRepo.addHeat(heatInfo.getShowroomId(), weight); } } else { throw new BusinessException("状态错误 " + order.getStatus()); } } catch (Exception e) { ErrorOrder errorOrder = ErrorOrder.builder() .orderId(orderId) .transactionId(transactionId) .payMethod(payMethod) .build(); if (e instanceof BusinessException) { log.error("订单回调出错 orderId: {} {}", orderId, e.getMessage()); } else { log.error("订单回调出错 orderId: " + orderId, e); } errorOrder.setErrorMessage(e.getMessage()); errorOrderRepo.save(errorOrder); } releaseOrderLock(orderId); } @EventListener public void onCreateAsset(CreateAssetEvent event) { Asset asset = event.getAsset(); if (asset.getOrderId() != null) { Order order = orderRepo.findById(asset.getOrderId()).orElse(null); if (event.isSuccess() && order != null) { order.setTxHash(asset.getTxHash()); order.setGasUsed(asset.getGasUsed()); order.setBlockNumber(asset.getBlockNumber()); order.setStatus(OrderStatus.FINISH); orderRepo.save(order); } } } @EventListener public void onHcMinted(MintedEvent event) { Asset asset = assetRepo.findById(event.getAssetId()).orElse(null); if (asset != null && asset.getOrderId() != null) { orderRepo.findById(asset.getOrderId()).ifPresent(order -> { order.setHcTxHash(asset.getTxHash()); order.setHcGasUsed(asset.getGasUsed()); order.setHcBlockNumber(asset.getBlockNumber()); orderRepo.save(order); }); } } @EventListener public void onTransferAsset(TransferAssetEvent event) { Asset asset = event.getAsset(); Order order = orderRepo.findById(asset.getOrderId()).orElseThrow(new BusinessException("订单不存在")); if (event.isSuccess()) { order.setTxHash(asset.getTxHash()); order.setGasUsed(asset.getGasUsed()); order.setBlockNumber(asset.getBlockNumber()); order.setStatus(OrderStatus.FINISH); orderRepo.save(order); } else { log.error("创建asset失败"); } } public void cancel(Long id) { Order order = orderRepo.findById(id).orElseThrow(new BusinessException("订单不存在")); cancel(order); } public void cancel(Order order) { // 取消订单与订单回调不能同时进行,需要抢锁 if (!getOrderLock(order.getId())) { log.error("订单取消失败 {}, redis锁了", order.getId()); return; } try { if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("当前订单状态无法取消[" + order.getStatus().name() + "]"); } CollectionSource source = Optional.ofNullable(order.getSource()).orElseGet(() -> collectionRepo.findById(order.getCollectionId()).map(Collection::getSource).orElse(null)); if (source == CollectionSource.TRANSFER) { Asset asset = assetRepo.findById(order.getAssetId()).orElse(null); if (asset != null) { log.info("set normal cancelOrder {}", order.getId()); asset.setStatus(AssetStatus.NORMAL); assetRepo.save(asset); } collectionRepo.setOnShelf(order.getCollectionId(), true); } collectionService.increaseStock(order.getCollectionId(), order.getQty()); order.setStatus(OrderStatus.CANCELLED); order.setCancelTime(LocalDateTime.now()); orderRepo.save(order); if (order.getCouponId() != null) { userCouponRepo.findById(order.getCouponId()).ifPresent(coupon -> { coupon.setUsed(false); coupon.setUseTime(null); userCouponRepo.save(coupon); }); } //加上积分 if (ObjectUtils.isNotEmpty(order.getVipPoint()) && order.getVipPoint() > 0) { userRepo.addVipPoint(order.getUserId(), order.getVipPoint()); cacheService.clearUserMy(order.getUserId()); log.info("取消加积分用户ID:{},订单ID:{},积分:{}", order.getUserId(), order.getId(), order.getVipPoint()); } if (order.isVip()) { collectionService.decreaseQuota(order.getCollectionId(), 1); log.info("取消加藏品额度CollectionId:{}", order.getCollectionId()); } rocketMQTemplate.syncSend(generalProperties.getUpdateQuotaTopic(), order.getCollectionId(), 10000); log.info("取消订单{}", order.getId()); } catch (Exception e) { if (e instanceof BusinessException) { log.error(e.getMessage()); } else { log.error("订单取消错误 orderId: " + order.getId(), e); } } releaseOrderLock(order.getId()); } public void refundCancelled(Order order) { } public void setNumber() { for (Collection collection : collectionRepo.findAll()) { if (collection.getSource() != CollectionSource.OFFICIAL) continue; collection.setCurrentNumber(0); collectionRepo.save(collection); for (Asset asset : assetRepo.findByCollectionId(collection.getId())) { if (asset.getStatus() == AssetStatus.GIFTED || asset.getStatus() == AssetStatus.TRANSFERRED) { } else { asset.setNumber(collectionService.getNextNumber(collection.getId())); assetRepo.save(asset); } } } } public void setNumberRecursive(Asset asset) { } @Scheduled(cron = "0 0 4 * * ?") public void setSales() { if (generalProperties.isNotifyServer()) { return; } List minters = userRepo.findByAuthoritiesContains(Authority.get(AuthorityName.ROLE_MINTER)); for (User minter : minters) { userRepo.setSales(minter.getId(), (int) orderRepo.countSales(minter.getId())); } } public void commission(Order order) { if (order.getInvitor() != null) { userRepo.findById(order.getInvitor()).ifPresent(user -> { BigDecimal shareRatio = user.getShareRatio(); if (StringUtils.isNotBlank(user.getSettleAccountId()) && shareRatio != null && shareRatio.compareTo(BigDecimal.ZERO) > 0) { BigDecimal totalPrice = order.getTotalPrice().subtract(order.getGasPrice()); commissionRecordRepo.save(CommissionRecord.builder() .orderId(order.getId()) .collectionId(order.getCollectionId()) .name(order.getName()) .totalPrice(totalPrice) .nickname(user.getNickname()) .userId(user.getId()) .shareRatio(user.getShareRatio()) .phone(user.getPhone()) .shareAmount(totalPrice.multiply(shareRatio) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)) .build()); } }); } } public Object queryCreateOrder(String id) { Object res = redisTemplate.opsForValue().get(RedisKeys.CREATE_ORDER + id); if (res != null) { if (res instanceof Map) { if (MapUtils.getBooleanValue((Map) res, "success", false)) { Order order = (Order) MapUtils.getObject((Map) res, "data"); if (!SecurityUtils.getAuthenticatedUser().getId().equals(order.getUserId())) { log.error("queryCreateOrder userId错误 requestUserId={} orderUserId={}", SecurityUtils.getAuthenticatedUser().getId(), order.getUserId()); return null; } } } } return res; } // 获取订单锁,有效时间1小时 public boolean getOrderLock(Long orderId) { BoundValueOperations ops = redisTemplate.boundValueOps(RedisKeys.ORDER_LOCK + orderId); Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.HOURS); return Boolean.TRUE.equals(flag); } // 释放订单锁 public void releaseOrderLock(Long orderId) { redisTemplate.delete(RedisKeys.ORDER_LOCK + orderId); } public void calcSettle(LocalDateTime start, LocalDateTime end, OutputStream outputStream) { List orders = orderRepo .findByCreatedAtBetweenAndSourceAndStatusIn(start, end, CollectionSource.TRANSFER, Arrays .asList(OrderStatus.PROCESSING, OrderStatus.FINISH)); List assets = assetRepo.findAllById(orders.stream().map(Order::getAssetId).collect(Collectors.toSet())); List bankCards = userBankCardRepo .findByUserIdIn(assets.stream().map(Asset::getUserId).collect(Collectors.toSet())); List settlements = new ArrayList<>(); for (Order order : orders) { BigDecimal amount = order.getTotalPrice() .subtract(order.getGasPrice()) .multiply(new BigDecimal("100") .subtract(BigDecimal.valueOf(order.getServiceCharge())) .subtract(BigDecimal.valueOf(order.getRoyalties())) .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP)) .setScale(2, RoundingMode.HALF_UP); Long userId = assets.stream().filter(a -> a.getId().equals(order.getAssetId())).map(Asset::getUserId) .findAny().orElse(null); if (userId != null) { UserBankCard userBankCard = bankCards.stream().filter(b -> b.getUserId().equals(userId)).findAny() .orElse(null); MarketSettlement marketSettlement = settlements.stream().filter(s -> s.getUserId().equals(userId)) .findAny().orElse(null); if (marketSettlement == null) { marketSettlement = new MarketSettlement(userId, Optional.ofNullable(userBankCard).map(UserBankCard::getRealName).orElse(null), Optional.ofNullable(userBankCard).map(UserBankCard::getBankNo).orElse(null), amount); settlements.add(marketSettlement); } else { marketSettlement.setAmount(marketSettlement.getAmount() .add(amount)); } } } EasyExcel.write(outputStream, MarketSettlement.class).sheet("sheet").doWrite(settlements); } @Scheduled(cron = "0 0/5 * * * ?") public void setBlackList() { List userIds = orderRepo .checkBlackList(LocalDateTime.now(), LocalDateTime.now().plusHours(1)); userIds.forEach(userId -> { redisTemplate.opsForValue().set(RedisKeys.BLACK_LIST + userId, 1, Duration.ofSeconds(60 * 60)); }); } public List addOrder(Long collectionId, List userIds, LocalDateTime time, boolean notify) { Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在")); User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("用户不存在")); int i = 0; List list = new ArrayList<>(); for (Long userId : userIds) { time = time.plusSeconds(i++); BigDecimal gasFee = new BigDecimal("1"); Order order = Order.builder() .id(snowflakeIdWorker.nextId()) .userId(userId) .collectionId(collectionId) .name(collection.getName()) .pic(collection.getPic()) .detail(collection.getDetail()) .properties(collection.getProperties()) .category(collection.getCategory()) .canResale(collection.isCanResale()) .royalties(collection.getRoyalties()) .serviceCharge(collection.getServiceCharge()) .type(collection.getType()) .source(collection.getSource()) .minterId(collection.getMinterId()) .minter(minter.getNickname()) .minterAvatar(minter.getAvatar()) .qty(1) .price(collection.getPrice()) .gasPrice(gasFee) .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(1)).add(gasFee)) .status(notify ? OrderStatus.NOT_PAID : OrderStatus.FINISH) .assetId(collection.getAssetId()) .countId(collection.getCountId()) .build(); orderRepo.saveAndFlush(order); String txHash = RandomStringUtils.randomAlphanumeric(64).toLowerCase(Locale.ROOT); String transactionId = DateTimeUtils.format(LocalDateTime.now(), "yyyyMMdd") + RandomStringUtils.randomNumeric(24); BigInteger gas = new BigInteger("157377"); if (notify) { rocketMQTemplate.syncSend(generalProperties.getOrderNotifyTopic(), new OrderNotifyEvent(order.getId(), PayMethod.ALIPAY, order.getTransactionId() , System.currentTimeMillis())); } else { order.setCreatedAt(ObjectUtils.clone(time)); order.setPayTime(time.plusSeconds(RandomUtils.nextInt(5, 50))); order.setTransactionId(transactionId); order.setTxHash(txHash); order.setGasUsed(gas); order.setPayMethod(PayMethod.ALIPAY); order.setStatus(OrderStatus.FINISH); order.setModifiedAt(order.getPayTime()); order.setModifiedBy("system"); order.setCreatedBy("system"); orderRepo.save(order); } list.add(order); } return list; } public Page byTag(Long tagId, List excludeUserId, Pageable pageable) { if (excludeUserId.isEmpty()) { excludeUserId.add(0L); } return orderRepo.findAll((root, query, criteriaBuilder) -> { Join join = root.join("tags"); return criteriaBuilder.and(criteriaBuilder.equal(join.get("id"), tagId), criteriaBuilder.equal(root.get("status"), OrderStatus.FINISH), criteriaBuilder.equal(root.get("source"), CollectionSource.TRANSFER), criteriaBuilder.not(root.get("ownerId").in(excludeUserId))); }, pageable); } @Async public void refundGas() throws ExecutionException, InterruptedException { new ForkJoinPool(500).submit(() -> { List list = orderRepo.findByCollectionId(8657801L).stream() .filter(o -> o.getStatus() == OrderStatus.FINISH || o.getStatus() == OrderStatus.PROCESSING) .collect(Collectors.toList()); list.parallelStream().forEach(o -> { log.info("refundGas {}", o.getId()); userBalanceService.modifyBalance(o.getUserId(), new BigDecimal(1), BalanceType.REFUND, null, false, null); }); }).get(); } }