AuctionOrderService.java 20 KB

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