|
|
@@ -144,21 +144,51 @@ public class WxPayConfig {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 生成 JSAPI 支付参数
|
|
|
+ * 生成 JSAPI 支付参数(每次直接从数据库读取最新配置)
|
|
|
*/
|
|
|
public Map<String, String> createJsapiPayParams(String openid, BigDecimal payPrice, String remark, String orderCode) {
|
|
|
- if (jsapiService == null) {
|
|
|
- throw new RuntimeException("微信支付服务未初始化,请检查配置");
|
|
|
- }
|
|
|
-
|
|
|
try {
|
|
|
+ // 每次从数据库读取最新配置
|
|
|
+ PaymentConfig dbConfig = paymentConfigService.getEnabledWechatConfig();
|
|
|
+ if (dbConfig == null) {
|
|
|
+ throw new RuntimeException("微信支付配置不存在,请前往【系统配置 → 微信支付配置】完成配置");
|
|
|
+ }
|
|
|
+
|
|
|
+ String dbAppId = dbConfig.getAppId();
|
|
|
+ String dbMchId = dbConfig.getMchId();
|
|
|
+ String dbApiV3Key = dbConfig.getApiV3Key();
|
|
|
+ String dbSerialNo = dbConfig.getSerialNo();
|
|
|
+ String dbNotifyUrl = dbConfig.getPayNotifyUrl();
|
|
|
+ // 直接从数据库读取已存储的私钥文本内容(上传时已写入 private_key 字段)
|
|
|
+ String pemContent = dbConfig.getPrivateKey();
|
|
|
+
|
|
|
+ if (StringUtils.isBlank(dbMchId) || StringUtils.isBlank(dbApiV3Key) || StringUtils.isBlank(dbSerialNo)) {
|
|
|
+ throw new RuntimeException("微信支付配置不完整,请前往【系统配置 → 微信支付配置】补全商户号、API密钥、证书序列号");
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(pemContent) || !pemContent.contains("-----BEGIN")) {
|
|
|
+ throw new RuntimeException("商户私钥未配置或格式无效,请前往【系统配置 → 微信支付配置】重新上传私钥文件");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 动态构建微信支付服务(SDK 直接接收 PEM 字符串)
|
|
|
+ Config config = new RSAAutoCertificateConfig.Builder()
|
|
|
+ .merchantId(dbMchId)
|
|
|
+ .privateKey(pemContent)
|
|
|
+ .merchantSerialNumber(dbSerialNo)
|
|
|
+ .apiV3Key(dbApiV3Key)
|
|
|
+ .build();
|
|
|
+ JsapiService currentJsapiService = new JsapiService.Builder().config(config).build();
|
|
|
+
|
|
|
+ // 解析私钥对象(用于签名),兼容 PKCS#8 和 PKCS#1 格式
|
|
|
+ PrivateKey currentPrivateKey = parsePrivateKey(pemContent);
|
|
|
+
|
|
|
+
|
|
|
// 构建预支付请求
|
|
|
PrepayRequest request = new PrepayRequest();
|
|
|
- request.setAppid(appId);
|
|
|
- request.setMchid(mchId);
|
|
|
+ request.setAppid(dbAppId);
|
|
|
+ request.setMchid(dbMchId);
|
|
|
request.setDescription(StringUtils.isEmpty(remark) ? "订单支付" : remark);
|
|
|
request.setOutTradeNo(orderCode);
|
|
|
- request.setNotifyUrl(notifyUrl);
|
|
|
+ request.setNotifyUrl(dbNotifyUrl);
|
|
|
|
|
|
// 金额设置(转换为分)
|
|
|
Amount amount = new Amount();
|
|
|
@@ -173,19 +203,49 @@ public class WxPayConfig {
|
|
|
request.setPayer(payer);
|
|
|
|
|
|
// 调用预支付接口
|
|
|
- PrepayResponse response = jsapiService.prepay(request);
|
|
|
+ PrepayResponse response = currentJsapiService.prepay(request);
|
|
|
|
|
|
// 生成前端支付参数
|
|
|
- return generatePaySign(response.getPrepayId());
|
|
|
+ return generatePaySign(response.getPrepayId(), dbAppId, dbMchId, currentPrivateKey);
|
|
|
+ } catch (RuntimeException e) {
|
|
|
+ throw e;
|
|
|
} catch (Exception e) {
|
|
|
throw new RuntimeException("生成JSAPI支付参数失败", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 从 PEM 字符串解析私钥,兼容 PKCS#8(BEGIN PRIVATE KEY)和 PKCS#1(BEGIN RSA PRIVATE KEY)格式
|
|
|
+ */
|
|
|
+ private PrivateKey parsePrivateKey(String pemContent) {
|
|
|
+ try {
|
|
|
+ // 去除 PEM 头、尾及所有空白字符,得到纯 Base64 内容
|
|
|
+ String base64 = pemContent
|
|
|
+ .replaceAll("-----[^-]+-----", "")
|
|
|
+ .replaceAll("\\s", "");
|
|
|
+ byte[] decoded = java.util.Base64.getDecoder().decode(base64);
|
|
|
+
|
|
|
+ // 优先尝试 PKCS#8 格式(BEGIN PRIVATE KEY,微信支付标准格式)
|
|
|
+ try {
|
|
|
+ java.security.spec.PKCS8EncodedKeySpec spec = new java.security.spec.PKCS8EncodedKeySpec(decoded);
|
|
|
+ return java.security.KeyFactory.getInstance("RSA").generatePrivate(spec);
|
|
|
+ } catch (Exception e1) {
|
|
|
+ // 降级尝试 PKCS#1 格式(BEGIN RSA PRIVATE KEY),借助 BouncyCastle 解析
|
|
|
+ org.bouncycastle.asn1.pkcs.RSAPrivateKey rsa =
|
|
|
+ org.bouncycastle.asn1.pkcs.RSAPrivateKey.getInstance(decoded);
|
|
|
+ java.security.spec.RSAPrivateKeySpec spec = new java.security.spec.RSAPrivateKeySpec(
|
|
|
+ rsa.getModulus(), rsa.getPrivateExponent());
|
|
|
+ return java.security.KeyFactory.getInstance("RSA").generatePrivate(spec);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("解析商户私钥失败,请检查私钥文件是否为标准 PEM 格式(PKCS#8 或 PKCS#1)", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 生成支付签名
|
|
|
*/
|
|
|
- private Map<String, String> generatePaySign(String prepayId) {
|
|
|
+ private Map<String, String> generatePaySign(String prepayId, String appId, String mchId, PrivateKey currentPrivateKey) {
|
|
|
try {
|
|
|
String timeStamp = String.valueOf(Instant.now().getEpochSecond());
|
|
|
String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
|
|
|
@@ -195,7 +255,7 @@ public class WxPayConfig {
|
|
|
String signMessage = appId + "\n" + timeStamp + "\n" + nonceStr + "\n" + packageStr + "\n";
|
|
|
|
|
|
// 使用商户私钥签名
|
|
|
- String paySign = WXPayUtility.sign(signMessage, "SHA256withRSA", privateKey);
|
|
|
+ String paySign = WXPayUtility.sign(signMessage, "SHA256withRSA", currentPrivateKey);
|
|
|
|
|
|
// 返回支付参数
|
|
|
Map<String, String> payParams = new HashMap<>();
|
|
|
@@ -216,3 +276,4 @@ public class WxPayConfig {
|
|
|
public JsapiService getJsapiService() { return jsapiService; }
|
|
|
public RefundService getRefundService() { return refundService; }
|
|
|
}
|
|
|
+
|