2024-03-07 朱宝民 虚拟卡注册接口、充值方法与微信支付整合、获取水卡列表接口优化
21个文件已添加
5 文件已重命名
18个文件已删除
8个文件已修改
4005 ■■■■■ 已修改文件
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVcRechargeMapper.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVirtualCardMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/pojoSe/SeVcRecharge.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/pojoSe/SeVirtualCard.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/voSe/VoCards.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/resources/application-global.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeClientCardMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVcRechargeMapper.xml 171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVirtualCardMapper.xml 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/pom.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/client/ClientCtrl.java 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/config/RestTemplateConfig.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/config/RestTemplateWechatCertConfig.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/result/SellResultCode.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/AesUtil.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/HmacSha256.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java 344 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/RestTemplateUtil.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/VirtualCardCtrl.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/VirtualCardSv.java 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/dto/DtoVirtualCard.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/LastOperateENUM.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/OrderStateENUM.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wallet/WalletCtrl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PayInfo.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PaymentCtrl.java 541 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PaymentSv.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/Code2Session.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/DtoOrder.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/OrderNotify.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/Refund.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/RefundRequest.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/RefundResponse.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/.gitignore 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/.mvn/wrapper/maven-wrapper.jar 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/.mvn/wrapper/maven-wrapper.properties 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/mvnw 308 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/mvnw.cmd 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/pom.xml 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/PipIrrWebChatApplication.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/config/WebFilterConfiguration.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/config/WebListenerConfiguration.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/payment/PayHelper.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/payment/PaymentCtrl.java 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/payment/PaymentSv.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/result/WebChatResultCode.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/util/ICallback.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/util/OkHttpUtil.java 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/resources/application.yml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/resources/log4j2.yml 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/test/java/com/dy/pipirrWebChat/PipIrrWebWebchatApplicationTests.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-web/pom.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVcRechargeMapper.java
New file
@@ -0,0 +1,34 @@
package com.dy.pipIrrGlobal.daoSe;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dy.pipIrrGlobal.pojoSe.SeVcRecharge;
import org.apache.ibatis.annotations.Mapper;
/**
 * @author ZhuBaoMin
 * @date 2024-03-05 20:21
 * @LastEditTime 2024-03-05 20:21
 * @Description
 */
@Mapper
public interface SeVcRechargeMapper extends BaseMapper<SeVcRecharge> {
    int deleteByPrimaryKey(Long id);
    int insert(SeVcRecharge record);
    int insertSelective(SeVcRecharge record);
    SeVcRecharge selectByPrimaryKey(Long id);
    int updateByPrimaryKeySelective(SeVcRecharge record);
    int updateByPrimaryKey(SeVcRecharge record);
    /**
     * 根据订单号获取虚拟卡充值对象
     * @param orderNumber
     * @return
     */
    SeVcRecharge getVCRechargeByorderNumber(String orderNumber);
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/daoSe/SeVirtualCardMapper.java
New file
@@ -0,0 +1,28 @@
package com.dy.pipIrrGlobal.daoSe;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard;
import org.apache.ibatis.annotations.Mapper;
/**
 * @author ZhuBaoMin
 * @date 2024-03-05 20:45
 * @LastEditTime 2024-03-05 20:45
 * @Description
 */
@Mapper
public interface SeVirtualCardMapper extends BaseMapper<SeVirtualCard> {
    int deleteByPrimaryKey(Long id);
    int insert(SeVirtualCard record);
    int insertSelective(SeVirtualCard record);
    SeVirtualCard selectByPrimaryKey(Long id);
    int updateByPrimaryKeySelective(SeVirtualCard record);
    int updateByPrimaryKey(SeVirtualCard record);
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/pojoSe/SeVcRecharge.java
New file
@@ -0,0 +1,102 @@
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 org.hibernate.validator.constraints.Length;
import java.util.Date;
/**
 * @author ZhuBaoMin
 * @date 2024-03-05 20:21
 * @LastEditTime 2024-03-05 20:21
 * @Description
 */
@TableName(value="se_vc_recharge", autoResultMap = true)
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "虚拟卡充值实体")
public class SeVcRecharge implements BaseEntity {
    public static final long serialVersionUID = 202403052025001L;
    /**
    * 主键
    */
    @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)
    @NotNull(message = "钱包余额不能为空")
    private Double money;
    /**
    * 充值后余额
    */
    @Schema(description = "充值后余额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "充值后余额不能为空")
    private Double afterRecharge;
    /**
    * 订单号
    */
    @Schema(description = "订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "订单号不能为空")
    private String orderNumber;
    /**
    * 充值金额
    */
    @Schema(description = "充值金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "充值金额不能为空")
    private Integer rechargeAmount;
    /**
    * 下单时间
    */
    @Schema(description = "下单时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Date orderTime;
    /**
    * 充值完成时间
    */
    @Schema(description = "充值完成时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Date rechargeTime;
    /**
    * 订单状态;1-未支付,2-已支付
    */
    @Schema(description = "订单状态", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @Length(message = "订单状态不大于{max},不小于{min}", min = 1, max = 2)
    private Byte orderState;
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/pojoSe/SeVirtualCard.java
New file
@@ -0,0 +1,76 @@
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.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import java.util.Date;
/**
 * @author ZhuBaoMin
 * @date 2024-03-05 20:45
 * @LastEditTime 2024-03-05 20:45
 * @Description
 */
@TableName(value="se_virtual_card", autoResultMap = true)
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "虚拟卡实体")
public class SeVirtualCard implements BaseEntity {
    public static final long serialVersionUID = 202403052048001L;
    /**
    * 主键
    */
    @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 clientId;
    /**
    * 钱包余额
    */
    @Schema(description = "钱包余额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @Min(value = 0, message = "钱包余额不能小于0")
    private Double money;
    /**
    * 最后操作;1-开户,2-充值,3-消费,4-申请退款,5-退款审核
    */
    @Schema(description = "操作类型", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @Min(value = 1, message = "最后操作不能小于1")
    @Max(value = 5, message = "最后操作不能大于5")
    private Byte lastOperate;
    /**
    * 最后操作时间
    */
    @Schema(description = "最后操作时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Date lastOperateTime;
    /**
    * 创建时间
    */
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Date createTime;
}
pipIrr-platform/pipIrr-global/src/main/java/com/dy/pipIrrGlobal/voSe/VoCards.java
@@ -26,13 +26,13 @@
    private String cardNum;
    @Schema(title = "电话号码")
    private Float phone;
    private String phone;
    @Schema(title = "身份证号码")
    private Float idCard;
    private String idCard;
    @Schema(title = "水卡状态")
    private Float cardState;
    private Integer cardState;
    @Schema(title = "水卡状态名称")
    private String stateName;
pipIrr-platform/pipIrr-global/src/main/resources/application-global.yml
@@ -112,10 +112,6 @@
        webPort: 8085
        actutorPort: 9085
        idSuffix: 7
    webchat:
        webPort: 8086
        actutorPort: 9086
        idSuffix: 8
#项目编号
#projectCode:
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeClientCardMapper.xml
@@ -364,7 +364,7 @@
            WHEN card.state = 3 THEN '已挂失'
        End) AS stateName,
        '农户卡' AS cardType,
        card.money
        FORMAT(card.money, 2) AS money
    FROM se_client_card card
        INNER JOIN se_client cli ON card.clientId = cli.id
    <where>
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVcRechargeMapper.xml
New file
@@ -0,0 +1,171 @@
<?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.SeVcRechargeMapper">
  <resultMap id="BaseResultMap" type="com.dy.pipIrrGlobal.pojoSe.SeVcRecharge">
    <!--@mbg.generated-->
    <!--@Table se_vc_recharge-->
    <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="after_recharge" jdbcType="FLOAT" property="afterRecharge" />
    <result column="order_number" jdbcType="VARCHAR" property="orderNumber" />
    <result column="recharge_amount" jdbcType="INTEGER" property="rechargeAmount" />
    <result column="order_time" jdbcType="TIMESTAMP" property="orderTime" />
    <result column="recharge_time" jdbcType="TIMESTAMP" property="rechargeTime" />
    <result column="order_state" jdbcType="TINYINT" property="orderState" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated-->
    id, vc_id, client_id, money, after_recharge, order_number, recharge_amount, order_time,
    recharge_time, order_state
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    <!--@mbg.generated-->
    select
    <include refid="Base_Column_List" />
    from se_vc_recharge
    where id = #{id,jdbcType=BIGINT}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
    <!--@mbg.generated-->
    delete from se_vc_recharge
    where id = #{id,jdbcType=BIGINT}
  </delete>
  <insert id="insert" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRecharge">
    <!--@mbg.generated-->
    insert into se_vc_recharge (id, vc_id, client_id,
      money, after_recharge, order_number,
      recharge_amount, order_time, recharge_time,
      order_state)
    values (#{id,jdbcType=BIGINT}, #{vcId,jdbcType=BIGINT}, #{clientId,jdbcType=BIGINT},
      #{money,jdbcType=FLOAT}, #{afterRecharge,jdbcType=FLOAT}, #{orderNumber,jdbcType=VARCHAR},
      #{rechargeAmount,jdbcType=INTEGER}, #{orderTime,jdbcType=TIMESTAMP}, #{rechargeTime,jdbcType=TIMESTAMP},
      #{orderState,jdbcType=TINYINT})
  </insert>
  <insert id="insertSelective" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRecharge">
    <!--@mbg.generated-->
    insert into se_vc_recharge
    <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="afterRecharge != null">
        after_recharge,
      </if>
      <if test="orderNumber != null">
        order_number,
      </if>
      <if test="rechargeAmount != null">
        recharge_amount,
      </if>
      <if test="orderTime != null">
        order_time,
      </if>
      <if test="rechargeTime != null">
        recharge_time,
      </if>
      <if test="orderState != null">
        order_state,
      </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="afterRecharge != null">
        #{afterRecharge,jdbcType=FLOAT},
      </if>
      <if test="orderNumber != null">
        #{orderNumber,jdbcType=VARCHAR},
      </if>
      <if test="rechargeAmount != null">
        #{rechargeAmount,jdbcType=INTEGER},
      </if>
      <if test="orderTime != null">
        #{orderTime,jdbcType=TIMESTAMP},
      </if>
      <if test="rechargeTime != null">
        #{rechargeTime,jdbcType=TIMESTAMP},
      </if>
      <if test="orderState != null">
        #{orderState,jdbcType=TINYINT},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRecharge">
    <!--@mbg.generated-->
    update se_vc_recharge
    <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="afterRecharge != null">
        after_recharge = #{afterRecharge,jdbcType=FLOAT},
      </if>
      <if test="orderNumber != null">
        order_number = #{orderNumber,jdbcType=VARCHAR},
      </if>
      <if test="rechargeAmount != null">
        recharge_amount = #{rechargeAmount,jdbcType=INTEGER},
      </if>
      <if test="orderTime != null">
        order_time = #{orderTime,jdbcType=TIMESTAMP},
      </if>
      <if test="rechargeTime != null">
        recharge_time = #{rechargeTime,jdbcType=TIMESTAMP},
      </if>
      <if test="orderState != null">
        order_state = #{orderState,jdbcType=TINYINT},
      </if>
    </set>
    where id = #{id,jdbcType=BIGINT}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVcRecharge">
    <!--@mbg.generated-->
    update se_vc_recharge
    set vc_id = #{vcId,jdbcType=BIGINT},
      client_id = #{clientId,jdbcType=BIGINT},
      money = #{money,jdbcType=FLOAT},
      after_recharge = #{afterRecharge,jdbcType=FLOAT},
      order_number = #{orderNumber,jdbcType=VARCHAR},
      recharge_amount = #{rechargeAmount,jdbcType=INTEGER},
      order_time = #{orderTime,jdbcType=TIMESTAMP},
      recharge_time = #{rechargeTime,jdbcType=TIMESTAMP},
      order_state = #{orderState,jdbcType=TINYINT}
    where id = #{id,jdbcType=BIGINT}
  </update>
  <!--根据订单号获取虚拟卡充值对象-->
  <select id="getVCRechargeByorderNumber" resultMap="BaseResultMap">
    SELECT
        <include refid="Base_Column_List" />
    FROM se_vc_recharge
    WHERE order_number = #{orderNumber}
    LIMIT 0,1
  </select>
</mapper>
pipIrr-platform/pipIrr-global/src/main/resources/mapper/SeVirtualCardMapper.xml
New file
@@ -0,0 +1,116 @@
<?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.SeVirtualCardMapper">
  <resultMap id="BaseResultMap" type="com.dy.pipIrrGlobal.pojoSe.SeVirtualCard">
    <!--@mbg.generated-->
    <!--@Table se_virtual_card-->
    <id column="id" jdbcType="BIGINT" property="id" />
    <result column="client_id" jdbcType="BIGINT" property="clientId" />
    <result column="money" jdbcType="FLOAT" property="money" />
    <result column="last_operate" jdbcType="TINYINT" property="lastOperate" />
    <result column="last_operate_time" jdbcType="TIMESTAMP" property="lastOperateTime" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated-->
    id, client_id, money, last_operate, last_operate_time, create_time
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    <!--@mbg.generated-->
    select
    <include refid="Base_Column_List" />
    from se_virtual_card
    where id = #{id,jdbcType=BIGINT}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
    <!--@mbg.generated-->
    delete from se_virtual_card
    where id = #{id,jdbcType=BIGINT}
  </delete>
  <insert id="insert" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVirtualCard">
    <!--@mbg.generated-->
    insert into se_virtual_card (id, client_id, money,
      last_operate, last_operate_time, create_time
      )
    values (#{id,jdbcType=BIGINT}, #{clientId,jdbcType=BIGINT}, #{money,jdbcType=FLOAT},
      #{lastOperate,jdbcType=TINYINT}, #{lastOperateTime,jdbcType=TIMESTAMP}, #{createTime,jdbcType=TIMESTAMP}
      )
  </insert>
  <insert id="insertSelective" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVirtualCard">
    <!--@mbg.generated-->
    insert into se_virtual_card
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="clientId != null">
        client_id,
      </if>
      <if test="money != null">
        money,
      </if>
      <if test="lastOperate != null">
        last_operate,
      </if>
      <if test="lastOperateTime != null">
        last_operate_time,
      </if>
      <if test="createTime != null">
        create_time,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=BIGINT},
      </if>
      <if test="clientId != null">
        #{clientId,jdbcType=BIGINT},
      </if>
      <if test="money != null">
        #{money,jdbcType=FLOAT},
      </if>
      <if test="lastOperate != null">
        #{lastOperate,jdbcType=TINYINT},
      </if>
      <if test="lastOperateTime != null">
        #{lastOperateTime,jdbcType=TIMESTAMP},
      </if>
      <if test="createTime != null">
        #{createTime,jdbcType=TIMESTAMP},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVirtualCard">
    <!--@mbg.generated-->
    update se_virtual_card
    <set>
      <if test="clientId != null">
        client_id = #{clientId,jdbcType=BIGINT},
      </if>
      <if test="money != null">
        money = #{money,jdbcType=FLOAT},
      </if>
      <if test="lastOperate != null">
        last_operate = #{lastOperate,jdbcType=TINYINT},
      </if>
      <if test="lastOperateTime != null">
        last_operate_time = #{lastOperateTime,jdbcType=TIMESTAMP},
      </if>
      <if test="createTime != null">
        create_time = #{createTime,jdbcType=TIMESTAMP},
      </if>
    </set>
    where id = #{id,jdbcType=BIGINT}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.dy.pipIrrGlobal.pojoSe.SeVirtualCard">
    <!--@mbg.generated-->
    update se_virtual_card
    set client_id = #{clientId,jdbcType=BIGINT},
      money = #{money,jdbcType=FLOAT},
      last_operate = #{lastOperate,jdbcType=TINYINT},
      last_operate_time = #{lastOperateTime,jdbcType=TIMESTAMP},
      create_time = #{createTime,jdbcType=TIMESTAMP}
    where id = #{id,jdbcType=BIGINT}
  </update>
</mapper>
pipIrr-platform/pipIrr-web/pipIrr-web-sell/pom.xml
@@ -15,6 +15,15 @@
    <name>pipIrr-web-sell</name>
    <description>web营销信息系统</description>
    <dependencies>
        <!--OkHttp-->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.2</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- 生成不包含依赖jar的可执行jar包
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/client/ClientCtrl.java
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/config/RestTemplateConfig.java
File was renamed from pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/config/RestTemplateConfig.java
@@ -1,4 +1,4 @@
package com.dy.pipirrWebChat.config;
package com.dy.pipIrrSell.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -9,12 +9,11 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
//import org.apache.http.client.HttpClient;
/**
 * @author ZhuBaoMin
 * @date 2024-02-23 15:42
 * @LastEditTime 2024-02-23 15:42
 * @date 2024-03-06 11:43
 * @LastEditTime 2024-03-06 11:43
 * @Description
 */
@@ -78,4 +77,3 @@
        return restTemplate;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/config/RestTemplateWechatCertConfig.java
File was renamed from pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/config/RestTemplateWechatCertConfig.java
@@ -1,6 +1,6 @@
package com.dy.pipirrWebChat.config;
package com.dy.pipIrrSell.config;
import com.dy.pipirrWebChat.payment.PayInfo;
import com.dy.pipIrrSell.wechatpay.PayInfo;
import okhttp3.OkHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -18,8 +18,8 @@
/**
 * @author ZhuBaoMin
 * @date 2024-02-23 19:18
 * @LastEditTime 2024-02-23 19:18
 * @date 2024-03-06 11:44
 * @LastEditTime 2024-03-06 11:44
 * @Description
 */
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/result/SellResultCode.java
@@ -79,14 +79,27 @@
    AUDITS_ADD_FAIL(80001, "总账审核记录添加失败"),
    /**
     * 电子钱包
     * 虚拟卡
     */
    VERIFY_FAIL(10001, "验签失败"),
    TOTAL_REFUND_EXCEED_TRADE(10001, "退款总额超过订单金额"),
    ACCEPTED(10001, "退款申请已受理"),
    PROCESSING(10001, "退款处理中"),
    ABNORMAL(10001, "退款异常"),
    RECHARGE_ADD_FAIL(10001, "充值记录添加失败"),
    CLIENT_ID_CANNOT_BE_NULL(90001, "农户编号不能为空"),
    WALLET_OPEN_ACCOUNT_FAIL(90002, "电子钱包账户注册失败"),
    WALLET_ACCOUNT_EXIST(90003, "该农户已注册电子钱包"),
    NO_ACCOUNT(90004, "您尚未注册电子钱包账户"),
    WALLET_OPEN_ACCOUNT_FAIL(90002, "虚拟卡账户注册失败"),
    //WALLET_ACCOUNT_EXIST(90003, "该农户已注册电子钱包"),
    NO_ACCOUNT(90004, "您指定的虚拟卡未注册"),
    UPDATE_ACCOUNT_FAIL(90005, "充值失败,电子钱包账户更新失败"),
    RECHARGE_FAIL(90006, "充值失败"),
    RECHARGE_NOT_EXIST(90006, "充值记录不存在"),
    VIRTUAL_CARD_NOT_EXIST(90006, "虚拟卡账户不存在"),
    BALANCE_IS_INSUFFICIENT(90007, "消费失败,余额不足"),
    CONSUME_FAIL(90008, "消费失败"),
    REFUND_AMOUNT_CANNOT_GREATER_THAN_MONEY(90009, "申请退款失败,退款金额不能大于余额"),
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/AesUtil.java
New file
@@ -0,0 +1,51 @@
package com.dy.pipIrrSell.util;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 11:46
 * @LastEditTime 2024-03-06 11:46
 * @Description
 */
public class AesUtil {
    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;
    /**
     * 解密
     * @param apiV3Key apiV3密钥
     * @param associatedData 附加数据
     * @param nonce 随机串
     * @param ciphertext 数据密文
     * @return 解密后字符串
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static String decryptToString(byte[] apiV3Key, byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(apiV3Key, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/HmacSha256.java
New file
@@ -0,0 +1,43 @@
package com.dy.pipIrrSell.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 11:47
 * @LastEditTime 2024-03-06 11:47
 * @Description
 */
public class HmacSha256 {
    public static String getSignature(String secretKey, String data) throws NoSuchAlgorithmException, InvalidKeyException {
        // 创建密钥对象
        byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
        // 创建Mac对象并初始化
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(secretKeySpec);
        // 将待加密的数据转换为字节数组
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        // 使用密钥对数据进行加密
        byte[] encryptedBytes = mac.doFinal(dataBytes);
        // 将加密后的结果转换为十六进制字符串
        StringBuilder hexString = new StringBuilder();
        for (byte b : encryptedBytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        String hmacSha256 = hexString.toString();
        return hmacSha256;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/PayHelper.java
New file
@@ -0,0 +1,344 @@
package com.dy.pipIrrSell.util;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.dy.pipIrrSell.wechatpay.PayInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
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;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 11:47
 * @LastEditTime 2024-03-06 11:47
 * @Description
 */
@Component
@RequiredArgsConstructor
public class PayHelper {
    private final RestTemplateUtil restTemplateUtil;
    private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    private String checkSessionUrl = PayInfo.checkSessionUrl;
    private String tokenUrl = PayInfo.tokenUrl;
    private String resetUserSessionKeyUrl = PayInfo.resetUserSessionKeyUrl;
    // 平台证书公钥
    public Map<String, Certificate> CERTIFICATE_MAP = new HashMap();
    /**
     * 获取32位随机字符串
     * @return 随机串
     */
    public String generateRandomString() {
        Random random = new Random();
        StringBuilder sb = new StringBuilder(32);
        for (int i = 0; i < 32; i++) {
            int index = random.nextInt(CHARACTERS.length());
            sb.append(CHARACTERS.charAt(index));
        }
        return sb.toString();
    }
    /**
     * 获取商户证书私钥对象
     * @param filename 私钥文件路径
     * @return 私钥对象
     * @throws IOException
     */
    public PrivateKey getPrivateKey(String filename) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }
    /**
     * 检验登录态
     * @param appid 小程序 appId
     * @param secret 小程序 appSecret
     * @param openid 用户唯一标识符
     * @param sessionKey 会话密钥
     * @return
     * @throws IOException
     */
    public JSONObject checkSessionKey(String appid, String secret, String openid, String sessionKey) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
        String accessToken = "";
        Integer expiresIn = 0;
        String signature = HmacSha256.getSignature(sessionKey, "");
        String sigMethod = "hmac_sha256";
        JSONObject job_token = getAccessToken(appid, secret);
        if(job_token != null) {
            accessToken = job_token.getString("access_token");
            expiresIn = job_token.getInteger("expires_in");
        }
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("access_token", accessToken);
        queryParams.put("openid", openid);
        queryParams.put("signature", signature);
        queryParams.put("sig_method", sigMethod);
        Map<String, String> headerParams = new HashMap<>();
        JSONObject result = restTemplateUtil.get(checkSessionUrl, queryParams, headerParams);
        return result;
    }
    /**
     * 重置登录态
     * @param appid 小程序 appId
     * @param secret 小程序 appSecret
     * @param openid 用户唯一标识符
     * @param sessionKey 会话密钥
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws IOException
     */
    public JSONObject resetUserSessionKey(String appid, String secret, String openid, String sessionKey) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
        String accessToken = "";
        Integer expiresIn = 0;
        String signature = HmacSha256.getSignature(sessionKey, "");
        String sigMethod = "hmac_sha256";
        JSONObject job_token = getAccessToken(appid, secret);
        if(job_token != null) {
            accessToken = job_token.getString("access_token");
            expiresIn = job_token.getInteger("expires_in");
        }
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("access_token", accessToken);
        queryParams.put("openid", openid);
        queryParams.put("signature", signature);
        queryParams.put("sig_method", sigMethod);
        Map<String, String> headerParams = new HashMap<>();
        JSONObject result = restTemplateUtil.get(resetUserSessionKeyUrl, queryParams, headerParams);
        return result;
    }
    /**
     * 获取接口调用凭据
     * @param appid 小程序 appId
     * @param secret 小程序 appSecret
     * @return 凭据及凭据有效时间
     * @throws IOException
     */
    public JSONObject getAccessToken(String appid, String secret) throws IOException {
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("grant_type", "client_credential");
        queryParams.put("appid", appid);
        queryParams.put("secret", secret);
        Map<String, String> headerParams = new HashMap<>();
        JSONObject job_result = restTemplateUtil.get(tokenUrl, queryParams, headerParams);
        return job_result;
    }
    /**
     * 构造签名串_下单
     * @param method HTTP请求方法
     * @param url URL
     * @param timestamp 时间戳
     * @param nonceStr 随机串
     * @param body 报文主题
     * @return 签名串
     */
    public String buildMessage_order(String method, String url, long timestamp, String nonceStr, String body) {
        return method + "\n"
                + url + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }
    /**
     * 构造签名串_再次下单
     * @param appid 小程序唯一标识
     * @param timestamp 时间戳
     * @param nonceStr 随机串
     * @param pkg package
     * @return 签名串
     */
    public String buildMessage_signAgain(String appid, String timestamp, String nonceStr, String pkg) {
        return appid + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + pkg + "\n";
    }
    /**
     * 签名
     * @param message 被签名信息
     * @param certFileName 私钥证书文件路径
     * @return signature签名值,签名信息中的一项,参与生成签名信息
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws IOException
     */
    public String sign(byte[] message, String certFileName) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, IOException {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(certFileName));
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }
    /**
     * 获取签名信息
     * @param method
     * @param url
     * @param body
     * @return 签名信息,HTTP头中的签名信息
     * HTTP头:Authorization: 认证类型 签名信息
     * 认证类型,WECHATPAY2-SHA256-RSA2048
     */
    public String getToken(String method, String url, String body, String nonceStr, Long timestamp, String certFileName) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException, NoSuchPaddingException {
        String message = buildMessage_order(method, url, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("utf-8"), certFileName);
        return "mchid=\"" + PayInfo.mchid + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + PayInfo.serial_no + "\","
                + "signature=\"" + signature + "\"";
    }
    /**
     * 构造验造签名串
     * @param wechatpayTimestamp 请求头中返回的时间戳
     * @param wechatpayNonce 请求头中返回的随机串
     * @param boey 请求返回的body
     * @return signatureStr构造的验签名串
     */
    public String responseSign(String wechatpayTimestamp, String wechatpayNonce, String boey) {
        String signatureStr = wechatpayTimestamp + "\n"
                + wechatpayNonce + "\n"
                + boey + "\n";
        return signatureStr;
    }
    /**
     * 重新下载证书
     */
    public void refreshCertificate() throws GeneralSecurityException, IOException {
        String method = "GET";
        String httpUrl = "/v3/certificates";
        String nonceStr = generateRandomString();
        Long timestamp = System.currentTimeMillis() / 1000;
        String header = PayInfo.schema + " " + getToken(method, httpUrl, "", nonceStr, timestamp, PayInfo.privateCertFileName);
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", header);
        headers.put("Accept", "application/json");
        JSONObject job_result = restTemplateUtil.getHeaders(PayInfo.certificates,null, headers);
        JSONObject job_headers = job_result.getJSONObject("headers");
        String wechatpayNonce = job_headers.getJSONArray("Wechatpay-Nonce").getString(0);
        String wechatpaySerial = job_headers.getJSONArray("Wechatpay-Serial").getString(0);
        String signature_h = job_headers.getJSONArray("Wechatpay-Signature").getString(0);
        String signatureType_h = job_headers.getJSONArray("Wechatpay-Signature-Type").getString(0);
        String wechatpayTimestamp = job_headers.getJSONArray("Wechatpay-Timestamp").getString(0);
        JSONObject job_body = job_result.getJSONObject("body");
        if(job_body != null) {
            JSONArray array = job_body.getJSONArray("data");
            if(array != null && array.size() > 0) {
                for(int i = 0; i < array.size(); i++) {
                    JSONObject job_data = array.getJSONObject(i);
                    String certificateSerial = job_data.getString("serial_no");
                    String effective_time  = job_data.getString("effective_time");
                    String expire_time  = job_data.getString("expire_time");
                    JSONObject job_certificate = job_data.getJSONObject("encrypt_certificate");
                    String algorithm = job_certificate.getString("algorithm");
                    String nonce  = job_certificate.getString("nonce");
                    String associated_data  = job_certificate.getString("associated_data");
                    String ciphertext  = job_certificate.getString("ciphertext");
                    //对证书密文进行解密得到平台证书公钥
                    String publicKey = AesUtil.decryptToString(PayInfo.key.getBytes("utf-8"), associated_data.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
                    // 将平台公钥字符串转成Certificate对象
                    final CertificateFactory cf = CertificateFactory.getInstance("X509");
                    ByteArrayInputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(StandardCharsets.UTF_8));
                    Certificate certificate = null;
                    try {
                        certificate = cf.generateCertificate(inputStream);
                    } catch (CertificateException e) {
                        e.printStackTrace();
                    }
                    // 响应头证书序号与响应体证书序列号一致,且时间差小于5分钟时才将证书存储map
                    Long timeDiff = (System.currentTimeMillis() / 1000 - Long.parseLong(wechatpayTimestamp))/60;
                    if(wechatpaySerial.equals(certificateSerial) && timeDiff <= 5) {
                        // 证书放入MAP
                        CERTIFICATE_MAP.put(certificateSerial, certificate);
                    }
                }
            }
        }
    }
    /**
     * 使用微信平台证书进行响应验签
     * @param wechatpaySerial 来自响应头的微信平台证书序列号
     * @param signatureStr 构造的验签名串
     * @param wechatpaySignature 来自响应头的微信平台签名
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public Boolean responseSignVerify(String wechatpaySerial, String signatureStr, String wechatpaySignature) throws GeneralSecurityException, IOException {
        if(CERTIFICATE_MAP.isEmpty() || !CERTIFICATE_MAP.containsKey(wechatpaySerial)) {
            CERTIFICATE_MAP.clear();
            refreshCertificate();
        }
        Certificate certificate = (Certificate)CERTIFICATE_MAP.get(wechatpaySerial);
        if(certificate == null) {
            return false;
        }
        // 获取公钥
        PublicKey publicKey = certificate.getPublicKey();
        // 初始化SHA256withRSA前面器
        Signature signature = Signature.getInstance("SHA256withRSA");
        // 用微信平台公钥对前面器进行初始化
        signature.initVerify(certificate);
        // 将构造的验签名串更新到签名器中
        signature.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        // 请求头中微信服务器返回的签名用Base64解码,使用签名器进行验证
        boolean valid = signature.verify(Base64.getDecoder().decode(wechatpaySignature));
        return valid;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/util/RestTemplateUtil.java
File was renamed from pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/util/RestTemplateUtil.java
@@ -1,4 +1,4 @@
package com.dy.pipirrWebChat.util;
package com.dy.pipIrrSell.util;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
@@ -13,8 +13,8 @@
/**
 * @author ZhuBaoMin
 * @date 2024-02-23 15:42
 * @LastEditTime 2024-02-23 15:42
 * @date 2024-03-06 13:47
 * @LastEditTime 2024-03-06 13:47
 * @Description
 */
@@ -41,6 +41,19 @@
        return JSONObject.parseObject(response.getBody());
    }
    public JSONObject getHeaders(String url, Map<String, Object> queryParams, Map<String, String> headerParams) throws IOException {
        String tempUrl = setParamsByAppendUrl(queryParams, url);
        HttpHeaders headers = new HttpHeaders();
        headerParams.forEach(headers::add);
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(null, headers);
        ResponseEntity<String> response = restTemplate.exchange(tempUrl, HttpMethod.GET, httpEntity, String.class);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("headers", response.getHeaders());
        jsonObject.put("body", response.getBody());
        return jsonObject;
    }
    public JSONObject get2(String url, Map<String, Object> queryParams, Map<String, String> headerParams) throws IOException {
        String tempUrl = setParamsByPath(queryParams, url);
        HttpHeaders headers = new HttpHeaders();
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/VirtualCardCtrl.java
New file
@@ -0,0 +1,74 @@
package com.dy.pipIrrSell.virtualCard;
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.SeVirtualCard;
import com.dy.pipIrrSell.result.SellResultCode;
import com.dy.pipIrrSell.virtualCard.enums.LastOperateENUM;
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.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
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 java.util.Date;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 8:40
 * @LastEditTime 2024-03-06 8:40
 * @Description
 */
@Slf4j
@Tag(name = "虚拟卡管理", description = "虚拟卡管理")
@RestController
@RequestMapping(path="virtual_card")
@RequiredArgsConstructor
@Validated
public class VirtualCardCtrl {
    private final VirtualCardSv virtualCardSv;
    @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_vc")
    @SsoAop()
    public BaseResponse<Boolean> addWallet(@RequestParam("clientId") @NotNull(message = "农户编号不能为空") Long clientId){
        if(clientId == null || clientId < 0) {
            return BaseResponseUtils.buildFail(SellResultCode.CLIENT_ID_CANNOT_BE_NULL.getMessage());
        }
        SeVirtualCard seVirtualCard = new SeVirtualCard();
        seVirtualCard.setClientId(clientId);
        seVirtualCard.setMoney(0d);
        seVirtualCard.setLastOperate(LastOperateENUM.OPEN_ACCOUNT.getCode());
        seVirtualCard.setLastOperateTime(new Date());
        seVirtualCard.setCreateTime(new Date());
        Long rec = virtualCardSv.insertVirtualCard(seVirtualCard);
        if(rec == null) {
            return BaseResponseUtils.buildFail(SellResultCode.WALLET_OPEN_ACCOUNT_FAIL.getMessage());
        }
        return BaseResponseUtils.buildSuccess(true) ;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/VirtualCardSv.java
New file
@@ -0,0 +1,150 @@
package com.dy.pipIrrSell.virtualCard;
import com.dy.common.webUtil.BaseResponse;
import com.dy.common.webUtil.BaseResponseUtils;
import com.dy.pipIrrGlobal.daoSe.SeVcRechargeMapper;
import com.dy.pipIrrGlobal.daoSe.SeVirtualCardMapper;
import com.dy.pipIrrGlobal.pojoSe.SeVcRecharge;
import com.dy.pipIrrGlobal.pojoSe.SeVirtualCard;
import com.dy.pipIrrSell.result.SellResultCode;
import com.dy.pipIrrSell.virtualCard.dto.DtoVirtualCard;
import com.dy.pipIrrSell.virtualCard.enums.LastOperateENUM;
import com.dy.pipIrrSell.virtualCard.enums.OrderStateENUM;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 8:41
 * @LastEditTime 2024-03-06 8:41
 * @Description
 */
@Slf4j
@Service
public class VirtualCardSv {
    @Autowired
    private SeVirtualCardMapper seVirtualCardMapper;
    @Autowired
    private SeVcRechargeMapper seVcRechargeMapper;
    /**
     * 注册虚拟卡
     * @param po
     * @return
     */
    public Long insertVirtualCard(SeVirtualCard po) {
        seVirtualCardMapper.insert(po);
        return po.getId();
    }
    /**
     * 修改虚拟卡
     * 充值、消费、申请退款、审核退款时需要修改虚拟卡的:余额、最后操作、最后操作时间
     * @param po
     * @return
     */
    public Integer updateVirtualCard(SeVirtualCard po) {
        return seVirtualCardMapper.updateByPrimaryKeySelective(po);
    }
    /**
     * 根据虚拟卡编号获取虚拟卡对象
     * @param virtualId
     * @return
     */
    public SeVirtualCard selectVirtuCardById(Long virtualId) {
        return seVirtualCardMapper.selectByPrimaryKey(virtualId);
    }
    /**
     * 添加虚拟卡充值记录
     * JSAPI下单后生成部分充值记录
     * @param po
     * @return
     */
    public BaseResponse<Boolean> insertVCRecharge(DtoVirtualCard po) {
        String orderNumber = po.getOrderNumber();
        Long virtualId = po.getVirtualId();
        Long clientId = po.getClientId();
        Integer rechargeAmount = po.getRechargeAmount();
        // 验证该虚拟卡账户是否存在并取出当前账户余额
        SeVirtualCard seVirtualCard = seVirtualCardMapper.selectByPrimaryKey(virtualId);
        if(seVirtualCard == null) {
            return BaseResponseUtils.buildFail(SellResultCode.NO_ACCOUNT.getMessage());
        }
        Double money = seVirtualCard.getMoney();
        // 添加充值记录
        SeVcRecharge seVcRecharge = new SeVcRecharge();
        seVcRecharge.setVcId(virtualId);
        seVcRecharge.setClientId(clientId);
        seVcRecharge.setMoney(money);
        seVcRecharge.setOrderNumber(orderNumber);
        seVcRecharge.setRechargeAmount(rechargeAmount);
        seVcRecharge.setOrderTime(new Date());
        seVcRecharge.setOrderState(OrderStateENUM.NON_PAYMENT.getCode());
        Integer rec = seVcRechargeMapper.insert(seVcRecharge);
        if(rec == null) {
            return BaseResponseUtils.buildFail(SellResultCode.RECHARGE_FAIL.getMessage());
        }
        return BaseResponseUtils.buildSuccess(true) ;
    }
    /**
     * 修改虚拟卡充值记录
     * 微信支付通知后:
     *      1. 更新充值表:充值后余额、支付完成时间、订单状态
     *      2. 更新虚拟卡表:账户余额、最后操作、最后操作时间
     * @param orderNumber 订单编号
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public BaseResponse<Boolean> updateVCRecharge(String orderNumber, Date rechargeTime) {
        SeVcRecharge seVcRecharge = seVcRechargeMapper.getVCRechargeByorderNumber(orderNumber);
        if(seVcRecharge == null) {
            return BaseResponseUtils.buildFail(SellResultCode.RECHARGE_NOT_EXIST.getMessage());
        }
        Long virtualId = seVcRecharge.getVcId();
        Double money = seVcRecharge.getMoney();
        Integer rechargeAmount = seVcRecharge.getRechargeAmount();
        Double afterRrecharge = money + rechargeAmount;
        seVcRecharge.setAfterRecharge(afterRrecharge);
        seVcRecharge.setRechargeTime(rechargeTime);
        seVcRecharge.setOrderState(OrderStateENUM.PAID.getCode());
        Integer rec = seVcRechargeMapper.updateByPrimaryKeySelective(seVcRecharge);
        if(rec == null) {
            return BaseResponseUtils.buildFail(SellResultCode.RECHARGE_FAIL.getMessage());
        }
        SeVirtualCard seVirtualCard = seVirtualCardMapper.selectByPrimaryKey(virtualId);
        if(seVirtualCard == null) {
            return BaseResponseUtils.buildFail(SellResultCode.VIRTUAL_CARD_NOT_EXIST.getMessage());
        }
        seVirtualCard.setMoney(afterRrecharge);
        seVirtualCard.setLastOperate(LastOperateENUM.RECHARGE.getCode());
        seVirtualCard.setLastOperateTime(new Date());
        Integer rec2 = seVirtualCardMapper.updateByPrimaryKeySelective(seVirtualCard);
        if(rec2 == null) {
            return BaseResponseUtils.buildFail(SellResultCode.RECHARGE_FAIL.getMessage());
        }
        return BaseResponseUtils.buildSuccess(true) ;
    }
    /**
     * 修改虚拟卡充值记录
     * 微信小程序支付通知后修改:余额、充值后余额、充值完成时间
     * @param po
     * @return
     */
    public Integer updateVCRecharge(SeVcRecharge po) {
        return seVcRechargeMapper.updateByPrimaryKeySelective(po);
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/dto/DtoVirtualCard.java
New file
@@ -0,0 +1,47 @@
package com.dy.pipIrrSell.virtualCard.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 9:40
 * @LastEditTime 2024-03-06 9:40
 * @Description
 */
@Data
@Schema(name = "虚拟卡充值传入对象")
public class DtoVirtualCard {
    public static final long serialVersionUID = 202403060943001L;
    /**
     * 订单号
     */
    @Schema(description = "订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "订单号不能为空")
    private String orderNumber;
    /**
     * 农户ID
     */
    @Schema(description = "农户ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "农户ID不能为空")
    private Long clientId;
    /**
     * 虚拟卡ID
     */
    @Schema(description = "虚拟卡ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "虚拟卡ID不能为空")
    private Long virtualId;
    /**
     * 充值金额
     */
    @Schema(description = "充值金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "充值金额不能为空")
    private Integer rechargeAmount;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/LastOperateENUM.java
New file
@@ -0,0 +1,24 @@
package com.dy.pipIrrSell.virtualCard.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 9:21
 * @LastEditTime 2024-03-06 9:21
 * @Description
 */
@Getter
@AllArgsConstructor
public enum LastOperateENUM {
    OPEN_ACCOUNT((byte)1, "开户"),
    RECHARGE((byte)2, "充值"),
    CONSUME((byte)3, "消费"),
    APPLY_REFUND((byte)4, "申请退款"),
    AUDIT_REFUND((byte)5, "退款审核");
    private final Byte code;
    private final String message;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/virtualCard/enums/OrderStateENUM.java
New file
@@ -0,0 +1,21 @@
package com.dy.pipIrrSell.virtualCard.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 10:00
 * @LastEditTime 2024-03-06 10:00
 * @Description
 */
@Getter
@AllArgsConstructor
public enum OrderStateENUM {
    NON_PAYMENT((byte)1, "未支付"),
    PAID((byte)2, "已支付");
    private final Byte code;
    private final String message;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wallet/WalletCtrl.java
@@ -76,7 +76,7 @@
        }
        if(walletSv.getWalletByClientId(clientId) != null) {
            return BaseResponseUtils.buildFail(SellResultCode.WALLET_ACCOUNT_EXIST.getMessage());
            //return BaseResponseUtils.buildFail(SellResultCode.WALLET_ACCOUNT_EXIST.getMessage());
        }
        SeWallet seWallet = new SeWallet();
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PayInfo.java
File was renamed from pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/payment/PayInfo.java
@@ -1,18 +1,33 @@
package com.dy.pipirrWebChat.payment;
package com.dy.pipIrrSell.wechatpay;
/**
 * @author ZhuBaoMin
 * @date 2024-02-22 20:20
 * @LastEditTime 2024-02-22 20:20
 * @date 2024-03-06 13:49
 * @LastEditTime 2024-03-06 13:49
 * @Description
 */
public class PayInfo {
    /*
    /**
     * 小程序登录API
     */
    public static String loginUrl = "https://api.weixin.qq.com/sns/jscode2session";
    /*
    /**
     * 检验登录态
     */
    public static String checkSessionUrl = "https://api.weixin.qq.com/wxa/checksession";
    /**
     * 重置登录态
     */
    public static String resetUserSessionKeyUrl = "https://api.weixin.qq.com/wxa/resetusersessionkey";
    /**
     * 获取接口调用凭据
     */
    public static String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
    /**
     * 统一下单API
     */
    //public static String orderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
@@ -26,17 +41,18 @@
    /*
     * 支付结果通知API
     */
    public static String notifyUrl = "https://www.muxiaobao.com/api/Payment/OrderNotify";
    //public static String notifyUrl = "https://www.muxiaobao.com/api/Payment/OrderNotify";
    public static String notifyUrl = "https://44978f7456.imdo.co/webchat/payment/orderNotify";
    /*
     * 查询订单API
     */
    public static String queryUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
    /*
    /**
     * 申请退款API
     */
    public static String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";
    public static String refundUrl = "https://api.mch.weixin.qq.co/v3/refund/domestic/refunds";
    /*
     * 退款通知API
@@ -128,7 +144,9 @@
    /**
     * 私钥文件路径
     */
    public static String certFileName = "C:\\webchat\\apiclient_key.pem";
    public static String privateCertFileName = "C:\\webchat\\apiclient_key.pem";
    public static String publicCertFileName = "C:\\webchat\\wxp_cert.pem";
    /*
     * 微信订单号,优先使用
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PaymentCtrl.java
New file
@@ -0,0 +1,541 @@
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.SeVirtualCard;
import com.dy.pipIrrGlobal.pojoSe.SeWebchatLogonState;
import com.dy.pipIrrGlobal.voSe.VoClient;
import com.dy.pipIrrSell.client.ClientSv;
import com.dy.pipIrrSell.result.SellResultCode;
import com.dy.pipIrrSell.util.AesUtil;
import com.dy.pipIrrSell.util.PayHelper;
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 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.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 13:49
 * @LastEditTime 2024-03-06 13:49
 * @Description
 */
@Slf4j
@Tag(name = "微信支付管理", description = "微信支付各种操作")
@RestController
@RequestMapping(path="payment")
@RequiredArgsConstructor
public class PaymentCtrl {
    private final PaymentSv paymentSv;
    private final RestTemplateUtil restTemplateUtil;
    private final PayHelper payHelper;
    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 Map CERTIFICATE_MAP = new HashMap();
    /**
     * 登录凭证校验
     * @param appid 小程序 appId
     * @param secret 小程序 appSecret
     * @param js_code 临时登录凭证code
     * @return
     * @throws Exception
     */
    @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 = "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 {
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("appid", appid);
        queryParams.put("secret", secret);
        queryParams.put("js_code", js_code);
        queryParams.put("grant_type", grantType);
        Map<String, String> headerParams = new HashMap<>();
        JSONObject job = restTemplateUtil.get(loginUrl, queryParams, headerParams);
        if(job.getLong("errcode") != null && job.getLong("errcode") >= -1) {
            return BaseResponseUtils.buildFail("登录凭证校验失败");
        }
        String openid = job.getString("openid");
        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 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();
        po.setOpenId(openid);
        po.setSessionKey(sessionKey);
        Date createTime = new Date();
        po.setCreateTime(createTime);
        Long id = paymentSv.insert(po);
        if(id == null || id <= 0) {
            return BaseResponseUtils.buildFail("登录态记录添加失败");
        }
        String SessionId = String.valueOf(id);
        return BaseResponseUtils.buildSuccess(SessionId) ;
    }
    /**
     * 下载微信支付平台证书 测试完废除
     * @return
     * @throws Exception
     */
    @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))}
            )
    })
    @GetMapping(path = "certificates")
    @Transactional(rollbackFor = Exception.class)
    @SsoAop()
    public BaseResponse<Boolean> certificates() throws Exception {
        String method = "GET";
        String httpUrl = "/v3/certificates";
        String nonceStr = payHelper.generateRandomString();
        Long timestamp = System.currentTimeMillis() / 1000;
        String header = schema + " " + payHelper.getToken(method, httpUrl, "", nonceStr, timestamp, privateCertFileName);
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", header);
        headers.put("Accept", "application/json");
        JSONObject job_result = restTemplateUtil.getHeaders(PayInfo.certificates,null, headers);
        JSONObject job_headers = job_result.getJSONObject("headers");
        String wechatpayNonce = job_headers.getJSONArray("Wechatpay-Nonce").getString(0);
        String wechatpaySerial = job_headers.getJSONArray("Wechatpay-Serial").getString(0);
        String wechatpaySignature = job_headers.getJSONArray("Wechatpay-Signature").getString(0);
        String wechatpaySignatureType = job_headers.getJSONArray("Wechatpay-Signature-Type").getString(0);
        String wechatpayTimestamp = job_headers.getJSONArray("Wechatpay-Timestamp").getString(0);
        JSONObject job_body = job_result.getJSONObject("body");
        // 构造验签名串
        String signatureStr = payHelper.responseSign(wechatpayTimestamp, wechatpayNonce, job_body.toJSONString());
        // 验证签名
        Boolean valid = payHelper.responseSignVerify(wechatpaySerial, signatureStr, wechatpaySignature);
        return BaseResponseUtils.buildSuccess();
    }
    /**
     * JSAPI下单
     * @param order 下单请求对象,包含需要传入的参数
     * @param bindingResult
     * @return
     */
    @Operation(summary = "JSAPI下单", description = "JSAPI下单")
    @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 = "placeOrder")
    @Transactional(rollbackFor = Exception.class)
    @SsoAop()
    public BaseResponse<Boolean> placeOrder(@RequestBody @Valid DtoOrder order, BindingResult bindingResult) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IOException, SignatureException, InvalidKeyException {
        if(bindingResult != null && bindingResult.hasErrors()){
            return BaseResponseUtils.buildFail(Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
        }
        // 接收参数:登录态ID、农户ID、虚拟卡ID、充值金额
        String sessionId = order.getSessionId();
        Long virtualId = order.getVirtualId();
        Integer rechargeAmount = order.getRechargeAmount();
        String prepayId = "";
        SeWebchatLogonState po = paymentSv.selectOne(Long.parseLong(sessionId));
        String openid = po.getOpenId();
        SeVirtualCard seVirtualCard = virtualCardSv.selectVirtuCardById(virtualId);
        Long clientId = seVirtualCard.getClientId();
        VoClient voClient = clientSv.getOneClient(clientId);
        String clientNum = voClient.getClientNum();
        // 生成订单号并添加充值记录
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String orderNumber = clientNum + dateFormat.format(new Date());
        // 生成虚拟卡充值记录(部分字段)
        DtoVirtualCard virtualCard = new DtoVirtualCard();
        virtualCard.setOrderNumber(orderNumber);
        virtualCard.setClientId(clientId);
        virtualCard.setVirtualId(virtualId);
        virtualCard.setRechargeAmount(rechargeAmount);
        BaseResponse result = virtualCardSv.insertVCRecharge(virtualCard);
        if(!result.getCode().equals("0001")) {
            return BaseResponseUtils.buildFail(SellResultCode.RECHARGE_ADD_FAIL.getMessage());
        }
        JSONObject job_body = new JSONObject();
        job_body.put("appid", appid);
        job_body.put("mchid", mchid);
        job_body.put("description", description);
        job_body.put("out_trade_no", orderNumber);
        job_body.put("notify_url", notifyUrl);
        //订单金额
        JSONObject job_amount = new JSONObject();
        job_amount.put("total", 1);
        job_amount.put("currency", "CNY");
        job_body.put("amount", job_amount);
        //支付者
        JSONObject job_payer = new JSONObject();
        job_payer.put("openid", openid);
        job_body.put("payer", job_payer);
        // 获取随机串和时间戳,放在此处以保证
        String nonceStr = payHelper.generateRandomString();
        Long timestamp = System.currentTimeMillis() / 1000;
        String method = "POST";
        String httpUrl = "/v3/pay/transactions/jsapi";
        String body = job_body.toJSONString();
        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_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());
        }
    }
    /**
     * 支付通知/退款结果通知
     * @param headers
     * @param orderNotify
     * @param response
     * @return
     * @throws IOException
     * @throws GeneralSecurityException
     */
    @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 = "orderNotify", consumes = MediaType.APPLICATION_JSON_VALUE)
    @Transactional(rollbackFor = Exception.class)
    @SsoAop()
    public JSONObject orderNotify(@RequestHeader HttpHeaders headers, @RequestBody OrderNotify orderNotify, HttpServletResponse response) throws IOException, GeneralSecurityException {
        JSONObject result = new JSONObject();
        /**
         * 1.验签处理
         *      从header中取出4个子参数,同时取出body
         *      验时间差,超过5分钟的不处理
         *      验证签名
         *      验证书序列号,必须与某一个证书的序列号一致
         */
        String wechatpayNonce = String.valueOf(headers.get("Wechatpay-Nonce").get(0));
        String wechatpaySerial = String.valueOf(headers.get("Wechatpay-Serial").get(0));
        String wechatpaySignature = String.valueOf(headers.get("Wechatpay-Signature").get(0));
        String wechatpayTimestamp = String.valueOf(headers.get("Wechatpay-Timestamp").get(0));
        String bodyStr = JSONObject.toJSONString(orderNotify);
        // 验时间戳,时间差大于5分钟的拒绝
        Long timeDiff = (System.currentTimeMillis() / 1000 - Long.parseLong(wechatpayTimestamp))/60;
        if(timeDiff > 5) {
            response.setStatus(500);
            result.put("code", "FAIL");
            result.put("message", "失败");
            return result;
        }
        // 构造验签名串
        String signatureStr = payHelper.responseSign(wechatpayTimestamp, wechatpayNonce, bodyStr);
        // 验证签名
        Boolean valid = payHelper.responseSignVerify(wechatpaySerial, signatureStr, wechatpaySignature);
        if(!valid) {
            response.setStatus(500);
            result.put("code", "FAIL");
            result.put("message", "失败");
            return result;
        }
        // 序列号验证要放在验签后,因为验签时可能会下载新的证书
        boolean SerialIsValid = false;
        for (String key : payHelper.CERTIFICATE_MAP.keySet()) {
            if(key.equals(wechatpaySerial)) {
                SerialIsValid = true;
            }
        }
        if(!SerialIsValid) {
            response.setStatus(500);
            result.put("code", "FAIL");
            result.put("message", "失败");
            return result;
        }
        /**
         * 解密处理
         *      1
         */
        String eventType = orderNotify.getEvent_type();
        if(eventType != null && eventType.equals("TRANSACTION.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 trade_state = job_resource.getString("trade_state");
            Date success_time = job_resource.getDate("success_time");
            // 更新虚拟卡表及充值表响应字段
            BaseResponse result_ = virtualCardSv.updateVCRecharge(out_trade_no, success_time);
            if(!result_.getCode().equals("0001")) {
                response.setStatus(500);
                result.put("code", "FAIL");
                result.put("message", "失败");
                return result;
            }
        } else if(eventType != null && eventType.equals("REFUND.SUCCESS")) {
            // 退款成功后回调
        }
        // 通知应答
        response.setStatus(200);
        result.put("code", "SUCCESS");
        result.put("message", "成功");
        return  result;
    }
    /**
     * 再次签名
     * @param prepayId 预支付交易会话标识
     * @return 小程序调起支付参数
     * @throws Exception
     */
    @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))}
            )
    })
    @GetMapping(path = "/signAgain")
    @Transactional(rollbackFor = Exception.class)
    @SsoAop()
    public BaseResponse<JSONObject> signAgain(@RequestParam("prepayId")  String prepayId) throws Exception {
        // 获取随机串和时间戳,放在此处以保证
        String appid = PayInfo.appid;
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = payHelper.generateRandomString();
        String pkg = "prepay_id=" + prepayId;
        String message = payHelper.buildMessage_signAgain(appid, timestamp, nonceStr, pkg);
        String paySign = payHelper.sign(message.getBytes("utf-8"), privateCertFileName);
        JSONObject job_result = new JSONObject();
        job_result.put("timestamp", timestamp);
        job_result.put("nonceStr", nonceStr);
        job_result.put("package", pkg);
        job_result.put("signType", signType);
        job_result.put("paySign", paySign);
        return BaseResponseUtils.buildSuccess(job_result) ;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/PaymentSv.java
New file
@@ -0,0 +1,55 @@
package com.dy.pipIrrSell.wechatpay;
import com.dy.pipIrrGlobal.daoSe.SeVcRechargeMapper;
import com.dy.pipIrrGlobal.daoSe.SeWebchatLogonStateMapper;
import com.dy.pipIrrGlobal.pojoSe.SeVcRecharge;
import com.dy.pipIrrGlobal.pojoSe.SeWebchatLogonState;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 13:51
 * @LastEditTime 2024-03-06 13:51
 * @Description
 */
@Slf4j
@Service
public class PaymentSv {
    @Autowired
    private SeWebchatLogonStateMapper seWebchatLogonStateMapper;
    @Autowired
    private SeVcRechargeMapper seVcRechargeMapper;
    /**
     * 添加登录态状态记录
     * @param po
     * @return
     */
    Long insert(SeWebchatLogonState po) {
        seWebchatLogonStateMapper.insert(po);
        return po.getId();
    }
    /**
     * 根据登录态ID获取登录态对象
     * @param id
     * @return
     */
    SeWebchatLogonState selectOne(Long id) {
        return seWebchatLogonStateMapper.selectByPrimaryKey(id);
    }
    /**
     * 添加虚拟卡充值记录
     * @param po
     * @return
     */
    Long insertVCRecharge(SeVcRecharge po) {
        seVcRechargeMapper.insert(po);
        return po.getId();
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/Code2Session.java
File was renamed from pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/payment/dto/Code2Session.java
@@ -1,4 +1,4 @@
package com.dy.pipirrWebChat.payment.dto;
package com.dy.pipIrrSell.wechatpay.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@@ -6,8 +6,8 @@
/**
 * @author ZhuBaoMin
 * @date 2024-02-22 15:34
 * @LastEditTime 2024-02-22 15:34
 * @date 2024-03-06 13:53
 * @LastEditTime 2024-03-06 13:53
 * @Description
 */
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/DtoOrder.java
New file
@@ -0,0 +1,40 @@
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 lombok.Data;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 13:54
 * @LastEditTime 2024-03-06 13:54
 * @Description 下单请求对象,下单时传给JSAPI下单接口
 */
@Data
@Schema(name = "下单请求对象")
public class DtoOrder {
    public static final long serialVersionUID = 202403012108001L;
    /**
     * 登录态ID,用来获取openID
     */
    @Schema(description = "登录态ID", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "登录态ID不能为空")
    private String sessionId;
    /**
     * 虚拟卡编号,外键,用来获取虚拟卡余额
     */
    @Schema(description = "虚拟卡编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "虚拟卡编号不能为空")
    private Long virtualId;
    /**
     * 充值金额金额
     */
    @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "虚支付金额不能为空")
    private Integer rechargeAmount;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/OrderNotify.java
New file
@@ -0,0 +1,81 @@
package com.dy.pipIrrSell.wechatpay.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 13:54
 * @LastEditTime 2024-03-06 13:54
 * @Description 支付和退款通知接收对象
 */
@Data
@Schema(name = "支付和退款回调对象")
public class OrderNotify {
    public static final long serialVersionUID = 202402291431001L;
    /**
     * 通知ID
     */
    private String id;
    /**
     * 通知创建时间
     */
    private String create_time;
    /**
     * 通知类型
     * 支付成功通知的类型为:TRANSACTION.SUCCESS
     */
    private String event_type;
    /**
     * 通知数据类型
     * 支付成功通知为:encrypt-resource
     */
    private String resource_type;
    /**
     * 通知数据
     */
    private NotifyResource resource;
    /**
     * 回调摘要,退款通知无此属性
     */
    private String summary;
    @Data
    public class NotifyResource {
        /**
         * 加密算法类型
         * 仅支持AEAD_AES_256_GCM
         */
        private String algorithm;
        /**
         * 数据密文
         */
        private String ciphertext;
        /**
         * 附加数据
         */
        public String associated_data;
        /**
         * 原始类型
         * 原始回调类型为:transaction
         */
        private String original_type;
        /**
         * 随机串
         */
        private String nonce;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/Refund.java
New file
@@ -0,0 +1,39 @@
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 lombok.Data;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 13:55
 * @LastEditTime 2024-03-06 13:55
 * @Description 退款请求对象,小程序调用接口时传参所用对象
 */
@Data
@Schema(name = "退款请求对象")
public class Refund {
    public static final long serialVersionUID = 202403011607001L;
    /**
     * 商户订单号
     */
    @Schema(description = "商户订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "商户订单号不能为空")
    private String tradeNo;
    /**
     * 退款单号,退款单号无值:新提交退款申请。退款单号有值:重新提交退款
     */
    @Schema(description = "退款单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private String refundNo;
    /**
     * 退款金额
     */
    @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotNull(message = "退款金额不能为空")
    private Integer refund;
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/RefundRequest.java
New file
@@ -0,0 +1,70 @@
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 lombok.Data;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 13:55
 * @LastEditTime 2024-03-06 13:55
 * @Description 退款请求对象,对象赋值完整后向微信请求退款
 */
@Data
@Schema(name = "退款请求对象")
public class RefundRequest {
    public static final long serialVersionUID = 202403011540001L;
    /**
     * 商户订单号,下单时的订单号
     */
    @Schema(description = "商户订单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "商户订单号不能为空")
    private String out_trade_no;
    /**
     * 商户退款单号,订单号前加前缀“R”
     */
    @Schema(description = "商户退款单号", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "商户退款单号不能为空")
    private String out_refund_no;
    /**
     * 退款结果回调url,refundUrl
     */
    @Schema(description = "退款结果回调url", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    @NotBlank(message = "退款结果回调url不能为空")
    private String notify_url;
    /**
     * 金额信息
     */
    @Schema(description = "金额信息", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
    private Amount amount;
    @Data
    public static class Amount {
        /**
         * 退款金额
         */
        @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
        @NotNull(message = "退款金额不能为空")
        private Integer refund;
        /**
         * 原订单金额,根据订单号查询
         */
        @Schema(description = "原订单金额", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
        @NotNull(message = "原订单金额不能为空")
        private Integer total;
        /**
         * 退款币种,固定为“CNY”
         */
        @Schema(description = "退款币种", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
        @NotBlank(message = "退款币种不能为空")
        private String currency;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-sell/src/main/java/com/dy/pipIrrSell/wechatpay/dto/RefundResponse.java
New file
@@ -0,0 +1,114 @@
package com.dy.pipIrrSell.wechatpay.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
 * @author ZhuBaoMin
 * @date 2024-03-06 13:56
 * @LastEditTime 2024-03-06 13:56
 * @Description 退款申请返回的对象
 */
@Data
@Schema(name = "退款申请返回对象")
public class RefundResponse {
    public static final long serialVersionUID = 202403011431001L;
    /**
     * 微信支付退款号
     */
    private String refund_id;
    /**
     * 商户退款单号
     */
    private String out_refund_no;
    /**
     * 微信支付订单号
     */
    private String transaction_id;
    /**
     * 商户订单号
     */
    private String out_trade_no;
    /**
     * 退款渠道
     */
    private String channel;
    /**
     * 退款入账账户
     */
    private String user_received_account;
    /**
     * 退款成功时间
     */
    private String success_time;
    /**
     * 退款创建时间
     */
    private String create_time;
    /**
     * 退款状态
     */
    private String status;
    /**
     * 金额信息
     */
    private Amount amount;
    @Data
    private static class Amount {
        /**
         * 订单总金额
         */
        private Integer total;
        /**
         * 退款金额
         */
        private Integer refund;
        /**
         * 用户支付金额
         */
        private Integer payer_total;
        /**
         * 用户退款金额
         */
        private Integer payer_refund;
        /**
         * 应结退款金额
         */
        private Integer settlement_refund;
        /**
         * 应结订单金额
         */
        private Integer settlement_total;
        /**
         * 优惠退款金额
         */
        private Integer discount_refund;
        /**
         * 退款币种
         */
        private String currency;
    }
}
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/.gitignore
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/.mvn/wrapper/maven-wrapper.jar
Binary files differ
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/.mvn/wrapper/maven-wrapper.properties
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/mvnw
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/mvnw.cmd
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/pom.xml
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/PipIrrWebChatApplication.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/config/WebFilterConfiguration.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/config/WebListenerConfiguration.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/payment/PayHelper.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/payment/PaymentCtrl.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/payment/PaymentSv.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/result/WebChatResultCode.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/util/ICallback.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/java/com/dy/pipirrWebChat/util/OkHttpUtil.java
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/resources/application.yml
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/main/resources/log4j2.yml
File was deleted
pipIrr-platform/pipIrr-web/pipIrr-web-webchat/src/test/java/com/dy/pipirrWebChat/PipIrrWebWebchatApplicationTests.java
File was deleted
pipIrr-platform/pipIrr-web/pom.xml
@@ -26,7 +26,6 @@
        <module>pipIrr-web-gis</module>
        <module>pipIrr-web-sell</module>
        <module>pipIrr-web-project</module>
        <module>pipIrr-web-webchat</module>
    </modules>
    <dependencies>