licailing 3 лет назад
Родитель
Сommit
9c960d42e2

+ 4 - 0
src/main/java/com/izouma/nineth/domain/MintActivity.java

@@ -61,4 +61,8 @@ public class MintActivity extends BaseEntity{
     @ApiModelProperty("gas费")
     @Column(precision = 10, scale = 2)
     private BigDecimal gasPrice;
+
+    @ApiModelProperty("空投/实物")
+    private boolean airDrop;
+
 }

+ 1 - 0
src/main/java/com/izouma/nineth/domain/MintMaterial.java

@@ -18,4 +18,5 @@ public class MintMaterial {
 
     private String name;
 
+    private Long privilegeId;
 }

+ 11 - 0
src/main/java/com/izouma/nineth/domain/MintOrder.java

@@ -62,9 +62,20 @@ public class MintOrder extends BaseEntity {
     @Enumerated(EnumType.STRING)
     private PayMethod payMethod;
 
+    @ApiModelProperty("交易ID")
+    @Searchable
+    private String transactionId;
+
+    @ApiModelProperty("取消时间")
+    private LocalDateTime cancelTime;
+
+    @ApiModelProperty("支付时间")
     private LocalDateTime payAt;
 
     @ApiModelProperty("是否消耗藏品")
     private boolean consume;
 
+    @ApiModelProperty("空投/实物")
+    private boolean airDrop;
+
 }

+ 1 - 0
src/main/java/com/izouma/nineth/enums/AssetStatus.java

@@ -6,6 +6,7 @@ public enum AssetStatus {
     TRANSFERRED("已转让"),
     GIFTING("转赠中"),
     GIFTED("已转赠"),
+    MINTING("铸造中")
     ;
 
     private final String description;

+ 5 - 0
src/main/java/com/izouma/nineth/repo/MintOrderRepo.java

@@ -1,16 +1,21 @@
 package com.izouma.nineth.repo;
 
 import com.izouma.nineth.domain.MintOrder;
+import com.izouma.nineth.enums.MintOrderStatus;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 
 import javax.transaction.Transactional;
+import java.time.LocalDateTime;
+import java.util.List;
 
 public interface MintOrderRepo extends JpaRepository<MintOrder, Long>, JpaSpecificationExecutor<MintOrder> {
     @Query("update MintOrder t set t.del = true where t.id = ?1")
     @Modifying
     @Transactional
     void softDelete(Long id);
+
+    List<MintOrder> findByStatusAndCreatedAtBeforeAndDelFalse(MintOrderStatus status, LocalDateTime createdAt);
 }

+ 238 - 23
src/main/java/com/izouma/nineth/service/MintOrderService.java

@@ -1,35 +1,66 @@
 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 org.opencv.face.Face;
+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.util.Collections;
-import java.util.List;
-import java.util.Optional;
+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 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<MintOrder> all(PageQuery pageQuery) {
         return mintOrderRepo.findAll(JpaUtils.toSpecification(pageQuery, MintOrder.class), JpaUtils.toPageRequest(pageQuery));
@@ -106,13 +137,12 @@ public class MintOrderService {
                 .filter(asset -> asset.getName()
                         .contains(mintActivity.getCollectionName()) && AssetStatus.NORMAL.equals(asset.getStatus()))
                 .collect(Collectors.toList());
-        if (assets.size() != 3) {
+        if (assets.size() != mintActivity.getNum()) {
             throw new BusinessException("有藏品不符合,请重新选择");
         }
-
+        Map<Long, Long> privilegeIds = new HashMap<>();
         // 铸造特权
         if (!mintActivity.isConsume()) {
-            List<Asset> finalAssets = assets;
             assets.forEach(asset -> {
                 List<Privilege> privileges = asset.getPrivileges()
                         .stream()
@@ -122,31 +152,39 @@ public class MintOrderService {
                     throw new BusinessException("无铸造特权");
                 } else {
                     boolean flag = false;
-//                    Privilege result = null;
                     for (Privilege privilege : privileges) {
                         // 打开多次 或者 可打开一次但未使用
                         if (!privilege.isOnce() || (privilege.isOnce() && !privilege
                                 .isOpened())) {
                             flag = true;
-//                            result = privilege;
+                            privilegeIds.put(asset.getId(), privilege.getId());
                             break;
                         }
                     }
-                    if (flag) {
-//                        result.setOpened(true);
-//                        result.setOpenedBy(user.getId());
-//                        result.setOpenTime(LocalDateTime.now());
-                    } else {
+                    if (!flag) {
                         throw new BusinessException("铸造特权已使用");
                     }
                 }
-                assetRepo.saveAll(finalAssets);
+            });
+            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);
+            });
             // 转让的用户
-            User blackHole = userRepo.findByIdAndDelFalse(1435297L).orElseThrow(new BusinessException("无法铸造"));
-//            assets.forEach(asset -> assetService.transfer(asset, asset.getPrice(), blackHole, "转赠", null));
+            userRepo.findByIdAndDelFalse(1435297L).orElseThrow(new BusinessException("无法铸造"));
         }
 
         // 铸造资产
@@ -155,6 +193,7 @@ public class MintOrderService {
             material.setAssetId(asset.getId());
             material.setCollectionId(asset.getCollectionId());
             material.setName(asset.getName());
+            material.setPrivilegeId(privilegeIds.get(asset.getId()));
             return material;
         }).collect(Collectors.toList());
 
@@ -170,6 +209,7 @@ public class MintOrderService {
                 .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))
@@ -180,4 +220,179 @@ public class MintOrderService {
                 .build());
 
     }
+
+    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.<WxPayMpOrderResult>createOrder(request);
+        }
+        throw new BusinessException("不支持此付款方式");
+    }
+
+    public Object payAdapay(Long id, String payChannel, String openId) throws BaseAdaPayException {
+        List<String> aliChannels = Arrays.asList("alipay", "alipay_qr", "alipay_wap");
+        List<String> 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<String, Object> 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<String, Object> 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<String, Object> 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<MintMaterial> materials = mintOrder.getMaterial();
+        List<Asset> 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<MintOrder> 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<MintMaterial> materials = order.getMaterial();
+
+        Map<Long, Long> privilegeIds = materials.stream()
+                .collect(Collectors.toMap(MintMaterial::getAssetId, MintMaterial::getPrivilegeId));
+
+        List<Asset> assets = assetRepo.findAllById(privilegeIds.keySet());
+
+        assets.forEach(asset -> {
+            if (order.isConsume()) {
+                asset.setStatus(AssetStatus.NORMAL);
+
+            } else {
+                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);
+    }
 }

+ 25 - 0
src/main/java/com/izouma/nineth/web/OrderNotifyController.java

@@ -17,6 +17,7 @@ import com.izouma.nineth.enums.PayMethod;
 import com.izouma.nineth.event.OrderNotifyEvent;
 import com.izouma.nineth.service.AssetService;
 import com.izouma.nineth.service.GiftOrderService;
+import com.izouma.nineth.service.MintOrderService;
 import com.izouma.nineth.service.OrderService;
 import com.izouma.nineth.utils.SnowflakeIdWorker;
 import lombok.AllArgsConstructor;
@@ -47,6 +48,7 @@ public class OrderNotifyController {
     private final SnowflakeIdWorker snowflakeIdWorker;
     private final RocketMQTemplate  rocketMQTemplate;
     private final GeneralProperties generalProperties;
+    private final MintOrderService  mintOrderService;
 
     @PostMapping("/order/alipay")
     @ResponseBody
@@ -208,4 +210,27 @@ public class OrderNotifyController {
             e.printStackTrace();
         }
     }
+
+    @PostMapping("/adapay/mintOrder/{orderId}")
+    @ResponseBody
+    public void adapayMintNotify(@PathVariable Long orderId, HttpServletRequest request) {
+        log.info("adapay mint notify: \n{}", JSON.toJSONString(request.getParameterMap(), PrettyFormat));
+        try {
+            String data = request.getParameter("data");
+            String sign = request.getParameter("sign");
+            String type = request.getParameter("type");
+            if ("payment.succeeded".equals(type)) {
+                boolean checkSign = AdapaySign.verifySign(data, sign, AdapayCore.PUBLIC_KEY);
+                log.info("checkSign {}", checkSign);
+                if (checkSign) {
+                    JSONObject jsonObject = JSON.parseObject(data);
+                    String channel = jsonObject.getString("pay_channel");
+                    String id = jsonObject.getString("id");
+                    mintOrderService.mintNotify(orderId, channel.startsWith("wx") ? PayMethod.WEIXIN : PayMethod.ALIPAY, id);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
 }

+ 50 - 0
src/main/java/com/izouma/nineth/web/OrderPayController.java

@@ -11,6 +11,7 @@ import com.izouma.nineth.exception.BusinessException;
 import com.izouma.nineth.repo.OrderRepo;
 import com.izouma.nineth.service.AssetService;
 import com.izouma.nineth.service.GiftOrderService;
+import com.izouma.nineth.service.MintOrderService;
 import com.izouma.nineth.service.OrderService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -35,6 +36,7 @@ public class OrderPayController {
     private final WxMpService      wxMpService;
     private final GiftOrderService giftOrderService;
     private final OrderRepo        orderRepo;
+    private final MintOrderService mintOrderService;
 
     @RequestMapping(value = "/alipay_h5", method = RequestMethod.GET)
     @ResponseBody
@@ -136,6 +138,54 @@ public class OrderPayController {
         return "PayOrderPC";
     }
 
+    @RequestMapping(value = "/mint/alipay_h5", method = RequestMethod.GET)
+    @ResponseBody
+    public String payMintOrderAlipayH5(Long id, Model model) throws BaseAdaPayException {
+        return (String) mintOrderService.payAdapay(id, "alipay_wap", null);
+    }
+
+    @RequestMapping(value = "/mint/alipay_wx", method = RequestMethod.GET)
+    public String payMintOrderAlipayWx(Long id, Model model) throws BaseAdaPayException {
+        String payUrl = (String) mintOrderService.payAdapay(id, "alipay_wap", null);
+        model.addAttribute("payUrl", payUrl);
+        model.addAttribute("orderId", id);
+        return "AlipayHtml";
+    }
+
+    @RequestMapping(value = "/mint/alipay_qr", method = RequestMethod.GET)
+    @ResponseBody
+    public String payMintOrderAlipayQR(Long id, Model model) throws BaseAdaPayException {
+        return (String) mintOrderService.payAdapay(id, "alipay_qr", null);
+    }
+
+    @RequestMapping(value = "/mint/alipay_app", method = RequestMethod.GET)
+    @ResponseBody
+    public String payMintOrderAlipayApp(Long id, Model model) throws BaseAdaPayException {
+        return (String) mintOrderService.payAdapay(id, "alipay", null);
+    }
+
+    @RequestMapping(value = "/mint/weixin_h5")
+    public String payMintOrderWeixinH5(Long id, Model model, @RequestHeader(value = "User-Agent") String userAgent) throws EncoderException, WxPayException {
+        detectUA(userAgent, model);
+        model.addAttribute("payUrl", mintOrderService.payOrderWeixin(id, WxPayConstants.TradeType.MWEB, null));
+        return "WeixinPayHtml";
+    }
+
+    @RequestMapping(value = "/mint/weixin")
+    @ResponseBody
+    public Object payMintOrderWeixin(@RequestParam Long id, @RequestParam(defaultValue = "wx_lite") String channel, @RequestParam(required = false) String openId) throws BaseAdaPayException {
+        return mintOrderService.payAdapay(id, channel, openId);
+    }
+
+    @RequestMapping(value = "/mint/weixin_pc")
+    public String payMintOrderWeixinPC(@RequestParam Long id, @RequestParam String code, Model model) throws WxPayException, EncoderException, WxErrorException {
+        WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
+        WxMpUser user = wxMpService.oauth2getUserInfo(accessToken, null);
+        WxPayMpOrderResult payParams = (WxPayMpOrderResult) mintOrderService.payOrderWeixin(id, WxPayConstants.TradeType.JSAPI, user.getOpenId());
+        model.addAttribute("payParams", JSON.toJSONString(payParams));
+        return "PayOrderPC";
+    }
+
     public void detectUA(String ua, Model model) {
         boolean weixin = Pattern.matches(".*(micromessenger).*", ua.toLowerCase());
         boolean ios = Pattern.matches(".*(ipad|iphone).*", ua.toLowerCase());