diff --git a/cash-dependencies/pom.xml b/cash-dependencies/pom.xml index b5163a004..cbbd275f7 100644 --- a/cash-dependencies/pom.xml +++ b/cash-dependencies/pom.xml @@ -42,6 +42,7 @@ 2.5.1 2.9.10 4.1.128.Final + 0.2.17 @@ -268,6 +269,12 @@ netty-codec-mqtt ${netty.version} + + + com.github.wechatpay-apiv3 + wechatpay-java + ${wechatpay.version} + diff --git a/cash-sdk/aggregation-pay/pom.xml b/cash-sdk/aggregation-pay/pom.xml new file mode 100644 index 000000000..3df1fb396 --- /dev/null +++ b/cash-sdk/aggregation-pay/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + com.czg + cash-sdk + 1.0.0 + + + aggregation-pay + jar + 聚合支付 + + + + 21 + 21 + UTF-8 + + + + + com.github.wechatpay-apiv3 + wechatpay-java + + + + \ No newline at end of file diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatConfig.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatConfig.java new file mode 100644 index 000000000..ace7aaab8 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatConfig.java @@ -0,0 +1,47 @@ +package com.czg.wechat; + +import com.czg.wechat.dto.config.WechatPayConfigDto; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAPublicKeyConfig; +import com.wechat.pay.java.service.file.FileUploadService; + +/** + * 微信支付 配置 + * + * @author yjjie + * @date 2025/12/26 09:33 + */ +public class WechatConfig { + + /** + * 获取微信支付配置 + * @param dto 微信支付配置 + * @return 微信支付配置 + */ + public static Config getConfig(WechatPayConfigDto dto) { + return new RSAPublicKeyConfig.Builder() + .merchantId(dto.getMerchantId()) + .privateKey(dto.getPrivateKey()) + .publicKey(dto.getPublicKey()) + .publicKeyId(dto.getPublicKeyId()) + .merchantSerialNumber(dto.getSerialNumber()) + .apiV3Key(dto.getApiV3Key()) + .build(); + } + + public static FileUploadService getFileUploadService(Config config) { + return new FileUploadService.Builder() + .config(config) + .build(); + } + + /** + * 获取文件上传服务 + * @param dto 微信支付配置 + * @return 文件上传服务 + */ + public static FileUploadService getFileUploadService(WechatPayConfigDto dto) { + return getFileUploadService(getConfig(dto)); + } + +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatEncrypt.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatEncrypt.java new file mode 100644 index 000000000..82c2cd179 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatEncrypt.java @@ -0,0 +1,47 @@ +package com.czg.wechat; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * 微信支付加解密 + * @author yjjie + * @date 2025/12/26 10:58 + */ +public class WechatEncrypt { + + public static String getFileBytesSha256(byte[] input) throws NoSuchAlgorithmException { + return bytesToHex(calculateSha256(input)); + } + + /** + * 核心方法:计算字节数组的 SHA-256 哈希值 + * @param input 待计算的字节数组 + * @return SHA-256 哈希值(32 字节的字节数组) + */ + private static byte[] calculateSha256(byte[] input) throws NoSuchAlgorithmException { + // 获取 SHA-256 消息摘要实例(Java 21 完全兼容) + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + // 计算哈希值 + return digest.digest(input); + } + + /** + * 辅助方法:将字节数组转换为十六进制字符串(SHA-256 结果常用格式) + * @param bytes 待转换的字节数组 + * @return 十六进制字符串(SHA-256 对应 64 位字符串) + */ + private static String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + // Java 21 可使用 var 简化声明 + for (var b : bytes) { + // 将字节转换为两位十六进制数,不足补 0 + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatEntryManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatEntryManager.java new file mode 100644 index 000000000..162a2ba72 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatEntryManager.java @@ -0,0 +1,157 @@ +package com.czg.wechat; + +import com.alibaba.fastjson2.JSONObject; +import com.czg.wechat.dto.config.WechatPayConfigDto; +import com.wechat.pay.java.service.file.FileUploadService; +import com.wechat.pay.java.service.file.model.FileUploadResponse; +import lombok.extern.slf4j.Slf4j; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +/** + * 微信支付进件 管理 + * + * @author yjjie + * @date 2025/12/26 10:57 + */ +@Slf4j +public class WechatEntryManager { + + public static String uploadImage(WechatPayConfigDto configDto, String url) { + // 校验入参 + if (configDto == null || url == null || url.trim().isEmpty()) { + log.error("上传图片失败:配置或URL参数为空"); + return ""; + } + + FileUploadService service = WechatConfig.getFileUploadService(configDto); + + try { + // 获取图片字节数组 + byte[] bytes = downloadImage(url); + if (bytes.length == 0) { + log.error("下载的图片内容为空,URL:{}", url); + return ""; + } + + // 2. 计算SHA256 + String sha256Hex = WechatEncrypt.getFileBytesSha256(bytes); + + // 3. 构造元数据(从URL提取文件名,或自定义) + JSONObject meta = new JSONObject(); + meta.put("sha256", sha256Hex); + // 从URL提取文件名,若提取失败则使用默认名 + String fileName = extractFileNameFromUrl(url); + meta.put("filename", fileName); + + // 4. 上传图片到微信接口 + FileUploadResponse uploadResponse = service.uploadImage( + configDto.getDomain() + "/v3/merchant/media/upload", + meta.toJSONString(), + fileName, + bytes + ); + + return uploadResponse.getMediaId(); + } catch (Exception e) { + log.error("微信上传图片报错,URL:{},错误信息:{}", url, e.getMessage(), e); + } + return ""; + } + + public static byte[] downloadImage(String url) throws Exception { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); + + if (response.statusCode() != 200) { + throw new RuntimeException("Failed to download image, status code: " + response.statusCode()); + } + + return response.body(); + } + + /** + * 从URL中提取文件名 + * + * @param url 图片URL + * @return 提取的文件名,失败则返回默认名 + */ + private static String extractFileNameFromUrl(String url) { + try { + if (url.contains("/")) { + String fileName = url.substring(url.lastIndexOf("/") + 1); + // 如果文件名包含参数,截取?之前的部分 + if (fileName.contains("?")) { + fileName = fileName.substring(0, fileName.indexOf("?")); + } + // 如果提取的文件名有效,直接返回 + if (fileName.contains(".")) { + return fileName; + } + } + } catch (Exception e) { + log.warn("提取文件名失败,使用默认文件名", e); + } + // 默认文件名 + return "upload_" + System.currentTimeMillis() + ".png"; + } + + public static void main(String[] args) { + WechatPayConfigDto dto = new WechatPayConfigDto() + .setMerchantId("1643779408") + .setApiV3Key("a92baac5eb7a36ed8ec198113e769a03") + .setSerialNumber("4DE9BAC2EA584C3F274F694C9753CA814C4E9BF4") + .setPublicKey(""" + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbeWXEEjBaYtw2OyM+SC + aCEMbryRXi4duKxx3vYG4rUVix+d5/Jz7Khev4Upml9zC+Xxvv/G9bHWAUyolzqD + wefahGurIxr43r4GJVnQ4i5G6BbBVw5d4Vuz0y/9Zd14zmc/EmBpT0Ml26H7tOZl + n1LSbQ4xNFkrRKrNEcExBLxkCd+K5K2TREZznywIi0izbHImvuzM8IneuR51FiqK + pdFnAjTwb126EIj6ECkL6KLCl8RWqpfiX8SFiolSQLs1/w79O0sIUk96X2zWpnoW + rTDFatPif/VEKl3y2dTlxxDxoZtVi48yTDW00OMzVl5D67oX3FVK0KsjHJSCfGlZ + 6wIDAQAB + -----END PUBLIC KEY-----""") + .setPrivateKey(""" + -----BEGIN PRIVATE KEY----- + MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDqnAZhTxT572fo + 6wvSr8Rt0vRXg+EFKC6UvUiNE24oocQU5fjedX9KL/+fmoqay/xqIxvxvCNFTs4J + zlMqavSl6bMWCpjvf/Ry82JmN1v2kJEO4lgP8BsEiqlUpObCH8BMAVUOn1j+9q4Q + uZJJcbtRvec2fNweDM8EJp4B7RlUdDbHm86lfcDVp8iini7VjDp6D7aHT+C8A8OT + ugxQIquDec778wVd9r2Sv3+t6rAzFs+n+Zu++2xtFEPhO5N0wjrLHaukl+9crU+1 + lktjDzcPd07SwGZ+A+3BTmW3UCramI3506e/3MWBECB7ge+gM4URAV0nJJCLH/Im + WgEvPMr5AgMBAAECggEBAKv+wraoMWqiVv1tA/froAgbtcJLDranJK8qrXuvmPz0 + yzm+91qvrSgIVFEADUk67swo/R2Vng37nhWWS2Y3jy/rSr2H+2Lp3Z5ATA0/3I3A + onfU/FaC4mvL9CP32KzMdj/CYkccDzSsSCQ+x75MQNXGcTGDDCSDo2kZnpEu73j3 + aqvO1jbqTGWigRfjOIaIhStjQIT8nEm/3mJ4f5dM9M6FMz33mhax8EahSgvdahYB + t45iaGOWw81OJhmry47EvpwjXBl7jtO2HX3LiLbq5Ebcwu1zqDz5NM7ttnnGAqWC + 6y7JN5Vt4wPYrCydiUDe7dj0looffr2yw6MkNfYjLGECgYEA+FAvbEInQEi9YguS + CQtLHngqvYeai66tvyykog9o38KHnPGx2Myf+rn/Hcp7KNRfjd5G34CCNg7KLnrx + YIYh6+2bY3jirzdYUxuNKGbvM4gky/6M/P9zHF/uALKOE02yArdqemf4UxUvrYCc + JdXsAMqvbpdvW1aGgNRB32YCkG0CgYEA8d89vawsCjNCEUh0VWTMoBLFoex3qBMZ + rfzYQeBo6bDCRlAlUVzl+ouBUxSYxP/U8rzeNaRzGUwRLmlGMjyIr58FBlHsg2cR + DlsX3HVCUjpS6sgPXOqakdiLfhMcHZAspislSyVfeS3TbUWiA45+5PuNUq+EZYwl + ESsB1+yfRT0CgYEA0Ct49ksnWM8iZbXJgeeD3FFlk2rBd2TDqEem5W4Bv8T3p+0/ + 6b7yR2HyrGj5gys3yFmWFP1JLESN3xWWkhMhEQcrg+LuN3Iwi8vHNR3GXu892f7W + 96q4OAt8Hf2S+j/igkB99YyANDbIt63gOh/zMF67X/14j5wkOpC3gK+maqkCgYEA + s7nIrPoUt3ejLiiCmTmPe5q3VDzcJP4cZNau8zSHgK6hjZHcSPsYwPWMoWl6o1fe + qoiBLacHB9MoKS58xLOKdcVZ/Ho/ntylJd+2eVCAeY1xM5h5IfgJ5znbXVFh4O3S + 357L1Wzt5qOQqW/GlZH65LevKbPWU4axvHISqpnfN5kCgYEAqiqLuAPu84VSsqsE + GFh25t+1f0JY1sNfilE3/t9AdQeeCFv/5z9KB1kMt3a5ZFeVonsFIvCyaOJjhSkj + 4HCB94vWS7NuN5G9r874lMaPbZYQGwrcVaf265tN7cYYr3gUx1qB6+u+fh/kcft1 + BBmTzhb0zp5k8ngwAkA1g/LK204= + -----END PRIVATE KEY-----""") + .setPublicKeyId("PUB_KEY_ID_0116437794082025111000382377001000") + .setDomain("https://api.mch.weixin.qq.com"); + + String string = uploadImage(dto, "https://czg-qr-order.oss-cn-beijing.aliyuncs.com/indexs/shuangbackground.png"); + log.info("图片上传成功:{}", string); + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatPayManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatPayManager.java new file mode 100644 index 000000000..897ad8fc3 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/WechatPayManager.java @@ -0,0 +1,11 @@ +package com.czg.wechat; + +/** + * @author yjjie + * @date 2025/12/26 09:15 + */ +public class WechatPayManager { + + public static void main(String[] args) { + } +} diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/dto/config/WechatPayConfigDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/dto/config/WechatPayConfigDto.java new file mode 100644 index 000000000..a0cb78bc4 --- /dev/null +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/wechat/dto/config/WechatPayConfigDto.java @@ -0,0 +1,49 @@ +package com.czg.wechat.dto.config; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * @author yjjie + * @date 2025/12/26 09:21 + */ +@Data +@Accessors(chain = true) +public class WechatPayConfigDto { + + /** + * 商户号 + */ + private String merchantId; + + /** + * Api V3密钥 + */ + private String apiV3Key; + + /** + * 商户证书序列号 + */ + private String serialNumber; + + /** + * 商户私钥 + */ + private String privateKey; + + /** + * 商户公钥 + */ + private String publicKey; + + /** + * 商户公钥 ID + */ + private String publicKeyId; + + /** + * 微信支付域名 + * + */ + private String domain; +} diff --git a/cash-sdk/pom.xml b/cash-sdk/pom.xml index 55ee0e62b..b92f22d79 100644 --- a/cash-sdk/pom.xml +++ b/cash-sdk/pom.xml @@ -13,6 +13,7 @@ 第三方内容 czg-pay + aggregation-pay cash-sdk