无关风月
19 小时以前 5dc40fcd64b0513150f1d8335ab849e6d8cdc28e
支付版本更新 根据资金流向使用V2或V3服务商版本支付
5个文件已删除
30个文件已添加
53个文件已修改
10264 ■■■■ 已修改文件
cloud-server-account/pom.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/controller/AppUserController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/controller/PaymentCallbackController.java 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/controller/UseBenefitsController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/feignclient/other/StoreClient.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/service/impl/RechargeRecordsServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/service/impl/TAppUserServiceImpl.java 750 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/service/impl/TStudentServiceImpl.java 100 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/service/impl/VipPaymentServiceImpl.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/util/PayMoneyUtil.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/util/wx/PartnerAppPrepay.java 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/util/wx/WXPayUtility.java 441 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/util/wx/WeChatSignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/util/wx/WeChatV3SignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/src/main/java/com/dsh/account/util/wx/WxV3PayConfig.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/pom.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminCallBack.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/controller/WeiXinV3Controller.java 145 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java 126 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/PartnerAppPrepay.java 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java 297 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayUtility.java 455 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatSignUtil.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatV3SignUtil.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java 122 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/pom.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/controller/WorldCupController.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/feignclient/other/StoreClient.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/feignclient/other/model/Store.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/service/impl/WorldCupServiceImpl.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/PayMoneyUtil.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/PartnerAppPrepay.java 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/WXPayUtility.java 441 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/WeChatSignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/WeChatV3SignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/WxV3PayConfig.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/pom.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/controller/CompetitionController.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/controller/ParticipantController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/feignclient/other/StoreClient.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/service/impl/CompetitionServiceImpl.java 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/util/PayMoneyUtil.java 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/PartnerAppPrepay.java 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/WXPayUtility.java 441 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/WeChatSignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/WeChatV3SignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/WxV3PayConfig.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/pom.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/controller/CourseController.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/controller/CoursePackagePaymentController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/feignclient/other/StoreClient.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/service/impl/TCoursePackagePaymentServiceImpl.java 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/service/impl/TCoursePackageServiceImpl.java 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/util/PayMoneyUtil.java 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/util/wx/PartnerAppPrepay.java 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/util/wx/WXPayUtility.java 441 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/util/wx/WeChatSignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/util/wx/WeChatV3SignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-course/src/main/java/com/dsh/course/util/wx/WxV3PayConfig.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/controller/code/OperatorController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/java/com/dsh/guns/modular/system/model/TOperator.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/operator/Operator.html 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/WEB-INF/view/system/operator/Operator_wx.html 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/operator/operator.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-management/src/main/webapp/static/modular/system/operator/operator_add.js 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/pom.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/controller/GameController.java 379 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/controller/SiteController.java 138 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/mapper/SiteMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/mapper/TOperatorUserMapper.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/service/IOperatorUserService.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/service/ISiteService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/service/impl/SiteServiceImpl.java 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/service/impl/TOperatorUserServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/util/PayMoneyUtil.java 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/util/wx/PartnerAppPrepay.java 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/util/wx/WXPayUtility.java 441 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/util/wx/WeChatSignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/util/wx/WeChatV3SignUtil.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/java/com/dsh/other/util/wx/WxV3PayConfig.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/resources/mapper/SiteMapper.xml 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-other/src/main/resources/mapper/TOperatorUserMapper.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cloud-server-account/pom.xml
@@ -14,6 +14,32 @@
    <name>账户</name>
    <description>账户</description>
    <dependencies>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java-core</artifactId>
            <version>0.2.12</version>
            <scope>compile</scope>
        </dependency>
        <!-- 微信支付V3 目前新版本-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.9</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <!-- OkHttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
        <!--日志处理-->
        <!--        <dependency>-->
        <!--            <groupId>com.github.binarywang</groupId>-->
cloud-server-account/src/main/java/com/dsh/account/controller/AppUserController.java
@@ -1,5 +1,6 @@
package com.dsh.account.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dsh.account.dto.BindDto;
@@ -32,6 +33,8 @@
import com.dsh.account.util.ResultUtil;
import com.dsh.account.util.TokenUtil;
import com.dsh.account.util.ToolUtil;
import com.dsh.account.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@@ -46,7 +49,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -453,6 +458,51 @@
    /**
     * 购买年度会员支付微信回调V3版本回调
     *
     * @param request
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/appUser/addVipPaymentWeChatCallback1")
    public void addVipPaymentWeChatCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String out_trade_no = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                ResultUtil resultUtil = vipPaymentService.addVipPaymentCallback(out_trade_no, transaction_id);
                if (resultUtil.getCode() == 200) {
                    PrintWriter out = response.getWriter();
                    out.write("SUCCESS");
                    out.flush();
                    out.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 购买年度会员支付微信回调
     *
     * @param request
@@ -479,7 +529,6 @@
            e.printStackTrace();
        }
    }
    /**
     * 购买年度会员支付支付宝回调
cloud-server-account/src/main/java/com/dsh/account/controller/PaymentCallbackController.java
@@ -1,11 +1,14 @@
package com.dsh.account.controller;
import com.alibaba.fastjson.JSONObject;
import com.dsh.account.service.RechargeRecordsService;
import com.dsh.account.service.TAppUserService;
import com.dsh.account.service.TStudentService;
import com.dsh.account.util.PayMoneyUtil;
import com.dsh.account.util.ResultUtil;
import com.dsh.account.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -14,7 +17,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
@@ -67,6 +72,44 @@
    /**
     * 课包续课微信支付回调接口
     */
    @PostMapping("/base/coursePackage/wechatPaymentCallback1")
    public void weChatCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String out_trade_no = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                ResultUtil resultUtil = tstuService.insertVipPaymentCallback(out_trade_no, transaction_id);
                if (resultUtil.getCode() == 200) {
                    PrintWriter out = response.getWriter();
                    out.write("SUCCESS");
                    out.flush();
                    out.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @PostMapping("/base/coursePackage/wechatPaymentCallback")
    public void weChatCallback(HttpServletRequest request, HttpServletResponse response) {
        try {
@@ -87,7 +130,6 @@
            e.printStackTrace();
        }
    }
    /**
     * 充值玩湃币支付宝支付回调接口
     */
@@ -115,6 +157,47 @@
    /**
     * 课包续课微信支付回调接口
     */
    @PostMapping("/base/recharge/wechatRechargeCallback1")
    public void wechatRechargeCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String out_trade_no = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                ResultUtil resultUtil = recordsService.addRechargeCallbackPay(out_trade_no, transaction_id);
                if (resultUtil.getCode() == 200) {
                    PrintWriter out = response.getWriter();
                    out.write("SUCCESS");
                    out.flush();
                    out.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 课包续课微信支付回调接口
     */
    @PostMapping("/base/recharge/wechatRechargeCallback")
    public void wechatRechargeCallback(HttpServletRequest request, HttpServletResponse response) {
        try {
@@ -135,14 +218,6 @@
            e.printStackTrace();
        }
    }
    /**
     * 兑换商品支付的微信回调
     *
     * @param request
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/pointMer/exchangeGoodPaymentWeChatCallback")
    public void exchangeGoodPaymentWeChatCallback(HttpServletRequest request, HttpServletResponse response) {
@@ -165,6 +240,52 @@
        }
    }
    /**
     * 兑换商品支付的微信回调V3版本
     *
     * @param request
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/pointMer/exchangeGoodPaymentWeChatCallback1")
    public void exchangeGoodPaymentWeChatCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String out_trade_no = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                ResultUtil resultUtil = tappService.exchangeAddPaymentCallback(out_trade_no, transaction_id);
                if (resultUtil.getCode() == 200) {
                    PrintWriter out = response.getWriter();
                    out.write("SUCCESS");
                    out.flush();
                    out.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 兑换商品支付的支付宝回调
cloud-server-account/src/main/java/com/dsh/account/controller/UseBenefitsController.java
@@ -1,6 +1,7 @@
package com.dsh.account.controller;
import com.alibaba.fastjson.JSONObject;
import com.dsh.account.entity.TAppUser;
import com.dsh.account.feignclient.activity.model.IntegralCommodity;
import com.dsh.account.feignclient.other.SysLogClient;
@@ -12,6 +13,8 @@
import com.dsh.account.util.ResultUtil;
import com.dsh.account.util.TokenUtil;
import com.dsh.account.util.ToolUtil;
import com.dsh.account.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@@ -22,7 +25,9 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
@@ -422,13 +427,55 @@
    }
    /**
     * V3版本回调
     * @param request
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/coupon/weChatPaymentCouponCallback1")
    public void weChatPaymentCouponCallback1(HttpServletRequest request, HttpServletResponse response){
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String code = jsonInfo.getString("out_trade_no");
            String trade_no = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                ResultUtil resultUtil = tauService.paymentCouponCallback(code, trade_no);
                if(resultUtil.getCode() == 200){
                    PrintWriter out = response.getWriter();
                    out.print("SUCCESS");
                    out.flush();
                    out.close();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    @ResponseBody
    @PostMapping("/base/coupon/weChatPaymentCouponCallback")
    public void weChatPaymentCouponCallback(HttpServletRequest request, HttpServletResponse response){
        try {
            System.err.println("进入回调");
            Map<String, String> map = payMoneyUtil.weixinpayCallback(request);
            if(null != map){
                String code = map.get("out_trade_no");
@@ -446,8 +493,6 @@
            e.printStackTrace();
        }
    }
    @ResponseBody
    @PostMapping("/base/coupon/aliPaymentCouponCallback")
cloud-server-account/src/main/java/com/dsh/account/feignclient/other/StoreClient.java
@@ -28,6 +28,9 @@
    // 根据运营商id获取微信商户号
    @PostMapping("/base/getmerchantNumberByOperatorId/{id}")
    String getmerchantNumberByOperatorId(@PathVariable("id")Integer id);
    // 根据运营商id获取支付宝商户号
    @PostMapping("/base/getmerchantNumberAliByOperatorId/{id}")
    String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id);
    @PostMapping("/store/queryByStoreId")
    OperatorUser queryByStoreId(Integer storeId);
cloud-server-account/src/main/java/com/dsh/account/service/impl/RechargeRecordsServiceImpl.java
@@ -28,6 +28,7 @@
import com.dsh.account.model.vo.userBenefitDetail.RechargesDetail;
import com.dsh.account.service.RechargeRecordsService;
import com.dsh.account.util.*;
import com.dsh.account.util.wx.WxV3PayConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
@@ -334,10 +335,13 @@
                "/base/recharge/alipayRechargeCallback");
        return alipay;
    }
    private ResultUtil WeChatPayment(String code, BigDecimal amount) throws Exception {
//
        ResultUtil weixinpay = payMoneyUtil.weixinpay("玩湃币充值", "", code, amount.toString(),
                "/base/recharge/wechatRechargeCallback", "APP", "");
//                ResultUtil weixinpay = payMoneyUtil.weixinpayV3(WxV3PayConfig.smidVx,"玩湃币充值",  code,
//                        "/base/recharge/wechatRechargeCallback1",
//                Long.valueOf(amount.toString()));
        return weixinpay;
    }
cloud-server-account/src/main/java/com/dsh/account/service/impl/TAppUserServiceImpl.java
@@ -48,6 +48,7 @@
import com.dsh.account.service.UserIntegralChangesService;
import com.dsh.account.util.*;
import com.dsh.account.util.akeylogin.Md5Util;
import com.dsh.account.util.wx.WxV3PayConfig;
import com.dsh.account.vo.GroupCityInfoVO;
import com.dsh.account.vo.entity.DayData;
import com.dsh.account.vo.entity.MonthData;
@@ -1407,12 +1408,7 @@
                userCouponPaymentClient.addUserCouponPayment(userCouponPayment);
                try {
                    if(exchangeType.getPayType() == 1){
                        String temp = "0";
                        if (coupon.getPublisherType()!=null && coupon.getPublisherType() != 2){
                            // 做分账
                            temp = "1";
                        }
                        return weChatPaymentCoupon("购买优惠券-"+temp, userCouponPayment);
                        return weChatPaymentCoupon("购买优惠券", userCouponPayment);
                    }
                    if(exchangeType.getPayType() == 2){
                        return aliPaymentCoupon("购买优惠券", userCouponPayment);
@@ -1644,9 +1640,9 @@
                            }
                        case 2:
                            if (merchandise.getType() == 2) {
                                return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(exchangeType.getStuIds().size())), code, returnId);
                                return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(exchangeType.getStuIds().size())), code, merchandise.getId());
                            } else {
                                return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(goodsNums)), code, returnId);
                                return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(goodsNums)), code, merchandise.getId());
                            }
                        default:
                            break;
@@ -1666,9 +1662,9 @@
                            }
                        case 2:
                            if (merchandise.getType() == 2) {
                                return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(exchangeType.getStuIds().size())), code, returnId);
                                return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(exchangeType.getStuIds().size())), code, merchandise.getId());
                            } else {
                                return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(goodsNums)), code, returnId);
                                return AlipayPayment(merchandise.getType(), merchandise.getCash().multiply(new BigDecimal(goodsNums)), code, merchandise.getId());
                            }
                        default:
                            break;
@@ -1687,7 +1683,6 @@
    }
    /**
     * 优惠券微信支付
     * @param body
@@ -1700,82 +1695,31 @@
        Double cash = userCouponPayment.getCash();
        Integer couponId = userCouponPayment.getCouponId();
        Coupon coupon = userConponClient.queryCouponById(couponId);
        String temp ="0";
        if (coupon.getPublisherType()!=null&&coupon.getPublisherType()!=2){
            temp  = "1";
        }
        List<Integer> couponStoreIds = userConponClient.getCouponStoreIds(couponId);
        System.err.println("购买优惠券");
        ResultUtil weixinpay = payMoneyUtil.weixinpay(body+"-"+temp, "", code, cash.toString(),
                "/base/coupon/weChatPaymentCouponCallback", "APP", "");
        if (weixinpay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            UserCouponPayment userCouponPayment1 = userCouponPaymentClient.getUserCouponPayment(code);
                            if (userCouponPayment1.getStatus() == 2) {
        if (coupon!=null){
            if (!couponStoreIds.isEmpty()&&!couponStoreIds.contains(-1)){
                        // 微信商户号
                        String s2 = storeClient.getmerchantNumberByOperatorId(coupon.getCityManagerId());
                    System.err.println("微信商户号"+s2);
                                break;
                        if(!StringUtils.hasLength(s2)){
                            return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
                            }
                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, "");
                            if (resultUtil.getCode() == 200 && userCouponPayment1.getStatus() == 1) {
                                /**
                                 * SUCCESS—支付成功,
                                 * REFUND—转入退款,
                                 * NOTPAY—未支付,
                                 * CLOSED—已关闭,
                                 * REVOKED—已撤销(刷卡支付),
                                 * USERPAYING--用户支付中,
                                 * PAYERROR--支付失败(其他原因,如银行返回失败)
                                 */
                                Map<String, String> data1 = resultUtil.getData();
                                String s = data1.get("trade_state");
                                String transaction_id = data1.get("transaction_id");
                                if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) {
                                    //有待支付的订单,这里不处理
                                    userCouponPayment1.setStatus(-1);
                                    userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1);
                                    break;
                                }
                                if ("SUCCESS".equals(s)) {
                                    userCouponPayment1.setStatus(2);
                                    userCouponPayment1.setPaymentTime(new Date());
                                    userCouponPayment1.setOrderNumber(transaction_id);
                                    userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1);
                                    UserCoupon userCoupon = new UserCoupon();
                                    userCoupon.setCouponId(userCouponPayment1.getCouponId());
                                    userCoupon.setUserId(userCouponPayment1.getUserId());
                                    userCoupon.setStatus(1);
                                    userCoupon.setInsertTime(new Date());
                                    userConponClient.insertToAppuserCoupon(userCoupon);
                                    if(null != userCouponPayment1.getIntegral() && 0 != userCouponPayment1.getIntegral()){
                                        //扣积分
                                        TAppUser user = appUserService.getById(userCouponPayment1.getUserId());
                                        user.setIntegral(user.getIntegral() - userCouponPayment1.getIntegral().intValue());
                                        appUserService.updateById(user);
                                    }
                                    break;
                                }
                                if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
                    ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,"购买优惠券",
                            code,"/base/coupon/weChatPaymentCouponCallback1",
                            cash.toString());
        return weixinpay;
            }else{
                // 平台发布
                        ResultUtil weixinpay = payMoneyUtil.weixinpay(body, "", code, cash.toString(),
                "/base/coupon/weChatPaymentCouponCallback", "APP", "");
                return weixinpay;
            }
        }
        return ResultUtil.error("支付失败!");
    }
@@ -1789,81 +1733,111 @@
    public ResultUtil aliPaymentCoupon(String body, UserCouponPayment userCouponPayment) throws Exception {
        String code = userCouponPayment.getCode();
        Double cash = userCouponPayment.getCash();
        Integer couponId = userCouponPayment.getCouponId();
        Coupon coupon = userConponClient.queryCouponById(couponId);
        List<Integer> couponStoreIds = userConponClient.getCouponStoreIds(couponId);
        if (coupon!=null){
            if (!couponStoreIds.isEmpty()&&!couponStoreIds.contains(-1)){
                    // 门店 向上查询运营商
                    Store store = storeClient.queryStoreById(couponStoreIds.get(0));
                    if (store.getOperatorId()==null || store.getOperatorId()==0){
                        // 平台
        ResultUtil alipay = payMoneyUtil.alipay(smid, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(),
                "/base/coupon/aliPaymentCouponCallback");
        if (alipay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            UserCouponPayment userCouponPayment1 = userCouponPaymentClient.getUserCouponPayment(code);
                            if (userCouponPayment1.getStatus() == 2) {
                                break;
                            }
                            AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(code);
                            if (null != alipayTradeQueryResponse) {
                                /**
                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
                                 * TRADE_SUCCESS(交易支付成功)、
                                 * TRADE_FINISHED(交易结束,不可退款)
                                 */
                                String s = alipayTradeQueryResponse.getTradeStatus();
                                String tradeNo = alipayTradeQueryResponse.getTradeNo();
                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
                                    //有待支付的订单,这里不处理
                                    userCouponPayment1.setStatus(-1);
                                    userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1);
                                    if ("TRADE_FINISHED".equals(s)) {
                                        // todo 资金结算
                                    }
                                    break;
                                }
                                if ("TRADE_SUCCESS".equals(s)) {
                                    userCouponPayment1.setStatus(2);
                                    userCouponPayment1.setPaymentTime(new Date());
                                    userCouponPayment1.setOrderNumber(tradeNo);
                                    userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1);
                                    UserCoupon userCoupon = new UserCoupon();
                                    userCoupon.setCouponId(userCouponPayment1.getCouponId());
                                    userCoupon.setUserId(userCouponPayment1.getUserId());
                                    userCoupon.setStatus(1);
                                    userCoupon.setInsertTime(new Date());
                                    userConponClient.insertToAppuserCoupon(userCoupon);
                                    if(null != userCouponPayment1.getIntegral() && 0 != userCouponPayment1.getIntegral()){
                                        //扣积分
                                        TAppUser user = appUserService.getById(userCouponPayment1.getUserId());
                                        user.setIntegral(user.getIntegral() - userCouponPayment1.getIntegral().intValue());
                                        appUserService.updateById(user);
                                    }
                                    payMoneyUtil.confirm(smid,code,tradeNo,cash.toString());
                                    break;
                                }
                                if ("WAIT_BUYER_PAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        return alipay;
                    }else{
                        // 支付宝商户号
                        String s2 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId());
                        System.err.println("支付宝商户号"+s2);
                        if(!StringUtils.hasLength(s2)){
                            return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
                        }
                        ResultUtil alipay = payMoneyUtil.alipay(s2, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(),
                                "/base/coupon/aliPaymentCouponCallback");
                        return alipay;
                    }
            }else {
                // 平台发布
                ResultUtil alipay = payMoneyUtil.alipay(smid, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(),
                        "/base/coupon/aliPaymentCouponCallback");
                return alipay;
            }
        }
//        ResultUtil alipay = payMoneyUtil.alipay(smid, body, "",String.valueOf(userCouponPayment.getId()), code, cash.toString(),
//                "/base/coupon/aliPaymentCouponCallback");
//        if (alipay.getCode() == 200) {
//            new Thread(new Runnable() {
//                @Override
//                public void run() {
//                    try {
//                        Thread.sleep(1000);
//
//                        int num = 1;
//                        int wait = 0;
//                        while (num <= 10) {
//                            int min = 5000;
//                            wait += (min * num);
//                            Thread.sleep(wait);
//                            UserCouponPayment userCouponPayment1 = userCouponPaymentClient.getUserCouponPayment(code);
//                            if (userCouponPayment1.getStatus() == 2) {
//                                break;
//                            }
//                            AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(code);
//                            if (null != alipayTradeQueryResponse) {
//                                /**
//                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
//                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
//                                 * TRADE_SUCCESS(交易支付成功)、
//                                 * TRADE_FINISHED(交易结束,不可退款)
//                                 */
//                                String s = alipayTradeQueryResponse.getTradeStatus();
//
//                                String tradeNo = alipayTradeQueryResponse.getTradeNo();
//                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
//                                    //有待支付的订单,这里不处理
//                                    userCouponPayment1.setStatus(-1);
//                                    userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1);
//                                    if ("TRADE_FINISHED".equals(s)) {
//                                        // todo 资金结算
//
//                                    }
//                                    break;
//                                }
//                                if ("TRADE_SUCCESS".equals(s)) {
//                                    userCouponPayment1.setStatus(2);
//                                    userCouponPayment1.setPaymentTime(new Date());
//                                    userCouponPayment1.setOrderNumber(tradeNo);
//                                    userCouponPaymentClient.updateUserCouponPayment(userCouponPayment1);
//                                    UserCoupon userCoupon = new UserCoupon();
//                                    userCoupon.setCouponId(userCouponPayment1.getCouponId());
//                                    userCoupon.setUserId(userCouponPayment1.getUserId());
//                                    userCoupon.setStatus(1);
//                                    userCoupon.setInsertTime(new Date());
//                                    userConponClient.insertToAppuserCoupon(userCoupon);
//                                    if(null != userCouponPayment1.getIntegral() && 0 != userCouponPayment1.getIntegral()){
//                                        //扣积分
//                                        TAppUser user = appUserService.getById(userCouponPayment1.getUserId());
//                                        user.setIntegral(user.getIntegral() - userCouponPayment1.getIntegral().intValue());
//                                        appUserService.updateById(user);
//                                    }
////                                    payMoneyUtil.confirm(smid,code,tradeNo,cash.toString());
//                                    break;
//
//                                }
//
//                                if ("WAIT_BUYER_PAY".equals(s)) {
//                                    num++;
//                                }
//                            }
//                        }
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                }
//            }).start();
//        }
        return ResultUtil.error("支付失败");
    }
    /**
@@ -1902,67 +1876,6 @@
        }
        Coupon coupon = ucponClient.queryCouponById(userCouponPayment1.getCouponId());
        // 创建一个两分钟后执行的线程
        new Thread(() -> {
            try {
                Thread.sleep(120000);
                if (coupon!=null){
                    if (coupon.getPublisherType()!=null&&coupon.getPublisherType()!=2){
                        if (coupon.getPublisherType() == 1){
                            System.err.println("进入分账逻辑");
                            // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
                            String proportionByOperatorId = storeClient.getProportionByOperatorId(coupon.getCityManagerId());
                            System.err.println("分账比例:"+proportionByOperatorId);
                            String[] split = proportionByOperatorId.split(",");
                            String s1 = split[0];
                            if (!s1.equals("未设置")){
                                BigDecimal bigDecimal = new BigDecimal(s1);
                                // 分账比例
                                BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                                // 微信商户号
                                String s2 = storeClient.getmerchantNumberByOperatorId(coupon.getCityManagerId());
                                System.err.println("商户号"+s2);
                                System.err.println("分账比例"+bigDecimal1);
                                System.err.println("分账金额"+coupon.getCash().multiply(bigDecimal1));
                                ResultUtil fenzhang = payMoneyUtil.fenzhang(userCouponPayment1.getOrderNumber(), coupon.getCash().multiply(bigDecimal1), s2,"");
                                if (!fenzhang.getCode().equals(200)){
                                    System.err.println("分账失败 原因是:"+fenzhang.getMsg());
                                }
                            }
                        } else if (coupon.getPublisherType() == 3){
                            // 门店 向上查询运营商
                            Store store = storeClient.queryStoreById(coupon.getCityManagerId());
                            if (store.getOperatorId()==null || store.getOperatorId()==0){
                                // 平台不分账
                            }else{
                                // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
                                String proportionByOperatorId = storeClient.getProportionByOperatorId(store.getOperatorId());
                                String[] split = proportionByOperatorId.split(",");
                                String s1 = split[0];
                                if (!s1.equals("未设置")){
                                    BigDecimal bigDecimal = new BigDecimal(s1);
                                    // 分账比例
                                    BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                                    // 微信商户号
                                    String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId());
                                    ResultUtil fenzhang = payMoneyUtil.fenzhang(userCouponPayment1.getOrderNumber(), coupon.getCash().multiply(bigDecimal1), s2,"购买优惠券分账");
                                    if (!fenzhang.getCode().equals(200)){
                                        System.err.println("分账失败 原因是:"+fenzhang.getMsg());
                                    }else{
                                        System.err.println("分账成功");
                                        return;
                                    }
                                }
                            }
                        }
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }).start();
        return ResultUtil.success();
    }
@@ -1989,80 +1902,85 @@
        }
    }
    private ResultUtil AlipayPayment(Integer type, BigDecimal cash, String code, Long returnId) {
    private ResultUtil AlipayPayment(Integer type, BigDecimal cash, String code, Integer returnId) {
        PointsMerchandise pointsMerchandise = mcClient.selectPointsMerchandiseById(returnId);
        System.err.println("查询积分商品"+pointsMerchandise);
        List<Integer> integers = mcsClient.queryPointMerStoreIds(pointsMerchandise.getId());
        String name = (type == 1 ? "购买实体商品" : type == 2 ? "报名运动营": type == 3 ? "购买门票" : "购买优惠券");
        switch (type) {
            case 1:
                if (!integers.isEmpty()) {
                    Store store1 = storeClient.queryStoreById(integers.get(0));
                    if (store1.getOperatorId() == null || store1.getOperatorId() == 0) {
                        // 平台门票
        ResultUtil alipay = payMoneyUtil.alipay(smid,name, name, String.valueOf(returnId), code, cash.toString(),
                "/base/pointMer/exchangeGoodPaymentAliCallback");
        if (alipay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            System.out.println("code:" + code);
                            List<UserPointsMerchandise> userPointsMerchandises = mcClient.queryUserPointMerchaseByCode(code);
                            System.out.println("list-----" + userPointsMerchandises);
                            if (userPointsMerchandises.get(0).getPayStatus() == 2) {
                                break;
                            }
//                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryALIOrder(code);
                            AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code);
                            if (resultUtil.getCode().equals("10000") && userPointsMerchandises.get(0).getPayStatus() == 1) {
                                /**
                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
                                 * TRADE_SUCCESS(交易支付成功)、
                                 * TRADE_FINISHED(交易结束,不可退款)
                                 */
//                                Map<String, String> data1 = resultUtil.getData();
//                                String s = data1.get("tradeStatus");
//                                String tradeNo = data1.get("tradeNo");
                                String tradeNo = resultUtil.getTradeNo();
                                String tradeStatus = resultUtil.getTradeStatus();
                                System.out.println("ssssss" + tradeStatus);
                                if ("TRADE_SUCCESS".equals(tradeStatus)) {
                                    for (UserPointsMerchandise userPointsMerchandise : userPointsMerchandises) {
                                        userPointsMerchandise.setPayStatus(2);
                                        userPointsMerchandise.setPaymentTime(new Date());
                                        userPointsMerchandise.setOrderNumber(tradeNo);
                                        userPointsMerchandise.setCashPayType(2);
                                        userPointsMerchandise.setInsertTime(null);
                                        userPointsMerchandise.setUserId(null);
                                        mcClient.updateDetailsUserPointMercase(userPointsMerchandise);
                                    }
                                    moneyOut(tradeNo, tradeNo);
                                    break;
                                }
                                if ("TRADE_FINISHED".equals(tradeStatus)) {
                                    // todo 资金结算
                                    payMoneyUtil.confirm(smid,code,tradeNo,cash.toString());
                                    break;
                                }
//                                if("REFUND".equals(tradeStatus) || "NOTPAY".equals(tradeStatus) || "CLOSED".equals(tradeStatus) || "REVOKED".equals(tradeStatus) || "PAYERROR".equals(tradeStatus) || num == 10){
//                                    mcClient.deletePaymentRecord(code);
//                                    break;
//                                }
                                if ("WAIT_BUYER_PAY".equals(tradeStatus)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        mcClient.deletePaymentRecord(code);
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        return alipay;
                    } else {
                        String s2 = storeClient.getmerchantNumberAliByOperatorId(store1.getOperatorId());
                        System.err.println("支付宝商户号" + s2);
                        if (!StringUtils.hasLength(s2)) {
                            return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
                        }
                        ResultUtil alipay = payMoneyUtil.alipay(s2, name, name, String.valueOf(returnId), code, cash.toString(),
                                "/base/pointMer/exchangeGoodPaymentAliCallback");
                        return alipay;
                    }
                }else{
                    // 平台门票
                    ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(),
                            "/base/pointMer/exchangeGoodPaymentAliCallback");
                    return alipay;
                }
            case 2:
                // 判断课包属于平台还是运营商
                CoursePackage coursePackage = cpageClient.queryCoursePackageById(pointsMerchandise.getCoursePackageId());
                Store store = storeClient.queryStoreById(coursePackage.getStoreId());
                if (store.getOperatorId() == null || store.getOperatorId() == 0) {
                    // 平台课包
                    ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(),
                            "/base/pointMer/exchangeGoodPaymentAliCallback");
                    return alipay;
                } else {
                    String s2 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId());
                    System.err.println("支付宝商户号" + s2);
                    if (!StringUtils.hasLength(s2)) {
                        return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
                    }
                    ResultUtil alipay = payMoneyUtil.alipay(s2, name, name, String.valueOf(returnId), code, cash.toString(),
                            "/base/pointMer/exchangeGoodPaymentAliCallback");
                    return alipay;
                }
            case 3:
                if (!integers.isEmpty()) {
                    Store store1 = storeClient.queryStoreById(integers.get(0));
                    if (store1.getOperatorId() == null || store1.getOperatorId() == 0) {
                        // 平台门票
                        ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(),
                                "/base/pointMer/exchangeGoodPaymentAliCallback");
                        return alipay;
                    } else {
                        String s2 = storeClient.getmerchantNumberAliByOperatorId(store1.getOperatorId());
                        System.err.println("支付宝商户号" + s2);
                        if (!StringUtils.hasLength(s2)) {
                            return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
                        }
                            ResultUtil alipay = payMoneyUtil.alipay(s2, name, name, String.valueOf(returnId), code, cash.toString(),
                                    "/base/pointMer/exchangeGoodPaymentAliCallback");
                            return alipay;
                        }
                    }else{
                    // 平台门票
                    ResultUtil alipay = payMoneyUtil.alipay(smid, name, name, String.valueOf(returnId), code, cash.toString(),
                            "/base/pointMer/exchangeGoodPaymentAliCallback");
                    return alipay;
                }
                }
        return ResultUtil.error("获取支付失败!");
    }
    public void moneyOut(String outNum, String tradeNo) throws AlipayApiException {
@@ -2163,119 +2081,84 @@
        PointsMerchandise pointsMerchandise = mcClient.selectPointsMerchandiseById(id);
        System.err.println("查询积分商品"+pointsMerchandise);
        // 分账表示 0否1是
        String temp = "0";
        List<Integer> integers = mcsClient.queryPointMerStoreIds(pointsMerchandise.getId());
        switch (type){
            case 1:
                temp = "0";
                if (!integers.isEmpty()){
                    Store store1 = storeClient.queryStoreById(integers.get(0));
                    if (store1.getOperatorId() ==null || store1.getOperatorId() == 0){
                        // 平台门票
                        ResultUtil weixinpay = payMoneyUtil.weixinpay(name, "", code, cash.toString(),
                                "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", "");
                        return weixinpay;
                    }else{
                        String s2 = storeClient.getmerchantNumberByOperatorId(store1.getOperatorId());
                        System.err.println("微信商户号"+s2);
                        if(!StringUtils.hasLength(s2)){
                            return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
                        }
                        ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,name,  code,
                                "/base/pointMer/exchangeGoodPaymentWeChatCallback1",
                                cash.toString());
                        return weixinpay;
                    }
                }
                break;
            case 2:
                // 判断课包属于平台还是运营商
                CoursePackage coursePackage = cpageClient.queryCoursePackageById(pointsMerchandise.getCoursePackageId());
                Store store = storeClient.queryStoreById(coursePackage.getStoreId());
                if (store.getOperatorId() ==null || store.getOperatorId() == 0){
                    // 平台课包 不分账
                    // 平台课包
                    ResultUtil weixinpay = payMoneyUtil.weixinpay(name, "", code, cash.toString(),
                            "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", "");
                    return weixinpay;
                }else{
                    temp = "1";
                    String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId());
                    System.err.println("微信商户号"+s2);
                    if(!StringUtils.hasLength(s2)){
                        return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
                }
                break;
                    ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,name,  code,
                            "/base/pointMer/exchangeGoodPaymentWeChatCallback1",
                            cash.toString());
                    return weixinpay;
                }
            case 3:
                List<Integer> integers = mcsClient.queryPointMerStoreIds(pointsMerchandise.getId());
                if (!integers.isEmpty()){
                    Store store1 = storeClient.queryStoreById(integers.get(0));
                    if (store1.getOperatorId() ==null || store1.getOperatorId() == 0){
                        // 平台门票 不分账
                    }else{
                        temp = "1";
                    }
                }
                // 查询门票指定的门店 属于平台还是运营商
                break;
        }
        System.err.println("支付");
        ResultUtil weixinpay = payMoneyUtil.weixinpay(name+"-"+temp, "", code, cash.toString(),
                        // 平台门票
                        ResultUtil weixinpay = payMoneyUtil.weixinpay(name, "", code, cash.toString(),
                "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", "");
        System.err.println("提交支付"+weixinpay);
        if (weixinpay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            UserPointsMerchandise userPointsMerchandise = mcClient.queryUserPointMerchaseByCode(code).get(0);
                            if (userPointsMerchandise.getPayStatus() == 2) {
                        return weixinpay;
                    }else{
                        String s2 = storeClient.getmerchantNumberByOperatorId(store1.getOperatorId());
                        System.err.println("微信商户号"+s2);
                                break;
                        if(!StringUtils.hasLength(s2)){
                            return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
                            }
                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, "");
                            if (resultUtil.getCode() == 200 && userPointsMerchandise.getPayStatus() == 1) {
                                /**
                                 * SUCCESS—支付成功,
                                 * REFUND—转入退款,
                                 * NOTPAY—未支付,
                                 * CLOSED—已关闭,
                                 * REVOKED—已撤销(刷卡支付),
                                 * USERPAYING--用户支付中,
                                 * PAYERROR--支付失败(其他原因,如银行返回失败)
                                 */
                                Map<String, String> data1 = resultUtil.getData();
                                String s = data1.get("trade_state");
                                String transaction_id = data1.get("transaction_id");
                                if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) {
                                    mcClient.deletePaymentRecord(code);
                                    break;
                                }
                                if ("SUCCESS".equals(s)) {
                                    userPointsMerchandise.setPayStatus(2);
                                    userPointsMerchandise.setPaymentTime(new Date());
                                    userPointsMerchandise.setOrderNumber(transaction_id);
                                    userPointsMerchandise.setCashPayType(1);
                                    mcClient.updateDetailsUserPointMercase(userPointsMerchandise);
                                    TAppUser tAppUser = baseMapper.selectById(userPointsMerchandise.getUserId());
                                    dealAppUserIntegral(userPointsMerchandise.getUserId(), tAppUser, userPointsMerchandise.getIntegral());
                                    //课程
                                    if(type == 2){
                                        CoursePackageOrder coursePackageOrder1 = paymentClient.getCoursePackageOrderByCode(code);
                                        if(null != coursePackageOrder1){
                                            coursePackageOrder1.setPayStatus(2);
                                            coursePackageOrder1.setOrderNumber(transaction_id);
                                            coursePackageOrder1.setAppUserId(null);
                                            paymentClient.updateCoursePackageOrder(coursePackageOrder1);
                                            GetCoursePackagePaymentConfig getCoursePackagePaymentConfig = new GetCoursePackagePaymentConfig();
                                            getCoursePackagePaymentConfig.setCoursePackageId(coursePackageOrder1.getCoursePackageId());
                                            getCoursePackagePaymentConfig.setClassHours(coursePackageOrder1.getClassHours());
                                            CoursePackagePaymentConfig coursePackagePaymentConfig = paymentClient.getCoursePackagePaymentConfig(getCoursePackagePaymentConfig);
                                            AddCoursePackageOrderStudent addCoursePackageOrderStudent = new AddCoursePackageOrderStudent();
                                            addCoursePackageOrderStudent.setCoursePackageOrderId(coursePackageOrder1.getId());
                                            addCoursePackageOrderStudent.setCoursePackagePaymentConfig(coursePackagePaymentConfig);
                                            paymentClient.addCoursePackageOrderStudent(addCoursePackageOrderStudent);
                                        }
                                    }
                                    break;
                                }
                                if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
                        ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,name,  code,
                                "/base/pointMer/exchangeGoodPaymentWeChatCallback1",
                                cash.toString());
        return weixinpay;
    }
                }else{
                    // 平台门票
                    ResultUtil weixinpay = payMoneyUtil.weixinpay(name, "", code, cash.toString(),
                            "/base/pointMer/exchangeGoodPaymentWeChatCallback", "APP", "");
                    return weixinpay;
                }
        }
        return ResultUtil.error("支付失败");
    }
    @Override
    public ResultUtil exchangeAddPaymentCallback(String code, String orderNumber) {
@@ -2316,113 +2199,6 @@
                paymentClient.addCoursePackageOrderStudent(addCoursePackageOrderStudent);
            }
        }
        // 创建一个2分钟后执行的线程
        new Thread(() -> {
            try {
                // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
                Thread.sleep(1000 * 60 * 2);
                // 分账表示 0否1是
                String temp = "0";
                // 运营商id
                Integer operatorId = 0;
                System.err.println("商品"+pointsMerchandise);
                switch (pointsMerchandise.getType()){
                    case 1:
                        temp = "0";
                        break;
                    case 2:
                        // 判断课包属于平台还是运营商
                        CoursePackage coursePackage = cpageClient.queryCoursePackageById(pointsMerchandise.getCoursePackageId());
                        Store store = storeClient.queryStoreById(coursePackage.getStoreId());
                        if (store.getOperatorId() ==null || store.getOperatorId() == 0){
                            // 平台课包 不分账
                        }else{
                            temp = "1";
                            operatorId = store.getOperatorId();
                        }
                        break;
                    case 3:
                        List<Integer> integers = mcsClient.queryPointMerStoreIds(pointsMerchandise.getId());
                        System.err.println("门店ids"+integers);
                        if (!integers.isEmpty()){
                            Store store1 = storeClient.queryStoreById(integers.get(0));
                            if (store1.getOperatorId() ==null || store1.getOperatorId() == 0){
                                // 平台门票 不分账
                            }else{
                                temp = "1";
                                operatorId = store1.getOperatorId();
                            }
                        }
                        // 查询门票指定的门店 属于平台还是运营商
                        break;
                }
                System.err.println("运营商id"+operatorId);
                if (operatorId != 0) {
                    //课程
                    if(pointsMerchandise.getType() == 2){
                        //课程
                        CoursePackageOrder coursePackageOrder1 = paymentClient.getCoursePackageOrderByCode(code);
                        if(null != coursePackageOrder1){
                            coursePackageOrder1.setPayStatus(2);
                            coursePackageOrder1.setOrderNumber(orderNumber);
                            coursePackageOrder1.setAppUserId(null);
                            paymentClient.updateCoursePackageOrder(coursePackageOrder1);
                            GetCoursePackagePaymentConfig getCoursePackagePaymentConfig = new GetCoursePackagePaymentConfig();
                            getCoursePackagePaymentConfig.setCoursePackageId(coursePackageOrder1.getCoursePackageId());
                            getCoursePackagePaymentConfig.setClassHours(coursePackageOrder1.getClassHours());
                            CoursePackagePaymentConfig coursePackagePaymentConfig = paymentClient.getCoursePackagePaymentConfig(getCoursePackagePaymentConfig);
                            AddCoursePackageOrderStudent addCoursePackageOrderStudent = new AddCoursePackageOrderStudent();
                            addCoursePackageOrderStudent.setCoursePackageOrderId(coursePackageOrder1.getId());
                            addCoursePackageOrderStudent.setCoursePackagePaymentConfig(coursePackagePaymentConfig);
                            paymentClient.addCoursePackageOrderStudent(addCoursePackageOrderStudent);
                            // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
                            String proportionByOperatorId = storeClient.getProportionByOperatorId(operatorId);
                            String[] split = proportionByOperatorId.split(",");
                            String s1 = split[0];
                            if (!s1.equals("未设置")){
                                BigDecimal bigDecimal = new BigDecimal(s1);
                                // 分账比例
                                BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                                // 微信商户号
                                String s2 = storeClient.getmerchantNumberByOperatorId(operatorId);
                                ResultUtil fenzhang = payMoneyUtil.fenzhang(orderNumber, coursePackageOrder1.getCashPayment().multiply(bigDecimal1), s2,"运动营商品");
                                if (!fenzhang.getCode().equals(200)){
                                    System.err.println("分账失败 原因是:"+fenzhang.getMsg());
                                }
                            }
                        }
                    }else{
                        System.err.println("门票");
                        // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
                        String proportionByOperatorId = storeClient.getProportionByOperatorId(operatorId);
                        System.err.println("分账比例"+proportionByOperatorId);
                        String[] split = proportionByOperatorId.split(",");
                        String s1 = split[0];
                        if (!s1.equals("未设置")){
                            BigDecimal bigDecimal = new BigDecimal(s1);
                            // 分账比例
                            BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                            // 微信商户号
                            String s2 = storeClient.getmerchantNumberByOperatorId(operatorId);
                            System.err.println("微信商户号"+s2);
                            ResultUtil fenzhang = payMoneyUtil.fenzhang(orderNumber, pointsMerchandise.getCash().multiply(bigDecimal1), s2,"门票");
                            if (!fenzhang.getCode().equals(200)){
                                System.err.println("分账失败 原因是:"+fenzhang.getMsg());
                            }
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }).start();
        return ResultUtil.success();
    }
cloud-server-account/src/main/java/com/dsh/account/service/impl/TStudentServiceImpl.java
@@ -43,6 +43,7 @@
import com.dsh.account.service.TCourseInfoRecordService;
import com.dsh.account.service.TStudentService;
import com.dsh.account.util.*;
import com.dsh.account.util.wx.WxV3PayConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@@ -428,89 +429,31 @@
        CoursePackage coursePackage = coursePackageClient.queryCoursePackageById(lessonId);
        Integer storeId = coursePackage.getStoreId();
        Store store = storeClient.queryStoreById(storeId);
        // 是否分账 0否1是
        int isFenZhang= 1;
        String merchantNumber = "";
        if (store.getOperatorId()==null || store.getOperatorId()==0){
            // 平台
            isFenZhang = 0;
        }
        ResultUtil weixinpay = payMoneyUtil.weixinpay("课包续费"+"-"+isFenZhang, "", code, amount.toString(),
                "/base/coursePackage/wechatPaymentCallback", "APP", "");
        if (weixinpay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            TCoursePackagePayment coursePackagePayment = couPayClient.getCoursePackagePaymentByCode(code);
                            if (coursePackagePayment.getPayStatus() == 2) {
                                break;
                            }
                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, "");
                            if (resultUtil.getCode() == 200 && coursePackagePayment.getPayStatus() == 1) {
                                /**
                                 * SUCCESS—支付成功,
                                 * REFUND—转入退款,
                                 * NOTPAY—未支付,
                                 * CLOSED—已关闭,
                                 * REVOKED—已撤销(刷卡支付),
                                 * USERPAYING--用户支付中,
                                 * PAYERROR--支付失败(其他原因,如银行返回失败)
                                 */
                                Map<String, String> data1 = resultUtil.getData();
                                String s = data1.get("trade_state");
                                String transaction_id = data1.get("transaction_id");
                                if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) {
                                    coursePackagePayment.setState(3);
                                    couPayClient.delPaymentCoursePackage(coursePackagePayment.getId());
                                    break;
                                }
                                if ("SUCCESS".equals(s)) {
                                    coursePackagePayment.setPayStatus(2);
                                    coursePackagePayment.setTotalClassHours(hour);
                                    coursePackagePayment.setLaveClassHours(hour);
                                    coursePackagePayment.setOrderNumber(transaction_id);
                                    couPayClient.updatePaymentCoursePackage(coursePackagePayment);
                                    if (store.getOperatorId()!=null && store.getOperatorId()!=0){
                                        // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
                                        Thread.sleep(120000);
                                        // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
                                        String proportionByOperatorId = storeClient.getProportionByOperatorId(store.getOperatorId());
                                        String[] split = proportionByOperatorId.split(",");
                                        String s1 = split[0];
                                        if (!s1.equals("未设置")){
                                            BigDecimal bigDecimal = new BigDecimal(s1);
                                            // 分账比例
                                            BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                                            // 微信商户号
        if (isFenZhang == 1) {
                                            String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId());
            System.err.println("微信商户号"+s2);
                                            ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, amount.multiply(bigDecimal1), s2,"课包续费分账");
                                            if (!fenzhang.getCode().equals(500)){
                                                System.err.println("分账失败 原因是:"+fenzhang.getMsg());
            if(!StringUtils.hasLength(s2)){
                return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
                                            }
                                        }
                                    }
                                    break;
                                }
                                if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
            ResultUtil weixinpay = payMoneyUtil.weixinpayV3(s2,"课包续费",
                    code,"/base/coursePackage/wechatPaymentCallback1",
                   amount.toString());
        return weixinpay;
        }else{
            ResultUtil weixinpay = payMoneyUtil.weixinpay("课包续费", "", code, amount.toString(),
                    "/base/coursePackage/wechatPaymentCallback", "APP", "");
            return weixinpay;
        }
    }
    private String smid = "2088330203191220";//平台支付宝商户号
    public ResultUtil AlipayPayment(String code, BigDecimal amount, Integer hour) {
@@ -521,14 +464,15 @@
        Integer storeId = coursePackage.getStoreId();
        Store store = storeClient.queryStoreById(storeId);
        String smid1= "";
        if (store.getOperatorId()==null){
        if (store.getOperatorId()==null || store.getOperatorId()==0){
            // 平台
            smid1 = smid;
        }else{
            String smidByOperatorId = storeClient.getSMIDByOperatorId(store.getOperatorId());
            smid1 = smidByOperatorId;
            smid1  = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId());
        }
        if (!StringUtils.hasLength(smid1)){
            return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
        }
        ResultUtil alipay = payMoneyUtil.alipay(smid1,"课包续费", "课包续费", "", code, amount.toString(),
                "/base/coursePackage/alipayPaymentCallback");
        if (alipay.getCode() == 200) {
@@ -581,7 +525,7 @@
                                        // 平台
                                        smid1 = smid;
                                        // 属于平台 不做资金冻结 不做分账处理
                                        payMoneyUtil.confirm(smid1,code,tradeNo,amount.toString());
//                                        payMoneyUtil.confirm(smid1,code,tradeNo,amount.toString());
                                    }else{
                                        String smidByOperatorId = storeClient.getSMIDByOperatorId(store.getOperatorId());
                                        smid1 = smidByOperatorId;
cloud-server-account/src/main/java/com/dsh/account/service/impl/VipPaymentServiceImpl.java
@@ -14,6 +14,7 @@
import com.dsh.account.util.PayMoneyUtil;
import com.dsh.account.util.RedisUtil;
import com.dsh.account.util.ResultUtil;
import com.dsh.account.util.wx.WxV3PayConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@@ -83,84 +84,11 @@
        String code = vipPayment.getCode();
        ResultUtil weixinpay = payMoneyUtil.weixinpay("购买年度会员", "", code, vipPayment.getAmount().toString(),
                "/base/appUser/addVipPaymentWeChatCallback", "APP", "");
        if (weixinpay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            VipPayment vipPayment1 = VipPaymentServiceImpl.this.getOne(new QueryWrapper<VipPayment>().eq("code", code));
                            if (vipPayment1.getPayStatus() == 2) {
                                return;
                            }
                            /**
                             * SUCCESS--支付成功
                             * REFUND--转入退款
                             * NOTPAY--未支付
                             * CLOSED--已关闭
                             * REVOKED--已撤销(刷卡支付)
                             * USERPAYING--用户支付中
                             * PAYERROR--支付失败(其他原因,如银行返回失败)
                             * ACCEPT--已接收,等待扣款
                             */
                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, "");
                            if (resultUtil.getCode() == 200) {
                                Map<String, String> map = resultUtil.getData();
                                String trade_type = map.get("trade_type");
                                String trade_state = map.get("trade_state");
                                String transaction_id = map.get("transaction_id");
                                if ("REFUND".equals(trade_state) || "NOTPAY".equals(trade_state) || "CLOSED".equals(trade_state) || "REVOKED".equals(trade_state) || "PAYERROR".equals(trade_state)) {
                                    vipPayment1.setState(3);
                                    VipPaymentServiceImpl.this.updateById(vipPayment1);
                                    return;
                                }
                                if ("SUCCESS".equals(trade_state)) {
                                    vipPayment1.setPayStatus(2);
                                    vipPayment1.setPayTime(new Date());
                                    vipPayment1.setOrderNumber(transaction_id);
                                    VipPaymentServiceImpl.this.updateById(vipPayment1);
                                    TAppUser appUser = appUserService.getById(vipPayment1.getAppUserId());
                                    Date vipEndTime = appUser.getVipEndTime();
                                    if (null == vipEndTime) {
                                        vipEndTime = new Date();
                                    }
                                    Calendar calendar = Calendar.getInstance();
                                    calendar.setTime(vipEndTime);
                                    calendar.set(Calendar.YEAR, calendar.get(Calendar.YEAR) + 1);
                                    appUser.setIsVip(1);
                                    appUser.setVipEndTime(calendar.getTime());
                                    appUserService.updateById(appUser);
                                    // 注册会员送券 先判断是否有注册送券类型的优惠券 判断优惠券状态 审核是否通过 是否删除 是否在有效期内 是否领取数量达上限
                                    List<Long> longs = userConponClient.queryCouponByUser(appUser.getId());
                                    redisUtil.setStrValue("VIP_P_" + vipPayment1.getAppUserId(), JSON.toJSONString(longs), 3600);
                                    return;
                                }
                                if ("USERPAYING".equals(trade_state) || "ACCEPT".equals(trade_state)) {
                                    Thread.sleep(wait);
                                    num++;
                                }
                            } else {
                                Thread.sleep(wait);
                                num++;
                            }
                            if (10 == num) {
                                vipPayment1.setState(3);
                                VipPaymentServiceImpl.this.updateById(vipPayment1);
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
//        ResultUtil weixinpay = payMoneyUtil.weixinpayV3(WxV3PayConfig.smidVx,"购买年度会员",
//                code,"/base/appUser/addVipPaymentWeChatCallback1",
//                Long.valueOf(vipPayment.getAmount().toString()));
        return weixinpay;
    }
    /**
     * 支付宝
@@ -233,7 +161,7 @@
                                    List<Long> longs = userConponClient.queryCouponByUser(appUser.getId());
                                    redisUtil.setStrValue("VIP_P_" + vipPayment1.getAppUserId(), JSON.toJSONString(longs), 3600);
                                    // 如果交易结束 将当前订单金额分配到商户
                                    payMoneyUtil.confirm(smid,vipPayment.getCode(),tradeNo,amount);
//                                    payMoneyUtil.confirm(smid,vipPayment.getCode(),tradeNo,amount);
                                    return;
                                }
                                if ("WAIT_BUYER_PAY".equals(tradeStatus)) {
cloud-server-account/src/main/java/com/dsh/account/util/PayMoneyUtil.java
@@ -26,6 +26,10 @@
//import com.github.binarywang.wxpay.service.ProfitSharingV3Service;
//import com.github.binarywang.wxpay.service.WxPayService;
import com.dsh.account.util.wx.PartnerAppPrepay;
import com.dsh.account.util.wx.WXPayUtility;
import com.dsh.account.util.wx.WeChatV3SignUtil;
import com.dsh.account.util.wx.WxV3PayConfig;
import org.apache.commons.collections.map.HashedMap;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -181,7 +185,8 @@
    /**
     * 支付宝支付
     */
    public ResultUtil  alipay(String smid,String body, String subject, String passbackParams, String outTradeNo, String amount, String notifyUrl) {
    public ResultUtil  alipay(String smid,String body, String subject,
                              String passbackParams, String outTradeNo, String amount, String notifyUrl) {
//        //构造client
//        CertAlipayRequest certAlipayRequest = new CertAlipayRequest ();
//        //设置网关地址
@@ -258,47 +263,7 @@
        SubMerchant subMerchant = new SubMerchant();
        subMerchant.setMerchantId(smid);
        model.setSubMerchant(subMerchant);
        ExtendParams extendParams = new ExtendParams();
        extendParams.setRoyaltyFreeze("true");// 冻结资金 用于后续分账处理
        model.setExtendParams(extendParams);
        //分账
//        Integer    coursePackagePayments = coursePackageClient.queryByCode(outTradeNo);
//        Integer paymentCompetitions = competitionsClient.queryByCode(outTradeNo);
//        Integer siteBookings = siteClient.queryByCode(outTradeNo);
//        List<Integer> stores = new ArrayList<>();
//        stores.add(coursePackagePayments);
//        stores.add(paymentCompetitions);
//        stores.add(siteBookings);
//
//        OperatorUser operatorUser = siteClient.queryOperator(stores);
//
//        String alipayProportion = operatorUser.getAlipayProportion();
//        String alipayNum = operatorUser.getAlipayNum();
//
//        ExtendParams extendParams = new ExtendParams();
////        extendParams.setSysServiceProviderId("YOUR_SERVICE_PROVIDER_ID");
//        model.setExtendParams(extendParams);
//
//        RoyaltyInfo royaltyInfo = new RoyaltyInfo();
////        royaltyInfo.setRoyaltyType("transfer");
//
//
//        RoyaltyDetailInfos royaltyDetailInfo1 = new RoyaltyDetailInfos();
//        royaltyDetailInfo1.setTransOutType("userId");
//        royaltyDetailInfo1.setTransOut(aliAppid);
//        royaltyDetailInfo1.setTransInType("loginName");
//        royaltyDetailInfo1.setTransIn("18398968484");
//
//        royaltyDetailInfo1.setDesc("分账描述1");
//        royaltyDetailInfo1.setAmountPercentage(alipayProportion);
//        List<RoyaltyDetailInfos> royaltyDetailInfos = new ArrayList<>();
//
//
//
//        royaltyInfo.setRoyaltyDetailInfos(royaltyDetailInfos);
//        model.setRoyaltyInfo(royaltyInfo);
//        System.err.println("=================="+royaltyInfo);
        //
        request.setBizModel(model);
        request.setNotifyUrl(callbackPath + notifyUrl);
        try {
@@ -547,17 +512,10 @@
        map.put("appid", appid);
        map.put("mch_id", mchId);
        map.put("nonce_str", nonce_str);
        String temp = "";
        if (body.split("-").length>1){
             temp = body.split("-")[1];
            map.put("body", body.split("-")[0]);
        }else{
            map.put("body", body);
        }
        if (StringUtils.hasLength(temp) && temp.equals("1")){
            // 添加分账标识
            map.put("profit_sharing", "Y");
        }
        map.put("attach", attach);//存储订单id
        map.put("out_trade_no", out_trade_no);//存储的订单code
        map.put("total_fee", i);
@@ -649,6 +607,62 @@
            return ResultUtil.error(map1.get("return_msg"), new JSONObject());
        }
    }
    public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee) throws Exception {
        int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue();
        // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
        PartnerAppPrepay client = new PartnerAppPrepay(
//                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//                "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//                "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
                "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
                "/usr/playpai/server/wxV3/pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
        );
        PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest();
        request.spAppid = appid;// appid
        request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号
        request.subMchid = subMchid;// 子商户号
        request.description = description;// 描述
        request.outTradeNo = outTradeNo;// 订单号
        request.notifyUrl = callbackPath+notifyUrl;// 回调地址
        request.amount = new PartnerAppPrepay.CommonAmountInfo();
        request.amount.total = (long) i;// 金额 单位分
        request.amount.currency = "CNY";
        String prepayId ="";
        Map<String, Object> map3 = new HashMap<>();
        try {
            PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request);
            // TODO: 请求成功,继续业务逻辑
            System.err.println("微信申请成功,预支付ID: " + response.prepayId);
            prepayId = response.prepayId;
        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败,根据状态码执行不同的逻辑
            e.printStackTrace();
        }
        map3.put("appid", appid);
        map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        String nonce_str = UUIDUtil.getRandomCode(16);
        map3.put("noncestr", nonce_str);
        map3.put("prepayid", prepayId);
        // 构造待签名字符串
        String message = WeChatV3SignUtil.buildSignMessage(map3);
        // 私钥路径(pem 文件)
        String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
//      String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem";
        // 生成签名
        String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath);
        map3.put("sign", sign);
        map3.put("package", "Sign=WXPay");
        map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号
        System.err.println(map3);
        return ResultUtil.success(map3);
    }
    @Resource
cloud-server-account/src/main/java/com/dsh/account/util/wx/PartnerAppPrepay.java
New file
@@ -0,0 +1,237 @@
package com.dsh.account.util.wx;
import com.google.gson.annotations.SerializedName;
import okhttp3.*;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;
/**
 * App下单
 */
public class PartnerAppPrepay {
  private static String HOST = "https://api.mch.weixin.qq.com";
  private static String METHOD = "POST";
  private static String PATH = "/v3/pay/partner/transactions/app";
//  public static void main(String[] args) {
//    // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
//    PartnerAppPrepay client = new PartnerAppPrepay(
//        "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//        "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//        "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//        "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//        "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
//    );
//
//    PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest();
//    request.spAppid = "wx41d32f362ba0f911";
//    request.spMchid = WxV3PayConfig.Mch_ID;
//    request.subMchid = "1720719391";
//    request.description = "Image形象店-深圳腾大-QQ公仔";
//    request.outTradeNo = "12177525012014070332333680182";
//    request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php";
////    request.goodsTag = "WXG";
////    request.settleInfo = new PartnerSettleInfo();
////    request.settleInfo.profitSharing = false;
//    request.amount = new CommonAmountInfo();
//    request.amount.total = 100L;
//    request.amount.currency = "CNY";
////    request.detail = new CouponInfo();
////    request.detail.costPrice = 1L;
////    request.detail.invoiceId = "wx123";
////    request.detail.goodsDetail = new ArrayList<>();
////    {
////      GoodsDetail item0 = new GoodsDetail();
////      item0.merchantGoodsId = "1246464644";
////      item0.wechatpayGoodsId = "1001";
////      item0.goodsName = "iPhone6s 16G";
////      item0.quantity = 1L;
////      item0.unitPrice = 528800L;
////      request.detail.goodsDetail.add(item0);
////    };
////    request.sceneInfo = new CommonSceneInfo();
////    request.sceneInfo.payerClientIp = "14.23.150.211";
////    request.sceneInfo.deviceId = "013467007045764";
////    request.sceneInfo.storeInfo = new StoreInfo();
////    request.sceneInfo.storeInfo.id = "0001";
////    request.sceneInfo.storeInfo.name = "腾讯大厦分店";
////    request.sceneInfo.storeInfo.areaCode = "440305";
////    request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
//    try {
//      PartnerAPIv3AppPrepayResponse response = client.run(request);
//
//      // TODO: 请求成功,继续业务逻辑
//      System.err.println("微信申请成功,预支付ID: " + response.prepayId);
//    } catch (WXPayUtility.ApiException e) {
//      // TODO: 请求失败,根据状态码执行不同的逻辑
//      e.printStackTrace();
//    }
//  }
  public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) {
    String uri = PATH;
    String reqBody = WXPayUtility.toJson(request);
    Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
    reqBuilder.addHeader("Accept", "application/json");
    reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
    reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
    reqBuilder.addHeader("Content-Type", "application/json");
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
    reqBuilder.method(METHOD, requestBody);
    Request httpRequest = reqBuilder.build();
    // 发送HTTP请求
    OkHttpClient client = new OkHttpClient.Builder().build();
    try (Response httpResponse = client.newCall(httpRequest).execute()) {
      String respBody = WXPayUtility.extractBody(httpResponse);
      if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
        // 2XX 成功,验证应答签名
        WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                                      httpResponse.headers(), respBody);
        // 从HTTP应答报文构建返回数据
        return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class);
      } else {
        throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
      }
    } catch (IOException e) {
      throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
    }
  }
  private final String mchid;
  private final String certificateSerialNo;
  private final PrivateKey privateKey;
  private final String wechatPayPublicKeyId;
  private final PublicKey wechatPayPublicKey;
  public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
    this.mchid = mchid;
    this.certificateSerialNo = certificateSerialNo;
    this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
    this.wechatPayPublicKeyId = wechatPayPublicKeyId;
    this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
  }
  public static class PartnerSettleInfo {
    @SerializedName("profit_sharing")
    public Boolean profitSharing;
  }
  public static class CommonAmountInfo {
    @SerializedName("total")
    public Long total;
    @SerializedName("currency")
    public String currency;
  }
  public static class CommonSceneInfo {
    @SerializedName("payer_client_ip")
    public String payerClientIp;
    @SerializedName("device_id")
    public String deviceId;
    @SerializedName("store_info")
    public StoreInfo storeInfo;
  }
  public static class GoodsDetail {
    @SerializedName("merchant_goods_id")
    public String merchantGoodsId;
    @SerializedName("wechatpay_goods_id")
    public String wechatpayGoodsId;
    @SerializedName("goods_name")
    public String goodsName;
    @SerializedName("quantity")
    public Long quantity;
    @SerializedName("unit_price")
    public Long unitPrice;
  }
  public static class PartnerAPIv3AppPrepayResponse {
    @SerializedName("prepay_id")
    public String prepayId;
  }
  public static class PartnerAPIv3CommonPrepayRequest {
    @SerializedName("sp_appid")
    public String spAppid;
    @SerializedName("sp_mchid")
    public String spMchid;
    @SerializedName("sub_appid")
    public String subAppid;
    @SerializedName("sub_mchid")
    public String subMchid;
    @SerializedName("description")
    public String description;
    @SerializedName("out_trade_no")
    public String outTradeNo;
    @SerializedName("time_expire")
    public String timeExpire;
    @SerializedName("attach")
    public String attach;
    @SerializedName("notify_url")
    public String notifyUrl;
    @SerializedName("goods_tag")
    public String goodsTag;
    @SerializedName("settle_info")
    public PartnerSettleInfo settleInfo;
    @SerializedName("support_fapiao")
    public Boolean supportFapiao;
    @SerializedName("amount")
    public CommonAmountInfo amount;
    @SerializedName("detail")
    public CouponInfo detail;
    @SerializedName("scene_info")
    public CommonSceneInfo sceneInfo;
  }
  public static class CouponInfo {
    @SerializedName("cost_price")
    public Long costPrice;
    @SerializedName("invoice_id")
    public String invoiceId;
    @SerializedName("goods_detail")
    public List<GoodsDetail> goodsDetail;
  }
  public static class StoreInfo {
    @SerializedName("id")
    public String id;
    @SerializedName("name")
    public String name;
    @SerializedName("area_code")
    public String areaCode;
    @SerializedName("address")
    public String address;
  }
}
cloud-server-account/src/main/java/com/dsh/account/util/wx/WXPayUtility.java
New file
@@ -0,0 +1,441 @@
package com.dsh.account.util.wx;
import com.google.gson.*;
import com.google.gson.annotations.Expose;
import com.wechat.pay.java.core.util.GsonUtil;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
public class WXPayUtility {
  private static final Gson gson = new GsonBuilder()
      .disableHtmlEscaping()
      .addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.serialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .addDeserializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.deserialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .create();
  private static final char[] SYMBOLS =
      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
  private static final SecureRandom random = new SecureRandom();
  /**
   * 将 Object 转换为 JSON 字符串
   */
  public static String toJson(Object object) {
    return gson.toJson(object);
  }
  /**
   * 将 JSON 字符串解析为特定类型的实例
   */
  public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    return gson.fromJson(json, classOfT);
  }
  /**
   * 从公私钥文件路径中读取文件内容
   *
   * @param keyPath 文件路径
   * @return 文件内容
   */
  private static String readKeyStringFromPath(String keyPath) {
    try {
      return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }
  /**
   * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
   *
   * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
          .replace("-----END PRIVATE KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePrivate(
          new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的私钥文件中加载私钥
   *
   * @param keyPath 私钥文件路径
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
    return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
   *
   * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
          .replace("-----END PUBLIC KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePublic(
          new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的公钥文件中加载公钥
   *
   * @param keyPath 公钥文件路径
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromPath(String keyPath) {
    return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
   */
  public static String createNonce(int length) {
    char[] buf = new char[length];
    for (int i = 0; i < length; ++i) {
      buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
    }
    return new String(buf);
  }
  /**
   * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
   *
   * @param publicKey 加密用公钥对象
   * @param plaintext 待加密明文
   * @return 加密后密文
   */
  public static String encrypt(PublicKey publicKey, String plaintext) {
    final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
    try {
      Cipher cipher = Cipher.getInstance(transformation);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
      throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
      throw new IllegalArgumentException("Plaintext is too long", e);
    }
  }
  /**
   * 使用私钥按照指定算法进行签名
   *
   * @param message 待签名串
   * @param algorithm 签名算法,如 SHA256withRSA
   * @param privateKey 签名用私钥对象
   * @return 签名结果
   */
  public static String sign(String message, String algorithm, PrivateKey privateKey) {
    byte[] sign;
    try {
      Signature signature = Signature.getInstance(algorithm);
      signature.initSign(privateKey);
      signature.update(message.getBytes(StandardCharsets.UTF_8));
      sign = signature.sign();
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
    } catch (SignatureException e) {
      throw new RuntimeException("An error occurred during the sign process.", e);
    }
    return Base64.getEncoder().encodeToString(sign);
  }
  /**
   * 使用公钥按照特定算法验证签名
   *
   * @param message 待签名串
   * @param signature 待验证的签名内容
   * @param algorithm 签名算法,如:SHA256withRSA
   * @param publicKey 验签用公钥对象
   * @return 签名验证是否通过
   */
  public static boolean verify(String message, String signature, String algorithm,
                               PublicKey publicKey) {
    try {
      Signature sign = Signature.getInstance(algorithm);
      sign.initVerify(publicKey);
      sign.update(message.getBytes(StandardCharsets.UTF_8));
      return sign.verify(Base64.getDecoder().decode(signature));
    } catch (SignatureException e) {
      return false;
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("verify uses an illegal publickey.", e);
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
    }
  }
  /**
   * 根据微信支付APIv3请求签名规则构造 Authorization 签名
   *
   * @param mchid 商户号
   * @param certificateSerialNo 商户API证书序列号
   * @param privateKey 商户API证书私钥
   * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
   * @param uri 请求接口的URL
   * @param body 请求接口的Body
   * @return 构造好的微信支付APIv3 Authorization 头
   */
  public static String buildAuthorization(String mchid, String certificateSerialNo,
                                          PrivateKey privateKey,
                                          String method, String uri, String body) {
    String nonce = createNonce(32);
    long timestamp = Instant.now().getEpochSecond();
    String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
        body == null ? "" : body);
    String signature = sign(message, "SHA256withRSA", privateKey);
    return String.format(
        "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
            "timestamp=\"%d\",serial_no=\"%s\"",
        mchid, nonce, signature, timestamp, certificateSerialNo);
  }
  /**
   * 对参数进行 URL 编码
   *
   * @param content 参数内容
   * @return 编码后的内容
   */
  public static String urlEncode(String content) {
    try {
      return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }
  /**
   * 对参数Map进行 URL 编码,生成 QueryString
   *
   * @param params Query参数Map
   * @return QueryString
   */
  public static String urlEncode(Map<String, Object> params) {
    if (params == null || params.isEmpty()) {
      return "";
    }
    int index = 0;
    StringBuilder result = new StringBuilder();
    for (Map.Entry<String, Object> entry : params.entrySet()) {
      result.append(entry.getKey())
          .append("=")
          .append(urlEncode(entry.getValue().toString()));
      index++;
      if (index < params.size()) {
        result.append("&");
      }
    }
    return result.toString();
  }
  /**
   * 从应答中提取 Body
   *
   * @param response HTTP 请求应答对象
   * @return 应答中的Body内容,Body为空时返回空字符串
   */
  public static String extractBody(Response response) {
    if (response.body() == null) {
      return "";
    }
    try {
      BufferedSource source = response.body().source();
      return source.readUtf8();
    } catch (IOException e) {
      throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e);
    }
  }
  /**
   * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
   *
   * @param wechatpayPublicKeyId 微信支付公钥ID
   * @param wechatpayPublicKey 微信支付公钥对象
   * @param headers 微信支付应答 Header 列表
   * @param body 微信支付应答 Body
   */
  public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                      Headers headers,
                                      String body) {
    String timestamp = headers.get("Wechatpay-Timestamp");
    try {
      Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
      // 拒绝过期请求
      if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
        throw new IllegalArgumentException(
            String.format("Validate http response,timestamp[%s] of httpResponse is expires, "
                    + "request-id[%s]",
                timestamp, headers.get("Request-ID")));
      }
    } catch (DateTimeException | NumberFormatException e) {
      throw new IllegalArgumentException(
          String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " +
                  "request-id[%s]", timestamp,
              headers.get("Request-ID")));
    }
    String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
        body == null ? "" : body);
    String serialNumber = headers.get("Wechatpay-Serial");
    if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
      throw new IllegalArgumentException(
          String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId,
              serialNumber));
    }
    String signature = headers.get("Wechatpay-Signature");
    boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
    if (!success) {
      throw new IllegalArgumentException(
          String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                  + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
              headers.get("Request-ID"), headers, body));
    }
  }
  /**
   * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
   */
  public static class ApiException extends RuntimeException {
    private static final long serialVersionUID = 2261086748874802175L;
    private final int statusCode;
    private final String body;
    private final Headers headers;
    private final String errorCode;
    private final String errorMessage;
    public ApiException(int statusCode, String body, Headers headers) {
      super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers));
      this.statusCode = statusCode;
      this.body = body;
      this.headers = headers;
      if (body != null && !body.isEmpty()) {
        JsonElement code;
        JsonElement message;
        try {
          JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class);
          code = jsonObject.get("code");
          message = jsonObject.get("message");
        } catch (JsonSyntaxException ignored) {
          code = null;
          message = null;
        }
        this.errorCode = code == null ? null : code.getAsString();
        this.errorMessage = message == null ? null : message.getAsString();
      } else {
        this.errorCode = null;
        this.errorMessage = null;
      }
    }
    /**
     * 获取 HTTP 应答状态码
     */
    public int getStatusCode() {
      return statusCode;
    }
    /**
     * 获取 HTTP 应答包体内容
     */
    public String getBody() {
      return body;
    }
    /**
     * 获取 HTTP 应答 Header
     */
    public Headers getHeaders() {
      return headers;
    }
    /**
     * 获取 错误码 (错误应答中的 code 字段)
     */
    public String getErrorCode() {
      return errorCode;
    }
    /**
     * 获取 错误消息 (错误应答中的 message 字段)
     */
    public String getErrorMessage() {
      return errorMessage;
    }
  }
}
cloud-server-account/src/main/java/com/dsh/account/util/wx/WeChatSignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.account.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
public class WeChatSignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 使用商户私钥对字符串进行 SHA256withRSA 签名
     *
     * @param content      待签名字符串
     * @param privateKeyPath 商户私钥文件路径(pem 格式)
     * @return 签名结果(Base64 编码)
     */
    public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception {
        String privateKeyPEM = readPemFile(privateKeyPath);
        String privateKeyContent = privateKeyPEM
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");
        byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }
    private static String readPemFile(String filePath) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
        }
        return sb.toString();
    }
    /**
     * 按照微信 V3 规范生成待签名字符串
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            Object value = map.get(key);
            if (value != null && !value.toString().isEmpty()) {
                sb.append(key).append("=").append(value).append("\n");
            }
        }
        return sb.toString();
    }
}
cloud-server-account/src/main/java/com/dsh/account/util/wx/WeChatV3SignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.account.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
public class WeChatV3SignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 从 PEM 文件中读取私钥内容
     */
    public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith("-----") && !line.endsWith("-----")) {
                    sb.append(line);
                }
            }
        }
        byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
        KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
        return kf.generatePrivate(keySpec);
    }
    /**
     * 构造待签名字符串(按 key 字典序排序)
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
//        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        sb.append(map.get("appid")).append("\n");
        sb.append(map.get("timestamp")).append("\n");
        sb.append(map.get("noncestr")).append("\n");
        sb.append(map.get("prepayid")).append("\n");
        return sb.toString();
    }
    /**
     * 使用商户私钥生成签名(SHA256withRSA + Base64)
     */
    public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception {
        PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signedBytes);
    }
}
cloud-server-account/src/main/java/com/dsh/account/util/wx/WxV3PayConfig.java
New file
@@ -0,0 +1,66 @@
package com.dsh.account.util.wx;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class WxV3PayConfig {
    // 服务商AppId
    private String appIdValue = "wx41d32f362ba0f911";
    public static String APP_ID= "wx41d32f362ba0f911";
    // 服务商商户号
    private String mchIdValue= "1681873607";
    public static String Mch_ID= "1681873607";
    // 平台收款商户号 todo 待申请
    public static String smidVx= "2088330203191220";
    private  String smidVxValue= "2088330203191220";
    // 服务商商户私钥
    private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq";
    public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq";
    // 证书序列号
    private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A";
    public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A";
    private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量
    @PostConstruct
    public void init() {
        APP_ID = this.appIdValue;
        APP_ID = this.appIdValue;
        smidVx = this.smidVxValue;
        apiV3Key = this.apiV3KeyValue;
        mchSerialNo = this.mchSerialNoValue;
        privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径
        // 可以在这里加一些非空检查
        if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) {
             System.err.println("微信支付V3配置加载不完整,请检查配置文件!");
             // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施
        } else {
             System.out.println("微信支付V3配置加载完成。");
        }
    }
    // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath
    // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法:
    /*
    public static PrivateKey getPrivateKey() {
        if (cachedPrivateKey != null) {
            return cachedPrivateKey;
        }
        try {
            String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径
            // ... rest of the method ...
        } // ... catch blocks ...
    }
    */
}
cloud-server-activity/pom.xml
@@ -14,6 +14,15 @@
    <name>福利</name>
    <description>福利</description>
    <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <!-- OkHttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
        <!-- 微信支付V3 目前新版本-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminCallBack.java
@@ -1,5 +1,6 @@
package com.dsh.activity.controller;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dsh.activity.entity.THuiminAgreement;
@@ -17,6 +18,8 @@
import com.dsh.activity.util.ResultUtil;
import com.dsh.activity.util.TokenUtil;
import com.dsh.activity.util.UUIDUtil;
import com.dsh.activity.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -30,8 +33,10 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -40,6 +45,7 @@
/**
 * 玩湃惠民卡回调
 *
 * @author zhibing.pu
 * @date 2023/6/24 11:27
 */
@@ -113,6 +119,7 @@
            e.printStackTrace();
        }
    }
    /**
     * 玩湃惠民卡微信支付回调接口
     */
@@ -143,18 +150,76 @@
                        out.close();
                    }else{
                        PrintWriter out = response.getWriter();
                        out.write(result);
                        out.write("SUCCESS");
                        out.flush();
                        out.close();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @PostMapping("/weixinPayHuiminCallback1")
    public void weixinPayHuiminCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String out_trade_no = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                TPayHuimin one = payHuiminService.getOne(new LambdaQueryWrapper<TPayHuimin>().eq(TPayHuimin::getCode, out_trade_no));
                if (one != null) {
                    if (one.getStatus() == 2) {
                        PrintWriter out = response.getWriter();
                        out.write("SUCCESS");
                        out.flush();
                        out.close();
                    } else if (one.getStatus() == 1) {
                        one.setOrderNumber(transaction_id);
                        one.setStatus(2);
                        one.setPaymentTime(new Date());
                        payHuiminService.updateById(one);
                        PrintWriter out = response.getWriter();
                        out.write("SUCCESS");
                        out.flush();
                        out.close();
                    } else {
                        PrintWriter out = response.getWriter();
                        out.write("SUCCESS");
                        out.flush();
                        out.close();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 玩湃惠民卡微信退款回调
     *
     * @param request
     * @param response
     */
cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java
@@ -28,6 +28,7 @@
import com.dsh.activity.model.response.*;
import com.dsh.activity.service.*;
import com.dsh.activity.util.*;
import com.dsh.activity.util.wx.WxV3PayConfig;
import com.google.gson.Gson;
import io.swagger.annotations.*;
import io.swagger.models.auth.In;
@@ -84,6 +85,8 @@
    @Autowired
    private TokenUtil tokenUtil;
    private String smid = "2088330203191220";//平台支付宝商户号
    // todo 待申请
    private String smidVx = "2088330203191220";//平台微信商户号
    /**
@@ -159,11 +162,14 @@
            BigDecimal bigDecimal = new BigDecimal(studentIds.split(",").length);
            switch (payType) {
                case 1:
                    return payMoneyUtil.weixinpay("购买玩湃惠民卡" + "-" + 0, "", tPayHuimin.getCode(), tPayHuimin.getSalesMoney().toString(),
                    // todo 惠民卡收入属于平台商户 定死
                    return payMoneyUtil.weixinpay("购买玩湃惠民卡", "", tPayHuimin.getCode(), tPayHuimin.getSalesMoney().toString(),
                            "/base/huimin/callBack/weixinPayHuiminCallback", "APP", "");
//                return payMoneyUtil.weixinpayV3("1720719391","购买玩湃惠民卡",tPayHuimin.getCode(),
//                        "/base/huimin/callBack/weixinPayHuiminCallback1",tPayHuimin.getSalesMoney().toString());
                case 2:
                    String string = tPayHuimin.getSalesMoney().toString();
                    return payMoneyUtil.alipay(smid, "购买玩湃惠民卡", "购买玩湃惠民卡", "", tPayHuimin.getCode(), string,
                    return payMoneyUtil.alipay("2088670241691219", "购买玩湃惠民卡", "购买玩湃惠民卡", "", tPayHuimin.getCode(), string,
                            "/base/huimin/callBack/aliPayHuiminCallback");
            }
cloud-server-activity/src/main/java/com/dsh/activity/controller/WeiXinV3Controller.java
@@ -1,76 +1,81 @@
//package com.dsh.activity.controller;
//
//import cn.hutool.core.collection.CollUtil;
//import com.alibaba.fastjson.JSONArray;
//import com.alibaba.fastjson.JSONObject;
//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
//import com.dsh.activity.entity.*;
//import com.dsh.activity.feignclient.account.StudentClient;
//import com.dsh.activity.feignclient.other.SiteClient;
//import com.dsh.activity.feignclient.other.StoreClient;
//import com.dsh.activity.feignclient.other.model.Site;
//import com.dsh.activity.feignclient.other.model.Store;
//import com.dsh.activity.model.response.*;
//import com.dsh.activity.service.*;
//import com.dsh.activity.util.*;
////import com.dsh.activity.util.wx.WechatPaymentService;
//import com.dsh.activity.util.wx.WxAppPayService;
//import io.swagger.annotations.Api;
//import io.swagger.annotations.ApiImplicitParam;
//import io.swagger.annotations.ApiImplicitParams;
//import io.swagger.annotations.ApiOperation;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.web.bind.annotation.PostMapping;
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.ResponseBody;
//import org.springframework.web.bind.annotation.RestController;
//
//import javax.annotation.Resource;
//import java.math.BigDecimal;
//import java.text.SimpleDateFormat;
//import java.time.LocalDate;
//import java.time.LocalDateTime;
//import java.time.ZoneId;
//import java.util.*;
//import java.util.stream.Collectors;
//
///**
// * @author zhibing.pu
// * @date 2023/6/24 11:27
// */
//@RestController
//@RequestMapping("/base")
//public class WeiXinV3Controller {
//
////    @Resource
////    private WechatPaymentService wechatPaymentService;
package com.dsh.activity.controller;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dsh.activity.entity.*;
import com.dsh.activity.feignclient.account.StudentClient;
import com.dsh.activity.feignclient.other.SiteClient;
import com.dsh.activity.feignclient.other.StoreClient;
import com.dsh.activity.feignclient.other.model.Site;
import com.dsh.activity.feignclient.other.model.Store;
import com.dsh.activity.model.response.*;
import com.dsh.activity.service.*;
import com.dsh.activity.util.*;
//import com.dsh.activity.util.wx.WechatPaymentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.security.PrivateKey;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author zhibing.pu
 * @date 2023/6/24 11:27
 */
@RestController
@RequestMapping("/base")
public class WeiXinV3Controller {
    //    @Resource
//    private WechatPaymentService wechatPaymentService;
//    @Autowired
//    private WxAppPayService wxAppPayService;
//    @ResponseBody
//    @PostMapping("/testWeiXinV3")
//    @ApiOperation(value = "获取添加人员、选择人员说明文案")
//    @ApiImplicitParams({
//            @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....")
//    })
//    public ResultUtil getContentForStudent() throws Exception {
////        Map<String, Object> stringObjectMap = wechatPaymentService.weChatDoUnifiedOrder();
//        // ... 在你的下单方法中调用 ...
    @Autowired
    private PayMoneyUtil payMoneyUtil;
    @ResponseBody
    @PostMapping("/testWeiXinV3")
    @ApiOperation(value = "获取添加人员、选择人员说明文案")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....")
    })
    public ResultUtil getContentForStudent() throws Exception {
//        Map<String, Object> stringObjectMap = wechatPaymentService.weChatDoUnifiedOrder();
        // ... 在你的下单方法中调用 ...
        try {
            ResultUtil resultUtil = payMoneyUtil.weixinpayV3("1720719391","购买玩湃惠民卡","15645654askjak13",
                    "/base/huimin/callBack/weixinPayHuiminCallback1","99");
            return resultUtil;
            // 将 payParams 返回给你的APP前端,前端使用这些参数调起微信支付SDK
            // return ResponseEntity.ok(payParams); // 示例
        } catch (Exception e) {
            // 处理异常
            // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("下单失败");
        }
        return ResultUtil.success();
    }
//    @PostMapping("/test-private-key")
//    public String testPrivateKey() {
//        try {
//            String description = "商品描述";
//            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
//            String outTradeNo =sdf.format(new Date()) + UUIDUtil.getNumberRandom(5);
//            BigDecimal amount = new BigDecimal("0.01"); // 支付金额,例如1分钱
//            Map<String, String> payParams = wxAppPayService.createOrder(description, outTradeNo, amount);
//            return ResultUtil.success(payParams);
//
//            // 将 payParams 返回给你的APP前端,前端使用这些参数调起微信支付SDK
//            // return ResponseEntity.ok(payParams); // 示例
//
//            PrivateKey privateKey = WXPaySignatureCertificateUtil.getPrivateKey();
//            return "私钥加载成功: " + privateKey.getAlgorithm();
//        } catch (Exception e) {
//            // 处理异常
//            // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("下单失败");
//            return "私钥加载失败: " + e.getMessage();
//        }
//        return ResultUtil.success();
//    }
//
//}
}
cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java
@@ -11,6 +11,7 @@
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.dsh.activity.util.httpClinet.HttpClientUtil;
import com.dsh.activity.util.wx.*;
import org.apache.commons.collections.map.HashedMap;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.dom4j.Document;
@@ -29,6 +30,7 @@
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@@ -70,8 +72,8 @@
    private String alipay_root_cert_path = "C:/cert/alipay/user/alipay_root_cert_path.crt";//支付宝CA根证书文件路径
    private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书
//    private String certPath = "D:/apiclient_cert.p12";//微信证书
    //    private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书
    private String certPath = "E:\\wanpaiapiclient_cert.p12";//微信证书
    public ResultUtil confirm(String smid,String code, String outTradeNo, String amount) {
        AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",
@@ -113,6 +115,7 @@
            return ResultUtil.error("出现问题啦");
        }
    }
    // 属于平台的运营商 因为无需分账不冻结资金
    public ResultUtil confirm1(String smid,String code, String outTradeNo, String amount) {
        AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",
@@ -238,9 +241,7 @@
        SubMerchant subMerchant = new SubMerchant();
        subMerchant.setMerchantId(smid);
        model.setSubMerchant(subMerchant);
        ExtendParams extendParams = new ExtendParams();
        extendParams.setRoyaltyFreeze("false");// 冻结资金 用于后续分账处理
        model.setExtendParams(extendParams);
        request.setBizModel(model);
        request.setNotifyUrl("http://8.137.22.229:5002" + notifyUrl);
@@ -383,6 +384,62 @@
        }
    }
    public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee) throws Exception {
        int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue();
        // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
        PartnerAppPrepay client = new PartnerAppPrepay(
//                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//                "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//                "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
                "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
                "/usr/playpai/server/wxV3/pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
        );
        PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest();
        request.spAppid = appid;// appid
        request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号
        request.subMchid = subMchid;// 子商户号
        request.description = description;// 描述
        request.outTradeNo = outTradeNo;// 订单号
        request.notifyUrl =callbackPath+ notifyUrl;// 回调地址
        request.amount = new PartnerAppPrepay.CommonAmountInfo();
        request.amount.total = (long) i;// 金额 单位分
        request.amount.currency = "CNY";
        String prepayId ="";
        Map<String, Object> map3 = new HashMap<>();
        try {
            PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request);
            // TODO: 请求成功,继续业务逻辑
            System.err.println("微信申请成功,预支付ID: " + response.prepayId);
            prepayId = response.prepayId;
        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败,根据状态码执行不同的逻辑
            e.printStackTrace();
        }
        map3.put("appid", appid);
        map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        String nonce_str = UUIDUtil.getRandomCode(16);
        map3.put("noncestr", nonce_str);
        map3.put("prepayid", prepayId);
        // 构造待签名字符串
        String message = WeChatV3SignUtil.buildSignMessage(map3);
        // 私钥路径(pem 文件)
        String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
//      String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem";
        // 生成签名
        String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath);
        map3.put("sign", sign);
        map3.put("package", "Sign=WXPay");
        map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号
        System.err.println(map3);
        return ResultUtil.success(map3);
    }
    /**
     * 微信统一下单
@@ -410,16 +467,8 @@
//        map.put("sub_mch_id", "123456");
        map.put("nonce_str", nonce_str);
        String temp = "";
        if (body.split("-").length>1){
            temp = body.split("-")[1];
            map.put("body", body.split("-")[0]);
        }else{
            map.put("body", body);
        }
        if (StringUtils.hasLength(temp) && temp.equals("1")){
            // 添加分账标识
            map.put("profit_sharing", "Y");
        }
        map.put("attach", attach);//存储订单id
        map.put("out_trade_no", out_trade_no);//存储的订单code
        map.put("total_fee", i);
@@ -509,8 +558,6 @@
            return ResultUtil.error(map1.get("return_msg"), new JSONObject());
        }
    }
    /**
@@ -1149,6 +1196,46 @@
        return sb.toString();
    }
    public String weixinSignatureWithSHA256(Map<String, Object> map, String key) {
        try {
            Set<Map.Entry<String, Object>> entries = map.entrySet();
            List<Map.Entry<String, Object>> infoIds = new ArrayList<>(entries);
            // 按 key 的 ASCII 字典序排序
            Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
                public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                    return (o1.getKey()).toString().compareTo(o2.getKey());
                }
            });
            // 构造待签名字符串
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, Object> item : infoIds) {
                if (item.getKey() != null && !item.getKey().isEmpty()) {
                    String keyName = item.getKey();
                    Object val = item.getValue();
                    if (val != null && !val.toString().isEmpty()) {
                        sb.append(keyName).append("=").append(val).append("\n");
                    }
                }
            }
            sb.deleteCharAt(sb.length() - 1); // 删除最后一个 &
            // 使用 SHA256-HMAC 加密
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256_HMAC.init(secretKey);
            byte[] hashBytes = sha256_HMAC.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8));
            String sign = byteArrayToHexString(hashBytes).toUpperCase();
            return sign;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 微信下单的签名算法
@@ -1185,6 +1272,7 @@
        }
        return null;
    }
    private String weixinSignature1(Map<String, Object> map) {
        try {
            Set<Map.Entry<String, Object>> entries = map.entrySet();
@@ -1206,16 +1294,18 @@
                    }
                }
            }
            sb.append("key=" + key);
            String sign = sha256_HMAC(sb.toString(), key).toUpperCase();
            sb.append("key=" + "1skiujh28376shznxmslwosiusytersq");
            String sign = MD5AndKL.MD5Encode(sb.toString(), "UTF-8").toUpperCase(); //注:MD5签名方式
            return sign;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * sha256_HMAC加密
     *
     * @param message 消息
     * @param secret  秘钥
     * @return 加密后字符串
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/PartnerAppPrepay.java
New file
@@ -0,0 +1,246 @@
package com.dsh.activity.util.wx;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
// 引用微信支付工具库 参考:https://pay.weixin.qq.com/doc/v3/partner/4014985777
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
 * App下单
 */
public class PartnerAppPrepay {
  private static String HOST = "https://api.mch.weixin.qq.com";
  private static String METHOD = "POST";
  private static String PATH = "/v3/pay/partner/transactions/app";
//  public static void main(String[] args) {
//    // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
//    PartnerAppPrepay client = new PartnerAppPrepay(
//        "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//        "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//        "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//        "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//        "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
//    );
//
//    PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest();
//    request.spAppid = "wx41d32f362ba0f911";
//    request.spMchid = WxV3PayConfig.Mch_ID;
//    request.subMchid = "1720719391";
//    request.description = "Image形象店-深圳腾大-QQ公仔";
//    request.outTradeNo = "12177525012014070332333680182";
//    request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php";
////    request.goodsTag = "WXG";
////    request.settleInfo = new PartnerSettleInfo();
////    request.settleInfo.profitSharing = false;
//    request.amount = new CommonAmountInfo();
//    request.amount.total = 100L;
//    request.amount.currency = "CNY";
////    request.detail = new CouponInfo();
////    request.detail.costPrice = 1L;
////    request.detail.invoiceId = "wx123";
////    request.detail.goodsDetail = new ArrayList<>();
////    {
////      GoodsDetail item0 = new GoodsDetail();
////      item0.merchantGoodsId = "1246464644";
////      item0.wechatpayGoodsId = "1001";
////      item0.goodsName = "iPhone6s 16G";
////      item0.quantity = 1L;
////      item0.unitPrice = 528800L;
////      request.detail.goodsDetail.add(item0);
////    };
////    request.sceneInfo = new CommonSceneInfo();
////    request.sceneInfo.payerClientIp = "14.23.150.211";
////    request.sceneInfo.deviceId = "013467007045764";
////    request.sceneInfo.storeInfo = new StoreInfo();
////    request.sceneInfo.storeInfo.id = "0001";
////    request.sceneInfo.storeInfo.name = "腾讯大厦分店";
////    request.sceneInfo.storeInfo.areaCode = "440305";
////    request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
//    try {
//      PartnerAPIv3AppPrepayResponse response = client.run(request);
//
//      // TODO: 请求成功,继续业务逻辑
//      System.err.println("微信申请成功,预支付ID: " + response.prepayId);
//    } catch (WXPayUtility.ApiException e) {
//      // TODO: 请求失败,根据状态码执行不同的逻辑
//      e.printStackTrace();
//    }
//  }
  public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) {
    String uri = PATH;
    String reqBody = WXPayUtility.toJson(request);
    Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
    reqBuilder.addHeader("Accept", "application/json");
    reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
    reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
    reqBuilder.addHeader("Content-Type", "application/json");
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
    reqBuilder.method(METHOD, requestBody);
    Request httpRequest = reqBuilder.build();
    // 发送HTTP请求
    OkHttpClient client = new OkHttpClient.Builder().build();
    try (Response httpResponse = client.newCall(httpRequest).execute()) {
      String respBody = WXPayUtility.extractBody(httpResponse);
      if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
        // 2XX 成功,验证应答签名
        WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                                      httpResponse.headers(), respBody);
        // 从HTTP应答报文构建返回数据
        return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class);
      } else {
        throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
      }
    } catch (IOException e) {
      throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
    }
  }
  private final String mchid;
  private final String certificateSerialNo;
  private final PrivateKey privateKey;
  private final String wechatPayPublicKeyId;
  private final PublicKey wechatPayPublicKey;
  public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
    this.mchid = mchid;
    this.certificateSerialNo = certificateSerialNo;
    this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
    this.wechatPayPublicKeyId = wechatPayPublicKeyId;
    this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
  }
  public static class PartnerSettleInfo {
    @SerializedName("profit_sharing")
    public Boolean profitSharing;
  }
  public static class CommonAmountInfo {
    @SerializedName("total")
    public Long total;
    @SerializedName("currency")
    public String currency;
  }
  public static class CommonSceneInfo {
    @SerializedName("payer_client_ip")
    public String payerClientIp;
    @SerializedName("device_id")
    public String deviceId;
    @SerializedName("store_info")
    public StoreInfo storeInfo;
  }
  public static class GoodsDetail {
    @SerializedName("merchant_goods_id")
    public String merchantGoodsId;
    @SerializedName("wechatpay_goods_id")
    public String wechatpayGoodsId;
    @SerializedName("goods_name")
    public String goodsName;
    @SerializedName("quantity")
    public Long quantity;
    @SerializedName("unit_price")
    public Long unitPrice;
  }
  public static class PartnerAPIv3AppPrepayResponse {
    @SerializedName("prepay_id")
    public String prepayId;
  }
  public static class PartnerAPIv3CommonPrepayRequest {
    @SerializedName("sp_appid")
    public String spAppid;
    @SerializedName("sp_mchid")
    public String spMchid;
    @SerializedName("sub_appid")
    public String subAppid;
    @SerializedName("sub_mchid")
    public String subMchid;
    @SerializedName("description")
    public String description;
    @SerializedName("out_trade_no")
    public String outTradeNo;
    @SerializedName("time_expire")
    public String timeExpire;
    @SerializedName("attach")
    public String attach;
    @SerializedName("notify_url")
    public String notifyUrl;
    @SerializedName("goods_tag")
    public String goodsTag;
    @SerializedName("settle_info")
    public PartnerSettleInfo settleInfo;
    @SerializedName("support_fapiao")
    public Boolean supportFapiao;
    @SerializedName("amount")
    public CommonAmountInfo amount;
    @SerializedName("detail")
    public CouponInfo detail;
    @SerializedName("scene_info")
    public CommonSceneInfo sceneInfo;
  }
  public static class CouponInfo {
    @SerializedName("cost_price")
    public Long costPrice;
    @SerializedName("invoice_id")
    public String invoiceId;
    @SerializedName("goods_detail")
    public List<GoodsDetail> goodsDetail;
  }
  public static class StoreInfo {
    @SerializedName("id")
    public String id;
    @SerializedName("name")
    public String name;
    @SerializedName("area_code")
    public String areaCode;
    @SerializedName("address")
    public String address;
  }
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java
File was deleted
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java
File was deleted
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayUtility.java
New file
@@ -0,0 +1,455 @@
package com.dsh.activity.util.wx;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.Expose;
import com.wechat.pay.java.core.util.GsonUtil;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
public class WXPayUtility {
  private static final Gson gson = new GsonBuilder()
      .disableHtmlEscaping()
      .addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.serialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .addDeserializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.deserialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .create();
  private static final char[] SYMBOLS =
      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
  private static final SecureRandom random = new SecureRandom();
  /**
   * 将 Object 转换为 JSON 字符串
   */
  public static String toJson(Object object) {
    return gson.toJson(object);
  }
  /**
   * 将 JSON 字符串解析为特定类型的实例
   */
  public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    return gson.fromJson(json, classOfT);
  }
  /**
   * 从公私钥文件路径中读取文件内容
   *
   * @param keyPath 文件路径
   * @return 文件内容
   */
  private static String readKeyStringFromPath(String keyPath) {
    try {
      return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }
  /**
   * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
   *
   * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
          .replace("-----END PRIVATE KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePrivate(
          new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的私钥文件中加载私钥
   *
   * @param keyPath 私钥文件路径
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
    return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
   *
   * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
          .replace("-----END PUBLIC KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePublic(
          new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的公钥文件中加载公钥
   *
   * @param keyPath 公钥文件路径
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromPath(String keyPath) {
    return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
   */
  public static String createNonce(int length) {
    char[] buf = new char[length];
    for (int i = 0; i < length; ++i) {
      buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
    }
    return new String(buf);
  }
  /**
   * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
   *
   * @param publicKey 加密用公钥对象
   * @param plaintext 待加密明文
   * @return 加密后密文
   */
  public static String encrypt(PublicKey publicKey, String plaintext) {
    final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
    try {
      Cipher cipher = Cipher.getInstance(transformation);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
      throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
      throw new IllegalArgumentException("Plaintext is too long", e);
    }
  }
  /**
   * 使用私钥按照指定算法进行签名
   *
   * @param message 待签名串
   * @param algorithm 签名算法,如 SHA256withRSA
   * @param privateKey 签名用私钥对象
   * @return 签名结果
   */
  public static String sign(String message, String algorithm, PrivateKey privateKey) {
    byte[] sign;
    try {
      Signature signature = Signature.getInstance(algorithm);
      signature.initSign(privateKey);
      signature.update(message.getBytes(StandardCharsets.UTF_8));
      sign = signature.sign();
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
    } catch (SignatureException e) {
      throw new RuntimeException("An error occurred during the sign process.", e);
    }
    return Base64.getEncoder().encodeToString(sign);
  }
  /**
   * 使用公钥按照特定算法验证签名
   *
   * @param message 待签名串
   * @param signature 待验证的签名内容
   * @param algorithm 签名算法,如:SHA256withRSA
   * @param publicKey 验签用公钥对象
   * @return 签名验证是否通过
   */
  public static boolean verify(String message, String signature, String algorithm,
                               PublicKey publicKey) {
    try {
      Signature sign = Signature.getInstance(algorithm);
      sign.initVerify(publicKey);
      sign.update(message.getBytes(StandardCharsets.UTF_8));
      return sign.verify(Base64.getDecoder().decode(signature));
    } catch (SignatureException e) {
      return false;
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("verify uses an illegal publickey.", e);
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
    }
  }
  /**
   * 根据微信支付APIv3请求签名规则构造 Authorization 签名
   *
   * @param mchid 商户号
   * @param certificateSerialNo 商户API证书序列号
   * @param privateKey 商户API证书私钥
   * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
   * @param uri 请求接口的URL
   * @param body 请求接口的Body
   * @return 构造好的微信支付APIv3 Authorization 头
   */
  public static String buildAuthorization(String mchid, String certificateSerialNo,
                                          PrivateKey privateKey,
                                          String method, String uri, String body) {
    String nonce = createNonce(32);
    long timestamp = Instant.now().getEpochSecond();
    String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
        body == null ? "" : body);
    String signature = sign(message, "SHA256withRSA", privateKey);
    return String.format(
        "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
            "timestamp=\"%d\",serial_no=\"%s\"",
        mchid, nonce, signature, timestamp, certificateSerialNo);
  }
  /**
   * 对参数进行 URL 编码
   *
   * @param content 参数内容
   * @return 编码后的内容
   */
  public static String urlEncode(String content) {
    try {
      return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }
  /**
   * 对参数Map进行 URL 编码,生成 QueryString
   *
   * @param params Query参数Map
   * @return QueryString
   */
  public static String urlEncode(Map<String, Object> params) {
    if (params == null || params.isEmpty()) {
      return "";
    }
    int index = 0;
    StringBuilder result = new StringBuilder();
    for (Map.Entry<String, Object> entry : params.entrySet()) {
      result.append(entry.getKey())
          .append("=")
          .append(urlEncode(entry.getValue().toString()));
      index++;
      if (index < params.size()) {
        result.append("&");
      }
    }
    return result.toString();
  }
  /**
   * 从应答中提取 Body
   *
   * @param response HTTP 请求应答对象
   * @return 应答中的Body内容,Body为空时返回空字符串
   */
  public static String extractBody(Response response) {
    if (response.body() == null) {
      return "";
    }
    try {
      BufferedSource source = response.body().source();
      return source.readUtf8();
    } catch (IOException e) {
      throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e);
    }
  }
  /**
   * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
   *
   * @param wechatpayPublicKeyId 微信支付公钥ID
   * @param wechatpayPublicKey 微信支付公钥对象
   * @param headers 微信支付应答 Header 列表
   * @param body 微信支付应答 Body
   */
  public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                      Headers headers,
                                      String body) {
    String timestamp = headers.get("Wechatpay-Timestamp");
    try {
      Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
      // 拒绝过期请求
      if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
        throw new IllegalArgumentException(
            String.format("Validate http response,timestamp[%s] of httpResponse is expires, "
                    + "request-id[%s]",
                timestamp, headers.get("Request-ID")));
      }
    } catch (DateTimeException | NumberFormatException e) {
      throw new IllegalArgumentException(
          String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " +
                  "request-id[%s]", timestamp,
              headers.get("Request-ID")));
    }
    String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
        body == null ? "" : body);
    String serialNumber = headers.get("Wechatpay-Serial");
    if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
      throw new IllegalArgumentException(
          String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId,
              serialNumber));
    }
    String signature = headers.get("Wechatpay-Signature");
    boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
    if (!success) {
      throw new IllegalArgumentException(
          String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                  + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
              headers.get("Request-ID"), headers, body));
    }
  }
  /**
   * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
   */
  public static class ApiException extends RuntimeException {
    private static final long serialVersionUID = 2261086748874802175L;
    private final int statusCode;
    private final String body;
    private final Headers headers;
    private final String errorCode;
    private final String errorMessage;
    public ApiException(int statusCode, String body, Headers headers) {
      super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers));
      this.statusCode = statusCode;
      this.body = body;
      this.headers = headers;
      if (body != null && !body.isEmpty()) {
        JsonElement code;
        JsonElement message;
        try {
          JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class);
          code = jsonObject.get("code");
          message = jsonObject.get("message");
        } catch (JsonSyntaxException ignored) {
          code = null;
          message = null;
        }
        this.errorCode = code == null ? null : code.getAsString();
        this.errorMessage = message == null ? null : message.getAsString();
      } else {
        this.errorCode = null;
        this.errorMessage = null;
      }
    }
    /**
     * 获取 HTTP 应答状态码
     */
    public int getStatusCode() {
      return statusCode;
    }
    /**
     * 获取 HTTP 应答包体内容
     */
    public String getBody() {
      return body;
    }
    /**
     * 获取 HTTP 应答 Header
     */
    public Headers getHeaders() {
      return headers;
    }
    /**
     * 获取 错误码 (错误应答中的 code 字段)
     */
    public String getErrorCode() {
      return errorCode;
    }
    /**
     * 获取 错误消息 (错误应答中的 message 字段)
     */
    public String getErrorMessage() {
      return errorMessage;
    }
  }
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatSignUtil.java
New file
@@ -0,0 +1,70 @@
package com.dsh.activity.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
public class WeChatSignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 使用商户私钥对字符串进行 SHA256withRSA 签名
     *
     * @param content      待签名字符串
     * @param privateKeyPath 商户私钥文件路径(pem 格式)
     * @return 签名结果(Base64 编码)
     */
    public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception {
        String privateKeyPEM = readPemFile(privateKeyPath);
        String privateKeyContent = privateKeyPEM
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");
        byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }
    private static String readPemFile(String filePath) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
        }
        return sb.toString();
    }
    /**
     * 按照微信 V3 规范生成待签名字符串
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            Object value = map.get(key);
            if (value != null && !value.toString().isEmpty()) {
                sb.append(key).append("=").append(value).append("\n");
            }
        }
        return sb.toString();
    }
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatV3SignUtil.java
New file
@@ -0,0 +1,67 @@
package com.dsh.activity.util.wx;
import com.dsh.activity.util.UUIDUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
public class WeChatV3SignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 从 PEM 文件中读取私钥内容
     */
    public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith("-----") && !line.endsWith("-----")) {
                    sb.append(line);
                }
            }
        }
        byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
        KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
        return kf.generatePrivate(keySpec);
    }
    /**
     * 构造待签名字符串(按 key 字典序排序)
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
//        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        sb.append(map.get("appid")).append("\n");
        sb.append(map.get("timestamp")).append("\n");
        sb.append(map.get("noncestr")).append("\n");
        sb.append(map.get("prepayid")).append("\n");
        return sb.toString();
    }
    /**
     * 使用商户私钥生成签名(SHA256withRSA + Base64)
     */
    public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception {
        PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signedBytes);
    }
}
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java
File was deleted
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java
File was deleted
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java
File was deleted
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java
@@ -1,56 +1,66 @@
//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.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class WxV3PayConfig {
    // 服务商AppId
    private String appIdValue = "wx41d32f362ba0f911";
    public static String APP_ID= "wx41d32f362ba0f911";
    // 服务商商户号
    private String mchIdValue= "1681873607";
    public static String Mch_ID= "1681873607";
    // 平台收款商户号 todo 待申请
    public static String smidVx= "2088330203191220";
    private  String smidVxValue= "2088330203191220";
    // 服务商商户私钥
    private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq";
    public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq";
    // 证书序列号
    private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A";
    public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A";
    private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量
    @PostConstruct
    public void init() {
        APP_ID = this.appIdValue;
        APP_ID = this.appIdValue;
        smidVx = this.smidVxValue;
        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-communityWorldCup/pom.xml
@@ -14,6 +14,32 @@
    <name>社区世界杯</name>
    <description>社区世界杯</description>
    <dependencies>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java-core</artifactId>
            <version>0.2.12</version>
            <scope>compile</scope>
        </dependency>
        <!-- 微信支付V3 目前新版本-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.9</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <!-- OkHttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
        <!--日志处理-->
        <dependency>
            <groupId>cn.mb.cloud</groupId>
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/controller/WorldCupController.java
@@ -23,6 +23,8 @@
import com.dsh.communityWorldCup.util.PayMoneyUtil;
import com.dsh.communityWorldCup.util.ResultUtil;
import com.dsh.communityWorldCup.util.TokenUtil;
import com.dsh.communityWorldCup.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import groovy.util.logging.Log4j;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -37,8 +39,10 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@@ -448,13 +452,10 @@
        }
    }
    /**
     * 微信支付回调
     */
    @ResponseBody
    @PostMapping("/base/worldCup/wxPayWorldCupCallback")
    public void wxPayWorldCupCallback(HttpServletRequest request, HttpServletResponse response){
            System.err.println("微信回调");
        try {
            Map<String, String> map = payMoneyUtil.weixinpayCallback(request);
            if(null != map){
@@ -477,6 +478,52 @@
            e.printStackTrace();
        }
    }
    /**
     * 微信支付回调
     */
    @ResponseBody
    @PostMapping("/base/worldCup/wxPayWorldCupCallback1")
    public void wxPayWorldCupCallback1(HttpServletRequest request, HttpServletResponse response){
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String code = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                ResultUtil resultUtil = worldCupService.paymentWorldCupCallback(code, transaction_id);
                if(resultUtil.getCode() == 200){
                    PrintWriter out = response.getWriter();
                    out.println("SUCCESS");
                    out.flush();
                    out.close();
                }else{
                    log.error("社区世界杯报名微信支付回业务处理异常:" + resultUtil.getMsg());
                }
            }else{
                log.error("社区世界杯报名微信支付回调解析异常");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/feignclient/other/StoreClient.java
@@ -2,6 +2,7 @@
import com.dsh.communityWorldCup.feignclient.other.model.Store;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@@ -20,4 +21,8 @@
     */
    @PostMapping("/store/queryStoreById")
    Store queryStoreById(Integer id);
    @PostMapping("/base/getmerchantNumberByOperatorId/{id}")
    String getmerchantNumberByOperatorId(@PathVariable("id")Integer id);
    @PostMapping("/base/getmerchantNumberAliByOperatorId/{id}")
    String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id);
}
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/feignclient/other/model/Store.java
@@ -87,4 +87,8 @@
     * 状态(1=正常,2=冻结,3=删除)
     */
    private Integer state;
    /**
     * 运营商id
     */
    private Integer operatorId;
}
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/service/impl/WorldCupServiceImpl.java
@@ -25,8 +25,10 @@
import com.dsh.communityWorldCup.model.*;
import com.dsh.communityWorldCup.service.*;
import com.dsh.communityWorldCup.util.*;
import com.dsh.communityWorldCup.util.wx.WxV3PayConfig;
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;
@@ -511,6 +513,7 @@
        return payment;
    }
    private String smidVx = "2088330203191220";//平台微信商户号
    /**
     * 支付逻辑
@@ -539,16 +542,57 @@
                worldCupPayment.setAmount(new BigDecimal(num).multiply(worldCup.getCash()));
                worldCupPayment.setUnitPrice(worldCupPayment.getAmount().divide(new BigDecimal(num)));
                worldCupPaymentService.save(worldCupPayment);
                List<WorldCupStore> list = worldCupStoreService.lambdaQuery().eq(WorldCupStore::getWorldCupId, worldCup.getId()).list();
                if (list.isEmpty()){
                return payMoneyUtil.weixinpay("社区世界杯报名", "", worldCupPayment.getCode(),
                        worldCupPayment.getAmount().toString(), "/base/worldCup/wxPayWorldCupCallback", "APP", "");
                }else{
                    Integer storeId = list.get(0).getStoreId();
                    Store store = storeClient.queryStoreById(storeId);
                    if (store.getOperatorId()==null||store.getOperatorId()==0){
                        // 平台
                        return payMoneyUtil.weixinpay("社区世界杯报名", "", worldCupPayment.getCode(),
                                worldCupPayment.getAmount().toString(), "/base/worldCup/wxPayWorldCupCallback", "APP", "");
                    }else{
                        String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId());
                        System.err.println("微信商户号"+s2);
                        if(!StringUtils.hasLength(s2)){
                            return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
                        }
                        return payMoneyUtil.weixinpayV3(s2,"社区世界杯报名",worldCupPayment.getCode()
                                ,"/base/worldCup/wxPayWorldCupCallback1",worldCupPayment.getAmount().toString());
                    }
                }
            }
            //支付宝
            if(payType == 2){
                worldCupPayment.setAmount(new BigDecimal(num).multiply(worldCup.getCash()));
                worldCupPayment.setUnitPrice(worldCupPayment.getAmount().divide(new BigDecimal(num)));
                worldCupPaymentService.save(worldCupPayment);
                List<WorldCupStore> list = worldCupStoreService.lambdaQuery().eq(WorldCupStore::getWorldCupId, worldCup.getId()).list();
                if (list.isEmpty()){
                return payMoneyUtil.alipay("2088330203191220", "社区世界杯报名", "世界杯报名", "", worldCupPayment.getCode(),
                        worldCupPayment.getAmount().toString(), "/base/worldCup/aliPayWorldCupCallback");
            }else{
                    Integer storeId = list.get(0).getStoreId();
                    Store store = storeClient.queryStoreById(storeId);
                    if (store.getOperatorId()==null||store.getOperatorId()==0){
                        return payMoneyUtil.alipay("2088330203191220", "社区世界杯报名", "世界杯报名", "", worldCupPayment.getCode(),
                                worldCupPayment.getAmount().toString(), "/base/worldCup/aliPayWorldCupCallback");
                    }else{
                        String s2 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId());
                        System.err.println("支付宝商户号"+s2);
                        if(!StringUtils.hasLength(s2)){
                            return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
                        }
                        return payMoneyUtil.alipay(s2, "社区世界杯报名", "世界杯报名", "", worldCupPayment.getCode(),
                                worldCupPayment.getAmount().toString(), "/base/worldCup/aliPayWorldCupCallback");
                    }
                }
            }
            //玩湃币
            if(payType == 3){
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/PayMoneyUtil.java
@@ -10,6 +10,10 @@
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.dsh.communityWorldCup.util.httpClinet.HttpClientUtil;
import com.dsh.communityWorldCup.util.wx.PartnerAppPrepay;
import com.dsh.communityWorldCup.util.wx.WXPayUtility;
import com.dsh.communityWorldCup.util.wx.WeChatV3SignUtil;
import com.dsh.communityWorldCup.util.wx.WxV3PayConfig;
import org.apache.commons.collections.map.HashedMap;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.dom4j.Document;
@@ -150,9 +154,6 @@
        SubMerchant subMerchant = new SubMerchant();
        subMerchant.setMerchantId(smid);
        model.setSubMerchant(subMerchant);
        ExtendParams extendParams = new ExtendParams();
        extendParams.setRoyaltyFreeze("true");// 冻结资金 用于后续分账处理
        model.setExtendParams(extendParams);
        request.setBizModel(model);
        request.setNotifyUrl(callbackPath + notifyUrl);
@@ -460,6 +461,62 @@
        }
    }
    public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee) throws Exception {
        int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue();
        // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
        PartnerAppPrepay client = new PartnerAppPrepay(
//                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//                "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//                "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
                "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
                "/usr/playpai/server/wxV3/pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
        );
        PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest();
        request.spAppid = appid;// appid
        request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号
        request.subMchid = subMchid;// 子商户号
        request.description = description;// 描述
        request.outTradeNo = outTradeNo;// 订单号
        request.notifyUrl = callbackPath+notifyUrl;// 回调地址
        request.amount = new PartnerAppPrepay.CommonAmountInfo();
        request.amount.total = (long) i;// 金额 单位分
        request.amount.currency = "CNY";
        String prepayId ="";
        Map<String, Object> map3 = new HashMap<>();
        try {
            PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request);
            // TODO: 请求成功,继续业务逻辑
            System.err.println("微信申请成功,预支付ID: " + response.prepayId);
            prepayId = response.prepayId;
        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败,根据状态码执行不同的逻辑
            e.printStackTrace();
        }
        map3.put("appid", appid);
        map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        String nonce_str = UUIDUtil.getRandomCode(16);
        map3.put("noncestr", nonce_str);
        map3.put("prepayid", prepayId);
        // 构造待签名字符串
        String message = WeChatV3SignUtil.buildSignMessage(map3);
        // 私钥路径(pem 文件)
        String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
//      String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem";
        // 生成签名
        String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath);
        map3.put("sign", sign);
        map3.put("package", "Sign=WXPay");
        map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号
        System.err.println(map3);
        return ResultUtil.success(map3);
    }
    /**
     * 微信支付成功后的回调处理
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/PartnerAppPrepay.java
New file
@@ -0,0 +1,237 @@
package com.dsh.communityWorldCup.util.wx;
import com.google.gson.annotations.SerializedName;
import okhttp3.*;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;
/**
 * App下单
 */
public class PartnerAppPrepay {
  private static String HOST = "https://api.mch.weixin.qq.com";
  private static String METHOD = "POST";
  private static String PATH = "/v3/pay/partner/transactions/app";
//  public static void main(String[] args) {
//    // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
//    PartnerAppPrepay client = new PartnerAppPrepay(
//        "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//        "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//        "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//        "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//        "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
//    );
//
//    PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest();
//    request.spAppid = "wx41d32f362ba0f911";
//    request.spMchid = WxV3PayConfig.Mch_ID;
//    request.subMchid = "1720719391";
//    request.description = "Image形象店-深圳腾大-QQ公仔";
//    request.outTradeNo = "12177525012014070332333680182";
//    request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php";
////    request.goodsTag = "WXG";
////    request.settleInfo = new PartnerSettleInfo();
////    request.settleInfo.profitSharing = false;
//    request.amount = new CommonAmountInfo();
//    request.amount.total = 100L;
//    request.amount.currency = "CNY";
////    request.detail = new CouponInfo();
////    request.detail.costPrice = 1L;
////    request.detail.invoiceId = "wx123";
////    request.detail.goodsDetail = new ArrayList<>();
////    {
////      GoodsDetail item0 = new GoodsDetail();
////      item0.merchantGoodsId = "1246464644";
////      item0.wechatpayGoodsId = "1001";
////      item0.goodsName = "iPhone6s 16G";
////      item0.quantity = 1L;
////      item0.unitPrice = 528800L;
////      request.detail.goodsDetail.add(item0);
////    };
////    request.sceneInfo = new CommonSceneInfo();
////    request.sceneInfo.payerClientIp = "14.23.150.211";
////    request.sceneInfo.deviceId = "013467007045764";
////    request.sceneInfo.storeInfo = new StoreInfo();
////    request.sceneInfo.storeInfo.id = "0001";
////    request.sceneInfo.storeInfo.name = "腾讯大厦分店";
////    request.sceneInfo.storeInfo.areaCode = "440305";
////    request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
//    try {
//      PartnerAPIv3AppPrepayResponse response = client.run(request);
//
//      // TODO: 请求成功,继续业务逻辑
//      System.err.println("微信申请成功,预支付ID: " + response.prepayId);
//    } catch (WXPayUtility.ApiException e) {
//      // TODO: 请求失败,根据状态码执行不同的逻辑
//      e.printStackTrace();
//    }
//  }
  public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) {
    String uri = PATH;
    String reqBody = WXPayUtility.toJson(request);
    Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
    reqBuilder.addHeader("Accept", "application/json");
    reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
    reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
    reqBuilder.addHeader("Content-Type", "application/json");
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
    reqBuilder.method(METHOD, requestBody);
    Request httpRequest = reqBuilder.build();
    // 发送HTTP请求
    OkHttpClient client = new OkHttpClient.Builder().build();
    try (Response httpResponse = client.newCall(httpRequest).execute()) {
      String respBody = WXPayUtility.extractBody(httpResponse);
      if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
        // 2XX 成功,验证应答签名
        WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                                      httpResponse.headers(), respBody);
        // 从HTTP应答报文构建返回数据
        return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class);
      } else {
        throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
      }
    } catch (IOException e) {
      throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
    }
  }
  private final String mchid;
  private final String certificateSerialNo;
  private final PrivateKey privateKey;
  private final String wechatPayPublicKeyId;
  private final PublicKey wechatPayPublicKey;
  public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
    this.mchid = mchid;
    this.certificateSerialNo = certificateSerialNo;
    this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
    this.wechatPayPublicKeyId = wechatPayPublicKeyId;
    this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
  }
  public static class PartnerSettleInfo {
    @SerializedName("profit_sharing")
    public Boolean profitSharing;
  }
  public static class CommonAmountInfo {
    @SerializedName("total")
    public Long total;
    @SerializedName("currency")
    public String currency;
  }
  public static class CommonSceneInfo {
    @SerializedName("payer_client_ip")
    public String payerClientIp;
    @SerializedName("device_id")
    public String deviceId;
    @SerializedName("store_info")
    public StoreInfo storeInfo;
  }
  public static class GoodsDetail {
    @SerializedName("merchant_goods_id")
    public String merchantGoodsId;
    @SerializedName("wechatpay_goods_id")
    public String wechatpayGoodsId;
    @SerializedName("goods_name")
    public String goodsName;
    @SerializedName("quantity")
    public Long quantity;
    @SerializedName("unit_price")
    public Long unitPrice;
  }
  public static class PartnerAPIv3AppPrepayResponse {
    @SerializedName("prepay_id")
    public String prepayId;
  }
  public static class PartnerAPIv3CommonPrepayRequest {
    @SerializedName("sp_appid")
    public String spAppid;
    @SerializedName("sp_mchid")
    public String spMchid;
    @SerializedName("sub_appid")
    public String subAppid;
    @SerializedName("sub_mchid")
    public String subMchid;
    @SerializedName("description")
    public String description;
    @SerializedName("out_trade_no")
    public String outTradeNo;
    @SerializedName("time_expire")
    public String timeExpire;
    @SerializedName("attach")
    public String attach;
    @SerializedName("notify_url")
    public String notifyUrl;
    @SerializedName("goods_tag")
    public String goodsTag;
    @SerializedName("settle_info")
    public PartnerSettleInfo settleInfo;
    @SerializedName("support_fapiao")
    public Boolean supportFapiao;
    @SerializedName("amount")
    public CommonAmountInfo amount;
    @SerializedName("detail")
    public CouponInfo detail;
    @SerializedName("scene_info")
    public CommonSceneInfo sceneInfo;
  }
  public static class CouponInfo {
    @SerializedName("cost_price")
    public Long costPrice;
    @SerializedName("invoice_id")
    public String invoiceId;
    @SerializedName("goods_detail")
    public List<GoodsDetail> goodsDetail;
  }
  public static class StoreInfo {
    @SerializedName("id")
    public String id;
    @SerializedName("name")
    public String name;
    @SerializedName("area_code")
    public String areaCode;
    @SerializedName("address")
    public String address;
  }
}
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/WXPayUtility.java
New file
@@ -0,0 +1,441 @@
package com.dsh.communityWorldCup.util.wx;
import com.google.gson.*;
import com.google.gson.annotations.Expose;
import com.wechat.pay.java.core.util.GsonUtil;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
public class WXPayUtility {
  private static final Gson gson = new GsonBuilder()
      .disableHtmlEscaping()
      .addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.serialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .addDeserializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.deserialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .create();
  private static final char[] SYMBOLS =
      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
  private static final SecureRandom random = new SecureRandom();
  /**
   * 将 Object 转换为 JSON 字符串
   */
  public static String toJson(Object object) {
    return gson.toJson(object);
  }
  /**
   * 将 JSON 字符串解析为特定类型的实例
   */
  public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    return gson.fromJson(json, classOfT);
  }
  /**
   * 从公私钥文件路径中读取文件内容
   *
   * @param keyPath 文件路径
   * @return 文件内容
   */
  private static String readKeyStringFromPath(String keyPath) {
    try {
      return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }
  /**
   * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
   *
   * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
          .replace("-----END PRIVATE KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePrivate(
          new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的私钥文件中加载私钥
   *
   * @param keyPath 私钥文件路径
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
    return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
   *
   * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
          .replace("-----END PUBLIC KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePublic(
          new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的公钥文件中加载公钥
   *
   * @param keyPath 公钥文件路径
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromPath(String keyPath) {
    return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
   */
  public static String createNonce(int length) {
    char[] buf = new char[length];
    for (int i = 0; i < length; ++i) {
      buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
    }
    return new String(buf);
  }
  /**
   * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
   *
   * @param publicKey 加密用公钥对象
   * @param plaintext 待加密明文
   * @return 加密后密文
   */
  public static String encrypt(PublicKey publicKey, String plaintext) {
    final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
    try {
      Cipher cipher = Cipher.getInstance(transformation);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
      throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
      throw new IllegalArgumentException("Plaintext is too long", e);
    }
  }
  /**
   * 使用私钥按照指定算法进行签名
   *
   * @param message 待签名串
   * @param algorithm 签名算法,如 SHA256withRSA
   * @param privateKey 签名用私钥对象
   * @return 签名结果
   */
  public static String sign(String message, String algorithm, PrivateKey privateKey) {
    byte[] sign;
    try {
      Signature signature = Signature.getInstance(algorithm);
      signature.initSign(privateKey);
      signature.update(message.getBytes(StandardCharsets.UTF_8));
      sign = signature.sign();
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
    } catch (SignatureException e) {
      throw new RuntimeException("An error occurred during the sign process.", e);
    }
    return Base64.getEncoder().encodeToString(sign);
  }
  /**
   * 使用公钥按照特定算法验证签名
   *
   * @param message 待签名串
   * @param signature 待验证的签名内容
   * @param algorithm 签名算法,如:SHA256withRSA
   * @param publicKey 验签用公钥对象
   * @return 签名验证是否通过
   */
  public static boolean verify(String message, String signature, String algorithm,
                               PublicKey publicKey) {
    try {
      Signature sign = Signature.getInstance(algorithm);
      sign.initVerify(publicKey);
      sign.update(message.getBytes(StandardCharsets.UTF_8));
      return sign.verify(Base64.getDecoder().decode(signature));
    } catch (SignatureException e) {
      return false;
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("verify uses an illegal publickey.", e);
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
    }
  }
  /**
   * 根据微信支付APIv3请求签名规则构造 Authorization 签名
   *
   * @param mchid 商户号
   * @param certificateSerialNo 商户API证书序列号
   * @param privateKey 商户API证书私钥
   * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
   * @param uri 请求接口的URL
   * @param body 请求接口的Body
   * @return 构造好的微信支付APIv3 Authorization 头
   */
  public static String buildAuthorization(String mchid, String certificateSerialNo,
                                          PrivateKey privateKey,
                                          String method, String uri, String body) {
    String nonce = createNonce(32);
    long timestamp = Instant.now().getEpochSecond();
    String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
        body == null ? "" : body);
    String signature = sign(message, "SHA256withRSA", privateKey);
    return String.format(
        "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
            "timestamp=\"%d\",serial_no=\"%s\"",
        mchid, nonce, signature, timestamp, certificateSerialNo);
  }
  /**
   * 对参数进行 URL 编码
   *
   * @param content 参数内容
   * @return 编码后的内容
   */
  public static String urlEncode(String content) {
    try {
      return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }
  /**
   * 对参数Map进行 URL 编码,生成 QueryString
   *
   * @param params Query参数Map
   * @return QueryString
   */
  public static String urlEncode(Map<String, Object> params) {
    if (params == null || params.isEmpty()) {
      return "";
    }
    int index = 0;
    StringBuilder result = new StringBuilder();
    for (Map.Entry<String, Object> entry : params.entrySet()) {
      result.append(entry.getKey())
          .append("=")
          .append(urlEncode(entry.getValue().toString()));
      index++;
      if (index < params.size()) {
        result.append("&");
      }
    }
    return result.toString();
  }
  /**
   * 从应答中提取 Body
   *
   * @param response HTTP 请求应答对象
   * @return 应答中的Body内容,Body为空时返回空字符串
   */
  public static String extractBody(Response response) {
    if (response.body() == null) {
      return "";
    }
    try {
      BufferedSource source = response.body().source();
      return source.readUtf8();
    } catch (IOException e) {
      throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e);
    }
  }
  /**
   * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
   *
   * @param wechatpayPublicKeyId 微信支付公钥ID
   * @param wechatpayPublicKey 微信支付公钥对象
   * @param headers 微信支付应答 Header 列表
   * @param body 微信支付应答 Body
   */
  public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                      Headers headers,
                                      String body) {
    String timestamp = headers.get("Wechatpay-Timestamp");
    try {
      Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
      // 拒绝过期请求
      if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
        throw new IllegalArgumentException(
            String.format("Validate http response,timestamp[%s] of httpResponse is expires, "
                    + "request-id[%s]",
                timestamp, headers.get("Request-ID")));
      }
    } catch (DateTimeException | NumberFormatException e) {
      throw new IllegalArgumentException(
          String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " +
                  "request-id[%s]", timestamp,
              headers.get("Request-ID")));
    }
    String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
        body == null ? "" : body);
    String serialNumber = headers.get("Wechatpay-Serial");
    if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
      throw new IllegalArgumentException(
          String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId,
              serialNumber));
    }
    String signature = headers.get("Wechatpay-Signature");
    boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
    if (!success) {
      throw new IllegalArgumentException(
          String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                  + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
              headers.get("Request-ID"), headers, body));
    }
  }
  /**
   * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
   */
  public static class ApiException extends RuntimeException {
    private static final long serialVersionUID = 2261086748874802175L;
    private final int statusCode;
    private final String body;
    private final Headers headers;
    private final String errorCode;
    private final String errorMessage;
    public ApiException(int statusCode, String body, Headers headers) {
      super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers));
      this.statusCode = statusCode;
      this.body = body;
      this.headers = headers;
      if (body != null && !body.isEmpty()) {
        JsonElement code;
        JsonElement message;
        try {
          JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class);
          code = jsonObject.get("code");
          message = jsonObject.get("message");
        } catch (JsonSyntaxException ignored) {
          code = null;
          message = null;
        }
        this.errorCode = code == null ? null : code.getAsString();
        this.errorMessage = message == null ? null : message.getAsString();
      } else {
        this.errorCode = null;
        this.errorMessage = null;
      }
    }
    /**
     * 获取 HTTP 应答状态码
     */
    public int getStatusCode() {
      return statusCode;
    }
    /**
     * 获取 HTTP 应答包体内容
     */
    public String getBody() {
      return body;
    }
    /**
     * 获取 HTTP 应答 Header
     */
    public Headers getHeaders() {
      return headers;
    }
    /**
     * 获取 错误码 (错误应答中的 code 字段)
     */
    public String getErrorCode() {
      return errorCode;
    }
    /**
     * 获取 错误消息 (错误应答中的 message 字段)
     */
    public String getErrorMessage() {
      return errorMessage;
    }
  }
}
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/WeChatSignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.communityWorldCup.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
public class WeChatSignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 使用商户私钥对字符串进行 SHA256withRSA 签名
     *
     * @param content      待签名字符串
     * @param privateKeyPath 商户私钥文件路径(pem 格式)
     * @return 签名结果(Base64 编码)
     */
    public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception {
        String privateKeyPEM = readPemFile(privateKeyPath);
        String privateKeyContent = privateKeyPEM
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");
        byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }
    private static String readPemFile(String filePath) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
        }
        return sb.toString();
    }
    /**
     * 按照微信 V3 规范生成待签名字符串
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            Object value = map.get(key);
            if (value != null && !value.toString().isEmpty()) {
                sb.append(key).append("=").append(value).append("\n");
            }
        }
        return sb.toString();
    }
}
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/WeChatV3SignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.communityWorldCup.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
public class WeChatV3SignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 从 PEM 文件中读取私钥内容
     */
    public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith("-----") && !line.endsWith("-----")) {
                    sb.append(line);
                }
            }
        }
        byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
        KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
        return kf.generatePrivate(keySpec);
    }
    /**
     * 构造待签名字符串(按 key 字典序排序)
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
//        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        sb.append(map.get("appid")).append("\n");
        sb.append(map.get("timestamp")).append("\n");
        sb.append(map.get("noncestr")).append("\n");
        sb.append(map.get("prepayid")).append("\n");
        return sb.toString();
    }
    /**
     * 使用商户私钥生成签名(SHA256withRSA + Base64)
     */
    public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception {
        PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signedBytes);
    }
}
cloud-server-communityWorldCup/src/main/java/com/dsh/communityWorldCup/util/wx/WxV3PayConfig.java
New file
@@ -0,0 +1,66 @@
package com.dsh.communityWorldCup.util.wx;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class WxV3PayConfig {
    // 服务商AppId
    private String appIdValue = "wx41d32f362ba0f911";
    public static String APP_ID= "wx41d32f362ba0f911";
    // 服务商商户号
    private String mchIdValue= "1681873607";
    public static String Mch_ID= "1681873607";
    // 平台收款商户号 todo 待申请
    public static String smidVx= "2088330203191220";
    private  String smidVxValue= "2088330203191220";
    // 服务商商户私钥
    private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq";
    public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq";
    // 证书序列号
    private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A";
    public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A";
    private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量
    @PostConstruct
    public void init() {
        APP_ID = this.appIdValue;
        APP_ID = this.appIdValue;
        smidVx = this.smidVxValue;
        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-competition/pom.xml
@@ -14,6 +14,32 @@
    <name>赛事</name>
    <description>赛事</description>
    <dependencies>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java-core</artifactId>
            <version>0.2.12</version>
            <scope>compile</scope>
        </dependency>
        <!-- 微信支付V3 目前新版本-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.9</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <!-- OkHttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
        <!--日志处理-->
        <dependency>
            <groupId>cn.mb.cloud</groupId>
cloud-server-competition/src/main/java/com/dsh/competition/controller/CompetitionController.java
@@ -3,6 +3,7 @@
import cn.hutool.core.date.DateUtil;
import cn.hutool.poi.excel.ExcelUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -26,6 +27,8 @@
import com.dsh.competition.service.IPaymentCompetitionService;
import com.dsh.competition.service.UserCompetitionService;
import com.dsh.competition.util.*;
import com.dsh.competition.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@@ -38,9 +41,11 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
@@ -440,6 +445,61 @@
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/competition/weChatPaymentCompetitionCallback1")
    public void weChatPaymentCompetitionCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String code = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>().eq("code", code).eq("payType", 1));
                if (paymentCompetition.getPayStatus() == 1) {
                    paymentCompetition.setAppUserId(null);
                    paymentCompetition.setPayStatus(2);
                    paymentCompetition.setPayTime(new Date());
                    paymentCompetition.setPayOrderNo(transaction_id);
                    paymentCompetitionService.updateById(paymentCompetition);
                    Competition competition = cttService.getById(paymentCompetition.getCompetitionId());
                    competition.setApplicantsNumber(competition.getApplicantsNumber() + 1);
                    cttService.updateById(competition);
                }
                PrintWriter out = response.getWriter();
                out.write("SUCCESS");
                out.flush();
                out.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 报名赛事微信支付回调
     *
     * @param request
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/competition/weChatPaymentCompetitionCallback")
    public void weChatPaymentCompetitionCallback(HttpServletRequest request, HttpServletResponse response) {
        try {
@@ -460,34 +520,7 @@
                    Competition competition = cttService.getById(paymentCompetition.getCompetitionId());
                    competition.setApplicantsNumber(competition.getApplicantsNumber() + 1);
                    cttService.updateById(competition);
                    if (competition.getOperatorId()!=null && competition.getOperatorId()!=0){
                        // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
                        Thread.sleep(120000);
                        // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
                        String proportionByOperatorId = storeClient.getProportionByOperatorId(competition.getOperatorId());
                        String[] split = proportionByOperatorId.split(",");
                        String s1 = split[0];
                        if (!s1.equals("未设置")){
                            BigDecimal bigDecimal = new BigDecimal(s1);
                            // 分账比例
                            BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                            // 微信商户号
                            String s2 = storeClient.getmerchantNumberByOperatorId(competition.getOperatorId());
                            String nonce_str = UUIDUtil.getRandomCode(16);
                            paymentCompetition.setFenzhangOrderNo(nonce_str);
                            BigDecimal bigDecimal2 = new BigDecimal(paymentCompetition.getAmount());
                            ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, bigDecimal2.multiply(bigDecimal1), s2,nonce_str,"报名赛事分账");
                            if (!fenzhang.getCode().equals(200)){
                                System.err.println("分账失败 原因是:"+fenzhang.getData()+"-"+fenzhang.getMsg());
                            }else{
                                paymentCompetition.setFenzhangNo(fenzhang.getData().toString());
                                paymentCompetition.setFenzhangAmount(bigDecimal2.multiply(bigDecimal1));
                                paymentCompetitionService.updateById(paymentCompetition);
                            }
                        }
                    }
                }
                PrintWriter out = response.getWriter();
                out.write(result);
cloud-server-competition/src/main/java/com/dsh/competition/controller/ParticipantController.java
@@ -27,6 +27,7 @@
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
@@ -90,11 +91,12 @@
                List<ParticipantVo> filteredParticipants = new ArrayList<>();
                for (ParticipantVo participant : participantVos) {
                    if (participant.getIdcard() != null && !participant.getIdcard().isEmpty()) {
//                    if (participant.getIdcard() != null && !participant.getIdcard().isEmpty()) {
                        filteredParticipants.add(participant);
//                    }
                    }
                }
                return ResultUtil.success(filteredParticipants);
            List<ParticipantVo> collect = filteredParticipants.stream().distinct().collect(Collectors.toList());
            return ResultUtil.success(collect);
//            }
//            return ResultUtil.success(participantVos);
cloud-server-competition/src/main/java/com/dsh/competition/feignclient/other/StoreClient.java
@@ -29,6 +29,9 @@
    // 根据运营商id获取微信商户号
    @PostMapping("/base/getmerchantNumberByOperatorId/{id}")
    String getmerchantNumberByOperatorId(@PathVariable("id")Integer id);
    // 根据运营商id获取支付宝商户号
    @PostMapping("/base/getmerchantNumberAliByOperatorId/{id}")
    String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id);
    /**
     * 根据名称模糊搜索门店
     *
cloud-server-competition/src/main/java/com/dsh/competition/service/impl/CompetitionServiceImpl.java
@@ -34,8 +34,10 @@
import com.dsh.competition.service.IPaymentCompetitionService;
import com.dsh.competition.service.UserCompetitionService;
import com.dsh.competition.util.*;
import com.dsh.competition.util.wx.WxV3PayConfig;
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;
@@ -299,11 +301,20 @@
        if (paymentCompetitionVo.getPayType() == 1) {//微信
            Competition byId = competitionService.getById(paymentCompetitionVo.getId());
            Integer operatorId = byId.getOperatorId();
            int temp = 0;
            if (operatorId!=null && operatorId != 0){
                temp =1;
                // 运营商
                String s = storeClient.getmerchantNumberByOperatorId(operatorId);
                if(!StringUtils.hasLength(s)){
                    return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
            }
            return weChatPaymentCompetition(code, money,temp,operatorId);
                return payMoneyUtil.weixinpayV3(s,"报名赛事",code,
                        "/base/competition/weChatPaymentCompetitionCallback1",
                        money.toString());
            }else{
                return    payMoneyUtil.weixinpay("报名赛事", "", code, money.toString(),
                "/base/competition/weChatPaymentCompetitionCallback", "APP", "");
            }
        }
        if (paymentCompetitionVo.getPayType() == 2) {//支付宝
@@ -315,7 +326,10 @@
                return aliPaymentCompetition(operatorId,smid,code, money);
            }
            // 获取该运营商的商户号
            String smid = storeClient.getSMIDByOperatorId(operatorId);
            String smid = storeClient.getmerchantNumberAliByOperatorId(operatorId);
            if (!StringUtils.hasLength(smid)){
                return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
            }
            return aliPaymentCompetition(operatorId,smid,code, money);
        }
        if (paymentCompetitionVo.getPayType() == 3) {//玩湃币
@@ -362,7 +376,6 @@
        return ResultUtil.success();
    }
    /**
     * 赛事微信支付
     *
@@ -372,69 +385,15 @@
     * @throws Exception
     */
    public ResultUtil weChatPaymentCompetition(String code, BigDecimal money,Integer isFenZhang,Integer operatorId) throws Exception {
//
//        payMoneyUtil.weixinpayV3(WxV3PayConfig.smidVx,"报名赛事",code,
//                "/base/competition/weChatPaymentCompetitionCallback1",
//                Long.valueOf(money.toString()));
        ResultUtil weixinpay = payMoneyUtil.weixinpay("报名赛事"+"-"+isFenZhang, "", code, money.toString(),
                "/base/competition/weChatPaymentCompetitionCallback", "APP", "");
        if (weixinpay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>()
                                    .eq("code", code).eq("payType", 1));
                            if (paymentCompetition.getPayStatus() == 2) {
                                break;
                            }
                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, "");
                            if (resultUtil.getCode() == 200 && paymentCompetition.getPayStatus() == 1) {
                                /**
                                 * SUCCESS—支付成功,
                                 * REFUND—转入退款,
                                 * NOTPAY—未支付,
                                 * CLOSED—已关闭,
                                 * REVOKED—已撤销(刷卡支付),
                                 * USERPAYING--用户支付中,
                                 * PAYERROR--支付失败(其他原因,如银行返回失败)
                                 */
                                Map<String, String> data1 = resultUtil.getData();
                                String s = data1.get("trade_state");
                                String transaction_id = data1.get("transaction_id");
                                if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) {
                                    paymentCompetition.setAppUserId(null);
                                    paymentCompetition.setState(3);
                                    userCompetitionService.remove(new QueryWrapper<UserCompetition>().eq("paymentCompetitionId", paymentCompetition.getId()));
                                    break;
                                }
                                if ("SUCCESS".equals(s)) {
                                    paymentCompetition.setAppUserId(null);
                                    paymentCompetition.setPayStatus(2);
                                    paymentCompetition.setPayTime(new Date());
                                    paymentCompetition.setPayOrderNo(transaction_id);
                                    paymentCompetitionService.updateById(paymentCompetition);
                                    Competition competition = cttService.getById(paymentCompetition.getCompetitionId());
                                    competition.setApplicantsNumber(competition.getApplicantsNumber() + 1);
                                    cttService.updateById(competition);
                                    break;
                                }
                                if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        return weixinpay;
    }
@@ -449,80 +408,80 @@
    public ResultUtil aliPaymentCompetition(Integer operatorId,String smid,String code, BigDecimal money) throws Exception {
        ResultUtil alipay = payMoneyUtil.alipay
                (smid,"报名赛事", "", "", code, money.toString(), "/base/competition/aliPaymentCompetitionCallback");
        if (alipay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>().eq("code", code).eq("payType", 2));
                            if (paymentCompetition.getPayStatus() == 2) {
                                break;
                            }
                            AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code);
                            if (resultUtil.getCode().equals("10000") && paymentCompetition.getPayStatus() == 1) {
                                /**
                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
                                 * TRADE_SUCCESS(交易支付成功)、
                                 * TRADE_FINISHED(交易结束,不可退款)
                                 */
//                                Map<String, String> data1 = resultUtil.getData();
//                                String s = data1.get("tradeStatus");
//                                String tradeNo = data1.get("tradeNo");
                                String tradeNo = resultUtil.getTradeNo();
                                String s = resultUtil.getTradeStatus();
                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
                                    paymentCompetition.setAppUserId(null);
                                    paymentCompetition.setState(3);
                                    userCompetitionService.remove(new QueryWrapper<UserCompetition>().eq("paymentCompetitionId", paymentCompetition.getId()));
                                    break;
                                }
                                if ("TRADE_SUCCESS".equals(s)) {
                                    paymentCompetition.setAppUserId(null);
                                    paymentCompetition.setPayStatus(2);
                                    paymentCompetition.setPayTime(new Date());
                                    paymentCompetition.setPayOrderNo(tradeNo);
                                    paymentCompetitionService.updateById(paymentCompetition);
                                    Competition competition = cttService.getById(paymentCompetition.getCompetitionId());
                                    competition.setApplicantsNumber(competition.getApplicantsNumber() + 1);
                                    cttService.updateById(competition);
                                    // 结算资金到商户账号
                                    payMoneyUtil.confirm(smid,code,tradeNo,money.toString());
                                    if (operatorId == null || operatorId == 0){
                                        // 属于平台的赛事 资金不做冻结 不做分账处理
                                        payMoneyUtil.confirm1(smid,code,tradeNo,money.toString());
                                    }else{
                                        // 资金冻结
                                        payMoneyUtil.confirm(smid,code,tradeNo,money.toString());
                                        // 分账处理
                                        extracted(operatorId, money, tradeNo);
                                    }
                                    // 结算资金到商户账号
                                    //分账
//                                    moneyOut(tradeNo, tradeNo,code);
                                    break;
                                }
                                if ("WAIT_BUYER_PAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
//        if (alipay.getCode() == 200) {
//            new Thread(new Runnable() {
//                @Override
//                public void run() {
//                    try {
//                        int num = 1;
//                        int wait = 0;
//                        while (num <= 10) {
//                            int min = 5000;
//                            wait += (min * num);
//                            Thread.sleep(wait);
//                            PaymentCompetition paymentCompetition = paymentCompetitionService.getOne(new QueryWrapper<PaymentCompetition>().eq("code", code).eq("payType", 2));
//                            if (paymentCompetition.getPayStatus() == 2) {
//                                break;
//                            }
//                            AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code);
//                            if (resultUtil.getCode().equals("10000") && paymentCompetition.getPayStatus() == 1) {
//                                /**
//                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
//                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
//                                 * TRADE_SUCCESS(交易支付成功)、
//                                 * TRADE_FINISHED(交易结束,不可退款)
//                                 */
////                                Map<String, String> data1 = resultUtil.getData();
////                                String s = data1.get("tradeStatus");
////                                String tradeNo = data1.get("tradeNo");
//                                String tradeNo = resultUtil.getTradeNo();
//                                String s = resultUtil.getTradeStatus();
//
//                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
//                                    paymentCompetition.setAppUserId(null);
//                                    paymentCompetition.setState(3);
//                                    userCompetitionService.remove(new QueryWrapper<UserCompetition>().eq("paymentCompetitionId", paymentCompetition.getId()));
//
//                                    break;
//                                }
//                                if ("TRADE_SUCCESS".equals(s)) {
//                                    paymentCompetition.setAppUserId(null);
//                                    paymentCompetition.setPayStatus(2);
//                                    paymentCompetition.setPayTime(new Date());
//                                    paymentCompetition.setPayOrderNo(tradeNo);
//                                    paymentCompetitionService.updateById(paymentCompetition);
//
//                                    Competition competition = cttService.getById(paymentCompetition.getCompetitionId());
//                                    competition.setApplicantsNumber(competition.getApplicantsNumber() + 1);
//                                    cttService.updateById(competition);
//
//                                    // 结算资金到商户账号
//                                    payMoneyUtil.confirm(smid,code,tradeNo,money.toString());
//                                    if (operatorId == null || operatorId == 0){
//                                        // 属于平台的赛事 资金不做冻结 不做分账处理
//                                        payMoneyUtil.confirm1(smid,code,tradeNo,money.toString());
//                                    }else{
//                                        // 资金冻结
//                                        payMoneyUtil.confirm(smid,code,tradeNo,money.toString());
//                                        // 分账处理
//                                        extracted(operatorId, money, tradeNo);
//                                    }
//                                    // 结算资金到商户账号
//                                    //分账
////                                    moneyOut(tradeNo, tradeNo,code);
//                                    break;
//                                }
//                                if ("WAIT_BUYER_PAY".equals(s)) {
//                                    num++;
//                                }
//                            }
//                        }
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                }
//            }).start();
//        }
        return alipay;
    }
    private void extracted(Integer operatorId, BigDecimal money, String tradeNo) throws Exception {
cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java
@@ -139,47 +139,47 @@
        //查出临时表里的数据,也去重
        List<Participant> list = this.list(new QueryWrapper<Participant>().eq("appUserId", uid).eq("state", 1));
        List<ParticipantVo> listVo = new ArrayList<>();
        for (Participant participant : list) {
            ParticipantVo participantVo = new ParticipantVo();
            participantVo.setId(participant.getId());
            participantVo.setName(participant.getName());
            participantVo.setIdcard(participant.getIdcard());
            if (null != participant.getBirthday()){
                Integer age = Integer.valueOf(sdf_year.format(new Date())) - Integer.valueOf(sdf_year.format(participant.getBirthday()));
                participantVo.setAge(age);
            }else{
                participantVo.setAge(18);
            }
            Student student = null;
            if(ToolUtil.isNotEmpty(participant.getPhone())){
                student = studentClient.queryStudentByPhone(participant.getPhone());
            }
            //设为不是学员
            participantVo.setIsStudent(0);
            if (null != student) {
                Integer integer = coursePackagePaymentClient.queryResidueClassHour(student.getId());
                participantVo.setResidueClassHour(integer);
            } else {
                participantVo.setResidueClassHour(0);
            }
            participantVo.setPhone(participant.getPhone());
            participantVo.setHeight(participant.getHeight());
            participantVo.setWeight(participant.getWeight());
            participantVo.setHeadImg(participant.getHeadImg());
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            if (null != participant.getBirthday()) {
                String format = sdf.format(participant.getBirthday());
                participantVo.setBirthday(format);
            }
            participantVo.setGender(participant.getGender());
            if (participant.getIdcard() == null || participant.getIdcard().isEmpty()) {
                listVo1.add(participantVo);
                continue;
            }
            linkedHashMap.put(participant.getIdcard(), participantVo);
        }
//        for (Participant participant : list) {
//            ParticipantVo participantVo = new ParticipantVo();
//            participantVo.setId(participant.getId());
//            participantVo.setName(participant.getName());
//            participantVo.setIdcard(participant.getIdcard());
//            if (null != participant.getBirthday()){
//                Integer age = Integer.valueOf(sdf_year.format(new Date())) - Integer.valueOf(sdf_year.format(participant.getBirthday()));
//                participantVo.setAge(age);
//            }else{
//                participantVo.setAge(18);
//
//            }
//            Student student = null;
//            if(ToolUtil.isNotEmpty(participant.getPhone())){
//                student = studentClient.queryStudentByPhone(participant.getPhone());
//            }
//            //设为不是学员
//            participantVo.setIsStudent(0);
//
//            if (null != student) {
//                Integer integer = coursePackagePaymentClient.queryResidueClassHour(student.getId());
//                participantVo.setResidueClassHour(integer);
//            } else {
//                participantVo.setResidueClassHour(0);
//            }
//            participantVo.setPhone(participant.getPhone());
//            participantVo.setHeight(participant.getHeight());
//            participantVo.setWeight(participant.getWeight());
//            participantVo.setHeadImg(participant.getHeadImg());
//            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
//            if (null != participant.getBirthday()) {
//                String format = sdf.format(participant.getBirthday());
//                participantVo.setBirthday(format);
//            }
//            participantVo.setGender(participant.getGender());
//            if (participant.getIdcard() == null || participant.getIdcard().isEmpty()) {
//                listVo1.add(participantVo);
//                continue;
//            }
//            linkedHashMap.put(participant.getIdcard(), participantVo);
//        }
        for (TStudent tStudent : tStudents) {
            ParticipantVo participantVo = new ParticipantVo();
cloud-server-competition/src/main/java/com/dsh/competition/util/PayMoneyUtil.java
@@ -11,6 +11,10 @@
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.dsh.competition.util.httpClinet.HttpClientUtil;
import com.dsh.competition.util.wx.PartnerAppPrepay;
import com.dsh.competition.util.wx.WXPayUtility;
import com.dsh.competition.util.wx.WeChatV3SignUtil;
import com.dsh.competition.util.wx.WxV3PayConfig;
import org.apache.commons.collections.map.HashedMap;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.dom4j.Document;
@@ -150,9 +154,7 @@
        SubMerchant subMerchant = new SubMerchant();
        subMerchant.setMerchantId(smid);
        model.setSubMerchant(subMerchant);
        ExtendParams extendParams = new ExtendParams();
        extendParams.setRoyaltyFreeze("true");// 冻结资金 用于后续分账处理
        model.setExtendParams(extendParams);
        request.setBizModel(model);
        request.setNotifyUrl(callbackPath + notifyUrl);
@@ -409,17 +411,11 @@
        map.put("appid", appid);
        map.put("mch_id", mchId);
        map.put("nonce_str", nonce_str);
        String temp = "";
        if (body.split("-").length>1){
            temp = body.split("-")[1];
            map.put("body", body.split("-")[0]);
        }else{
            map.put("body", body);
        }
        if (StringUtils.hasLength(temp) && temp.equals("1")){
            // 添加分账标识
            map.put("profit_sharing", "Y");
        }
        map.put("attach", attach);//存储订单id
        map.put("out_trade_no", out_trade_no);//存储的订单code
        map.put("total_fee", i);
@@ -510,6 +506,62 @@
            return ResultUtil.error(map1.get("return_msg"), new JSONObject());
        }
    }
    public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee) throws Exception {
        int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue();
        // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
        PartnerAppPrepay client = new PartnerAppPrepay(
//                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//                "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//                "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
                "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
                "/usr/playpai/server/wxV3/pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
        );
        PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest();
        request.spAppid = appid;// appid
        request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号
        request.subMchid = subMchid;// 子商户号
        request.description = description;// 描述
        request.outTradeNo = outTradeNo;// 订单号
        request.notifyUrl =callbackPath+ notifyUrl;// 回调地址
        request.amount = new PartnerAppPrepay.CommonAmountInfo();
        request.amount.total = (long) i;// 金额 单位分
        request.amount.currency = "CNY";
        String prepayId ="";
        Map<String, Object> map3 = new HashMap<>();
        try {
            PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request);
            // TODO: 请求成功,继续业务逻辑
            System.err.println("微信申请成功,预支付ID: " + response.prepayId);
            prepayId = response.prepayId;
        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败,根据状态码执行不同的逻辑
            e.printStackTrace();
        }
        map3.put("appid", appid);
        map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        String nonce_str = UUIDUtil.getRandomCode(16);
        map3.put("noncestr", nonce_str);
        map3.put("prepayid", prepayId);
        // 构造待签名字符串
        String message = WeChatV3SignUtil.buildSignMessage(map3);
        // 私钥路径(pem 文件)
        String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
//      String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem";
        // 生成签名
        String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath);
        map3.put("sign", sign);
        map3.put("package", "Sign=WXPay");
        map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号
        System.err.println(map3);
        return ResultUtil.success(map3);
    }
    /**
     * 发起分账
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/PartnerAppPrepay.java
New file
@@ -0,0 +1,237 @@
package com.dsh.competition.util.wx;
import com.google.gson.annotations.SerializedName;
import okhttp3.*;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;
/**
 * App下单
 */
public class PartnerAppPrepay {
  private static String HOST = "https://api.mch.weixin.qq.com";
  private static String METHOD = "POST";
  private static String PATH = "/v3/pay/partner/transactions/app";
//  public static void main(String[] args) {
//    // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
//    PartnerAppPrepay client = new PartnerAppPrepay(
//        "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//        "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//        "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//        "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//        "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
//    );
//
//    PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest();
//    request.spAppid = "wx41d32f362ba0f911";
//    request.spMchid = WxV3PayConfig.Mch_ID;
//    request.subMchid = "1720719391";
//    request.description = "Image形象店-深圳腾大-QQ公仔";
//    request.outTradeNo = "12177525012014070332333680182";
//    request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php";
////    request.goodsTag = "WXG";
////    request.settleInfo = new PartnerSettleInfo();
////    request.settleInfo.profitSharing = false;
//    request.amount = new CommonAmountInfo();
//    request.amount.total = 100L;
//    request.amount.currency = "CNY";
////    request.detail = new CouponInfo();
////    request.detail.costPrice = 1L;
////    request.detail.invoiceId = "wx123";
////    request.detail.goodsDetail = new ArrayList<>();
////    {
////      GoodsDetail item0 = new GoodsDetail();
////      item0.merchantGoodsId = "1246464644";
////      item0.wechatpayGoodsId = "1001";
////      item0.goodsName = "iPhone6s 16G";
////      item0.quantity = 1L;
////      item0.unitPrice = 528800L;
////      request.detail.goodsDetail.add(item0);
////    };
////    request.sceneInfo = new CommonSceneInfo();
////    request.sceneInfo.payerClientIp = "14.23.150.211";
////    request.sceneInfo.deviceId = "013467007045764";
////    request.sceneInfo.storeInfo = new StoreInfo();
////    request.sceneInfo.storeInfo.id = "0001";
////    request.sceneInfo.storeInfo.name = "腾讯大厦分店";
////    request.sceneInfo.storeInfo.areaCode = "440305";
////    request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
//    try {
//      PartnerAPIv3AppPrepayResponse response = client.run(request);
//
//      // TODO: 请求成功,继续业务逻辑
//      System.err.println("微信申请成功,预支付ID: " + response.prepayId);
//    } catch (WXPayUtility.ApiException e) {
//      // TODO: 请求失败,根据状态码执行不同的逻辑
//      e.printStackTrace();
//    }
//  }
  public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) {
    String uri = PATH;
    String reqBody = WXPayUtility.toJson(request);
    Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
    reqBuilder.addHeader("Accept", "application/json");
    reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
    reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
    reqBuilder.addHeader("Content-Type", "application/json");
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
    reqBuilder.method(METHOD, requestBody);
    Request httpRequest = reqBuilder.build();
    // 发送HTTP请求
    OkHttpClient client = new OkHttpClient.Builder().build();
    try (Response httpResponse = client.newCall(httpRequest).execute()) {
      String respBody = WXPayUtility.extractBody(httpResponse);
      if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
        // 2XX 成功,验证应答签名
        WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                                      httpResponse.headers(), respBody);
        // 从HTTP应答报文构建返回数据
        return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class);
      } else {
        throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
      }
    } catch (IOException e) {
      throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
    }
  }
  private final String mchid;
  private final String certificateSerialNo;
  private final PrivateKey privateKey;
  private final String wechatPayPublicKeyId;
  private final PublicKey wechatPayPublicKey;
  public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
    this.mchid = mchid;
    this.certificateSerialNo = certificateSerialNo;
    this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
    this.wechatPayPublicKeyId = wechatPayPublicKeyId;
    this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
  }
  public static class PartnerSettleInfo {
    @SerializedName("profit_sharing")
    public Boolean profitSharing;
  }
  public static class CommonAmountInfo {
    @SerializedName("total")
    public Long total;
    @SerializedName("currency")
    public String currency;
  }
  public static class CommonSceneInfo {
    @SerializedName("payer_client_ip")
    public String payerClientIp;
    @SerializedName("device_id")
    public String deviceId;
    @SerializedName("store_info")
    public StoreInfo storeInfo;
  }
  public static class GoodsDetail {
    @SerializedName("merchant_goods_id")
    public String merchantGoodsId;
    @SerializedName("wechatpay_goods_id")
    public String wechatpayGoodsId;
    @SerializedName("goods_name")
    public String goodsName;
    @SerializedName("quantity")
    public Long quantity;
    @SerializedName("unit_price")
    public Long unitPrice;
  }
  public static class PartnerAPIv3AppPrepayResponse {
    @SerializedName("prepay_id")
    public String prepayId;
  }
  public static class PartnerAPIv3CommonPrepayRequest {
    @SerializedName("sp_appid")
    public String spAppid;
    @SerializedName("sp_mchid")
    public String spMchid;
    @SerializedName("sub_appid")
    public String subAppid;
    @SerializedName("sub_mchid")
    public String subMchid;
    @SerializedName("description")
    public String description;
    @SerializedName("out_trade_no")
    public String outTradeNo;
    @SerializedName("time_expire")
    public String timeExpire;
    @SerializedName("attach")
    public String attach;
    @SerializedName("notify_url")
    public String notifyUrl;
    @SerializedName("goods_tag")
    public String goodsTag;
    @SerializedName("settle_info")
    public PartnerSettleInfo settleInfo;
    @SerializedName("support_fapiao")
    public Boolean supportFapiao;
    @SerializedName("amount")
    public CommonAmountInfo amount;
    @SerializedName("detail")
    public CouponInfo detail;
    @SerializedName("scene_info")
    public CommonSceneInfo sceneInfo;
  }
  public static class CouponInfo {
    @SerializedName("cost_price")
    public Long costPrice;
    @SerializedName("invoice_id")
    public String invoiceId;
    @SerializedName("goods_detail")
    public List<GoodsDetail> goodsDetail;
  }
  public static class StoreInfo {
    @SerializedName("id")
    public String id;
    @SerializedName("name")
    public String name;
    @SerializedName("area_code")
    public String areaCode;
    @SerializedName("address")
    public String address;
  }
}
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/WXPayUtility.java
New file
@@ -0,0 +1,441 @@
package com.dsh.competition.util.wx;
import com.google.gson.*;
import com.google.gson.annotations.Expose;
import com.wechat.pay.java.core.util.GsonUtil;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
public class WXPayUtility {
  private static final Gson gson = new GsonBuilder()
      .disableHtmlEscaping()
      .addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.serialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .addDeserializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.deserialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .create();
  private static final char[] SYMBOLS =
      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
  private static final SecureRandom random = new SecureRandom();
  /**
   * 将 Object 转换为 JSON 字符串
   */
  public static String toJson(Object object) {
    return gson.toJson(object);
  }
  /**
   * 将 JSON 字符串解析为特定类型的实例
   */
  public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    return gson.fromJson(json, classOfT);
  }
  /**
   * 从公私钥文件路径中读取文件内容
   *
   * @param keyPath 文件路径
   * @return 文件内容
   */
  private static String readKeyStringFromPath(String keyPath) {
    try {
      return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }
  /**
   * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
   *
   * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
          .replace("-----END PRIVATE KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePrivate(
          new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的私钥文件中加载私钥
   *
   * @param keyPath 私钥文件路径
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
    return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
   *
   * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
          .replace("-----END PUBLIC KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePublic(
          new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的公钥文件中加载公钥
   *
   * @param keyPath 公钥文件路径
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromPath(String keyPath) {
    return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
   */
  public static String createNonce(int length) {
    char[] buf = new char[length];
    for (int i = 0; i < length; ++i) {
      buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
    }
    return new String(buf);
  }
  /**
   * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
   *
   * @param publicKey 加密用公钥对象
   * @param plaintext 待加密明文
   * @return 加密后密文
   */
  public static String encrypt(PublicKey publicKey, String plaintext) {
    final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
    try {
      Cipher cipher = Cipher.getInstance(transformation);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
      throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
      throw new IllegalArgumentException("Plaintext is too long", e);
    }
  }
  /**
   * 使用私钥按照指定算法进行签名
   *
   * @param message 待签名串
   * @param algorithm 签名算法,如 SHA256withRSA
   * @param privateKey 签名用私钥对象
   * @return 签名结果
   */
  public static String sign(String message, String algorithm, PrivateKey privateKey) {
    byte[] sign;
    try {
      Signature signature = Signature.getInstance(algorithm);
      signature.initSign(privateKey);
      signature.update(message.getBytes(StandardCharsets.UTF_8));
      sign = signature.sign();
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
    } catch (SignatureException e) {
      throw new RuntimeException("An error occurred during the sign process.", e);
    }
    return Base64.getEncoder().encodeToString(sign);
  }
  /**
   * 使用公钥按照特定算法验证签名
   *
   * @param message 待签名串
   * @param signature 待验证的签名内容
   * @param algorithm 签名算法,如:SHA256withRSA
   * @param publicKey 验签用公钥对象
   * @return 签名验证是否通过
   */
  public static boolean verify(String message, String signature, String algorithm,
                               PublicKey publicKey) {
    try {
      Signature sign = Signature.getInstance(algorithm);
      sign.initVerify(publicKey);
      sign.update(message.getBytes(StandardCharsets.UTF_8));
      return sign.verify(Base64.getDecoder().decode(signature));
    } catch (SignatureException e) {
      return false;
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("verify uses an illegal publickey.", e);
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
    }
  }
  /**
   * 根据微信支付APIv3请求签名规则构造 Authorization 签名
   *
   * @param mchid 商户号
   * @param certificateSerialNo 商户API证书序列号
   * @param privateKey 商户API证书私钥
   * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
   * @param uri 请求接口的URL
   * @param body 请求接口的Body
   * @return 构造好的微信支付APIv3 Authorization 头
   */
  public static String buildAuthorization(String mchid, String certificateSerialNo,
                                          PrivateKey privateKey,
                                          String method, String uri, String body) {
    String nonce = createNonce(32);
    long timestamp = Instant.now().getEpochSecond();
    String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
        body == null ? "" : body);
    String signature = sign(message, "SHA256withRSA", privateKey);
    return String.format(
        "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
            "timestamp=\"%d\",serial_no=\"%s\"",
        mchid, nonce, signature, timestamp, certificateSerialNo);
  }
  /**
   * 对参数进行 URL 编码
   *
   * @param content 参数内容
   * @return 编码后的内容
   */
  public static String urlEncode(String content) {
    try {
      return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }
  /**
   * 对参数Map进行 URL 编码,生成 QueryString
   *
   * @param params Query参数Map
   * @return QueryString
   */
  public static String urlEncode(Map<String, Object> params) {
    if (params == null || params.isEmpty()) {
      return "";
    }
    int index = 0;
    StringBuilder result = new StringBuilder();
    for (Map.Entry<String, Object> entry : params.entrySet()) {
      result.append(entry.getKey())
          .append("=")
          .append(urlEncode(entry.getValue().toString()));
      index++;
      if (index < params.size()) {
        result.append("&");
      }
    }
    return result.toString();
  }
  /**
   * 从应答中提取 Body
   *
   * @param response HTTP 请求应答对象
   * @return 应答中的Body内容,Body为空时返回空字符串
   */
  public static String extractBody(Response response) {
    if (response.body() == null) {
      return "";
    }
    try {
      BufferedSource source = response.body().source();
      return source.readUtf8();
    } catch (IOException e) {
      throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e);
    }
  }
  /**
   * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
   *
   * @param wechatpayPublicKeyId 微信支付公钥ID
   * @param wechatpayPublicKey 微信支付公钥对象
   * @param headers 微信支付应答 Header 列表
   * @param body 微信支付应答 Body
   */
  public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                      Headers headers,
                                      String body) {
    String timestamp = headers.get("Wechatpay-Timestamp");
    try {
      Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
      // 拒绝过期请求
      if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
        throw new IllegalArgumentException(
            String.format("Validate http response,timestamp[%s] of httpResponse is expires, "
                    + "request-id[%s]",
                timestamp, headers.get("Request-ID")));
      }
    } catch (DateTimeException | NumberFormatException e) {
      throw new IllegalArgumentException(
          String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " +
                  "request-id[%s]", timestamp,
              headers.get("Request-ID")));
    }
    String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
        body == null ? "" : body);
    String serialNumber = headers.get("Wechatpay-Serial");
    if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
      throw new IllegalArgumentException(
          String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId,
              serialNumber));
    }
    String signature = headers.get("Wechatpay-Signature");
    boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
    if (!success) {
      throw new IllegalArgumentException(
          String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                  + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
              headers.get("Request-ID"), headers, body));
    }
  }
  /**
   * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
   */
  public static class ApiException extends RuntimeException {
    private static final long serialVersionUID = 2261086748874802175L;
    private final int statusCode;
    private final String body;
    private final Headers headers;
    private final String errorCode;
    private final String errorMessage;
    public ApiException(int statusCode, String body, Headers headers) {
      super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers));
      this.statusCode = statusCode;
      this.body = body;
      this.headers = headers;
      if (body != null && !body.isEmpty()) {
        JsonElement code;
        JsonElement message;
        try {
          JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class);
          code = jsonObject.get("code");
          message = jsonObject.get("message");
        } catch (JsonSyntaxException ignored) {
          code = null;
          message = null;
        }
        this.errorCode = code == null ? null : code.getAsString();
        this.errorMessage = message == null ? null : message.getAsString();
      } else {
        this.errorCode = null;
        this.errorMessage = null;
      }
    }
    /**
     * 获取 HTTP 应答状态码
     */
    public int getStatusCode() {
      return statusCode;
    }
    /**
     * 获取 HTTP 应答包体内容
     */
    public String getBody() {
      return body;
    }
    /**
     * 获取 HTTP 应答 Header
     */
    public Headers getHeaders() {
      return headers;
    }
    /**
     * 获取 错误码 (错误应答中的 code 字段)
     */
    public String getErrorCode() {
      return errorCode;
    }
    /**
     * 获取 错误消息 (错误应答中的 message 字段)
     */
    public String getErrorMessage() {
      return errorMessage;
    }
  }
}
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/WeChatSignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.competition.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
public class WeChatSignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 使用商户私钥对字符串进行 SHA256withRSA 签名
     *
     * @param content      待签名字符串
     * @param privateKeyPath 商户私钥文件路径(pem 格式)
     * @return 签名结果(Base64 编码)
     */
    public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception {
        String privateKeyPEM = readPemFile(privateKeyPath);
        String privateKeyContent = privateKeyPEM
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");
        byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }
    private static String readPemFile(String filePath) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
        }
        return sb.toString();
    }
    /**
     * 按照微信 V3 规范生成待签名字符串
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            Object value = map.get(key);
            if (value != null && !value.toString().isEmpty()) {
                sb.append(key).append("=").append(value).append("\n");
            }
        }
        return sb.toString();
    }
}
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/WeChatV3SignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.competition.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
public class WeChatV3SignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 从 PEM 文件中读取私钥内容
     */
    public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith("-----") && !line.endsWith("-----")) {
                    sb.append(line);
                }
            }
        }
        byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
        KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
        return kf.generatePrivate(keySpec);
    }
    /**
     * 构造待签名字符串(按 key 字典序排序)
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
//        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        sb.append(map.get("appid")).append("\n");
        sb.append(map.get("timestamp")).append("\n");
        sb.append(map.get("noncestr")).append("\n");
        sb.append(map.get("prepayid")).append("\n");
        return sb.toString();
    }
    /**
     * 使用商户私钥生成签名(SHA256withRSA + Base64)
     */
    public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception {
        PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signedBytes);
    }
}
cloud-server-competition/src/main/java/com/dsh/competition/util/wx/WxV3PayConfig.java
New file
@@ -0,0 +1,66 @@
package com.dsh.competition.util.wx;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class WxV3PayConfig {
    // 服务商AppId
    private String appIdValue = "wx41d32f362ba0f911";
    public static String APP_ID= "wx41d32f362ba0f911";
    // 服务商商户号
    private String mchIdValue= "1681873607";
    public static String Mch_ID= "1681873607";
    // 平台收款商户号 todo 待申请
    public static String smidVx= "2088330203191220";
    private  String smidVxValue= "2088330203191220";
    // 服务商商户私钥
    private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq";
    public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq";
    // 证书序列号
    private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A";
    public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A";
    private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量
    @PostConstruct
    public void init() {
        APP_ID = this.appIdValue;
        APP_ID = this.appIdValue;
        smidVx = this.smidVxValue;
        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-course/pom.xml
@@ -15,6 +15,32 @@
    <description>课程</description>
    <dependencies>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java-core</artifactId>
            <version>0.2.12</version>
            <scope>compile</scope>
        </dependency>
        <!-- 微信支付V3 目前新版本-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.9</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <!-- OkHttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
cloud-server-course/src/main/java/com/dsh/course/controller/CourseController.java
@@ -2,6 +2,7 @@
import cn.mb.cloud.common.data.controller.BaseController;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -14,6 +15,8 @@
import com.dsh.course.model.vo.TQueryBenefitsVideosVO;
import com.dsh.course.service.*;
import com.dsh.course.util.*;
import com.dsh.course.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -26,8 +29,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
@@ -267,6 +272,47 @@
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/course/weChatPaymentCourseCallback1")
    public void weChatPaymentCourseCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String code = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            String attach = jsonInfo.getString("attach");
            if (trade_state.equals("SUCCESS")) {
                ResultUtil resultUtil = coursePackageService.paymentCourseCallback(code, transaction_id, attach);
                if (resultUtil.getCode() == 200) {
                    PrintWriter out = response.getWriter();
                    out.write("SUCCESS");
                    out.flush();
                    out.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @ResponseBody
    @PostMapping("/base/course/weChatPaymentCourseCallback")
    public void weChatPaymentCourseCallback(HttpServletRequest request, HttpServletResponse response) {
        try {
@@ -289,7 +335,6 @@
            e.printStackTrace();
        }
    }
    /**
     * 购买课程支付宝回调
cloud-server-course/src/main/java/com/dsh/course/controller/CoursePackagePaymentController.java
@@ -26,9 +26,11 @@
import com.dsh.course.model.vo.response.*;
import com.dsh.course.service.*;
import com.dsh.course.util.*;
import com.dsh.course.util.wx.WxV3PayConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.obs.services.internal.ServiceException;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -44,8 +46,10 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
@@ -1216,6 +1220,51 @@
    /**
     * 课包续课微信支付回调接口
     */
    @PostMapping("/base/coursePackage/wechatRegisteredCoursesCallback1")
    public void weChatCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(requestBody.toString());
            com.alibaba.fastjson.JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            com.alibaba.fastjson.JSONObject jsonInfo = (com.alibaba.fastjson.JSONObject) com.alibaba.fastjson.JSONObject.parse(decryptedData);
            String out_trade_no = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                ResultUtil resultUtil = packagePaymentService.insertVipPaymentCallback(out_trade_no, transaction_id);
                if (resultUtil.getCode() == 200) {
                    PrintWriter out = response.getWriter();
                    out.write("SUCCESS");
                    out.flush();
                    out.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 课包续课微信支付回调接口
     */
    /**
     * 课包续课微信支付回调接口
     */
    @PostMapping("/base/coursePackage/wechatRegisteredCoursesCallback")
    public void weChatCallback(HttpServletRequest request, HttpServletResponse response) {
        try {
@@ -1236,8 +1285,6 @@
            e.printStackTrace();
        }
    }
    /**
     * 获取学员剩余课时
     *
cloud-server-course/src/main/java/com/dsh/course/feignclient/other/StoreClient.java
@@ -22,7 +22,8 @@
     */
    @PostMapping("/base/getProportionByOperatorId/{id}")
    String getProportionByOperatorId(@PathVariable("id")Integer id);
    @PostMapping("/base/getmerchantNumberAliByOperatorId/{id}")
    String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id);
    /**
     * 根据运营商id获取对应运营商商户号
     * @return
cloud-server-course/src/main/java/com/dsh/course/service/impl/TCoursePackagePaymentServiceImpl.java
@@ -37,6 +37,7 @@
import com.dsh.course.model.vo.response.*;
import com.dsh.course.service.*;
import com.dsh.course.util.*;
import com.dsh.course.util.wx.WxV3PayConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
@@ -928,80 +929,19 @@
        Integer storeId = byId.getStoreId();
        Store store = storeClient.queryStoreById(storeId);
        System.err.println("门店查询"+store);
        // 是否分账 0否1是
        int isFenZhang= 1;
        String merchantNumber = "";
        if (store.getOperatorId()==null || store.getOperatorId()==0){
            // 平台
            isFenZhang = 0;
        }
        System.err.println("拉起支付");
        ResultUtil weixinpay = payMoneyUtil.weixinpay("课包续费"+"-"+isFenZhang, "", code, request.toString(),
                    ResultUtil weixinpay = payMoneyUtil.weixinpay("课包续费", "", code, request.toString(),
                "/base/coursePackage/wechatRegisteredCoursesCallback", "APP", "");
        if (weixinpay.getCode() == 200) {
            String finalCode = code;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>()
                                    .eq("code", finalCode).eq("state", 1));
                            if (coursePackageOrder1.getPayStatus() == 2) {
                                break;
                            }
                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(finalCode, "");
                            if (resultUtil.getCode() == 200 && coursePackageOrder1.getPayStatus() == 1) {
                                /**
                                 * SUCCESS—支付成功,
                                 * REFUND—转入退款,
                                 * NOTPAY—未支付,
                                 * CLOSED—已关闭,
                                 * REVOKED—已撤销(刷卡支付),
                                 * USERPAYING--用户支付中,
                                 * PAYERROR--支付失败(其他原因,如银行返回失败)
                                 */
                                Map<String, String> data1 = resultUtil.getData();
                                String s = data1.get("trade_state");
                                String transaction_id = data1.get("transaction_id");
                                if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) {
                                    //有待支付的订单,这里不处理
//                                    coursePackageOrder1.setState(3);
//                                    coursePackageOrderService.updateById(coursePackageOrder1);
                                    break;
                                }
                                if ("SUCCESS".equals(s)) {
                                    coursePackageOrder1.setPayStatus(2);
                                    coursePackageOrder1.setOrderNumber(transaction_id);
                                    coursePackageOrder1.setAppUserId(null);
                                    coursePackageOrderService.updateById(coursePackageOrder1);
                                    //修改课时有效期
                                    CoursePackagePaymentConfig coursePackagePaymentConfig = coursePackagePaymentConfigService.getOne(new QueryWrapper<CoursePackagePaymentConfig>()
                                            .eq("coursePackageId", coursePackageOrder1.getId())
                                            .eq("classHours", coursePackageOrder1.getClassHours())
                                    );
                                    coursePackageService.addCoursePackageOrderStudent(coursePackageOrder1.getId(), coursePackagePaymentConfig);
                                    break;
                                }
                                if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        return weixinpay;
        }else{
            String s = storeClient.getmerchantNumberByOperatorId(store.getOperatorId());
            System.err.println("微信商户号"+s);
            if(!StringUtils.hasLength(s)){
                return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
            }
            return payMoneyUtil.weixinpayV3(s,"课包续费",code,
                    "/base/coursePackage/wechatRegisteredCoursesCallback1",request.toString(),"");
        }
    }
    private String smid = "2088330203191220";//平台支付宝商户号
@@ -1034,79 +974,82 @@
        }else if (store.getOperatorId() == 0){
            smid1 = smid;
        }else{
            smid1 = storeClient.getSMIDByOperatorId(store.getOperatorId());
            smid1 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId());
        }
        if (!StringUtils.hasLength(smid1)){
            return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
        }
        ResultUtil alipay = payMoneyUtil.alipay(smid1,"课包购买", "课包购买", "", code, request.toString(),
                "/base/coursePackage/alipayRegisteredCoursesCallback");
        if (alipay.getCode() == 200) {
            String finalCode = code;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>()
                                    .eq("code", finalCode).eq("state", 1));
                            if (coursePackageOrder1.getPayStatus() == 2) {
                                break;
                            }
                            AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(finalCode);
                            if (null != alipayTradeQueryResponse) {
                                /**
                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
                                 * TRADE_SUCCESS(交易支付成功)、
                                 * TRADE_FINISHED(交易结束,不可退款)
                                 */
                                String s = alipayTradeQueryResponse.getTradeStatus();
                                String tradeNo = alipayTradeQueryResponse.getTradeNo();
                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
                                    //有待支付的订单,这里不处理
//                                    coursePackageOrder1.setState(3);
//                                    coursePackageOrderService.updateById(coursePackageOrder1);
                                    break;
                                }
                                if ("TRADE_SUCCESS".equals(s)) {
                                    coursePackagePayment.setPayStatus(2);
                                    coursePackagePayment.setOrderNumber(tradeNo);
                                    baseMapper1.updateById(coursePackagePayment);
                                        // 判断这个课包属于哪个门店 属于哪个运营商 根据运营商 id 获取对应的商户号
                                        Integer coursePackageId = coursePackagePayment.getCoursePackageId();
                                        TCoursePackage byId = coursePackageService.getById(coursePackageId);
                                        Store store = storeClient.queryStoreById(byId.getStoreId());
                                        if (store.getOperatorId() == null ){
                                            // 说明是平台的门店 无需冻结资金 不走分账
                                            payMoneyUtil.confirm1(smid,code,tradeNo,request.toString());
                                        }else if (store.getOperatorId() == 0){
                                            // 说明是平台的门店
                                            payMoneyUtil.confirm1(smid,code,tradeNo,request.toString());
                                            // 发起分账
                                            extracted(store, coursePackageOrder1, tradeNo);
                                        }else{
                                            payMoneyUtil.confirm(storeClient.getSMIDByOperatorId(store.getOperatorId()),code,tradeNo,request.toString());
                                            // 发起分账
                                            extracted(store, coursePackageOrder1, tradeNo);
                                        }
                                    break;
                                }
                                if ("WAIT_BUYER_PAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
//        if (alipay.getCode() == 200) {
//            String finalCode = code;
//            new Thread(new Runnable() {
//                @Override
//                public void run() {
//                    try {
//                        Thread.sleep(1000);
//                        int num = 1;
//                        int wait = 0;
//                        while (num <= 10) {
//                            int min = 5000;
//                            wait += (min * num);
//                            Thread.sleep(wait);
//                            CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>()
//                                    .eq("code", finalCode).eq("state", 1));
//                            if (coursePackageOrder1.getPayStatus() == 2) {
//                                break;
//                            }
//                            AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(finalCode);
//                            if (null != alipayTradeQueryResponse) {
//                                /**
//                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
//                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
//                                 * TRADE_SUCCESS(交易支付成功)、
//                                 * TRADE_FINISHED(交易结束,不可退款)
//                                 */
//                                String s = alipayTradeQueryResponse.getTradeStatus();
//
//                                String tradeNo = alipayTradeQueryResponse.getTradeNo();
//                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
//                                    //有待支付的订单,这里不处理
////                                    coursePackageOrder1.setState(3);
////                                    coursePackageOrderService.updateById(coursePackageOrder1);
//                                    break;
//                                }
//                                if ("TRADE_SUCCESS".equals(s)) {
//                                    coursePackagePayment.setPayStatus(2);
//                                    coursePackagePayment.setOrderNumber(tradeNo);
//                                    baseMapper1.updateById(coursePackagePayment);
//                                        // 判断这个课包属于哪个门店 属于哪个运营商 根据运营商 id 获取对应的商户号
//                                        Integer coursePackageId = coursePackagePayment.getCoursePackageId();
//                                        TCoursePackage byId = coursePackageService.getById(coursePackageId);
//                                        Store store = storeClient.queryStoreById(byId.getStoreId());
//                                        if (store.getOperatorId() == null ){
//                                            // 说明是平台的门店 无需冻结资金 不走分账
//                                            payMoneyUtil.confirm1(smid,code,tradeNo,request.toString());
//                                        }else if (store.getOperatorId() == 0){
//                                            // 说明是平台的门店
//                                            payMoneyUtil.confirm1(smid,code,tradeNo,request.toString());
//                                            // 发起分账
//                                            extracted(store, coursePackageOrder1, tradeNo);
//                                        }else{
//                                            payMoneyUtil.confirm(storeClient.getSMIDByOperatorId(store.getOperatorId()),code,tradeNo,request.toString());
//                                            // 发起分账
//                                            extracted(store, coursePackageOrder1, tradeNo);
//                                        }
//                                    break;
//                                }
//                                if ("WAIT_BUYER_PAY".equals(s)) {
//                                    num++;
//                                }
//                            }
//                        }
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                }
//            }).start();
//        }
        return alipay;
    }
@@ -1353,27 +1296,7 @@
        Integer storeId = byId.getStoreId();
        Store store = storeClient.queryStoreById(storeId);
        if (store.getOperatorId()!=null && store.getOperatorId()!=0){
            // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
            Thread.sleep(120000);
            // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
            String proportionByOperatorId = storeClient.getProportionByOperatorId(store.getOperatorId());
            String[] split = proportionByOperatorId.split(",");
            String s1 = split[0];
            if (!s1.equals("未设置")){
                BigDecimal bigDecimal = new BigDecimal(s1);
                // 分账比例
                BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                // 微信商户号
                String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId());
                String nonce_str = UUIDUtil.getRandomCode(16);
                ResultUtil fenzhang = payMoneyUtil.fenzhang(orderNumber, coursePackageOrder1.getCashPayment().multiply(bigDecimal1), s2,nonce_str);
                if (!fenzhang.getCode().equals(200)){
                    System.err.println("分账失败 原因是:"+fenzhang.getData());
                }
            }
        }
        return ResultUtil.success();
    }
cloud-server-course/src/main/java/com/dsh/course/service/impl/TCoursePackageServiceImpl.java
@@ -36,8 +36,10 @@
import com.dsh.course.model.*;
import com.dsh.course.service.*;
import com.dsh.course.util.*;
import com.dsh.course.util.wx.WxV3PayConfig;
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;
@@ -945,80 +947,28 @@
        TCoursePackage byId = coursePackageService.getById(paymentCourseVo.getId());
        Integer storeId = byId.getStoreId();
        Store store = storeClient.queryStoreById(storeId);
        // 是否分账 0否1是
        int isFenZhang= 1;
        if (store.getOperatorId()==null || store.getOperatorId()==0){
            // 平台
            isFenZhang = 0;
        }
        ResultUtil weixinpay = payMoneyUtil.weixinpay("报名运动营"+"-"+isFenZhang, coursePackagePaymentConfig.getId().toString(), code, paymentPrice.toString(),
            ResultUtil weixinpay = payMoneyUtil.weixinpay("报名运动营", "", code, coursePackagePaymentConfig.getId().toString(),
                "/base/course/weChatPaymentCourseCallback", "APP", "");
        if (weixinpay.getCode() == 200) {
            String finalCode = code;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>()
                                    .eq("code", finalCode).eq("state", 1));
                            if (coursePackageOrder1.getPayStatus() == 2) {
                                break;
                            }
                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(finalCode, "");
                            if (resultUtil.getCode() == 200 && coursePackageOrder1.getPayStatus() == 1) {
                                /**
                                 * SUCCESS—支付成功,
                                 * REFUND—转入退款,
                                 * NOTPAY—未支付,
                                 * CLOSED—已关闭,
                                 * REVOKED—已撤销(刷卡支付),
                                 * USERPAYING--用户支付中,
                                 * PAYERROR--支付失败(其他原因,如银行返回失败)
                                 */
                                Map<String, String> data1 = resultUtil.getData();
                                String s = data1.get("trade_state");
                                String transaction_id = data1.get("transaction_id");
                                if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) {
                                    //有待支付的订单,这里不处理
//                                    coursePackageOrder1.setState(3);
//                                    coursePackageOrderService.updateById(coursePackageOrder1);
                                    break;
                                }
                                if ("SUCCESS".equals(s)) {
                                    coursePackageOrder1.setPayStatus(2);
                                    coursePackageOrder1.setOrderNumber(transaction_id);
                                    coursePackageOrder1.setAppUserId(null);
                                    coursePackageOrderService.updateById(coursePackageOrder1);
                                    //修改课时有效期
                                    addCoursePackageOrderStudent(coursePackageOrder1.getId(), coursePackagePaymentConfig);
                                    break;
                                }
                                if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        return weixinpay;
        }else{
            String s = storeClient.getmerchantNumberByOperatorId(store.getOperatorId());
            System.err.println("微信商户号"+s);
            if(!StringUtils.hasLength(s)){
                return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
            }
            return payMoneyUtil.weixinpayV3(s,"报名运动营",code,
                    "/base/course/weChatPaymentCourseCallback1",paymentPrice.toString(),"");
        }
//        return payMoneyUtil.weixinpayV3(WxV3PayConfig.smidVx,"报名运动营",code,
//                "/base/course/weChatPaymentCourseCallback1",Long.valueOf(paymentPrice.toString()),
//                coursePackagePaymentConfig.getId()+"");
    }
    private String smid = "2088330203191220";//平台支付宝商户号
    private String smidVx = "2088330203191220";//平台微信商户号
    /**
     * 课程支付宝支付
     *
@@ -1072,91 +1022,94 @@
            if (store.getOperatorId()==0){
                smid1 = smid;
            }else{
                smid1 = storeClient.getSMIDByOperatorId(store.getOperatorId());
                smid1 = storeClient.getmerchantNumberAliByOperatorId(store.getOperatorId());
            }
        }
        if (!StringUtils.hasLength(smid1)){
            return ResultUtil.error("运营商未配置支付宝商户号,获取支付失败!");
        }
        ResultUtil alipay = payMoneyUtil.alipay(smid1,"报名运动营", "报名运动营",
                coursePackagePaymentConfig.getId().toString(),
                code, paymentPrice.toString(), "/base/course/aliPaymentCourseCallback");
        if (alipay.getCode() == 200) {
            String finalCode = code;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>()
                                    .eq("code", finalCode).eq("state", 1));
                            if (coursePackageOrder1.getPayStatus() == 2) {
                                break;
                            }
                            AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(finalCode);
                            if (null != alipayTradeQueryResponse) {
                                /**
                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
                                 * TRADE_SUCCESS(交易支付成功)、
                                 * TRADE_FINISHED(交易结束,不可退款)
                                 */
                                String s = alipayTradeQueryResponse.getTradeStatus();
                                String tradeNo = alipayTradeQueryResponse.getTradeNo();
                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
                                    //有待支付的订单,这里不处理
//                                    coursePackageOrder1.setState(3);
//        if (alipay.getCode() == 200) {
//            String finalCode = code;
//            new Thread(new Runnable() {
//                @Override
//                public void run() {
//                    try {
//                        Thread.sleep(1000);
//                        int num = 1;
//                        int wait = 0;
//                        while (num <= 10) {
//                            int min = 5000;
//                            wait += (min * num);
//                            Thread.sleep(wait);
//                            CoursePackageOrder coursePackageOrder1 = coursePackageOrderService.getOne(new QueryWrapper<CoursePackageOrder>()
//                                    .eq("code", finalCode).eq("state", 1));
//                            if (coursePackageOrder1.getPayStatus() == 2) {
//                                break;
//                            }
//                            AlipayTradeQueryResponse alipayTradeQueryResponse = payMoneyUtil.queryALIOrder(finalCode);
//                            if (null != alipayTradeQueryResponse) {
//                                /**
//                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
//                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
//                                 * TRADE_SUCCESS(交易支付成功)、
//                                 * TRADE_FINISHED(交易结束,不可退款)
//                                 */
//                                String s = alipayTradeQueryResponse.getTradeStatus();
//
//                                String tradeNo = alipayTradeQueryResponse.getTradeNo();
//                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
//                                    //有待支付的订单,这里不处理
////                                    coursePackageOrder1.setState(3);
////                                    coursePackageOrderService.updateById(coursePackageOrder1);
//                                    break;
//                                }
//                                if ("TRADE_SUCCESS".equals(s)) {
//                                    coursePackageOrder1.setPayStatus(2);
//                                    coursePackageOrder1.setOrderNumber(tradeNo);
//                                    coursePackageOrder1.setAppUserId(null);
//                                    coursePackageOrderService.updateById(coursePackageOrder1);
                                    break;
                                }
                                if ("TRADE_SUCCESS".equals(s)) {
                                    coursePackageOrder1.setPayStatus(2);
                                    coursePackageOrder1.setOrderNumber(tradeNo);
                                    coursePackageOrder1.setAppUserId(null);
                                    coursePackageOrderService.updateById(coursePackageOrder1);
                                    addCoursePackageOrderStudent(coursePackageOrder1.getId(), coursePackagePaymentConfig);
                                    moneyOut(tradeNo,tradeNo,finalCode);
                                    // 根据课程id 查询这个课程属于哪个门店 属于哪个运营商
                                    Integer id = paymentCourseVo.getId();
                                    TCoursePackage byId = coursePackageService.getById(id);
                                    Integer storeId = byId.getStoreId();
                                    Store store = storeClient.queryStoreById(storeId);
                                    Integer operatorId = store.getOperatorId();
                                    if (operatorId==null){
                                        String smid ="2088330203191220";
                                            // 说明是平台的 不走分账
//                                            payMoneyUtil.confirm(smid,finalCode,tradeNo,paymentPrice.toString());
                                        payMoneyUtil.confirm1(smid,finalCode,tradeNo,paymentPrice.toString());
                                        break;
                                    }else if (operatorId==0){
                                        String smid ="2088330203191220";
                                        // 说明是平台的
                                        payMoneyUtil.confirm1(smid,finalCode,tradeNo,paymentPrice.toString());
                                        break;
                                    }else{
                                        // 当前课程不属于门店 查询这个课程属于哪个门店 属于哪个运营商
                                        String smidByOperatorId = storeClient.getSMIDByOperatorId(operatorId);
                                        payMoneyUtil.confirm(smidByOperatorId,finalCode,tradeNo,paymentPrice.toString());
                                        // 分账处理
                                        extracted(store, coursePackageOrder1, tradeNo);
                                        break;
                                    }
                                }
                                if ("WAIT_BUYER_PAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
//                                    addCoursePackageOrderStudent(coursePackageOrder1.getId(), coursePackagePaymentConfig);
//                                    moneyOut(tradeNo,tradeNo,finalCode);
//                                    // 根据课程id 查询这个课程属于哪个门店 属于哪个运营商
//                                    Integer id = paymentCourseVo.getId();
//                                    TCoursePackage byId = coursePackageService.getById(id);
//                                    Integer storeId = byId.getStoreId();
//                                    Store store = storeClient.queryStoreById(storeId);
//                                    Integer operatorId = store.getOperatorId();
//                                    if (operatorId==null){
//                                        String smid ="2088330203191220";
//                                            // 说明是平台的 不走分账
////                                            payMoneyUtil.confirm(smid,finalCode,tradeNo,paymentPrice.toString());
//                                        payMoneyUtil.confirm1(smid,finalCode,tradeNo,paymentPrice.toString());
//                                        break;
//                                    }else if (operatorId==0){
//                                        String smid ="2088330203191220";
//                                        // 说明是平台的
//                                        payMoneyUtil.confirm1(smid,finalCode,tradeNo,paymentPrice.toString());
//                                        break;
//                                    }else{
//                                        // 当前课程不属于门店 查询这个课程属于哪个门店 属于哪个运营商
//                                        String smidByOperatorId = storeClient.getSMIDByOperatorId(operatorId);
//                                        payMoneyUtil.confirm(smidByOperatorId,finalCode,tradeNo,paymentPrice.toString());
//                                        // 分账处理
//                                        extracted(store, coursePackageOrder1, tradeNo);
//                                        break;
//                                    }
//                                }
//                                if ("WAIT_BUYER_PAY".equals(s)) {
//                                    num++;
//                                }
//                            }
//                        }
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                }
//            }).start();
//        }
        return alipay;
    }
@@ -1355,30 +1308,6 @@
        TCoursePackage byId = coursePackageService.getById(coursePackageOrder1.getCoursePackageId());
        Store store = storeClient.queryStoreById(byId.getStoreId());
//        moneyOut(trade_no,trade_no,code);
        if (store.getOperatorId()!=null && store.getOperatorId()!=0){
            // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
            Thread.sleep(120000);
            // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
            String proportionByOperatorId = storeClient.getProportionByOperatorId(store.getOperatorId());
            String[] split = proportionByOperatorId.split(",");
            String s1 = split[0];
            if (!s1.equals("未设置")){
                BigDecimal bigDecimal = new BigDecimal(s1);
                // 分账比例
                BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                // 微信商户号
                String s2 = storeClient.getmerchantNumberByOperatorId(store.getOperatorId());
                String nonce_str = UUIDUtil.getRandomCode(16);
                ResultUtil fenzhang = payMoneyUtil.fenzhang(trade_no, coursePackageOrder1.getCashPayment().multiply(bigDecimal1), s2,nonce_str);
                if (!fenzhang.getCode().equals(200)){
                    System.err.println("分账失败 原因是:"+fenzhang.getData().toString());
                }else{
                    coursePackageOrder1.setFenzhangNo(fenzhang.getData().toString());
                    coursePackageOrder1.setFenzhangOrderNo(nonce_str);
                    coursePackageOrderService.updateById(coursePackageOrder1);
                }
            }
        }
        return ResultUtil.success();
    }
cloud-server-course/src/main/java/com/dsh/course/util/PayMoneyUtil.java
@@ -11,6 +11,10 @@
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.dsh.course.util.httpClinet.HttpClientUtil;
import com.dsh.course.util.wx.PartnerAppPrepay;
import com.dsh.course.util.wx.WXPayUtility;
import com.dsh.course.util.wx.WeChatV3SignUtil;
import com.dsh.course.util.wx.WxV3PayConfig;
import org.apache.commons.collections.map.HashedMap;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.dom4j.Document;
@@ -232,9 +236,6 @@
        SubMerchant subMerchant = new SubMerchant();
        subMerchant.setMerchantId(smid);
        model.setSubMerchant(subMerchant);
        ExtendParams extendParams = new ExtendParams();
        extendParams.setRoyaltyFreeze("true");// 冻结资金 用于后续分账处理
        model.setExtendParams(extendParams);
        request.setBizModel(model);
        request.setNotifyUrl(callbackPath + notifyUrl);
        try {
@@ -401,17 +402,9 @@
        map.put("appid", appid);
        map.put("mch_id", mchId);
        map.put("nonce_str", nonce_str);
        String temp = "";
        if (body.split("-").length>1){
            temp = body.split("-")[1];
            map.put("body", body.split("-")[0]);
        }else{
            map.put("body", body);
        }
        if (StringUtils.hasLength(temp) && temp.equals("1")){
            // 添加分账标识
            map.put("profit_sharing", "Y");
        }
        map.put("attach", attach);//存储订单id
        map.put("out_trade_no", out_trade_no);//存储的订单code
        map.put("total_fee", i);
@@ -501,6 +494,64 @@
            return ResultUtil.error(map1.get("return_msg"), new JSONObject());
        }
    }
    public ResultUtil weixinpayV3(String subMchid,String description, String outTradeNo, String notifyUrl, String totalFee, String attach) throws Exception {
        int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue();
        // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
        PartnerAppPrepay client = new PartnerAppPrepay(
//                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//                "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//                "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
                "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
                "/usr/playpai/server/wxV3/pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
        );
        PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest();
        request.spAppid = appid;// appid
        request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号
        request.subMchid = subMchid;// 子商户号
        request.description = description;// 描述
        request.outTradeNo = outTradeNo;// 订单号
        request.notifyUrl =callbackPath+ notifyUrl;// 回调地址
        request.amount = new PartnerAppPrepay.CommonAmountInfo();
        request.amount.total = (long) i;// 金额 单位分
        request.amount.currency = "CNY";
        request.attach = attach;
        String prepayId ="";
        Map<String, Object> map3 = new HashMap<>();
        try {
            PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request);
            // TODO: 请求成功,继续业务逻辑
            System.err.println("微信申请成功,预支付ID: " + response.prepayId);
            prepayId = response.prepayId;
        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败,根据状态码执行不同的逻辑
            e.printStackTrace();
        }
        map3.put("appid", appid);
        map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        String nonce_str = UUIDUtil.getRandomCode(16);
        map3.put("noncestr", nonce_str);
        map3.put("prepayid", prepayId);
        // 构造待签名字符串
        String message = WeChatV3SignUtil.buildSignMessage(map3);
        // 私钥路径(pem 文件)
        String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
//      String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem";
        // 生成签名
        String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath);
        map3.put("sign", sign);
        map3.put("package", "Sign=WXPay");
        map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号
        System.err.println(map3);
        return ResultUtil.success(map3);
    }
    /**
cloud-server-course/src/main/java/com/dsh/course/util/wx/PartnerAppPrepay.java
New file
@@ -0,0 +1,237 @@
package com.dsh.course.util.wx;
import com.google.gson.annotations.SerializedName;
import okhttp3.*;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;
/**
 * App下单
 */
public class PartnerAppPrepay {
  private static String HOST = "https://api.mch.weixin.qq.com";
  private static String METHOD = "POST";
  private static String PATH = "/v3/pay/partner/transactions/app";
//  public static void main(String[] args) {
//    // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
//    PartnerAppPrepay client = new PartnerAppPrepay(
//        "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//        "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//        "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//        "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//        "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
//    );
//
//    PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest();
//    request.spAppid = "wx41d32f362ba0f911";
//    request.spMchid = WxV3PayConfig.Mch_ID;
//    request.subMchid = "1720719391";
//    request.description = "Image形象店-深圳腾大-QQ公仔";
//    request.outTradeNo = "12177525012014070332333680182";
//    request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php";
////    request.goodsTag = "WXG";
////    request.settleInfo = new PartnerSettleInfo();
////    request.settleInfo.profitSharing = false;
//    request.amount = new CommonAmountInfo();
//    request.amount.total = 100L;
//    request.amount.currency = "CNY";
////    request.detail = new CouponInfo();
////    request.detail.costPrice = 1L;
////    request.detail.invoiceId = "wx123";
////    request.detail.goodsDetail = new ArrayList<>();
////    {
////      GoodsDetail item0 = new GoodsDetail();
////      item0.merchantGoodsId = "1246464644";
////      item0.wechatpayGoodsId = "1001";
////      item0.goodsName = "iPhone6s 16G";
////      item0.quantity = 1L;
////      item0.unitPrice = 528800L;
////      request.detail.goodsDetail.add(item0);
////    };
////    request.sceneInfo = new CommonSceneInfo();
////    request.sceneInfo.payerClientIp = "14.23.150.211";
////    request.sceneInfo.deviceId = "013467007045764";
////    request.sceneInfo.storeInfo = new StoreInfo();
////    request.sceneInfo.storeInfo.id = "0001";
////    request.sceneInfo.storeInfo.name = "腾讯大厦分店";
////    request.sceneInfo.storeInfo.areaCode = "440305";
////    request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
//    try {
//      PartnerAPIv3AppPrepayResponse response = client.run(request);
//
//      // TODO: 请求成功,继续业务逻辑
//      System.err.println("微信申请成功,预支付ID: " + response.prepayId);
//    } catch (WXPayUtility.ApiException e) {
//      // TODO: 请求失败,根据状态码执行不同的逻辑
//      e.printStackTrace();
//    }
//  }
  public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) {
    String uri = PATH;
    String reqBody = WXPayUtility.toJson(request);
    Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
    reqBuilder.addHeader("Accept", "application/json");
    reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
    reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
    reqBuilder.addHeader("Content-Type", "application/json");
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
    reqBuilder.method(METHOD, requestBody);
    Request httpRequest = reqBuilder.build();
    // 发送HTTP请求
    OkHttpClient client = new OkHttpClient.Builder().build();
    try (Response httpResponse = client.newCall(httpRequest).execute()) {
      String respBody = WXPayUtility.extractBody(httpResponse);
      if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
        // 2XX 成功,验证应答签名
        WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                                      httpResponse.headers(), respBody);
        // 从HTTP应答报文构建返回数据
        return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class);
      } else {
        throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
      }
    } catch (IOException e) {
      throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
    }
  }
  private final String mchid;
  private final String certificateSerialNo;
  private final PrivateKey privateKey;
  private final String wechatPayPublicKeyId;
  private final PublicKey wechatPayPublicKey;
  public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
    this.mchid = mchid;
    this.certificateSerialNo = certificateSerialNo;
    this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
    this.wechatPayPublicKeyId = wechatPayPublicKeyId;
    this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
  }
  public static class PartnerSettleInfo {
    @SerializedName("profit_sharing")
    public Boolean profitSharing;
  }
  public static class CommonAmountInfo {
    @SerializedName("total")
    public Long total;
    @SerializedName("currency")
    public String currency;
  }
  public static class CommonSceneInfo {
    @SerializedName("payer_client_ip")
    public String payerClientIp;
    @SerializedName("device_id")
    public String deviceId;
    @SerializedName("store_info")
    public StoreInfo storeInfo;
  }
  public static class GoodsDetail {
    @SerializedName("merchant_goods_id")
    public String merchantGoodsId;
    @SerializedName("wechatpay_goods_id")
    public String wechatpayGoodsId;
    @SerializedName("goods_name")
    public String goodsName;
    @SerializedName("quantity")
    public Long quantity;
    @SerializedName("unit_price")
    public Long unitPrice;
  }
  public static class PartnerAPIv3AppPrepayResponse {
    @SerializedName("prepay_id")
    public String prepayId;
  }
  public static class PartnerAPIv3CommonPrepayRequest {
    @SerializedName("sp_appid")
    public String spAppid;
    @SerializedName("sp_mchid")
    public String spMchid;
    @SerializedName("sub_appid")
    public String subAppid;
    @SerializedName("sub_mchid")
    public String subMchid;
    @SerializedName("description")
    public String description;
    @SerializedName("out_trade_no")
    public String outTradeNo;
    @SerializedName("time_expire")
    public String timeExpire;
    @SerializedName("attach")
    public String attach;
    @SerializedName("notify_url")
    public String notifyUrl;
    @SerializedName("goods_tag")
    public String goodsTag;
    @SerializedName("settle_info")
    public PartnerSettleInfo settleInfo;
    @SerializedName("support_fapiao")
    public Boolean supportFapiao;
    @SerializedName("amount")
    public CommonAmountInfo amount;
    @SerializedName("detail")
    public CouponInfo detail;
    @SerializedName("scene_info")
    public CommonSceneInfo sceneInfo;
  }
  public static class CouponInfo {
    @SerializedName("cost_price")
    public Long costPrice;
    @SerializedName("invoice_id")
    public String invoiceId;
    @SerializedName("goods_detail")
    public List<GoodsDetail> goodsDetail;
  }
  public static class StoreInfo {
    @SerializedName("id")
    public String id;
    @SerializedName("name")
    public String name;
    @SerializedName("area_code")
    public String areaCode;
    @SerializedName("address")
    public String address;
  }
}
cloud-server-course/src/main/java/com/dsh/course/util/wx/WXPayUtility.java
New file
@@ -0,0 +1,441 @@
package com.dsh.course.util.wx;
import com.google.gson.*;
import com.google.gson.annotations.Expose;
import com.wechat.pay.java.core.util.GsonUtil;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
public class WXPayUtility {
  private static final Gson gson = new GsonBuilder()
      .disableHtmlEscaping()
      .addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.serialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .addDeserializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.deserialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .create();
  private static final char[] SYMBOLS =
      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
  private static final SecureRandom random = new SecureRandom();
  /**
   * 将 Object 转换为 JSON 字符串
   */
  public static String toJson(Object object) {
    return gson.toJson(object);
  }
  /**
   * 将 JSON 字符串解析为特定类型的实例
   */
  public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    return gson.fromJson(json, classOfT);
  }
  /**
   * 从公私钥文件路径中读取文件内容
   *
   * @param keyPath 文件路径
   * @return 文件内容
   */
  private static String readKeyStringFromPath(String keyPath) {
    try {
      return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }
  /**
   * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
   *
   * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
          .replace("-----END PRIVATE KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePrivate(
          new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的私钥文件中加载私钥
   *
   * @param keyPath 私钥文件路径
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
    return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
   *
   * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
          .replace("-----END PUBLIC KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePublic(
          new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的公钥文件中加载公钥
   *
   * @param keyPath 公钥文件路径
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromPath(String keyPath) {
    return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
   */
  public static String createNonce(int length) {
    char[] buf = new char[length];
    for (int i = 0; i < length; ++i) {
      buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
    }
    return new String(buf);
  }
  /**
   * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
   *
   * @param publicKey 加密用公钥对象
   * @param plaintext 待加密明文
   * @return 加密后密文
   */
  public static String encrypt(PublicKey publicKey, String plaintext) {
    final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
    try {
      Cipher cipher = Cipher.getInstance(transformation);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
      throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
      throw new IllegalArgumentException("Plaintext is too long", e);
    }
  }
  /**
   * 使用私钥按照指定算法进行签名
   *
   * @param message 待签名串
   * @param algorithm 签名算法,如 SHA256withRSA
   * @param privateKey 签名用私钥对象
   * @return 签名结果
   */
  public static String sign(String message, String algorithm, PrivateKey privateKey) {
    byte[] sign;
    try {
      Signature signature = Signature.getInstance(algorithm);
      signature.initSign(privateKey);
      signature.update(message.getBytes(StandardCharsets.UTF_8));
      sign = signature.sign();
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
    } catch (SignatureException e) {
      throw new RuntimeException("An error occurred during the sign process.", e);
    }
    return Base64.getEncoder().encodeToString(sign);
  }
  /**
   * 使用公钥按照特定算法验证签名
   *
   * @param message 待签名串
   * @param signature 待验证的签名内容
   * @param algorithm 签名算法,如:SHA256withRSA
   * @param publicKey 验签用公钥对象
   * @return 签名验证是否通过
   */
  public static boolean verify(String message, String signature, String algorithm,
                               PublicKey publicKey) {
    try {
      Signature sign = Signature.getInstance(algorithm);
      sign.initVerify(publicKey);
      sign.update(message.getBytes(StandardCharsets.UTF_8));
      return sign.verify(Base64.getDecoder().decode(signature));
    } catch (SignatureException e) {
      return false;
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("verify uses an illegal publickey.", e);
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
    }
  }
  /**
   * 根据微信支付APIv3请求签名规则构造 Authorization 签名
   *
   * @param mchid 商户号
   * @param certificateSerialNo 商户API证书序列号
   * @param privateKey 商户API证书私钥
   * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
   * @param uri 请求接口的URL
   * @param body 请求接口的Body
   * @return 构造好的微信支付APIv3 Authorization 头
   */
  public static String buildAuthorization(String mchid, String certificateSerialNo,
                                          PrivateKey privateKey,
                                          String method, String uri, String body) {
    String nonce = createNonce(32);
    long timestamp = Instant.now().getEpochSecond();
    String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
        body == null ? "" : body);
    String signature = sign(message, "SHA256withRSA", privateKey);
    return String.format(
        "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
            "timestamp=\"%d\",serial_no=\"%s\"",
        mchid, nonce, signature, timestamp, certificateSerialNo);
  }
  /**
   * 对参数进行 URL 编码
   *
   * @param content 参数内容
   * @return 编码后的内容
   */
  public static String urlEncode(String content) {
    try {
      return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }
  /**
   * 对参数Map进行 URL 编码,生成 QueryString
   *
   * @param params Query参数Map
   * @return QueryString
   */
  public static String urlEncode(Map<String, Object> params) {
    if (params == null || params.isEmpty()) {
      return "";
    }
    int index = 0;
    StringBuilder result = new StringBuilder();
    for (Map.Entry<String, Object> entry : params.entrySet()) {
      result.append(entry.getKey())
          .append("=")
          .append(urlEncode(entry.getValue().toString()));
      index++;
      if (index < params.size()) {
        result.append("&");
      }
    }
    return result.toString();
  }
  /**
   * 从应答中提取 Body
   *
   * @param response HTTP 请求应答对象
   * @return 应答中的Body内容,Body为空时返回空字符串
   */
  public static String extractBody(Response response) {
    if (response.body() == null) {
      return "";
    }
    try {
      BufferedSource source = response.body().source();
      return source.readUtf8();
    } catch (IOException e) {
      throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e);
    }
  }
  /**
   * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
   *
   * @param wechatpayPublicKeyId 微信支付公钥ID
   * @param wechatpayPublicKey 微信支付公钥对象
   * @param headers 微信支付应答 Header 列表
   * @param body 微信支付应答 Body
   */
  public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                      Headers headers,
                                      String body) {
    String timestamp = headers.get("Wechatpay-Timestamp");
    try {
      Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
      // 拒绝过期请求
      if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
        throw new IllegalArgumentException(
            String.format("Validate http response,timestamp[%s] of httpResponse is expires, "
                    + "request-id[%s]",
                timestamp, headers.get("Request-ID")));
      }
    } catch (DateTimeException | NumberFormatException e) {
      throw new IllegalArgumentException(
          String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " +
                  "request-id[%s]", timestamp,
              headers.get("Request-ID")));
    }
    String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
        body == null ? "" : body);
    String serialNumber = headers.get("Wechatpay-Serial");
    if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
      throw new IllegalArgumentException(
          String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId,
              serialNumber));
    }
    String signature = headers.get("Wechatpay-Signature");
    boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
    if (!success) {
      throw new IllegalArgumentException(
          String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                  + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
              headers.get("Request-ID"), headers, body));
    }
  }
  /**
   * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
   */
  public static class ApiException extends RuntimeException {
    private static final long serialVersionUID = 2261086748874802175L;
    private final int statusCode;
    private final String body;
    private final Headers headers;
    private final String errorCode;
    private final String errorMessage;
    public ApiException(int statusCode, String body, Headers headers) {
      super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers));
      this.statusCode = statusCode;
      this.body = body;
      this.headers = headers;
      if (body != null && !body.isEmpty()) {
        JsonElement code;
        JsonElement message;
        try {
          JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class);
          code = jsonObject.get("code");
          message = jsonObject.get("message");
        } catch (JsonSyntaxException ignored) {
          code = null;
          message = null;
        }
        this.errorCode = code == null ? null : code.getAsString();
        this.errorMessage = message == null ? null : message.getAsString();
      } else {
        this.errorCode = null;
        this.errorMessage = null;
      }
    }
    /**
     * 获取 HTTP 应答状态码
     */
    public int getStatusCode() {
      return statusCode;
    }
    /**
     * 获取 HTTP 应答包体内容
     */
    public String getBody() {
      return body;
    }
    /**
     * 获取 HTTP 应答 Header
     */
    public Headers getHeaders() {
      return headers;
    }
    /**
     * 获取 错误码 (错误应答中的 code 字段)
     */
    public String getErrorCode() {
      return errorCode;
    }
    /**
     * 获取 错误消息 (错误应答中的 message 字段)
     */
    public String getErrorMessage() {
      return errorMessage;
    }
  }
}
cloud-server-course/src/main/java/com/dsh/course/util/wx/WeChatSignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.course.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
public class WeChatSignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 使用商户私钥对字符串进行 SHA256withRSA 签名
     *
     * @param content      待签名字符串
     * @param privateKeyPath 商户私钥文件路径(pem 格式)
     * @return 签名结果(Base64 编码)
     */
    public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception {
        String privateKeyPEM = readPemFile(privateKeyPath);
        String privateKeyContent = privateKeyPEM
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");
        byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }
    private static String readPemFile(String filePath) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
        }
        return sb.toString();
    }
    /**
     * 按照微信 V3 规范生成待签名字符串
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            Object value = map.get(key);
            if (value != null && !value.toString().isEmpty()) {
                sb.append(key).append("=").append(value).append("\n");
            }
        }
        return sb.toString();
    }
}
cloud-server-course/src/main/java/com/dsh/course/util/wx/WeChatV3SignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.course.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
public class WeChatV3SignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 从 PEM 文件中读取私钥内容
     */
    public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith("-----") && !line.endsWith("-----")) {
                    sb.append(line);
                }
            }
        }
        byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
        KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
        return kf.generatePrivate(keySpec);
    }
    /**
     * 构造待签名字符串(按 key 字典序排序)
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
//        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        sb.append(map.get("appid")).append("\n");
        sb.append(map.get("timestamp")).append("\n");
        sb.append(map.get("noncestr")).append("\n");
        sb.append(map.get("prepayid")).append("\n");
        return sb.toString();
    }
    /**
     * 使用商户私钥生成签名(SHA256withRSA + Base64)
     */
    public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception {
        PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signedBytes);
    }
}
cloud-server-course/src/main/java/com/dsh/course/util/wx/WxV3PayConfig.java
New file
@@ -0,0 +1,66 @@
package com.dsh.course.util.wx;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class WxV3PayConfig {
    // 服务商AppId
    private String appIdValue = "wx41d32f362ba0f911";
    public static String APP_ID= "wx41d32f362ba0f911";
    // 服务商商户号
    private String mchIdValue= "1681873607";
    public static String Mch_ID= "1681873607";
    // 平台收款商户号 todo 待申请
    public static String smidVx= "2088330203191220";
    private  String smidVxValue= "2088330203191220";
    // 服务商商户私钥
    private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq";
    public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq";
    // 证书序列号
    private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A";
    public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A";
    private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量
    @PostConstruct
    public void init() {
        APP_ID = this.appIdValue;
        APP_ID = this.appIdValue;
        smidVx = this.smidVxValue;
        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/guns/modular/system/controller/code/OperatorController.java
@@ -128,15 +128,10 @@
    }
    @RequestMapping("/bindWx/{id}")
    public String bindWx(Model model,@PathVariable("id") Integer id) {
        TOperator operator = operatorService.getById(id);
        model.addAttribute("id",id);
        model.addAttribute("merchantNumber",operatorService.getById(id).getMerchantNumber());
        model.addAttribute("name",operatorService.getById(id).getMerchantName());
        OperatorUser one = operatorUserService.lambdaQuery()
                .eq(OperatorUser::getOperatorId, id).one();
        if (one!=null){
            model.addAttribute("wechatProportion",one.getWechatProportion());
        }
        model.addAttribute("merchantNumber",operator.getMerchantNumber());
        model.addAttribute("merchantNumberAli",operator.getMerchantNumberAli());
        return PREFIX + "Operator_wx.html";
    }
    /**
@@ -197,21 +192,11 @@
     */
    @RequestMapping(value = "/merchantNumberWx")
    @ResponseBody
    public Object listAll(Integer id, String merchantNumber,String name,String wechatProportion) throws Exception {
    public Object listAll(Integer id, String merchantNumber,String merchantNumberAli) throws Exception {
        TOperator byId = operatorService.getById(id);
        byId.setMerchantNumber(merchantNumber);
        byId.setMerchantName(name);
        byId.setMerchantNumberAli(merchantNumberAli);
        operatorService.updateById(byId);
        OperatorUser one = operatorUserService.lambdaQuery()
                .eq(OperatorUser::getOperatorId, id).one();
        if (one!=null){
            one.setWechatProportion(wechatProportion);
            operatorUserService.updateById(one);
        }
        ResultUtil resultUtil = payMoneyUtil.addReceiver(merchantNumber, name);
        if (resultUtil.getCode() == 500){
            return ResultUtil.error(resultUtil.getMsg());
        }
        return SUCCESS_TIP;
    }
cloud-server-management/src/main/java/com/dsh/guns/modular/system/model/TOperator.java
@@ -36,6 +36,11 @@
    @TableField("merchantNumber")
    private String merchantNumber;
    /**
     * 支付宝商户号
     */
    @TableField("merchantNumberAli")
    private String merchantNumberAli;
    /**
     * 微信商户全称
     */
    @TableField("merchantName")
cloud-server-management/src/main/webapp/WEB-INF/view/system/operator/Operator.html
@@ -40,9 +40,7 @@
                            <#button name="冻结" icon="fa-remove" clickFun="TSite.offShelf()" space="true"/>
                            <#button name="解冻" icon="fa-check" clickFun="TSite.onShelf()" space="true"/>
                            <#button name="重置密码" icon="fa-search" clickFun="TSite.reload()" space="true"/>
                            @if(shiro.hasPermission("/operator/merchantNumberWx")){
                            <#button name="绑定微信商户号" icon="fa-search" clickFun="TSite.bindWx()" space="true"/>
                            @}
                            <#button name="绑定商户号" icon="fa-search" clickFun="TSite.bindWx()" space="true"/>
                        </div>
                        <#table id="TSiteTable"/>
                    </div>
cloud-server-management/src/main/webapp/WEB-INF/view/system/operator/Operator_wx.html
@@ -54,17 +54,11 @@
                        <div class="col-sm-9">
                            <input style="width: 300px" class="form-control" id="merchantNumber" value="${merchantNumber}"  placeholder="请输入" type="number">
                        </div>
                        <label class="col-sm-3 control-label">*微信商户全称:</label>
                        <label class="col-sm-3 control-label">*支付宝商户号:</label>
                        <div class="col-sm-9">
                            <input style="width: 300px" class="form-control" id="name" value="${name}"  placeholder="请输入" type="text">
                            <input style="width: 300px" class="form-control" id="merchantNumberAli" value="${merchantNumberAli}"  placeholder="请输入" type="number">
                        </div>
                        <div class="form-group" >
                            <label class="col-sm-3 control-label">*微信分账比例(%):</label>
                            <div class="col-sm-9">
                                <input style="width: 300px" class="form-control" id="wechatProportion" value="${wechatProportion}"  placeholder="请输入" type="number">
                            </div>
                        </div>
                    </div>
                </div>
            </div>
cloud-server-management/src/main/webapp/static/modular/system/operator/operator.js
@@ -30,19 +30,6 @@
                return {1:"全国",2:"指定区域"}[data]
            }
        },
        {title: '商户绑定状态', field: 'status', visible: true, align: 'center', valign: 'middle',
            formatter: function (data,row) {
                var btn = "";
                if(data==1) {
                    var str = '<button class="btn btn-outline btn-primary" onclick="TSite.Allocationratio('+row.id+')" >去绑定</button>'
                    btn = ['<p class="toolTip" style="overflow:hidden;white-space:nowrap;text-overflow:ellipsis;" title="" onfocus="TUser.tooltip()">' + str + '</p>']
                }else{
                    var str = '<h3>已绑定</h3>'
                    btn = [str]
                }
                return btn;
            }
        },
        {title: '状态', field: 'state', visible: true, align: 'center', valign: 'middle',
            formatter:function (data) {
                return {1:"正常",2:"冻结",3:"删除"}[data]
cloud-server-management/src/main/webapp/static/modular/system/operator/operator_add.js
@@ -127,18 +127,7 @@
}
TSite.merchantNumber = function(){
    if($("#merchantNumber").val() == "" || $("#merchantNumber").val() == null){
        Feng.error("请填写微信商户号")
        return;
    }
    if($("#name").val() == "" || $("#name").val() == null){
        Feng.error("请填写微信商户全称")
        return;
    }
    if($("#wechatProportion").val() == "" || $("#wechatProportion").val() == null){
        Feng.error("请填写微信分账比例")
        return;
    }
    //提交信息
    var ajax = new $ax(Feng.ctxPath + "/operator/merchantNumberWx", function(data){
        console.log(data)
@@ -154,8 +143,7 @@
    });
    ajax.set("id",$("#id").val());
    ajax.set("merchantNumber",$("#merchantNumber").val());
    ajax.set("name",$("#name").val());
    ajax.set("wechatProportion",$("#wechatProportion").val());
    ajax.set("merchantNumberAli",$("#merchantNumberAli").val());
    ajax.start();
};
TSite.oneChange = function (e) {
cloud-server-other/pom.xml
@@ -14,6 +14,32 @@
    <name>其他</name>
    <description>其他</description>
    <dependencies>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java-core</artifactId>
            <version>0.2.12</version>
            <scope>compile</scope>
        </dependency>
        <!-- 微信支付V3 目前新版本-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.9</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        <!-- OkHttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
        <!--日志处理-->
        <dependency>
            <groupId>cn.mb.cloud</groupId>
cloud-server-other/src/main/java/com/dsh/other/controller/GameController.java
@@ -17,18 +17,23 @@
import com.dsh.other.service.*;
import com.dsh.other.util.*;
import com.dsh.other.util.httpClinet.HttpResult;
import com.dsh.other.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@@ -176,10 +181,11 @@
    private String smidVx = "2088330203191220";//平台微信商户号
    @Autowired
    private IOperatorUserService operatorUserService;
    @ResponseBody
    @PostMapping("/api/game/payGame")
    @ApiOperation(value = "支付游戏", tags = {"用户—游戏"})
@@ -198,13 +204,13 @@
            if (null == uid) {
                return ResultUtil.tokenErr();
            }
            Site byId = siteService.getById(spaceId);
            Store byId1 = storeService.getById(byId.getStoreId());
            Site site = siteService.getById(spaceId);
            Store store = storeService.getById(site.getStoreId());
            // 是否分账 0否1是
            int isFenZhang= 1;
            String merchantNumber = "";
            if (byId1.getOperatorId()==null || byId1.getOperatorId()==0){
            if (store.getOperatorId()==null || store.getOperatorId()==0){
                // 平台
                isFenZhang = 0;
            }
@@ -232,67 +238,27 @@
                tGameRecord.setMoney(config.getCash());
                gameRecordService.updateById(tGameRecord);
                String params = uid + "_" + gameId + "_" + spaceId + "_" + sutuId+"_"+code+"_"+configId + "_" + gameType;
                ResultUtil weixinpay = payMoneyUtil.weixinpay("游戏支付-"+isFenZhang, params, code, config.getCash().toString(),
                        "/base/game/wechatPaymentGameCallback", "APP", "");
//                ResultUtil weixinpay = payMoneyUtil.weixinpay("游戏支付-"+isFenZhang, params, code, config.getCash().toString(),
//                        "/base/game/wechatPaymentGameCallback", "APP", "");
                System.err.println("启动游戏支付");
                if (weixinpay.getCode() == 200) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                int num = 1;
                                int wait = 0;
                                while (num <= 15) {
                                    int min = 5000;
                                    wait += (min * num);
                                    Thread.sleep(wait);
                                    List<TGameRecord> list = gameRecordService
                                            .list(new QueryWrapper<TGameRecord>().eq("number", code).eq("payType", 1));
                                    TGameRecord one = list.get(0);
                                    if (one.getStatus() == 1) {
                                        break;
                                    }
                                    ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(code, "");
                                    if (resultUtil.getCode() == 200 && one.getStatus() == 0) {
                                        /**
                                         * SUCCESS—支付成功,
                                         * REFUND—转入退款,
                                         * NOTPAY—未支付,
                                         * CLOSED—已关闭,
                                         * REVOKED—已撤销(刷卡支付),
                                         * USERPAYING--用户支付中,
                                         * PAYERROR--支付失败(其他原因,如银行返回失败)
                                         */
                                        Map<String, String> data1 = resultUtil.getData();
                                        String s = data1.get("trade_state");
                                        String transaction_id = data1.get("transaction_id");
                                        if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) {
                                            break;
                                        }
                                        if ("SUCCESS".equals(s)) {
                                            for (TGameRecord gameRecord : list) {
                                                gameRecord.setStatus(1);
                                                gameRecord.setOrderNo(transaction_id);
                                            }
                                            gameRecordService.updateBatchById(list);
                                            Integer integer = startGame(uid, gameType, gameId, spaceId, sutuId);
                                            break;
                                        }
                                        if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) {
                                            num++;
                                        }
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                }
                if (store.getOperatorId()==null||store.getOperatorId()==0){
                    // 平台
                                    ResultUtil weixinpay = payMoneyUtil.weixinpay("游戏支付", params, code, config.getCash().toString(),
                        "/base/game/wechatPaymentGameCallback", "APP", "");
                return weixinpay;
                }else{
                    String smidVx= operatorUserService.getmerchantNumberByOperatorId(store.getOperatorId());
                    if (!StringUtils.hasLength(smidVx)){
                        return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
                    }
                    return payMoneyUtil.weixinpayV3(smidVx,"游戏支付"
                            ,code,"/base/huimin/callBack/wechatPaymentGameCallback1",
                          config.getCash().toString(),params);
                }
            } else if (type == 2) {
                tGameRecord.setMoney(config.getCash());
                gameRecordService.updateById(tGameRecord);
@@ -305,81 +271,81 @@
                    // 是平台的
                    smid1 = smid;
                }else{
                    String smidByOperatorId = siteService.getSMIDByOperatorId(operationId);
                    String smidByOperatorId = siteService.getmerchantNumberAliByOperatorId(operationId);
                    smid1 = smidByOperatorId;
                }
                ResultUtil alipay = payMoneyUtil.alipay(smid1,"游戏支付", "游戏支付", params, code, config.getCash().toString(),
                        "/base/game/aliPaymentGameCallback");
                if (alipay.getCode() == 200) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                int num = 1;
                                int wait = 0;
                                while (num <= 10) {
                                    int min = 5000;
                                    wait += (min * num);
                                    Thread.sleep(wait);
                                    List<TGameRecord> list = gameRecordService.list(new QueryWrapper<TGameRecord>().eq("number", code).eq("payType", 2));
                                    TGameRecord one = list.get(0);
                                    if (one.getStatus() == 1) {
                                        break;
                                    }
                                    AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code);
                                    if (resultUtil.getCode().equals("10000") && one.getStatus() == 0) {
                                        /**
                                         * WAIT_BUYER_PAY(交易创建,等待买家付款)、
                                         * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
                                         * TRADE_SUCCESS(交易支付成功)、
                                         * TRADE_FINISHED(交易结束,不可退款)
                                         */
                                        String tradeNo = resultUtil.getTradeNo();
                                        String s = resultUtil.getTradeStatus();
                                        if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
                                            break;
                                        }
                                        if ("TRADE_SUCCESS".equals(s)) {
                                            for (TGameRecord gameRecord : list) {
                                                gameRecord.setStatus(1);
                                                gameRecord.setOrderNo(tradeNo);
                                                CourseCounsum courseCounsum = new CourseCounsum();
                                                courseCounsum.setChangeType(3);
                                                courseCounsum.setInsertTime(new Date());
                                                courseCounsum.setReason("智慧球场;" + config.getCash());
                                                courseCounsum.setAppUserId(uid);
                                                courseRecordClient.save(courseCounsum);
                                            }
                                            gameRecordService.updateBatchById(list);
                                            Integer integer = startGame(uid, gameType, gameId, spaceId, sutuId);
                                            // 判断这个课包属于哪个门店 属于哪个运营商 根据运营商 id 获取对应的商户号
                                            TGame game = gameService.getById(gameId);
                                            String smid2="";
                                            Integer operationId = game.getOperationId();
                                            if (operationId==0){
                                                // 是平台的
                                                smid2 = smid;
                                            }else{
                                                String smidByOperatorId = siteService.getSMIDByOperatorId(operationId);
                                                smid2 = smidByOperatorId;
                                            }
                                            payMoneyUtil.confirm(smid2,code,tradeNo,config.getCash().toString());
                                            break;
                                        }
                                        if ("WAIT_BUYER_PAY".equals(s)) {
                                            num++;
                                        }
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                }
//                if (alipay.getCode() == 200) {
//                    new Thread(new Runnable() {
//                        @Override
//                        public void run() {
//                            try {
//                                int num = 1;
//                                int wait = 0;
//                                while (num <= 10) {
//                                    int min = 5000;
//                                    wait += (min * num);
//                                    Thread.sleep(wait);
//                                    List<TGameRecord> list = gameRecordService.list(new QueryWrapper<TGameRecord>().eq("number", code).eq("payType", 2));
//                                    TGameRecord one = list.get(0);
//                                    if (one.getStatus() == 1) {
//                                        break;
//                                    }
//                                    AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code);
//                                    if (resultUtil.getCode().equals("10000") && one.getStatus() == 0) {
//                                        /**
//                                         * WAIT_BUYER_PAY(交易创建,等待买家付款)、
//                                         * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
//                                         * TRADE_SUCCESS(交易支付成功)、
//                                         * TRADE_FINISHED(交易结束,不可退款)
//                                         */
//                                        String tradeNo = resultUtil.getTradeNo();
//                                        String s = resultUtil.getTradeStatus();
//                                        if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
//                                            break;
//                                        }
//                                        if ("TRADE_SUCCESS".equals(s)) {
//                                            for (TGameRecord gameRecord : list) {
//                                                gameRecord.setStatus(1);
//                                                gameRecord.setOrderNo(tradeNo);
//
//                                                CourseCounsum courseCounsum = new CourseCounsum();
//                                                courseCounsum.setChangeType(3);
//                                                courseCounsum.setInsertTime(new Date());
//                                                courseCounsum.setReason("智慧球场;" + config.getCash());
//                                                courseCounsum.setAppUserId(uid);
//                                                courseRecordClient.save(courseCounsum);
//
//                                            }
//                                            gameRecordService.updateBatchById(list);
//                                            Integer integer = startGame(uid, gameType, gameId, spaceId, sutuId);
//                                            // 判断这个课包属于哪个门店 属于哪个运营商 根据运营商 id 获取对应的商户号
//                                            TGame game = gameService.getById(gameId);
//                                            String smid2="";
//                                            Integer operationId = game.getOperationId();
//                                            if (operationId==0){
//                                                // 是平台的
//                                                smid2 = smid;
//                                            }else{
//                                                String smidByOperatorId = siteService.getSMIDByOperatorId(operationId);
//                                                smid2 = smidByOperatorId;
//                                            }
//                                            payMoneyUtil.confirm(smid2,code,tradeNo,config.getCash().toString());
//                                            break;
//                                        }
//                                        if ("WAIT_BUYER_PAY".equals(s)) {
//                                            num++;
//                                        }
//                                    }
//                                }
//                            } catch (Exception e) {
//                                e.printStackTrace();
//                            }
//                        }
//                    }).start();
//                }
                return alipay;
            } else if (type == 3) {
                tGameRecord.setMoney(config.getPlayCoin());
@@ -515,49 +481,49 @@
            Integer integer = startGame(Integer.valueOf(s[0]), Integer.valueOf(s[6]), Integer.valueOf(s[1]), Integer.valueOf(s[2]), Integer.valueOf(s[3]));
            TGameRecord one = gameRecordService.getOne(new QueryWrapper<TGameRecord>().eq("number", out_trade_no).eq("payType", 1));
            System.err.println("游戏支付记录"+one);
            if (one!=null){
                Integer gameId = one.getGameId();
                Site byId1 = siteService.getById(one.getSiteId());
                Store byId = storeService.getById(byId1.getStoreId());
                if (byId!=null){
                    if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){
                        // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
                        Thread.sleep(120000);
                        // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
                        OperatorUser operatorId = operatorUserService.getOne(
                                new QueryWrapper<OperatorUser>().eq("operatorId",byId.getOperatorId())
                        );
                        if (operatorId.getWechatProportion() == null){
                            operatorId.setWechatProportion("0");
                        }
                        if (operatorId.getAlipayProportion() == null){
                            operatorId.setAlipayProportion("0");
                        }
                        String proportion=  operatorId.getWechatProportion()+","+operatorId.getAlipayProportion();
                        String[] split = proportion.split(",");
                        String s1 = split[0];
                        if (!s1.equals("未设置")){
                            BigDecimal bigDecimal = new BigDecimal(s1);
                            // 分账比例
                            BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                            // 微信商户号
                            String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId());
                            String nonce_str = UUIDUtil.getRandomCode(16);
                            ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, one.getMoney().multiply(bigDecimal1), s2,nonce_str,"玩游戏分账");
                            if (!fenzhang.getCode().equals(200)){
                                System.err.println("分账失败 原因是:"+fenzhang.getMsg());
                            }else{
                                one.setFenzhangNo(fenzhang.getData().toString());
                                one.setFenzhangOrderNo(nonce_str);
                                one.setFenzhangAmount(one.getMoney().multiply(bigDecimal1));
                                gameRecordService.updateById(one);
                            }
                        }
                    }
                }
            }
//            if (one!=null){
//                Integer gameId = one.getGameId();
//                Site byId1 = siteService.getById(one.getSiteId());
//                Store byId = storeService.getById(byId1.getStoreId());
//                if (byId!=null){
//                    if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){
//                        // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
//                        Thread.sleep(120000);
//                        // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
//                        OperatorUser operatorId = operatorUserService.getOne(
//                                new QueryWrapper<OperatorUser>().eq("operatorId",byId.getOperatorId())
//                        );
//                        if (operatorId.getWechatProportion() == null){
//                            operatorId.setWechatProportion("0");
//                        }
//                        if (operatorId.getAlipayProportion() == null){
//                            operatorId.setAlipayProportion("0");
//                        }
//                        String proportion=  operatorId.getWechatProportion()+","+operatorId.getAlipayProportion();
//                        String[] split = proportion.split(",");
//                        String s1 = split[0];
//                        if (!s1.equals("未设置")){
//                            BigDecimal bigDecimal = new BigDecimal(s1);
//                            // 分账比例
//                            BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
//                            // 微信商户号
//                            String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId());
//                            String nonce_str = UUIDUtil.getRandomCode(16);
//
//                            ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, one.getMoney().multiply(bigDecimal1), s2,nonce_str,"玩游戏分账");
//                            if (!fenzhang.getCode().equals(200)){
//                                System.err.println("分账失败 原因是:"+fenzhang.getMsg());
//                            }else{
//                                one.setFenzhangNo(fenzhang.getData().toString());
//                                one.setFenzhangOrderNo(nonce_str);
//                                one.setFenzhangAmount(one.getMoney().multiply(bigDecimal1));
//                                gameRecordService.updateById(one);
//                            }
//                        }
//                    }
//                }
//
//            }
            PrintWriter out = null;
            try {
@@ -570,6 +536,67 @@
            out.close();
        }
    }
    @ResponseBody
    @PostMapping("/base/game/wechatPaymentGameCallback1")
    public void wechatPaymentGameCallback1(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.err.println("进入游戏回调");
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String out_trade_no = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            String attach = jsonInfo.getString("attach");
            if (trade_state.equals("SUCCESS")) {
                List<TGameRecord> list = gameRecordService.list(new QueryWrapper<TGameRecord>().eq("number", out_trade_no).eq("payType", 1));
                for (TGameRecord gameRecord : list) {
                    gameRecord.setStatus(1);
                    gameRecord.setOrderNo(transaction_id);
                    CourseCounsum courseCounsum = new CourseCounsum();
                    courseCounsum.setChangeType(3);
                    courseCounsum.setInsertTime(new Date());
                    courseCounsum.setReason("智慧球场;" + gameRecord.getMoney());
                    courseCounsum.setAppUserId(gameRecord.getUserId());
                    courseRecordClient.save(courseCounsum);
                }
                gameRecordService.updateBatchById(list);
                String[] s = attach.split("_");
                Integer integer = startGame(Integer.valueOf(s[0]), Integer.valueOf(s[6]), Integer.valueOf(s[1]), Integer.valueOf(s[2]), Integer.valueOf(s[3]));
                TGameRecord one = gameRecordService.getOne(new QueryWrapper<TGameRecord>().eq("number", out_trade_no).eq("payType", 1));
                System.err.println("游戏支付记录" + one);
                PrintWriter out = null;
                try {
                    out = response.getWriter();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                out.print("SUCCESS");
                out.flush();
                out.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    @ResponseBody
    @PostMapping("/base/game/aliPaymentGameCallback")
cloud-server-other/src/main/java/com/dsh/other/controller/SiteController.java
@@ -22,6 +22,8 @@
import com.dsh.other.model.vo.siteVo.SiteSearchVO;
import com.dsh.other.service.*;
import com.dsh.other.util.*;
import com.dsh.other.util.wx.WxV3PayConfig;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
@@ -34,8 +36,10 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@@ -111,6 +115,17 @@
    @ResponseBody
    public String getmerchantNumberByOperatorId(@PathVariable("id")Integer id) {
        return siteService.getmerchantNumberByOperatorId(id);
    }
    /**
     * 通过运营商id 获取支付宝商户号
     * @param id
     * @return
     */
    @RequestMapping("/base/getmerchantNumberAliByOperatorId/{id}")
    @ResponseBody
    public String getmerchantNumberAliByOperatorId(@PathVariable("id")Integer id) {
        return siteService.getmerchantNumberAliByOperatorId(id);
    }
    /**
     * 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
@@ -367,6 +382,56 @@
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/site/weChatPaymentSiteCallback1")
    public void weChatPaymentSiteCallback1(HttpServletRequest request, HttpServletResponse response) {
        try {
            System.err.println("微信回调");
            System.err.println("请求" + request);
            BufferedReader reader = request.getReader();
            String string1 = reader.toString();
            System.err.println("请求reader" + string1);
            StringBuilder requestBody = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
            System.err.println("全部请求体" + requestBody);
            JSONObject jsonObject = JSONObject.parseObject(requestBody.toString());
            JSONObject resource = jsonObject.getJSONObject("resource");
            AesUtil aesUtil = new AesUtil(WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8),
                    resource.getString("ciphertext"));
            System.err.println("微信解密的字符串信息" + decryptedData);
            JSONObject jsonInfo = (JSONObject) JSONObject.parse(decryptedData);
            String code = jsonInfo.getString("out_trade_no");
            String transaction_id = jsonInfo.getString("transaction_id");
            String trade_state = jsonInfo.getString("trade_state");
            if (trade_state.equals("SUCCESS")) {
                SiteBooking siteBooking = siteBookingService.getOne(new QueryWrapper<SiteBooking>().eq("orderNo", code).eq("state", 1));
                if (siteBooking.getStatus() == 0) {
                    siteBooking.setPayTime(new Date());
                    siteBooking.setStatus(1);
                    siteBooking.setPayOrderNo(transaction_id);
                    siteBookingService.updateById(siteBooking);
                    PrintWriter out = response.getWriter();
                    out.write("SUCCESS");
                    out.flush();
                    out.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 购买课程微信支付回调
     *
     * @param request
     * @param response
     */
    @ResponseBody
    @PostMapping("/base/site/weChatPaymentSiteCallback")
    public void weChatPaymentSiteCallback(HttpServletRequest request, HttpServletResponse response) {
        try {
@@ -383,42 +448,42 @@
                    siteBooking.setPayOrderNo(transaction_id);
                    siteBookingService.updateById(siteBooking);
                    Store byId = service.getById(siteBooking.getStoreId());
                    if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){
                        System.err.println("预约场地分账");
                        // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
                        Thread.sleep(120000);
                        // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
                        OperatorUser operatorId = operatorUserService.getOne(
                                new QueryWrapper<OperatorUser>().eq("operatorId",byId.getOperatorId())
                        );
                        if (operatorId.getWechatProportion() == null){
                            operatorId.setWechatProportion("0");
                        }
                        if (operatorId.getAlipayProportion() == null){
                            operatorId.setAlipayProportion("0");
                        }
                        String proportion=  operatorId.getWechatProportion()+","+operatorId.getAlipayProportion();
                        String[] split = proportion.split(",");
                        String s1 = split[0];
                        if (!s1.equals("未设置")){
                            BigDecimal bigDecimal = new BigDecimal(s1);
                            // 分账比例
                            BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
                            // 微信商户号
                            String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId());
                            String nonce_str = UUIDUtil.getRandomCode(16);
                            BigDecimal bigDecimal2 = new BigDecimal(siteBooking.getPayMoney());
                            ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, bigDecimal2.multiply(bigDecimal1), s2,nonce_str,"预约场地分账");
                            if (!fenzhang.getCode().equals(200)){
                                System.err.println("分账失败 原因是:"+fenzhang.getMsg());
                            }else{
                                siteBooking.setFenzhangNo(fenzhang.getData().toString());
                                siteBooking.setFenzhangOrderNo(nonce_str);
                                siteBooking.setFenzhangAmount(bigDecimal2.multiply(bigDecimal1));
                                siteBookingService.updateById(siteBooking);
                            }
                        }
                    }
//                    if (byId.getOperatorId()!=null && byId.getOperatorId()!=0){
//                        System.err.println("预约场地分账");
//                        // 休眠两分钟后再调用分账接口 避免提示订单正在处理中
//                        Thread.sleep(120000);
//                        // 根据运营商id获取对应运营商分账比例 返回格式: 微信分账比例,支付宝分账比例
//                        OperatorUser operatorId = operatorUserService.getOne(
//                                new QueryWrapper<OperatorUser>().eq("operatorId",byId.getOperatorId())
//                        );
//                        if (operatorId.getWechatProportion() == null){
//                            operatorId.setWechatProportion("0");
//                        }
//                        if (operatorId.getAlipayProportion() == null){
//                            operatorId.setAlipayProportion("0");
//                        }
//                        String proportion=  operatorId.getWechatProportion()+","+operatorId.getAlipayProportion();
//                        String[] split = proportion.split(",");
//                        String s1 = split[0];
//                        if (!s1.equals("未设置")){
//                            BigDecimal bigDecimal = new BigDecimal(s1);
//                            // 分账比例
//                            BigDecimal bigDecimal1 = bigDecimal.divide(new BigDecimal(100)).setScale(2);
//                            // 微信商户号
//                            String s2 =siteService.getmerchantNumberByOperatorId(byId.getOperatorId());
//                            String nonce_str = UUIDUtil.getRandomCode(16);
//                            BigDecimal bigDecimal2 = new BigDecimal(siteBooking.getPayMoney());
//                            ResultUtil fenzhang = payMoneyUtil.fenzhang(transaction_id, bigDecimal2.multiply(bigDecimal1), s2,nonce_str,"预约场地分账");
//                            if (!fenzhang.getCode().equals(200)){
//                                System.err.println("分账失败 原因是:"+fenzhang.getMsg());
//                            }else{
//                                siteBooking.setFenzhangNo(fenzhang.getData().toString());
//                                siteBooking.setFenzhangOrderNo(nonce_str);
//                                siteBooking.setFenzhangAmount(bigDecimal2.multiply(bigDecimal1));
//                                siteBookingService.updateById(siteBooking);
//                            }
//                        }
//                    }
                    PrintWriter out = response.getWriter();
                    out.write(result);
                    out.flush();
@@ -430,7 +495,6 @@
            e.printStackTrace();
        }
    }
    /**
     * 预约场地支付宝回调
     *
cloud-server-other/src/main/java/com/dsh/other/mapper/SiteMapper.java
@@ -51,4 +51,5 @@
    String getSMIDByOperatorId(@Param("id")Integer id);
    String getmerchantNumberByOperatorId(@Param("id")Integer id);
    String getmerchantNumberAliByOperatorId(@Param("id")Integer id);
}
cloud-server-other/src/main/java/com/dsh/other/mapper/TOperatorUserMapper.java
@@ -15,13 +15,7 @@
 */
public interface TOperatorUserMapper extends BaseMapper<OperatorUser> {
    List<Map<String, Object>> listAll(@Param("page") Page<Map<String, Object>> page,
                                      @Param("province") String province,
                                      @Param("city") String city,
                                      @Param("userName") String userName,
                                      @Param("phone") String phone,
                                      @Param("platform") Integer platform,
                                      @Param("type") Integer type,
                                      @Param("state") Integer state);
    String getmerchantNumberByOperatorId(@Param("operatorId") Integer operatorId);
}
cloud-server-other/src/main/java/com/dsh/other/service/IOperatorUserService.java
@@ -13,5 +13,7 @@
 */
public interface IOperatorUserService extends IService<OperatorUser> {
    List<Map<String, Object>> listAll(Page<Map<String, Object>> page, String province, String city, String userName, String phone, Integer platform, Integer type, Integer state);
    String getmerchantNumberByOperatorId(Integer operatorId);
}
cloud-server-other/src/main/java/com/dsh/other/service/ISiteService.java
@@ -104,4 +104,5 @@
    String getSMIDByOperatorId(Integer id);
    String getmerchantNumberByOperatorId(Integer id);
    String getmerchantNumberAliByOperatorId(Integer id);
}
cloud-server-other/src/main/java/com/dsh/other/service/impl/SiteServiceImpl.java
@@ -457,7 +457,7 @@
        return ResultUtil.success();
    }
    private String smidVx = "2088330203191220";//平台微信商户号
    /**
     * 课程微信支付
     *
@@ -474,64 +474,20 @@
        String merchantNumber = "";
        if (byId.getOperatorId()==null || byId.getOperatorId()==0){
            // 平台
            isFenZhang = 0;
        }
        ResultUtil weixinpay = payMoneyUtil.weixinpay("预约场地-"+isFenZhang, id.toString(), code, paymentPrice.toString(),
                "/base/site/weChatPaymentSiteCallback", "APP", "");
        if (weixinpay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            SiteBooking siteBooking = siteBookingService.getById(id);
                            if (siteBooking.getStatus() != 0) {
                                break;
                            }
                            ResultUtil<Map<String, String>> resultUtil = payMoneyUtil.queryWXOrder(siteBooking.getOrderNo(), "");
                            if (resultUtil.getCode() == 200 && siteBooking.getStatus() == 0) {
                                /**
                                 * SUCCESS—支付成功,
                                 * REFUND—转入退款,
                                 * NOTPAY—未支付,
                                 * CLOSED—已关闭,
                                 * REVOKED—已撤销(刷卡支付),
                                 * USERPAYING--用户支付中,
                                 * PAYERROR--支付失败(其他原因,如银行返回失败)
                                 */
                                Map<String, String> data1 = resultUtil.getData();
                                String s = data1.get("trade_state");
                                String transaction_id = data1.get("transaction_id");
                                if ("REFUND".equals(s) || "CLOSED".equals(s) || "REVOKED".equals(s) || "PAYERROR".equals(s) || num == 10) {
                                    break;
                                }
                                if ("SUCCESS".equals(s)) {
                                    siteBooking.setPayTime(new Date());
                                    siteBooking.setStatus(1);
                                    siteBooking.setPayOrderNo(transaction_id);
                                    siteBookingService.updateById(siteBooking);
                                    break;
                                }
                                if ("USERPAYING".equals(s) || "NOTPAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        return weixinpay;
        }else{
            String smidVx= operatorUserService.getmerchantNumberByOperatorId(byId.getOperatorId());
            if (!StringUtils.hasLength(smidVx)){
                return ResultUtil.error("运营商未配置微信商户号,获取支付失败!");
            }
            return payMoneyUtil.weixinpayV3(smidVx,"预约场地"
                    ,code,"/base/site/weChatPaymentSiteCallback1",
                    paymentPrice.toString(),id.toString());
        }
    }
    private String smid = "2088330203191220";//平台支付宝商户号
@@ -560,76 +516,76 @@
        }else if (operatorId == 0){
            smid1 = smid;
        }else{
            smid1 = siteService.getSMIDByOperatorId(operatorId);
            smid1 = siteService.getmerchantNumberAliByOperatorId(operatorId);
        }
        ResultUtil alipay = payMoneyUtil.alipay(smid1,"预约场地", "预约场地", id.toString(), code, paymentPrice.toString(), "/base/site/aliPaymentSiteCallback");
        System.out.println("预约场地----" + alipay.getCode());
        if (alipay.getCode() == 200) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        int num = 1;
                        int wait = 0;
                        while (num <= 10) {
                            int min = 5000;
                            wait += (min * num);
                            Thread.sleep(wait);
                            SiteBooking siteBooking = siteBookingService.getById(id);
                            if (siteBooking.getStatus() != 0) {
                                break;
                            }
                            AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code);
                            if (resultUtil.getCode().equals("10000") && siteBooking.getStatus() == 0) {
                                /**
                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
                                 * TRADE_SUCCESS(交易支付成功)、
                                 * TRADE_FINISHED(交易结束,不可退款)
                                 */
//                                Map<String, String> data1 = resultUtil.getData();
//                                String s = data1.get("tradeStatus");
                                String tradeNo = resultUtil.getTradeNo();
                                String s = resultUtil.getTradeStatus();
                                System.out.println("ssssss" + s);
                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
                                    break;
                                }
                                if ("TRADE_SUCCESS".equals(s)) {
                                    siteBooking.setPayTime(new Date());
                                    siteBooking.setStatus(1);
                                    siteBooking.setPayOrderNo(tradeNo);
                                    siteBookingService.updateById(siteBooking);
                                    System.err.println("======完成支付");
                                    // 判断预约的门店 属于哪个运营商
                                    Integer storeId = siteBooking.getStoreId();
                                    Store byId = storeService.getById(storeId);
                                    Integer operatorId = byId.getOperatorId();
                                    String smid1 = "";
                                    if (operatorId == null || operatorId == 0){
                                        // 平台的门店 不冻结资金不做分账处理
                                        payMoneyUtil.confirm1(smid1,code,tradeNo,paymentPrice.toString());
                                    }else{
                                        smid1 = siteService.getSMIDByOperatorId(operatorId);
                                        payMoneyUtil.confirm(smid1,code,tradeNo,paymentPrice.toString());
                                        // 分账
                                        extracted(operatorId, new BigDecimal(paymentPrice.toString()), tradeNo);
                                    }
                                    break;
                                }
                                if ("WAIT_BUYER_PAY".equals(s)) {
                                    num++;
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
//        if (alipay.getCode() == 200) {
//            new Thread(new Runnable() {
//                @Override
//                public void run() {
//                    try {
//                        int num = 1;
//                        int wait = 0;
//                        while (num <= 10) {
//                            int min = 5000;
//                            wait += (min * num);
//                            Thread.sleep(wait);
//                            SiteBooking siteBooking = siteBookingService.getById(id);
//                            if (siteBooking.getStatus() != 0) {
//                                break;
//                            }
//                            AlipayTradeQueryResponse resultUtil = payMoneyUtil.queryALIOrder(code);
//                            if (resultUtil.getCode().equals("10000") && siteBooking.getStatus() == 0) {
//                                /**
//                                 * WAIT_BUYER_PAY(交易创建,等待买家付款)、
//                                 * TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、
//                                 * TRADE_SUCCESS(交易支付成功)、
//                                 * TRADE_FINISHED(交易结束,不可退款)
//                                 */
////                                Map<String, String> data1 = resultUtil.getData();
////                                String s = data1.get("tradeStatus");
//                                String tradeNo = resultUtil.getTradeNo();
//                                String s = resultUtil.getTradeStatus();
//                                System.out.println("ssssss" + s);
//                                if ("TRADE_CLOSED".equals(s) || "TRADE_FINISHED".equals(s) || num == 10) {
//                                    break;
//                                }
//                                if ("TRADE_SUCCESS".equals(s)) {
//                                    siteBooking.setPayTime(new Date());
//                                    siteBooking.setStatus(1);
//                                    siteBooking.setPayOrderNo(tradeNo);
//                                    siteBookingService.updateById(siteBooking);
//                                    System.err.println("======完成支付");
//                                    // 判断预约的门店 属于哪个运营商
//                                    Integer storeId = siteBooking.getStoreId();
//                                    Store byId = storeService.getById(storeId);
//                                    Integer operatorId = byId.getOperatorId();
//                                    String smid1 = "";
//                                    if (operatorId == null || operatorId == 0){
//                                        // 平台的门店 不冻结资金不做分账处理
//                                        payMoneyUtil.confirm1(smid1,code,tradeNo,paymentPrice.toString());
//                                    }else{
//                                        smid1 = siteService.getSMIDByOperatorId(operatorId);
//                                        payMoneyUtil.confirm(smid1,code,tradeNo,paymentPrice.toString());
//                                        // 分账
//                                        extracted(operatorId, new BigDecimal(paymentPrice.toString()), tradeNo);
//                                    }
//
//                                    break;
//                                }
//                                if ("WAIT_BUYER_PAY".equals(s)) {
//                                    num++;
//                                }
//                            }
//                        }
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                }
//            }).start();
//        }
        return alipay;
    }
@@ -1011,4 +967,9 @@
    public String getmerchantNumberByOperatorId(Integer id) {
        return siteMapper.getmerchantNumberByOperatorId(id);
    }
    @Override
    public String getmerchantNumberAliByOperatorId(Integer id) {
        return siteMapper.getmerchantNumberAliByOperatorId(id);
    }
}
cloud-server-other/src/main/java/com/dsh/other/service/impl/TOperatorUserServiceImpl.java
@@ -21,9 +21,8 @@
 */
@Service
public class TOperatorUserServiceImpl extends ServiceImpl<TOperatorUserMapper, OperatorUser> implements IOperatorUserService {
    @Override
    public List<Map<String, Object>> listAll(Page<Map<String, Object>> page, String province, String city, String userName, String phone, Integer platform, Integer type, Integer state) {
        return this.baseMapper.listAll(page, province, city, userName, phone, platform, type, state);
    public String getmerchantNumberByOperatorId(Integer operatorId) {
        return this.baseMapper.getmerchantNumberByOperatorId(operatorId);
    }
}
cloud-server-other/src/main/java/com/dsh/other/util/PayMoneyUtil.java
@@ -12,6 +12,10 @@
import com.alipay.api.msg.MsgHandler;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.dsh.other.util.wx.PartnerAppPrepay;
import com.dsh.other.util.wx.WXPayUtility;
import com.dsh.other.util.wx.WeChatV3SignUtil;
import com.dsh.other.util.wx.WxV3PayConfig;
import lombok.Synchronized;
import org.apache.commons.collections.map.HashedMap;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -72,8 +76,6 @@
    private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书
    public ResultUtil confirm(String smid,String code, String outTradeNo, String amount) {
        AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",
@@ -115,6 +117,7 @@
            return ResultUtil.error("出现问题啦");
        }
    }
    // 属于平台的运营商 因为无需分账不冻结资金
    public ResultUtil confirm1(String smid,String code, String outTradeNo, String amount) {
        AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",
@@ -156,6 +159,7 @@
            return ResultUtil.error("出现问题啦");
        }
    }
    /**
     * 支付宝支付
     */
@@ -451,16 +455,7 @@
        map.put("mch_id", mchId);
        map.put("nonce_str", nonce_str);
        String temp = "";
        if (body.split("-").length>1){
            temp = body.split("-")[1];
            map.put("body", body.split("-")[0]);
        }else{
            map.put("body", body);
        }
        if (StringUtils.hasLength(temp) && temp.equals("1")){
            // 添加分账标识
            map.put("profit_sharing", "Y");
        }
        map.put("attach", attach);//存储订单id
        map.put("out_trade_no", out_trade_no);//存储的订单code
        map.put("total_fee", i);
@@ -551,8 +546,69 @@
            return ResultUtil.error(map1.get("return_msg"), new JSONObject());
        }
    }
    public ResultUtil weixinpayV3(String subMchid, String description, String outTradeNo, String notifyUrl, String totalFee, String attach) throws Exception {
        int i = new BigDecimal(totalFee).multiply(new BigDecimal("100")).intValue();
        // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
        PartnerAppPrepay client = new PartnerAppPrepay(
//                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//                "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//                "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
                "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
                "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
                "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
                "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
                "/usr/playpai/server/wxV3/pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
        );
        PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest request = new PartnerAppPrepay.PartnerAPIv3CommonPrepayRequest();
        request.spAppid = appid;// appid
        request.spMchid = WxV3PayConfig.Mch_ID;// 服务商商户号
        request.subMchid = subMchid;// 子商户号
        request.description = description;// 描述
        request.outTradeNo = outTradeNo;// 订单号
        request.notifyUrl = callbackPath+notifyUrl;// 回调地址
        request.attach = attach;// 回调地址
        request.amount = new PartnerAppPrepay.CommonAmountInfo();
        request.amount.total = (long) i;// 金额 单位分
        request.amount.currency = "CNY";
        String prepayId = "";
        Map<String, Object> map3 = new HashMap<>();
        try {
            PartnerAppPrepay.PartnerAPIv3AppPrepayResponse response = client.run(request);
            // TODO: 请求成功,继续业务逻辑
            System.err.println("微信申请成功,预支付ID: " + response.prepayId);
            prepayId = response.prepayId;
        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败,根据状态码执行不同的逻辑
            e.printStackTrace();
        }
        map3.put("appid", appid);
        map3.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        String nonce_str = UUIDUtil.getRandomCode(16);
        map3.put("noncestr", nonce_str);
        map3.put("prepayid", prepayId);
        // 构造待签名字符串
        String message = WeChatV3SignUtil.buildSignMessage(map3);
        // 私钥路径(pem 文件)
        String privateKeyPath = "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
//      String privateKeyPath = "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem";
        // 生成签名
        String sign = WeChatV3SignUtil.signWithPrivateKey(message, privateKeyPath);
        map3.put("sign", sign);
        map3.put("package", "Sign=WXPay");
        map3.put("partnerid", WxV3PayConfig.Mch_ID);// 服务商商户号
        System.err.println(map3);
        return ResultUtil.success(map3);
    }
    /**
     * 发起分账
     *
     * @param order 微信订单号
     * @return
     */
@@ -626,8 +682,10 @@
            return ResultUtil.error(map1.get("return_msg"), new JSONObject());
        }
    }
    /**
     * 发起分账回退
     *
     * @param order 微信订单号
     * @return
     */
@@ -1336,8 +1394,10 @@
        }
        return null;
    }
    /**
     * sha256_HMAC加密
     *
     * @param message 消息
     * @param secret  秘钥
     * @return 加密后字符串
cloud-server-other/src/main/java/com/dsh/other/util/wx/PartnerAppPrepay.java
New file
@@ -0,0 +1,237 @@
package com.dsh.other.util.wx;
import com.google.gson.annotations.SerializedName;
import okhttp3.*;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;
/**
 * App下单
 */
public class PartnerAppPrepay {
  private static String HOST = "https://api.mch.weixin.qq.com";
  private static String METHOD = "POST";
  private static String PATH = "/v3/pay/partner/transactions/app";
//  public static void main(String[] args) {
//    // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/partner/4013080340
//    PartnerAppPrepay client = new PartnerAppPrepay(
//        "1681873607",                  // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考https://pay.weixin.qq.com/doc/v3/partner/4013080340
//        "55714944F7A7E52526F708280B176DCC838F371A",       // 商户API证书序列号,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013058924
//        "E:\\wanpai\\1681873607_20250424_cert\\apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径
//        "PUB_KEY_ID_0116818736072025042400351694002605",          // 微信支付公钥ID,如何获取请参考https://pay.weixin.qq.com/doc/v3/partner/4013038589
//        "E:\\wanpai\\pub_key.pem"     // 微信支付公钥文件路径,本地文件路径
//    );
//
//    PartnerAPIv3CommonPrepayRequest request = new PartnerAPIv3CommonPrepayRequest();
//    request.spAppid = "wx41d32f362ba0f911";
//    request.spMchid = WxV3PayConfig.Mch_ID;
//    request.subMchid = "1720719391";
//    request.description = "Image形象店-深圳腾大-QQ公仔";
//    request.outTradeNo = "12177525012014070332333680182";
//    request.notifyUrl = "https://www.weixin.qq.com/wxpay/pay.php";
////    request.goodsTag = "WXG";
////    request.settleInfo = new PartnerSettleInfo();
////    request.settleInfo.profitSharing = false;
//    request.amount = new CommonAmountInfo();
//    request.amount.total = 100L;
//    request.amount.currency = "CNY";
////    request.detail = new CouponInfo();
////    request.detail.costPrice = 1L;
////    request.detail.invoiceId = "wx123";
////    request.detail.goodsDetail = new ArrayList<>();
////    {
////      GoodsDetail item0 = new GoodsDetail();
////      item0.merchantGoodsId = "1246464644";
////      item0.wechatpayGoodsId = "1001";
////      item0.goodsName = "iPhone6s 16G";
////      item0.quantity = 1L;
////      item0.unitPrice = 528800L;
////      request.detail.goodsDetail.add(item0);
////    };
////    request.sceneInfo = new CommonSceneInfo();
////    request.sceneInfo.payerClientIp = "14.23.150.211";
////    request.sceneInfo.deviceId = "013467007045764";
////    request.sceneInfo.storeInfo = new StoreInfo();
////    request.sceneInfo.storeInfo.id = "0001";
////    request.sceneInfo.storeInfo.name = "腾讯大厦分店";
////    request.sceneInfo.storeInfo.areaCode = "440305";
////    request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
//    try {
//      PartnerAPIv3AppPrepayResponse response = client.run(request);
//
//      // TODO: 请求成功,继续业务逻辑
//      System.err.println("微信申请成功,预支付ID: " + response.prepayId);
//    } catch (WXPayUtility.ApiException e) {
//      // TODO: 请求失败,根据状态码执行不同的逻辑
//      e.printStackTrace();
//    }
//  }
  public PartnerAPIv3AppPrepayResponse run(PartnerAPIv3CommonPrepayRequest request) {
    String uri = PATH;
    String reqBody = WXPayUtility.toJson(request);
    Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
    reqBuilder.addHeader("Accept", "application/json");
    reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
    reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
    reqBuilder.addHeader("Content-Type", "application/json");
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
    reqBuilder.method(METHOD, requestBody);
    Request httpRequest = reqBuilder.build();
    // 发送HTTP请求
    OkHttpClient client = new OkHttpClient.Builder().build();
    try (Response httpResponse = client.newCall(httpRequest).execute()) {
      String respBody = WXPayUtility.extractBody(httpResponse);
      if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
        // 2XX 成功,验证应答签名
        WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
                                      httpResponse.headers(), respBody);
        // 从HTTP应答报文构建返回数据
        return WXPayUtility.fromJson(respBody, PartnerAPIv3AppPrepayResponse.class);
      } else {
        throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
      }
    } catch (IOException e) {
      throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
    }
  }
  private final String mchid;
  private final String certificateSerialNo;
  private final PrivateKey privateKey;
  private final String wechatPayPublicKeyId;
  private final PublicKey wechatPayPublicKey;
  public PartnerAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
    this.mchid = mchid;
    this.certificateSerialNo = certificateSerialNo;
    this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
    this.wechatPayPublicKeyId = wechatPayPublicKeyId;
    this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
  }
  public static class PartnerSettleInfo {
    @SerializedName("profit_sharing")
    public Boolean profitSharing;
  }
  public static class CommonAmountInfo {
    @SerializedName("total")
    public Long total;
    @SerializedName("currency")
    public String currency;
  }
  public static class CommonSceneInfo {
    @SerializedName("payer_client_ip")
    public String payerClientIp;
    @SerializedName("device_id")
    public String deviceId;
    @SerializedName("store_info")
    public StoreInfo storeInfo;
  }
  public static class GoodsDetail {
    @SerializedName("merchant_goods_id")
    public String merchantGoodsId;
    @SerializedName("wechatpay_goods_id")
    public String wechatpayGoodsId;
    @SerializedName("goods_name")
    public String goodsName;
    @SerializedName("quantity")
    public Long quantity;
    @SerializedName("unit_price")
    public Long unitPrice;
  }
  public static class PartnerAPIv3AppPrepayResponse {
    @SerializedName("prepay_id")
    public String prepayId;
  }
  public static class PartnerAPIv3CommonPrepayRequest {
    @SerializedName("sp_appid")
    public String spAppid;
    @SerializedName("sp_mchid")
    public String spMchid;
    @SerializedName("sub_appid")
    public String subAppid;
    @SerializedName("sub_mchid")
    public String subMchid;
    @SerializedName("description")
    public String description;
    @SerializedName("out_trade_no")
    public String outTradeNo;
    @SerializedName("time_expire")
    public String timeExpire;
    @SerializedName("attach")
    public String attach;
    @SerializedName("notify_url")
    public String notifyUrl;
    @SerializedName("goods_tag")
    public String goodsTag;
    @SerializedName("settle_info")
    public PartnerSettleInfo settleInfo;
    @SerializedName("support_fapiao")
    public Boolean supportFapiao;
    @SerializedName("amount")
    public CommonAmountInfo amount;
    @SerializedName("detail")
    public CouponInfo detail;
    @SerializedName("scene_info")
    public CommonSceneInfo sceneInfo;
  }
  public static class CouponInfo {
    @SerializedName("cost_price")
    public Long costPrice;
    @SerializedName("invoice_id")
    public String invoiceId;
    @SerializedName("goods_detail")
    public List<GoodsDetail> goodsDetail;
  }
  public static class StoreInfo {
    @SerializedName("id")
    public String id;
    @SerializedName("name")
    public String name;
    @SerializedName("area_code")
    public String areaCode;
    @SerializedName("address")
    public String address;
  }
}
cloud-server-other/src/main/java/com/dsh/other/util/wx/WXPayUtility.java
New file
@@ -0,0 +1,441 @@
package com.dsh.other.util.wx;
import com.google.gson.*;
import com.google.gson.annotations.Expose;
import com.wechat.pay.java.core.util.GsonUtil;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
public class WXPayUtility {
  private static final Gson gson = new GsonBuilder()
      .disableHtmlEscaping()
      .addSerializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.serialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .addDeserializationExclusionStrategy(new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
          final Expose expose = fieldAttributes.getAnnotation(Expose.class);
          return expose != null && !expose.deserialize();
        }
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
          return false;
        }
      })
      .create();
  private static final char[] SYMBOLS =
      "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
  private static final SecureRandom random = new SecureRandom();
  /**
   * 将 Object 转换为 JSON 字符串
   */
  public static String toJson(Object object) {
    return gson.toJson(object);
  }
  /**
   * 将 JSON 字符串解析为特定类型的实例
   */
  public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    return gson.fromJson(json, classOfT);
  }
  /**
   * 从公私钥文件路径中读取文件内容
   *
   * @param keyPath 文件路径
   * @return 文件内容
   */
  private static String readKeyStringFromPath(String keyPath) {
    try {
      return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }
  /**
   * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
   *
   * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
          .replace("-----END PRIVATE KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePrivate(
          new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的私钥文件中加载私钥
   *
   * @param keyPath 私钥文件路径
   * @return PrivateKey 对象
   */
  public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
    return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
   *
   * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromString(String keyString) {
    try {
      keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
          .replace("-----END PUBLIC KEY-----", "")
          .replaceAll("\\s+", "");
      return KeyFactory.getInstance("RSA").generatePublic(
          new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException(e);
    } catch (InvalidKeySpecException e) {
      throw new IllegalArgumentException(e);
    }
  }
  /**
   * 从 PKCS#8 格式的公钥文件中加载公钥
   *
   * @param keyPath 公钥文件路径
   * @return PublicKey 对象
   */
  public static PublicKey loadPublicKeyFromPath(String keyPath) {
    return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
  }
  /**
   * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
   */
  public static String createNonce(int length) {
    char[] buf = new char[length];
    for (int i = 0; i < length; ++i) {
      buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
    }
    return new String(buf);
  }
  /**
   * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
   *
   * @param publicKey 加密用公钥对象
   * @param plaintext 待加密明文
   * @return 加密后密文
   */
  public static String encrypt(PublicKey publicKey, String plaintext) {
    final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
    try {
      Cipher cipher = Cipher.getInstance(transformation);
      cipher.init(Cipher.ENCRYPT_MODE, publicKey);
      return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
      throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
      throw new IllegalArgumentException("Plaintext is too long", e);
    }
  }
  /**
   * 使用私钥按照指定算法进行签名
   *
   * @param message 待签名串
   * @param algorithm 签名算法,如 SHA256withRSA
   * @param privateKey 签名用私钥对象
   * @return 签名结果
   */
  public static String sign(String message, String algorithm, PrivateKey privateKey) {
    byte[] sign;
    try {
      Signature signature = Signature.getInstance(algorithm);
      signature.initSign(privateKey);
      signature.update(message.getBytes(StandardCharsets.UTF_8));
      sign = signature.sign();
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
    } catch (SignatureException e) {
      throw new RuntimeException("An error occurred during the sign process.", e);
    }
    return Base64.getEncoder().encodeToString(sign);
  }
  /**
   * 使用公钥按照特定算法验证签名
   *
   * @param message 待签名串
   * @param signature 待验证的签名内容
   * @param algorithm 签名算法,如:SHA256withRSA
   * @param publicKey 验签用公钥对象
   * @return 签名验证是否通过
   */
  public static boolean verify(String message, String signature, String algorithm,
                               PublicKey publicKey) {
    try {
      Signature sign = Signature.getInstance(algorithm);
      sign.initVerify(publicKey);
      sign.update(message.getBytes(StandardCharsets.UTF_8));
      return sign.verify(Base64.getDecoder().decode(signature));
    } catch (SignatureException e) {
      return false;
    } catch (InvalidKeyException e) {
      throw new IllegalArgumentException("verify uses an illegal publickey.", e);
    } catch (NoSuchAlgorithmException e) {
      throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
    }
  }
  /**
   * 根据微信支付APIv3请求签名规则构造 Authorization 签名
   *
   * @param mchid 商户号
   * @param certificateSerialNo 商户API证书序列号
   * @param privateKey 商户API证书私钥
   * @param method 请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
   * @param uri 请求接口的URL
   * @param body 请求接口的Body
   * @return 构造好的微信支付APIv3 Authorization 头
   */
  public static String buildAuthorization(String mchid, String certificateSerialNo,
                                          PrivateKey privateKey,
                                          String method, String uri, String body) {
    String nonce = createNonce(32);
    long timestamp = Instant.now().getEpochSecond();
    String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
        body == null ? "" : body);
    String signature = sign(message, "SHA256withRSA", privateKey);
    return String.format(
        "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
            "timestamp=\"%d\",serial_no=\"%s\"",
        mchid, nonce, signature, timestamp, certificateSerialNo);
  }
  /**
   * 对参数进行 URL 编码
   *
   * @param content 参数内容
   * @return 编码后的内容
   */
  public static String urlEncode(String content) {
    try {
      return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }
  }
  /**
   * 对参数Map进行 URL 编码,生成 QueryString
   *
   * @param params Query参数Map
   * @return QueryString
   */
  public static String urlEncode(Map<String, Object> params) {
    if (params == null || params.isEmpty()) {
      return "";
    }
    int index = 0;
    StringBuilder result = new StringBuilder();
    for (Map.Entry<String, Object> entry : params.entrySet()) {
      result.append(entry.getKey())
          .append("=")
          .append(urlEncode(entry.getValue().toString()));
      index++;
      if (index < params.size()) {
        result.append("&");
      }
    }
    return result.toString();
  }
  /**
   * 从应答中提取 Body
   *
   * @param response HTTP 请求应答对象
   * @return 应答中的Body内容,Body为空时返回空字符串
   */
  public static String extractBody(Response response) {
    if (response.body() == null) {
      return "";
    }
    try {
      BufferedSource source = response.body().source();
      return source.readUtf8();
    } catch (IOException e) {
      throw new RuntimeException(String.format("An error occurred during reading response body. Status: %d", response.code()), e);
    }
  }
  /**
   * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
   *
   * @param wechatpayPublicKeyId 微信支付公钥ID
   * @param wechatpayPublicKey 微信支付公钥对象
   * @param headers 微信支付应答 Header 列表
   * @param body 微信支付应答 Body
   */
  public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                      Headers headers,
                                      String body) {
    String timestamp = headers.get("Wechatpay-Timestamp");
    try {
      Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
      // 拒绝过期请求
      if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
        throw new IllegalArgumentException(
            String.format("Validate http response,timestamp[%s] of httpResponse is expires, "
                    + "request-id[%s]",
                timestamp, headers.get("Request-ID")));
      }
    } catch (DateTimeException | NumberFormatException e) {
      throw new IllegalArgumentException(
          String.format("Validate http response,timestamp[%s] of httpResponse is invalid, " +
                  "request-id[%s]", timestamp,
              headers.get("Request-ID")));
    }
    String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
        body == null ? "" : body);
    String serialNumber = headers.get("Wechatpay-Serial");
    if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
      throw new IllegalArgumentException(
          String.format("Invalid Wechatpay-Serial, Local: %s, Remote: %s", wechatpayPublicKeyId,
              serialNumber));
    }
    String signature = headers.get("Wechatpay-Signature");
    boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
    if (!success) {
      throw new IllegalArgumentException(
          String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                  + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
              headers.get("Request-ID"), headers, body));
    }
  }
  /**
   * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
   */
  public static class ApiException extends RuntimeException {
    private static final long serialVersionUID = 2261086748874802175L;
    private final int statusCode;
    private final String body;
    private final Headers headers;
    private final String errorCode;
    private final String errorMessage;
    public ApiException(int statusCode, String body, Headers headers) {
      super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode, body, headers));
      this.statusCode = statusCode;
      this.body = body;
      this.headers = headers;
      if (body != null && !body.isEmpty()) {
        JsonElement code;
        JsonElement message;
        try {
          JsonObject jsonObject = GsonUtil.getGson().fromJson(body, JsonObject.class);
          code = jsonObject.get("code");
          message = jsonObject.get("message");
        } catch (JsonSyntaxException ignored) {
          code = null;
          message = null;
        }
        this.errorCode = code == null ? null : code.getAsString();
        this.errorMessage = message == null ? null : message.getAsString();
      } else {
        this.errorCode = null;
        this.errorMessage = null;
      }
    }
    /**
     * 获取 HTTP 应答状态码
     */
    public int getStatusCode() {
      return statusCode;
    }
    /**
     * 获取 HTTP 应答包体内容
     */
    public String getBody() {
      return body;
    }
    /**
     * 获取 HTTP 应答 Header
     */
    public Headers getHeaders() {
      return headers;
    }
    /**
     * 获取 错误码 (错误应答中的 code 字段)
     */
    public String getErrorCode() {
      return errorCode;
    }
    /**
     * 获取 错误消息 (错误应答中的 message 字段)
     */
    public String getErrorMessage() {
      return errorMessage;
    }
  }
}
cloud-server-other/src/main/java/com/dsh/other/util/wx/WeChatSignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.other.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
public class WeChatSignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 使用商户私钥对字符串进行 SHA256withRSA 签名
     *
     * @param content      待签名字符串
     * @param privateKeyPath 商户私钥文件路径(pem 格式)
     * @return 签名结果(Base64 编码)
     */
    public static String signWithRSAPrivateKey(String content, String privateKeyPath) throws Exception {
        String privateKeyPEM = readPemFile(privateKeyPath);
        String privateKeyContent = privateKeyPEM
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");
        byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }
    private static String readPemFile(String filePath) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
        }
        return sb.toString();
    }
    /**
     * 按照微信 V3 规范生成待签名字符串
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            Object value = map.get(key);
            if (value != null && !value.toString().isEmpty()) {
                sb.append(key).append("=").append(value).append("\n");
            }
        }
        return sb.toString();
    }
}
cloud-server-other/src/main/java/com/dsh/other/util/wx/WeChatV3SignUtil.java
New file
@@ -0,0 +1,74 @@
package com.dsh.other.util.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.BufferedReader;
import java.io.FileReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
public class WeChatV3SignUtil {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }
    /**
     * 从 PEM 文件中读取私钥内容
     */
    public static PrivateKey loadPrivateKeyFromPem(String pemFilePath) throws Exception {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(pemFilePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.startsWith("-----") && !line.endsWith("-----")) {
                    sb.append(line);
                }
            }
        }
        byte[] pkcs8Bytes = Base64.getDecoder().decode(sb.toString());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
        KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
        return kf.generatePrivate(keySpec);
    }
    /**
     * 构造待签名字符串(按 key 字典序排序)
     */
    public static String buildSignMessage(Map<String, Object> map) {
        List<String> keys = new ArrayList<>(map.keySet());
//        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        sb.append(map.get("appid")).append("\n");
        sb.append(map.get("timestamp")).append("\n");
        sb.append(map.get("noncestr")).append("\n");
        sb.append(map.get("prepayid")).append("\n");
        return sb.toString();
    }
    /**
     * 使用商户私钥生成签名(SHA256withRSA + Base64)
     */
    public static String signWithPrivateKey(String message, String privateKeyPath) throws Exception {
        PrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPath);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(message.getBytes(StandardCharsets.UTF_8));
        byte[] signedBytes = signature.sign();
        return Base64.getEncoder().encodeToString(signedBytes);
    }
}
cloud-server-other/src/main/java/com/dsh/other/util/wx/WxV3PayConfig.java
New file
@@ -0,0 +1,66 @@
package com.dsh.other.util.wx;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class WxV3PayConfig {
    // 服务商AppId
    private String appIdValue = "wx41d32f362ba0f911";
    public static String APP_ID= "wx41d32f362ba0f911";
    // 服务商商户号
    private String mchIdValue= "1681873607";
    public static String Mch_ID= "1681873607";
    // 平台收款商户号 todo 待申请
    public static String smidVx= "2088330203191220";
    private  String smidVxValue= "2088330203191220";
    // 服务商商户私钥
    private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq";
    public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq";
    // 证书序列号
    private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A";
    public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A";
    private String privateKeyPathValue= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    public static String privateKeyPath= "/usr/playpai/server/wxV3/1681873607_20250424_cert/apiclient_key.pem";
    // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量
    @PostConstruct
    public void init() {
        APP_ID = this.appIdValue;
        APP_ID = this.appIdValue;
        smidVx = this.smidVxValue;
        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-other/src/main/resources/mapper/SiteMapper.xml
@@ -142,9 +142,12 @@
        ORDER BY insertTime desc
    </select>
    <select id="getSMIDByOperatorId" resultType="java.lang.String">
        select alipayNum from t_operator_user where operatorId = #{id}
        select merchantNumberAli from t_operator where operatorId = #{id}
    </select>
    <select id="getmerchantNumberByOperatorId" resultType="java.lang.String">
        select merchantNumber from t_operator where id = #{id}
    </select>
    <select id="getmerchantNumberAliByOperatorId" resultType="java.lang.String">
        select merchantNumberAli from t_operator where id = #{id}
    </select>
</mapper>
cloud-server-other/src/main/resources/mapper/TOperatorUserMapper.xml
New file
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dsh.other.mapper.TOperatorUserMapper">
    <select id="getmerchantNumberByOperatorId" resultType="java.lang.String">
        select merchantNumber from t_operator_user where id=#{operatorId}
    </select>
</mapper>