| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- package com.izouma.nineth.service;
- import com.izouma.nineth.annotations.RedisLock;
- 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 javax.persistence.Transient;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- 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<String, Object> redisTemplate;
- @Autowired
- private SnowflakeIdWorker snowflakeIdWorker;
- @Autowired
- private AuctionPassRecordRepo auctionPassRecordRepo;
- @Lazy
- @Autowired
- private OrderPayService orderPayService;
- @Autowired
- private SmsService smsService;
- @Autowired
- private UserBalanceService userBalanceService;
- @Autowired
- private ShowroomService showroomService;
- @Autowired
- private CollectionRepo collectionRepo;
- @Autowired
- private ShowroomRepo showroomRepo;
- @Autowired
- private UserBalanceRepo userBalanceRepo;
- public Page<AuctionOrder> all(PageQuery pageQuery) {
- return auctionOrderRepo
- .findAll(JpaUtils.toSpecification(pageQuery, AuctionOrder.class), JpaUtils.toPageRequest(pageQuery));
- }
- @RedisLock("'createAuctionOrder::'+#auctionId")
- 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 = auctionActivityRepo.getStatus(auctionId);
- 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.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("超过支付时长");
- }
- } else {
- if (auction.getEndTime().isBefore(LocalDateTime.now())) {
- throw new BusinessException("拍卖已结束");
- }
- if (AuctionPaymentType.DEPOSIT.equals(type)) {
- return this.createDeposit(user, auction);
- }
- }
- UserAddress userAddress = null;
- if (addressId != null) {
- userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
- }
- try {
- auctionActivityRepo.updateStatus(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);
- }
- 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) {
- auctionActivityRepo.updateStatus(auctionId, AuctionStatus.ONGOING);
- throw e;
- }
- }
- public AuctionOrder createDeposit(User user, AuctionActivity auction) {
- if (user.getId().equals(auction.getSellerId())) {
- throw new BusinessException("不可自己出价自己的");
- }
- //竞拍人绿魔卡余额限制
- UserBalance userBalance = userBalanceRepo.findByUserId(user.getId()).orElse(null);
- BigDecimal minAmount = sysConfigService.getBigDecimal("auction_min_amount");
- if (userBalance == null) {
- throw new BusinessException("请开通绿魔卡,并充值" + minAmount + "元");
- } else if (minAmount.compareTo(userBalance.getBalance()) > 0) {
- throw new BusinessException("绿魔卡余额不足" + minAmount + "元,请先充值");
- }
- //保证金
- // 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);
- }
- @Transient
- 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;
- }
- //此拍卖结束
- //修改买家和成交价
- auction.setStatus(AuctionStatus.FINISH);
- auction.setPurchaserId(order.getUserId());
- auction.setPurchasePrice(order.getTotalPrice());
- auctionActivityRepo.save(auction);
- log.info("拍卖结束:{}", order.getAuctionId());
- 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());
- // 发送短信提醒用户转让成功
- if (asset.getUserId() != null) {
- smsService.sellOut(userRepo.findPhoneById(asset.getUserId()));
- }
- //用户冲余额
- BigDecimal amount = order.getTotalPrice()
- .multiply(BigDecimal.valueOf(100 - order.getRoyalties() - order.getServiceCharge()))
- .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
- userBalanceService.addBalance(asset.getUserId(), amount, id, BalanceType.AUCTION);
- }
- //改出价记录表为竞得(一口价无出价表)
- if (ObjectUtils.isNotEmpty(order.getAuctionRecordId())) {
- AuctionRecord record = auctionRecordRepo.findById(order.getAuctionRecordId())
- .orElseThrow(new BusinessException("无出价记录"));
- record.setPurchased(true);
- auctionRecordRepo.save(record);
- }
- //退保证金
- List<AuctionOrder> 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))) {
- //超过支付时长
- log.info("取消订单流拍:{}", auction.getId());
- auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.PASS);
- //添加到流拍记录表里
- auctionPassRecordRepo.save(AuctionPassRecord.builder()
- .auctionId(auction.getId())
- .userId(auction.getPurchaserId())
- .purchasePrice(auction.getPurchasePrice())
- .build());
- //流拍不退自己的保证金
- isRefund = true;
- if (AuctionSource.TRANSFER.equals(order.getSource())) {
- //改回资产状态
- Asset asset = assetRepo.findById(auction.getAssetId())
- .orElseThrow(new BusinessException("资产不存在"));
- asset.setStatus(AssetStatus.NORMAL);
- assetRepo.save(asset);
- }
- }
- } else if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) {
- //删除出价记录
- auctionRecordRepo.softDelete(order.getAuctionRecordId());
- } else {
- //拍卖是否结束
- if (LocalDateTime.now().isBefore(auction.getEndTime())) {
- //返回拍卖状态
- auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.ONGOING);
- } else {
- //最后一个出价的人得
- auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.PURCHASED);
- }
- }
- 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<AuctionOrder> 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;
- case BALANCE:
- userBalanceService.addBalance(order.getUserId(), order.getTotalPrice(), order.getId(), BalanceType.AUCTION_RETURN);
- 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<String, Object> 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/10 * * * ?")
- public void passOverTimeAuction() {
- List<AuctionActivity> 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<AuctionOrder> auctionOrders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(act
- .getId(), AuctionPaymentType.PURCHASE_PRICE, AuctionOrderStatus.NOT_PAID);
- // if (CollUtil.isNotEmpty(auctionOrders)) {
- auctionOrders.forEach(this::cancel);
- // return;
- // }
- auctionActivityRepo.updateStatus(act.getId(), AuctionStatus.PASS);
- log.info("拍卖定时任务流拍{}", act.getId());
- if (AuctionSource.TRANSFER.equals(act.getSource())) {
- //改回资产状态
- Asset asset = assetRepo.findById(act.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
- asset.setStatus(AssetStatus.NORMAL);
- assetRepo.save(asset);
- }
- //退其余保证金
- List<AuctionOrder> 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);
- }
- public void privilege(AuctionOrder order, User user) {
- if (showroomRepo.findByUserIdAndType(order.getUserId(), "AUCTION").isEmpty()) {
- //Bidder特殊拍卖展厅服务 创建一个bidder展厅藏品
- Long collectionId = (long) sysConfigService.getInt("bidder_collection_id");
- List<Asset> assets = assetRepo.findAllByUserIdAndCollectionIdAndStatus(order.getUserId(), collectionId, AssetStatus.NORMAL);
- Asset asset;
- if (assets.isEmpty()) {
- Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("无藏品"));
- if (!CollectionType.SHOWROOM.equals(collection.getType())) {
- throw new BusinessException("不是展厅藏品");
- }
- //创建资产
- asset = assetService.createAsset(collection, user, order.getId(), BigDecimal.ZERO, "拍卖赠送",
- null, false);
- } else {
- asset = assets.get(0);
- }
- //创建展厅
- showroomService.save(asset, "AUCTION");
- }
- //一个月的优先拍卖权
- PurchaserPrivilege.builder()
- .userId(order.getUserId())
- .title("元宇宙Bidder")
- .priority(true)
- .priorityExpireAt(LocalDateTime.now().plusMonths(1))
- .build();
- //前20名分钱
- BigDecimal totalPrice = order.getTotalPrice();
- //手续费
- int auctionServiceCharge = sysConfigService.getInt("auction_service_charge");
- BigDecimal serviceCharge = totalPrice.multiply(new BigDecimal(auctionServiceCharge))
- .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
- //奖励费用
- BigDecimal subtract = totalPrice.subtract(serviceCharge);
- int auctionReward = sysConfigService.getInt("auction_reward");
- BigDecimal reward = subtract.multiply(new BigDecimal(auctionReward))
- .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
- List<Long> records = auctionRecordRepo.findByAuctionId(order.getAuctionId(), 20);
- BigDecimal everyReward = reward.divide(new BigDecimal(records.size()), 2, RoundingMode.HALF_UP);
- //分奖励
- records.forEach(userId -> userBalanceService.addBalance(userId, everyReward, order.getId(), BalanceType.REWARD));
- //拍卖者所得
- BigDecimal amount = totalPrice.subtract(serviceCharge).subtract(reward);
- userBalanceService.addBalance(order.getUserId(), amount, order.getId(), BalanceType.AUCTION);
- }
- }
|