package com.izouma.nineth.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; import com.github.binarywang.wxpay.bean.order.WxPayMwebOrderResult; import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; 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.*; import com.izouma.nineth.domain.*; import com.izouma.nineth.domain.Collection; import com.izouma.nineth.dto.PageQuery; import com.izouma.nineth.enums.*; import com.izouma.nineth.event.OrderNotifyEvent; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.*; import com.izouma.nineth.utils.JpaUtils; import com.izouma.nineth.utils.SecurityUtils; import com.izouma.nineth.utils.SnowflakeIdWorker; 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.MapUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment; import org.springframework.data.domain.Page; import org.springframework.data.redis.core.BoundSetOperations; 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.transaction.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @Service public class MintOrderService { private final MintOrderRepo mintOrderRepo; private final UserRepo userRepo; private final AssetService assetService; private final AssetRepo assetRepo; private final MintActivityRepo mintActivityRepo; private final UserAddressRepo userAddressRepo; private final GeneralProperties generalProperties; private final Environment env; private final AdapayProperties adapayProperties; private final SnowflakeIdWorker snowflakeIdWorker; private final WxPayProperties wxPayProperties; private final WxPayService wxPayService; private final MintMaterialRepo mintMaterialRepo; private final MintActivityService mintActivityService; private final RedisTemplate redisTemplate; private final RocketMQTemplate rocketMQTemplate; private final CollectionRepo collectionRepo; private final OrderRepo orderRepo; private final ShowCollectionRepo showCollectionRepo; private final AirDropService airDropService; private final OrderPayService orderPayService; public MintOrderService(MintOrderRepo mintOrderRepo, UserRepo userRepo, AssetService assetService, AssetRepo assetRepo, MintActivityRepo mintActivityRepo, UserAddressRepo userAddressRepo, GeneralProperties generalProperties, Environment env, AdapayProperties adapayProperties, SnowflakeIdWorker snowflakeIdWorker, WxPayProperties wxPayProperties, WxPayService wxPayService, MintMaterialRepo mintMaterialRepo, MintActivityService mintActivityService, RedisTemplate redisTemplate, RocketMQTemplate rocketMQTemplate, CollectionRepo collectionRepo, OrderRepo orderRepo, ShowCollectionRepo showCollectionRepo, AirDropService airDropService, @Lazy OrderPayService orderPayService) { this.mintOrderRepo = mintOrderRepo; this.userRepo = userRepo; this.assetService = assetService; this.assetRepo = assetRepo; this.mintActivityRepo = mintActivityRepo; this.userAddressRepo = userAddressRepo; this.generalProperties = generalProperties; this.env = env; this.adapayProperties = adapayProperties; this.snowflakeIdWorker = snowflakeIdWorker; this.wxPayProperties = wxPayProperties; this.wxPayService = wxPayService; this.mintMaterialRepo = mintMaterialRepo; this.mintActivityService = mintActivityService; this.redisTemplate = redisTemplate; this.rocketMQTemplate = rocketMQTemplate; this.collectionRepo = collectionRepo; this.orderRepo = orderRepo; this.showCollectionRepo = showCollectionRepo; this.airDropService = airDropService; this.orderPayService = orderPayService; } public Page all(PageQuery pageQuery) { return mintOrderRepo.findAll(JpaUtils.toSpecification(pageQuery, MintOrder.class), JpaUtils.toPageRequest(pageQuery)); } @Transactional public void create(Long userId, List assetIds) { User user = userRepo.findByIdAndDelFalse(userId).orElseThrow(new BusinessException("用户不存在")); User blackHole = userRepo.findByIdAndDelFalse(1435297L).orElseThrow(new BusinessException("无法铸造")); if (assetIds.size() != 3) { throw new BusinessException("数量不正确,请重新选择"); } List assets = assetRepo.findAllByIdInAndUserId(assetIds, userId); assets = assets.stream() .filter(asset -> asset.getName().contains("尼尔斯") && AssetStatus.NORMAL.equals(asset.getStatus())) .collect(Collectors.toList()); if (assets.size() != 3) { throw new BusinessException("有藏品不符合,请重新选择"); } // 铸造订单 MintOrder order = mintOrderRepo.save(MintOrder.builder() .userId(userId) .phone(user.getPhone()) .consume(true) .status(MintOrderStatus.AIR_DROP) .build()); // 铸造资产 List materials = assets.stream().map(asset -> { MintMaterial material = new MintMaterial(); material.setAssetId(asset.getId()); material.setCollectionId(asset.getCollectionId()); material.setName(asset.getName()); material.setNumber(asset.getNumber()); material.setPic(asset.getPic()); material.setCategory(asset.getCategory()); material.setOrderId(order.getId()); return material; }).collect(Collectors.toList()); mintMaterialRepo.saveAll(materials); // 改为转赠 assets.forEach(asset -> assetService.transfer(asset, asset.getPrice(), blackHole, TransferReason.GIFT, null)); } /** * 订单 * * @param id 编号 */ public void finish(Long id) { MintOrder mintOrder = mintOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在")); mintOrder.setStatus(MintOrderStatus.FINISH); mintOrderRepo.save(mintOrder); } /** * 发货 * * @param id 编号 * @param courierId 快递单号 */ public void dispatch(Long id, String courierId) { MintOrder mintOrder = mintOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在")); mintOrder.setStatus(MintOrderStatus.RECEIVE); mintOrder.setCourierId(courierId); mintOrderRepo.save(mintOrder); } /** * @param user 用户 * @param assetId 资产 * @param mintActivityId 铸造活动 * @param addressId 地址 */ // @Transactional public MintOrder create(User user, List assetId, Long mintActivityId, Long addressId) { // 参加的活动 MintActivity mintActivity = mintActivityRepo.findByIdAndDelFalse(mintActivityId) .orElseThrow(new BusinessException("无此铸造活动")); if (mintActivity.isScheduleSale()) { if (mintActivity.getStartTime().isAfter(LocalDateTime.now())) { throw new BusinessException("当前还未开售"); } } if (!mintActivity.isOnShelf()) { throw new BusinessException("活动已下架"); } UserAddress userAddress = null; if (addressId != null) { userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在")); } int stock = Optional.ofNullable(mintActivityService.decreaseStock(mintActivityId, 1)) .map(Math::toIntExact) .orElseThrow(new BusinessException("很遗憾,铸造活动已无库存")); try { if (stock < 0) { throw new BusinessException("铸造活动已无库存"); } if (mintActivity.getNum() > 0) { if (assetId.size() != mintActivity.getNum()) { throw new BusinessException("数量不正确,请重新选择"); } } List assets = assetRepo.findAllByIdInAndUserId(assetId, user.getId()); // if (assets.stream().anyMatch(a -> a.isPublicShow() || a.isConsignment())) { // throw new BusinessException("请先下架所选藏品"); // } if (!mintActivity.isAudit()) { if (assets.stream().anyMatch(a -> a.getStatus() != AssetStatus.NORMAL)) { throw new BusinessException("所选藏品不符和规则,请重新选择"); } if (!mintActivityService.matchRule(new ArrayList<>(assets), mintActivity.getRule())) { throw new BusinessException("所选藏品不符和规则,请重新选择"); } } else { // 资产产品是否符合铸造活动的名称 assets = assets.stream() .filter(asset -> asset.getName() .contains(mintActivity.getCollectionName()) && AssetStatus.NORMAL.equals(asset.getStatus())) .collect(Collectors.toList()); if (mintActivity.getNum() > 0 && (assets.size() != mintActivity.getNum())) { throw new BusinessException("有藏品不符合,请重新选择"); } } Map privilegeIds = new HashMap<>(); // 铸造特权 if (!mintActivity.isConsume()) { assets.forEach(asset -> { List privileges = asset.getPrivileges() .stream() .filter(p -> p.getName().equals("铸造")) .collect(Collectors.toList()); if (privileges.size() == 0) { throw new BusinessException("无铸造特权"); } else { boolean flag = false; for (Privilege privilege : privileges) { // 打开多次 或者 可打开一次但未使用 if (!privilege.isOnce() || (privilege.isOnce() && !privilege .isOpened())) { flag = true; privilegeIds.put(asset.getId(), privilege.getId()); break; } } if (!flag) { throw new BusinessException("铸造特权已使用"); } } }); assets.forEach(asset -> { asset.getPrivileges() .stream() .filter(p -> p.getId().equals(privilegeIds.get(asset.getId()))) .forEach(p -> { p.setOpened(true); p.setOpenTime(LocalDateTime.now()); p.setOpenedBy(SecurityUtils.getAuthenticatedUser().getId()); }); assetRepo.save(asset); }); } else { // 转让的用户 userRepo.findByIdAndDelFalse(1435297L).orElseThrow(new BusinessException("无法铸造")); // 消耗改为转赠 assets.forEach(asset -> { if (!asset.getUserId().equals(user.getId())) { throw new BusinessException("此藏品不属于你"); } // 取消公开展示 if (asset.isPublicShow()) { if (asset.isConsignment()) { if (asset.getPublicCollectionId() != null) { List orders = orderRepo.findByCollectionId(asset.getPublicCollectionId()); if (orders.stream().anyMatch(o -> o.getStatus() != OrderStatus.CANCELLED)) { throw new BusinessException("已有订单不可取消寄售"); } } // asset.setConsignment(false); } Collection collection = collectionRepo.findById(asset.getPublicCollectionId()) .orElseThrow(new BusinessException("无展示记录")); collectionRepo.delete(collection); // 如果展厅有此藏品 showCollectionRepo.deleteAllByCollectionId(asset.getPublicCollectionId()); } }); // 统一处理 assets.forEach(asset -> { if (asset.isPublicShow()) { asset.setPublicShow(false); asset.setPublicCollectionId(null); if (asset.isConsignment()) { asset.setConsignment(false); } } asset.setStatus(AssetStatus.MINTING); assetRepo.save(asset); }); } // 铸造订单 MintOrder mintOrder = mintOrderRepo.save(MintOrder.builder() .userId(user.getId()) .phone(user.getPhone()) .consume(mintActivity.isConsume()) .status(MintOrderStatus.NOT_PAID) .airDrop(mintActivity.isAirDrop()) .gasPrice(mintActivity.getGasPrice()) .mintActivityId(mintActivityId) .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null)) .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null)) .address(Optional.ofNullable(userAddress).map(u -> u.getProvinceName() + " " + u.getCityName() + " " + u.getDistrictName() + " " + u.getAddress()) .orElse(null)) .build()); // 铸造资产 List materials = assets.stream().map(asset -> { MintMaterial material = new MintMaterial(); material.setAssetId(asset.getId()); material.setCollectionId(asset.getCollectionId()); material.setName(asset.getName()); material.setPrivilegeId(privilegeIds.get(asset.getId())); material.setNumber(asset.getNumber()); material.setPic(asset.getPic()); material.setCategory(asset.getCategory()); material.setOrderId(mintOrder.getId()); return material; }).collect(Collectors.toList()); mintMaterialRepo.saveAll(materials); if (mintOrder.getGasPrice().compareTo(BigDecimal.ZERO) == 0) { this.mintNotify(mintOrder.getId(), PayMethod.WEIXIN, null); } return mintOrder; } catch (Exception e) { // 错了加库存 mintActivityService.increaseStock(mintActivityId, 1); throw e; } } public Object payOrderWeixin(Long id, String tradeType, String openId) throws WxPayException, EncoderException { MintOrder order = mintOrderRepo.findById(id).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() != MintOrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); request.setBody("铸造GAS费"); request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId())); request.setTotalFee(order.getGasPrice().multiply(BigDecimal.valueOf(100)).intValue()); if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) { // 测试环境设为1分 // request.setTotalFee(1); } request.setSpbillCreateIp("180.102.110.170"); request.setNotifyUrl(wxPayProperties.getNotifyUrl()); request.setTradeType(tradeType); request.setOpenid(openId); request.setSignType("MD5"); JSONObject body = new JSONObject(); body.put("action", "payMintOrder"); body.put("userId", order.getUserId()); body.put("orderId", order.getId()); request.setAttach(body.toJSONString()); if (WxPayConstants.TradeType.MWEB.equals(tradeType)) { WxPayMwebOrderResult result = wxPayService.createOrder(request); return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl()); } else if (WxPayConstants.TradeType.JSAPI.equals(tradeType)) { return wxPayService.createOrder(request); } throw new BusinessException("不支持此付款方式"); } public Object payAdapay(Long id, String payChannel, String openId) throws BaseAdaPayException { List aliChannels = Arrays.asList("alipay", "alipay_qr", "alipay_wap"); List wxChannels = Arrays.asList("wx_pub", "wx_lite"); if (!aliChannels.contains(payChannel) && !wxChannels.contains(payChannel)) { throw new BusinessException("不支持此渠道"); } MintOrder order = mintOrderRepo.findById(id).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() != MintOrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } Map paymentParams = new HashMap<>(); paymentParams.put("order_no", String.valueOf(snowflakeIdWorker.nextId())); paymentParams.put("pay_amt", order.getGasPrice().setScale(2, RoundingMode.HALF_UP).toPlainString()); paymentParams.put("app_id", adapayProperties.getAppId()); paymentParams.put("pay_channel", payChannel); paymentParams.put("goods_title", "铸造GAS费"); paymentParams.put("goods_desc", "铸造GAS费"); paymentParams.put("time_expire", DateTimeFormatter.ofPattern("yyyyMMddHHmmss") .format(LocalDateTime.now().plusMinutes(5))); paymentParams.put("notify_url", adapayProperties.getNotifyUrl() + "/mintOrder/" + order.getId()); Map expend = new HashMap<>(); paymentParams.put("expend", expend); if ("wx_pub".equals(payChannel)) { if (StringUtils.isBlank(openId)) { throw new BusinessException("缺少openId"); } expend.put("open_id", openId); expend.put("limit_pay", "1"); } Map response; if ("wx_lite".equals(payChannel)) { paymentParams.put("adapay_func_code", "wxpay.createOrder"); paymentParams.put("callback_url", generalProperties.getHost() + "/9th/orders"); response = AdapayCommon.requestAdapayUits(paymentParams); log.info("createOrderResponse {}", JSON.toJSONString(response, SerializerFeature.PrettyFormat)); } else { response = Payment.create(paymentParams); log.info("createOrderResponse {}", JSON.toJSONString(response, SerializerFeature.PrettyFormat)); AdapayService.checkSuccess(response); // adapay 同步 BoundSetOperations ops = redisTemplate.boundSetOps(RedisKeys.ACTIVITY_PAY_RECORD + order.getId()); ops.add(MapUtils.getString(response, "id")); ops.expire(7, TimeUnit.DAYS); } switch (payChannel) { case "alipay_wap": case "alipay": return MapUtils.getString(MapUtils.getMap(response, "expend"), "pay_info"); case "alipay_qr": return MapUtils.getString(MapUtils.getMap(response, "expend"), "qrcode_url"); case "wx_pub": JSONObject payParams = JSON.parseObject(MapUtils.getString(MapUtils.getMap(response, "expend"), "pay_info")); payParams.put("timestamp", payParams.get("timeStamp")); payParams.remove("timeStamp"); return payParams; default: return MapUtils.getMap(response, "expend"); } } @Transactional public void mintNotify(Long orderId, PayMethod payMethod, String transactionId) { 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(), OrderNotifyEvent.TYPE_MINT_ORDER)); return; } try { MintOrder order = mintOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() == MintOrderStatus.NOT_PAID) { MintActivity activity = mintActivityRepo.findById(order.getMintActivityId()) .orElseThrow(new BusinessException("无活动")); //活动是否需要审核 if (activity.isAudit()) { order.setStatus(MintOrderStatus.PENDING); order.setTransactionId(transactionId); order.setPayMethod(payMethod); order.setPayAt(LocalDateTime.now()); mintOrderRepo.save(order); } else { this.notify(order, payMethod, transactionId, true); } } } catch (Exception e) { log.info("铸造订单回调出错 orderId={}", orderId, e); } releaseOrderLock(orderId); } public void orderAudit(Long orderId, boolean pass) { MintOrder order = mintOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在")); if (!MintOrderStatus.PENDING.equals(order.getStatus())) { throw new BusinessException("状态错误"); } if (pass) { this.notify(order, null, null, false); return; } this.cancel(order, true); } @Transactional public void notify(MintOrder mintOrder, PayMethod payMethod, String transactionId, boolean saveTransactionId) { Long orderId = mintOrder.getId(); try { // MintOrder mintOrder = mintOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在")); List materials = mintMaterialRepo.findAllByOrderIdAndDelFalse(orderId); List assets = assetRepo.findAllById(materials .stream() .map(MintMaterial::getAssetId) .collect(Collectors.toList())); if (saveTransactionId) { mintOrder.setPayMethod(payMethod); mintOrder.setTransactionId(transactionId); mintOrder.setPayAt(LocalDateTime.now()); } if (mintOrder.isAirDrop()) { mintOrder.setStatus(MintOrderStatus.AIR_DROP); MintActivity mintActivity = mintActivityRepo.findById(mintOrder.getMintActivityId()).orElse(null); if (ObjectUtils.isNotEmpty(mintActivity) && mintActivity.isAutoDrop()) { User user = userRepo.findById(mintOrder.getUserId()).orElseThrow(new BusinessException("无用户")); airDropService.create(AirDrop.builder() .name("铸造活动[" + mintActivity.getName() + "]空投") .remark(mintOrder.getId().toString()) .type(AirDropType.asset) .userIds(Collections.singletonList(mintOrder.getUserId())) .collectionId(mintActivity.getAirDropCollectionId()) .targets(Collections.singletonList(new DropTarget(user.getId(), user.getPhone(), user.getNickname(), 1))) .build()); mintOrder.setStatus(MintOrderStatus.FINISH); mintOrderRepo.save(mintOrder); } } else { mintOrder.setStatus(MintOrderStatus.DELIVERY); } if (mintOrder.isConsume()) { User newOwner = userRepo.findByIdAndDelFalse(1435297L).orElseThrow(new BusinessException("无法铸造")); assets.forEach(asset -> assetService.transfer(asset, asset.getPrice(), newOwner, TransferReason.GIFT, null)); } mintOrderRepo.save(mintOrder); } catch (Exception e) { if (e instanceof BusinessException) { log.error("铸造订单回调出错 orderId: {} {}", orderId, e.getMessage()); } else { log.error("铸造订单回调出错 orderId: " + orderId, e); } } } public void cancel(MintOrder order, boolean refund) { if (!getOrderLock(order.getId())) { log.error("订单取消失败 {}, redis锁了", order.getId()); return; } try { // 审核中或未支付订单可以取消 if (!(MintOrderStatus.PENDING == order.getStatus() || MintOrderStatus.NOT_PAID == order.getStatus() || refund)) { throw new BusinessException("此状态无法取消"); } List materials = mintMaterialRepo.findAllByOrderIdAndDelFalse(order.getId()); List assets = assetRepo.findAllById(materials.stream() .map(MintMaterial::getAssetId) .collect(Collectors.toList())); if (order.isConsume()) { assets.forEach(asset -> { asset.setStatus(AssetStatus.NORMAL); assetRepo.save(asset); }); } else { Map privilegeIds = materials.stream() .collect(Collectors.toMap(MintMaterial::getAssetId, MintMaterial::getPrivilegeId)); assets.forEach(asset -> { asset.getPrivileges() .stream() .filter(p -> p.getId().equals(privilegeIds.get(asset.getId()))) .forEach(p -> { p.setOpened(false); p.setOpenTime(null); p.setOpenedBy(null); }); assetRepo.save(asset); }); } log.info("set normal mintOrder {}", order.getId()); order.setStatus(MintOrderStatus.CANCELLED); order.setCancelTime(LocalDateTime.now()); mintOrderRepo.save(order); // 加库存 mintActivityService.increaseStock(order.getMintActivityId(), 1); log.info("取消订单{}", order.getId()); if (order.getPayMethod() != null && StringUtils.isNotBlank(order.getTransactionId())) { 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.getGasPrice(), Constants.PayChannel.HM); log.info("退款成功"); break; case SANDPAY: orderPayService.refund(order.getId().toString(), order.getTransactionId(), order.getGasPrice(), Constants.PayChannel.SAND); log.info("退款成功"); break; case PAYEASE: orderPayService.refund(order.getTransactionId(), order.getTransactionId(), order.getGasPrice(), Constants.PayChannel.PE); } } catch (Exception e) { log.error("铸造订单退款失败 {} ", order.getId(), e); } } } catch (Exception e) { log.error("订单取消错误 orderId: " + order.getId(), e); } releaseOrderLock(order.getId()); } public boolean getOrderLock(Long orderId) { BoundValueOperations ops = redisTemplate.boundValueOps(RedisKeys.MINT_ORDER_LOCK + orderId); Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.DAYS); return Boolean.TRUE.equals(flag); } public void releaseOrderLock(Long orderId) { redisTemplate.delete(RedisKeys.MINT_ORDER_LOCK + orderId); } }