package com.izouma.nineth.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alipay.api.AlipayClient; import com.alipay.api.request.AlipayTradeWapPayRequest; 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.izouma.nineth.config.AlipayProperties; import com.izouma.nineth.config.WxPayProperties; import com.izouma.nineth.domain.Collection; import com.izouma.nineth.domain.*; import com.izouma.nineth.dto.PageQuery; import com.izouma.nineth.enums.CollectionType; import com.izouma.nineth.enums.OrderStatus; import com.izouma.nineth.enums.PayMethod; import com.izouma.nineth.event.CreateAssetEvent; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.*; import com.izouma.nineth.utils.JpaUtils; 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.lang3.RandomUtils; import org.apache.commons.lang3.Range; import org.springframework.context.event.EventListener; import org.springframework.core.env.Environment; import org.springframework.data.domain.Page; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import javax.transaction.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.*; @Service @AllArgsConstructor @Slf4j public class OrderService { private OrderRepo orderRepo; private CollectionRepo collectionRepo; private UserAddressRepo userAddressRepo; private UserRepo userRepo; private Environment env; private AlipayClient alipayClient; private AlipayProperties alipayProperties; private WxPayService wxPayService; private WxPayProperties wxPayProperties; private AssetService assetService; private SysConfigService sysConfigService; private BlindBoxItemRepo blindBoxItemRepo; public Page all(PageQuery pageQuery) { return orderRepo.findAll(JpaUtils.toSpecification(pageQuery, Order.class), JpaUtils.toPageRequest(pageQuery)); } @Transactional public Order create(Long userId, Long collectionId, int qty, Long addressId) { if (qty <= 0) throw new BusinessException("数量必须大于0"); User user = userRepo.findByIdAndDelFalse(userId).orElseThrow(new BusinessException("用户不存在")); Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("藏品不存在")); User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在")); if (!collection.isOnShelf()) { throw new BusinessException("藏品已下架"); } if (qty > collection.getStock()) { throw new BusinessException("库存不足"); } if (!collection.isSalable()) { throw new BusinessException("该藏品当前不可购买"); } if (collection.getType() == CollectionType.BLIND_BOX) { if (collection.getStartTime().isAfter(LocalDateTime.now())) { throw new BusinessException("盲盒未开售"); } } UserAddress userAddress = null; if (addressId != null) { userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在")); } collection.setStock(collection.getStock() - qty); collection.setSale(collection.getSale() + qty); collectionRepo.save(collection); minter.setSales(minter.getSales() + 1); BigDecimal gasFee = sysConfigService.getBigDecimal("gas_fee"); Order order = Order.builder() .userId(userId) .collectionId(collectionId) .name(collection.getName()) .pic(collection.getPics()) .detail(collection.getDetail()) .properties(collection.getProperties()) .category(collection.getCategory()) .canResale(collection.isCanResale()) .royalties(collection.getRoyalties()) .serviceCharge(collection.getServiceCharge()) .type(collection.getType()) .minterId(collection.getMinterId()) .minter(minter.getNickname()) .minterAvatar(minter.getAvatar()) .qty(qty) .price(collection.getPrice()) .gasPrice(gasFee) .totalPrice(collection.getPrice().multiply(BigDecimal.valueOf(qty)).add(gasFee)) .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)) .status(OrderStatus.NOT_PAID) .build(); return orderRepo.save(order); } public void payOrderAlipay(Long id, Model model) { try { Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } 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("total_amount", order.getTotalPrice().stripTrailingZeros().toPlainString()); bizContent.put("disable_pay_channels", "pcredit,creditCard"); if (Arrays.stream(env.getActiveProfiles()).noneMatch(s -> s.equals("prod"))) { // 测试环境设为1分 bizContent.put("total_amount", "0.01"); } bizContent.put("subject", order.getName()); bizContent.put("product_code", "QUICK_WAP_PAY"); JSONObject body = new JSONObject(); body.put("action", "payOrder"); body.put("userId", order.getUserId()); body.put("orderId", order.getId()); bizContent.put("body", body.toJSONString()); AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest(); alipayRequest.setReturnUrl(alipayProperties.getReturnUrl()); alipayRequest.setNotifyUrl(alipayProperties.getNotifyUrl()); alipayRequest.setBizContent(JSON.toJSONString(bizContent)); String form = alipayClient.pageExecute(alipayRequest).getBody(); model.addAttribute("form", form); } catch (BusinessException err) { model.addAttribute("errMsg", err.getError()); } catch (Exception e) { model.addAttribute("errMsg", e.getMessage()); } } public String payOrderWeixinH5(Long id) throws WxPayException, EncoderException { Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); request.setBody(order.getName()); request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId())); request.setTotalFee(order.getTotalPrice().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(WxPayConstants.TradeType.MWEB); request.setSignType("MD5"); JSONObject body = new JSONObject(); body.put("action", "payOrder"); body.put("userId", order.getUserId()); body.put("orderId", order.getId()); request.setAttach(body.toJSONString()); WxPayMwebOrderResult result = wxPayService.createOrder(request); return result.getMwebUrl() + "&redirect_url=" + new URLCodec().encode(wxPayProperties.getReturnUrl()); } public Object payOrderWeixin(Long id, String openId) throws WxPayException { Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); request.setBody(order.getName()); request.setOutTradeNo(String.valueOf(new SnowflakeIdWorker(1, 1).nextId())); request.setTotalFee(order.getTotalPrice().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(WxPayConstants.TradeType.JSAPI); request.setOpenid(openId); request.setSignType("MD5"); JSONObject body = new JSONObject(); body.put("action", "payOrder"); body.put("userId", order.getUserId()); body.put("orderId", order.getId()); request.setAttach(body.toJSONString()); return wxPayService.createOrder(request); } public void notifyAlipay(Long orderId, PayMethod payMethod, String transactionId) { Order order = orderRepo.findById(orderId).orElseThrow(new BusinessException("订单不存在")); if (order.getStatus() == OrderStatus.NOT_PAID) { if (order.getType() == CollectionType.BLIND_BOX) { List items = blindBoxItemRepo.findByBlindBoxId(order.getCollectionId()); Map> randomRange = new HashMap<>(); int c = 0, sum = 0; for (BlindBoxItem item : items) { randomRange.put(item, Range.between(c, c + item.getStock())); c += item.getStock(); sum += item.getStock(); } int retry = 0; BlindBoxItem winItem = null; while (winItem == null) { retry++; int rand = RandomUtils.nextInt(0, sum + 1); for (Map.Entry> entry : randomRange.entrySet()) { BlindBoxItem item = entry.getKey(); Range range = entry.getValue(); if (rand >= range.getMinimum() && rand < range.getMaximum()) { int total = items.stream().filter(i -> !i.isRare()) .mapToInt(BlindBoxItem::getTotal).sum(); int stock = items.stream().filter(i -> !i.isRare()) .mapToInt(BlindBoxItem::getStock).sum(); if (item.isRare()) { double nRate = stock / (double) total; double rRate = (item.getStock() - 1) / (double) item.getTotal(); if (Math.abs(nRate - rRate) < (1 / (double) item.getTotal()) || retry > 1 || rRate == 0) { if (!(nRate > 0.1 && item.getStock() == 1)) { winItem = item; } } } else { double nRate = (stock - 1) / (double) total; double rRate = item.getStock() / (double) item.getTotal(); if (Math.abs(nRate - rRate) < 0.2 || retry > 1 || nRate == 0) { winItem = item; } } } } if (retry > 100 && winItem == null) { throw new BusinessException("盲盒抽卡失败"); } } winItem.setStock(winItem.getStock() - 1); winItem.setSale(winItem.getSale() + 1); order.setStatus(OrderStatus.PROCESSING); order.setPayTime(LocalDateTime.now()); order.setTransactionId(transactionId); order.setPayMethod(payMethod); orderRepo.save(order); assetService.createAsset(order, winItem); } else { order.setStatus(OrderStatus.PROCESSING); order.setPayTime(LocalDateTime.now()); order.setTransactionId(transactionId); order.setPayMethod(payMethod); orderRepo.save(order); assetService.createAsset(order); } } else if (order.getStatus() == OrderStatus.CANCELLED) { } } @EventListener public void onCreateAsset(CreateAssetEvent event) { Order order = event.getOrder(); Asset asset = event.getAsset(); if (event.isSuccess()) { order.setTxHash(asset.getTxHash()); order.setGasUsed(asset.getGasUsed()); order.setBlockNumber(asset.getBlockNumber()); order.setStatus(OrderStatus.FINISH); orderRepo.save(order); } else { log.error("创建asset失败"); } } public void cancel(Long id) { Order order = orderRepo.findById(id).orElseThrow(new BusinessException("订单不存在")); cancel(order); } public void cancel(Order order) { if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("已支付订单无法取消"); } Collection collection = collectionRepo.findById(order.getCollectionId()) .orElseThrow(new BusinessException("藏品不存在")); User minter = userRepo.findById(collection.getMinterId()).orElseThrow(new BusinessException("铸造者不存在")); collection.setSale(collection.getSale() - 1); collection.setStock(collection.getStock() + 1); collectionRepo.save(collection); minter.setSales(minter.getSales() - 1); userRepo.save(minter); order.setStatus(OrderStatus.CANCELLED); order.setCancelTime(LocalDateTime.now()); orderRepo.save(order); } @Scheduled(fixedRate = 60000) public void batchCancel() { List orders = orderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus.NOT_PAID, LocalDateTime.now().minusMinutes(5)); orders.stream().parallel().forEach(this::cancel); } public void refundCancelled(Order order) { } }