无关风月
4 天以前 4742874ad840d7e1e3ac79dc288b38e9a642319d
bug修改
21个文件已修改
2个文件已添加
2263 ■■■■■ 已修改文件
cloud-server-account/src/main/java/com/dsh/account/controller/AppUserController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/controller/StudentController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/controller/PointMercharsController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/controller/THuiminCardController.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/entity/TPayHuimin.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/feignclient/account/StudentClient.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/service/impl/PayHuiminServiceImpl.java 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java 594 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java 444 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java 354 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java 112 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/course/feignClient/activity/HuiminCardClient.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/THuiminCardController.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/TShopController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/tHuiminCard/tHuiminCard.html 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/tHuiminCard/tHuiminCard_addOrder.html 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/tShop/TShop_add.html 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/tHuiminCard/tHuiminCard.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/tHuiminCard/tHuiminCard2.js 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/tHuiminCard/tHuiminCard3.js 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/controller/AppUserController.java
@@ -198,7 +198,8 @@
    @ResponseBody
    @GetMapping("/base/appUser/getAppUserByPhone/{phone}")
    public TAppUser getAppUserByPhone(@PathVariable("phone") String phone) {
        TAppUser appUser = appUserService.getOne(new QueryWrapper<TAppUser>().eq("phone", phone));
        TAppUser appUser = appUserService.getOne(new QueryWrapper<TAppUser>().eq("phone", phone)
                .eq("state",1));
        return appUser;
    }
cloud-server-account/src/main/java/com/dsh/account/controller/StudentController.java
@@ -108,6 +108,12 @@
            return new ArrayList<>();
        }
    }
    @PostMapping("/student/getStudentById")
    @ResponseBody
    TStudent getStudentById(Integer studentId) {
        TStudent byId = studentService.getById(studentId);
        return byId;
    }
    @ResponseBody
cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java
@@ -741,6 +741,12 @@
                return ResultUtil.tokenErr();
            }
            TPayHuimin tPayHuimin = payHuiminService.getById(id);
            if (tPayHuimin.getPaymentType()==3){
                // 后台录入直接退款
                tPayHuimin.setStatus(3);
                payHuiminService.updateById(tPayHuimin);
                return ResultUtil.success();
            }
            if (tPayHuimin == null) {
                return ResultUtil.error("订单不存在");
            }
@@ -752,7 +758,6 @@
                // 已过期
                return ResultUtil.error("玩湃惠民卡已过期,不可退款");
            }
            tPayHuimin.setStatus(4);
            payHuiminService.updateById(tPayHuimin);
            int count = huiminRecordService.list(new LambdaQueryWrapper<THuiminRecord>()
                    .eq(THuiminRecord::getPayId, tPayHuimin.getId())).size();
cloud-server-activity/src/main/java/com/dsh/activity/controller/PointMercharsController.java
@@ -1568,7 +1568,8 @@
        if (ToolUtil.isNotEmpty(pointMercharsPayedVo.getStatus())) {
            userPointsMerchandiseLambdaQueryWrapper.eq(UserPointsMerchandise::getStatus, pointMercharsPayedVo.getStatus());
        }
        List<UserPointsMerchandise> list = upmseService.list(userPointsMerchandiseLambdaQueryWrapper.orderByDesc(UserPointsMerchandise::getPaymentTime));
        List<UserPointsMerchandise> list = upmseService.list(userPointsMerchandiseLambdaQueryWrapper.orderByDesc(UserPointsMerchandise::getPaymentTime)
                .isNotNull(UserPointsMerchandise::getPaymentTime));
        System.out.println(list);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (list.size() > 0) {
cloud-server-activity/src/main/java/com/dsh/activity/controller/THuiminCardController.java
@@ -1,5 +1,6 @@
package com.dsh.activity.controller;
import cn.hutool.core.collection.CollUtil;
import cn.mb.cloud.common.data.controller.BaseController;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -7,12 +8,18 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dsh.activity.entity.THuiminCard;
import com.dsh.activity.entity.TPayHuimin;
import com.dsh.activity.entity.TStudent;
import com.dsh.activity.feignclient.account.StudentClient;
import com.dsh.activity.service.HuiminCardService;
import com.dsh.activity.service.PayHuiminService;
import com.dsh.activity.util.UUIDUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -25,6 +32,8 @@
    private HuiminCardService tHuiminCardService;
    @Autowired
    private PayHuiminService payHuiminService;
    @Autowired
    private StudentClient studentClient;
    @PostMapping("/queryPage")
@@ -155,4 +164,51 @@
        tHuiminCardService.updateById(tHuiminCard);
        return "success";
    }
    @GetMapping("/getCards")
    public List<THuiminCard> getCards(@RequestParam("storeId")String storeId){
        List<THuiminCard> list = tHuiminCardService.lambdaQuery().apply("FIND_IN_SET(" + storeId + ",storeIds)").list();
        return list;
    }
    @GetMapping("/addPayHuiMing")
    public void addPayHuiMing(@RequestParam("studentId")Integer studentId, @RequestParam("cardId")Integer cardId){
        TPayHuimin tPayHuimin = new TPayHuimin();
        THuiminCard byId = tHuiminCardService.getById(cardId);
        List<TStudent> studentByIds = studentClient.getStudentByIds(studentId + "");
        if (CollUtil.isNotEmpty(studentByIds)){
            tPayHuimin.setAppUserId(studentByIds.get(0).getAppUserId());
        }
        tPayHuimin.setSalesMoney(byId.getSalesMoney());
        tPayHuimin.setStudentId(studentId+"");
        switch (byId.getHuiMinType()) {
            case 1:
                // 年度卡
                LocalDateTime localDateTime = LocalDateTime.now().plusDays(365);
                // 将LocalDateTime转换为Date类型
                Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
                tPayHuimin.setEndTime(date);
                break;
            case 2:
                // 年内卡
                Date date1 = new Date();
                date1.setMonth(11);
                date1.setDate(31);
                date1.setHours(23);
                date1.setMinutes(59);
                date1.setSeconds(57);
                tPayHuimin.setEndTime(date1);
                break;
        }
        tPayHuimin.setInsertTime(new Date());
        tPayHuimin.setPaymentType(3);
        tPayHuimin.setRefundStatus(1);
        tPayHuimin.setCardId(byId.getId());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        tPayHuimin.setCode(sdf.format(new Date()) + UUIDUtil.getNumberRandom(5));
        System.err.println("支付数据" + tPayHuimin);
        tPayHuimin.setPaymentTime(new Date());
        tPayHuimin.setStatus(2);
        payHuiminService.save(tPayHuimin);
    }
}
cloud-server-activity/src/main/java/com/dsh/activity/entity/TPayHuimin.java
@@ -80,7 +80,7 @@
    @TableField("status")
    private Integer status;
    /**
     *付款方式1微信2支付宝
     *付款方式1微信2支付宝 3后台录入
     */
    @TableField("paymentType")
    private Integer paymentType;
cloud-server-activity/src/main/java/com/dsh/activity/feignclient/account/StudentClient.java
@@ -25,6 +25,9 @@
    @PostMapping("/student/queryStudentList")
    List<TStudent> queryStudentList(Integer appUserId);
    @PostMapping("/student/getStudentById")
    TStudent getStudentById(Integer studentId);
    /**
     * 获取有学员的用户ids
     *
cloud-server-activity/src/main/java/com/dsh/activity/service/impl/PayHuiminServiceImpl.java
@@ -19,8 +19,10 @@
import com.dsh.activity.util.DateUtil;
import com.dsh.activity.util.PayMoneyUtil;
import com.dsh.activity.util.ResultUtil;
import com.sun.org.apache.bcel.internal.generic.NEW;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
@@ -53,22 +55,44 @@
                    .eq(THuiminRecord::getPayId, record.getId()));
            record.setUseTimes(list.size());
        }
        if (CollUtil.isNotEmpty(records)){
            List<Integer> appUserIdList = records.stream().map(SalesDetailVO::getAppUserId).collect(Collectors.toList());
            List<AppUser> appUserList = appUserClient.queryAppUserBatch(appUserIdList);
            Map<Integer, AppUser> appUserMap = appUserList.stream().collect(Collectors.toMap(AppUser::getId, appUser -> appUser));
            records.forEach(item->{
                AppUser appUser = appUserMap.get(item.getAppUserId());
                if (Objects.nonNull(appUser)){
                    item.setUserName(appUser.getName());
                    item.setPhone(appUser.getPhone());
        for (SalesDetailVO record : records) {
            AppUser appUser = appUserClient.queryAppUser(record.getAppUserId());
            if (Objects.nonNull(appUser)){
                record.setUserName(appUser.getName());
                record.setPhone(appUser.getPhone());
            }
            StringBuilder stringBuilder = new StringBuilder();
            String[] split = record.getStudentId().split(",");
            for (String s : split) {
                String replace = s.replace(" ", "");
                List<TStudent> studentById = studentClient.getStudentByIds(replace);
                if (!studentById.isEmpty()){
                    stringBuilder.append(studentById.get(0).getName()+",");
                }
                List<TStudent> studentList = studentClient.getStudentByIds(item.getStudentId());
                if (CollUtil.isNotEmpty(studentList)){
                    item.setStudentName(studentList.stream().map(TStudent::getName).collect(Collectors.joining(",")));
                }
            });
            }
            String string = stringBuilder.toString();
            if (StringUtils.hasLength(string)) {
                String stringWithoutLastCharacter = string.substring(0, string.length() - 1);
                record.setStudentName(stringWithoutLastCharacter);
            }
        }
//        if (CollUtil.isNotEmpty(records)){
//            List<Integer> appUserIdList = records.stream().map(SalesDetailVO::getAppUserId).collect(Collectors.toList());
//            List<AppUser> appUserList = appUserClient.queryAppUserBatch(appUserIdList);
//            Map<Integer, AppUser> appUserMap = appUserList.stream().collect(Collectors.toMap(AppUser::getId, appUser -> appUser));
//            records.forEach(item->{
//                AppUser appUser = appUserMap.get(item.getAppUserId());
//                if (Objects.nonNull(appUser)){
//                    item.setUserName(appUser.getName());
//                    item.setPhone(appUser.getPhone());
//                }
//                List<TStudent> studentList = studentClient.getStudentByIds(item.getStudentId());
//                if (CollUtil.isNotEmpty(studentList)){
//                    item.setStudentName(studentList.stream().map(TStudent::getName).collect(Collectors.joining(",")));
//                }
//            });
//        }
        return salesDetailVOPage;
    }
@@ -81,6 +105,15 @@
    public ResultUtil<?> refund(Integer id) throws AlipayApiException {
        TPayHuimin payHuimin = this.getById(id);
        if (Objects.nonNull(payHuimin)) {
            // 后台录入订单直接修改为已退款
            if (payHuimin.getPaymentType()==3){
                payHuimin.setStatus(3);
                payHuimin.setRefundStatus(3);
                payHuimin.setRefundNumber(payHuimin.getCode());
                payHuimin.setRefundTime(new Date());
                this.updateById(payHuimin);
                return ResultUtil.success();
            }
            Integer count = huiminRecordService.lambdaQuery()
                    .eq(THuiminRecord::getPayId, payHuimin.getId())
                    .count();
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java
@@ -1,32 +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";
}
//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
@@ -1,297 +1,297 @@
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);
        }
    }
}
//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/WxAppPayService.java
@@ -1,222 +1,222 @@
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.Component;
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;
@Component
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);
        }
    }
}
//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.Component;
//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;
//
//@Component
//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
@@ -1,57 +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);
        }
    }
}
//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
@@ -1,177 +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);
        }
    }
}
//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
@@ -1,56 +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 ...
    }
    */
}
//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-management/src/main/java/com/dsh/course/feignClient/activity/HuiminCardClient.java
@@ -38,4 +38,9 @@
    @GetMapping("/tHuiminCard/changeState")
    String changeState(@RequestParam("id")Integer id, @RequestParam("status") Integer status);
    @GetMapping("/tHuiminCard/getCards")
    List<THuiminCard> getCards(@RequestParam("storeId")String storeId);
    @GetMapping("/tHuiminCard/addPayHuiMing")
    void addPayHuiMing(@RequestParam("studentId")Integer studentId, @RequestParam("cardId")Integer cardId);
}
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/THuiminCardController.java
@@ -6,6 +6,9 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dsh.course.feignClient.account.AppUserClient;
import com.dsh.course.feignClient.account.StudentClient;
import com.dsh.course.feignClient.account.model.TAppUser;
import com.dsh.course.feignClient.activity.HuiminCardClient;
import com.dsh.course.feignClient.activity.model.THuiminCard;
import com.dsh.guns.config.UserExt;
@@ -45,6 +48,11 @@
    private TOperatorService operatorService;
    @Autowired
    private ITSiteService tSiteService;
    @Autowired
    private AppUserClient appUserClient;
    @Autowired
    private StudentClient studentClient;
    /**
     * 跳转到玩湃惠民卡首页
@@ -73,7 +81,66 @@
        model.addAttribute("objectType", user.getObjectType());
        return PREFIX + "tHuiminCard_detail.html";
    }
    /**
     * 跳转到订单录入页面
     */
    @RequestMapping("/tHuiminCardAddOrder")
    public String tHuiminCardAddOrder(Model model) {
        Integer objectType = UserExt.getUser().getObjectType();
        Integer objectId = UserExt.getUser().getObjectId();
        model.addAttribute("role", objectType);
        if (objectType==1){
            List<TOperator> list = operatorService.list(new LambdaQueryWrapper<TOperator>()
                    .eq(TOperator::getState, 1));
            model.addAttribute("operators", list);
        }else if (objectType==2){
            List<TStore> list = storeService.list(new LambdaQueryWrapper<TStore>()
                    .eq(TStore::getOperatorId, objectId));
            model.addAttribute("stores", list);
        }else{
            List<THuiminCard> cards = huiminCardClient.getCards(objectId + "");
            model.addAttribute("cards", cards);
        }
        return PREFIX + "tHuiminCard_addOrder.html";
    }
    @RequestMapping("/addPayHuiMing")
    @ResponseBody
    public Object addPayHuiMing(Integer studentId,Integer cardId) {
    huiminCardClient.addPayHuiMing(studentId ,cardId);
        return 200;
    }
    @RequestMapping(value = "/checkUser")
    @ResponseBody
    public Object checkUser(String phone) {
        TAppUser appUserByPhone = appUserClient.getAppUserByPhone(phone);
        if (appUserByPhone==null){
            return 500;
        }
        List<TStudent> tStudents = studentClient.queryStudentList(appUserByPhone.getId());
        if (tStudents.isEmpty()){
            return 501;
        }
        return tStudents;
    }
    @RequestMapping(value = "/getStores")
    @ResponseBody
    public Object getStores(String operatorId) {
        if (operatorId.isEmpty()){
            return  new ArrayList<>();
        }else{
            List<TStore> list = storeService.lambdaQuery().eq(TStore::getOperatorId, operatorId).list();
            return list;
        }
    }
    @RequestMapping(value = "/getCards")
    @ResponseBody
    public Object getCards(String storeId) {
        List<THuiminCard> list = huiminCardClient.getCards(storeId);
        return list;
    }
    /**
     * 获取玩湃惠民卡详情
     */
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/TShopController.java
@@ -531,10 +531,10 @@
                        }
                    }
                }else {
                    TCity one = cityService.getOne(new LambdaQueryWrapper<TCity>().eq(TCity::getId, tStore.getProvinceCode()));
                    TCity one = cityService.getOne(new LambdaQueryWrapper<TCity>().eq(TCity::getCode, tStore.getProvinceCode()));
                    tStore.setProvince(one.getName());
                    tStore.setProvinceCode(one.getCode());
                    TCity one1 = cityService.getOne(new LambdaQueryWrapper<TCity>().eq(TCity::getId, tStore.getCityCode()));
                    TCity one1 = cityService.getOne(new LambdaQueryWrapper<TCity>().eq(TCity::getCode, tStore.getCityCode()));
                    tStore.setCity(one1.getName());
                    tStore.setCityCode(one1.getCode());
                }
cloud-server-management/src/main/webapp/WEB-INF/view/system/tHuiminCard/tHuiminCard.html
@@ -73,6 +73,10 @@
                            <i class="fa fa-remove"></i>
                            下架
                        </button>
                        <button type="button" class="btn btn-primary" v-on:click="addOrder()">
                            <i class="fa fa-remove"></i>
                            订单录入
                        </button>
                    </div>
                </div>
                <el-table
@@ -155,8 +159,9 @@
</div>
<script src="${ctxPath}/js/vue/vue.js"></script>
<script src="${ctxPath}/js/elementui/index.js"></script>
<script src="${ctxPath}/modular/system/tHuiminCard/tHuiminCard2.js"></script>
<link rel="stylesheet" href="${ctxPath}/js/elementui/index.css">
<script src="${ctxPath}/modular/system/tHuiminCard/tHuiminCard2.js"></script>
<script src="${ctxPath}/modular/system/tHuiminCard/tHuiminCard.js"></script>
<script src="${ctxPath}/modular/system/tHuiminAgreement/tHuiminAgreement.js"></script>
<script>
cloud-server-management/src/main/webapp/WEB-INF/view/system/tHuiminCard/tHuiminCard_addOrder.html
New file
@@ -0,0 +1,150 @@
@layout("/common/_container.html"){
<style>
    .avatar-uploader .el-upload {
        border: 1px dashed #d9d9d9;
        border-radius: 6px;
        cursor: pointer;
        position: relative;
        height: 100px;
        width: 100px;
        overflow: hidden;
    }
    .avatar-uploader .el-upload:hover {
        border-color: #409EFF;
    }
    .avatar-uploader-icon {
        font-size: 28px;
        color: #8c939d;
        width: 100px;
        height: 100px;
        line-height: 100px;
        margin-top: 32px;
        text-align: center;
    }
    .avatar {
        width: 100px;
        height: 100px;
        display: block;
    }
    .col-sm-12 {
        margin-top: 20px;
    }
    .col-sm-12 select {
        height: 33px;
    }
</style>
<script type="text/javascript" src="http://webapi.amap.com/maps?v=1.4.15&key=77b37f0753049c4e712ea79a24e0719c"></script>
<div class="ibox float-e-margins">
    <div class="ibox-content">
        <div class="form-horizontal" id="carInfoForm">
            <div class="form-group">
                <input hidden id="role" value="${role}">
                <div style="display:flex">
                    <label class="col-sm-3 control-label">*用户手机号:</label>
                    <input type="number" id="phone" name="type" placeholder="请输入">
                    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button type="button" class="btn btn-primary" onclick="THuiminCardInfo.checkUser()">搜索
                    </button>
                </div>
            </div>
            <div class="form-group" id="users">
                <label class="col-sm-3 control-label">*绑定人员信息:</label>
                <div class="col-sm-9">
                    <select class="form-control" id="userList" name="userList">
                    </select>
                </div>
            </div>
            @if(role==1){
            <div class="form-group">
                <label class="col-sm-3 control-label">*所属运营商:</label>
                <div class="col-sm-9">
                    <select class="form-control" id="account" name="account" onchange="THuiminCardInfo.getStores(this)">
                        <option value="">请选择运营商</option>
                        <option value="0">平台</option>
                        @for(i in operators){
                        <option value="${i.id}">${i.name}</option>
                        @}
                    </select>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-3 control-label">*可用门店:</label>
                <div class="col-sm-9">
                    <select class="form-control" id="shopId" name="shopId" onchange="THuiminCardInfo.getCards(this)">
                        <option value="">选择门店</option>
                    </select>
                </div>
            </div>
            <div class="form-group">
                <label class="col-sm-3 control-label">选择惠民卡:</label>
                <div class="col-sm-9">
                    <select class="form-control" id="cards" name="cards" >
                        <option value="">选择惠民卡</option>
                    </select>
                </div>
            </div>
            @}
            @if(role==2){
            <div class="form-group" id="shop">
                <label class="col-sm-3 control-label">所属门店:</label>
                <div class="col-sm-9">
                    <select class="form-control" id="shopId" name="shopId" onchange="THuiminCardInfo.getCards(this)" >
                        <option value="">选择门店</option>
                        @for(i in stores){
                        <option value="${i.id}">${i.name}</option>
                        @}
                    </select>
                </div>
            </div>
            <div class="form-group" id="card">
                <label class="col-sm-3 control-label">选择惠民卡:</label>
                <div class="col-sm-9">
                    <select class="form-control" id="cards" name="cards" >
                        <option value="">选择惠民卡</option>
                    </select>
                </div>
            </div>
            @}
            @if(role==3){
            <div class="form-group" id="card">
                <label class="col-sm-3 control-label">选择惠民卡:</label>
                <div class="col-sm-9">
                    <select class="form-control" id="cards" name="cards" >
                        <option value="">选择惠民卡</option>
                        @for(i in cards){
                        <option value="${i.id}">${i.name}</option>
                        @}
                    </select>
                </div>
            </div>
            @}
        </div>
    </div>
    <div class="row btn-group-m-t">
        <div class="col-sm-10 col-sm-offset-5">
            <#button btnCss="info" name="提交" id="ensure" icon="fa-check" clickFun="THuiminCardInfo.addSubmit()"/>
            <#button btnCss="danger" name="关闭" id="cancel" icon="fa-eraser" clickFun="THuiminCardInfo.close()"/>
        </div>
    </div>
</div>
<script src="${ctxPath}/modular/system/tHuiminCard/tHuiminCard3.js"></script>
<script src="${ctxPath}/js/vue/vue.js"></script>
<script src="${ctxPath}/js/elementui/index.js"></script>
<link rel="stylesheet" href="${ctxPath}/js/elementui/index.css">
@}
cloud-server-management/src/main/webapp/WEB-INF/view/system/tShop/TShop_add.html
@@ -42,8 +42,6 @@
<script type="text/javascript" src="http://webapi.amap.com/maps?v=1.4.15&key=77b37f0753049c4e712ea79a24e0719c"></script>
<div class="ibox float-e-margins">
    <div class="ibox-content">
        <div class="form-horizontal" id="carInfoForm">
            <input hidden id="role" value="${role}">
            <input hidden id="operator" value="${operator}">
cloud-server-management/src/main/webapp/static/modular/system/tHuiminCard/tHuiminCard.js
@@ -81,6 +81,18 @@
    });
    this.layerIndex = index;
};
THuiminCard.addOrder = function (id, pageType) {
    console.log(id)
    let index = layer.open({
        type: 2,
        title: '订单录入',
        area: ['100%', '100%'], //宽高
        fix: false, //不固定
        maxmin: true,
        content: Feng.ctxPath + '/tHuiminCard/tHuiminCardAddOrder'
    });
    this.layerIndex = index;
};
/**
 * 删除玩湃惠民卡
cloud-server-management/src/main/webapp/static/modular/system/tHuiminCard/tHuiminCard2.js
@@ -143,10 +143,103 @@
                id = this.ids[0]
            }
            THuiminCard.openTHuiminCardDetail(id,'detail')
        },
        addOrder() {
            THuiminCard.addOrder()
        }
    },
    created() {
        // 初始化逻辑
        this.handleSearch()
    }
});
});
var THuiminCardInfo = {
    goodsPicArray: [],
    tCarInfoData : {},
    validateFields: {
    }
};
THuiminCardInfo.close = function() {
    parent.layer.close(window.parent.THuiminCard.layerIndex);
}
THuiminCardInfo.checkUser = function (e) {
    console.log("用户手机号")
    console.log(e)
    var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/checkUser", function(data){
        if(data==500){
            $("#userList").empty()
            Feng.error("操作失败,当前用户未注册")
            return;
        }else if (data==501){
            $("#userList").empty()
            Feng.error("操作失败,当前用户未添加人员信息")
            return;
        }
        else{
            var content='<option value="">选择人员</option>';
            $.each(data, function(k,v) {
                content += "<option value='"+v.id+"'>"+v.name+"</option>";
            });
            $("#userList").empty().append(content);
            $("#shopId").empty();
            $("#cards").empty();
        }
    });
    ajax.set("phone",phone);
    ajax.start();
}
THuiminCardInfo.getStores = function (e) {
    var operatorId=$(e).val();
    var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/getStores", function(data){
        if(data!=null){
            var content='<option value="">选择门店</option>';
            $.each(data, function(k,v) {
                content += "<option value='"+v.id+"'>"+v.name+"</option>";
            });
            $("#shopId").empty().append(content);
            $("#cards").empty()
        }
    });
    ajax.set("operatorId",operatorId);
    ajax.start();
}
THuiminCardInfo.getCards = function (e) {
    var operatorId=$(e).val();
    var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/getCards", function(data){
        if(data!=null){
            var content='<option value="">选择玩湃惠民卡</option>';
            $.each(data, function(k,v) {
                content += "<option value='"+v.id+"'>"+v.name+"</option>";
            });
            $("#cards").empty().append(content);
        }
    });
    ajax.set("storeId",operatorId);
    ajax.start();
}
THuiminCardInfo.addSubmit = function () {
    let cardId = $("#cards").val()
    if(cardId==""){
        Feng.info("请选择惠民卡")
        return;
    }
    let user = $("#user").val()
    if(user==""){
        Feng.info("请选择绑定人员信息")
        return;
    }
    var operatorId=$(e).val();
    var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/addPayHuiMing", function(data){
        if(data!=null){
            var content='<option value="">选择玩湃惠民卡</option>';
            $.each(data, function(k,v) {
                content += "<option value='"+v.id+"'>"+v.name+"</option>";
            });
            $("#cards").empty().append(content);
        }
    });
    ajax.set("studentId",user);
    ajax.set("cardId",cardId);
    ajax.start();
}
cloud-server-management/src/main/webapp/static/modular/system/tHuiminCard/tHuiminCard3.js
New file
@@ -0,0 +1,98 @@
var THuiminCardInfo = {
    goodsPicArray: [],
    tCarInfoData : {},
    validateFields: {
    }
};
THuiminCardInfo.close = function() {
    parent.layer.close(window.parent.THuiminCard.layerIndex);
}
THuiminCardInfo.checkUser = function () {
    if ($("#phone").val()==""){
        Feng.error("请输入手机号")
        return
    }
    var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/checkUser", function(data){
        if(data==500){
            $("#userList").empty()
            Feng.error("操作失败,当前用户未注册")
            return;
        }else if (data==501){
            $("#userList").empty()
            Feng.error("操作失败,当前用户未添加人员信息")
            return;
        }
        else{
            var content='<option value="">选择人员</option>';
            $.each(data, function(k,v) {
                content += "<option value='"+v.id+"'>"+v.name+"</option>";
            });
            $("#userList").empty().append(content);
            $("#shopId").empty();
            $("#cards").empty();
        }
    });
    ajax.set("phone",$("#phone").val());
    ajax.start();
}
THuiminCardInfo.getStores = function (e) {
    var operatorId=$("#account").val();
    console.log("运营商id")
    console.log(operatorId)
    var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/getStores", function(data){
        if(data!=null){
            var content='<option value="">选择门店</option>';
            $.each(data, function(k,v) {
                content += "<option value='"+v.id+"'>"+v.name+"</option>";
            });
            $("#shopId").empty().append(content);
            $("#cards").empty()
        }
        if (operatorId==""){
            console.log("置空")
            $("#shopId").empty()
            $("#cards").empty()
        }
    });
    ajax.set("operatorId",operatorId);
    ajax.start();
}
THuiminCardInfo.getCards = function (e) {
    var operatorId=$("#shopId").val();
    var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/getCards", function(data){
        if(data!=null){
            var content='<option value="">选择玩湃惠民卡</option>';
            $.each(data, function(k,v) {
                content += "<option value='"+v.id+"'>"+v.huiMinName+"</option>";
            });
            $("#cards").empty().append(content);
        }
    });
    ajax.set("storeId",operatorId);
    ajax.start();
}
THuiminCardInfo.addSubmit = function () {
    let cardId = $("#cards").val()
    if(cardId==""){
        Feng.info("请选择惠民卡")
        return;
    }
    let user = $("#userList").val()
    if(user==""){
        Feng.info("请选择绑定人员信息")
        return;
    }
    var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/addPayHuiMing", function(data){
        if (data==200){
            Feng.success("录入成功")
            THuiminCardInfo.close()
            return
        }
    });
    ajax.set("studentId",user);
    ajax.set("cardId",cardId);
    ajax.start();
}