package com.izouma.nineth.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; 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.WxPayRefundRequest; 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.AlipayProperties; import com.izouma.nineth.config.GeneralProperties; 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.*; import com.izouma.nineth.event.CreateAssetEvent; import com.izouma.nineth.event.TransferAssetEvent; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.*; import com.izouma.nineth.security.Authority; 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.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.context.event.EventListener; import org.springframework.core.env.Environment; import org.springframework.data.domain.Page; import org.springframework.data.redis.core.RedisTemplate; 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.math.RoundingMode; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; 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; private AssetRepo assetRepo; private UserCouponRepo userCouponRepo; private CollectionService collectionService; private RedisTemplate redisTemplate; private CommissionRecordRepo commissionRecordRepo; private AdapayProperties adapayProperties; private GeneralProperties generalProperties; 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, Long userCouponId, Long invitor) { 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("铸造者不存在")); UserCoupon coupon = null; if (userCouponId != null) { coupon = userCouponRepo.findById(userCouponId).orElseThrow(new BusinessException("兑换券不存在")); if (coupon.isUsed()) { throw new BusinessException("该兑换券已使用"); } if (coupon.isLimited() && !coupon.getCollectionIds().contains(collectionId)) { throw 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("地址信息不存在")); } collectionRepo.increaseStock(collectionId, -qty); collectionRepo.increaseSale(collectionId, qty); BigDecimal gasFee = sysConfigService.getBigDecimal("gas_fee"); Order order = Order.builder() .userId(userId) .collectionId(collectionId) .name(collection.getName()) .pic(collection.getPic()) .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) .assetId(collection.getAssetId()) .couponId(userCouponId) .invitor(invitor) .build(); if (coupon != null) { coupon.setUsed(true); coupon.setUseTime(LocalDateTime.now()); if (coupon.isNeedGas()) { order.setTotalPrice(order.getGasPrice()); } else { order.setTotalPrice(BigDecimal.ZERO); } } if (collection.getSource() == CollectionSource.TRANSFER) { Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("资产不存在")); asset.setStatus(AssetStatus.TRADING); assetRepo.save(asset); collection.setOnShelf(false); collectionRepo.save(collection); } order = orderRepo.save(order); if (order.getTotalPrice().equals(BigDecimal.ZERO)) { notifyOrder(order.getId(), PayMethod.WEIXIN, null); } return 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 Object payOrderWeixin(Long id, String tradeType, String openId) 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(tradeType); 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()); 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("不支持此渠道"); } Order order = orderRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("订单不存在")); Collection collection = collectionRepo.findById(order.getCollectionId()) .orElseThrow(new BusinessException("藏品不存在")); User invitor = null; if (order.getInvitor() != null) { invitor = userRepo.findById(order.getInvitor()).orElse(null); } if (order.getStatus() != OrderStatus.NOT_PAID) { throw new BusinessException("订单状态错误"); } Map paymentParams = new HashMap<>(); paymentParams.put("order_no", String.valueOf(new SnowflakeIdWorker(0, 0).nextId())); paymentParams.put("pay_amt", order.getTotalPrice().setScale(2, RoundingMode.HALF_UP).toPlainString()); paymentParams.put("app_id", adapayProperties.getAppId()); paymentParams.put("pay_channel", payChannel); paymentParams.put("goods_title", collection.getName()); paymentParams.put("goods_desc", collection.getName()); paymentParams.put("time_expire", DateTimeFormatter.ofPattern("yyyyMMddHHmmss") .format(LocalDateTime.now().plusMinutes(3))); paymentParams.put("notify_url", adapayProperties.getNotifyUrl() + "/order/" + order.getId()); List> divMembers = new ArrayList<>(); BigDecimal totalAmount = order.getTotalPrice().subtract(order.getGasPrice()); BigDecimal restAmount = order.getTotalPrice().multiply(BigDecimal.valueOf(1)); if (collection.getSource().equals(CollectionSource.TRANSFER)) { Asset asset = assetRepo.findById(collection.getAssetId()).orElseThrow(new BusinessException("无记录")); User owner = userRepo.findById(asset.getUserId()).orElseThrow(new BusinessException("拥有者用户不存在")); if (collection.getServiceCharge() + collection.getRoyalties() > 0) { restAmount = divMoney(totalAmount, restAmount, divMembers, "0", collection.getServiceCharge() + collection.getRoyalties(), true); } restAmount = divMoney(restAmount, divMembers, owner.getMemberId(), restAmount, false); } else { if (invitor != null && invitor.getShareRatio() != null && invitor.getShareRatio().compareTo(BigDecimal.ZERO) > 0) { restAmount = divMoney(totalAmount, restAmount, divMembers, invitor.getMemberId(), invitor.getShareRatio().intValue(), false); } restAmount = divMoney(restAmount, divMembers, "0", restAmount, true); } if (restAmount.compareTo(BigDecimal.ZERO) != 0) { log.error("分账出错 {}", JSON.toJSONString(divMembers, SerializerFeature.PrettyFormat)); throw new BusinessException("分账出错"); } if (divMembers.size() > 1) { paymentParams.put("div_members", divMembers); } 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"); } } public static BigDecimal divMoney(BigDecimal totalAmount, BigDecimal restAmount, List> divMembers, String memberId, int ratio, boolean feeFlag) { if (ratio == -1 || (ratio > 0 && ratio < 100)) { BigDecimal divAmount = ratio == -1 ? restAmount : totalAmount.multiply(BigDecimal.valueOf(ratio)) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); Map divMem = new HashMap<>(); divMem.put("member_id", memberId); divMem.put("amount", divAmount.toPlainString()); divMem.put("fee_flag", feeFlag ? "Y" : "N"); divMembers.add(divMem); return restAmount.subtract(divAmount); } else { throw new BusinessException("分账比例错误"); } } public static BigDecimal divMoney(BigDecimal restAmount, List> divMembers, String memberId, BigDecimal divAmount, boolean feeFlag) { if (divAmount.compareTo(BigDecimal.ZERO) > 0) { Map divMem = new HashMap<>(); divMem.put("member_id", memberId); divMem.put("amount", divAmount.toPlainString()); divMem.put("fee_flag", feeFlag ? "Y" : "N"); divMembers.add(divMem); } 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 { orderRepo.save(order); assetService.createAsset(collection, user, order.getId(), order.getPrice(), "出售", collectionService.getNextNumber(order.getCollectionId())); } addSales(collection.getMinterId()); } commission(order); } else if (order.getStatus() == OrderStatus.CANCELLED) { } } @EventListener public void onCreateAsset(CreateAssetEvent event) { Asset asset = event.getAsset(); Order order = orderRepo.findById(asset.getOrderId()).orElseThrow(new BusinessException("订单不存在")); 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失败"); } } @EventListener public void onTransferAsset(TransferAssetEvent event) { Asset asset = event.getAsset(); Order order = orderRepo.findById(asset.getOrderId()).orElseThrow(new BusinessException("订单不存在")); 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("铸造者不存在")); if (collection.getSource() == CollectionSource.TRANSFER) { Asset asset = assetRepo.findById(collection.getAssetId()).orElse(null); if (asset != null) { asset.setStatus(AssetStatus.NORMAL); assetRepo.save(asset); } collection.setOnShelf(true); } collectionRepo.increaseSale(collection.getId(), -order.getQty()); collectionRepo.increaseStock(collection.getId(), order.getQty()); order.setStatus(OrderStatus.CANCELLED); order.setCancelTime(LocalDateTime.now()); orderRepo.save(order); if (order.getCouponId() != null) { userCouponRepo.findById(order.getCouponId()).ifPresent(coupon -> { coupon.setUsed(false); coupon.setUseTime(null); userCouponRepo.save(coupon); }); } } @Scheduled(fixedRate = 30000) public void batchCancel() { List orders = orderRepo.findByStatusAndCreatedAtBeforeAndDelFalse(OrderStatus.NOT_PAID, LocalDateTime.now().minusSeconds(210)); orders.forEach(o -> { try { cancel(o); } catch (Exception ignored) { } }); } public void refundCancelled(Order order) { } public synchronized void addSales(Long userId) { if (userId != null) { userRepo.findById(userId).ifPresent(user -> { user.setSales(user.getSales() + 1); userRepo.save(user); }); } } public void setNumber() { for (Collection collection : collectionRepo.findAll()) { if (collection.getSource() != CollectionSource.OFFICIAL) continue; collection.setCurrentNumber(0); collectionRepo.save(collection); for (Asset asset : assetRepo.findByCollectionId(collection.getId())) { if (asset.getStatus() == AssetStatus.GIFTED || asset.getStatus() == AssetStatus.TRANSFERRED) { } else { asset.setNumber(collectionService.getNextNumber(collection.getId())); assetRepo.save(asset); } } } } public void setNumberRecursive(Asset asset) { } @Scheduled(fixedRate = 120000) public void setSales() { List minters = userRepo.findByAuthoritiesContains(Authority.get(AuthorityName.ROLE_MINTER)); for (User minter : minters) { userRepo.setSales(minter.getId(), (int) orderRepo.countSales(minter.getId())); } } public void commission(Order order) { if (order.getInvitor() != null) { userRepo.findById(order.getInvitor()).ifPresent(user -> { BigDecimal shareRatio = user.getShareRatio(); if (shareRatio != null && shareRatio.compareTo(BigDecimal.ZERO) > 0) { BigDecimal totalPrice = order.getTotalPrice().subtract(order.getGasPrice()); commissionRecordRepo.save(CommissionRecord.builder() .orderId(order.getId()) .totalPrice(totalPrice) .nickname(user.getNickname()) .userId(user.getId()) .shareRatio(user.getShareRatio()) .phone(user.getPhone()) .shareAmount(totalPrice.multiply(shareRatio) .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)) .build()); } }); } } public void refund(Long id) throws WxPayException { Order order = orderRepo.findById(id).orElseThrow(new BusinessException("无记录")); if (order.getStatus() != OrderStatus.FINISH) { throw new BusinessException("订单未付款"); } WxPayRefundRequest request = new WxPayRefundRequest(); 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())); wxPayService.refund(request); } }