Ver Fonte

支付和分账

Evan há 2 anos atrás
pai
commit
152eeb3e1c

+ 270 - 0
mp-admin/src/main/java/com/qs/mp/web/controller/api/callback/YsPayCallBackController.java

@@ -0,0 +1,270 @@
+package com.qs.mp.web.controller.api.callback;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.eptok.yspay.opensdkjava.common.Constants;
+import com.eptok.yspay.opensdkjava.fund.MercFundApi;
+import com.eptok.yspay.opensdkjava.orderpay.OrderQueryApi;
+import com.eptok.yspay.opensdkjava.pojo.vo.OnlineReqDataVo;
+import com.eptok.yspay.opensdkjava.util.DateUtil;
+import com.eptok.yspay.opensdkjava.util.YsOnlineSignUtils;
+import com.eptok.yspay.opensdkjava.util.YsfSignUtil;
+import com.qs.mp.common.constant.DivisionLogStatusConstants;
+import com.qs.mp.common.constant.YsServerApiConstants;
+import com.qs.mp.common.domain.YsCallLog;
+import com.qs.mp.common.enums.PayOrderStatusEnum;
+import com.qs.mp.common.service.IYsCallLogService;
+import com.qs.mp.common.utils.LogUtil;
+import com.qs.mp.pay.domain.DivisionLog;
+import com.qs.mp.pay.domain.PayOrder;
+import com.qs.mp.pay.service.IDivisionLogService;
+import com.qs.mp.pay.service.IPayOrderService;
+import com.qs.mp.pay.service.IWalletService;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 银盛支付回调控制类
+ *
+ * @author Evan
+ * @date 2023/3/28
+ */
+@RestController
+@RequestMapping("/service/notify")
+public class YsPayCallBackController {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
+
+
+    @Value("${ys-pay.private-key-path}")
+    private String privateKeyPath;
+
+    @Value("${ys-pay.public-key-path}")
+    private String publicKeyPath;
+
+
+    @Value("${ys-pay.partner-id}")
+    private String partnerId;
+
+    @Value("${ys-pay.private-key-pass}")
+    private String privateKeyPass;
+
+    @Value("${ys-pay.div-notify-url}")
+    private String ysDivNotifyUrl;
+
+    @Value("${ys-pay.master-seller-id}")
+    private String masterSellerId;
+
+    @Value("${ys-pay.child-seller-id}")
+    private String childSellerId;
+
+    @Autowired
+    private IPayOrderService payOrderService;
+
+    @Autowired
+    private IWalletService walletService;
+
+    @Autowired
+    private IYsCallLogService ysCallLogService;
+
+    @Autowired
+    private IDivisionLogService divisionLogService;
+
+
+    @RequestMapping(value = "/ysCallback", method = RequestMethod.GET)
+    public void payCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        Map<String, String[]> reqParams = request.getParameterMap();
+
+        Map<String, String> params = new HashMap<>();
+        for (String key : reqParams.keySet()) {
+            String[] values = reqParams.get(key);
+            String valueStr = "";
+            for (int i = 0; i < values.length; i++) {
+                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
+            }
+            params.put(key, valueStr);
+        }
+
+        String sign = params.get("sign");
+        boolean flag = YsOnlineSignUtils.rsaCheckContent(params, sign, Constants.CHARSET_UTF_8, publicKeyPath);
+        if (!flag) {
+            logger.error("ys支付回调-check sign error");
+            responseWrite(response, "fail");
+            return;
+        }
+        String code = params.get("code");
+        String tradeNo = params.get("out_trade_no");
+        String orderNo = params.get("trade_no");
+        String tradeStatus = params.get("trade_status");
+        String completeDate = params.get("notify_time");
+        int totalAmount = new BigDecimal(request.getParameter("total_amount")).multiply(new BigDecimal(100)).intValue();
+        logger.info("data: tradeNo:"+tradeNo +" orderNo:"+orderNo +" totalAmount:"+totalAmount+ "tradeStatus:"+tradeStatus);
+
+        if (StringUtils.isBlank(code) || StringUtils.isBlank(tradeNo) || StringUtils.isBlank(orderNo) || StringUtils.isBlank(tradeStatus)) {
+            logger.error("ys支付回调-参数错误");
+            responseWrite(response, "fail");
+            return;
+        }
+
+        PayOrder order = payOrderService.getOne(new LambdaQueryWrapper<PayOrder>()
+            .eq(PayOrder::getTradeNo, tradeNo));
+
+        if (PayOrderStatusEnum.SUCCESS.getValue().equals(order.getOrderStatus())) {
+            logger.info("支付订单已成功,消息忽略,shopOrderNo:" + orderNo);
+            responseWrite(response,"true");
+        }
+
+
+        if (!"TRADE_SUCCESS".equals(tradeStatus)) {
+            logger.error("ys-非支付成功消息,忽略");
+            responseWrite(response,"fail");
+            return;
+        }
+
+        //回调金额与订单金额一致性校验
+        if(order.getTransactionAmount() != totalAmount){
+            logger.error("回调金额与订单金额不一致");
+            responseWrite(response,"fail");
+            return;
+        }
+
+
+        PayOrder payOrder = payOrderService.getById(order.getOrderId());
+        payOrder.setOrderNo(orderNo);
+        payOrder.setCompleteDate(completeDate);
+//        payOrder.setChannelNo(channelNo);
+        payOrder.setOrderStatus(PayOrderStatusEnum.SUCCESS.getValue());
+
+
+        try {
+            // 更新订单,单个事务处理
+            logger.info("ys支付回调消息更新成功 orderId:" + order.getOrderId());
+            walletService.payOrderStatusHandle(payOrder);
+
+            // 订单支付状态单独保存
+            LambdaUpdateWrapper<PayOrder> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(PayOrder::getOrderStatus, PayOrderStatusEnum.WAIT.getValue());
+            updateWrapper.eq(PayOrder::getOrderId, order.getOrderId());
+            boolean ret = payOrderService.update(payOrder, updateWrapper);
+            if (!ret) {
+                LogUtil.error(logger, "ys支付回调消息更新支付单状态失败 orderId:" + order.getOrderId());
+                responseWrite(response,"fail");
+            }
+        } catch (Exception e){
+            LogUtil.error(logger, e, "ys支付回调消息处理异常 orderId:" + order.getOrderId());
+            responseWrite(response,"fail");
+        }
+
+        // 调用分账登记接口
+        OnlineReqDataVo req = new OnlineReqDataVo();
+        req.setReqUrl(YsServerApiConstants.COMMON_API);
+        req.setNotifyUrl(ysDivNotifyUrl);
+        req.setPartnerId(partnerId);
+        req.setPrivateKeyFilePath(privateKeyPath);
+        req.setPrivateKeyPassword(privateKeyPass);
+        req.setYsPublicKeyFilePath(publicKeyPath);
+        // 分账登记业务参数
+        Map<String, Object> bizContent = new HashMap<>();
+        String divTradeNo = DateUtil.getDateNowYmd() + DateUtil.getRandom(14);
+        bizContent.put("out_trade_no", divTradeNo);
+        bizContent.put("payee_usercode", partnerId);
+        bizContent.put("total_amount", request.getParameter("total_amount"));
+        bizContent.put("is_divistion", "01");
+        bizContent.put("is_again_division", "N");
+        bizContent.put("division_mode", "01");
+        // 分账明细
+        JSONArray divList = new JSONArray();
+        JSONObject divObject = new JSONObject();
+        divObject.put("division_mer_usercode", partnerId);
+        divObject.put("div_ratio", "1");
+        divObject.put("is_chargeFee", "02");
+        divList.add(divObject);
+        //分账明细
+        bizContent.put("div_list", divList);
+        req.setParamData(bizContent);
+        String result = null;
+        DivisionLog divisionLog = new DivisionLog();
+        YsCallLog ysCallLog = new YsCallLog();
+        ysCallLog.setInterfaceId("ysepay.single.division.online.accept");
+        ysCallLog.setReqJson(JSONObject.toJSONString(req));
+        try{
+            logger.info("分账登记调用sdk接口addScanMerc请求入参为:"+ JSONObject.toJSONString(req));
+            result = MercFundApi.divisionOnlineAccept(req);
+            ysCallLog.setResJson(result);
+            logger.info("分账登记调用addScanMerc出参为:"+ result);
+            //根据返回结果处理自己的业务逻辑,result内容详见接口文档
+        }catch (Exception e){
+            logger.error("线上分账登记接口失败:"+ e.getMessage());
+        }
+        // 保存调用日志
+        ysCallLogService.save(ysCallLog);
+
+        // 保存分账登记记录
+        divisionLog.setTradeNo(divTradeNo);
+        divisionLog.setStatus(DivisionLogStatusConstants.INIT);
+        divisionLog.setAmount(totalAmount);
+        divisionLog.setOrderNo(orderNo);
+        divisionLog.setBizId(payOrder.getOrderId());
+        divisionLogService.save(divisionLog);
+        responseWrite(response, "success");
+    }
+
+    /**
+     * 分账回调
+     * @param request
+     * @param response
+     * @throws Exception
+     */
+    @RequestMapping(value = "/ysDivCallback", method = RequestMethod.GET)
+    public void divCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        Map<String, String[]> reqParams = request.getParameterMap();
+
+        Map<String, String> params = new HashMap<>();
+        for (String key : reqParams.keySet()) {
+            String[] values = reqParams.get(key);
+            String valueStr = "";
+            for (int i = 0; i < values.length; i++) {
+                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
+            }
+            params.put(key, valueStr);
+        }
+
+        System.out.println("分账回调的数据 = " + JSONObject.toJSONString(params));
+
+
+        String divTradeNo = params.get("out_trade_no");
+
+        DivisionLog divisionLog = divisionLogService.getOne(
+            new LambdaQueryWrapper<DivisionLog>().eq(DivisionLog::getTradeNo, divTradeNo));
+
+        responseWrite(response, "success");
+
+    }
+
+
+
+    private void responseWrite(HttpServletResponse response,String echostr){
+        try {
+            response.getWriter().write(echostr);
+            response.getWriter().flush();
+            response.getWriter().close();
+        } catch (IOException e) {
+            LogUtil.error(logger, e, "微信公众号设置url回调处理异常");
+        }
+    }
+}

+ 5 - 2
mp-admin/src/main/resources/application-dev.yml

@@ -148,6 +148,9 @@ ys-pay:
     public-key-path: /Users/cup/WORK/data/ysPay/ysPayPub.cer
     # 支付完后跳转
     return-url: https://test-mp-h5.quanshu123.com
-    # 回调地址
-    notify-url: https://www.quanshu123.com
+    # 支付回调地址
+    pay-notify-url: https://www.quanshu123.com/service/notify/ysCallback
+    # 分账回调地址
+    div-notify-url: https://www.quanshu123.com/service/notify/ysDivCallback
+
 

+ 3 - 1
mp-admin/src/main/resources/application-test.yml

@@ -154,5 +154,7 @@ ys-pay:
   # 支付完后跳转
   return-url: https://test-mp-h5.quanshu123.com
   # 回调地址
-  notify-url: https://www.quanshu123.com
+  pay-notify-url: https://www.quanshu123.com/service/notify/ysCallback
+  # 分账回调地址
+  div-notify-url: https://www.quanshu123.com/service/notify/ysDivCallback
 

+ 3 - 1
mp-admin/src/test/java/com/qs/mp/task/MyTest.java

@@ -123,10 +123,12 @@ public class MyTest {
     @Value("${ys-pay.private-key-pass}")
     private String privateKeyPass;
 
+    @Value("${ys-pay.private-key-path}")
+    private String privateKeyPath;
 
     @Test
     void test18() {
-        walletService.h5Pay(null,"023902109123",100,"测试测试测试");
+        walletService.h5Pay(null,"0239021091223",100,"测试测试测试");
     }
 
     @Test

+ 17 - 0
mp-common/src/main/java/com/qs/mp/common/constant/DivisionLogStatusConstants.java

@@ -0,0 +1,17 @@
+package com.qs.mp.common.constant;
+
+/**
+ * 分账状态常量
+ * @author Evan
+ * @date 2023/3/29
+ */
+public class DivisionLogStatusConstants {
+
+    /** 初始化 */
+    public static int INIT = 0;
+    /** 成功 */
+    public static int SUCCESS = 1;
+    /** 失败 */
+    public static int FAIL = 2;
+
+}

+ 5 - 0
mp-common/src/main/java/com/qs/mp/common/constant/YsPayMethodConstants.java

@@ -11,4 +11,9 @@ public class YsPayMethodConstants {
     public static String H5_PAY_METHOD = "ysepay.online.wap.directpay.createbyuser";
 
 
+    /** 分账登记方法 */
+    public static String DIVISION_SUBMIT_METHOD = "ysepay.single.division.online.accept";
+
+    public static String DIVISION_QUERY_METHOD = "ysepay.single.division.online.query";
+
 }

+ 2 - 0
mp-common/src/main/java/com/qs/mp/common/constant/YsServerApiConstants.java

@@ -11,4 +11,6 @@ public class YsServerApiConstants {
 
     public static String SEARCH_API = "https://search.ysepay.com/gateway.do";
 
+    public static String COMMON_API = "https://commonapi.ysepay.com/gateway.do";
+
 }

+ 45 - 0
mp-common/src/main/java/com/qs/mp/common/enums/YsCallBizTypeEnum.java

@@ -0,0 +1,45 @@
+package com.qs.mp.common.enums;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONType;
+import com.baomidou.mybatisplus.annotation.IEnum;
+import com.qs.mp.common.json.EnumValueDeserializer;
+
+/**
+ * 盈盛调用业务类型枚举
+ * @author Evan
+ * @date 2023/3/30
+ */
+@JSONType(deserializer = EnumValueDeserializer.class)
+public enum YsCallBizTypeEnum implements IEnum<Integer> {
+
+    ORDER_PAY(1,"订单支付"),
+
+    DIVISION(2,"分账");
+
+
+    private final int value;
+    private final String desc;
+
+    YsCallBizTypeEnum(final int value, final String desc) {
+        this.value = value;
+        this.desc = desc;
+    }
+
+    @Override
+    public Integer getValue() {
+        return value;
+    }
+
+    /**
+     * 重写toString,单个转化成json
+     * @return
+     */
+    @Override
+    public String toString() {
+        JSONObject object = new JSONObject();
+        object.put("value",value);
+        object.put("desc", desc);
+        return object.toString();
+    }
+}

+ 11 - 3
mp-service/src/main/java/com/qs/mp/common/domain/YsCallLog.java

@@ -1,7 +1,10 @@
 package com.qs.mp.common.domain;
 
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.qs.mp.common.enums.YsCallBizTypeEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import java.util.Date;
@@ -34,9 +37,14 @@ public class YsCallLog implements Serializable {
     /**
      * 请求id
      */
-    @ApiModelProperty("请求id")
-    @TableField("req_id")
-    private String reqId;
+    @ApiModelProperty("关联id")
+    @TableField("biz_id")
+    private String bizId;
+
+    @ApiModelProperty("关联业务")
+    @TableField("biz_Type")
+    @JSONField(serialzeFeatures = SerializerFeature.WriteEnumUsingToString)
+    private YsCallBizTypeEnum bizType;
 
     /**
      * 请求接口

+ 2 - 2
mp-service/src/main/java/com/qs/mp/pay/domain/PayOrder.java

@@ -55,14 +55,14 @@ public class PayOrder implements Serializable {
   private String orderTime;
 
   /**
-   * 支付来源 1:支付宝  2:微信支付 5:云闪付
+   * 支付来源 1:支付宝  2:微信支付 5:云闪付,3银盛
    * 增加直连来源(2022.7) 10:支付宝直连
    */
   @TableField("pay_resource")
   private String payResource;
 
   /**
-   * 1004:支付宝CTB;1008:微信公众号(需要绑定支付目录)1014: 支付宝服务窗1016:云闪付CTB1017:微信小程序1019:支付宝小程序(请用1014编码)
+   * 1004:支付宝CTB;1008:微信公众号(需要绑定支付目录)1014: 支付宝服务窗1016:云闪付CTB1017:微信小程序1019:支付宝小程序(请用1014编码),1020:银盛H5
    *
    */
   @TableField("trans_type_code")

+ 42 - 5
mp-service/src/main/java/com/qs/mp/pay/service/impl/WalletServiceImpl.java

@@ -18,11 +18,13 @@ import com.eptok.yspay.opensdkjava.util.YsOnlineSignUtils;
 import com.qs.mp.channel.service.IChannelOrderService;
 import com.qs.mp.common.constant.YsPayMethodConstants;
 import com.qs.mp.common.constant.YsServerApiConstants;
+import com.qs.mp.common.domain.YsCallLog;
 import com.qs.mp.common.enums.AppSourceEnum;
 import com.qs.mp.common.enums.AsyncTaskTypeEnum;
 import com.qs.mp.common.enums.BizTypeEnum;
 import com.qs.mp.common.enums.MqTopicType;
 import com.qs.mp.common.enums.PayOrderStatusEnum;
+import com.qs.mp.common.enums.YsCallBizTypeEnum;
 import com.qs.mp.common.exception.ServiceException;
 import com.qs.mp.common.pulsar.PulsarClientService;
 import com.qs.mp.common.service.IYsCallLogService;
@@ -36,6 +38,7 @@ import com.qs.mp.pay.domain.PayOrder;
 import com.qs.mp.pay.service.IPayOrderService;
 import com.qs.mp.pay.service.IWalletService;
 import com.qs.mp.system.service.id.BizIdGenerator;
+import com.qs.mp.user.domain.UserTicketOrder;
 import com.qs.mp.user.service.IUserDeliverOrderService;
 import com.qs.mp.user.service.IUserTicketOrderService;
 import java.math.BigDecimal;
@@ -161,20 +164,25 @@ public class WalletServiceImpl implements IWalletService {
 
   @Value("${ys-pay.return-url}")
   private String ysReturnUrl;
-  @Value("${ys-pay.notify-url}")
-  private String ysNotifyUrl;
+  @Value("${ys-pay.pay-notify-url}")
+  private String ysPayNotifyUrl;
 
   @Override
   public JSONObject h5Pay(BizTypeEnum bizType, String bizId, int money, String orderRemark) {
     Map<String, String> params = new HashMap<>();
     String orderId = String.valueOf(bizIdGenerator.newId());
     String tradeNo = DateUtil.getDateNowYmd() + DateUtil.getRandom(14);
+    // 返回页面拼接
+    if (BizTypeEnum.TICKET_ORDER.equals(bizType)) {
+      UserTicketOrder userTicketOrder = userTicketOrderService.getById(bizId);
+      ysReturnUrl = ysReturnUrl + "?boxId=" + userTicketOrder.getBoxId() + "&orderId=" + bizId + "&istry=0";
+    }
     params.put("method", YsPayMethodConstants.H5_PAY_METHOD);
     params.put("partner_id", partnerId);
     params.put("timestamp", DateUtil.getDateNow());
     params.put("charset", Constants.CHARSET_UTF_8);
     params.put("sign_type", "RSA");
-    params.put("notify_url", ysNotifyUrl);
+    params.put("notify_url", ysPayNotifyUrl);
     params.put("return_url", ysReturnUrl);
     params.put("seller_id", partnerId);
     params.put("version","3.0");
@@ -191,10 +199,39 @@ public class WalletServiceImpl implements IWalletService {
       logger.error("签名失败,bizId=" + bizId, e);
       throw new ServiceException("支付发起签名失败");
     }
-    // map转为jsonobject
 
+    String result = OkHttpUtil.post("https://openapi.ysepay.com/gateway.do", params);
+
+    // 保存调用记录
+    YsCallLog ysCallLog = new YsCallLog();
+    ysCallLog.setBizId(orderId);
+    ysCallLog.setBizType(YsCallBizTypeEnum.ORDER_PAY);
+    ysCallLog.setInterfaceId(YsPayMethodConstants.H5_PAY_METHOD);
+    ysCallLog.setReqJson(JSONObject.toJSONString(params));
+    ysCallLog.setResJson(result);
+    ysCallLogService.save(ysCallLog);
 
-    JSONObject data = JSONObject.parseObject(JSONObject.toJSONString(params));
+    //保存订单记录
+    PayOrder payOrder = new PayOrder();
+    payOrder.setOrderId(orderId);
+    payOrder.setBizType(bizType);
+    payOrder.setBizId(bizId);
+    payOrder.setTradeNo(tradeNo);
+    payOrder.setOrderTime(DateUtils.getTime());
+    payOrder.setPayResource("3"); // 支付宝直连
+    payOrder.setTransTypeCode("1020"); // 支付宝小程序
+    payOrder.setTransactionAmount(money);
+    payOrder.setOrderRemark(orderRemark);
+    payOrder.setOrderName(orderRemark);
+    payOrder.setOrderStatus(PayOrderStatusEnum.WAIT.getValue());
+    boolean ret = payOrderService.save(payOrder);
+    if (!ret) {
+      LogUtil.error(logger, "支付订单数据保存失败.");
+      throw new ServiceException("支付订单保存失败");
+    }
+
+    JSONObject data = new JSONObject();
+    data.put("payUrl", result);
     return data;
   }