package com.izouma.nineth.service; import com.izouma.nineth.config.Constants; import com.izouma.nineth.config.RedisKeys; import com.izouma.nineth.domain.*; import com.izouma.nineth.dto.PageQuery; import com.izouma.nineth.enums.*; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.*; import com.izouma.nineth.service.sms.SmsService; import com.izouma.nineth.utils.JpaUtils; import com.izouma.nineth.utils.SecurityUtils; import com.izouma.nineth.utils.SnowflakeIdWorker; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Page; 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 java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; @Slf4j @Service public class AuctionOrderService { @Autowired private AuctionOrderRepo auctionOrderRepo; @Autowired private SysConfigService sysConfigService; @Autowired private UserRepo userRepo; @Autowired private AssetService assetService; @Autowired private AuctionActivityRepo auctionActivityRepo; @Autowired private AuctionRecordRepo auctionRecordRepo; @Autowired private AssetRepo assetRepo; @Autowired private UserAddressRepo userAddressRepo; @Autowired private AuctionActivityService auctionActivityService; @Autowired private RedisTemplate redisTemplate; @Autowired private SnowflakeIdWorker snowflakeIdWorker; @Autowired private AuctionPassRecordRepo auctionPassRecordRepo; @Lazy @Autowired private OrderPayService orderPayService; @Autowired private SmsService smsService; public Page all(PageQuery pageQuery) { return auctionOrderRepo .findAll(JpaUtils.toSpecification(pageQuery, AuctionOrder.class), JpaUtils.toPageRequest(pageQuery)); } public AuctionOrder create(Long userId, Long auctionId, Long addressId, Long auctionRecordId, AuctionPaymentType type) { User user = userRepo.findById(userId).orElseThrow(new BusinessException("无用户")); AuctionActivity auction = auctionActivityRepo.findById(auctionId) .orElseThrow(new BusinessException("无拍卖信息")); if (!auction.isOnShelf()) { throw new BusinessException("拍卖已结束"); } String status = (String) redisTemplate.opsForValue().get(RedisKeys.AUCTION_STATUS + auctionId); if (status == null) status = auction.getStatus().toString(); switch (AuctionStatus.valueOf(status)) { case NOTSTARTED: throw new BusinessException("拍卖还未开始"); // case PURCHASED: // throw new BusinessException("拍卖成交中"); case PASS: throw new BusinessException("已经流拍"); case FINISH: throw new BusinessException("拍卖已结束"); case FIXED_PRICE_PURCHASED: if (AuctionPaymentType.FIXED_PRICE.equals(type)) { throw new BusinessException("一口价成交中"); } } if (user.getId().equals(auction.getSellerId())) { throw new BusinessException("不可自己出价自己的"); } if (AuctionPaymentType.DEPOSIT.equals(type)) { return this.createDeposit(user, auction); } if (AuctionPaymentType.PURCHASE_PRICE.equals(type)) { if (auction.getEndTime().isAfter(LocalDateTime.now())) { throw new BusinessException("拍卖还未结束"); } AuctionRecord record = auctionRecordRepo.findTopByAuctionIdAndUserIdOrderByIdDesc(auctionId, userId); if (ObjectUtils.isEmpty(record) || !record.isPayDeposit()) { throw new BusinessException("未支付保证金"); } if (record.getBidderPrice().compareTo(auction.getPurchasePrice()) != 0) { throw new BusinessException("与成交价不否"); } int time = sysConfigService.getInt("auction_cancel_time"); if (LocalDateTime.now().isAfter(auction.getEndTime().plusMinutes(time))) { throw new BusinessException("超过支付时长"); } } UserAddress userAddress = null; if (addressId != null) { userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在")); } try { auctionActivityService.changeStatus(auctionId, AuctionPaymentType.FIXED_PRICE .equals(type) ? AuctionStatus.FIXED_PRICE_PURCHASED : AuctionStatus.PURCHASED); if (AuctionSource.TRANSFER.equals(auction.getSource())) { Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在")); asset.setStatus(AssetStatus.AUCTION_TRADING); assetRepo.save(asset); // 发送短信提醒用户转让成功 if (asset.getUserId() != null) { smsService.sellOut(userRepo.findPhoneById(asset.getUserId())); } } BigDecimal price = AuctionPaymentType.FIXED_PRICE.equals(type) ? auction.getFixedPrice() : auction .getPurchasePrice(); AuctionOrder order = AuctionOrder.builder() .id(snowflakeIdWorker.nextId()) .auctionId(auction.getId()) .userId(user.getId()) .nickname(user.getNickname()) .paymentType(type) .name(auction.getName()) .pic(auction.getPic()) .serviceCharge(auction.getServiceCharge()) .royalties(auction.getRoyalties()) .source(auction.getSource()) .price(price) .totalPrice(price) .auctionRecordId(auctionRecordId) .status(AuctionOrderStatus.NOT_PAID) .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null)) .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null)) .address(Optional.ofNullable(userAddress).map(UserAddress::getDetail).orElse(null)) .build(); return auctionOrderRepo.save(order); } catch (Exception e) { auctionActivityService.changeStatus(auctionId, AuctionStatus.ONGOING); throw e; } } public AuctionOrder createDeposit(User user, AuctionActivity auction) { if (user.getId().equals(auction.getSellerId())) { throw new BusinessException("不可自己出价自己的"); } //保证金 // BigDecimal deposit = sysConfigService.getBigDecimal("deposit"); AuctionOrder order = auctionOrderRepo .findByUserIdAndAuctionIdAndPaymentTypeAndStatusIn(user.getId(), auction.getId(), AuctionPaymentType.DEPOSIT, Arrays .asList(AuctionOrderStatus.NOT_PAID, AuctionOrderStatus.FINISH)); if (ObjectUtils.isNotEmpty(order)) { if (AuctionOrderStatus.FINISH.equals(order.getStatus())) { throw new BusinessException("保证金已交过,无需再交"); } throw new BusinessException("保证金未支付,取消后再重新出价"); } AuctionRecord auctionRecord = AuctionRecord.builder() .auctionId(auction.getId()) .type(AuctionRecordType.DEPOSIT) .bidderPrice(auction.getDeposit()) .auctionPic(null) .userId(SecurityUtils.getAuthenticatedUser().getId()) .avatar(SecurityUtils.getAuthenticatedUser().getAvatar()) .name(auction.getName()) .purchased(false) .auctionType(auction.getAuctionType()) .build(); AuctionRecord record = auctionRecordRepo.save(auctionRecord); order = AuctionOrder.builder() .id(snowflakeIdWorker.nextId()) .auctionId(auction.getId()) .userId(user.getId()) .nickname(user.getNickname()) .paymentType(AuctionPaymentType.DEPOSIT) .name(auction.getName()) .pic(auction.getPic()) .serviceCharge(auction.getServiceCharge()) .royalties(auction.getRoyalties()) .source(auction.getSource()) .price(auction.getDeposit()) .totalPrice(auction.getDeposit()) .auctionRecordId(record.getId()) .status(AuctionOrderStatus.NOT_PAID) .build(); return auctionOrderRepo.save(order); } public void notify(Long id, PayMethod payMethod, String transactionId) { AuctionOrder order = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("无记录")); if (!order.getStatus().equals(AuctionOrderStatus.NOT_PAID)) { throw new BusinessException("订单已处理"); } AuctionActivity auction = auctionActivityRepo.findById(order.getAuctionId()) .orElseThrow(new BusinessException("无拍卖活动")); if (auction.getAuctionType().equals(AuctionType.ENTITY)) { order.setStatus(AuctionOrderStatus.DELIVERY); } else { order.setStatus(AuctionOrderStatus.FINISH); } order.setPayMethod(payMethod); order.setTransactionId(transactionId); order.setPayTime(LocalDateTime.now()); //存订单 auctionOrderRepo.save(order); if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) { //改出价记录表 AuctionRecord record = auctionRecordRepo.findById(order.getAuctionRecordId()) .orElseThrow(new BusinessException("无出价记录")); record.setPayDeposit(true); auctionRecordRepo.save(record); return; } //此拍卖结束 auctionActivityService.changeStatus(order.getAuctionId(), AuctionStatus.FINISH); if (AuctionSource.TRANSFER.equals(order.getSource())) { Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在")); if (asset.isPublicShow()) { //取消公开展示 assetService.cancelPublic(asset); } User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("无用户")); //转让流程 assetService.transfer(asset, order.getTotalPrice(), user, TransferReason.AUCTION, order.getId()); } //改出价记录表为竞得(一口价无出价表) auctionRecordRepo.findById(order.getAuctionRecordId()) .ifPresent(record -> { record.setPurchased(true); auctionRecordRepo.save(record); }); //退保证金 List orders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(order.getAuctionId(), AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH); //退款 orders.forEach(this::refund); } public void cancel(AuctionOrder order) { if (!getOrderLock(order.getId())) { log.error("订单取消失败 {}, redis锁了", order.getId()); return; } boolean isRefund = false; try { AuctionActivity auction = auctionActivityRepo.findById(order.getAuctionId()) .orElseThrow(new BusinessException("无记录")); if (AuctionPaymentType.PURCHASE_PRICE.equals(order.getPaymentType())) { //如果是拍卖,需获取取消订单的时长 int time = sysConfigService.getInt("auction_cancel_time"); if (LocalDateTime.now().isAfter(auction.getEndTime().plusMinutes(time))) { //超过支付时长 auctionActivityService.changeStatus(order.getAuctionId(), AuctionStatus.PASS); //添加到流拍记录表里 auctionPassRecordRepo.save(AuctionPassRecord.builder() .auctionId(auction.getId()) .userId(auction.getPurchaserId()) .purchasePrice(auction.getPurchasePrice()) .build()); //流拍不退自己的保证金 isRefund = true; } } else if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) { //删除出价记录 auctionRecordRepo.softDelete(order.getAuctionRecordId()); } else { //拍卖是否结束 if (LocalDateTime.now().isBefore(auction.getEndTime())) { //返回拍卖状态 auctionActivityService.changeStatus(order.getAuctionId(), AuctionStatus.ONGOING); } else { //最后一个出价的人得 auctionActivityService.changeStatus(order.getAuctionId(), AuctionStatus.PURCHASED); } } if (AuctionSource.TRANSFER.equals(order.getSource())) { //改回资产状态 Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在")); asset.setStatus(AssetStatus.NORMAL); assetRepo.save(asset); } order.setStatus(AuctionOrderStatus.CANCELLED); order.setCancelTime(LocalDateTime.now()); auctionOrderRepo.save(order); log.info("取消订单{}", order.getId()); } catch (Exception e) { log.error("订单取消错误 orderId: " + order.getId(), e); } if (isRefund) { //退其余保证金 List orders = auctionOrderRepo .findAllByAuctionIdAndPaymentTypeAndStatus(order.getAuctionId(), AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH); //退款 orders.stream() .filter(o -> !order.getUserId().equals(o.getUserId())) .forEach(this::refund); } releaseOrderLock(order.getId()); } /** * 退款方法 * * @param order 订单 */ private void refund(AuctionOrder order) { log.info("退款拍卖保证金订单{}", order.getId()); PayMethod payMethod = order.getPayMethod(); if (PayMethod.ALIPAY == payMethod) { if (StringUtils.length(order.getTransactionId()) == 28) { payMethod = PayMethod.HMPAY; } else if (StringUtils.length(order.getTransactionId()) == 30) { payMethod = PayMethod.SANDPAY; } } try { switch (payMethod) { case HMPAY: orderPayService.refund(order.getId().toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.HM); log.info("退款成功{}", order.getId()); break; case SANDPAY: orderPayService.refund(order.getId().toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.SAND); log.info("退款成功{}", order.getId()); break; case PAYEASE: orderPayService.refund(order.getId().toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.PE); log.info("退款成功{}", order.getId()); break; } order.setRefundTime(LocalDateTime.now()); order.setStatus(AuctionOrderStatus.REFUNDED); auctionOrderRepo.save(order); } catch (Exception e) { log.error("拍卖保证金订单退款失败 {} ", order.getId(), e); order.setRefundTime(LocalDateTime.now()); order.setStatus(AuctionOrderStatus.REFUNDING); auctionOrderRepo.save(order); } } public boolean getOrderLock(Long orderId) { BoundValueOperations ops = redisTemplate.boundValueOps(RedisKeys.AUCTION_ORDER_LOCK + orderId); Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.DAYS); return Boolean.TRUE.equals(flag); } public void releaseOrderLock(Long orderId) { redisTemplate.delete(RedisKeys.AUCTION_ORDER_LOCK + orderId); } @Scheduled(cron = "0 0/30 * * * ?") public void passOverTimeAuction() { List purchased = auctionActivityRepo.findAllByStatus(AuctionStatus.PURCHASED); if (purchased != null) { int time = sysConfigService.getInt("auction_cancel_time"); purchased.forEach(act -> { if (LocalDateTime.now().isAfter(act.getEndTime().plusMinutes(time))) { List auctionOrders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(act .getId(), AuctionPaymentType.PURCHASE_PRICE, AuctionOrderStatus.NOT_PAID); auctionOrders.forEach(this::cancel); auctionActivityService.changeStatus(act.getId(), AuctionStatus.PASS); log.info("拍卖定时任务流拍{}", act.getId()); //退其余保证金 List orders = auctionOrderRepo .findAllByAuctionIdAndPaymentTypeAndStatus(act.getId(), AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH); //退款 orders.stream() .filter(o -> !act.getPurchaserId().equals(o.getUserId())) .forEach(this::refund); //添加到流拍记录表里 auctionPassRecordRepo.save(AuctionPassRecord.builder() .auctionId(act.getId()) .userId(act.getPurchaserId()) .purchasePrice(act.getPurchasePrice()) .build()); } }); } } /** * 发货 * * @param id 编号 * @param courierId 快递单号 */ public void dispatch(Long id, String courierId) { AuctionOrder auctionOrder = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在")); auctionOrder.setStatus(AuctionOrderStatus.RECEIVE); auctionOrder.setCourierId(courierId); auctionOrderRepo.save(auctionOrder); } /** * 订单 * * @param id 编号 */ public void finish(Long id) { AuctionOrder auctionOrder = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在")); auctionOrder.setStatus(AuctionOrderStatus.FINISH); auctionOrderRepo.save(auctionOrder); } }