AuctionOrderService.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. package com.izouma.nineth.service;
  2. import com.izouma.nineth.config.Constants;
  3. import com.izouma.nineth.config.RedisKeys;
  4. import com.izouma.nineth.domain.*;
  5. import com.izouma.nineth.dto.PageQuery;
  6. import com.izouma.nineth.enums.*;
  7. import com.izouma.nineth.exception.BusinessException;
  8. import com.izouma.nineth.repo.*;
  9. import com.izouma.nineth.utils.JpaUtils;
  10. import com.izouma.nineth.utils.SecurityUtils;
  11. import com.izouma.nineth.utils.SnowflakeIdWorker;
  12. import lombok.AllArgsConstructor;
  13. import lombok.extern.slf4j.Slf4j;
  14. import org.apache.commons.lang3.ObjectUtils;
  15. import org.apache.commons.lang3.StringUtils;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.context.annotation.Lazy;
  18. import org.springframework.data.domain.Page;
  19. import org.springframework.data.redis.core.BoundValueOperations;
  20. import org.springframework.data.redis.core.RedisTemplate;
  21. import org.springframework.scheduling.annotation.Scheduled;
  22. import org.springframework.stereotype.Service;
  23. import java.math.BigDecimal;
  24. import java.time.LocalDateTime;
  25. import java.util.Arrays;
  26. import java.util.List;
  27. import java.util.Optional;
  28. import java.util.concurrent.TimeUnit;
  29. @Slf4j
  30. @Service
  31. public class AuctionOrderService {
  32. @Autowired
  33. private AuctionOrderRepo auctionOrderRepo;
  34. @Autowired
  35. private SysConfigService sysConfigService;
  36. @Autowired
  37. private UserRepo userRepo;
  38. @Autowired
  39. private AssetService assetService;
  40. @Autowired
  41. private AuctionActivityRepo auctionActivityRepo;
  42. @Autowired
  43. private AuctionRecordRepo auctionRecordRepo;
  44. @Autowired
  45. private AssetRepo assetRepo;
  46. @Autowired
  47. private UserAddressRepo userAddressRepo;
  48. @Autowired
  49. private AuctionActivityService auctionActivityService;
  50. @Autowired
  51. private RedisTemplate<String, Object> redisTemplate;
  52. @Autowired
  53. private SnowflakeIdWorker snowflakeIdWorker;
  54. @Autowired
  55. private AuctionPassRecordRepo auctionPassRecordRepo;
  56. @Lazy
  57. @Autowired
  58. private OrderPayService orderPayService;
  59. public Page<AuctionOrder> all(PageQuery pageQuery) {
  60. return auctionOrderRepo
  61. .findAll(JpaUtils.toSpecification(pageQuery, AuctionOrder.class), JpaUtils.toPageRequest(pageQuery));
  62. }
  63. public AuctionOrder create(Long userId, Long auctionId, Long addressId, Long auctionRecordId, AuctionPaymentType type) {
  64. User user = userRepo.findById(userId).orElseThrow(new BusinessException("无用户"));
  65. AuctionActivity auction = auctionActivityRepo.findById(auctionId)
  66. .orElseThrow(new BusinessException("无拍卖信息"));
  67. if (!auction.isOnShelf()) {
  68. throw new BusinessException("拍卖已结束");
  69. }
  70. String status = (String) redisTemplate.opsForValue().get(RedisKeys.AUCTION_STATUS + auctionId);
  71. if (status == null)
  72. status = auction.getStatus().toString();
  73. switch (AuctionStatus.valueOf(status)) {
  74. case NOTSTARTED:
  75. throw new BusinessException("拍卖还未开始");
  76. // case PURCHASED:
  77. // throw new BusinessException("拍卖成交中");
  78. case PASS:
  79. throw new BusinessException("已经流拍");
  80. case FINISH:
  81. throw new BusinessException("拍卖已结束");
  82. case FIXED_PRICE_PURCHASED:
  83. if (AuctionPaymentType.FIXED_PRICE.equals(type)) {
  84. throw new BusinessException("一口价成交中");
  85. }
  86. }
  87. if (user.getId().equals(auction.getSellerId())) {
  88. throw new BusinessException("不可自己出价自己的");
  89. }
  90. if (AuctionPaymentType.DEPOSIT.equals(type)) {
  91. return this.createDeposit(user, auction);
  92. }
  93. if (AuctionPaymentType.PURCHASE_PRICE.equals(type)) {
  94. if (auction.getEndTime().isAfter(LocalDateTime.now())) {
  95. throw new BusinessException("拍卖还未结束");
  96. }
  97. AuctionRecord record = auctionRecordRepo.findTopByAuctionIdAndUserIdOrderByIdDesc(auctionId, userId);
  98. if (ObjectUtils.isEmpty(record) || !record.isPayDeposit()) {
  99. throw new BusinessException("未支付保证金");
  100. }
  101. if (record.getBidderPrice().compareTo(auction.getPurchasePrice()) != 0) {
  102. throw new BusinessException("与成交价不否");
  103. }
  104. int time = sysConfigService.getInt("auction_cancel_time");
  105. if (LocalDateTime.now().isAfter(auction.getEndTime().plusMinutes(time))) {
  106. throw new BusinessException("超过支付时长");
  107. }
  108. }
  109. UserAddress userAddress = null;
  110. if (addressId != null) {
  111. userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
  112. }
  113. try {
  114. auctionActivityService.changeStatus(auctionId, AuctionPaymentType.FIXED_PRICE
  115. .equals(type) ? AuctionStatus.FIXED_PRICE_PURCHASED : AuctionStatus.PURCHASED);
  116. if (AuctionSource.TRANSFER.equals(auction.getSource())) {
  117. Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
  118. asset.setStatus(AssetStatus.AUCTION_TRADING);
  119. assetRepo.save(asset);
  120. }
  121. BigDecimal price = AuctionPaymentType.FIXED_PRICE.equals(type) ? auction.getFixedPrice() : auction
  122. .getPurchasePrice();
  123. AuctionOrder order = AuctionOrder.builder()
  124. .id(snowflakeIdWorker.nextId())
  125. .auctionId(auction.getId())
  126. .userId(user.getId())
  127. .nickname(user.getNickname())
  128. .paymentType(type)
  129. .name(auction.getName())
  130. .pic(auction.getPic())
  131. .serviceCharge(auction.getServiceCharge())
  132. .royalties(auction.getRoyalties())
  133. .source(auction.getSource())
  134. .price(price)
  135. .totalPrice(price)
  136. .auctionRecordId(auctionRecordId)
  137. .status(AuctionOrderStatus.NOT_PAID)
  138. .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null))
  139. .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null))
  140. .address(Optional.ofNullable(userAddress).map(UserAddress::getDetail).orElse(null))
  141. .build();
  142. return auctionOrderRepo.save(order);
  143. } catch (Exception e) {
  144. auctionActivityService.changeStatus(auctionId, AuctionStatus.ONGOING);
  145. throw e;
  146. }
  147. }
  148. public AuctionOrder createDeposit(User user, AuctionActivity auction) {
  149. if (user.getId().equals(auction.getSellerId())) {
  150. throw new BusinessException("不可自己出价自己的");
  151. }
  152. //保证金
  153. // BigDecimal deposit = sysConfigService.getBigDecimal("deposit");
  154. AuctionOrder order = auctionOrderRepo
  155. .findByUserIdAndAuctionIdAndPaymentTypeAndStatusIn(user.getId(), auction.getId(),
  156. AuctionPaymentType.DEPOSIT, Arrays
  157. .asList(AuctionOrderStatus.NOT_PAID, AuctionOrderStatus.FINISH));
  158. if (ObjectUtils.isNotEmpty(order)) {
  159. if (AuctionOrderStatus.FINISH.equals(order.getStatus())) {
  160. throw new BusinessException("保证金已交过,无需再交");
  161. }
  162. throw new BusinessException("保证金未支付,取消后再重新出价");
  163. }
  164. AuctionRecord auctionRecord = AuctionRecord.builder()
  165. .auctionId(auction.getId())
  166. .type(AuctionRecordType.DEPOSIT)
  167. .bidderPrice(auction.getDeposit())
  168. .auctionPic(null)
  169. .userId(SecurityUtils.getAuthenticatedUser().getId())
  170. .avatar(SecurityUtils.getAuthenticatedUser().getAvatar())
  171. .name(auction.getName())
  172. .purchased(false)
  173. .auctionType(auction.getAuctionType())
  174. .build();
  175. AuctionRecord record = auctionRecordRepo.save(auctionRecord);
  176. order = AuctionOrder.builder()
  177. .id(snowflakeIdWorker.nextId())
  178. .auctionId(auction.getId())
  179. .userId(user.getId())
  180. .nickname(user.getNickname())
  181. .paymentType(AuctionPaymentType.DEPOSIT)
  182. .name(auction.getName())
  183. .pic(auction.getPic())
  184. .serviceCharge(auction.getServiceCharge())
  185. .royalties(auction.getRoyalties())
  186. .source(auction.getSource())
  187. .price(auction.getDeposit())
  188. .totalPrice(auction.getDeposit())
  189. .auctionRecordId(record.getId())
  190. .status(AuctionOrderStatus.NOT_PAID)
  191. .build();
  192. return auctionOrderRepo.save(order);
  193. }
  194. public void notify(Long id, PayMethod payMethod, String transactionId) {
  195. AuctionOrder order = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("无记录"));
  196. if (!order.getStatus().equals(AuctionOrderStatus.NOT_PAID)) {
  197. throw new BusinessException("订单已处理");
  198. }
  199. AuctionActivity auction = auctionActivityRepo.findById(order.getAuctionId())
  200. .orElseThrow(new BusinessException("无拍卖活动"));
  201. if (auction.getAuctionType().equals(AuctionType.ENTITY)) {
  202. order.setStatus(AuctionOrderStatus.DELIVERY);
  203. } else {
  204. order.setStatus(AuctionOrderStatus.FINISH);
  205. }
  206. order.setPayMethod(payMethod);
  207. order.setTransactionId(transactionId);
  208. order.setPayTime(LocalDateTime.now());
  209. //存订单
  210. auctionOrderRepo.save(order);
  211. if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) {
  212. //改出价记录表
  213. AuctionRecord record = auctionRecordRepo.findById(order.getAuctionRecordId())
  214. .orElseThrow(new BusinessException("无出价记录"));
  215. record.setPayDeposit(true);
  216. auctionRecordRepo.save(record);
  217. return;
  218. }
  219. //此拍卖结束
  220. auctionActivityService.changeStatus(order.getAuctionId(), AuctionStatus.FINISH);
  221. if (AuctionSource.TRANSFER.equals(order.getSource())) {
  222. Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
  223. if (asset.isPublicShow()) {
  224. //取消公开展示
  225. assetService.cancelPublic(asset);
  226. }
  227. User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("无用户"));
  228. //转让流程
  229. assetService.transfer(asset, order.getTotalPrice(), user, TransferReason.AUCTION, order.getId());
  230. }
  231. //该出价记录表为竞得
  232. AuctionRecord record = auctionRecordRepo.findById(order.getAuctionRecordId())
  233. .orElseThrow(new BusinessException("无出价记录"));
  234. record.setPurchased(true);
  235. auctionRecordRepo.save(record);
  236. //退保证金
  237. List<AuctionOrder> orders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(order.getAuctionId(),
  238. AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
  239. //退款
  240. orders.forEach(this::refund);
  241. }
  242. public void cancel(AuctionOrder order) {
  243. if (!getOrderLock(order.getId())) {
  244. log.error("订单取消失败 {}, redis锁了", order.getId());
  245. return;
  246. }
  247. boolean isRefund = false;
  248. try {
  249. AuctionActivity auction = auctionActivityRepo.findById(order.getAuctionId())
  250. .orElseThrow(new BusinessException("无记录"));
  251. if (AuctionPaymentType.PURCHASE_PRICE.equals(order.getPaymentType())) {
  252. //如果是拍卖,需获取取消订单的时长
  253. int time = sysConfigService.getInt("auction_cancel_time");
  254. if (LocalDateTime.now().isAfter(auction.getEndTime().plusMinutes(time))) {
  255. //超过支付时长
  256. auctionActivityService.changeStatus(order.getAuctionId(), AuctionStatus.PASS);
  257. //添加到流拍记录表里
  258. auctionPassRecordRepo.save(AuctionPassRecord.builder()
  259. .auctionId(auction.getId())
  260. .userId(auction.getPurchaserId())
  261. .purchasePrice(auction.getPurchasePrice())
  262. .build());
  263. //流拍不退自己的保证金
  264. isRefund = true;
  265. }
  266. } else if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) {
  267. //删除出价记录
  268. auctionRecordRepo.softDelete(order.getAuctionRecordId());
  269. } else {
  270. //拍卖是否结束
  271. if (LocalDateTime.now().isBefore(auction.getEndTime())) {
  272. //返回拍卖状态
  273. auctionActivityService.changeStatus(order.getAuctionId(), AuctionStatus.ONGOING);
  274. } else {
  275. //最后一个出价的人得
  276. auctionActivityService.changeStatus(order.getAuctionId(), AuctionStatus.PURCHASED);
  277. }
  278. }
  279. if (AuctionSource.TRANSFER.equals(order.getSource())) {
  280. //改回资产状态
  281. Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
  282. asset.setStatus(AssetStatus.NORMAL);
  283. assetRepo.save(asset);
  284. }
  285. order.setStatus(AuctionOrderStatus.CANCELLED);
  286. order.setCancelTime(LocalDateTime.now());
  287. auctionOrderRepo.save(order);
  288. log.info("取消订单{}", order.getId());
  289. } catch (Exception e) {
  290. log.error("订单取消错误 orderId: " + order.getId(), e);
  291. }
  292. if (isRefund) {
  293. //退其余保证金
  294. List<AuctionOrder> orders = auctionOrderRepo
  295. .findAllByAuctionIdAndPaymentTypeAndStatus(order.getAuctionId(),
  296. AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
  297. //退款
  298. orders.stream()
  299. .filter(o -> !order.getUserId().equals(o.getUserId()))
  300. .forEach(this::refund);
  301. }
  302. releaseOrderLock(order.getId());
  303. }
  304. /**
  305. * 退款方法
  306. *
  307. * @param order 订单
  308. */
  309. private void refund(AuctionOrder order) {
  310. log.info("退款拍卖保证金订单{}", order.getId());
  311. PayMethod payMethod = order.getPayMethod();
  312. if (PayMethod.ALIPAY == payMethod) {
  313. if (StringUtils.length(order.getTransactionId()) == 28) {
  314. payMethod = PayMethod.HMPAY;
  315. } else if (StringUtils.length(order.getTransactionId()) == 30) {
  316. payMethod = PayMethod.SANDPAY;
  317. }
  318. }
  319. try {
  320. switch (payMethod) {
  321. case HMPAY:
  322. orderPayService.refund(order.getId().toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.HM);
  323. log.info("退款成功{}", order.getId());
  324. break;
  325. case SANDPAY:
  326. orderPayService.refund(order.getId().toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.SAND);
  327. log.info("退款成功{}", order.getId());
  328. break;
  329. case PAYEASE:
  330. orderPayService.refund(order.getId().toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.PE);
  331. log.info("退款成功{}", order.getId());
  332. break;
  333. }
  334. order.setRefundTime(LocalDateTime.now());
  335. order.setStatus(AuctionOrderStatus.REFUNDED);
  336. auctionOrderRepo.save(order);
  337. } catch (Exception e) {
  338. log.error("拍卖保证金订单退款失败 {} ", order.getId(), e);
  339. order.setRefundTime(LocalDateTime.now());
  340. order.setStatus(AuctionOrderStatus.REFUNDING);
  341. auctionOrderRepo.save(order);
  342. }
  343. }
  344. public boolean getOrderLock(Long orderId) {
  345. BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.AUCTION_ORDER_LOCK + orderId);
  346. Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.DAYS);
  347. return Boolean.TRUE.equals(flag);
  348. }
  349. public void releaseOrderLock(Long orderId) {
  350. redisTemplate.delete(RedisKeys.AUCTION_ORDER_LOCK + orderId);
  351. }
  352. @Scheduled(cron = "0 0/30 * * * ?")
  353. public void passOverTimeAuction() {
  354. List<AuctionActivity> purchased = auctionActivityRepo.findAllByStatus(AuctionStatus.PURCHASED);
  355. if (purchased != null) {
  356. int time = sysConfigService.getInt("auction_cancel_time");
  357. purchased.forEach(act -> {
  358. if (LocalDateTime.now().isAfter(act.getEndTime().plusMinutes(time))) {
  359. List<AuctionOrder> auctionOrders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(act
  360. .getId(), AuctionPaymentType.PURCHASE_PRICE, AuctionOrderStatus.NOT_PAID);
  361. auctionOrders.forEach(this::cancel);
  362. auctionActivityService.changeStatus(act.getAssetId(), AuctionStatus.PASS);
  363. log.info("拍卖定时任务流拍{}", act.getId());
  364. //退其余保证金
  365. List<AuctionOrder> orders = auctionOrderRepo
  366. .findAllByAuctionIdAndPaymentTypeAndStatus(act.getId(),
  367. AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
  368. //退款
  369. orders.stream()
  370. .filter(o -> !act.getPurchaserId().equals(o.getUserId()))
  371. .forEach(this::refund);
  372. //添加到流拍记录表里
  373. auctionPassRecordRepo.save(AuctionPassRecord.builder()
  374. .auctionId(act.getId())
  375. .userId(act.getPurchaserId())
  376. .purchasePrice(act.getPurchasePrice())
  377. .build());
  378. }
  379. });
  380. }
  381. }
  382. /**
  383. * 发货
  384. *
  385. * @param id 编号
  386. * @param courierId 快递单号
  387. */
  388. public void dispatch(Long id, String courierId) {
  389. AuctionOrder auctionOrder = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在"));
  390. auctionOrder.setStatus(AuctionOrderStatus.RECEIVE);
  391. auctionOrder.setCourierId(courierId);
  392. auctionOrderRepo.save(auctionOrder);
  393. }
  394. /**
  395. * 订单
  396. *
  397. * @param id 编号
  398. */
  399. public void finish(Long id) {
  400. AuctionOrder auctionOrder = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在"));
  401. auctionOrder.setStatus(AuctionOrderStatus.FINISH);
  402. auctionOrderRepo.save(auctionOrder);
  403. }
  404. }