无关风月
2025-04-28 e7e7a3a2e5cd9aefa3e71dd05bbc5f6f4b88a1c0
bug修改
10个文件已修改
10个文件已添加
2046 ■■■■■ 已修改文件
cloud-server-activity/pom.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java 623 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/controller/WeiXinV3Controller.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardStudentVO.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardVO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/model/response/MyHuiminCardDetailVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java 298 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatPaymentServiceImpl.java 315 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WechatPaymentService.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/resources/mapper/HuiminCardMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/resources/mapper/PayHuiminMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/resources/sharding-jdbc.properties 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/resources/sharding-jdbc.properties 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/pom.xml
@@ -14,6 +14,17 @@
    <name>福利</name>
    <description>福利</description>
    <dependencies>
        <!-- 微信支付V3 目前新版本-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.9</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
@@ -94,6 +105,12 @@
            <artifactId>geodesy</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java-core</artifactId>
            <version>0.2.12</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java
@@ -1,5 +1,6 @@
package com.dsh.activity.controller;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@@ -37,8 +38,11 @@
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.TextStyle;
import java.util.*;
import java.util.stream.Collectors;
@@ -94,35 +98,37 @@
            @ApiImplicitParam(value = "绑定学员ids多,个逗号拼接", name = "studentIds", dataType = "String", required = true),
            @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....")
    })
    public ResultUtil<THuiminCard> payHuiminCard(Integer id,Integer payType,String studentIds) {
    public ResultUtil<THuiminCard> payHuiminCard(Integer id, Integer payType, String studentIds) {
        try {
            Integer uid = tokenUtil.getUserIdFormRedis();
            if (null == uid) {
                return ResultUtil.tokenErr();
            }
            THuiminCard huiminCard = huiminCardService.getById(id);
            if (huiminCard.getEndTime()!=null && huiminCard.getEndTime().before(new Date())){
            if (huiminCard.getEndTime() != null && huiminCard.getEndTime().before(new Date())) {
                return ResultUtil.error("该惠民卡已过期");
            }
            if (huiminCard.getGrantCount()!=null){
                if (huiminCard.getGrantCount()<=payHuiminService.lambdaQuery().eq(TPayHuimin::getCardId, huiminCard.getId())
                        .eq(TPayHuimin::getStatus,2).count()){
            if (huiminCard.getGrantCount() != null) {
                if (huiminCard.getGrantCount() <= payHuiminService.lambdaQuery().eq(TPayHuimin::getCardId, huiminCard.getId())
                        .eq(TPayHuimin::getStatus, 2).count()) {
                    return ResultUtil.error("该惠民卡已售完");
                }
            }
            if (huiminCard.getLimitCount()!=null){
                if (huiminCard.getLimitCount()<=payHuiminService.lambdaQuery().eq(TPayHuimin::getCardId, huiminCard.getId())
                        .eq(TPayHuimin::getStatus,2)
            if (huiminCard.getLimitCount() != null) {
                if (huiminCard.getLimitCount() <= payHuiminService.lambdaQuery().eq(TPayHuimin::getCardId, huiminCard.getId())
                        .eq(TPayHuimin::getStatus, 2)
                        .eq(TPayHuimin::getAppUserId, uid)
                        .count()){
                        .count()) {
                    return ResultUtil.error("该惠民卡购买次数已达上限");
                }
            }
            TPayHuimin tPayHuimin = new TPayHuimin();
            tPayHuimin.setSalesMoney(huiminCard.getSalesMoney());
            String[] split = studentIds.split(",");
            tPayHuimin.setSalesMoney(huiminCard.getSalesMoney().multiply(new BigDecimal(split.length)).setScale(2));
            tPayHuimin.setAppUserId(uid);
            tPayHuimin.setStudentId(studentIds);
            switch (huiminCard.getHuiMinType()){
            switch (huiminCard.getHuiMinType()) {
                case 1:
                    // 年度卡
                    LocalDateTime localDateTime = LocalDateTime.now().plusDays(365);
@@ -148,16 +154,16 @@
            tPayHuimin.setCardId(huiminCard.getId());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
            tPayHuimin.setCode(sdf.format(new Date()) + UUIDUtil.getNumberRandom(5));
            System.err.println("支付数据"+tPayHuimin);
            System.err.println("支付数据" + tPayHuimin);
            payHuiminService.save(tPayHuimin);
            BigDecimal bigDecimal = new BigDecimal(studentIds.split(",").length);
            switch (payType){
            switch (payType) {
                case 1:
                    return payMoneyUtil.weixinpay("购买惠民卡"+"-"+0, "", tPayHuimin.getCode(), tPayHuimin.getSalesMoney().multiply(bigDecimal).toString(),
                    return payMoneyUtil.weixinpay("购买惠民卡" + "-" + 0, "", tPayHuimin.getCode(), tPayHuimin.getSalesMoney().toString(),
                            "/base/huimin/callBack/weixinPayHuiminCallback", "APP", "");
                case 2:
                    String string = tPayHuimin.getSalesMoney().multiply(bigDecimal).setScale(2).toString();
                    return payMoneyUtil.alipay(smid,"购买惠民卡", "购买惠民卡", "", tPayHuimin.getCode(), string,
                    String string = tPayHuimin.getSalesMoney().toString();
                    return payMoneyUtil.alipay(smid, "购买惠民卡", "购买惠民卡", "", tPayHuimin.getCode(), string,
                            "/base/huimin/callBack/aliPayHuiminCallback");
            }
@@ -180,7 +186,7 @@
            @ApiImplicitParam(value = "门店id", name = "storeId", dataType = "int", required = true),
            @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....")
    })
    public ResultUtil<THuiminCard> getHuiminCardDetail(Integer id,Integer storeId) {
    public ResultUtil<THuiminCard> getHuiminCardDetail(Integer id, Integer storeId) {
        try {
            Integer uid = tokenUtil.getUserIdFormRedis();
            if (null == uid) {
@@ -189,54 +195,54 @@
            THuiminCard huiminCard = huiminCardService.getById(id);
            List<TPayHuimin> payHuimins = payHuiminService.list(new LambdaQueryWrapper<TPayHuimin>()
                    .eq(TPayHuimin::getAppUserId, uid)
                    .ge(TPayHuimin::getEndTime,new Date())
                    .ge(TPayHuimin::getEndTime, new Date())
                    .eq(TPayHuimin::getStatus, 1));
            List<TPayHuimin> collect = payHuimins.stream().filter(e -> e.getCardId().equals(huiminCard.getId())).collect(Collectors.toList());
            if (!collect.isEmpty()){
            if (!collect.isEmpty()) {
                huiminCard.setIsBuy(1);
            }else{
            } else {
                huiminCard.setIsBuy(0);
            }
                List<Store> stores = storeClient.queryStoreByIds(Collections.singletonList(storeId));
                if (!stores.isEmpty()){
                    Store store = stores.get(0);
                    Integer operatorId = store.getOperatorId();
                    if (operatorId==null||operatorId==0){
                        // 平台门店
                        THuiminAgreement huiminAgreement = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
            List<Store> stores = storeClient.queryStoreByIds(Collections.singletonList(storeId));
            if (!stores.isEmpty()) {
                Store store = stores.get(0);
                Integer operatorId = store.getOperatorId();
                if (operatorId == null || operatorId == 0) {
                    // 平台门店
                    THuiminAgreement huiminAgreement = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
                            .isNull(THuiminAgreement::getOperatorId));
                    if (huiminAgreement != null) {
                        List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>()
                                .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement.getId()));
                        huiminCard.setAgreementSettings(list);
                    } else {
                        huiminCard.setAgreementSettings(new ArrayList<>());
                    }
                } else {
                    // 运营商门店
                    THuiminAgreement huiminAgreement = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
                            .eq(THuiminAgreement::getOperatorId, operatorId));
                    if (huiminAgreement != null) {
                        List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>()
                                .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement.getId()));
                        huiminCard.setAgreementSettings(list);
                    } else {
                        THuiminAgreement huiminAgreement1 = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
                                .isNull(THuiminAgreement::getOperatorId));
                        if (huiminAgreement!=null){
                        if (huiminAgreement1 != null) {
                            List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>()
                                    .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement.getId()));
                                    .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement1.getId()));
                            huiminCard.setAgreementSettings(list);
                        }else {
                        } else {
                            huiminCard.setAgreementSettings(new ArrayList<>());
                        }
                    }else{
                        // 运营商门店
                        THuiminAgreement huiminAgreement = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
                                .eq(THuiminAgreement::getOperatorId,operatorId));
                        if (huiminAgreement!=null){
                            List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>()
                                    .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement.getId()));
                            huiminCard.setAgreementSettings(list);
                        }else {
                            THuiminAgreement huiminAgreement1 = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
                                    .isNull(THuiminAgreement::getOperatorId));
                            if (huiminAgreement1!=null){
                                List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>()
                                        .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement1.getId()));
                                huiminCard.setAgreementSettings(list);
                            }else {
                                huiminCard.setAgreementSettings(new ArrayList<>());
                            }
                        }
                    }
                }else{
                    huiminCard.setAgreementSettings(new ArrayList<>());
                }
            } else {
                huiminCard.setAgreementSettings(new ArrayList<>());
            }
            return ResultUtil.success(huiminCard);
        } catch (Exception e) {
@@ -244,6 +250,7 @@
            return ResultUtil.success();
        }
    }
    /**
     * 惠民卡富文本内容-惠民卡列表
     */
@@ -256,39 +263,40 @@
            @ApiImplicitParam(value = "门店id", name = "storeId", dataType = "int", required = true),
            @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....")
    })
    public ResultUtil<HuiminAgreementVO> getHuiminAgreementAndList(Integer pageSize, Integer pageNo,Integer storeId) {
    public ResultUtil<HuiminAgreementVO> getHuiminAgreementAndList(Integer pageSize, Integer pageNo, Integer storeId) {
        try {
            Integer uid = tokenUtil.getUserIdFormRedis();
            if (null == uid) {
                return ResultUtil.tokenErr();
            }
            pageNo = (pageNo - 1) * pageSize;
            HuiminAgreementVO huiminAgreementVO = new HuiminAgreementVO();
            List<THuiminCard> cardList = huiminCardService.getHuiminAgreementAndList(pageNo,pageSize,storeId);
            List<THuiminCard> cardList = huiminCardService.getHuiminAgreementAndList(pageNo, pageSize, storeId);
            List<THuiminCard> list = huiminCardService.list();
            List<THuiminCard> cardListNolimit = huiminCardService.getHuiminAgreementAndListNolimit(storeId);
            if (cardListNolimit.isEmpty()){
            if (cardListNolimit.isEmpty()) {
                // 没有配置惠民卡 展示富文本内容
                huiminAgreementVO.setShowType(1);
            }else{
            } else {
                huiminAgreementVO.setShowType(2);
            }
            List<Store> stores = storeClient.queryStoreByIds(Collections.singletonList(storeId));
            Store store = stores.get(0);
            if (store.getOperatorId()==null || store.getOperatorId()==0){
            if (store.getOperatorId() == null || store.getOperatorId() == 0) {
                // 平台
                THuiminAgreement one = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
                        .isNull(THuiminAgreement::getOperatorId).last("limit 1"));
                huiminAgreementVO.setIntroduce(one.getStoreNoHuiminCardIntro());
            }else{
            } else {
                THuiminAgreement one = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
                        .eq(THuiminAgreement::getOperatorId,store.getOperatorId()).last("limit 1"));
                if (one == null){
                        .eq(THuiminAgreement::getOperatorId, store.getOperatorId()).last("limit 1"));
                if (one == null) {
                    THuiminAgreement two = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>()
                            .isNull(THuiminAgreement::getOperatorId).last("limit 1"));
                    huiminAgreementVO.setIntroduce(two.getStoreNoHuiminCardIntro());
                }else{
                } else {
                    huiminAgreementVO.setIntroduce(one.getStoreNoHuiminCardIntro());
                }
@@ -297,37 +305,354 @@
            List<TPayHuimin> payHuimins = payHuiminService.list(new LambdaQueryWrapper<TPayHuimin>()
                    .eq(TPayHuimin::getAppUserId, uid)
                    .ge(TPayHuimin::getEndTime,new Date())
                    .in(TPayHuimin::getStatus, Arrays.asList(2,3)));
                    .ge(TPayHuimin::getEndTime, new Date())
                    .eq(TPayHuimin::getStatus, 2));
            List<HuiminCardVO> huiminCardVOS = new ArrayList<>();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            List<TStudent> tStudents = studentClient.queryStudentList(uid);
            List<Integer> integers = new ArrayList<>();
            for (TPayHuimin payHuimin : payHuimins) {
                if (integers.contains(payHuimin.getCardId())){
                    continue;
                }
                THuiminCard huiminCard = list.stream().filter(e -> e.getId().equals(payHuimin.getCardId())).findFirst().orElse(null);
                if (huiminCard == null) continue;
                HuiminCardVO huiminCardVO1 = new HuiminCardVO();
                huiminCardVO1.setId(huiminCard.getId());
                huiminCardVO1.setHuiminName(huiminCard.getHuiMinName());
                huiminCardVO1.setCover(huiminCard.getBuyCover());
                huiminCardVO1.setIsBuy(1);
                String siteIds = "";
                if (huiminCard.getUseScope() == 2) {
                    siteIds = huiminCard.getUseIds();
                } else {
                    siteIds = siteClient.querySiteByStoreIds(huiminCard.getStoreIds())
                            .stream()
                            .map(Site::getId)
                            .map(String::valueOf)
                            .collect(Collectors.joining(","));
                }
                String storeIds = huiminCard.getStoreIds();
                if (storeIds.contains("2024")) {
                    siteIds = siteIds + ",32";
                }
                if (storeIds.contains("1001")) {
                    siteIds = siteIds + ",3";
                }
                // 门店ids
                JSONArray sid = JSONArray.parseArray(Arrays.toString(storeIds.split(",")));
                // 场地ids
                JSONArray rid = JSONArray.parseArray(Arrays.toString(siteIds.split(",")));
                // 可用时间
                LocalDate today = LocalDate.now();
                int week = today.getDayOfWeek().getValue();
                JSONArray time = JSONArray.parseArray(huiminCard.getUseWeeks());
                JSONArray jsonArray = new JSONArray();
                String string = LocalDate.now().toString();
                Date date = new Date();
                if (date.before(huiminCard.getEndTime()) && date.after(huiminCard.getStartTime())) {
                    for (Object o : time) {
                        JSONObject jsonObject = (JSONObject) o;
                        for (Object days : jsonObject.getJSONArray("days")) {
                            JSONObject jsonObject1 = (JSONObject) days;
                            if (jsonObject1.getString("value").equals(String.valueOf(week)) && jsonObject1.getBoolean("checked")) {
                                JSONObject jsonObject2 = new JSONObject();
                                String s1 = string + " " + jsonObject.getString("startTime") + ":00";
                                String e1 = string + " " + jsonObject.getString("endTime") + ":00";
                                // 转化为Date类型
                                Date start = DateUtil.parse(s1, "yyyy-MM-dd HH:mm:ss");
                                Date end = DateUtil.parse(e1, "yyyy-MM-dd HH:mm:ss");
                                if (new Date().after(start) && new Date().before(end)) {
//                                    jsonObject2.put("start_time", start.getTime() / 1000);
                                    Date date1 = new Date();
                                    jsonObject2.put("start_time", date1.getTime() / 1000);
                                    date1.setTime(date1.getTime() + 30 * 1000);
                                    if (start.before(end)) {
                                        jsonObject2.put("end_time", date1.getTime() / 1000);
                                        jsonArray.add(jsonObject2);
                                    }
                                }
                            }
                        }
                    }
                }
                if (!jsonArray.isEmpty()) {
                    huiminCardVO1.setEndTime(simpleDateFormat.format(payHuimin.getEndTime()));
                    TStudent tStudent = tStudents.stream().filter(e -> e.getId().equals(Integer.valueOf(payHuimin.getStudentId().split(",")[0]))).findFirst().orElse(null);
                    if (tStudent!=null){
                        huiminCardVO1.setStudentName(tStudent.getName());
                    }
                    String temp = "{\"sid\":" + sid.toJSONString() + "," + "\"rid\":" + rid + "," + "\"uid\":\"" + payHuimin.getStudentId().split(",")[0] + "\"," + "\"time\":" + jsonArray.toJSONString() + ",\"type\":" + 2 + "}";
                    huiminCardVO1.setQrCode(temp);
                    huiminCardVOS.add(huiminCardVO1);
                    integers.add(payHuimin.getCardId());
                }
            }
            for (THuiminCard huiminCardVO : cardList) {
                HuiminCardVO huiminCardVO1 = new HuiminCardVO();
                huiminCardVO1.setId(huiminCardVO.getId());
                List<TPayHuimin> collect = payHuimins.stream().filter(e -> e.getCardId().equals(huiminCardVO.getId())).collect(Collectors.toList());
                if (!collect.isEmpty()){
                if (!collect.isEmpty()) {
                    for (TPayHuimin tPayHuimin : collect) {
                        if (tPayHuimin.getStatus().equals(2)){
                        if (tPayHuimin.getStatus().equals(2)) {
                            huiminCardVO1.setCover(huiminCardVO.getBuyCover());
                            huiminCardVO1.setIsBuy(1);
                            break;
                        }else{
                        } else {
                            huiminCardVO1.setCover(huiminCardVO.getUnBuyCover());
                            huiminCardVO1.setIsBuy(0);
                        }
                    }
                }else{
                } else {
                    huiminCardVO1.setCover(huiminCardVO.getUnBuyCover());
                    huiminCardVO1.setIsBuy(0);
                }
                huiminCardVOS.add(huiminCardVO1);
            }
            huiminAgreementVO.setCardList(huiminCardVOS);
            // 手动对huiminCardVOS进行分页
            if (pageNo < 1) {
                pageNo = 1;
            }
            if (pageSize < 1) {
                pageSize = 10; // 默认每页10条
            }
            int startIndex = (pageNo - 1) * pageSize;
            int endIndex = Math.min(startIndex + pageSize, huiminCardVOS.size());
// 防止 startIndex 超过 huiminCardVOS.size()
            if (startIndex > huiminCardVOS.size()) {
                startIndex = huiminCardVOS.size();
            }
            List<HuiminCardVO> paginatedHuiminCardVOS = huiminCardVOS.subList(startIndex, endIndex);
// 将分页后的数据设置到 huiminAgreementVO 中
            huiminAgreementVO.setCardList(paginatedHuiminCardVOS);
// 将分页后的数据设置到 huiminAgreementVO 中
            huiminAgreementVO.setCardList(paginatedHuiminCardVOS);
//            huiminAgreementVO.setCardList(huiminCardVOS);
            return ResultUtil.success(huiminAgreementVO);
        } catch (Exception e) {
            e.printStackTrace();
            return ResultUtil.success(new HuiminAgreementVO());
        }
    }
    /**
     * 惠民卡列表-根据惠民卡获取已购买绑定惠民卡的学员列表
     */
    @ResponseBody
    @PostMapping("/getStudentListByCarId")
    @ApiOperation(value = "惠民卡列表-根据惠民卡获取已购买绑定惠民卡的学员列表")
    @ApiImplicitParams({
            @ApiImplicitParam(value = "惠民卡id", name = "id", dataType = "int", required = true),
            @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....")
    })
    public ResultUtil<List<TStudent>> getStudentListByCarId(Integer id) {
        try {
            Integer uid = tokenUtil.getUserIdFormRedis();
            if (null == uid) {
                return ResultUtil.tokenErr();
            }
            List<TPayHuimin> payHuimins = payHuiminService.list(new LambdaQueryWrapper<TPayHuimin>()
                    .eq(TPayHuimin::getAppUserId, uid)
                    .eq(TPayHuimin::getCardId, id)
                    .ge(TPayHuimin::getEndTime, new Date())
                    .eq(TPayHuimin::getStatus, 2));
            List<THuiminCard> huiminCards = huiminCardService.list();
            List<Integer> studentIds = new ArrayList<>();
            for (TPayHuimin payHuimin : payHuimins) {
                THuiminCard huiminCard = huiminCards.stream().filter(e -> e.getId().equals(payHuimin.getCardId())).findFirst().orElse(null);
                if (huiminCard != null) {
                    String siteIds = "";
                    if (huiminCard.getUseScope() == 2) {
                        siteIds = huiminCard.getUseIds();
                    } else {
                        siteIds = siteClient.querySiteByStoreIds(huiminCard.getStoreIds())
                                .stream()
                                .map(Site::getId)
                                .map(String::valueOf)
                                .collect(Collectors.joining(","));
                    }
                    String storeIds = huiminCard.getStoreIds();
                    if (storeIds.contains("2024")) {
                        siteIds = siteIds + ",32";
                    }
                    if (storeIds.contains("1001")) {
                        siteIds = siteIds + ",3";
                    }
                    // 门店ids
                    JSONArray sid = JSONArray.parseArray(Arrays.toString(storeIds.split(",")));
                    // 场地ids
                    JSONArray rid = JSONArray.parseArray(Arrays.toString(siteIds.split(",")));
                    // 可用时间
                    LocalDate today = LocalDate.now();
                    int week = today.getDayOfWeek().getValue();
                    JSONArray time = JSONArray.parseArray(huiminCard.getUseWeeks());
                    JSONArray jsonArray = new JSONArray();
                    String string = LocalDate.now().toString();
                    Date date = new Date();
                    if (date.before(huiminCard.getEndTime()) && date.after(huiminCard.getStartTime())) {
                        for (Object o : time) {
                            JSONObject jsonObject = (JSONObject) o;
                            for (Object days : jsonObject.getJSONArray("days")) {
                                JSONObject jsonObject1 = (JSONObject) days;
                                if (jsonObject1.getString("value").equals(String.valueOf(week)) && jsonObject1.getBoolean("checked")) {
                                    JSONObject jsonObject2 = new JSONObject();
                                    String s1 = string + " " + jsonObject.getString("startTime") + ":00";
                                    String e1 = string + " " + jsonObject.getString("endTime") + ":00";
                                    // 转化为Date类型
                                    Date start = DateUtil.parse(s1, "yyyy-MM-dd HH:mm:ss");
                                    Date end = DateUtil.parse(e1, "yyyy-MM-dd HH:mm:ss");
                                    if (new Date().after(start) && new Date().before(end)) {
//                                    jsonObject2.put("start_time", start.getTime() / 1000);
                                        Date date1 = new Date();
                                        jsonObject2.put("start_time", date1.getTime() / 1000);
                                        date1.setTime(date1.getTime() + 30 * 1000);
                                        if (start.before(end)) {
                                            jsonObject2.put("end_time", date1.getTime() / 1000);
                                            jsonArray.add(jsonObject2);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (!jsonArray.isEmpty()) {
                        // 将学员加入
                        String[] split = payHuimin.getStudentId().split(",");
                        for (String s : split) {
                            studentIds.add(Integer.valueOf(s));
                        }
                    }
                }
            }
            String collect = studentIds.stream().map(String::valueOf).collect(Collectors.joining(","));
            List<TStudent> studentByIds = studentClient.getStudentByIds(collect);
            return ResultUtil.success(studentByIds);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 惠民卡列表-选择学员后返回对应的二维码生成规则和有效期结束时间
     */
    @ResponseBody
    @PostMapping("/getQrCodeByStudentId")
    @ApiOperation(value = "惠民卡列表-选择学员后返回对应的二维码生成规则和有效期结束时间")
    @ApiImplicitParams({
            @ApiImplicitParam(value = "惠民卡id", name = "cardId", dataType = "int", required = true),
            @ApiImplicitParam(value = "学员id", name = "studentId", dataType = "int", required = true),
            @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....")
    })
    public ResultUtil<HuiminCardStudentVO> getQrCodeByStudentId(Integer cardId, Integer studentId) {
        try {
            Integer uid = tokenUtil.getUserIdFormRedis();
            if (null == uid) {
                return ResultUtil.tokenErr();
            }
            List<TStudent> tStudents = studentClient.queryStudentList(uid);
            List<TPayHuimin> payHuimins = payHuiminService.list(new LambdaQueryWrapper<TPayHuimin>()
                            .apply("FIND_IN_SET(" + studentId + ", studentId) > 0")
//                    .eq(TPayHuimin::getStudentId, studentId)
                    .eq(TPayHuimin::getCardId, cardId)
                    .ge(TPayHuimin::getEndTime, new Date())
                    .eq(TPayHuimin::getStatus, 2)
                    .orderByDesc(TPayHuimin::getEndTime));
            HuiminCardStudentVO huiminCardStudentVO = new HuiminCardStudentVO();
            if (!payHuimins.isEmpty()) {
                TPayHuimin tPayHuimin = payHuimins.get(0);
                THuiminCard huiminCard = huiminCardService.getById(cardId);
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
                huiminCardStudentVO.setEndTime(simpleDateFormat.format(tPayHuimin.getEndTime()));
                String siteIds = "";
                if (huiminCard.getUseScope() == 2) {
                    siteIds = huiminCard.getUseIds();
                } else {
                    siteIds = siteClient.querySiteByStoreIds(huiminCard.getStoreIds())
                            .stream()
                            .map(Site::getId)
                            .map(String::valueOf)
                            .collect(Collectors.joining(","));
                }
                String storeIds = huiminCard.getStoreIds();
                if (storeIds.contains("2024")) {
                    siteIds = siteIds + ",32";
                }
                if (storeIds.contains("1001")) {
                    siteIds = siteIds + ",3";
                }
                // 门店ids
                JSONArray sid = JSONArray.parseArray(Arrays.toString(storeIds.split(",")));
                // 场地ids
                JSONArray rid = JSONArray.parseArray(Arrays.toString(siteIds.split(",")));
                // 可用时间
                LocalDate today = LocalDate.now();
                int week = today.getDayOfWeek().getValue();
                JSONArray time = JSONArray.parseArray(huiminCard.getUseWeeks());
                JSONArray jsonArray = new JSONArray();
                String string = LocalDate.now().toString();
                Date date = new Date();
                if (date.before(huiminCard.getEndTime()) && date.after(huiminCard.getStartTime())) {
                    for (Object o : time) {
                        JSONObject jsonObject = (JSONObject) o;
                        for (Object days : jsonObject.getJSONArray("days")) {
                            JSONObject jsonObject1 = (JSONObject) days;
                            if (jsonObject1.getString("value").equals(String.valueOf(week)) && jsonObject1.getBoolean("checked")) {
                                JSONObject jsonObject2 = new JSONObject();
                                String s1 = string + " " + jsonObject.getString("startTime") + ":00";
                                String e1 = string + " " + jsonObject.getString("endTime") + ":00";
                                // 转化为Date类型
                                Date start = DateUtil.parse(s1, "yyyy-MM-dd HH:mm:ss");
                                Date end = DateUtil.parse(e1, "yyyy-MM-dd HH:mm:ss");
                                jsonObject2.put("start_time", start.getTime() / 1000);
                                start.setTime(start.getTime() + 30 * 1000);
                                if (new Date().after(start) && new Date().before(end)) {
//                                    jsonObject2.put("start_time", start.getTime() / 1000);
                                    Date date1 = new Date();
                                    jsonObject2.put("start_time", date1.getTime() / 1000);
                                    date1.setTime(date1.getTime() + 30 * 1000);
                                    if (start.before(end)) {
                                        jsonObject2.put("end_time", date1.getTime() / 1000);
                                        jsonArray.add(jsonObject2);
                                    }
                                }
                            }
                        }
                    }
                }
                String temp = "{\"sid\":" + sid.toJSONString() + "," + "\"rid\":" + rid + "," + "\"uid\":\"" + studentId + "\"," + "\"time\":" + jsonArray.toJSONString() + ",\"type\":" + 2 + "}";
                huiminCardStudentVO.setQrCode(temp);
                TStudent tStudent = tStudents.stream().filter(e -> e.getId().equals(studentId)).findFirst().orElse(null);
                if (tStudent!=null){
                    huiminCardStudentVO.setStudentName(tStudent.getName());
                }
            }
            return ResultUtil.success(huiminCardStudentVO);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 惠民卡富文本内容-惠民卡列表
     */
@@ -346,36 +671,34 @@
                return ResultUtil.tokenErr();
            }
            pageNo = (pageNo - 1) * pageSize;
            List<MyHuiminCardVO> cardList = huiminCardService.getMyHuiminCardList(pageNo,pageSize,uid);
            List<MyHuiminCardVO> cardList = huiminCardService.getMyHuiminCardList(pageNo, pageSize, uid);
            List<THuiminCard> list = huiminCardService.list();
            for (MyHuiminCardVO myHuiminCardVO : cardList) {
                TPayHuimin byId = payHuiminService.getById(myHuiminCardVO.getId());
                myHuiminCardVO.setEndTime(byId.getEndTime());
                myHuiminCardVO.setSalesMoney(byId.getSalesMoney());
                int count = huiminRecordService.count(new LambdaQueryWrapper<THuiminRecord>()
                        .eq(THuiminRecord::getHuiminCardId, myHuiminCardVO.getCardId()));
                // 购卡7日内没有使用记录可退款。超过7日不管有没有使用记录都不能退款
                if (new Date().after(DateUtil.addDay(myHuiminCardVO.getPaymentTime(),7))){
                if (new Date().after(DateUtil.addDay(myHuiminCardVO.getPaymentTime(), 7))) {
                    // 超过七天不可退款
                    myHuiminCardVO.setIsRefund(0);
                }else if (count>=7){
                } else if (count >= 7) {
                    myHuiminCardVO.setIsRefund(0);
                }else{
                } else {
                    myHuiminCardVO.setIsRefund(1);
                }
                THuiminCard huiminCard = list.stream().filter(e -> e.getId().equals(myHuiminCardVO.getCardId())).findFirst().orElse(null);
                if (null!=huiminCard){
                if (null != huiminCard) {
                    myHuiminCardVO.setCover(huiminCard.getBuyCover());
                }
                if (myHuiminCardVO.getEndTime().before(new Date())){
                if (myHuiminCardVO.getEndTime().before(new Date())) {
                    // 已过期
                    myHuiminCardVO.setIsExpire(1);
                    if (null!=huiminCard){
                    if (null != huiminCard) {
                        myHuiminCardVO.setCover(huiminCard.getUnBuyCover());
                    }
                }else{
                } else {
                    myHuiminCardVO.setIsExpire(0);
                }
                // 查询绑定人员列表
@@ -388,6 +711,7 @@
            return ResultUtil.success(new ArrayList<>());
        }
    }
    /**
     * 个人中心-我的惠民卡-退款
     */
@@ -405,35 +729,35 @@
                return ResultUtil.tokenErr();
            }
            TPayHuimin tPayHuimin = payHuiminService.getById(id);
            if (tPayHuimin==null){
            if (tPayHuimin == null) {
                return ResultUtil.error("订单不存在");
            }
            if (tPayHuimin.getStatus()==3 && tPayHuimin.getRefundStatus()!=1){
            if (tPayHuimin.getStatus() == 3 && tPayHuimin.getRefundStatus() != 1) {
                return ResultUtil.error("不可重复退款");
            }
            if (tPayHuimin.getEndTime().before(new Date())){
            if (tPayHuimin.getEndTime().before(new Date())) {
                // 已过期
                return ResultUtil.error("惠民卡已过期,不可退款");
            }
            int count = huiminRecordService.count(new LambdaQueryWrapper<THuiminRecord>()
                    .eq(THuiminRecord::getHuiminCardId, tPayHuimin.getCardId()));
            // 购卡7日内没有使用记录可退款。超过7日不管有没有使用记录都不能退款
            if (new Date().after(DateUtil.addDay(tPayHuimin.getPaymentTime(),7))){
            if (new Date().after(DateUtil.addDay(tPayHuimin.getPaymentTime(), 7))) {
                // 超过七天不可退款
                return ResultUtil.error("惠民卡购买超过七天,不可退款");
            }else if (count>=7){
            } else if (count >= 7) {
                // 使用记录大于等于7次不可退款
                return ResultUtil.error("惠民卡使用记录大于等于7次,不可退款");
            }
            if (tPayHuimin.getPaymentType()==1){
            if (tPayHuimin.getPaymentType() == 1) {
                Map<String, String> map = payMoneyUtil.wxRefund(tPayHuimin.getOrderNumber(), tPayHuimin.getCode(),
                        tPayHuimin.getSalesMoney().toString(), tPayHuimin.getSalesMoney().toString(), "/base/huimin/callBack/wxRefundHuiminCallback");
                if(!"SUCCESS".equals(map.get("return_code"))){
                if (!"SUCCESS".equals(map.get("return_code"))) {
                    System.err.println("-------------微信退款失败---------");
                    System.err.println(map.get("return_msg"));
                    return ResultUtil.error("微信退款失败");
                }
            }else{
            } else {
                Map<String, String> map = payMoneyUtil.aliRefund(tPayHuimin.getOrderNumber(), tPayHuimin.getSalesMoney().toString());
                String return_code = map.get("code");
                if (!"10000".equals(return_code)) {
@@ -462,10 +786,61 @@
//        String format = simpleDateFormat.format(date);
//        System.err.println(format);
        String storeIds = "1001,1002";
        JSONArray jsonArray = JSONArray.parseArray(Arrays.toString(storeIds.split(",")));
        System.err.println(jsonArray);
//        String storeIds = "1001,1002";
//        JSONArray jsonArray = JSONArray.parseArray(Arrays.toString(storeIds.split(",")));
//        System.err.println(jsonArray);
//
//        // 获取当前日期
//        LocalDate today = LocalDate.now();
//
//        // 获取当前日期是周几
//        DayOfWeek dayOfWeek = today.getDayOfWeek();
//
//        // 输出当前是周几,使用中文显示
//        System.out.println("今天是: " + dayOfWeek.getValue());
        Date start = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.err.println(simpleDateFormat.format(start));
        start.setTime(start.getTime() + 30 * 1000);
        System.err.println(simpleDateFormat.format(start));
//        String temp = "[{\"days\":[{\"value\":\"1\",\"label\":\"星期一\",\"checked\":true},{\"value\":\"2\",\"label\":\"星期二\",\"checked\":true},{\"value\":\"3\",\"label\":\"星期三\",\"checked\":true},{\"value\":\"4\",\"label\":\"星期四\",\"checked\":true},{\"value\":\"5\",\"label\":\"星期五\",\"checked\":true},{\"value\":\"6\",\"label\":\"星期六\",\"checked\":false},{\"value\":\"7\",\"label\":\"星期日\",\"checked\":false}],\"startTime\":\"00:00\",\"endTime\":\"23:59\"}]";
//        // 可用时间
//        LocalDate today = LocalDate.now();
//
//        int week = today.getDayOfWeek().getValue();
//
//        JSONArray time = JSONArray.parseArray(temp);
//        int i = 0;
//        JSONArray jsonArray = new JSONArray();
//
//        StringBuilder startTime = new StringBuilder();
//        StringBuilder endTime = new StringBuilder();
//
//        String string = LocalDate.now().toString();
//
//        for (Object o : time) {
//            JSONObject jsonObject = (JSONObject) o;
//            for (Object days : jsonObject.getJSONArray("days")) {
//                JSONObject jsonObject1 = (JSONObject) days;
//                if (jsonObject1.getString("value").equals(String.valueOf(week))&&jsonObject1.getBoolean("checked")){
//                    JSONObject jsonObject2 = new JSONObject();
//                    String s1 = string+" "+jsonObject.getString("startTime")+":00";
//                    String e1 = string+" "+jsonObject.getString("endTime")+":00";
//                    // 转化为Date类型
//                    Date start = DateUtil.parse(s1,"yyyy-MM-dd HH:mm:ss");
//                    Date end = DateUtil.parse(e1,"yyyy-MM-dd HH:mm:ss");
//                    jsonObject2.put("startTime",start.getTime());
//                    jsonObject2.put("endTime",end.getTime());
//                    jsonArray.add(jsonObject2);
//                }
//
//
//            }
//            i++;
//        }
//        System.err.println(jsonArray);
    }
    /**
     * 个人中心-我的惠民卡-查看详情
     */
@@ -483,15 +858,19 @@
                return ResultUtil.tokenErr();
            }
            TPayHuimin tPayHuimin = payHuiminService.getById(id);
            if (tPayHuimin==null){
            if (tPayHuimin == null) {
                return ResultUtil.error("惠民卡不存在");
            }
            THuiminCard byId = huiminCardService.getById(tPayHuimin.getCardId());
            MyHuiminCardDetailVO myHuiminCardDetailVO = new MyHuiminCardDetailVO();
            List<TStudent> studentByIds1 = studentClient.getStudentByIds(tPayHuimin.getStudentId());
            if (CollUtil.isNotEmpty(studentByIds1)) {
                myHuiminCardDetailVO.setStudentName(studentByIds1.get(0).getName());
            }
            String siteIds = "";
            if (byId.getUseScope()==2){
            if (byId.getUseScope() == 2) {
                siteIds = byId.getUseIds();
            }else{
            } else {
                siteIds = siteClient.querySiteByStoreIds(byId.getStoreIds())
                        .stream()
                        .map(Site::getId)
@@ -499,8 +878,57 @@
                        .collect(Collectors.joining(","));
            }
            String storeIds = byId.getStoreIds();
            JSONArray jsonArray = JSONArray.parseArray(Arrays.toString(storeIds.split(",")));
            String temp ="{\"sid\":\"" +siteIds+"\","+"\"useTimes:\"\""+byId.getUseWeeks()+"\","+"\"unUseTimes:\""+byId.getUnUseTimes()+"\"}";
            if (storeIds.contains("2024")) {
                siteIds = siteIds + ",32";
            }
            if (storeIds.contains("1001")) {
                siteIds = siteIds + ",3";
            }
            // 门店ids
            JSONArray sid = JSONArray.parseArray(Arrays.toString(storeIds.split(",")));
            // 场地ids
            JSONArray rid = JSONArray.parseArray(Arrays.toString(siteIds.split(",")));
            // 可用时间
            LocalDate today = LocalDate.now();
            int week = today.getDayOfWeek().getValue();
            JSONArray time = JSONArray.parseArray(byId.getUseWeeks());
            JSONArray jsonArray = new JSONArray();
            String string = LocalDate.now().toString();
            Date date = new Date();
            if (date.before(byId.getEndTime()) && date.after(byId.getStartTime())) {
                for (Object o : time) {
                    JSONObject jsonObject = (JSONObject) o;
                    for (Object days : jsonObject.getJSONArray("days")) {
                        JSONObject jsonObject1 = (JSONObject) days;
                        if (jsonObject1.getString("value").equals(String.valueOf(week)) && jsonObject1.getBoolean("checked")) {
                            JSONObject jsonObject2 = new JSONObject();
                            String s1 = string + " " + jsonObject.getString("startTime") + ":00";
                            String e1 = string + " " + jsonObject.getString("endTime") + ":00";
                            // 转化为Date类型
                            Date start = DateUtil.parse(s1, "yyyy-MM-dd HH:mm:ss");
                            Date end = DateUtil.parse(e1, "yyyy-MM-dd HH:mm:ss");
                            jsonObject2.put("start_time", start.getTime() / 1000);
                            start.setTime(start.getTime() + 30 * 1000);
                            if (new Date().after(start) && new Date().before(end)) {
//                                    jsonObject2.put("start_time", start.getTime() / 1000);
                                Date date1 = new Date();
                                jsonObject2.put("start_time", date1.getTime() / 1000);
                                date1.setTime(date1.getTime() + 30 * 1000);
                                if (start.before(end)) {
                                    jsonObject2.put("end_time", date1.getTime() / 1000);
                                    jsonArray.add(jsonObject2);
                                }
                            }
                        }
                    }
                }
            }
            String temp = "{\"sid\":" + sid.toJSONString() + "," + "\"rid\":" + rid + "," + "\"uid\":\"" + tPayHuimin.getStudentId() + "\"," + "\"time\":" + jsonArray.toJSONString() + ",\"type\":" + 2 + "}";
            myHuiminCardDetailVO.setQrCode(temp);
            myHuiminCardDetailVO.setCardId(tPayHuimin.getCardId());
            myHuiminCardDetailVO.setHuiminCard(byId);
@@ -515,6 +943,7 @@
            return ResultUtil.success(new MyHuiminCardDetailVO());
        }
    }
    /**
     * 获取添加人员、选择人员说明文案
     */
@@ -532,7 +961,7 @@
            }
            THuiminAgreement one = huiminAgreementService.lambdaQuery()
                    .isNull(THuiminAgreement::getOperatorId).last("limit 1").one();
            return ResultUtil.success(one);
             return ResultUtil.success(one);
        } catch (Exception e) {
            e.printStackTrace();
            return ResultUtil.success(new THuiminAgreement());
cloud-server-activity/src/main/java/com/dsh/activity/controller/WeiXinV3Controller.java
New file
@@ -0,0 +1,76 @@
package com.dsh.activity.controller;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dsh.activity.entity.*;
import com.dsh.activity.feignclient.account.StudentClient;
import com.dsh.activity.feignclient.other.SiteClient;
import com.dsh.activity.feignclient.other.StoreClient;
import com.dsh.activity.feignclient.other.model.Site;
import com.dsh.activity.feignclient.other.model.Store;
import com.dsh.activity.model.response.*;
import com.dsh.activity.service.*;
import com.dsh.activity.util.*;
//import com.dsh.activity.util.wx.WechatPaymentService;
import com.dsh.activity.util.wx.WxAppPayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author zhibing.pu
 * @date 2023/6/24 11:27
 */
@RestController
@RequestMapping("/base")
public class WeiXinV3Controller {
//    @Resource
//    private WechatPaymentService wechatPaymentService;
    @Autowired
    private WxAppPayService wxAppPayService;
    @ResponseBody
    @PostMapping("/testWeiXinV3")
    @ApiOperation(value = "获取添加人员、选择人员说明文案")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....")
    })
    public ResultUtil getContentForStudent() throws Exception {
//        Map<String, Object> stringObjectMap = wechatPaymentService.weChatDoUnifiedOrder();
        // ... 在你的下单方法中调用 ...
        try {
            String description = "商品描述";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
            String outTradeNo =sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
            BigDecimal amount = new BigDecimal("0.01"); // 支付金额,例如1分钱
            Map<String, String> payParams = wxAppPayService.createOrder(description, outTradeNo, amount);
            return ResultUtil.success(payParams);
            // 将 payParams 返回给你的APP前端,前端使用这些参数调起微信支付SDK
            // return ResponseEntity.ok(payParams); // 示例
        } catch (Exception e) {
            // 处理异常
            // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("下单失败");
        }
        return ResultUtil.success();
    }
}
cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardStudentVO.java
New file
@@ -0,0 +1,23 @@
package com.dsh.activity.model.response;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "根据学员id展示惠民卡二维码VO")
public class HuiminCardStudentVO {
    @ApiModelProperty("有效期至")
    private String endTime;
    @ApiModelProperty("二维码规则")
    private String qrCode;
    @ApiModelProperty("学员名称")
    private String studentName;
}
cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardVO.java
@@ -14,10 +14,18 @@
    @ApiModelProperty("惠民卡id")
    private Integer id;
    @ApiModelProperty("惠民卡名称")
    private String huiminName;
    @ApiModelProperty("封面图")
    private String cover;
    @ApiModelProperty("是否购买 0否1是")
    private Integer isBuy;
    @ApiModelProperty("二维码规则")
    private String qrCode;
    @ApiModelProperty("有效期")
    private String endTime;
    @ApiModelProperty("学员姓名")
    private String studentName;
cloud-server-activity/src/main/java/com/dsh/activity/model/response/MyHuiminCardDetailVO.java
@@ -30,6 +30,8 @@
    private List<TStudent> studentList;
    @ApiModelProperty("惠民卡")
    private THuiminCard huiminCard;
    @ApiModelProperty("购买使用者名称")
    private String studentName;
}
cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java
@@ -61,8 +61,9 @@
    private String key = "6f5e0c2dcabfa9c27b5da5836a362fef";//微信商户号
//    private String callbackPath = "https://online.daowepark.com:443/activity";//支付回调网关地址
    private String callbackPath = "http://vbef9arg13uu.guyubao.com/activity";//支付回调网关地址
    private String callbackPath = "https://online.daowepark.com:443/activity";//支付回调网关地址
//    private String callbackPath = "http://vbef9arg13uu.guyubao.com/activity";//支付回调网关地址
    private String app_cert_path = "C:/cert/alipay/user/app_cert_path.crt";//应用公钥证书路径
@@ -70,8 +71,8 @@
    private String alipay_root_cert_path = "C:/cert/alipay/user/alipay_root_cert_path.crt";//支付宝CA根证书文件路径
//    private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书
    private String certPath = "D:/apiclient_cert.p12";//微信证书
    private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书
//    private String certPath = "D:/apiclient_cert.p12";//微信证书
    public ResultUtil confirm(String smid,String code, String outTradeNo, String amount) {
        AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",
@@ -407,6 +408,7 @@
        Map<String, Object> map = new HashMap<>();
        map.put("appid", appid);
        map.put("mch_id", mchId);
        map.put("sub_mch_id", "123456");
        map.put("nonce_str", nonce_str);
        String temp = "";
        if (body.split("-").length>1){
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java
New file
@@ -0,0 +1,32 @@
package com.dsh.activity.util.wx;
/**
 * 常量
 */
public class WXPayConstants {
    public static final String DOMAIN_API = "https://api.mch.weixin.qq.com";
    //app下单
    public static final String PAY_TRANSACTIONS_APP = "/v3/pay/partner/transactions/app";
//    public static final String PAY_TRANSACTIONS_APP = "/v3/pay/transactions/app";
    //微信支付回调
    public static final String WECHAT_PAY_NOTIFY_URL =
    "https://xxx.xxxx.com/deal/api/appPayment/weChatPayNotify";
    //申请退款
    public static final String REFUND_DOMESTIC_REFUNDS      = "/refund/domestic/refunds";
    //微信退款回调
    public static final String WECHAT_REFUNDS_NOTIFY_URL = "https://xxx.xxxx.com/api/appPayment/weChatPayRefundsNotify";
    //关闭订单
    public static final String PAY_TRANSACTIONS_OUT_TRADE_NO   = "/pay/transactions/out-trade-no/{}/close";
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java
New file
@@ -0,0 +1,298 @@
package com.dsh.activity.util.wx;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.SneakyThrows;
import org.apache.http.impl.client.CloseableHttpClient;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
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.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
 * 微信支付V3 API 签名验证工具类
 */
public class WXPaySignatureCertificateUtil {
    // 保存微信平台证书
    private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>();
    // 缓存公钥,避免频繁IO操作
    private static PublicKey cachedPublicKey = null;
    // 缓存私钥,避免频繁IO操作
    private static PrivateKey cachedPrivateKey = null;
    /**
     * 获取HTTP客户端,用于微信支付API请求
     */
    public static CloseableHttpClient getWechatPayClient() throws IOException {
        PrivateKey merchantPrivateKey = getPrivateKey();
        // 构建支付客户端
        return WechatPayHttpClientBuilder.create()
                .withMerchant(WxV3PayConfig.Mch_ID, WxV3PayConfig.mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(getVerifier()))
                .build();
    }
    /**
     * 获取自动更新的证书验证器
     */
    public static AutoUpdateCertificatesVerifier getVerifier() {
        String mchSerialNo = WxV3PayConfig.mchSerialNo;
        AutoUpdateCertificatesVerifier verifier = verifierMap.get(mchSerialNo);
        if (verifier == null) {
            synchronized (WXPaySignatureCertificateUtil.class) {
                verifier = verifierMap.get(mchSerialNo);
                if (verifier == null) {
                    try {
                        PrivateKey privateKey = getPrivateKey();
                        PrivateKeySigner signer = new PrivateKeySigner(mchSerialNo, privateKey);
                        WechatPay2Credentials credentials = new WechatPay2Credentials(WxV3PayConfig.Mch_ID, signer);
                        verifier = new AutoUpdateCertificatesVerifier(
                                credentials, WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
                        verifierMap.put(mchSerialNo, verifier);
                    } catch (Exception e) {
                        throw new RuntimeException("初始化证书验证器失败", e);
                    }
                }
            }
        }
        return verifier;
    }
    /**
     * 使用商户私钥对数据进行签名
     *
     * @param message 待签名数据
     * @return Base64编码的签名结果
     */
    public static String sign(String message) {
        try {
            PrivateKey privateKey = getPrivateKey();
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(privateKey);
            signature.update(message.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(signature.sign());
        } catch (Exception e) {
            throw new RuntimeException("签名失败", e);
        }
    }
    /**
     * 使用微信支付平台公钥验证签名
     *
     * @param message 原始消息
     * @param signature Base64编码的签名
     * @return 验证结果
     */
    public static boolean verifySign(String message, String signature) {
        try {
            PublicKey publicKey = getWechatPublicKey();
            Signature sig = Signature.getInstance("SHA256withRSA");
            sig.initVerify(publicKey);
            sig.update(message.getBytes(StandardCharsets.UTF_8));
            return sig.verify(Base64.getDecoder().decode(signature));
        } catch (Exception e) {
            throw new RuntimeException("验签失败", e);
        }
    }
    /**
     * 验证微信支付通知消息的签名
     *
     * @param request HTTP请求
     * @param body 请求体内容
     * @return 验证结果
     */
    public static boolean verifyNotify(HttpServletRequest request, String body) {
        try {
            // 获取必要的HTTP头信息
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");
            // 拼接验签字符串
            String message = Stream.of(timestamp, nonce, body)
                    .collect(Collectors.joining("\n", "", "\n"));
            // 使用自动更新的证书验证器进行验证
            return getVerifier().verify(serialNo, message.getBytes(StandardCharsets.UTF_8), signature);
        } catch (Exception e) {
            throw new RuntimeException("验证微信支付通知签名失败", e);
        }
    }
    /**
     * APP支付签名
     */
    public static String appPaySign(String timestamp, String nonceStr, String prepayId) {
        String message = Stream.of(WxV3PayConfig.APP_ID, timestamp, nonceStr, prepayId)
                .collect(Collectors.joining("\n", "", "\n"));
        return sign(message);
    }
    /**
     * JSAPI支付签名
     */
    public static String jsApiPaySign(String timestamp, String nonceStr, String prepayId) {
        String message = Stream.of(WxV3PayConfig.APP_ID, timestamp, nonceStr, "prepay_id="+prepayId)
                .collect(Collectors.joining("\n", "", "\n"));
        return sign(message);
    }
    /**
     * 构建App支付参数
     */
    public static Map<String, String> buildAppPayParams(String prepayId) {
        try {
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = generateNonceStr();
            String sign = appPaySign(timestamp, nonceStr, prepayId);
            Map<String, String> params = new HashMap<>();
            params.put("appid", WxV3PayConfig.APP_ID);
            params.put("partnerid", WxV3PayConfig.Mch_ID);
            params.put("prepayid", prepayId);
            params.put("package", "Sign=WXPay");
            params.put("noncestr", nonceStr);
            params.put("timestamp", timestamp);
            params.put("sign", sign);
            return params;
        } catch (Exception e) {
            throw new RuntimeException("构建App支付参数失败", e);
        }
    }
    /**
     * 构建JSAPI支付参数
     */
    public static Map<String, String> buildJsApiPayParams(String prepayId) {
        try {
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = generateNonceStr();
            String sign = jsApiPaySign(timestamp, nonceStr, prepayId);
            Map<String, String> params = new HashMap<>();
            params.put("appId", WxV3PayConfig.APP_ID);
            params.put("timeStamp", timestamp);
            params.put("nonceStr", nonceStr);
            params.put("package", "prepay_id=" + prepayId);
            params.put("signType", "RSA");
            params.put("paySign", sign);
            return params;
        } catch (Exception e) {
            throw new RuntimeException("构建JSAPI支付参数失败", e);
        }
    }
    /**
     * 生成随机字符串
     */
    private static String generateNonceStr() {
        return java.util.UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
    /**
     * 获取商户私钥
     */
    public static PrivateKey getPrivateKey() {
        if (cachedPrivateKey != null) {
            return cachedPrivateKey;
        }
        try {
            String filePath = "D:\\玩湃v3参数\\apiclient_key.pem";
            String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            cachedPrivateKey = kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
            return cachedPrivateKey;
        } catch (IOException e) {
            throw new RuntimeException("读取私钥文件失败", e);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式", e);
        }
    }
    /**
     * 获取微信支付平台公钥
     */
    public static PublicKey getWechatPublicKey() {
        if (cachedPublicKey != null) {
            return cachedPublicKey;
        }
        try {
            // 使用验证器获取最新的证书
            X509Certificate certificate = getVerifier().getValidCertificate();
            cachedPublicKey = certificate.getPublicKey();
            return cachedPublicKey;
        } catch (Exception e) {
            throw new RuntimeException("获取微信平台公钥失败", e);
        }
    }
    /**
     * 从PEM格式文件加载公钥
     */
    public static PublicKey loadPublicKeyFromFile(String filePath) {
        try {
            String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
            String publicKeyPEM = content
                    .replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "")
                    .replaceAll("\\s+", "");
            byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
        } catch (Exception e) {
            throw new RuntimeException("加载公钥文件失败", e);
        }
    }
    /**
     * 从证书文件加载公钥
     */
    public static PublicKey loadPublicKeyFromCert(String filePath) {
        try (FileInputStream inputStream = new FileInputStream(filePath)) {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream);
            return cert.getPublicKey();
        } catch (Exception e) {
            throw new RuntimeException("加载证书文件失败", e);
        }
    }
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatPaymentServiceImpl.java
New file
@@ -0,0 +1,315 @@
//package com.dsh.activity.util.wx;/*
//*
//*改了七八遍 照顾找包困难的朋友吧
//*
//*/
//import cn.hutool.core.text.StrFormatter;
//import com.alibaba.fastjson.JSONObject;
//import com.fasterxml.jackson.databind.ObjectMapper;
//import com.fasterxml.jackson.databind.node.ObjectNode;
//import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
//import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
//import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
//import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
//import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
//import org.apache.commons.lang3.RandomStringUtils;
//import org.apache.http.client.methods.CloseableHttpResponse;
//import org.apache.http.client.methods.HttpPost;
//import org.apache.http.entity.StringEntity;
//import org.apache.http.impl.client.CloseableHttpClient;
//import org.apache.http.util.EntityUtils;
//import org.springframework.stereotype.Service;
//
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import java.io.BufferedReader;
//import java.io.ByteArrayOutputStream;
//import java.math.BigDecimal;
//import java.nio.charset.StandardCharsets;
//import java.util.HashMap;
//import java.util.Map;
//
//@Service
//public class WeChatPaymentServiceImpl implements WechatPaymentService {
//
//
//
// /**
//     * V3微信支付统一下单
//     *
//     * @return
//     *
//     */
//    @Override
//    public Map<String, Object>weChatDoUnifiedOrder() {
//        Map<String,Object> map =new HashMap<>();
//        //支付总金额
//        BigDecimal totalPrice = BigDecimal.ZERO;
//        totalPrice = totalPrice.add(BigDecimal.valueOf(600));
//        //转换金额保留两位小数点
//        Integer money=new BigDecimal(String.valueOf(totalPrice)).movePointRight(2).intValue();
//            try {
//                CloseableHttpClient httpClient = WXPaySignatureCertificateUtil.checkSign();
//
//                //app下单
//                HttpPost httpPost = new HttpPost(WXPayConstants.DOMAIN_API+WXPayConstants.PAY_TRANSACTIONS_APP);
//                httpPost.addHeader("Accept", "application/json");
//                httpPost.addHeader("Content-type", "application/json; charset=utf-8");
//                ByteArrayOutputStream bos = new ByteArrayOutputStream();
//                ObjectMapper objectMapper = new ObjectMapper();
//                ObjectNode rootNode = objectMapper.createObjectNode();
//                rootNode.put("mchid", "商户id")
//                        .put("appid", "APPID")
//                        .put("description","描述")
//                        .put("notify_url", WXPayConstants.WECHAT_PAY_NOTIFY_URL)//回调
//                        .put("out_trade_no", "订单号");
//                rootNode.putObject("amount")
//                        .put("total","总金额");
//                objectMapper.writeValue(bos, rootNode);
//                httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
//                //完成签名并执行请求
//                CloseableHttpResponse response = httpClient.execute(httpPost);
//                //获取返回状态
//                int statusCode = response.getStatusLine().getStatusCode();
//                if (statusCode == 200) { //处理成功
//                    String result = EntityUtils.toString(response.getEntity(), "UTF-8");
//                    JSONObject object = JSONObject.parseObject(result);
//                    //获取预付单
//                    String prepayId = object.getString("prepay_id");
//                    //生成签名
//                    Long timestamp = System.currentTimeMillis() / 1000;
//                    //随机字符串 这个是微信支付maven自带的 也可以用其它的
//                    //这个是v2支付依赖自带的工具包 可以去掉了
//                    //String nonceStr = WXPayUtil.generateNonceStr();
//                    //该方法org.apache.commons.lang3.RandomStringUtils依赖自带随机生成字符串 RandomStringUtils.randomAlphanumeric(32) 代表生成32位
//                    String nonceStr = RandomStringUtils.randomAlphanumeric(32);
//                    //生成带签名支付信息
//                    String paySign = WXPaySignatureCertificateUtil.appPaySign(String.valueOf(timestamp), nonceStr, prepayId);
//                    Map<String, String> param = new HashMap<>();
//                    param.put("appid", WxV3PayConfig.APP_ID);
//                    param.put("partnerid", WxV3PayConfig.Mch_ID);
//                    param.put("prepayid", prepayId);
//                    param.put("package", "Sign=WXPay");
//                    param.put("noncestr", nonceStr);
//                    param.put("timestamp", String.valueOf(timestamp));
//                    param.put("sign", paySign);
//                    map.put("code",200);
//                    map.put("message", "下单成功");
//                    map.put("data", param);
//                    return map;
//                }
//                    map.put("code",200);
//                    map.put("message", "下单失败");
//                    map.put("data", response);
//                    return map;
//            } catch (Exception e) {
//                e.printStackTrace();
//            }
//            return null;
//    }
//
//
//    /**
//     * 微信支付回调通知
//     * @return
//     */
//    @Override
//    public Map<String, Object> weChatNotificationHandler(HttpServletRequest request, HttpServletResponse response){
//        Map<String,Object> map = new HashMap<>();
//        try {
//            BufferedReader br = request.getReader();
//            String str = null;
//            StringBuilder sb = new StringBuilder();
//            while ((str = br.readLine())!=null) {
//                sb.append(str);
//            }
//            // 构建request,传入必要参数
//            NotificationRequest requests = new NotificationRequest.Builder()
//                    .withSerialNumber(request.getHeader("Wechatpay-Serial"))
//                    .withNonce(request.getHeader("Wechatpay-Nonce"))
//                    .withTimestamp(request.getHeader("Wechatpay-Timestamp"))
//                    .withSignature(request.getHeader("Wechatpay-Signature"))
//                    .withBody(String.valueOf(sb))
//                    .build();
//            //验签
//            NotificationHandler handler = new NotificationHandler(WXPaySignatureCertificateUtil.getVerifier(), WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
//            //解析请求体
//            Notification notification = handler.parse(requests);
//            String decryptData = notification.getDecryptData();
//            //解析
//            JSONObject jsonObject = JSONObject.parseObject(decryptData);
//            //支付状态交易状态,枚举值: SUCCESS:支付成功 REFUND:转入退款 NOTPAY:未支付 CLOSED:已关闭 REVOKED:已撤销(付款码支付)
//            // USERPAYING:用户支付中(付款码支付) PAYERROR:支付失败(其他原因,如银行返回失败)
//            String trade_state = String.valueOf(jsonObject.get("trade_state"));
//            if (trade_state.equals("SUCCESS")) {
//                //订单号
//                String orderNumber = String.valueOf(jsonObject.get("out_trade_no"));
//                //微信支付微信生成的订单号
//                String transactionId = String.valueOf(jsonObject.get("transaction_id"));
//                //省略查询订单
//                //此处处理业务
//                    map.put("code","SUCCESS");
//                    map.put("message","成功");
//                    //消息推送成功
//                    return map;
//                }
//                map.put("code","RESOURCE_NOT_EXISTS");
//                map.put("message", "订单不存在");
//                return map;
//            }catch (Exception e) {
//            e.printStackTrace();
//        }
//        map.put("code","FAIL");
//        map.put("message", "失败");
//        return map;
//    }
///**
//     * 关闭订单
//     * @param outTradeNo 订单号
//     * @return
//     */
//    @Override
//    public Map<String, Object> closeOrder(String outTradeNo) {
//        Map<String,Object> map = new HashMap<>();
//        try {
//            //验证证书
//                CloseableHttpClient httpClient = WXPaySignatureCertificateUtil.checkSign();
//            //关闭订单
//            String url = StrFormatter.format(WXPayConstants.DOMAIN_API+WXPayConstants.PAY_TRANSACTIONS_OUT_TRADE_NO, outTradeNo);
//            HttpPost httpPost = new HttpPost(url);
//            httpPost.addHeader("Accept", "application/json");
//            httpPost.addHeader("Content-type", "application/json; charset=utf-8");
//            ByteArrayOutputStream bos = new ByteArrayOutputStream();
//            //2.添加商户id
//            ObjectMapper objectMapper = new ObjectMapper();
//            ObjectNode rootNode = objectMapper.createObjectNode();
//            rootNode.put("mchid", WxV3PayConfig.Mch_ID);
//            objectMapper.writeValue(bos, rootNode);
//            //3.调起微信关单接口
//            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
//            //完成签名并执行请求
//            CloseableHttpResponse response = httpClient.execute(httpPost);
//            System.out.println(response.getStatusLine().getStatusCode() == 204);
//            //无数据(Http状态码为204) 微信返回结果无数据 状态码为204 成功
//            if (response.getStatusLine().getStatusCode() == 204) {
//
//                //code 退款码请前往微信支付文档查询
//                map.put("code",200);
//                map.put("message", "关闭订单成功!");
//                return map;
//            }
//        } catch (Exception e) {
//        }
//        return null;
//    }
//
// /**
//     * 微信退款
//     * @param outTradeNo 订单号
//     * @return
//     */
//    @Override
//    public Map<String, Object> weChatRefunds(String outTradeNo) {
//        Map<String,Object> map = new HashMap<>();
//        //退款总金额
//        BigDecimal totalPrice = BigDecimal.ZERO;
//        totalPrice = totalPrice.add(BigDecimal.valueOf(600));
//        //转换金额
//        Integer money=new BigDecimal(String.valueOf(totalPrice)).movePointRight(2).intValue();
//
//        try {
//            //验证证书
//             CloseableHttpClient httpClient = WXPaySignatureCertificateUtil.checkSign();
//            //申请退款接口
//            HttpPost httpPost = new HttpPost(WXPayConstants.DOMAIN_API+WXPayConstants.REFUND_DOMESTIC_REFUNDS);
//            httpPost.addHeader("Accept", "application/json");
//            httpPost.addHeader("Content-type","application/json; charset=utf-8");
//            ByteArrayOutputStream bos = new ByteArrayOutputStream();
//            ObjectMapper objectMapper = new ObjectMapper();
//            ObjectNode rootNode = objectMapper.createObjectNode();
//            //微信支付订单号
//            rootNode.put("transaction_id", "微信支付订单号")
//                    //退款订单号
//                    .put("out_refund_no","生成退款订单号")
//                    .put("notify_url","退款回调");
//                    //退款金额
//            rootNode.putObject("amount")
//                    .put("refund", "100.00")
//                    //原订单金额
//                    .put("total", "100.00")
//                    .put("currency","CNY");
//            objectMapper.writeValue(bos, rootNode);
//            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
//            CloseableHttpResponse response = httpClient.execute(httpPost);
//            //退款成功返回消息
//            String bodyAsString = EntityUtils.toString(response.getEntity());
//            JSONObject jsonObject = JSONObject.parseObject(bodyAsString);
//            if (jsonObject.get("status").equals("SUCCESS") || jsonObject.get("status").equals("PROCESSING")) {
//                //code返回
//                map.put("code",200);
//                map.put("message", "退款成功");
//                return map;
//            }
//        }catch (Exception e) {
//            e.printStackTrace();
//        }
//        map.put("code",500);
//        map.put("message", "申请退款失败!");
////        map.put("data", jsonObject);
//        return map;
//    }
//
//    /**
//     * 申请退款回调
//     * @param request
//     * @return
//     */
//    @Override
//    public Map<String,Object> weChatPayRefundsNotify(HttpServletRequest request) {
//        Map<String,Object> map = new HashMap<>();
//        try {
//            BufferedReader br = request.getReader();
//            String str = null;
//            StringBuilder sb = new StringBuilder();
//            while ((str = br.readLine())!=null) {
//                sb.append(str);
//            }
//            // 构建request,传入必要参数
//            NotificationRequest requests = new NotificationRequest.Builder()
//                    .withSerialNumber(request.getHeader("Wechatpay-Serial"))
//                    .withNonce(request.getHeader("Wechatpay-Nonce"))
//                    .withTimestamp(request.getHeader("Wechatpay-Timestamp"))
//                    .withSignature(request.getHeader("Wechatpay-Signature"))
//                    .withBody(String.valueOf(sb))
//                    .build();
//            //验签
//            NotificationHandler handler = new NotificationHandler(WXPaySignatureCertificateUtil.getVerifier(), WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
//            //解析请求体
//            Notification notification = handler.parse(requests);
//            String decryptData = notification.getDecryptData();
//            //解析
//            JSONObject jsonObject = JSONObject.parseObject(decryptData);
//            String refund_status = String.valueOf(jsonObject.get("refund_status"));
//            if (refund_status.equals("SUCCESS")) {
//                //订单号
//                String orderNumber = String.valueOf(jsonObject.get("out_trade_no"));
//                //微信支付订单号
//                String transactionId = String.valueOf(jsonObject.get("transaction_id"));
//
//                //这里是处理业务逻辑
//
//
//                //code 退款码请前往微信支付文档查询
//                map.put("code","RESOURCE_NOT_EXISTS");
//                map.put("message", "订单不存在");
//                return map;
//            }
//        }catch (Exception e) {
//            e.printStackTrace();
//        }
//        map.put("code","USER_ACCOUNT_ABNORMAL");
//        map.put("message", "退款请求失败");
//        return map;
//    }
//
//}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WechatPaymentService.java
New file
@@ -0,0 +1,56 @@
//package com.dsh.activity.util.wx;
//
//import javax.servlet.http.HttpServletRequest;
//import javax.servlet.http.HttpServletResponse;
//import java.util.Map;
//
///**
// * @author 影子
// */
//public interface WechatPaymentService
//{
//
//
//    /**
//     * 微信商品支付
//     * @return
//     */
//    public Map<String, Object> weChatDoUnifiedOrder() throws Exception;
//
//
//
//
////    /**
////     * 微信支付回调通知
////     * @param
////     * @return
////     */
////    public Map<String, Object> weChatNotificationHandler(HttpServletRequest request, HttpServletResponse response);
////
////  /**
////     *微信关闭订单
////     * @param outTradeNo
////     * @return
////     */
////    public Map<String, Object> closeOrder(String outTradeNo);
////
////
////
////
////    /**
////     * 申请退款
////     * @param
////     * @return
////     */
////    public Map<String, Object> weChatPayRefundsNotify(HttpServletRequest request);
////
////
////
////
////    /**
////     * 微信退款
////     * @param outTradeNo 订单号
////     * @return
////     */
////    public Map<String, Object> weChatRefunds(String outTradeNo);
//}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java
New file
@@ -0,0 +1,221 @@
package com.dsh.activity.util.wx;
import com.dsh.activity.util.wx.WXPayConstants;
import com.dsh.activity.util.wx.WXPaySignatureCertificateUtil;
import com.dsh.activity.util.wx.WxV3PayConfig;
import com.fasterxml.jackson.databind.JsonNode; // 引入Jackson库处理JSON
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Service
public class WxAppPayService {
    private static final Logger log = LoggerFactory.getLogger(WxAppPayService.class);
    private final ObjectMapper objectMapper; // 用于JSON序列化和反序列化
    private final CloseableHttpClient wechatPayClient; // 注入通过工具类创建的HTTP客户端
    @Autowired
    public WxAppPayService(ObjectMapper objectMapper) throws IOException {
        this.objectMapper = objectMapper;
        // 在构造函数中初始化带有签名验证功能的HTTP客户端
        this.wechatPayClient = WXPaySignatureCertificateUtil.getWechatPayClient();
        log.info("微信支付V3 HTTP客户端初始化完成。");
    }
    /**
     * 创建APP支付预付单 (统一下单)
     *
     * @param description 商品描述
     * @param outTradeNo  商户订单号 (要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一)
     * @param totalAmount 支付总金额 (单位:元)
     * @return 用于调起APP支付的参数 Map (appid, partnerid, prepayid, package, noncestr, timestamp, sign)
     * @throws IOException          网络或IO异常
     * @throws RuntimeException     支付请求失败或处理异常
     */
    public Map<String, String> createOrder(String description, String outTradeNo, BigDecimal totalAmount)
            throws IOException {
        // 1. 构建请求URL
        String url = WXPayConstants.DOMAIN_API + WXPayConstants.PAY_TRANSACTIONS_APP;
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        // 2. 构建请求体 JSON
        ObjectNode rootNode = objectMapper.createObjectNode();
//        rootNode.put("appid", WxV3PayConfig.APP_ID);//服务商不需要该字段
//        rootNode.put("mchid", WxV3PayConfig.Mch_ID);//服务商不需要该字段
        rootNode.put("description", description);
        rootNode.put("out_trade_no", outTradeNo);
        rootNode.put("notify_url", WXPayConstants.WECHAT_PAY_NOTIFY_URL); // 使用常量中的回调地址
        ObjectNode amountNode = objectMapper.createObjectNode();
        // 微信支付金额单位为分,需要转换
        amountNode.put("total", totalAmount.multiply(new BigDecimal("100")).intValue());
        amountNode.put("currency", "CNY"); // 货币类型,默认人民币
        rootNode.set("amount", amountNode);
        // 如果你是服务商模式,需要添加子商户信息
        rootNode.put("sp_mchid", WxV3PayConfig.Mch_ID);
        rootNode.put("sub_mchid", "123");
         rootNode.put("sp_appid", "wx41d32f362ba0f911");
        // rootNode.put("sub_appid", "子商户AppID"); // 如果子商户需要用自己的AppID拉起支付
        String requestBody = rootNode.toString();
        log.info("微信APP支付下单请求体: {}", requestBody);
        httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
        // 3. 发送请求
        try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {
            int statusCode = response.getStatusLine().getStatusCode();
            String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
            log.info("微信APP支付下单响应状态码: {}, 响应体: {}", statusCode, responseBody);
            if (statusCode == 200) { // HTTP状态码 200 表示成功
                JsonNode responseNode = objectMapper.readTree(responseBody);
                String prepayId = responseNode.get("prepay_id").asText();
                log.info("成功获取预支付交易会话标识 (prepay_id): {}", prepayId);
                // 4. 生成调起支付所需的参数
                Map<String, String> payParams = WXPaySignatureCertificateUtil.buildAppPayParams(prepayId);
                log.info("生成APP支付参数: {}", payParams);
                return payParams;
            } else {
                // 处理错误情况
                log.error("微信APP支付下单失败,状态码: {}, 响应: {}", statusCode, responseBody);
                // 可以根据 responseBody 中的 code 和 message 进一步分析错误
                throw new RuntimeException("微信APP支付下单失败: " + responseBody);
            }
        } catch (Exception e) {
            log.error("微信APP支付下单请求执行异常", e);
            throw new IOException("微信APP支付下单请求执行异常", e);
        }
    }
     /**
     * 关闭订单 (根据商户订单号)
     *
     * @param outTradeNo 商户订单号
     * @return true 如果关闭成功或订单已关闭/不存在 (根据微信文档,成功是204 No Content)
     * @throws IOException          网络或IO异常
     * @throws RuntimeException     关闭请求失败或处理异常
     */
    public boolean closeOrder(String outTradeNo) throws IOException {
        // 1. 构建请求URL, 注意替换占位符
        String url = WXPayConstants.DOMAIN_API
                   + WXPayConstants.PAY_TRANSACTIONS_OUT_TRADE_NO.replace("{}", outTradeNo);
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        // 2. 构建请求体 JSON (只需要商户号)
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", WxV3PayConfig.Mch_ID);
        // 如果是服务商模式,则用 sp_mchid
         rootNode.put("sp_mchid", WxV3PayConfig.Mch_ID);
        String requestBody = rootNode.toString();
        log.info("微信关单请求 URL: {}, 请求体: {}", url, requestBody);
        httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
        // 3. 发送请求
        try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {
            int statusCode = response.getStatusLine().getStatusCode();
            // 关单成功时,微信返回 HTTP 状态码 204 No Content
            log.info("微信关单响应状态码: {}", statusCode);
            if (statusCode == 204) {
                log.info("订单 {} 关闭成功。", outTradeNo);
                return true;
            } else {
                String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
                log.error("微信关单失败,商户订单号: {}, 状态码: {}, 响应: {}", outTradeNo, statusCode, responseBody);
                // 根据需要可以解析responseBody获取错误详情
                return false; // 或者抛出异常,根据业务决定
            }
        } catch (Exception e) {
            log.error("微信关单请求执行异常, 商户订单号: {}", outTradeNo, e);
            throw new IOException("微信关单请求执行异常", e);
        }
    }
    // --- 退款相关方法 (可选) ---
    /**
     * 申请退款
     *
     * @param outTradeNo   原商户订单号
     * @param outRefundNo  商户退款单号 (商户系统内部的退款单号,要求唯一)
     * @param reason       退款原因 (可选)
     * @param refundAmount 退款金额 (单位:元)
     * @param totalAmount  原订单总金额 (单位:元)
     * @return 退款处理结果,可以返回微信返回的JSON节点或自定义对象
     * @throws IOException          网络或IO异常
     * @throws RuntimeException     退款请求失败或处理异常
     */
    public JsonNode createRefund(String outTradeNo, String outRefundNo, String reason,
                                BigDecimal refundAmount, BigDecimal totalAmount) throws IOException {
        String url = WXPayConstants.DOMAIN_API + WXPayConstants.REFUND_DOMESTIC_REFUNDS;
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
        ObjectNode rootNode = objectMapper.createObjectNode();
        // 如果是服务商模式,需要添加子商户号
        // rootNode.put("sub_mchid", "子商户号");
        rootNode.put("out_trade_no", outTradeNo);
        rootNode.put("out_refund_no", outRefundNo);
        if (reason != null && !reason.isEmpty()) {
            rootNode.put("reason", reason);
        }
        // 设置退款回调地址
        rootNode.put("notify_url", WXPayConstants.WECHAT_REFUNDS_NOTIFY_URL);
        ObjectNode amountNode = objectMapper.createObjectNode();
        amountNode.put("refund", refundAmount.multiply(new BigDecimal("100")).intValue()); // 退款金额,分
        amountNode.put("total", totalAmount.multiply(new BigDecimal("100")).intValue());   // 原订单金额,分
        amountNode.put("currency", "CNY");
        rootNode.set("amount", amountNode);
        String requestBody = rootNode.toString();
        log.info("微信申请退款请求体: {}", requestBody);
        httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
        try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) {
            int statusCode = response.getStatusLine().getStatusCode();
            String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
            log.info("微信申请退款响应状态码: {}, 响应体: {}", statusCode, responseBody);
            if (statusCode == 200) { // 成功受理
                log.info("微信退款申请成功受理。");
                return objectMapper.readTree(responseBody);
            } else {
                log.error("微信申请退款失败,状态码: {}, 响应: {}", statusCode, responseBody);
                throw new RuntimeException("微信申请退款失败: " + responseBody);
            }
        } catch (Exception e) {
            log.error("微信申请退款请求执行异常", e);
            throw new IOException("微信申请退款请求执行异常", e);
        }
    }
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java
New file
@@ -0,0 +1,57 @@
package com.dsh.activity.util.wx;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
 * 微信支付V3 AES解密工具类 (AEAD_AES_256_GCM)
 */
public class WxPayAesUtil {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128; // GCM认证标签长度,固定为128位
    private static final int NONCE_LENGTH_BYTE = 12; // GCM随机串长度,固定为12字节
    private static final String TRANSFORMATION = "AES/GCM/NoPadding"; // 算法/模式/填充
    /**
     * 解密微信支付回调通知中的加密信息
     *
     * @param apiV3Key      APIv3密钥 (来自 WxV3PayConfig)
     * @param associatedData 附加数据 (resource.associated_data)
     * @param nonce         随机串 (resource.nonce)
     * @param ciphertext    密文 (resource.ciphertext),Base64编码
     * @return 解密后的明文字符串
     * @throws GeneralSecurityException 解密失败时抛出异常
     */
    public static String decrypt(String apiV3Key, String associatedData, String nonce, String ciphertext)
            throws GeneralSecurityException {
        try {
            SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce.getBytes(StandardCharsets.UTF_8));
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8)); // 设置附加认证数据
            byte[] decodedCiphertext = Base64.getDecoder().decode(ciphertext);
            byte[] decryptedBytes = cipher.doFinal(decodedCiphertext);
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException("当前JDK环境不支持AEAD_AES_256_GCM", e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException("无效的密钥或GCM参数", e);
        } catch (Exception e) {
            // 包括 BadPaddingException 等解密失败的情况
            throw new GeneralSecurityException("AES/GCM 解密失败", e);
        }
    }
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java
New file
@@ -0,0 +1,177 @@
package com.dsh.activity.util.wx;
import com.dsh.activity.util.wx.WXPaySignatureCertificateUtil;
import com.dsh.activity.util.wx.WxPayAesUtil;
import com.dsh.activity.util.wx.WxV3PayConfig;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/appPayment") // 路径前缀,与常量中配置的回调URL匹配
public class WxPayNotifyController {
    private static final Logger log = LoggerFactory.getLogger(WxPayNotifyController.class);
    private final ObjectMapper objectMapper;
    @Autowired
    public WxPayNotifyController(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
    /**
     * 接收微信支付结果通知
     * URL需要与 WXPayConstants.WECHAT_PAY_NOTIFY_URL 匹配
     */
    @PostMapping("/weChatPayNotify")
    public ResponseEntity<Map<String, String>> handleWeChatPayNotify(HttpServletRequest request, @RequestBody String requestBody) {
        Map<String, String> responseMap = new HashMap<>();
        try {
            // 1. 验证签名 (使用工具类,它会处理证书)
            boolean verifyResult = WXPaySignatureCertificateUtil.verifyNotify(request, requestBody);
            if (!verifyResult) {
                log.error("微信支付通知验签失败!");
                responseMap.put("code", "FAIL");
                responseMap.put("message", "验签失败");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST); // 返回错误状态码
            }
            log.info("微信支付通知验签成功。");
            // 2. 解析通知体
            JsonNode notifyData = objectMapper.readTree(requestBody);
            String eventType = notifyData.get("event_type").asText();
            // 只处理支付成功的通知类型
            if ("TRANSACTION.SUCCESS".equals(eventType)) {
                log.info("处理支付成功通知...");
                JsonNode resourceNode = notifyData.get("resource");
                String associatedData = resourceNode.get("associated_data").asText();
                String nonce = resourceNode.get("nonce").asText();
                String ciphertext = resourceNode.get("ciphertext").asText();
                // 3. 解密关键信息
                String decryptedDataJson = WxPayAesUtil.decrypt(WxV3PayConfig.apiV3Key, associatedData, nonce, ciphertext);
                log.info("解密后的支付通知信息: {}", decryptedDataJson);
                JsonNode decryptedData = objectMapper.readTree(decryptedDataJson);
                // 4. 处理业务逻辑
                String outTradeNo = decryptedData.get("out_trade_no").asText();
                String transactionId = decryptedData.get("transaction_id").asText();
                String tradeState = decryptedData.get("trade_state").asText();
                // ... 获取其他需要的信息,例如支付金额、用户openid等
                // TODO: 在这里实现你的业务逻辑:
                // 1. 根据 outTradeNo 查询你的数据库订单状态。
                // 2. 判断订单是否已经处理过(防止重复处理通知)。
                // 3. 如果订单未处理且 tradeState 为 SUCCESS,则更新订单状态为支付成功。
                // 4. 记录 transactionId (微信支付订单号)。
                // 5. 执行后续业务流程(如发货、增加积分等)。
                // 6. 如果处理失败,可以考虑记录日志并后续重试,但仍需返回成功给微信,避免微信重复通知。
                log.info("业务逻辑处理完成,商户订单号: {}, 微信订单号: {}", outTradeNo, transactionId);
            } else {
                log.warn("收到非支付成功类型的通知: {}", eventType);
                // 其他类型的通知,根据需要处理或忽略
            }
            // 5. 返回成功响应给微信平台
            responseMap.put("code", "SUCCESS");
            responseMap.put("message", "成功");
            return new ResponseEntity<>(responseMap, HttpStatus.OK);
        } catch (Exception e) {
            log.error("处理微信支付通知异常", e);
            responseMap.put("code", "FAIL");
            responseMap.put("message", "处理失败");
            // 即使处理失败,也尽量返回成功给微信,避免重复通知轰炸,然后在后台处理异常。
            // 但如果验签失败,可以返回错误状态码。
            return new ResponseEntity<>(responseMap, HttpStatus.INTERNAL_SERVER_ERROR); // 或者返回OK,根据策略定
        }
    }
    /**
     * 接收微信退款结果通知
     * URL需要与 WXPayConstants.WECHAT_REFUNDS_NOTIFY_URL 匹配
     */
    @PostMapping("/weChatPayRefundsNotify")
    public ResponseEntity<Map<String, String>> handleWeChatRefundsNotify(HttpServletRequest request, @RequestBody String requestBody) {
        Map<String, String> responseMap = new HashMap<>();
        try {
            // 1. 验证签名
            boolean verifyResult = WXPaySignatureCertificateUtil.verifyNotify(request, requestBody);
             if (!verifyResult) {
                log.error("微信退款通知验签失败!");
                responseMap.put("code", "FAIL");
                responseMap.put("message", "验签失败");
                return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST);
            }
            log.info("微信退款通知验签成功。");
            // 2. 解析通知体
            JsonNode notifyData = objectMapper.readTree(requestBody);
            String eventType = notifyData.get("event_type").asText();
             // 处理退款成功或异常的通知
            if ("REFUND.SUCCESS".equals(eventType) || "REFUND.ABNORMAL".equals(eventType) || "REFUND.CLOSED".equals(eventType)) {
                 log.info("处理退款通知,类型: {}", eventType);
                JsonNode resourceNode = notifyData.get("resource");
                String associatedData = resourceNode.get("associated_data").asText();
                String nonce = resourceNode.get("nonce").asText();
                String ciphertext = resourceNode.get("ciphertext").asText();
                // 3. 解密关键信息
                String decryptedDataJson = WxPayAesUtil.decrypt(WxV3PayConfig.apiV3Key, associatedData, nonce, ciphertext);
                log.info("解密后的退款通知信息: {}", decryptedDataJson);
                JsonNode decryptedData = objectMapper.readTree(decryptedDataJson);
                // 4. 处理业务逻辑
                String outTradeNo = decryptedData.get("out_trade_no").asText();
                String outRefundNo = decryptedData.get("out_refund_no").asText();
                String refundStatus = decryptedData.get("refund_status").asText(); // SUCCESS, CLOSED, ABNORMAL
                // ... 获取其他需要的信息,例如退款金额、微信退款单号 refund_id 等
                 // TODO: 在这里实现你的退款业务逻辑:
                // 1. 根据 outRefundNo 查询你的数据库退款单状态。
                // 2. 判断退款单是否已经处理过。
                // 3. 根据 refundStatus 更新退款单状态。
                // 4. 如果退款成功 (SUCCESS),可能需要执行一些操作,如返还库存、通知用户等。
                // 5. 如果退款关闭或异常,也需要记录状态。
                log.info("退款业务逻辑处理完成,商户订单号: {}, 商户退款单号: {}, 退款状态: {}", outTradeNo, outRefundNo, refundStatus);
            } else {
                log.warn("收到非退款类型的通知: {}", eventType);
            }
            // 5. 返回成功响应给微信平台
            responseMap.put("code", "SUCCESS");
            responseMap.put("message", "成功");
            return new ResponseEntity<>(responseMap, HttpStatus.OK);
        } catch (Exception e) {
            log.error("处理微信退款通知异常", e);
            responseMap.put("code", "FAIL");
            responseMap.put("message", "处理失败");
            return new ResponseEntity<>(responseMap, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java
New file
@@ -0,0 +1,56 @@
package com.dsh.activity.util.wx;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; // 如果需要静态访问
@Component
public class WxV3PayConfig {
    private String appIdValue = "wx41d32f362ba0f911";
    public static String APP_ID= "wx41d32f362ba0f911";
    private String mchIdValue= "1681873607";
    public static String Mch_ID= "1681873607";
    private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq";
    public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq";
    private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A";
    public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A";
    private String privateKeyPathValue= "D:\\玩湃v3参数\\1681873607_20250424_cert\\apiclient_key.pem";
    public static String privateKeyPath= "D:\\玩湃v3参数\\1681873607_20250424_cert\\apiclient_key.pem";
    // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量
    @PostConstruct
    public void init() {
        APP_ID = this.appIdValue;
        Mch_ID = this.mchIdValue;
        apiV3Key = this.apiV3KeyValue;
        mchSerialNo = this.mchSerialNoValue;
        privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径
        // 可以在这里加一些非空检查
        if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) {
             System.err.println("微信支付V3配置加载不完整,请检查配置文件!");
             // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施
        } else {
             System.out.println("微信支付V3配置加载完成。");
        }
    }
    // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath
    // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法:
    /*
    public static PrivateKey getPrivateKey() {
        if (cachedPrivateKey != null) {
            return cachedPrivateKey;
        }
        try {
            String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径
            // ... rest of the method ...
        } // ... catch blocks ...
    }
    */
}
cloud-server-activity/src/main/resources/mapper/HuiminCardMapper.xml
@@ -12,7 +12,6 @@
        and t1.status = 1
            order by t1.sort desc
            limit #{pageNo}, #{pageSize}
    </select>
    <select id="getHuiminAgreementAndListNolimit" resultType="com.dsh.activity.entity.THuiminCard">
@@ -31,6 +30,7 @@
            t1.status!=1
            and
            t1.appUserId = #{appUserId}
order by t1.status asc
            limit #{pageNo}, #{pageSize}
    </select>
cloud-server-activity/src/main/resources/mapper/PayHuiminMapper.xml
@@ -173,6 +173,7 @@
                    </when>
                </choose>
            </if>
        and tph.paymentTime IS NOT NULL
        </where>
        GROUP BY tph.id
        ORDER BY tph.insertTime DESC
cloud-server-activity/src/main/resources/sharding-jdbc.properties
@@ -1,22 +1,9 @@
datasource.names=master0
datasource.master0.type=com.alibaba.druid.pool.DruidDataSource
datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver
datasource.master0.url=jdbc:mysql://192.168.110.80:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
datasource.master0.username=root
datasource.master0.password=123456
datasource.master0.maxActive=20
datasource.master0.maxWait=60000
datasource.master0.minIdle=5
datasource.master0.initialSize=2
#datasource.names=master0
#datasource.master0.type=com.alibaba.druid.pool.DruidDataSource
#datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver
#datasource.master0.url=jdbc:mysql://127.0.0.1:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
#datasource.master0.url=jdbc:mysql://192.168.110.80:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
#datasource.master0.username=root
#datasource.master0.password=playpai2023!
#datasource.master0.password=123456
#datasource.master0.maxActive=20
#datasource.master0.maxWait=60000
#datasource.master0.minIdle=5
@@ -24,4 +11,17 @@
datasource.names=master0
datasource.master0.type=com.alibaba.druid.pool.DruidDataSource
datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver
datasource.master0.url=jdbc:mysql://127.0.0.1:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
datasource.master0.username=root
datasource.master0.password=playpai2023!
datasource.master0.maxActive=20
datasource.master0.maxWait=60000
datasource.master0.minIdle=5
datasource.master0.initialSize=2
cloud-server-communityWorldCup/src/main/resources/sharding-jdbc.properties
@@ -1,22 +1,9 @@
#datasource.names=master0
#datasource.master0.type=com.alibaba.druid.pool.DruidDataSource
#datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver
#datasource.master0.url=jdbc:mysql://8.137.22.229:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
#datasource.master0.username=root
#datasource.master0.password=playpai2023!
#datasource.master0.maxActive=20
#datasource.master0.maxWait=60000
#datasource.master0.minIdle=5
#datasource.master0.initialSize=2
datasource.names=master0
datasource.master0.type=com.alibaba.druid.pool.DruidDataSource
datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver
datasource.master0.url=jdbc:mysql://192.168.110.80:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
datasource.master0.url=jdbc:mysql://8.137.22.229:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
datasource.master0.username=root
datasource.master0.password=123456
datasource.master0.password=playpai2023!
datasource.master0.maxActive=20
datasource.master0.maxWait=60000
datasource.master0.minIdle=5
@@ -24,3 +11,16 @@
#datasource.names=master0
#datasource.master0.type=com.alibaba.druid.pool.DruidDataSource
#datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver
#datasource.master0.url=jdbc:mysql://192.168.110.80:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
#datasource.master0.username=root
#datasource.master0.password=123456
#datasource.master0.maxActive=20
#datasource.master0.maxWait=60000
#datasource.master0.minIdle=5
#datasource.master0.initialSize=2
cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java
@@ -67,6 +67,10 @@
        if (null != one) {
            return ResultUtil.error("电话号码重复");
        }
//        Participant two = this.getOne(new QueryWrapper<Participant>().eq("appUserId", uid).eq("idcard", addParticipant.getIdcard()).eq("state", 1));
//        if (null != two) {
//            return ResultUtil.error("身份证号码重复");
//        }
        if (ToolUtil.isNotEmpty(addParticipant.getName()) && ToolUtil.isNotEmpty(addParticipant.getIdcard())) {
            Boolean aBoolean = JuHeUtil.idcardAuthentication(addParticipant.getIdcard(), addParticipant.getName());
            if (!aBoolean) {
@@ -94,8 +98,8 @@
        tStudent.setName(addParticipant.getName());
        tStudent.setPhone(addParticipant.getPhone());
        if (ToolUtil.isNotEmpty(addParticipant.getBirthday())){
            Date date = DateUtils.parseDate(addParticipant.getBirthday());
            tStudent.setBirthday(date);
            Date parse = sdf.parse(addParticipant.getBirthday());
            tStudent.setBirthday(parse);
        }else{
                String birthDateStr = addParticipant.getIdcard().substring(6, 14);
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
@@ -187,7 +191,9 @@
            Integer integer = coursePackagePaymentClient.queryResidueClassHour(tStudent.getId());
            participantVo.setResidueClassHour(integer);
            participantVo.setPhone(tStudent.getPhone());
            participantVo.setHeight(tStudent.getHeight().intValue());
            if (tStudent.getHeight()!=null){
                participantVo.setHeight(tStudent.getHeight().intValue());
            }
            participantVo.setWeight(tStudent.getWeight());
            participantVo.setHeadImg(tStudent.getHeadImg());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");