Administrator
2024-03-12 3f17034c768ba4fc330e01f014b4f880e6a1569c
2024-03-12 朱宝民 增加已挂失,未补卡接口
16个文件已修改
13个文件已添加
1762 ■■■■■ 已修改文件
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeClientCardMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVcRechargeMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVcRefundItemMapper.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVcRefundMapper.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/pojoSe/SeVcRefund.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/pojoSe/SeVcRefundItem.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/voSe/VoOrders.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeClientCardMapper.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVcRechargeMapper.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVcRefundItemMapper.xml 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVcRefundMapper.xml 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/clientCard/ClientCardCtrl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/clientCard/ClientCardSv.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/config/RestTemplateConfig.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/result/SellResultCode.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/VirtualCardCtrl.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/VirtualCardSv.java 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/dto/DtoAudit.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/dto/DtoRefund.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/LastOperateENUM.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/RefundItemStateENUM.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/RefundStateENUM.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wallet/WalletSv.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PayInfo.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PaymentCtrl.java 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/Orders.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/Refund.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/ToRefund.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeClientCardMapper.java
@@ -107,4 +107,16 @@
     * @return
     */
    List<VoCards> getCards(Map<?, ?> params);
    /**
     * 获取已挂失,未补卡的记录数量,应用程序使用
     * @return
     */
    Long getUnreplacedRecordCount();
    /**
     * 获取已挂失,未补卡的记录,应用程序使用
     * @return
     */
    List<VoCards> getUnreplaced(Map<?, ?> params);
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVcRechargeMapper.java
@@ -2,7 +2,11 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dy.pipIrrGlobal.pojoSe.SeVcRecharge;
import com.dy.pipIrrGlobal.voSe.VoOrders;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * @author ZhuBaoMin
@@ -31,4 +35,18 @@
     * @return
     */
    SeVcRecharge getVCRechargeByorderNumber(String orderNumber);
    /**
     * 根据虚拟卡号获取订单列表
     * @param virtualId
     * @return
     */
    List<VoOrders> getOrders(@Param("virtualId") Long virtualId);
    /**
     * 根据订单号获取充值金额
     * @param orderNumber
     * @return
     */
    Integer getRechargeAmountByOrderNumber(@Param("orderNumber") String orderNumber);
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVcRefundItemMapper.java
New file
@@ -0,0 +1,49 @@
package com.dy.pipIrrGlobal.daoSe;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * @author ZhuBaoMin
 * @date 2024-03-10 21:05
 * @LastEditTime 2024-03-10 21:05
 * @Description
 */
@Mapper
public interface SeVcRefundItemMapper extends BaseMapper<SeVcRefundItem> {
    int deleteByPrimaryKey(Long id);
    int insert(SeVcRefundItem record);
    int insertSelective(SeVcRefundItem record);
    SeVcRefundItem selectByPrimaryKey(Long id);
    int updateByPrimaryKeySelective(SeVcRefundItem record);
    int updateByPrimaryKey(SeVcRefundItem record);
    /**
     * 根据订单号获取最后一个退单号
     * @param orderNumber
     * @return
     */
    String getLastRefundNumber(@Param("orderNumber") String orderNumber);
    /**
     * 根据退款通知接口返回的退款单号反查退款ID,查询该退款ID下未退款记录数量
     * @param refundNumber
     * @return
     */
    Integer getNoRefundedCount(String refundNumber);
    /**
     * 根据退款单号获取退款ID,退款通知后更新退款表所需
     * @param refundNumber
     * @return
     */
    Long getRefundIdByRefundNumber(String refundNumber);
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVcRefundMapper.java
New file
@@ -0,0 +1,37 @@
package com.dy.pipIrrGlobal.daoSe;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dy.pipIrrGlobal.pojoSe.SeVcRefund;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * @author ZhuBaoMin
 * @date 2024-03-08 11:06
 * @LastEditTime 2024-03-08 11:06
 * @Description
 */
@Mapper
public interface SeVcRefundMapper extends BaseMapper<SeVcRefund> {
    int deleteByPrimaryKey(Long id);
    int insert(SeVcRefund record);
    int insertSelective(SeVcRefund record);
    SeVcRefund selectByPrimaryKey(Long id);
    int updateByPrimaryKeySelective(SeVcRefund record);
    int updateByPrimaryKey(SeVcRefund record);
    /**
     * 根据订单号获取其各笔退款金额
     * @param orderNumber
     * @return
     */
    List<Integer> getRefundAmount(@Param("orderNumber") String orderNumber);
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/pojoSe/SeVcRefund.java
New file
@@ -0,0 +1,116 @@
package com.dy.pipIrrGlobal.pojoSe;
import com.alibaba.fastjson2.annotation.JSONField;
import com.alibaba.fastjson2.writer.ObjectWriterImplToString;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.dy.common.po.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import java.util.Date;
/**
 * @author ZhuBaoMin
 * @date 2024-03-08 11:06
 * @LastEditTime 2024-03-08 11:06
 * @Description
 */
@TableName(value="se_vc_refund", autoResultMap = true)
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "虚拟卡退款实体")
public class SeVcRefund implements BaseEntity {
    public static final long serialVersionUID = 202403081109001L;
    /**
    * 主键
    */
    @JSONField(serializeUsing= ObjectWriterImplToString.class)
    @TableId(type = IdType.INPUT)
    @Schema(description = "实体id", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Long id;
    /**
    * 虚拟卡ID
    */
    @Schema(description = "虚拟卡ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "虚拟卡ID不能为空")
    private Long vcId;
    /**
    * 农户ID
    */
    @Schema(description = "农户ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "农户ID不能为空")
    private Long clientId;
    /**
    * 虚拟卡余额,退款通知后更新余额
    */
    @Schema(description = "虚拟卡余额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Double money;
    /**
    * 退款金额
    */
    @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "退款金额不能为空")
    private Integer refundAmount;
    /**
    * 退款后余额
    */
    @Schema(description = "退款后余额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Double afterRefund;
    /**
    * 申请时间
    */
    @Schema(description = "申请时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "申请时间不能为空")
    private Date applicationTime;
    /**
    * 审核人
    */
    @Schema(description = "审核人", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Long auditor;
    /**
    * 审核时间
    */
    @Schema(description = "审核时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Date auditTime;
    /**
    * 审核备注
    */
    @Schema(description = "审核备注", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private String remarks;
    /**
    * 退款单号;12位农户号+17位时间戳+2位数量
    */
    @Schema(description = "退款单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private String refundNumber;
    /**
    * 退款完成时间
    */
    @Schema(description = "退款完成时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Date refundTime;
    /**
    * 退款状态;1-待审核,2-待退款,3-已退款
    */
    @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Byte refundStatus;
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/pojoSe/SeVcRefundItem.java
New file
@@ -0,0 +1,88 @@
package com.dy.pipIrrGlobal.pojoSe;
import com.alibaba.fastjson2.annotation.JSONField;
import com.alibaba.fastjson2.writer.ObjectWriterImplToString;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.dy.common.po.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import java.util.Date;
/**
 * @author ZhuBaoMin
 * @date 2024-03-10 21:05
 * @LastEditTime 2024-03-10 21:05
 * @Description
 */
@TableName(value="se_vc_refund_item", autoResultMap = true)
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "虚拟卡退款分项实体")
public class SeVcRefundItem implements BaseEntity {
    public static final long serialVersionUID = 202403102108001L;
    /**
     * 主键
     */
    @JSONField(serializeUsing= ObjectWriterImplToString.class)
    @TableId(type = IdType.INPUT)
    @Schema(description = "实体id", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Long id;
    /**
     * 退款ID
     */
    @Schema(description = "退款ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "退款ID不能为空")
    private Long refundId;
    /**
     * 订单号;12位农户好+17位时间戳
     */
    @Schema(description = "订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "订单号不能为空")
    private String orderNumber;
    /**
     * 退款单号;12位农户号+17位时间戳+2位数量
     */
    @Schema(description = "退款单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "退款单号不能为空")
    private String refundNumber;
    /**
     * 退款金额;与订单对应的退款金额
     */
    @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "退款金额不能为空")
    private Integer refundAmount;
    /**
     * 退款单创建时间
     */
    @Schema(description = "退款单创建时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "退款单创建时间不能为空")
    private Date createTime;
    /**
     * 退款完成时间
     */
    @Schema(description = "退款完成时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Date refundTime;
    /**
     * 退款状态;1-未退款,2-已退款
     */
    @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Byte refundStatus;
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/voSe/VoOrders.java
New file
@@ -0,0 +1,44 @@
package com.dy.pipIrrGlobal.voSe;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
import java.util.Date;
/**
 * @author ZhuBaoMin
 * @date 2024-03-08 9:36
 * @LastEditTime 2024-03-08 9:36
 * @Description
 */
@Data
@Schema(title = "订单视图对象")
public class VoOrders {
    public static final long serialVersionUID = 202403072157001L;
    /**
     * 订单号
     */
    @Schema(description = "订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "订单号不能为空")
    private String orderNumber;
    /**
     * 充值金额
     */
    @Schema(description = "充值金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "充值金额不能为空")
    @Positive(message = "充值金额必须为大于0的整数")
    private Integer rechargeAmount;
    /**
     * 充值完成时间
     */
    @Schema(description = "充值完成时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "充值完成时间不能为空")
    private Date rechargeTime;
}
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeClientCardMapper.xml
@@ -387,4 +387,40 @@
      </if>
    </trim>
  </select>
  <!--获取已挂失未补卡的记录数量-->
  <select id="getUnreplacedRecordCount" resultType="java.lang.Long">
    SELECT
        COUNT(*) AS recordCount
    FROM se_client_card card
        INNER JOIN se_client cli ON card.clientId = cli.id
    WHERE card.state = 3 AND NOT EXISTS (SELECT * FROM se_client_card card2 WHERE card.clientId = card2.clientId AND card2.state = 1)
  </select>
  <!--获取已挂失未补卡的记录-->
  <select id="getUnreplaced" resultType="com.dy.pipIrrGlobal.voSe.VoCards">
    SELECT
        cli.clientNum,
        cli.name AS clientName,
        card.cardNum,
        cli.phone,
        cli.idCard,
        card.state AS cardState,
        (CASE
            WHEN card.state = 1 THEN '正常'
            WHEN card.state = 2 THEN '已注销'
            WHEN card.state = 3 THEN '已挂失'
        End) AS stateName,
        '农户卡' AS cardType,
        FORMAT(card.money,2) AS money
    FROM se_client_card card
        INNER JOIN se_client cli ON card.clientId = cli.id
    WHERE card.state = 3 AND NOT EXISTS (SELECT * FROM se_client_card card2 WHERE card.clientId = card2.clientId AND card2.state = 1)
    ORDER BY card.id
    <trim prefix="limit " >
      <if test="start != null and count != null">
        #{start,javaType=Integer,jdbcType=INTEGER}, #{count,javaType=Integer,jdbcType=INTEGER}
      </if>
    </trim>
  </select>
</mapper>
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVcRechargeMapper.xml
@@ -168,4 +168,32 @@
    WHERE order_number = #{orderNumber}
    LIMIT 0,1
  </select>
  <!--根据虚拟卡号获取订单列表-->
  <select id="getOrders" resultType="com.dy.pipIrrGlobal.voSe.VoOrders">
    SELECT
      order_number AS orderNumber,
      recharge_amount AS rechargeAmount,
      recharge_time AS rechargeTime
    FROM se_vc_recharge
    <where>
      AND order_state = 2
      <if test = "virtualId != null and virtualId > 0">
        AND vc_id = ${virtualId}
      </if>
    </where>
    ORDER BY order_number
  </select>
<!--根据订单号获取充值金额-->
  <select id="getRechargeAmountByOrderNumber" resultType="java.lang.Integer">
    SELECT
        recharge_amount AS rechargeAmount
    FROM se_vc_recharge
    <where>
      <if test = "orderNumber != null and orderNumber !=''">
        AND order_number = #{orderNumber}
      </if>
    </where>
  </select>
</mapper>
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVcRefundItemMapper.xml
New file
@@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dy.pipIrrGlobal.daoSe.SeVcRefundItemMapper">
  <resultMap id="BaseResultMap" type="com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem">
    <!--@mbg.generated-->
    <!--@Table se_vc_refund_item-->
    <id column="id" jdbcType="BIGINT" property="id" />
    <result column="refund_id" jdbcType="BIGINT" property="refundId" />
    <result column="order_number" jdbcType="VARCHAR" property="orderNumber" />
    <result column="refund_number" jdbcType="VARCHAR" property="refundNumber" />
    <result column="refund_amount" jdbcType="INTEGER" property="refundAmount" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
    <result column="refund_time" jdbcType="TIMESTAMP" property="refundTime" />
    <result column="refund_status" jdbcType="TINYINT" property="refundStatus" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated-->
    id, refund_id, order_number, refund_number, refund_amount, create_time, refund_time,
    refund_status
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    <!--@mbg.generated-->
    select
    <include refid="Base_Column_List" />
    from se_vc_refund_item
    where id = #{id,jdbcType=BIGINT}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
    <!--@mbg.generated-->
    delete from se_vc_refund_item
    where id = #{id,jdbcType=BIGINT}
  </delete>
  <insert id="insert" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem">
    <!--@mbg.generated-->
    insert into se_vc_refund_item (id, refund_id, order_number,
      refund_number, refund_amount, create_time,
      refund_time, refund_status)
    values (#{id,jdbcType=BIGINT}, #{refundId,jdbcType=BIGINT}, #{orderNumber,jdbcType=VARCHAR},
      #{refundNumber,jdbcType=VARCHAR}, #{refundAmount,jdbcType=INTEGER}, #{createTime,jdbcType=TIMESTAMP},
      #{refundTime,jdbcType=TIMESTAMP}, #{refundStatus,jdbcType=TINYINT})
  </insert>
  <insert id="insertSelective" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem">
    <!--@mbg.generated-->
    insert into se_vc_refund_item
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="refundId != null">
        refund_id,
      </if>
      <if test="orderNumber != null">
        order_number,
      </if>
      <if test="refundNumber != null">
        refund_number,
      </if>
      <if test="refundAmount != null">
        refund_amount,
      </if>
      <if test="createTime != null">
        create_time,
      </if>
      <if test="refundTime != null">
        refund_time,
      </if>
      <if test="refundStatus != null">
        refund_status,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=BIGINT},
      </if>
      <if test="refundId != null">
        #{refundId,jdbcType=BIGINT},
      </if>
      <if test="orderNumber != null">
        #{orderNumber,jdbcType=VARCHAR},
      </if>
      <if test="refundNumber != null">
        #{refundNumber,jdbcType=VARCHAR},
      </if>
      <if test="refundAmount != null">
        #{refundAmount,jdbcType=INTEGER},
      </if>
      <if test="createTime != null">
        #{createTime,jdbcType=TIMESTAMP},
      </if>
      <if test="refundTime != null">
        #{refundTime,jdbcType=TIMESTAMP},
      </if>
      <if test="refundStatus != null">
        #{refundStatus,jdbcType=TINYINT},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem">
    <!--@mbg.generated-->
    update se_vc_refund_item
    <set>
      <if test="refundId != null">
        refund_id = #{refundId,jdbcType=BIGINT},
      </if>
      <if test="orderNumber != null">
        order_number = #{orderNumber,jdbcType=VARCHAR},
      </if>
      <if test="refundNumber != null">
        refund_number = #{refundNumber,jdbcType=VARCHAR},
      </if>
      <if test="refundAmount != null">
        refund_amount = #{refundAmount,jdbcType=INTEGER},
      </if>
      <if test="createTime != null">
        create_time = #{createTime,jdbcType=TIMESTAMP},
      </if>
      <if test="refundTime != null">
        refund_time = #{refundTime,jdbcType=TIMESTAMP},
      </if>
      <if test="refundStatus != null">
        refund_status = #{refundStatus,jdbcType=TINYINT},
      </if>
    </set>
    where id = #{id,jdbcType=BIGINT}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem">
    <!--@mbg.generated-->
    update se_vc_refund_item
    set refund_id = #{refundId,jdbcType=BIGINT},
      order_number = #{orderNumber,jdbcType=VARCHAR},
      refund_number = #{refundNumber,jdbcType=VARCHAR},
      refund_amount = #{refundAmount,jdbcType=INTEGER},
      create_time = #{createTime,jdbcType=TIMESTAMP},
      refund_time = #{refundTime,jdbcType=TIMESTAMP},
      refund_status = #{refundStatus,jdbcType=TINYINT}
    where id = #{id,jdbcType=BIGINT}
  </update>
  <!--根据订单号获取最后一个退单号-->
  <select id="getLastRefundNumber" resultType="java.lang.String">
    SELECT
        refund_number AS refundNumber
    FROM se_vc_refund_item
    <where>
      <if test = "orderNumber != null">
        AND order_number = #{orderNumber}
      </if>
    </where>
    ORDER BY refund_number DESC
    LIMIT 0,1
  </select>
  <!--根据退款通知接口返回的退款单号反查退款ID,查询该退款ID下未退款记录数量-->
  <select id="getNoRefundedCount" resultType="java.lang.Integer">
    SELECT
      COUNT(*) AS recordCount
    FROM se_vc_refund_item
    WHERE refund_id = (SELECT refund_id FROM se_vc_refund_item WHERE refund_number = #{refundNumber}) AND refund_status = 1
  </select>
  <!--根据退款单号获取退款ID,退款通知后更新退款表所需-->
  <select id="getRefundIdByRefundNumber" resultType="java.lang.Long">
    SELECT
        refund_id AS refundId
    FROM se_vc_refund_item
    WHERE refund_number = #{refundNumber}
  </select>
</mapper>
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVcRefundMapper.xml
New file
@@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dy.pipIrrGlobal.daoSe.SeVcRefundMapper">
  <resultMap id="BaseResultMap" type="com.dy.pipIrrGlobal.pojoSe.SeVcRefund">
    <!--@mbg.generated-->
    <!--@Table se_vc_refund-->
    <id column="id" jdbcType="BIGINT" property="id" />
    <result column="vc_id" jdbcType="BIGINT" property="vcId" />
    <result column="client_id" jdbcType="BIGINT" property="clientId" />
    <result column="money" jdbcType="FLOAT" property="money" />
    <result column="refund_amount" jdbcType="FLOAT" property="refundAmount" />
    <result column="after_refund" jdbcType="FLOAT" property="afterRefund" />
    <result column="application_time" jdbcType="TIMESTAMP" property="applicationTime" />
    <result column="auditor" jdbcType="BIGINT" property="auditor" />
    <result column="audit_time" jdbcType="TIMESTAMP" property="auditTime" />
    <result column="remarks" jdbcType="VARCHAR" property="remarks" />
    <result column="refund_number" jdbcType="VARCHAR" property="refundNumber" />
    <result column="refund_time" jdbcType="TIMESTAMP" property="refundTime" />
    <result column="refund_status" jdbcType="TINYINT" property="refundStatus" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated-->
    id, vc_id, client_id, money, refund_amount, after_refund, application_time, auditor,
    audit_time, remarks, refund_number, refund_time, refund_status
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    <!--@mbg.generated-->
    select
    <include refid="Base_Column_List" />
    from se_vc_refund
    where id = #{id,jdbcType=BIGINT}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
    <!--@mbg.generated-->
    delete from se_vc_refund
    where id = #{id,jdbcType=BIGINT}
  </delete>
  <insert id="insert" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRefund">
    <!--@mbg.generated-->
    insert into se_vc_refund (id, vc_id, client_id,
      money, refund_amount, after_refund,
      application_time, auditor, audit_time,
      remarks, refund_number, refund_time,
      refund_status)
    values (#{id,jdbcType=BIGINT}, #{vcId,jdbcType=BIGINT}, #{clientId,jdbcType=BIGINT},
      #{money,jdbcType=FLOAT}, #{refundAmount,jdbcType=FLOAT}, #{afterRefund,jdbcType=FLOAT},
      #{applicationTime,jdbcType=TIMESTAMP}, #{auditor,jdbcType=BIGINT}, #{auditTime,jdbcType=TIMESTAMP},
      #{remarks,jdbcType=VARCHAR}, #{refundNumber,jdbcType=VARCHAR}, #{refundTime,jdbcType=TIMESTAMP},
      #{refundStatus,jdbcType=TINYINT})
  </insert>
  <insert id="insertSelective" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRefund">
    <!--@mbg.generated-->
    insert into se_vc_refund
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="vcId != null">
        vc_id,
      </if>
      <if test="clientId != null">
        client_id,
      </if>
      <if test="money != null">
        money,
      </if>
      <if test="refundAmount != null">
        refund_amount,
      </if>
      <if test="afterRefund != null">
        after_refund,
      </if>
      <if test="applicationTime != null">
        application_time,
      </if>
      <if test="auditor != null">
        auditor,
      </if>
      <if test="auditTime != null">
        audit_time,
      </if>
      <if test="remarks != null">
        remarks,
      </if>
      <if test="refundNumber != null">
        refund_number,
      </if>
      <if test="refundTime != null">
        refund_time,
      </if>
      <if test="refundStatus != null">
        refund_status,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=BIGINT},
      </if>
      <if test="vcId != null">
        #{vcId,jdbcType=BIGINT},
      </if>
      <if test="clientId != null">
        #{clientId,jdbcType=BIGINT},
      </if>
      <if test="money != null">
        #{money,jdbcType=FLOAT},
      </if>
      <if test="refundAmount != null">
        #{refundAmount,jdbcType=FLOAT},
      </if>
      <if test="afterRefund != null">
        #{afterRefund,jdbcType=FLOAT},
      </if>
      <if test="applicationTime != null">
        #{applicationTime,jdbcType=TIMESTAMP},
      </if>
      <if test="auditor != null">
        #{auditor,jdbcType=BIGINT},
      </if>
      <if test="auditTime != null">
        #{auditTime,jdbcType=TIMESTAMP},
      </if>
      <if test="remarks != null">
        #{remarks,jdbcType=VARCHAR},
      </if>
      <if test="refundNumber != null">
        #{refundNumber,jdbcType=VARCHAR},
      </if>
      <if test="refundTime != null">
        #{refundTime,jdbcType=TIMESTAMP},
      </if>
      <if test="refundStatus != null">
        #{refundStatus,jdbcType=TINYINT},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRefund">
    <!--@mbg.generated-->
    update se_vc_refund
    <set>
      <if test="vcId != null">
        vc_id = #{vcId,jdbcType=BIGINT},
      </if>
      <if test="clientId != null">
        client_id = #{clientId,jdbcType=BIGINT},
      </if>
      <if test="money != null">
        money = #{money,jdbcType=FLOAT},
      </if>
      <if test="refundAmount != null">
        refund_amount = #{refundAmount,jdbcType=FLOAT},
      </if>
      <if test="afterRefund != null">
        after_refund = #{afterRefund,jdbcType=FLOAT},
      </if>
      <if test="applicationTime != null">
        application_time = #{applicationTime,jdbcType=TIMESTAMP},
      </if>
      <if test="auditor != null">
        auditor = #{auditor,jdbcType=BIGINT},
      </if>
      <if test="auditTime != null">
        audit_time = #{auditTime,jdbcType=TIMESTAMP},
      </if>
      <if test="remarks != null">
        remarks = #{remarks,jdbcType=VARCHAR},
      </if>
      <if test="refundNumber != null">
        refund_number = #{refundNumber,jdbcType=VARCHAR},
      </if>
      <if test="refundTime != null">
        refund_time = #{refundTime,jdbcType=TIMESTAMP},
      </if>
      <if test="refundStatus != null">
        refund_status = #{refundStatus,jdbcType=TINYINT},
      </if>
    </set>
    where id = #{id,jdbcType=BIGINT}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRefund">
    <!--@mbg.generated-->
    update se_vc_refund
    set vc_id = #{vcId,jdbcType=BIGINT},
      client_id = #{clientId,jdbcType=BIGINT},
      money = #{money,jdbcType=FLOAT},
      refund_amount = #{refundAmount,jdbcType=FLOAT},
      after_refund = #{afterRefund,jdbcType=FLOAT},
      application_time = #{applicationTime,jdbcType=TIMESTAMP},
      auditor = #{auditor,jdbcType=BIGINT},
      audit_time = #{auditTime,jdbcType=TIMESTAMP},
      remarks = #{remarks,jdbcType=VARCHAR},
      refund_number = #{refundNumber,jdbcType=VARCHAR},
      refund_time = #{refundTime,jdbcType=TIMESTAMP},
      refund_status = #{refundStatus,jdbcType=TINYINT}
    where id = #{id,jdbcType=BIGINT}
  </update>
  <!--根据订单号获取其各笔退款金额-->
  <select id="getRefundAmount" resultType="java.lang.Integer">
    SELECT
        refund_amount AS refundAmount
    FROM se_vc_refund
    <where>
      AND refund_status = 3
      <if test = "orderNumber != null">
        AND refund_amount LIKE CONCAT(#{orderNumber},'%')
      </if>
    </where>
  </select>
</mapper>
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/clientCard/ClientCardCtrl.java
@@ -146,4 +146,34 @@
            return BaseResponseUtils.buildException(e.getMessage()) ;
        }
    }
    /**
     * 获得一页已挂失,未补卡的水卡记录
     * @param vo
     * @return
     */
    @Operation(summary = "获得一页已挂失,未补卡的水卡记录", description = "返回一页已挂失,未补卡的水卡数据")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = ResultCodeMsg.RsCode.SUCCESS_CODE,
                    description = "返回一页农户数据(BaseResponse.content:QueryResultVo[{}])",
                    content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
                            schema = @Schema(implementation = BaClient.class))}
            )
    })
    @GetMapping(path = "getUnreplaced")
    @SsoAop()
    public BaseResponse<QueryResultVo<List<VoCards>>> getUnreplaced(QoCards vo){
        try {
            QueryResultVo<List<VoCards>> res = clientCardSv.getUnreplaced(vo);
            if(res.itemTotal != null && res.itemTotal > 0) {
                return BaseResponseUtils.buildSuccess(res);
            }else {
                return BaseResponseUtils.buildFail(SellResultCode.THE_CARD_NOT_EXIST.getMessage());
            }
        } catch (Exception e) {
            log.error("查询农户异常", e);
            return BaseResponseUtils.buildException(e.getMessage()) ;
        }
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/clientCard/ClientCardSv.java
@@ -130,4 +130,23 @@
        return rsVo ;
    }
    /**
     * 获取已挂失,未补卡的记录,应用程序使用
     * @param queryVo
     * @return
     */
    public QueryResultVo<List<VoCards>> getUnreplaced(QoCards queryVo){
        Map<String, Object> params = (Map<String, Object>) PojoUtils.generalize(queryVo) ;
        Long itemTotal = seClientCardMapper.getUnreplacedRecordCount();
        QueryResultVo<List<VoCards>> rsVo = new QueryResultVo<>() ;
        rsVo.pageSize = queryVo.pageSize ;
        rsVo.pageCurr = queryVo.pageCurr ;
        rsVo.calculateAndSet(itemTotal, params);
        rsVo.obj = seClientCardMapper.getUnreplaced(params);
        return rsVo ;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/config/RestTemplateConfig.java
@@ -58,6 +58,7 @@
    //@Bean(name = "wechatRestTemplate")
    @Bean()
    public RestTemplate restTemplate() throws Exception {
        //RestTemplate restTemplate = new RestTemplate(restTemplateWechatCertConfig.wechatHttpRequestFactory());
        RestTemplate restTemplate = new RestTemplate(restTemplateWechatCertConfig.wechatHttpRequestFactory());
        // 添加拦截器
        //List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/result/SellResultCode.java
@@ -104,6 +104,7 @@
    CONSUME_FAIL(90008, "消费失败"),
    REFUND_AMOUNT_CANNOT_GREATER_THAN_MONEY(90009, "申请退款失败,退款金额不能大于余额"),
    APPLICATION_REFUND_FAIL(900010, "申请退款失败"),
    NOT_SUFFICIENT_FUNDS(900010, "退款失败,余额不足"),
    NO_TO_AUDIT_REFUND(900011, "该电子钱包没有待审核的退款申请"),
    AUDIT_REFUND_FAIL(900012, "审核退款申请失败"),
    No_WALLER_RECHARGES(900013, "没有符合条件的充值数据"),
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java
@@ -1,9 +1,19 @@
package com.dy.pipIrrSell.util;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.dy.common.webUtil.BaseResponse;
import com.dy.common.webUtil.BaseResponseUtils;
import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard;
import com.dy.pipIrrGlobal.voSe.VoOrders;
import com.dy.pipIrrSell.result.SellResultCode;
import com.dy.pipIrrSell.virtualCard.VirtualCardSv;
import com.dy.pipIrrSell.wechatpay.PayInfo;
import com.dy.pipIrrSell.wechatpay.dto.Refund;
import com.dy.pipIrrSell.wechatpay.dto.RefundRequest;
import com.dy.pipIrrSell.wechatpay.dto.RefundResponse;
import com.dy.pipIrrSell.wechatpay.dto.ToRefund;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@@ -19,10 +29,7 @@
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.*;
/**
 * @author ZhuBaoMin
@@ -33,6 +40,7 @@
@Component
@RequiredArgsConstructor
public class PayHelper {
    private final VirtualCardSv virtualCardSv;
    private final RestTemplateUtil restTemplateUtil;
    private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -40,6 +48,10 @@
    private String checkSessionUrl = PayInfo.checkSessionUrl;
    private String tokenUrl = PayInfo.tokenUrl;
    private String resetUserSessionKeyUrl = PayInfo.resetUserSessionKeyUrl;
    private String notifyUrl = PayInfo.notifyUrl;
    private String schema = PayInfo.schema;
    private String privateCertFileName = PayInfo.privateCertFileName;
    private String refundUrl = PayInfo.refundUrl;
    // 平台证书公钥
    public Map<String, Certificate> CERTIFICATE_MAP = new HashMap();
@@ -341,4 +353,154 @@
        boolean valid = signature.verify(Base64.getDecoder().decode(wechatpaySignature));
        return valid;
    }
    /**
     * 获取待退款对象列表
     *      待退款对象包含订单号和可退款金额
     *      订单对象包含订单号、充值金额、充值完成时间
     *      1. 根据虚拟卡号到虚拟卡表中取出该卡余额
     *      2. 根据虚拟卡号到充值表取出订单对象列表
     * @param virtualId
     * @param refundAmount
     * @return
     */
    public List<ToRefund> getToRefunds(Long virtualId, Integer refundAmount) {
        ToRefund toRefund = new ToRefund();
        List<ToRefund> list = new ArrayList<>();
        Double money = 0d;
        // 根据虚拟卡号获取当前虚拟卡余额
        SeVirtualCard seVirtualCard = virtualCardSv.selectVirtuCardById(virtualId);
        if(seVirtualCard != null) {
            money = seVirtualCard.getMoney();
        }
        // 要退金额大于该卡余额,返回空列表
        if(refundAmount > money) {
            return list;
        }
        // 根据虚拟卡号获取订单列表(仅限充值成功的)
        List<VoOrders> list_Orders = virtualCardSv.selectOrders(virtualId);
        // 遍历订单列表,获取
        if(list_Orders != null && list_Orders.size() > 0) {
            JSONArray array_Orders = (JSONArray) JSON.toJSON(list_Orders);
            for(int i = 0; i < array_Orders.size(); i++) {
                JSONObject job_order = array_Orders.getJSONObject(i);
                String orderNumber = job_order.getString("orderNumber");
                Integer rechargeAmount = job_order.getInteger("rechargeAmount");
                Date rechargeTime = job_order.getDate("rechargeTime");
                // 计算充值至今时间差(分钟)
                Long timestamp_Recharge = rechargeTime.getTime() / 1000;
                Long timestamp_Current = System.currentTimeMillis() / 1000;
                Long timeDiff_Minute = (timestamp_Current - timestamp_Recharge)/60;
                // 获取该订单已退款笔数
                Integer refundCount = 0;
                List<Integer> list_RefundAmount = virtualCardSv.selectRefundAmount(orderNumber);
                if(list_RefundAmount != null && list_RefundAmount.size() > 0) {
                    refundCount = list_RefundAmount.size();
                }
                // 充值至今未超过一年且该订单退款总次数未超过50次
                if(timeDiff_Minute/(365*24*60) >= 1 && (refundCount + 1) > 50)
                    return list;
                /**
                 * 1. 如果要退金额小于当前订单的充值金额,要退金额即为应退金额并返回
                 * 2. 如果要推金额大于当前订单充值金额,当前订单充值金额即为应退金额
                 *      a. 生成应退款对象
                 *      b. 计算新的余额
                 *      c. 金蒜新的要退款金额
                 *      d. 如果要退金额大于0,遍历下一个订单
                 */
                if(refundAmount <= rechargeAmount) {
                    toRefund = new ToRefund();
                    toRefund.setOrderNumber(orderNumber);
                    toRefund.setRefundAmount(refundAmount);
                    list.add(toRefund);
                    // 计算新的余额和新的要退金额
                    money = money - refundAmount;
                    refundAmount = refundAmount - refundAmount;
                    return list;
                }else {
                    toRefund = new ToRefund();
                    toRefund.setOrderNumber(orderNumber);
                    toRefund.setRefundAmount(rechargeAmount);
                    list.add(toRefund);
                    // 计算新的余额和新的要退金额
                    money = money - rechargeAmount;
                    refundAmount = refundAmount - rechargeAmount;
                    if(refundAmount > 0) {
                        continue;
                    }else {
                        return list;
                    }
                }
            }
        }
        return list;
    }
    /**
     * 退款申请,调用微信支付退款申请接口
     * @param po 退款请求对象,包含订单号、退款单号、退款金额
     * @return
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws IOException
     * @throws SignatureException
     * @throws InvalidKeyException
     */
    public BaseResponse<Boolean> refunds(Refund po) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException {
        String tradeNo = po.getTradeNo();
        String refundNo = po.getRefundNo();
        Integer refund = po.getRefund();
        // 生成body
        Integer total = virtualCardSv.getRechargeAmountByOrderNumber(tradeNo);
        RefundRequest.Amount amount = new RefundRequest.Amount();
        amount.setRefund(refund);
        amount.setTotal(total);
        amount.setCurrency("CNY");
        RefundRequest refundRequest = new RefundRequest();
        refundRequest.setOut_trade_no(tradeNo);
        refundRequest.setOut_refund_no(refundNo);
        refundRequest.setNotify_url(notifyUrl);
        refundRequest.setAmount(amount);
        // 生成header
        String nonceStr = generateRandomString();
        Long timestamp = System.currentTimeMillis() / 1000;
        String method = "POST";
        String httpUrl = "/v3/refund/domestic/refunds";
        String body = JSONObject.toJSONString(refundRequest);
        String header = schema + " " + getToken(method, httpUrl, body, nonceStr, timestamp, privateCertFileName);
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", header);
        headers.put("Accept", "application/json");
        headers.put("Content-Type", "application/json");
        JSONObject job_refundResponse = restTemplateUtil.post(PayInfo.refundUrl, body, headers);
        RefundResponse refundResponse = JSON.parseObject(job_refundResponse.toJSONString(), RefundResponse.class);
        String status = refundResponse.getStatus();
        if(status != null && status.equals("SUCCESS")) {
            // 退款申请已受理
            return BaseResponseUtils.buildSuccess(true) ;
        } else if(status != null && status.equals("PROCESSING")) {
            // 退款处理中
            return BaseResponseUtils.buildFail(SellResultCode.PROCESSING.getMessage());
        } else {
            // 退款异常
            return BaseResponseUtils.buildError(SellResultCode.ABNORMAL.getMessage());
        }
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/VirtualCardCtrl.java
@@ -1,29 +1,49 @@
package com.dy.pipIrrSell.virtualCard;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.dy.common.aop.SsoAop;
import com.dy.common.webUtil.BaseResponse;
import com.dy.common.webUtil.BaseResponseUtils;
import com.dy.common.webUtil.ResultCodeMsg;
import com.dy.pipIrrGlobal.pojoSe.SeVcRefund;
import com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem;
import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard;
import com.dy.pipIrrSell.result.SellResultCode;
import com.dy.pipIrrSell.util.PayHelper;
import com.dy.pipIrrSell.virtualCard.dto.DtoAudit;
import com.dy.pipIrrSell.virtualCard.dto.DtoRefund;
import com.dy.pipIrrSell.virtualCard.enums.LastOperateENUM;
import com.dy.pipIrrSell.virtualCard.enums.RefundItemStateENUM;
import com.dy.pipIrrSell.virtualCard.enums.RefundStateENUM;
import com.dy.pipIrrSell.wechatpay.dto.Refund;
import com.dy.pipIrrSell.wechatpay.dto.ToRefund;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
 * @author ZhuBaoMin
@@ -40,7 +60,13 @@
@Validated
public class VirtualCardCtrl {
    private final VirtualCardSv virtualCardSv;
    private final PayHelper payHelper;
    /**
     * 虚拟卡账号注册
     * @param clientId
     * @return
     */
    @Operation(summary = "注册虚拟卡", description = "注册虚拟卡")
    @ApiResponses(value = {
            @ApiResponse(
@@ -71,4 +97,132 @@
    }
    /**
     * 用户申请退款
     * @param po
     * @param bindingResult
     * @return
     */
    @Operation(summary = "虚拟卡申请退款", description = "虚拟卡申请退款")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = ResultCodeMsg.RsCode.SUCCESS_CODE,
                    description = "操作结果:true:成功,false:失败(BaseResponse.content)",
                    content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
                            schema = @Schema(implementation = Boolean.class))}
            )
    })
    @PostMapping(path = "add_refund", consumes = MediaType.APPLICATION_JSON_VALUE)
    @Transactional(rollbackFor = Exception.class)
    @SsoAop()
    public BaseResponse<Boolean> addRefund(@RequestBody @Valid DtoRefund po, BindingResult bindingResult){
        if(bindingResult != null && bindingResult.hasErrors()){
            return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
        }
        Long virtualId = po.getVirtualId();
        Integer refundAmount = po.getRefundAmount();
        // 根据虚拟卡ID获取虚拟卡对象
        SeVirtualCard seVirtualCard = virtualCardSv.selectVirtuCardById(virtualId);
        if(seVirtualCard == null) {
            return BaseResponseUtils.buildFail(SellResultCode.VIRTUAL_CARD_NOT_EXIST.getMessage());
        }
        Long clientId = seVirtualCard.getClientId();
        Double money = seVirtualCard.getMoney();
        // 验证退款金额是否大于余额
        if(refundAmount > money) {
            return BaseResponseUtils.buildFail(SellResultCode.REFUND_AMOUNT_CANNOT_GREATER_THAN_MONEY.getMessage());
        }
        // 计算消费后余额
        Double afterRefund = money - refundAmount;
        SeVcRefund seVcRefund = new SeVcRefund();
        seVcRefund.setVcId(virtualId);
        seVcRefund.setClientId(clientId);
        seVcRefund.setMoney(money);
        seVcRefund.setRefundAmount(refundAmount);
        seVcRefund.setAfterRefund(afterRefund);
        seVcRefund.setApplicationTime(new Date());
        seVcRefund.setRefundStatus(RefundStateENUM.TO_AUDIT.getCode());
        Long rec = virtualCardSv.addRefund(seVcRefund);
        if(rec == 0) {
            return BaseResponseUtils.buildFail(SellResultCode.APPLICATION_REFUND_FAIL.getMessage());
        }
        return BaseResponseUtils.buildSuccess(true) ;
    }
    /**
     * 审核退款申请
     * @param po
     * @param bindingResult
     * @return
     */
    @Operation(summary = "审核退款申请", description = "审核退款申请")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = ResultCodeMsg.RsCode.SUCCESS_CODE,
                    description = "操作结果:true:成功,false:失败(BaseResponse.content)",
                    content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
                            schema = @Schema(implementation = Boolean.class))}
            )
    })
    @PostMapping(path = "audit_refund", consumes = MediaType.APPLICATION_JSON_VALUE)
    @Transactional(rollbackFor = Exception.class)
    @SsoAop()
    public BaseResponse<Boolean> auditRefund(@RequestBody @Valid DtoAudit po, BindingResult bindingResult) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException {
        if(bindingResult != null && bindingResult.hasErrors()){
            return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
        }
        // 根据退款ID获取退款对象,并更新审核人、审核时间、审核备注、退款状态字段
        SeVcRefund seVcRefund = virtualCardSv.selectRefundByRefundId(po.getRefundId());
        Long virtualId = seVcRefund.getVcId();
        Integer refundAmount = seVcRefund.getRefundAmount();
        seVcRefund.setAuditor(po.getAuditor());
        seVcRefund.setAuditTime(new Date());
        seVcRefund.setRemarks(po.getRemarks());
        seVcRefund.setRefundStatus(RefundStateENUM.TO_REFUND.getCode());
        Integer rec = virtualCardSv.updateRefund(seVcRefund);
        if(rec == 0) {
            return BaseResponseUtils.buildFail(SellResultCode.AUDIT_REFUND_FAIL.getMessage());
        }
        // 完成审核后获取待退款订单列表
        List<ToRefund> list_ToRefund = payHelper.getToRefunds(virtualId, refundAmount);
        if(list_ToRefund == null || list_ToRefund.size() <=0)
            return BaseResponseUtils.buildFail(SellResultCode.NOT_SUFFICIENT_FUNDS.getMessage());
        //遍历待退款列表
        JSONArray array_ToRefund = (JSONArray) JSON.toJSON(list_ToRefund);
        for(int i = 0; i < array_ToRefund.size(); i++) {
            JSONObject job_ToRefund = array_ToRefund.getJSONObject(i);
            String orderNumber_ToRefund = job_ToRefund.getString("orderNumber");
            Integer refundAmount_ToRefund = job_ToRefund.getInteger("refundAmount");
            // 生成退款分项记录
            SeVcRefundItem seVcRefundItem = new SeVcRefundItem();
            seVcRefundItem.setRefundId(po.getRefundId());
            seVcRefundItem.setOrderNumber(orderNumber_ToRefund);
            String refundNumber = virtualCardSv.generateRefundNumber(orderNumber_ToRefund);
            seVcRefundItem.setRefundNumber(refundNumber);
            seVcRefundItem.setRefundAmount(refundAmount_ToRefund);
            seVcRefundItem.setCreateTime(new Date());
            seVcRefundItem.setRefundStatus(RefundItemStateENUM.NO_REFUND.getCode());
            Long refundItemId = virtualCardSv.addRefundItem(seVcRefundItem);
            // 调用微信退款申请接口
            Refund refund = new Refund();
            refund.setTradeNo(orderNumber_ToRefund);
            refund.setRefundNo(refundNumber);
            refund.setRefund(refundAmount_ToRefund);
            BaseResponse rep = payHelper.refunds(refund);
        }
        return BaseResponseUtils.buildSuccess(true) ;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/VirtualCardSv.java
@@ -3,9 +3,14 @@
import com.dy.common.webUtil.BaseResponse;
import com.dy.common.webUtil.BaseResponseUtils;
import com.dy.pipIrrGlobal.daoSe.SeVcRechargeMapper;
import com.dy.pipIrrGlobal.daoSe.SeVcRefundItemMapper;
import com.dy.pipIrrGlobal.daoSe.SeVcRefundMapper;
import com.dy.pipIrrGlobal.daoSe.SeVirtualCardMapper;
import com.dy.pipIrrGlobal.pojoSe.SeVcRecharge;
import com.dy.pipIrrGlobal.pojoSe.SeVcRefund;
import com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem;
import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard;
import com.dy.pipIrrGlobal.voSe.VoOrders;
import com.dy.pipIrrSell.result.SellResultCode;
import com.dy.pipIrrSell.virtualCard.dto.DtoVirtualCard;
import com.dy.pipIrrSell.virtualCard.enums.LastOperateENUM;
@@ -16,6 +21,7 @@
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
/**
 * @author ZhuBaoMin
@@ -32,6 +38,12 @@
    @Autowired
    private SeVcRechargeMapper seVcRechargeMapper;
    @Autowired
    private SeVcRefundMapper seVcRefundMapper;
    @Autowired
    private SeVcRefundItemMapper seVcRefundItemMapper;
    /**
     * 注册虚拟卡
@@ -61,6 +73,7 @@
    public SeVirtualCard selectVirtuCardById(Long virtualId) {
        return seVirtualCardMapper.selectByPrimaryKey(virtualId);
    }
    /**
     * 添加虚拟卡充值记录
     * JSAPI下单后生成部分充值记录
@@ -139,7 +152,7 @@
    }
    /**
     * 修改虚拟卡充值记录
     * 修改虚拟卡充值记录(废弃)
     * 微信小程序支付通知后修改:余额、充值后余额、充值完成时间
     * @param po
     * @return
@@ -147,4 +160,115 @@
    public Integer updateVCRecharge(SeVcRecharge po) {
        return seVcRechargeMapper.updateByPrimaryKeySelective(po);
    }
    /**
     * 根据虚拟卡号获取订单列表
     * @param virtualId
     * @return
     */
    public List<VoOrders> selectOrders(Long virtualId) {
        List<VoOrders> rsVo = seVcRechargeMapper.getOrders(virtualId);
        return rsVo ;
    }
    /**
     * 根据退款ID获取退款对象
     * @param refundId
     * @return
     */
    public SeVcRefund selectRefundByRefundId(Long refundId) {
        return seVcRefundMapper.selectByPrimaryKey(refundId);
    }
    /**
     * 添加退款申请
     * @param po
     * @return
     */
    public Long addRefund(SeVcRefund po) {
        seVcRefundMapper.insert(po);
        return po.getId();
    }
    /**
     * 修改退款记录
     * @param po
     * @return
     */
    public Integer updateRefund(SeVcRefund po) {
        return seVcRefundMapper.updateByPrimaryKeySelective(po);
    }
    /**
     * 根据订单号获取其各笔退款金额
     * @param orderNumber
     * @return
     */
    public List<Integer> selectRefundAmount(String orderNumber) {
        List<Integer> rsVo = seVcRefundMapper.getRefundAmount(orderNumber);
        return rsVo;
    }
    /**
     * 添加退款分项
     * @param po
     * @return
     */
    public Long addRefundItem(SeVcRefundItem po) {
        seVcRefundItemMapper.insert(po);
        return po.getRefundId();
    }
    /**
     * 编辑退款分项
     * @param po
     * @return
     */
    public Integer updateRefundItem(SeVcRefundItem po) {
        return seVcRefundItemMapper.updateByPrimaryKeySelective(po);
    }
    /**
     * 根据订单号生成退款单号
     * @param orderNumber
     * @return
     */
    public String generateRefundNumber(String orderNumber) {
        String refundNumber = seVcRefundItemMapper.getLastRefundNumber(orderNumber);
        if(refundNumber == null) {
            refundNumber = orderNumber + "01";
            return refundNumber;
        }
        String a = String.format("%02d", (Integer.parseInt(refundNumber.substring(29,30).trim()) + 1));
        return  a;
    }
    /**
     * 根据订单号获取充值金额,调用退款申请接口使用
     * @param orderNumber
     * @return
     */
    public Integer getRechargeAmountByOrderNumber(String orderNumber) {
        return seVcRechargeMapper.getRechargeAmountByOrderNumber(orderNumber);
    }
    /**
     * 根据退款通知接口返回的退款单号反查退款ID,查询该退款ID下未退款记录数量
     * @param refundNumber
     * @return
     */
    public Integer getNoRefundedCount(String refundNumber) {
        return seVcRefundItemMapper.getNoRefundedCount(refundNumber);
    }
    /**
     * 根据退款单号获取退款ID,退款通知后更新退款表所需
     * @param refundNumber
     * @return
     */
    public Long getRefundIdByRefundNumber(String refundNumber) {
        return seVcRefundItemMapper.getRefundIdByRefundNumber(refundNumber);
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/dto/DtoAudit.java
New file
@@ -0,0 +1,40 @@
package com.dy.pipIrrSell.virtualCard.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
/**
 * @author ZhuBaoMin
 * @date 2024-03-08 21:09
 * @LastEditTime 2024-03-08 21:09
 * @Description
 */
@Data
@Schema(name = "退款审核传入对象")
public class DtoAudit {
    public static final long serialVersionUID = 202403082109001L;
    /**
     * 虚拟卡退款ID
     */
    @Schema(description = "虚拟卡退款ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "虚拟卡退款ID不能为空")
    private Long refundId;
    /**
     * 审核人ID
     */
    @Schema(description = "审核人ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "审核ID不能为空")
    private Long auditor;
    /**
     * 审核备注
     */
    @Schema(description = "审核备注", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @Length(max = 200)
    private String remarks;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/dto/DtoRefund.java
New file
@@ -0,0 +1,34 @@
package com.dy.pipIrrSell.virtualCard.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
/**
 * @author ZhuBaoMin
 * @date 2024-03-08 20:17
 * @LastEditTime 2024-03-08 20:17
 * @Description
 */
@Data
@Schema(name = "用户退款传入对象")
public class DtoRefund {
    public static final long serialVersionUID = 202403080858001L;
    /**
     * 虚拟卡编号
     */
    @Schema(description = "虚拟卡编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "虚拟卡编号不能为空")
    private Long virtualId;
    /**
     * 退款金额
     */
    @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "退款金额不能为空")
    @Positive(message = "充值金额必须为大于0的整数")
    private Integer refundAmount;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/LastOperateENUM.java
@@ -17,7 +17,8 @@
    RECHARGE((byte)2, "充值"),
    CONSUME((byte)3, "消费"),
    APPLY_REFUND((byte)4, "申请退款"),
    AUDIT_REFUND((byte)5, "退款审核");
    AUDIT_REFUND((byte)5, "退款审核"),
    REFUND((byte)6, "退款");
    private final Byte code;
    private final String message;
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/RefundItemStateENUM.java
New file
@@ -0,0 +1,21 @@
package com.dy.pipIrrSell.virtualCard.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * @author ZhuBaoMin
 * @date 2024-03-11 8:49
 * @LastEditTime 2024-03-11 8:49
 * @Description 退款分项的退款状态,即微信退款状态
 */
@Getter
@AllArgsConstructor
public enum RefundItemStateENUM {
    NO_REFUND((byte)1, "未退款"),
    REFUNDED((byte)2, "已退款");
    private final Byte code;
    private final String message;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/RefundStateENUM.java
New file
@@ -0,0 +1,22 @@
package com.dy.pipIrrSell.virtualCard.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * @author ZhuBaoMin
 * @date 2024-03-08 20:45
 * @LastEditTime 2024-03-08 20:45
 * @Description
 */
@Getter
@AllArgsConstructor
public enum RefundStateENUM {
    TO_AUDIT((byte)1, "待审核"),
    TO_REFUND((byte)2, "待退款"),
    REFUNDED((byte)3, "已退款");
    private final Byte code;
    private final String message;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wallet/WalletSv.java
@@ -13,8 +13,8 @@
import com.dy.pipIrrGlobal.voSe.VoWalletConsume;
import com.dy.pipIrrGlobal.voSe.VoWalletRecharge;
import com.dy.pipIrrGlobal.voSe.VoWalletRefund;
import com.dy.pipIrrSell.wallet.qo.QueryVo;
import com.dy.pipIrrSell.wallet.qo.QoWalletRecharge;
import com.dy.pipIrrSell.wallet.qo.QueryVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.utils.PojoUtils;
import org.springframework.beans.factory.annotation.Autowired;
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PayInfo.java
@@ -52,7 +52,7 @@
    /**
     * 申请退款API
     */
    public static String refundUrl = "https://api.mch.weixin.qq.co/v3/refund/domestic/refunds";
    public static String refundUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
    /*
     * 退款通知API
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PaymentCtrl.java
@@ -1,11 +1,12 @@
package com.dy.pipIrrSell.wechatpay;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.dy.common.aop.SsoAop;
import com.dy.common.webUtil.BaseResponse;
import com.dy.common.webUtil.BaseResponseUtils;
import com.dy.common.webUtil.ResultCodeMsg;
import com.dy.pipIrrGlobal.pojoSe.SeVcRefund;
import com.dy.pipIrrGlobal.pojoSe.SeVcRefundItem;
import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard;
import com.dy.pipIrrGlobal.pojoSe.SeWebchatLogonState;
import com.dy.pipIrrGlobal.voSe.VoClient;
@@ -16,7 +17,12 @@
import com.dy.pipIrrSell.util.RestTemplateUtil;
import com.dy.pipIrrSell.virtualCard.VirtualCardSv;
import com.dy.pipIrrSell.virtualCard.dto.DtoVirtualCard;
import com.dy.pipIrrSell.wechatpay.dto.*;
import com.dy.pipIrrSell.virtualCard.enums.LastOperateENUM;
import com.dy.pipIrrSell.virtualCard.enums.RefundItemStateENUM;
import com.dy.pipIrrSell.wallet.enums.RefundStatusENUM;
import com.dy.pipIrrSell.wechatpay.dto.Code2Session;
import com.dy.pipIrrSell.wechatpay.dto.DtoOrder;
import com.dy.pipIrrSell.wechatpay.dto.OrderNotify;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -65,25 +71,23 @@
    private final VirtualCardSv virtualCardSv;
    private final ClientSv clientSv;
    private String privateCertFileName = PayInfo.privateCertFileName;
    private String appid = PayInfo.appid;
    private String mchid = PayInfo.mchid;
    private String schema = PayInfo.schema;
    private String signType = PayInfo.signType;
    private String description = PayInfo.description;
    private String loginUrl = PayInfo.loginUrl;
    private String notifyUrl = PayInfo.notifyUrl;
    private String grantType = PayInfo.grantType;
    private String refundUrl = PayInfo.refundUrl;
    private final String privateCertFileName = PayInfo.privateCertFileName;
    private final String appid = PayInfo.appid;
    private final String mchid = PayInfo.mchid;
    private final String schema = PayInfo.schema;
    private final String signType = PayInfo.signType;
    private final String description = PayInfo.description;
    private final String loginUrl = PayInfo.loginUrl;
    private final String notifyUrl = PayInfo.notifyUrl;
    private final String grantType = PayInfo.grantType;
    // 平台证书公钥
    private Map CERTIFICATE_MAP = new HashMap();
    private final Map CERTIFICATE_MAP = new HashMap();
    /**
     * 登录凭证校验
     * @param appid 小程序 appId
     * @param secret 小程序 appSecret
     * @param js_code 临时登录凭证code
     * @param code2Session 登录凭证校验传入对象
     * @param bindingResult
     * @return
     * @throws Exception
     */
@@ -99,11 +103,15 @@
    @PostMapping(path = "getSessionId")
    @Transactional(rollbackFor = Exception.class)
    @SsoAop()
    public BaseResponse<Boolean> getSessionId(@RequestParam("appid")  String appid, @RequestParam("secret") String secret, @RequestParam("js_code") String js_code) throws Exception {
    public BaseResponse<Boolean> getSessionId(@RequestBody @Valid Code2Session code2Session, BindingResult bindingResult) throws Exception {
        if(bindingResult != null && bindingResult.hasErrors()){
            return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
        }
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("appid", appid);
        queryParams.put("secret", secret);
        queryParams.put("js_code", js_code);
        queryParams.put("secret", code2Session.getSecret());
        queryParams.put("js_code", code2Session.getJs_code());
        queryParams.put("grant_type", grantType);
        Map<String, String> headerParams = new HashMap<>();
        JSONObject job = restTemplateUtil.get(loginUrl, queryParams, headerParams);
@@ -116,20 +124,20 @@
        String sessionKey = job.getString("session_key");
        // 检验登录态
        JSONObject checkSessionKey = payHelper.checkSessionKey(appid, secret, openid, sessionKey);
        if(checkSessionKey != null) {
            Integer errcode = checkSessionKey.getInteger("errcode");
            String errmsg = checkSessionKey.getString("errmsg");
        }
        //JSONObject checkSessionKey = payHelper.checkSessionKey(appid, secret, openid, sessionKey);
        //if(checkSessionKey != null) {
        //    Integer errcode = checkSessionKey.getInteger("errcode");
        //    String errmsg = checkSessionKey.getString("errmsg");
        //}
        // 重置登录态
        JSONObject resetUserSessionKey = payHelper.resetUserSessionKey(appid, secret, openid, sessionKey);
        if(resetUserSessionKey != null) {
            Integer errcode = checkSessionKey.getInteger("errcode");
            String errmsg = checkSessionKey.getString("errmsg");
            String openid_New = checkSessionKey.getString("openid");
            String sessionKey_New = checkSessionKey.getString("session_key");
        }
        //JSONObject resetUserSessionKey = payHelper.resetUserSessionKey(appid, secret, openid, sessionKey);
        //if(resetUserSessionKey != null) {
        //    Integer errcode = checkSessionKey.getInteger("errcode");
        //    String errmsg = checkSessionKey.getString("errmsg");
        //    String openid_New = checkSessionKey.getString("openid");
        //    String sessionKey_New = checkSessionKey.getString("session_key");
        //}
        // 添加登录态记录
        SeWebchatLogonState po = new SeWebchatLogonState();
@@ -277,110 +285,14 @@
        headers.put("Accept", "application/json");
        headers.put("Content-Type", "application/json");
        JSONObject job_result = restTemplateUtil.post(PayInfo.orderUrl, body, headers);
        if(job_result != null) {
            System.out.println(job_result.toString());
            prepayId = job_result.getString("prepay_id");
        }
        // 暂时注释掉,认证通过后再放开
        //JSONObject job_result = restTemplateUtil.post(PayInfo.orderUrl, body, headers);
        //if(job_result != null) {
        //    System.out.println(job_result.toString());
        //    prepayId = job_result.getString("prepay_id");
        //}
        return BaseResponseUtils.buildSuccess(prepayId) ;
    }
    /**
     * 申请退款
     * @param po 退款请求对象
     * @param bindingResult
     * @return
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws IOException
     * @throws SignatureException
     * @throws InvalidKeyException
     */
    @Operation(summary = "申请退款", description = "申请退款")
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = ResultCodeMsg.RsCode.SUCCESS_CODE,
                    description = "操作结果:true:成功,false:失败(BaseResponse.content)",
                    content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE,
                            schema = @Schema(implementation = Boolean.class))}
            )
    })
    @PostMapping(path = "refunds")
    @Transactional(rollbackFor = Exception.class)
    @SsoAop()
    public BaseResponse<Boolean> refunds(@RequestBody @Valid Refund po, BindingResult bindingResult) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException {
        if(bindingResult != null && bindingResult.hasErrors()){
            return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
        }
        /**
         * 1. 判断交易时间是否超过一年
         * 2. 判断退款总金额是否超过订单金额
         * 3. 判断当前订单退款次数是否超过50次
         * 4. 判断与该订单上次退款是否相隔1分钟
         */
        String tradeNo = po.getTradeNo();
        String refundNo = po.getRefundNo();
        Integer refund = po.getRefund();
        if(refundNo == null || refundNo.length() <= 0) {
            // 新提退款申请,生成退款单号
            //refundNo = generateRefundNo(tradeNo);
        }
        // 根据订单号获取总支付金额和总退款金额
        Integer totalTradeAmount = 0;
        Integer totalRefundAmount = 0;
        //Integer totalTradeAmount = getTotalTradeAmount(tradeNo);
        //Integer totalRefundAmount = getTotalRefundAmount(tradeNo);
        if(totalRefundAmount > totalTradeAmount) {
            return BaseResponseUtils.buildFail(SellResultCode.TOTAL_REFUND_EXCEED_TRADE.getMessage());
        }
        // 生成body
        RefundRequest.Amount amount = new RefundRequest.Amount();
        amount.setRefund(refund);
        amount.setTotal(totalTradeAmount);
        amount.setCurrency("CNY");
        RefundRequest refundRequest = new RefundRequest();
        refundRequest.setOut_trade_no(tradeNo);
        refundRequest.setOut_refund_no(refundNo);
        refundRequest.setNotify_url(notifyUrl);
        refundRequest.setAmount(amount);
        // 生成header
        String nonceStr = payHelper.generateRandomString();
        Long timestamp = System.currentTimeMillis() / 1000;
        String method = "POST";
        String httpUrl = "/v3/refund/domestic/refunds";
        String body = JSONObject.toJSONString(refundRequest);
        String header = schema + " " + payHelper.getToken(method, httpUrl, body, nonceStr, timestamp, privateCertFileName);
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", header);
        headers.put("Accept", "application/json");
        headers.put("Content-Type", "application/json");
        JSONObject job_refundResponse = restTemplateUtil.post(PayInfo.orderUrl, body, headers);
        RefundResponse refundResponse = JSON.parseObject(job_refundResponse.toJSONString(), RefundResponse.class);
        String status = refundResponse.getStatus();
        if(status != null && status.equals("SUCCESS")) {
            // 退款申请已受理
            return BaseResponseUtils.buildSuccess(true) ;
        } else if(status != null && status.equals("PROCESSING")) {
            // 退款处理中
            return BaseResponseUtils.buildFail(SellResultCode.PROCESSING.getMessage());
        } else {
            // 退款异常
            return BaseResponseUtils.buildError(SellResultCode.ABNORMAL.getMessage());
        }
    }
    /**
@@ -491,7 +403,68 @@
            }
        } else if(eventType != null && eventType.equals("REFUND.SUCCESS")) {
            // 退款成功后回调
            /**
             * 退款成功的回调
             * 取出通知数据对象,继而取出解密所需的associatedData和nonce,以及密文ciphertext
             * 解密ciphertext得到
             */
            OrderNotify.NotifyResource notifyResource = orderNotify.getResource();
            String associatedData = notifyResource.getAssociated_data();
            String nonce = notifyResource.getNonce();
            String ciphertext = notifyResource.getCiphertext();
            String resource = AesUtil.decryptToString(PayInfo.key.getBytes("utf-8"), associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
            JSONObject job_resource = JSONObject.parseObject(resource);
            // 解密后取出:商户订单员、微信支付订单号、交易状态、支付完成时间
            String out_trade_no = job_resource.getString("out_trade_no");
            String transaction_id = job_resource.getString("transaction_id");
            String out_refund_no = job_resource.getString("out_refund_no");
            String refund_status = job_resource.getString("refund_status");
            Date success_time = job_resource.getDate("success_time");
            if(!refund_status.equals("SUCCESS")) {
                response.setStatus(500);
                result.put("code", "FAIL");
                result.put("message", "失败");
                return result;
            }
            // 更新虚拟卡表及充值表响应字段
            SeVcRefundItem seVcRefundItem = new SeVcRefundItem();
            seVcRefundItem.setRefundTime(success_time);
            seVcRefundItem.setRefundStatus(RefundItemStateENUM.REFUNDED.getCode());
            Integer rec = virtualCardSv.updateRefundItem(seVcRefundItem);
            if(rec == null && rec <= 0) {
                response.setStatus(500);
                result.put("code", "FAIL");
                result.put("message", "失败");
                return result;
            }
            // 根据退款单号反查退款ID,根据退款ID获取退款状态是未退款的记录数量,如果是0则说明全部退款完成,更新退款表状态为已退款,将退款后金额更新到虚拟卡表
            /**
             * 根据退款通知接口返回的退款单号反查退款ID,查询该退款ID下未退款记录数量
             *      如果结果为0,则该退款已经完成
             *      1. 更新退款表状态为已退款
             *      2. 将退款后余额更新到虚拟卡表中
             */
            Integer noRefundedCount = virtualCardSv.getNoRefundedCount(out_refund_no);
            if(noRefundedCount != null && noRefundedCount == 0) {
                // 获取退款对象并修改退款状态
                Long refundId = virtualCardSv.getRefundIdByRefundNumber(out_refund_no);
                SeVcRefund seVcRefund = virtualCardSv.selectRefundByRefundId(refundId);
                seVcRefund.setRefundStatus(RefundStatusENUM.REFUNDED.getCode());
                virtualCardSv.updateRefund(seVcRefund);
                // 获取虚拟卡对象并修改余额、最后操作、最后操作时间
                Long vcId = seVcRefund.getVcId();
                Double afterRefund = seVcRefund.getAfterRefund();
                SeVirtualCard seVirtualCard = virtualCardSv.selectVirtuCardById(vcId);
                seVirtualCard.setMoney(afterRefund);
                seVirtualCard.setLastOperate(LastOperateENUM.REFUND.getCode());
                seVirtualCard.setLastOperateTime(new Date());
                virtualCardSv.updateVirtualCard(seVirtualCard);
            }
        }
        // 通知应答
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/Orders.java
New file
@@ -0,0 +1,44 @@
package com.dy.pipIrrSell.wechatpay.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
import java.util.Date;
/**
 * @author ZhuBaoMin
 * @date 2024-03-07 21:56
 * @LastEditTime 2024-03-07 21:56
 * @Description 订单对象,从充值表中取出的订单信息
 */
@Data
@Schema(name = "订单对象")
public class Orders {
    public static final long serialVersionUID = 202403072157001L;
    /**
     * 订单号
     */
    @Schema(description = "订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "订单号不能为空")
    private String orderNumber;
    /**
     * 充值金额
     */
    @Schema(description = "充值金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "充值金额不能为空")
    @Positive(message = "充值金额必须为大于0的整数")
    private Integer rechargeAmount;
    /**
     * 充值完成时间
     */
    @Schema(description = "充值完成时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "充值完成时间不能为空")
    private Date rechargeTime;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/Refund.java
@@ -25,7 +25,7 @@
    private String tradeNo;
    /**
     * 退款单号,退款单号无值:新提交退款申请。退款单号有值:重新提交退款
     * 退款单号
     */
    @Schema(description = "退款单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private String refundNo;
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/ToRefund.java
New file
@@ -0,0 +1,35 @@
package com.dy.pipIrrSell.wechatpay.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
/**
 * @author ZhuBaoMin
 * @date 2024-03-07 21:43
 * @LastEditTime 2024-03-07 21:43
 * @Description 待退款对象,用户选择一个虚拟卡退款时,系统遍历出一个退款对象列表,由多个订单共同完成退款
 */
@Data
@Schema(name = "待退款对象")
public class ToRefund {
    public static final long serialVersionUID = 202403072144001L;
    /**
     * 订单号
     */
    @Schema(description = "订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "订单号不能为空")
    private String orderNumber;
    /**
     * 退款金额
     */
    @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "退款金额不能为空")
    @Positive(message = "退款金额必须为大于0的整数")
    private Integer refundAmount;
}