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.AdapayProperties; import com.izouma.nineth.config.GeneralProperties; import com.izouma.nineth.config.RedisKeys; import com.izouma.nineth.config.WxPayProperties; import com.izouma.nineth.domain.*; import com.izouma.nineth.dto.PageQuery; import com.izouma.nineth.enums.AssetStatus; import com.izouma.nineth.enums.MintOrderStatus; import com.izouma.nineth.enums.PayMethod; 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.StringUtils; import org.apache.rocketmq.spring.core.RocketMQTemplate; 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 @AllArgsConstructor public class MintOrderService { private MintOrderRepo mintOrderRepo; private UserRepo userRepo; private AssetService assetService; private AssetRepo assetRepo; private MintActivityRepo mintActivityRepo; private UserAddressRepo userAddressRepo; private GeneralProperties generalProperties; private Environment env; private AdapayProperties adapayProperties; private SnowflakeIdWorker snowflakeIdWorker; private WxPayProperties wxPayProperties; private WxPayService wxPayService; private MintMaterialRepo mintMaterialRepo; private MintActivityService mintActivityService; private RedisTemplate redisTemplate; private RocketMQTemplate rocketMQTemplate; 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()) // .material(materials) .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, "转赠", 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) { try { // 参加的活动 MintActivity mintActivity = mintActivityRepo.findByIdAndDelFalse(mintActivityId) .orElseThrow(new BusinessException("无此铸造活动")); int stock = Optional.ofNullable(mintActivityService.decreaseStock(mintActivityId, 1)) .map(Math::toIntExact) .orElseThrow(new BusinessException("很遗憾,藏品已售罄")); 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()); // 资产产品是否符合铸造活动的名称 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 { // 消耗改为转赠 assets.forEach(asset -> { asset.setStatus(AssetStatus.MINTING); assetRepo.save(asset); }); // 转让的用户 userRepo.findByIdAndDelFalse(1435297L).orElseThrow(new BusinessException("无法铸造")); } UserAddress userAddress = null; if (addressId != null) { userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在")); } // 铸造订单 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); //库存 // mintActivity.setStock(mintActivity.getStock() - 1); // mintActivityRepo.save(mintActivity); //销量 // mintActivityService.increaseSale(mintActivityId, 1); 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())); return; } 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())); mintOrder.setPayMethod(payMethod); if (mintOrder.isAirDrop()) { mintOrder.setStatus(MintOrderStatus.AIR_DROP); } else { mintOrder.setStatus(MintOrderStatus.DELIVERY); } mintOrder.setTransactionId(transactionId); mintOrder.setPayAt(LocalDateTime.now()); if (mintOrder.isConsume()) { User newOwner = userRepo.findByIdAndDelFalse(1435297L).orElseThrow(new BusinessException("无法铸造")); assets.forEach(asset -> assetService.transfer(asset, asset.getPrice(), newOwner, "转赠", null)); } mintOrderRepo.save(mintOrder); } catch (Exception e) { if (e instanceof BusinessException) { log.error("铸造订单回调出错 orderId: {} {}", orderId, e.getMessage()); } else { log.error("铸造订单回调出错 orderId: " + orderId, e); } } releaseOrderLock(orderId); } @Scheduled(fixedRate = 60000) public void batchCancel() { if (generalProperties.isNotifyServer()) { return; } if (Arrays.asList(env.getActiveProfiles()).contains("dev")) { return; } List orders = mintOrderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(MintOrderStatus.NOT_PAID, LocalDateTime.now().minusMinutes(5)); orders.forEach(o -> { try { cancel(o); } catch (Exception ignored) { } }); } public void cancel(MintOrder order) { if (!getOrderLock(order.getId())) { log.error("订单取消失败 {}, redis锁了", order.getId()); return; } try { if (order.getStatus() != MintOrderStatus.NOT_PAID) { throw new BusinessException("已支付订单无法取消"); } Set transactionIds = redisTemplate.opsForSet() .members(RedisKeys.ACTIVITY_PAY_RECORD + order.getId()); if (transactionIds != null && transactionIds.size() > 0) { if (transactionIds.parallelStream().anyMatch(transactionId -> { try { Map map = Payment.query(transactionId.toString()); return "succeeded".equalsIgnoreCase(MapUtils.getString(map, "status")) || "pending".equalsIgnoreCase(MapUtils.getString(map, "status")); } catch (BaseAdaPayException e) { e.printStackTrace(); } return false; })) { throw new BusinessException("订单已经支付成功或待支付,不能取消 " + order.getId()); } } 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); // 加库存 // mintActivityRepo.addStock(order.getMintActivityId()); mintActivityService.increaseStock(order.getMintActivityId(), 1); // mintActivityService.decreaseSale(order.getMintActivityId(), 1); rocketMQTemplate.syncSend(generalProperties.getUpdateStockTopic(), order.getMintActivityId(), 10000); log.info("取消订单{}", order.getId()); } 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); } }