diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index de38128..853f6ea 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -7,25 +7,24 @@
-
+
+
+
-
-
-
\ No newline at end of file
diff --git a/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/pay/WeChatPayConfig2.java b/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/pay/WeChatPayConfig2.java
deleted file mode 100644
index 478ee8b..0000000
--- a/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/pay/WeChatPayConfig2.java
+++ /dev/null
@@ -1,207 +0,0 @@
-package org.jeecg.config.pay;
-
-import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
-import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
-import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
-import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
-import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
-import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
-import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
-import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
-import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
-import lombok.Data;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.stereotype.Component;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.security.GeneralSecurityException;
-import java.security.PrivateKey;
-
-/**
- * @author java996.icu
- * @title: WeChatPayConfig2
- * @projectName chemu
- * @description: TODO
- * @date 2022/12/7 16:36
- * @Version V1.0
- */
-@Component
-@Data
-@Slf4j
-@ConfigurationProperties(prefix = "wxpay")
-public class WeChatPayConfig2 {
-
- /**
- * 应用编号
- */
- private String appId;
- /**
- * 商户号
- */
- private String mchId;
- /**
- * 服务商商户号
- */
- private String slMchId;
- /**
- * APIv2密钥
- */
- private String apiKey;
- /**
- * APIv3密钥
- */
- private String apiV3Key;
- /**
- * 支付通知回调地址
- */
- private String notifyUrl;
- /**
- * 退款回调地址
- */
- private String refundNotifyUrl;
-
- /**
- * API 证书中的 key.pem
- */
- private String keyPemPath;
-
- /**
- * 商户序列号
- */
- private String serialNo;
-
- /**
- * 微信支付V3-url前缀
- */
- private String baseUrl;
-
-
- /**
- * 获取商户的私钥文件
- * @param keyPemPath
- * @return
- */
- public PrivateKey getPrivateKey(String keyPemPath){
-
- InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
- if(inputStream==null){
- throw new RuntimeException("私钥文件不存在");
- }
- return PemUtil.loadPrivateKey(inputStream);
- }
-//
-// /**
-// * 获取证书管理器实例
-// * @return
-// */
-// @Bean
-// public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
-//
-// log.info("获取证书管理器实例");
-//
-// //获取商户私钥
-// PrivateKey privateKey = getPrivateKey(keyPemPath);
-//
-// //私钥签名对象
-// PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
-//
-// //身份认证对象
-// WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
-//
-// // 使用定时更新的签名验证器,不需要传入证书
-// CertificatesManager certificatesManager = CertificatesManager.getInstance();
-// certificatesManager.putMerchant(mchId,wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));
-//
-// return certificatesManager.getVerifier(mchId);
-// }
-
-
-
- @Bean
- public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {
- log.info("开始获取微信支付证书管理器实例");
-
- // 验证关键参数
- log.debug("商户号: {}", mchId);
- log.debug("证书序列号: {}", serialNo);
- log.debug("APIv3密钥长度: {}", apiV3Key.length()); // 不记录具体密钥内容
-
- // 获取商户私钥
- PrivateKey privateKey = getPrivateKey(keyPemPath);
- if (privateKey == null) {
- throw new IllegalArgumentException("无法从路径加载私钥: " + keyPemPath);
- }
-
- // 创建签名器
- PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);
- WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
-
- try {
- CertificatesManager certificatesManager = CertificatesManager.getInstance();
-
- // 显式设置域名(如果库需要)
- // certificatesManager.setDomain("api.mch.weixin.qq.com");
-
- // 添加商户信息
- certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
-
- // 获取验证器
- Verifier verifier = certificatesManager.getVerifier(mchId);
- log.info("成功获取证书管理器实例");
- return verifier;
- } catch (HttpCodeException e) {
- log.error("微信支付API返回错误: "+ e);
- throw e;
- } catch (Exception e) {
- log.error("获取证书管理器实例时发生错误", e);
- throw e;
- }
- }
-
-
- /**
- * 获取支付http请求对象
- * @param verifier
- * @return
- */
- @Bean(name = "wxPayClient")
- public CloseableHttpClient getWxPayClient(Verifier verifier) {
-
- //获取商户私钥
- PrivateKey privateKey = getPrivateKey(keyPemPath);
-
- WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
- .withMerchant(mchId, serialNo, privateKey)
- .withValidator(new WechatPay2Validator(verifier));
-
- // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
- return builder.build();
- }
-
- /**
- * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
- */
- @Bean(name = "wxPayNoSignClient")
- public CloseableHttpClient getWxPayNoSignClient(){
-
- //获取商户私钥
- PrivateKey privateKey = getPrivateKey(keyPemPath);
-
- //用于构造HttpClient
- WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
- //设置商户信息
- .withMerchant(mchId, serialNo, privateKey)
- //无需进行签名验证、通过withValidator((response) -> true)实现
- .withValidator((response) -> true);
-
- // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
- return builder.build();
- }
-
-
-}
diff --git a/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java b/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
index 1b286eb..c52cba9 100644
--- a/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
+++ b/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java
@@ -76,6 +76,7 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/login/**", "anon");
filterChainDefinitionMap.put("/token/**", "anon");
filterChainDefinitionMap.put("/city/**", "anon");
+ filterChainDefinitionMap.put("/cashout/**", "anon");
diff --git a/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/cityMoneyLog/controller/CityMoneyLogController.java b/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/cityMoneyLog/controller/CityMoneyLogController.java
index c8ca0c2..2a9bb40 100644
--- a/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/cityMoneyLog/controller/CityMoneyLogController.java
+++ b/jeecg-boot-base/jeecg-boot-base-core/src/main/java/org/jeecg/modules/cityMoneyLog/controller/CityMoneyLogController.java
@@ -20,7 +20,6 @@ import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.oConvertUtils;
-import org.jeecg.config.pay.WeChatPayConfig2;
import org.jeecg.modules.cityMoneyLog.entity.CityMoneyLog;
import org.jeecg.modules.cityMoneyLog.req.TransferBatchesDetailsRequest;
import org.jeecg.modules.cityMoneyLog.req.TransferBatchesRequest;
@@ -68,8 +67,6 @@ public class CityMoneyLogController extends JeecgController edit(@RequestBody CityMoneyLog hanHaiWater) {
- HanHaiMember hanHaiMember = hanHaiMemberService.getById(hanHaiWater.getUserId());
- Integer n = (Integer) redisUtil.get("WITHDRAWAL:" + hanHaiWater.getId());
- if (n != null) {
- throw new JeecgBootException("请勿重复点击,后果自负!");
- }
- redisUtil.set("WITHDRAWAL:" + hanHaiWater.getId(), 1, 5);
-
- //微信-商家转账到零钱
-
- String idStr = "H" + IdWorker.getIdStr();
- TransferBatchesRequest transferBatchesRequest = new TransferBatchesRequest();
-
- transferBatchesRequest.setAppid(appId);
- transferBatchesRequest.setOutBillNo(idStr);
- transferBatchesRequest.setTransferRemark("商家提现");
- transferBatchesRequest.setOpenid(hanHaiMember.getAppletOpenid());
- transferBatchesRequest.setTransferSceneId("1005");
- transferBatchesRequest.setNotifyUrl(withdrawalNotifyUrl);
- String serialNo = null;
- //加密真实姓名
- try {
-
- X509Certificate certificate = weChatPayConfig2.getVerifier().getValidCertificate();
- serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
- //这里放真实姓名,目前你的数据库表没有这个字段
- String encryptOAEP = RsaCryptoUtil.encryptOAEP(hanHaiWater.getName(), certificate);
- transferBatchesRequest.setUserName(encryptOAEP);
- } catch (Exception e) {
- log.info("真实姓名加密失败");
- e.printStackTrace();
- throw new JeecgBootException("真实姓名加密失败");
- }
- transferBatchesRequest.setTransferAmount(MoneyUtil.Yuan2Fen(hanHaiWater.getPrice().doubleValue()));
- List transferBatchesDetailsRequests = new ArrayList<>();
- TransferBatchesDetailsRequest transferBatchesDetailsRequest = new TransferBatchesDetailsRequest();
- transferBatchesDetailsRequest.setInfoType("岗位类型");
- transferBatchesDetailsRequest.setInfoContent("销售员");
- TransferBatchesDetailsRequest transferBatchesDetailsRequest2 = new TransferBatchesDetailsRequest();
- transferBatchesDetailsRequest2.setInfoType("报酬说明");
- transferBatchesDetailsRequest2.setInfoContent("佣金报酬");
-
-
- transferBatchesDetailsRequests.add(transferBatchesDetailsRequest);
- transferBatchesDetailsRequests.add(transferBatchesDetailsRequest2);
- transferBatchesRequest.setTransferDetailList(transferBatchesDetailsRequests);
- String jsonString = JSONObject.toJSONString(transferBatchesRequest);
-
- log.info("请求参数:"+jsonString);
-
- String postTransBatRequest = HttpRequestUtil.postTransBatRequest(transferBatchUrl, jsonString, serialNo, wxsSerialNo, mchId, pemPath);
- log.error("返回结果1:" + postTransBatRequest);
- TransferBatchesResp transferBatchesResp = JSON.parseObject(postTransBatRequest, TransferBatchesResp.class);
- log.error("返回结果2:" + transferBatchesResp);
- if (transferBatchesResp == null || org.apache.commons.lang3.StringUtils.isBlank(transferBatchesResp.getOutBillNo()) || transferBatchesResp.getState().equals("FAIL")) {
- throw new JeecgBootException("打款失败");
- }
-
- //这里写自己的逻辑,比如改变这条记录的状态,变成提现成功
-
- CityMoneyLog hanHaiWater1 = new CityMoneyLog();
- hanHaiWater1.setId(hanHaiWater.getId());
- hanHaiWater1.setState(1);
- hanHaiWater1.setPackageInfo(transferBatchesResp.getPackageInfo());
- hanHaiWater1.setOutBatchNo(transferBatchesResp.getOutBillNo());
- hanHaiWater1.setBatchId(transferBatchesResp.getTransferBillNo());
- cityMoneyLogService.updateById(hanHaiWater1);
-
-
- return Result.OK("提现成功");
- }
-
+//
+// /**
+// * 编辑
+// *
+// * @param hanHaiWater
+// * @return
+// */
+// @AutoLog(value = "佣金流水-编辑")
+// @ApiOperation(value="佣金流水-编辑", notes="佣金流水-编辑")
+// @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
+// public Result edit(@RequestBody CityMoneyLog hanHaiWater) {
+// HanHaiMember hanHaiMember = hanHaiMemberService.getById(hanHaiWater.getUserId());
+// Integer n = (Integer) redisUtil.get("WITHDRAWAL:" + hanHaiWater.getId());
+// if (n != null) {
+// throw new JeecgBootException("请勿重复点击,后果自负!");
+// }
+// redisUtil.set("WITHDRAWAL:" + hanHaiWater.getId(), 1, 5);
+//
+// //微信-商家转账到零钱
+//
+// String idStr = "H" + IdWorker.getIdStr();
+// TransferBatchesRequest transferBatchesRequest = new TransferBatchesRequest();
+//
+// transferBatchesRequest.setAppid(appId);
+// transferBatchesRequest.setOutBillNo(idStr);
+// transferBatchesRequest.setTransferRemark("商家提现");
+// transferBatchesRequest.setOpenid(hanHaiMember.getAppletOpenid());
+// transferBatchesRequest.setTransferSceneId("1005");
+// transferBatchesRequest.setNotifyUrl(withdrawalNotifyUrl);
+// String serialNo = null;
+// //加密真实姓名
+// try {
+//
+// X509Certificate certificate = weChatPayConfig2.getVerifier().getValidCertificate();
+// serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
+// //这里放真实姓名,目前你的数据库表没有这个字段
+// String encryptOAEP = RsaCryptoUtil.encryptOAEP(hanHaiWater.getName(), certificate);
+// transferBatchesRequest.setUserName(encryptOAEP);
+// } catch (Exception e) {
+// log.info("真实姓名加密失败");
+// e.printStackTrace();
+// throw new JeecgBootException("真实姓名加密失败");
+// }
+// transferBatchesRequest.setTransferAmount(MoneyUtil.Yuan2Fen(hanHaiWater.getPrice().doubleValue()));
+// List transferBatchesDetailsRequests = new ArrayList<>();
+// TransferBatchesDetailsRequest transferBatchesDetailsRequest = new TransferBatchesDetailsRequest();
+// transferBatchesDetailsRequest.setInfoType("岗位类型");
+// transferBatchesDetailsRequest.setInfoContent("销售员");
+// TransferBatchesDetailsRequest transferBatchesDetailsRequest2 = new TransferBatchesDetailsRequest();
+// transferBatchesDetailsRequest2.setInfoType("报酬说明");
+// transferBatchesDetailsRequest2.setInfoContent("佣金报酬");
+//
+//
+// transferBatchesDetailsRequests.add(transferBatchesDetailsRequest);
+// transferBatchesDetailsRequests.add(transferBatchesDetailsRequest2);
+// transferBatchesRequest.setTransferDetailList(transferBatchesDetailsRequests);
+// String jsonString = JSONObject.toJSONString(transferBatchesRequest);
+//
+// log.info("请求参数:"+jsonString);
+//
+// String postTransBatRequest = HttpRequestUtil.postTransBatRequest(transferBatchUrl, jsonString, serialNo, wxsSerialNo, mchId, pemPath);
+// log.error("返回结果1:" + postTransBatRequest);
+// TransferBatchesResp transferBatchesResp = JSON.parseObject(postTransBatRequest, TransferBatchesResp.class);
+// log.error("返回结果2:" + transferBatchesResp);
+// if (transferBatchesResp == null || org.apache.commons.lang3.StringUtils.isBlank(transferBatchesResp.getOutBillNo()) || transferBatchesResp.getState().equals("FAIL")) {
+// throw new JeecgBootException("打款失败");
+// }
+//
+// //这里写自己的逻辑,比如改变这条记录的状态,变成提现成功
+//
+// CityMoneyLog hanHaiWater1 = new CityMoneyLog();
+// hanHaiWater1.setId(hanHaiWater.getId());
+// hanHaiWater1.setState(1);
+// hanHaiWater1.setPackageInfo(transferBatchesResp.getPackageInfo());
+// hanHaiWater1.setOutBatchNo(transferBatchesResp.getOutBillNo());
+// hanHaiWater1.setBatchId(transferBatchesResp.getTransferBillNo());
+// cityMoneyLogService.updateById(hanHaiWater1);
+//
+//
+// return Result.OK("提现成功");
+// }
+//
/**
* 通过id删除
*
diff --git a/jeecg-boot-module-system/pom.xml b/jeecg-boot-module-system/pom.xml
index 22295d6..635de10 100644
--- a/jeecg-boot-module-system/pom.xml
+++ b/jeecg-boot-module-system/pom.xml
@@ -82,6 +82,7 @@
+ yaodu-api
org.springframework.boot
diff --git a/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/service/CashoutService.java b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/service/CashoutService.java
new file mode 100644
index 0000000..f6e8903
--- /dev/null
+++ b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/service/CashoutService.java
@@ -0,0 +1,20 @@
+package org.jeecg.modules.api.service;
+
+import io.swagger.annotations.ApiOperation;
+import org.jeecg.common.api.vo.Result;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+
+public interface CashoutService {
+ //会员中心-开通会员
+ @ApiOperation(value="测试提现-提现", notes="测试提现-提现")
+ @RequestMapping(value = "/cashout", method = {RequestMethod.POST})
+ public Result> cashout();
+
+ //开通会员支付回调
+ //支付回调
+ @PostMapping("/cashoutNotify")
+ public Object cashoutNotify(@RequestBody String requestBody);
+}
diff --git a/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/service/impl/CashoutServiceImpl.java b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/service/impl/CashoutServiceImpl.java
new file mode 100644
index 0000000..26fae4c
--- /dev/null
+++ b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/service/impl/CashoutServiceImpl.java
@@ -0,0 +1,29 @@
+package org.jeecg.modules.api.service.impl;
+
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.modules.api.service.CashoutService;
+import org.jeecg.modules.transferTest.TransferToUser;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class CashoutServiceImpl implements CashoutService {
+ @Override
+ public Result> cashout() {
+ String mchid = "1673516176";
+ String certiticateSerialNo ="525CDBD76E640EFB008288572C97D2715F3F18B2";
+ String privateKeyFilePath = "jeecg-boot-module-system/src/main/resources/apiclient_key_yaodu.pem";
+ String wechatPayPublicKeyId = "PUB_KEY_ID_0116735161762025040100448900000949";
+ String wechatPayPublicKeyFilePaht = "jeecg-boot-module-system/src/main/resources/pub_key_yaodu.pem";
+ TransferToUser transferToUser = new TransferToUser(mchid, certiticateSerialNo, privateKeyFilePath, wechatPayPublicKeyId, wechatPayPublicKeyFilePaht );
+ transferToUser.run();
+ return Result.OK("测试提现结束");
+ }
+
+ @Override
+ public Object cashoutNotify(String requestBody) {
+ System.out.println("测试提现回调");
+ return Result.OK("测试回调");
+ }
+}
diff --git a/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/yaoduapi/CashoutController.java b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/yaoduapi/CashoutController.java
new file mode 100644
index 0000000..4b55af1
--- /dev/null
+++ b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/api/yaoduapi/CashoutController.java
@@ -0,0 +1,35 @@
+package org.jeecg.modules.api.yaoduapi;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.api.vo.Result;
+import org.jeecg.modules.api.service.CashoutService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+@Api(tags="测试提现接口-提现")
+@RestController
+@RequestMapping("/cashout")
+@Slf4j
+public class CashoutController {
+ /******************************************************************************************************************/
+ //会员信息
+ @Resource
+ private CashoutService cashoutService;
+ /******************************************************************************************************************/
+ //会员中心-开通会员
+ @ApiOperation(value="测试提现-提现", notes="测试提现-提现")
+ @RequestMapping(value = "/cashout", method = {RequestMethod.POST})
+ public Result> cashout(){
+ return cashoutService.cashout();
+ }
+
+ //提现回调
+ @PostMapping("/cashoutNotify")
+ public Object cashoutNotify(@RequestBody String requestBody){
+ return cashoutService.cashoutNotify(requestBody);
+ }
+
+}
diff --git a/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/TransferToUser.java b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/TransferToUser.java
new file mode 100644
index 0000000..aba921d
--- /dev/null
+++ b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/TransferToUser.java
@@ -0,0 +1,257 @@
+package org.jeecg.modules.transferTest;
+
+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.ArrayList;
+import java.util.List;
+
+/**
+ * 发起转账
+ */
+public class TransferToUser {
+ private static String HOST = "https://api.mch.weixin.qq.com";
+ private static String METHOD = "POST";
+ private static String PATH = "/v3/fund-app/mch-transfer/transfer-bills";
+
+ public static void main(String[] args) {
+ // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756
+// String mchid = "1712378227";
+// String certiticateSerialNo ="33E9FE8076531A7C7AD401DC34E053DBD7C28E22";
+// String privateKeyFilePath = "jeecg-boot-module-system/src/main/resources/apiclient_key.pem";
+// String wechatPayPublicKeyId = "PUB_KEY_ID_0117123782272025033100396400002931";
+// String wechatPayPublicKeyFilePaht = "jeecg-boot-module-system/src/main/resources/pub_key.pem";
+ String mchid = "1673516176";
+ String certiticateSerialNo ="525CDBD76E640EFB008288572C97D2715F3F18B2";
+ String privateKeyFilePath = "jeecg-boot-module-system/src/main/resources/apiclient_key_yaodu.pem";
+ String wechatPayPublicKeyId = "PUB_KEY_ID_0116735161762025040100448900000949";
+ String wechatPayPublicKeyFilePaht = "jeecg-boot-module-system/src/main/resources/pub_key_yaodu.pem";
+ TransferToUser client = new TransferToUser(
+ mchid, // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
+ certiticateSerialNo, // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
+ privateKeyFilePath, // 商户API证书私钥文件路径,本地文件路径
+ wechatPayPublicKeyId, // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
+ wechatPayPublicKeyFilePaht // 微信支付公钥文件路径,本地文件路径
+ );
+
+ String appid = "wxa4d29e67e8a58d38";
+ String openid = "oFzrW4migndUepy7zYgYO2YoZ5to";
+ String notifyUrl = "https://admin.hhlm1688.com/api/hello/";
+ TransferToUserRequest request = new TransferToUserRequest();
+ request.appid = appid;
+ request.outBillNo = "plfk2020042013";
+ request.transferSceneId = "1000";
+ request.openid = openid;
+ request.userName = client.encrypt("唐斌");
+ request.transferAmount = 400L;
+ request.transferRemark = "新会员开通有礼";
+ request.notifyUrl = notifyUrl;
+ request.userRecvPerception = "现金奖励";
+ request.transferSceneReportInfos = new ArrayList<>();
+ {
+ TransferSceneReportInfo item0 = new TransferSceneReportInfo();
+ item0.infoType = "活动名称";
+ item0.infoContent = "新会员有礼";
+ request.transferSceneReportInfos.add(item0);
+ TransferSceneReportInfo item1 = new TransferSceneReportInfo();
+ item1.infoType = "奖励说明";
+ item1.infoContent = "注册会员抽奖一等奖";
+ request.transferSceneReportInfos.add(item1);
+
+
+
+ };
+ try {
+ TransferToUserResponse response = client.run(request);
+
+ // TODO: 请求成功,继续业务逻辑
+ System.out.println(response);
+ } catch (WXPayUtility.ApiException e) {
+ // TODO: 请求失败,根据状态码执行不同的逻辑
+ e.printStackTrace();
+ }
+ }
+
+ public void run(){
+ String mchid = "1673516176";
+ String certiticateSerialNo ="525CDBD76E640EFB008288572C97D2715F3F18B2";
+ String privateKeyFilePath = "jeecg-boot-module-system/src/main/resources/apiclient_key_yaodu.pem";
+ String wechatPayPublicKeyId = "PUB_KEY_ID_0116735161762025040100448900000949";
+ String wechatPayPublicKeyFilePaht = "jeecg-boot-module-system/src/main/resources/pub_key_yaodu.pem";
+ TransferToUser client = new TransferToUser(
+ mchid, // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
+ certiticateSerialNo, // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
+ privateKeyFilePath, // 商户API证书私钥文件路径,本地文件路径
+ wechatPayPublicKeyId, // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
+ wechatPayPublicKeyFilePaht // 微信支付公钥文件路径,本地文件路径
+ );
+
+ String appid = "wxa4d29e67e8a58d38";
+ String openid = "oFzrW4migndUepy7zYgYO2YoZ5to";
+ String notifyUrl = "https://admin.hhlm1688.com/api/cashout/cashout/";
+ TransferToUserRequest request = new TransferToUserRequest();
+ request.appid = appid;
+ request.outBillNo = "plfk2020042013";
+ request.transferSceneId = "1000";
+ request.openid = openid;
+ request.userName = client.encrypt("唐斌");
+ request.transferAmount = 400L;
+ request.transferRemark = "新会员开通有礼";
+ request.notifyUrl = notifyUrl;
+ request.userRecvPerception = "现金奖励";
+ request.transferSceneReportInfos = new ArrayList<>();
+ {
+ TransferSceneReportInfo item0 = new TransferSceneReportInfo();
+ item0.infoType = "活动名称";
+ item0.infoContent = "新会员有礼";
+ request.transferSceneReportInfos.add(item0);
+ TransferSceneReportInfo item1 = new TransferSceneReportInfo();
+ item1.infoType = "奖励说明";
+ item1.infoContent = "注册会员抽奖一等奖";
+ request.transferSceneReportInfos.add(item1);
+
+
+
+ };
+ try {
+ TransferToUserResponse response = client.run(request);
+
+ // TODO: 请求成功,继续业务逻辑
+ System.out.println(response);
+ } catch (WXPayUtility.ApiException e) {
+ // TODO: 请求失败,根据状态码执行不同的逻辑
+ e.printStackTrace();
+ }
+ }
+
+ public TransferToUserResponse run(TransferToUserRequest 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, TransferToUserResponse.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 TransferToUser(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 String encrypt(String plainText) {
+ return WXPayUtility.encrypt(this.wechatPayPublicKey, plainText);
+ }
+
+ public static class TransferToUserResponse {
+ @SerializedName("out_bill_no")
+ public String outBillNo;
+
+ @SerializedName("transfer_bill_no")
+ public String transferBillNo;
+
+ @SerializedName("create_time")
+ public String createTime;
+
+ @SerializedName("state")
+ public TransferBillStatus state;
+
+ @SerializedName("package_info")
+ public String packageInfo;
+ }
+
+ public enum TransferBillStatus {
+ @SerializedName("ACCEPTED")
+ ACCEPTED,
+ @SerializedName("PROCESSING")
+ PROCESSING,
+ @SerializedName("WAIT_USER_CONFIRM")
+ WAIT_USER_CONFIRM,
+ @SerializedName("TRANSFERING")
+ TRANSFERING,
+ @SerializedName("SUCCESS")
+ SUCCESS,
+ @SerializedName("FAIL")
+ FAIL,
+ @SerializedName("CANCELING")
+ CANCELING,
+ @SerializedName("CANCELLED")
+ CANCELLED
+ }
+
+ public static class TransferSceneReportInfo {
+ @SerializedName("info_type")
+ public String infoType;
+
+ @SerializedName("info_content")
+ public String infoContent;
+ }
+
+ public static class TransferToUserRequest {
+ @SerializedName("appid")
+ public String appid;
+
+ @SerializedName("out_bill_no")
+ public String outBillNo;
+
+ @SerializedName("transfer_scene_id")
+ public String transferSceneId;
+
+ @SerializedName("openid")
+ public String openid;
+
+ @SerializedName("user_name")
+ public String userName;
+
+ @SerializedName("transfer_amount")
+ public Long transferAmount;
+
+ @SerializedName("transfer_remark")
+ public String transferRemark;
+
+ @SerializedName("notify_url")
+ public String notifyUrl;
+
+ @SerializedName("user_recv_perception")
+ public String userRecvPerception;
+
+ @SerializedName("transfer_scene_report_infos")
+ public List transferSceneReportInfos;
+ }
+
+}
diff --git a/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/WXPayUtility.java b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/WXPayUtility.java
new file mode 100644
index 0000000..30c5374
--- /dev/null
+++ b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/WXPayUtility.java
@@ -0,0 +1,381 @@
+package org.jeecg.modules.transferTest;
+
+import com.google.gson.*;
+import com.google.gson.annotations.Expose;
+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 fromJson(String json, Class 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 params) {
+ if (params == null || params.isEmpty()) {
+ return "";
+ }
+
+ int index = 0;
+ StringBuilder result = new StringBuilder();
+ for (Map.Entry 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 {
+ public final int statusCode;
+ public final String body;
+ public final Headers headers;
+ public ApiException(int statusCode, String body, Headers headers) {
+ super(String.format("微信支付API访问失败,StatusCode: %s, Body: %s", statusCode, body));
+ this.statusCode = statusCode;
+ this.body = body;
+ this.headers = headers;
+ }
+ }
+}
diff --git a/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/test.java b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/test.java
new file mode 100644
index 0000000..8dcae59
--- /dev/null
+++ b/jeecg-boot-module-system/src/main/java/org/jeecg/modules/transferTest/test.java
@@ -0,0 +1,13 @@
+package org.jeecg.modules.transferTest;
+
+public class test {
+ public static void main(String[] args) {
+ String mchid = "1673516176";
+ String certiticateSerialNo ="525CDBD76E640EFB008288572C97D2715F3F18B2";
+ String privateKeyFilePath = "jeecg-boot-module-system/src/main/resources/apiclient_key_yaodu.pem";
+ String wechatPayPublicKeyId = "PUB_KEY_ID_0116735161762025040100448900000949";
+ String wechatPayPublicKeyFilePaht = "jeecg-boot-module-system/src/main/resources/pub_key_yaodu.pem";
+ TransferToUser transferToUser = new TransferToUser(mchid, certiticateSerialNo, privateKeyFilePath, wechatPayPublicKeyId, wechatPayPublicKeyFilePaht );
+ transferToUser.run();
+ }
+}
diff --git a/jeecg-boot-module-system/src/main/resources/apiclient_key_yaodu.pem b/jeecg-boot-module-system/src/main/resources/apiclient_key_yaodu.pem
new file mode 100644
index 0000000..034d3f3
--- /dev/null
+++ b/jeecg-boot-module-system/src/main/resources/apiclient_key_yaodu.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDFHyPaZuXY6GCd
+mC/CId2jMQ3Iun2cZycw2LxdWLNwtKGYKVk+GWa4upm60Zl+qrNpLam3qTD5Oyy3
+T8Mz9EWHwBYr2xTSKINRbIjabdVyV0/H2RLitwRg3XyHQPxYFcvI9t9F2GHELsAA
+qGmFp0+PiCVMzT+nB3sU+yLaNyiMC8rcprjh4tBV857K3Y5rzljbJsxa9WVzS0EM
+GQRwPV2LaWPApcGZIKJLRAUa8yGMKMINyb9iJjdA1/4vD8tLWmvFhXtTYmrWmFhu
+5QsyKDClOek2gqGgxDL9Hvvx5PYR59Mg57kyz0vSKhYa0MKgsFLuu7QJOFXk2bJo
+DJymcLypAgMBAAECggEBAL+1626riHsOdXiP3FLYEPB38sn35dZI1GrDP18ht1Kz
+uj18aVjl52tdv8lbtAbnCZoPWPJQUFr0XCbkIhrTRRQjkuyQI43I7P4xql+VVnPf
+yq24xo9MI6v5fPUmFMWuXQVUZA1PxrXAKef54raj49LaPDyXmYJe2iurm1fTMVIR
+Jba1rBi0hp5W1fMCiCPk1SMfANTE0UOTYdSRhNVSgf51aJHFI+FR8iSmf8rEv+B0
+s8uDXvxTGgbEaZsO7wcjQfsbQixoqEmfTzEoBTHkQ1npigG32gsz8QO6P4dhJRX7
+WJ3N8x0vgJqxCLeRLEb6c8ZJ62SuEi6PtETVWXujeCECgYEA/gP8fyp5lBKF/weC
+UzMCEAJzwTKjmC0IOWnoIwQTSKZTCKHmz5LMNYn3nTf2d8LpbB9GYmBhUdiO/88f
+AZ/vbuskkoH0rmOgelVx13tDaVuNOOVmtKmKvzYOxiGeL6ooX2cwfRnVOMIPTBFM
+G1lAzxeowTy5uLKvuuMGBVJj7MUCgYEAxqleqjcp3+4QoE9Z/2hEt6sYnI/I1T/h
+kbgTO8wQSGG8ambl/qJu+9ON/Ag1jK2amrNKP1vXbTQsjkUby6U5MgVwGDB4bNN1
++wgwxMXJ74QJtbnXp7XBqC96S6Pyw+cLEjvWk0xJ14Os2oCO1OvZibu5KHrsPECn
+3OmddJkjFpUCgYEAoiwHW2TRxCBjXiP8J4QMUA5QusrKuVAezRD5fMmQSjSuFHfQ
+9Tsilxfjd4OQHnvZLQd2lz4zQ96/xUAF6rKiWa1UZxkDDwdaIGBG0yzGKBCkQ+vp
+u3P2ugcYPZSe+o1nQymNQoFoqNj0jTsJ3PgJsW3Idr5/UBT8rpNcd69XToUCgYB8
+AQTCIyTUTnm6V03KC3+5VedK8sVdtz5KAyieTsZrJ/bAQ/KUezfjoS4jf8xNP6Ad
+qIRUADP8SnD1bVXoS/3jp1lNABRreaNPStGGQh/GjhixgouGeAGlxd0EkhXbCsDy
+ZL+PujLtf5fJ3C1L4twrCS6OggwroAAn+Pr76Qrp8QKBgQD9uH47NRToKkaTPflY
+mVlbLKCWnAjoqlxHrANjM6lOeC4SfAYaZRIpCK9B7LFR7eeqIb0Y/RjfwtLdFKQF
+1hVCnLYTsEJ5gG64qqInVb4e0FCF/2w1uoBGwB2pUWjd0DUXH3yHc3LVCBhxOkRa
+oULtLnG34V6fFclMh3Stk/pMVg==
+-----END PRIVATE KEY-----
diff --git a/jeecg-boot-module-system/src/main/resources/pub_key_yaodu.pem b/jeecg-boot-module-system/src/main/resources/pub_key_yaodu.pem
new file mode 100644
index 0000000..abafafe
--- /dev/null
+++ b/jeecg-boot-module-system/src/main/resources/pub_key_yaodu.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxK2wqQooeGKkAQchZdwK
+U3qFiPSyf83l7USGGAdEhb78iq0Dvi7RfgdHuqSVBv0fnSQJicNO+s10ofi9waxl
+SCPnuO8t9MeOuS4IpZ54VWY/9pJ5A2Z0x49L0djoFWStFCpKzsg2fWBvc/7kYVFr
+nq/jFyRho8/GZtxL9RLZjWLyfnpe+erxSNFEnQLoW6LC4D5L0w9oiHboHmN9Igzc
+uB6pIuMwccImX1xPeu/jx2QMYrxAW/2bW3e6z4ojQWqhRtFq55INnXLV8VXE7rwI
+1L0RB4R39JOsKroVE/g7SiHRbGEem2BbNaAFhjCMuZzpWSCFQkTlxLJMxxTt9j0S
++wIDAQAB
+-----END PUBLIC KEY-----