package com.izouma.nineth.service; 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.Adapay; import com.huifu.adapay.core.exception.BaseAdaPayException; import com.huifu.adapay.model.AdapayCommon; import com.huifu.adapay.model.Payment; import com.huifu.adapay.model.Refund; 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.CreateAssetEvent; import com.izouma.nineth.event.CreateOrderEvent; import com.izouma.nineth.event.OrderNotifyEvent; import com.izouma.nineth.event.TransferAssetEvent; 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.AESEncryptUtil; import com.izouma.nineth.utils.JpaUtils; import com.izouma.nineth.utils.SecurityUtils; import com.izouma.nineth.utils.SnowflakeIdWorker; import com.izouma.nineth.utils.excel.ExcelUtils; import lombok.AllArgsConstructor; 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.StringUtils; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.spring.core.RocketMQTemplate; 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.redis.core.BoundSetOperations; import org.springframework.data.redis.core.BoundValueOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import java.io.File; import java.io.OutputStream; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @Service @AllArgsConstructor @Slf4j public class OrderService { private OrderRepo orderRepo; private CollectionRepo collectionRepo; private UserAddressRepo userAddressRepo; private UserRepo userRepo; private Environment env; private AlipayClient alipayClient; private AlipayProperties alipayProperties; private WxPayService wxPayService; private WxPayProperties wxPayProperties; private AssetService assetService; private SysConfigService sysConfigService; private AssetRepo assetRepo; private UserCouponRepo userCouponRepo; private CollectionService collectionService; private CommissionRecordRepo commissionRecordRepo; private AdapayProperties adapayProperties; private GeneralProperties generalProperties; private RocketMQTemplate rocketMQTemplate; private RedisTemplate redisTemplate; private SnowflakeIdWorker snowflakeIdWorker; private SmsService smsService; private ErrorOrderRepo errorOrderRepo; private ShowCollectionRepo showCollectionRepo; private ShowroomService showroomService; private CollectionPrivilegeRepo collectionPrivilegeRepo; private UserBankCardRepo userBankCardRepo; private IdentityAuthRepo identityAuthRepo; public Page all(PageQuery pageQuery) { 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, int vipPurchase, int vipPoint) { 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(); SendResult result = rocketMQTemplate.syncSend(generalProperties.getCreateOrderTopic(), new CreateOrderEvent(id, userId, collectionId, qty, addressId, userCouponId, invitor, vip), 100000); log.info("发送订单到队列: {}, userId={}, result={}", id, userId, result); return String.valueOf(id); } public void limitReq(Long collectionId) { BoundValueOperations ops = redisTemplate.boundValueOps(RedisKeys.LIMIT_REQ + collectionId); ops.setIfAbsent(3000, Duration.ofSeconds(30)); Long val = ops.decrement(); if (val == null || val < 0) { throw new BusinessException("前方拥堵,请稍后再试"); } } public Order create(Long userId, Long collectionId, int qty, Long addressId, Long userCouponId, Long invitor, Long id, boolean vip) { 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.getAssetId() != null) { Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("藏品不存在")); if (asset.getStatus() != AssetStatus.NORMAL) { 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.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 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 >= collection.getMaxCount()) { throw new BusinessException("限购" + collection.getMaxCount() + "件"); } } User user = null; if (ObjectUtils.isNotEmpty(collection.getMinimumCharge()) && collection.getMinimumCharge() .compareTo(BigDecimal.ZERO) > 0) { user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); 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 (user == null) { user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); } if (vip) { int purchase = orderRepo.countByUserIdAndCollectionIdAndVipTrueAndStatusIn(userId, collectionId, Arrays.asList(OrderStatus.FINISH, OrderStatus.NOT_PAID, OrderStatus.PROCESSING)); if (user.getVipPurchase() - purchase <= 0) { throw new BusinessException("vip名额已使用完毕!"); } // vip扣除额度 if (ObjectUtils.isNotEmpty(collection.getVipQuota())) { collectionService.decreaseQuota(collectionId, 1); } } 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 = orderRepo.save(order); if (order.getTotalPrice().equals(BigDecimal.ZERO)) { notifyOrder(order.getId(), PayMethod.WEIXIN, null); } if (usePoint > 0) { // 扣除积分 userRepo.addVipPoint(userId, -usePoint); } 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); 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 limit = collection.getMaxCount(); int count = 0; 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", limit); 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, int 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.findById(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 = null; try { winItem = collectionService.draw(collection.getId()); } catch (BusinessException ignored) { } if (winItem == null) { log.info("抽卡失败退款 orderId: {}", orderId); order.setStatus(OrderStatus.CANCELLED); order.setCancelTime(LocalDateTime.now()); Map refundParams = new HashMap<>(); refundParams.put("refund_amt", order.getTotalPrice().setScale(2, RoundingMode.HALF_UP) .toPlainString()); refundParams.put("refund_order_no", String.valueOf(snowflakeIdWorker.nextId())); try { Map response = Refund.create(transactionId, refundParams); } catch (BaseAdaPayException e) { e.printStackTrace(); } orderRepo.save(order); throw new BusinessException("抽卡失败, 已退款 " + orderId); } 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()); } else { if (collection.getSource() == CollectionSource.TRANSFER) { orderRepo.save(order); Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null); assetService.transfer(asset, order.getPrice(), user, "转让", order.getId()); collectionRepo.delete(collection); // 如果展厅有此藏品 showCollectionRepo.deleteAllByCollectionId(order.getCollectionId()); // 发送短信提醒用户转让成功 if (asset != null && asset.getUserId() != null) { smsService.sellOut(userRepo.findPhoneById(asset.getUserId())); } } 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(), "出售", collection.getTotal() > 1 ? collectionService.getNextNumber(order.getCollectionId()) : null); if (collection.getType() == CollectionType.SHOWROOM) { showroomService.save(asset); } } } commission(order); if (collection.getAssetId() == null) { collectionService.increaseSale(order.getCollectionId(), order.getQty()); } } 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 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("已支付订单无法取消"); } // 查询adapay支付记录,如果已经支付,则不能取消 Set transactionIds = redisTemplate.opsForSet().members(RedisKeys.PAY_RECORD + order.getId()); if (transactionIds != null && transactionIds.size() > 0) { AtomicInteger succeeded = new AtomicInteger(); AtomicInteger pending = new AtomicInteger(); transactionIds.parallelStream().forEach(s -> { String transactionIdStr = Optional.ofNullable(s).map(Object::toString).orElse(""); String transactionId = null; String merchant = null; if (transactionIdStr.contains("#")) { String[] arr = transactionIdStr.split("#"); merchant = arr[0]; transactionId = arr[1]; } else { merchant = Adapay.defaultMerchantKey; transactionId = transactionIdStr; } try { Map map = Payment.query(transactionId, merchant); if ("succeeded".equalsIgnoreCase(MapUtils.getString(map, "status"))) { succeeded.getAndIncrement(); } if ("pending".equalsIgnoreCase(MapUtils.getString(map, "status"))) { pending.getAndIncrement(); // 未支付的订单调用关单接口 Map closeParams = new HashMap<>(); closeParams.put("payment_id", transactionId); Payment.close(closeParams, merchant); } } catch (BaseAdaPayException e) { log.error("adapay error", e); } }); // if (succeeded.get() + pending.get() > 0) { if (succeeded.get() > 0) { if (ChronoUnit.MINUTES.between(order.getCreatedAt(), LocalDateTime.now()) < 10) { throw new BusinessException("订单已经支付成功或待支付,不能取消 " + order.getId()); } } } 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.updateVipPoint(order.getUserId(), order.getVipPoint()); 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); } }