|
|
@@ -15,16 +15,15 @@ import com.github.binarywang.wxpay.service.WxPayService;
|
|
|
import com.huifu.adapay.core.exception.BaseAdaPayException;
|
|
|
import com.huifu.adapay.model.AdapayCommon;
|
|
|
import com.huifu.adapay.model.Payment;
|
|
|
-import com.izouma.nineth.config.AdapayProperties;
|
|
|
-import com.izouma.nineth.config.AlipayProperties;
|
|
|
-import com.izouma.nineth.config.GeneralProperties;
|
|
|
-import com.izouma.nineth.config.WxPayProperties;
|
|
|
+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.PageQuery;
|
|
|
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.*;
|
|
|
@@ -35,6 +34,7 @@ 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.CollectionUtils;
|
|
|
import org.apache.commons.collections.MapUtils;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
import org.apache.rocketmq.client.producer.SendResult;
|
|
|
@@ -42,6 +42,8 @@ import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
|
|
import org.springframework.context.event.EventListener;
|
|
|
import org.springframework.core.env.Environment;
|
|
|
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 org.springframework.ui.Model;
|
|
|
@@ -52,6 +54,7 @@ import java.math.RoundingMode;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
@Service
|
|
|
@AllArgsConstructor
|
|
|
@@ -77,6 +80,8 @@ public class OrderService {
|
|
|
private GeneralProperties generalProperties;
|
|
|
private SnowflakeIdWorker snowflakeIdWorker;
|
|
|
private RocketMQTemplate rocketMQTemplate;
|
|
|
+ private RedisTemplate<String, Object> redisTemplate;
|
|
|
+ private ErrorOrderRepo errorOrderRepo;
|
|
|
|
|
|
public Page<Order> all(PageQuery pageQuery) {
|
|
|
return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery));
|
|
|
@@ -203,7 +208,7 @@ public class OrderService {
|
|
|
JSONObject bizContent = new JSONObject();
|
|
|
bizContent.put("notifyUrl", alipayProperties.getNotifyUrl());
|
|
|
bizContent.put("returnUrl", alipayProperties.getReturnUrl());
|
|
|
- bizContent.put("out_trade_no", String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
|
|
|
+ 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"))) {
|
|
|
@@ -240,7 +245,7 @@ public class OrderService {
|
|
|
|
|
|
WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
|
|
|
request.setBody(order.getName());
|
|
|
- request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId()));
|
|
|
+ request.setOutTradeNo(String.valueOf(snowflakeIdWorker.nextId()));
|
|
|
request.setTotalFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
|
|
|
if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) {
|
|
|
// 测试环境设为1分
|
|
|
@@ -284,7 +289,7 @@ public class OrderService {
|
|
|
}
|
|
|
|
|
|
Map<String, Object> paymentParams = new HashMap<>();
|
|
|
- paymentParams.put("order_no", String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
|
|
|
+ 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);
|
|
|
@@ -389,39 +394,99 @@ public class OrderService {
|
|
|
return restAmount.subtract(divAmount);
|
|
|
}
|
|
|
|
|
|
- @Transactional
|
|
|
public void notifyOrder(Long orderId, PayMethod payMethod, String transactionId) {
|
|
|
- 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) {
|
|
|
- BlindBoxItem winItem = collectionService.draw(collection.getId());
|
|
|
- order.setWinCollectionId(winItem.getCollectionId());
|
|
|
- orderRepo.save(order);
|
|
|
- assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
|
|
|
- collectionService.getNextNumber(winItem.getCollectionId()));
|
|
|
- addSales(winItem.getMinterId());
|
|
|
- } else {
|
|
|
- if (collection.getSource() == CollectionSource.TRANSFER) {
|
|
|
- Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null);
|
|
|
- assetService.transfer(asset, order.getPrice(), user, "转让", order.getId());
|
|
|
- collectionRepo.delete(collection);
|
|
|
- } else {
|
|
|
+ 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<String, Object> 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<String, Object> 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);
|
|
|
- assetService.createAsset(collection, user, order.getId(), order.getPrice(), "出售",
|
|
|
- collectionService.getNextNumber(order.getCollectionId()));
|
|
|
+ assetService.createAsset(winItem, user, order.getId(), order.getPrice(), "出售",
|
|
|
+ winItem.getTotal() > 1 ? collectionService.getNextNumber(winItem.getCollectionId()) : null);
|
|
|
+ } 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());
|
|
|
+ List<Long> collectionIds = collectionRepo.findAllByAssetId(collection.getAssetId());
|
|
|
+ if (CollectionUtils.isNotEmpty(collectionIds)) {
|
|
|
+ log.info("删除collection {}", collectionIds);
|
|
|
+ collectionRepo.deleteAllByIdIn(collectionIds);
|
|
|
+ } else {
|
|
|
+ log.info("删除collection {}", collection.getId());
|
|
|
+ collectionRepo.delete(collection);
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ orderRepo.save(order);
|
|
|
+ assetService.createAsset(collection, user, order.getId(), order.getPrice(), "出售",
|
|
|
+ collection.getTotal() > 1 ? collectionService.getNextNumber(order.getCollectionId()) : null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ commission(order);
|
|
|
+ if (collection.getAssetId() == null) {
|
|
|
+ collectionService.increaseSale(order.getCollectionId(), order.getQty());
|
|
|
}
|
|
|
- addSales(collection.getMinterId());
|
|
|
+ } else {
|
|
|
+ throw new BusinessException("状态错误 " + order.getStatus());
|
|
|
}
|
|
|
- commission(order);
|
|
|
- } else if (order.getStatus() == OrderStatus.CANCELLED) {
|
|
|
+ } 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
|
|
|
@@ -573,7 +638,20 @@ public class OrderService {
|
|
|
request.setTransactionId(order.getTransactionId());
|
|
|
request.setTotalFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
|
|
|
request.setRefundFee(order.getTotalPrice().multiply(BigDecimal.valueOf(100)).intValue());
|
|
|
- request.setOutRefundNo(String.valueOf(new SnowflakeIdWorker(0, 0).nextId()));
|
|
|
+ request.setOutRefundNo(String.valueOf(snowflakeIdWorker.nextId()));
|
|
|
wxPayService.refund(request);
|
|
|
}
|
|
|
+
|
|
|
+ // 获取订单锁,有效时间1小时
|
|
|
+ public boolean getOrderLock(Long orderId) {
|
|
|
+ BoundValueOperations<String, Object> 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);
|
|
|
+ }
|
|
|
+
|
|
|
}
|