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.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.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.springframework.core.env.Environment; import org.springframework.data.domain.Page; 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.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; 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("有藏品不符合,请重新选择"); } // 铸造资产 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()); return material; }).collect(Collectors.toList()); // 铸造订单 mintOrderRepo.save(MintOrder.builder() .userId(userId) .phone(user.getPhone()) .material(materials) .consume(true) .status(MintOrderStatus.AIR_DROP) .build()); // 改为转赠 assets.forEach(asset -> assetService.transfer(asset, asset.getPrice(), blackHole, "转赠", null)); } public void finish(Long id) { MintOrder mintOrder = mintOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在")); mintOrder.setStatus(MintOrderStatus.FINISH); mintOrderRepo.save(mintOrder); } /** * @param user 用户 * @param assetId 资产 * @param mintActivityId 铸造活动 * @param addressId 地址 */ @Transactional public Long create(User user, List assetId, Long mintActivityId, Long addressId) { // 参加的活动 MintActivity mintActivity = mintActivityRepo.findByIdAndDelFalse(mintActivityId) .orElseThrow(new BusinessException("无此铸造活动")); if (mintActivity.getStock() <= 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("无法铸造")); } // 铸造资产 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()); return material; }).collect(Collectors.toList()); UserAddress userAddress = null; if (addressId != null) { userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在")); } // 铸造订单 mintOrderRepo.save(MintOrder.builder() .userId(user.getId()) .phone(user.getPhone()) .material(materials) .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.getProvinceId() + " " + u.getCityId() + " " + u.getDistrictId() + " " + u.getAddress()) .orElse(null)) .build()); //库存 mintActivity.setStock(mintActivity.getStock() - 1); return mintActivityRepo.save(mintActivity).getId(); } 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); } 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) { MintOrder mintOrder = mintOrderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在")); List materials = mintOrder.getMaterial(); 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); } @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 (order.getStatus() != MintOrderStatus.NOT_PAID) { throw new BusinessException("已支付订单无法取消"); } List materials = order.getMaterial(); 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()); } }