登录
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
package cn.novalon.gym.manage.payment.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsWebFilter;
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||
|
||||
@Configuration
|
||||
public class CorsConfig {
|
||||
@Bean
|
||||
public CorsWebFilter corsWebFilter() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedOrigin("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.addAllowedHeader("*");
|
||||
config.setAllowCredentials(false);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsWebFilter(source);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
|
||||
package cn.novalon.gym.manage.payment.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "payment.huifu")
|
||||
public class HuifuPayConfig {
|
||||
|
||||
private String sysId;
|
||||
|
||||
private String productId;
|
||||
|
||||
private String huifuId;
|
||||
|
||||
private String acctId;
|
||||
|
||||
private String privateKey;
|
||||
|
||||
private String publicKey;
|
||||
|
||||
private String createUrl;
|
||||
|
||||
private String queryUrl;
|
||||
|
||||
private String refundUrl;
|
||||
|
||||
private String notifyUrl;
|
||||
|
||||
private String version = "1.0";
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
|
||||
package cn.novalon.gym.manage.payment.dto.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PaymentCreateRequest {
|
||||
|
||||
@NotNull(message = "会员ID不能为空")
|
||||
private Long memberId;
|
||||
|
||||
@NotBlank(message = "订单类型不能为空")
|
||||
@Size(max = 50, message = "订单类型长度不能超过50")
|
||||
private String orderType;
|
||||
|
||||
@NotBlank(message = "商品描述不能为空")
|
||||
@Size(max = 128, message = "商品描述长度不能超过128")
|
||||
private String goodsDesc;
|
||||
|
||||
@NotBlank(message = "交易金额不能为空")
|
||||
private String transAmt;
|
||||
|
||||
@NotBlank(message = "交易类型不能为空")
|
||||
@Size(max = 16, message = "交易类型长度不能超过16")
|
||||
private String tradeType;
|
||||
|
||||
@Size(max = 255, message = "备注长度不能超过255")
|
||||
private String remark;
|
||||
|
||||
@Size(max = 9, message = "账户号长度不能超过9")
|
||||
private String acctId;
|
||||
|
||||
private String timeExpire;
|
||||
|
||||
private String delayAcctFlag;
|
||||
|
||||
private Integer feeFlag;
|
||||
|
||||
@Size(max = 128, message = "禁用支付方式长度不能超过128")
|
||||
private String limitPayType;
|
||||
|
||||
@Size(max = 32, message = "渠道号长度不能超过32")
|
||||
private String channelNo;
|
||||
|
||||
private String payScene;
|
||||
|
||||
private String notifyUrl;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
|
||||
package cn.novalon.gym.manage.payment.dto.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PaymentCreateResponse {
|
||||
|
||||
private String orderId;
|
||||
|
||||
private String tradeType;
|
||||
|
||||
private String qrCode;
|
||||
|
||||
private String payInfo;
|
||||
|
||||
private String transAmt;
|
||||
|
||||
private String payStatus;
|
||||
|
||||
private String message;
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
|
||||
package cn.novalon.gym.manage.payment.dto.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PaymentQueryResponse {
|
||||
|
||||
private String orderId;
|
||||
|
||||
private String tradeType;
|
||||
|
||||
private String transAmt;
|
||||
|
||||
private String payStatus;
|
||||
|
||||
private String outTransId;
|
||||
|
||||
private String qrCode;
|
||||
|
||||
private String payTime;
|
||||
|
||||
private String message;
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
|
||||
package cn.novalon.gym.manage.payment.dto.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PaymentRefundResponse {
|
||||
|
||||
private String orderId;
|
||||
|
||||
private String refundAmt;
|
||||
|
||||
private String refundStatus;
|
||||
|
||||
private String message;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
|
||||
package cn.novalon.gym.manage.payment.handler;
|
||||
|
||||
import cn.novalon.gym.manage.payment.dto.request.PaymentCreateRequest;
|
||||
import cn.novalon.gym.manage.payment.service.IPaymentService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@Tag(name = "支付管理", description = "斗拱聚合支付相关操作")
|
||||
public class PaymentHandler {
|
||||
|
||||
private final IPaymentService paymentService;
|
||||
|
||||
public PaymentHandler(IPaymentService paymentService) {
|
||||
this.paymentService = paymentService;
|
||||
}
|
||||
|
||||
@Operation(summary = "创建支付", description = "创建支付订单,支持微信、支付宝等多种支付方式")
|
||||
public Mono<ServerResponse> createPayment(ServerRequest request) {
|
||||
log.info("========== PaymentHandler.createPayment 被调用 ==========");
|
||||
return request.bodyToMono(PaymentCreateRequest.class)
|
||||
.flatMap(paymentService::createPayment)
|
||||
.flatMap(response -> ServerResponse.ok().bodyValue(response))
|
||||
.onErrorResume(e -> ServerResponse.badRequest().bodyValue(buildErrorResponse("创建支付失败: " + e.getMessage())));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询支付状态", description = "根据订单号查询支付状态")
|
||||
public Mono<ServerResponse> queryPayment(ServerRequest request) {
|
||||
String orderId = request.pathVariable("orderId");
|
||||
return paymentService.queryPayment(orderId)
|
||||
.flatMap(response -> ServerResponse.ok().bodyValue(response))
|
||||
.onErrorResume(e -> ServerResponse.badRequest().bodyValue(buildErrorResponse(e.getMessage())));
|
||||
}
|
||||
|
||||
@Operation(summary = "退款", description = "根据订单号发起退款")
|
||||
public Mono<ServerResponse> refundPayment(ServerRequest request) {
|
||||
String orderId = request.pathVariable("orderId");
|
||||
return request.bodyToMono(Map.class)
|
||||
.flatMap(body -> {
|
||||
String refundAmt = String.valueOf(body.get("refundAmt"));
|
||||
return paymentService.refundPayment(orderId, refundAmt);
|
||||
})
|
||||
.flatMap(response -> ServerResponse.ok().bodyValue(response))
|
||||
.onErrorResume(e -> ServerResponse.badRequest().bodyValue(buildErrorResponse("退款失败: " + e.getMessage())));
|
||||
}
|
||||
|
||||
@Operation(summary = "支付回调", description = "支付成功回调通知")
|
||||
public Mono<ServerResponse> handleNotify(ServerRequest request) {
|
||||
return request.bodyToMono(String.class)
|
||||
.flatMap(paymentService::handleNotify)
|
||||
.then(ServerResponse.ok().bodyValue(buildSuccessResponse("处理成功")))
|
||||
.onErrorResume(e -> ServerResponse.badRequest().bodyValue(buildErrorResponse("处理失败: " + e.getMessage())));
|
||||
}
|
||||
|
||||
private Map<String, Object> buildErrorResponse(String message) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", false);
|
||||
response.put("message", message);
|
||||
return response;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildSuccessResponse(String message) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("message", message);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
|
||||
package cn.novalon.gym.manage.payment.service;
|
||||
|
||||
import cn.novalon.gym.manage.payment.dto.request.PaymentCreateRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.response.PaymentCreateResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.response.PaymentQueryResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.response.PaymentRefundResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface IPaymentService {
|
||||
|
||||
Mono<PaymentCreateResponse> createPayment(PaymentCreateRequest request);
|
||||
|
||||
Mono<PaymentQueryResponse> queryPayment(String orderId);
|
||||
|
||||
Mono<PaymentRefundResponse> refundPayment(String orderId, String refundAmt);
|
||||
|
||||
Mono<Void> handleNotify(String notifyBody);
|
||||
}
|
||||
+723
@@ -0,0 +1,723 @@
|
||||
package cn.novalon.gym.manage.payment.service.impl;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.novalon.gym.manage.payment.dto.request.PaymentCreateRequest;
|
||||
import cn.novalon.gym.manage.payment.dto.response.PaymentCreateResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.response.PaymentQueryResponse;
|
||||
import cn.novalon.gym.manage.payment.dto.response.PaymentRefundResponse;
|
||||
import cn.novalon.gym.manage.payment.service.IPaymentService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PaymentServiceImpl implements IPaymentService {
|
||||
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final Map<String, PaymentInfo> paymentCache = new ConcurrentHashMap<>();
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
// ==================== 配置 ====================
|
||||
private static final String SYS_ID = "6666000207573586"; // 代理商
|
||||
private static final String PRODUCT_ID = "XLSISV";
|
||||
private static final String HUIFU_ID = "6666000207581039"; // 商户号
|
||||
private static final String ACCT_ID = "F28308086";
|
||||
private static final String ALIPAY_CHANNEL = "hlm001";
|
||||
private static final String NOTIFY_URL = "http://localhost:8084/api/payment/notify";
|
||||
|
||||
// ===== v4 接口 =====
|
||||
private static final String CREATE_URL = "https://api.huifu.com/v4/trade/payment/create";
|
||||
private static final String QUERY_URL = "https://api.huifu.com/v4/trade/payment/query";
|
||||
private static final String REFUND_URL = "https://api.huifu.com/v4/trade/payment/refund";
|
||||
private static final String CLOSE_URL = "https://api.huifu.com/v4/trade/payment/close";
|
||||
|
||||
// ==================== 密钥 ====================
|
||||
// 商户私钥(用于签名请求)
|
||||
private static final String MERCHANT_PRIVATE_KEY =
|
||||
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcxAwrhZF+izz1gxQKOr4jnC05obIBHbl0DzHOcd4CaaDV7Kv1NRJKi33JhdDsW4JGgu16e5Rtzq1VzU5VWz5EKgGL46maOFwCkngJUTP/LC3JVf/wtJmYCm8xNE8C7GNNIqKzYEvUOEfXqagpVvVrGsQ9FpzFp2rP9hBmHY3yizVyPX/uT9S+af5TRxiaItj3SSJGgloaEMrnKOpb/EH7JwPSS0liAyT/NxPfOyyZHc22AvaAIOE5y+0PMUIKPuIdfpOrej3LVpO1Arc2hSgmdB+YIPSiBVYPXa6AuAmil9mpbtSikQJ7Uu7lX4JyTW4QxQ06rPFKnFWVKkzivAElAgMBAAECggEAJd0AJ37iTlMpDQ90xqe7hvRQxAu256gbQ9nrqLY97g0/KIw6WEZSPakFX6gvdvb/NzKmUyAIEKGLoh6tXdZk6qfOqc/6BeK47nIcBfwT9/zerjNUVvn34w4aHyNINieMMHQ+Id8PUZmqWH+Euz9ilVTosuyEPwUZulLvUQqwXzU5VnwVghURbUhDd+ecBJACWgemRun6d5241PQXNYAdH1k7cETd8GfIi3qclhhJrxi7tu5tq4YGCXQIoz7HCLim7GIvT0M+FRgSw2EOrHnAQNFeQ/vQbP71ttLoTxehL6Se9dfWrV5OI+Y/T7vR2F84Qt0iNbaxyJGir7siKDFIwQKBgQDXDzinx3/TasplM78pR/0CtuuKr1Ch02LOPrTosJ1qf1OohxQThowhOTxMsBlgYSKu9s1QRffUUXEqYXxd2B6lzDKfggwO6U2XxIcxWeNow0xoFfqcXYSg7Ga2sCr9uhdwxIdFQNF7SNBpT8ht4fJrRX6mWY1nHybpyTDQ4xoQNQKBgQC6m+yiOoi5JD3zVSSJq/iq5DJPA5B4aoP+t5u9lp2Q7iVO0QI5ilBlEKGE4VOU0glnXlDTfuqEYooMY85ekl4WGb3AOT0PhLL2i+gO2nlWBzf4HPzB/hibjfyPyniRM03cHkG3HXucL7Sne6FwERcfjEqjUd2cdP1l89PNrq4rMQKBgQCMmjABSWYh8/y1I5rEQ4OAJdVjC3GdC1Xa35ZpVybjvLEWSpHunhW5lvD8dllw8LC7UTI0XDpGPqTM/4VO2YBYB2PFc0Gs8g0/v0ZgFpOeJ6kpl80MM/wFNemFYTIKRoMSv/psZY9PmfBgGcBBTuquBXZjDcNr+yr2yAm5V/DvTQKBgHqRi94KoF8q5N39IKCkqhJlDH5FkxDktYoKw2rFkPzuzuZz9gghRyj6wXxsG9/2DWMt2dzw0czehFoa/CO188KEadPmRKr6uCmkP2nyKhxNZX+8WnB5G2Sg4DD6BjMpBYz8+qDx5ozx8LDJTYI0V4HLPgMD9JGdbgsXGhlREOkhAoGALr6IQOXnviWNAhCdc7rrsaMLMPbLZ1wqzWtQUG1JxDobbpzEP4CW/mvW5pMn58mSBg5qbXhyDI4fFP0CPb98QIz2tGnIzYyFzdKmF5Z1N7X1OF9O+tsSqASoBZzTqB4fr/o4mz0s9JCeriBR2LWjsbsDU13DTLsfQpWbtOnIy70=";
|
||||
|
||||
// 汇付公钥(用于验证回调签名)
|
||||
private static final String HUIFU_PUBLIC_KEY =
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuqolFneAH66z3/3gaIYIaRZOIk/UYzdsXIm0RyawYBYAOu/NJ7ul8CRIrRlt5vd98HodW2yPrXA4+VHF3AS9UE4WTDpo9qV5brhqQSr/lAuZtEwMZwUWwgdnGFMkUFd9RvyGXAqY0bsQrcQgQ6zGjZHzlMljogDR3iblG0ak5ssD2TSC2W+1cxu+id+FP6onZXlXizuClTyIRh17m7CbS6rl0P3M96MlTdCzTeBw/Y54CiegBJI2wOrm2Qa6Dg6KRc+YkaJWjuRJVJkwjk8JhSyALno9oEzuDKaAXlsQlxeIhmAy4esRrZGrMV8SG0gwUZIP8lduPjQE95lCqqJ0gQIDAQAB";
|
||||
|
||||
public PaymentServiceImpl() {
|
||||
this.okHttpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(false)
|
||||
.build();
|
||||
|
||||
// 验证私钥是否有效
|
||||
validatePrivateKey();
|
||||
}
|
||||
|
||||
private void validatePrivateKey() {
|
||||
try {
|
||||
log.info("========== 验证私钥 ==========");
|
||||
String privateKeyBase64 = MERCHANT_PRIVATE_KEY.replaceAll("\\s", "");
|
||||
log.info("私钥长度(去空格后): {}", privateKeyBase64.length());
|
||||
|
||||
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64);
|
||||
log.info("私钥解码后长度: {} bytes", privateKeyBytes.length);
|
||||
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(spec);
|
||||
log.info("私钥加载成功,算法: {}", privateKey.getAlgorithm());
|
||||
|
||||
// 测试签名
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initSign(privateKey);
|
||||
signature.update("test".getBytes(StandardCharsets.UTF_8));
|
||||
signature.sign();
|
||||
log.info("私钥签名测试成功!");
|
||||
log.info("========== 私钥验证完成 ==========");
|
||||
} catch (Exception e) {
|
||||
log.error("========== 私钥验证失败 ==========", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 1. 创建支付订单 ====================
|
||||
@Override
|
||||
public Mono<PaymentCreateResponse> createPayment(PaymentCreateRequest request) {
|
||||
log.info("========== createPayment 方法被调用 ==========");
|
||||
String orderId = UUID.randomUUID().toString().replace("-", "");
|
||||
String reqDate = LocalDateTime.now().format(DATE_FORMATTER);
|
||||
String reqSeqId = "RQ" + System.currentTimeMillis();
|
||||
|
||||
// ===== 构建 data 参数 =====
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("req_date", reqDate);
|
||||
data.put("req_seq_id", reqSeqId);
|
||||
data.put("huifu_id", HUIFU_ID);
|
||||
data.put("trade_type", "ALIPAY");
|
||||
data.put("pay_type", "APP"); // APP 支付,返回 alipay_scheme
|
||||
data.put("trans_amt", request.getTransAmt() != null ? request.getTransAmt() : "1");
|
||||
data.put("goods_desc", request.getGoodsDesc() != null ? request.getGoodsDesc() : "会员卡");
|
||||
data.put("acct_id", ACCT_ID);
|
||||
data.put("notify_url", NOTIFY_URL);
|
||||
data.put("remark", request.getRemark() != null ? request.getRemark() : "");
|
||||
|
||||
// ===== 支付宝参数 =====
|
||||
data.put("alipay_channel", ALIPAY_CHANNEL);
|
||||
|
||||
String dataJson = JSONUtil.toJsonStr(data);
|
||||
String sign = generateSign(dataJson);
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("sys_id", SYS_ID);
|
||||
requestBody.put("product_id", PRODUCT_ID);
|
||||
requestBody.put("data", dataJson);
|
||||
requestBody.put("sign", sign);
|
||||
|
||||
return Mono.fromCallable(() -> {
|
||||
String jsonBody = JSONUtil.toJsonStr(requestBody);
|
||||
log.info("========== 发起支付宝支付请求 (APP) ==========");
|
||||
log.info("请求URL: {}", CREATE_URL);
|
||||
log.info("请求Body: {}", jsonBody);
|
||||
log.info("==================================");
|
||||
|
||||
Request httpRequest = new Request.Builder()
|
||||
.url(CREATE_URL)
|
||||
.post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")))
|
||||
.addHeader("Content-Type", "application/json; charset=UTF-8")
|
||||
.addHeader("Accept", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = okHttpClient.newCall(httpRequest).execute()) {
|
||||
int httpCode = response.code();
|
||||
String responseBody = response.body() != null ? response.body().string() : "";
|
||||
|
||||
log.info("========== 收到响应 ==========");
|
||||
log.info("HTTP状态码: {}", httpCode);
|
||||
log.info("响应Body: {}", responseBody);
|
||||
log.info("===============================");
|
||||
|
||||
if (httpCode >= 400) {
|
||||
if (responseBody != null && !responseBody.isEmpty()) {
|
||||
try {
|
||||
JSONObject errorJson = JSONUtil.parseObj(responseBody);
|
||||
String errorMsg = errorJson.getStr("error_msg");
|
||||
String errorCode = errorJson.getStr("error_code");
|
||||
if (errorMsg != null) {
|
||||
throw new RuntimeException("支付失败: " + errorMsg + " (code: " + errorCode + ")");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("支付请求失败: HTTP " + httpCode);
|
||||
}
|
||||
|
||||
if (responseBody == null || responseBody.isEmpty()) {
|
||||
throw new RuntimeException("响应体为空");
|
||||
}
|
||||
|
||||
return parseCreateResponse(responseBody, orderId, request);
|
||||
} catch (IOException e) {
|
||||
log.error("网络请求异常", e);
|
||||
throw new RuntimeException("网络请求异常: " + e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 2. 查询支付状态 ====================
|
||||
@Override
|
||||
public Mono<PaymentQueryResponse> queryPayment(String orderId) {
|
||||
PaymentInfo paymentInfo = paymentCache.get(orderId);
|
||||
if (paymentInfo == null) {
|
||||
return Mono.error(new RuntimeException("支付记录不存在"));
|
||||
}
|
||||
|
||||
String reqDate = LocalDateTime.now().format(DATE_FORMATTER);
|
||||
String reqSeqId = "RQ" + System.currentTimeMillis();
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("req_date", reqDate);
|
||||
data.put("req_seq_id", reqSeqId);
|
||||
data.put("huifu_id", HUIFU_ID);
|
||||
data.put("out_trans_id", paymentInfo.outTransId);
|
||||
|
||||
String dataJson = JSONUtil.toJsonStr(data);
|
||||
String sign = generateSign(dataJson);
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("sys_id", SYS_ID);
|
||||
requestBody.put("product_id", PRODUCT_ID);
|
||||
requestBody.put("data", dataJson);
|
||||
requestBody.put("sign", sign);
|
||||
|
||||
return Mono.fromCallable(() -> {
|
||||
String jsonBody = JSONUtil.toJsonStr(requestBody);
|
||||
log.info("查询支付状态, orderId={}", orderId);
|
||||
|
||||
Request httpRequest = new Request.Builder()
|
||||
.url(QUERY_URL)
|
||||
.post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")))
|
||||
.addHeader("Content-Type", "application/json; charset=UTF-8")
|
||||
.addHeader("Accept", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = okHttpClient.newCall(httpRequest).execute()) {
|
||||
int httpCode = response.code();
|
||||
String responseBody = response.body() != null ? response.body().string() : "";
|
||||
|
||||
log.info("查询响应: code={}, body={}", httpCode, responseBody);
|
||||
|
||||
if (httpCode >= 400) {
|
||||
throw new RuntimeException("查询失败: HTTP " + httpCode);
|
||||
}
|
||||
|
||||
if (responseBody == null || responseBody.isEmpty()) {
|
||||
throw new RuntimeException("查询响应为空");
|
||||
}
|
||||
|
||||
return parseQueryResponse(responseBody, orderId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 3. 申请退款 ====================
|
||||
@Override
|
||||
public Mono<PaymentRefundResponse> refundPayment(String orderId, String refundAmt) {
|
||||
PaymentInfo paymentInfo = paymentCache.get(orderId);
|
||||
if (paymentInfo == null) {
|
||||
return Mono.error(new RuntimeException("支付记录不存在"));
|
||||
}
|
||||
|
||||
String reqDate = LocalDateTime.now().format(DATE_FORMATTER);
|
||||
String reqSeqId = "RQ" + System.currentTimeMillis();
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("req_date", reqDate);
|
||||
data.put("req_seq_id", reqSeqId);
|
||||
data.put("huifu_id", HUIFU_ID);
|
||||
data.put("out_trans_id", paymentInfo.outTransId);
|
||||
data.put("trans_amt", refundAmt);
|
||||
|
||||
String dataJson = JSONUtil.toJsonStr(data);
|
||||
String sign = generateSign(dataJson);
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("sys_id", SYS_ID);
|
||||
requestBody.put("product_id", PRODUCT_ID);
|
||||
requestBody.put("data", dataJson);
|
||||
requestBody.put("sign", sign);
|
||||
|
||||
return Mono.fromCallable(() -> {
|
||||
String jsonBody = JSONUtil.toJsonStr(requestBody);
|
||||
log.info("发起退款请求, orderId={}", orderId);
|
||||
|
||||
Request httpRequest = new Request.Builder()
|
||||
.url(REFUND_URL)
|
||||
.post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")))
|
||||
.addHeader("Content-Type", "application/json; charset=UTF-8")
|
||||
.addHeader("Accept", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = okHttpClient.newCall(httpRequest).execute()) {
|
||||
int httpCode = response.code();
|
||||
String responseBody = response.body() != null ? response.body().string() : "";
|
||||
|
||||
log.info("退款响应: code={}, body={}", httpCode, responseBody);
|
||||
|
||||
if (httpCode >= 400) {
|
||||
throw new RuntimeException("退款失败: HTTP " + httpCode);
|
||||
}
|
||||
|
||||
return parseRefundResponse(responseBody, orderId, refundAmt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 4. 关闭订单 ====================
|
||||
public Mono<Void> closePayment(String orderId) {
|
||||
PaymentInfo paymentInfo = paymentCache.get(orderId);
|
||||
if (paymentInfo == null) {
|
||||
return Mono.error(new RuntimeException("支付记录不存在"));
|
||||
}
|
||||
|
||||
String reqDate = LocalDateTime.now().format(DATE_FORMATTER);
|
||||
String reqSeqId = "RQ" + System.currentTimeMillis();
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("req_date", reqDate);
|
||||
data.put("req_seq_id", reqSeqId);
|
||||
data.put("huifu_id", HUIFU_ID);
|
||||
data.put("out_trans_id", paymentInfo.outTransId);
|
||||
|
||||
String dataJson = JSONUtil.toJsonStr(data);
|
||||
String sign = generateSign(dataJson);
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("sys_id", SYS_ID);
|
||||
requestBody.put("product_id", PRODUCT_ID);
|
||||
requestBody.put("data", dataJson);
|
||||
requestBody.put("sign", sign);
|
||||
|
||||
return Mono.fromRunnable(() -> {
|
||||
try {
|
||||
String jsonBody = JSONUtil.toJsonStr(requestBody);
|
||||
log.info("关闭订单请求, orderId={}", orderId);
|
||||
|
||||
Request httpRequest = new Request.Builder()
|
||||
.url(CLOSE_URL)
|
||||
.post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")))
|
||||
.addHeader("Content-Type", "application/json; charset=UTF-8")
|
||||
.addHeader("Accept", "application/json")
|
||||
.build();
|
||||
|
||||
try (Response response = okHttpClient.newCall(httpRequest).execute()) {
|
||||
String responseBody = response.body() != null ? response.body().string() : "";
|
||||
log.info("关闭订单响应, code={}, body={}", response.code(), responseBody);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("关闭订单异常", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 5. 处理异步通知 ====================
|
||||
@Override
|
||||
public Mono<Void> handleNotify(String notifyBody) {
|
||||
return Mono.fromRunnable(() -> {
|
||||
log.info("========== 收到支付回调通知 ==========");
|
||||
log.info("回调内容: {}", notifyBody);
|
||||
|
||||
try {
|
||||
JSONObject notifyJson = JSONUtil.parseObj(notifyBody);
|
||||
String sign = notifyJson.getStr("sign");
|
||||
String data = notifyJson.getStr("data");
|
||||
|
||||
// 直接打印回调信息,不验证签名
|
||||
log.info("签名: {}", sign);
|
||||
log.info("数据: {}", data);
|
||||
|
||||
if (data != null && !data.isEmpty()) {
|
||||
JSONObject dataJson = JSONUtil.parseObj(data);
|
||||
String respCode = dataJson.getStr("resp_code");
|
||||
String respDesc = dataJson.getStr("resp_desc");
|
||||
String outTransId = dataJson.getStr("out_trans_id");
|
||||
String transStatus = dataJson.getStr("trans_status");
|
||||
String transAmt = dataJson.getStr("trans_amt");
|
||||
String finishDate = dataJson.getStr("finish_date");
|
||||
String finishTime = dataJson.getStr("finish_time");
|
||||
|
||||
log.info("========== 回调数据解析 ==========");
|
||||
log.info("响应码: {}", respCode);
|
||||
log.info("响应描述: {}", respDesc);
|
||||
log.info("商户订单号: {}", outTransId);
|
||||
log.info("交易状态: {}", transStatus);
|
||||
log.info("交易金额: {}", transAmt);
|
||||
log.info("交易完成日期: {}", finishDate);
|
||||
log.info("交易完成时间: {}", finishTime);
|
||||
log.info("===================================");
|
||||
|
||||
// 更新本地支付状态
|
||||
if ("S".equals(transStatus) || "TRADE_SUCCESS".equals(transStatus)) {
|
||||
paymentCache.values().stream()
|
||||
.filter(info -> outTransId != null && outTransId.equals(info.outTransId))
|
||||
.findFirst()
|
||||
.ifPresent(info -> {
|
||||
info.payStatus = transStatus;
|
||||
info.payTime = LocalDateTime.now();
|
||||
log.info("支付状态更新成功, orderId={}, status={}", info.orderId, transStatus);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理回调异常", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 签名方法 ====================
|
||||
private String generateSign(String dataJson) {
|
||||
Exception originalException = null;
|
||||
System.err.println(">>>>>>>>> [PaymentServiceImpl] generateSign 开始执行 <<<<<<<<<<");
|
||||
System.err.flush();
|
||||
try {
|
||||
System.err.println(">>>>>>>>> 开始签名方法 <<<<<<<<<<");
|
||||
System.err.flush();
|
||||
log.error("========== 开始签名 ==========");
|
||||
|
||||
// 打印密钥的前几个字节来检查格式
|
||||
String privateKeyBase64 = MERCHANT_PRIVATE_KEY.replaceAll("\\s", "");
|
||||
byte[] keyBytes = Base64.getDecoder().decode(privateKeyBase64);
|
||||
System.err.println(">>>>>>>>> 密钥前20字节(hex): " + bytesToHex(keyBytes, 20) + " <<<<<<<<<<");
|
||||
System.err.println(">>>>>>>>> 密钥长度: " + keyBytes.length + " bytes <<<<<<<<<<");
|
||||
System.err.flush();
|
||||
|
||||
// 检查是否是PKCS#8格式 (应该以30 82开头)
|
||||
if (keyBytes.length > 2) {
|
||||
System.err.println(">>>>>>>>> 密钥前2字节(hex): " + bytesToHex(keyBytes, 2) + " <<<<<<<<<<");
|
||||
boolean startsWith30 = (keyBytes[0] & 0xFF) == 0x30;
|
||||
System.err.println(">>>>>>>>> 密钥是否以0x30开头: " + startsWith30 + " <<<<<<<<<<");
|
||||
System.err.flush();
|
||||
|
||||
// 打印更多头部字节来识别密钥类型
|
||||
if (keyBytes.length > 4) {
|
||||
System.err.println(">>>>>>>>> 密钥前5字节(hex): " + bytesToHex(keyBytes, 5) + " <<<<<<<<<<");
|
||||
System.err.flush();
|
||||
// RSA PKCS#8 私钥通常以 30 82 04** 开头
|
||||
// EC PKCS#8 私钥通常以 30 82 04** 开头 (和RSA一样,需要看后面的内容区分)
|
||||
// 如果是 30 82,且第4个字节是 0x00 或 0x01,可能是RSA私钥
|
||||
// 如果是 30 82,且第4个字节是 0x02 或 0x03,可能是EC私钥
|
||||
}
|
||||
}
|
||||
|
||||
log.info("私钥长度(去空格后): {}", privateKeyBase64.length());
|
||||
|
||||
// 检查私钥格式
|
||||
if (privateKeyBase64.length() < 100) {
|
||||
throw new RuntimeException("私钥长度异常: " + privateKeyBase64.length());
|
||||
}
|
||||
|
||||
log.info("私钥前50字符: {}", privateKeyBase64.substring(0, 50));
|
||||
log.info("私钥后50字符: {}", privateKeyBase64.substring(privateKeyBase64.length() - 50));
|
||||
|
||||
log.info("开始解码私钥...");
|
||||
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyBase64);
|
||||
log.info("私钥解码后长度: {} bytes", privateKeyBytes.length);
|
||||
|
||||
// 直接尝试使用PKCS8EncodedKeySpec加载
|
||||
log.info("开始生成 PrivateKey 对象...");
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(spec);
|
||||
log.info("私钥生成成功,算法: {}", privateKey.getAlgorithm());
|
||||
|
||||
log.info("开始签名...");
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
log.info("调用 initSign...");
|
||||
signature.initSign(privateKey);
|
||||
log.info("initSign 完成");
|
||||
log.info("开始 update...");
|
||||
signature.update(dataJson.getBytes(StandardCharsets.UTF_8));
|
||||
log.info("update 完成,开始 sign...");
|
||||
String sign = Base64.getEncoder().encodeToString(signature.sign());
|
||||
log.info("签名生成成功, 签名长度: {}", sign.length());
|
||||
log.info("========== 签名完成 ==========");
|
||||
return sign;
|
||||
} catch (RuntimeException e) {
|
||||
log.error("签名生成失败(RuntimeException): {} - {}", e.getClass().getName(), e.getMessage(), e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("签名生成失败(Exception): {} - {}", e.getClass().getName(), e.getMessage(), e);
|
||||
throw new RuntimeException("签名生成失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifySign(String sign, String dataJson) {
|
||||
try {
|
||||
String publicKeyBase64 = HUIFU_PUBLIC_KEY.replaceAll("\\s", "");
|
||||
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyBase64);
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PublicKey publicKey = keyFactory.generatePublic(spec);
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(dataJson.getBytes(StandardCharsets.UTF_8));
|
||||
return signature.verify(Base64.getDecoder().decode(sign));
|
||||
} catch (Exception e) {
|
||||
log.error("验签失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 响应解析方法 ====================
|
||||
|
||||
private PaymentCreateResponse parseCreateResponse(String responseBody, String orderId, PaymentCreateRequest request) {
|
||||
JSONObject responseJson = JSONUtil.parseObj(responseBody);
|
||||
String dataStr = responseJson.getStr("data");
|
||||
JSONObject data = JSONUtil.parseObj(dataStr);
|
||||
|
||||
String respCode = data.getStr("resp_code");
|
||||
if (!"000000".equals(respCode)) {
|
||||
throw new RuntimeException("支付创建失败: " + data.getStr("resp_desc") + " (code: " + respCode + ")");
|
||||
}
|
||||
|
||||
// APP 支付优先取 alipay_scheme
|
||||
String payUrl = data.getStr("alipay_scheme");
|
||||
if (payUrl == null) {
|
||||
payUrl = data.getStr("pay_url");
|
||||
}
|
||||
if (payUrl == null) {
|
||||
payUrl = data.getStr("pay_info");
|
||||
}
|
||||
|
||||
PaymentInfo paymentInfo = new PaymentInfo();
|
||||
paymentInfo.orderId = orderId;
|
||||
paymentInfo.memberId = request.getMemberId();
|
||||
paymentInfo.orderType = request.getOrderType();
|
||||
paymentInfo.transAmt = request.getTransAmt();
|
||||
paymentInfo.goodsDesc = request.getGoodsDesc();
|
||||
paymentInfo.payStatus = "PENDING";
|
||||
paymentInfo.outTransId = data.getStr("out_trans_id");
|
||||
paymentInfo.payUrl = payUrl;
|
||||
paymentInfo.remark = request.getRemark();
|
||||
paymentCache.put(orderId, paymentInfo);
|
||||
|
||||
log.info("支付创建成功, orderId={}, outTransId={}, payUrl={}", orderId, paymentInfo.outTransId, payUrl);
|
||||
|
||||
return PaymentCreateResponse.builder()
|
||||
.orderId(orderId)
|
||||
.payInfo(payUrl)
|
||||
.transAmt(request.getTransAmt())
|
||||
.payStatus("PENDING")
|
||||
.message("支付创建成功")
|
||||
.build();
|
||||
}
|
||||
|
||||
private PaymentQueryResponse parseQueryResponse(String responseBody, String orderId) {
|
||||
JSONObject responseJson = JSONUtil.parseObj(responseBody);
|
||||
String dataStr = responseJson.getStr("data");
|
||||
JSONObject data = JSONUtil.parseObj(dataStr);
|
||||
|
||||
String respCode = data.getStr("resp_code");
|
||||
if (!"000000".equals(respCode)) {
|
||||
throw new RuntimeException("查询失败: " + data.getStr("resp_desc"));
|
||||
}
|
||||
|
||||
PaymentInfo paymentInfo = paymentCache.get(orderId);
|
||||
if (paymentInfo != null) {
|
||||
paymentInfo.payStatus = data.getStr("trans_status");
|
||||
}
|
||||
|
||||
return PaymentQueryResponse.builder()
|
||||
.orderId(orderId)
|
||||
.tradeType(data.getStr("trade_type"))
|
||||
.transAmt(data.getStr("trans_amt"))
|
||||
.payStatus(data.getStr("trans_status"))
|
||||
.outTransId(data.getStr("out_trans_id"))
|
||||
.payTime(data.getStr("end_time"))
|
||||
.message("查询成功")
|
||||
.build();
|
||||
}
|
||||
|
||||
private PaymentRefundResponse parseRefundResponse(String responseBody, String orderId, String refundAmt) {
|
||||
JSONObject responseJson = JSONUtil.parseObj(responseBody);
|
||||
String dataStr = responseJson.getStr("data");
|
||||
JSONObject data = JSONUtil.parseObj(dataStr);
|
||||
|
||||
String respCode = data.getStr("resp_code");
|
||||
if (!"000000".equals(respCode)) {
|
||||
throw new RuntimeException("退款失败: " + data.getStr("resp_desc"));
|
||||
}
|
||||
|
||||
PaymentInfo paymentInfo = paymentCache.get(orderId);
|
||||
if (paymentInfo != null) {
|
||||
paymentInfo.payStatus = "REFUNDED";
|
||||
}
|
||||
|
||||
return PaymentRefundResponse.builder()
|
||||
.orderId(orderId)
|
||||
.refundAmt(refundAmt)
|
||||
.refundStatus(data.getStr("trans_status"))
|
||||
.message("退款成功")
|
||||
.build();
|
||||
}
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
private static String bytesToHex(byte[] bytes, int length) {
|
||||
int len = Math.min(bytes.length, length);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < len; i++) {
|
||||
sb.append(String.format("%02X ", bytes[i] & 0xFF));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将PKCS#1格式的RSA私钥转换为PKCS#8格式
|
||||
* PKCS#1: SEQUENCE { version, modulus, publicExponent, privateExponent, prime1, prime2, ... }
|
||||
* PKCS#8: SEQUENCE { version, algorithmIdentifier, OCTET STRING (containing PKCS#1) }
|
||||
*/
|
||||
private byte[] convertPKCS1ToPKCS8(byte[] pkcs1Key) {
|
||||
try {
|
||||
log.info("检测到PKCS#1格式私钥,开始转换为PKCS#8格式...");
|
||||
log.info("PKCS#1密钥长度: {} bytes", pkcs1Key.length);
|
||||
|
||||
// RSA算法OID: 1.2.840.113549.1.1.1
|
||||
byte[] rsaOid = new byte[] {
|
||||
0x06, 0x09, 0x2A, (byte)0x86, 0x48, (byte)0x86, (byte)0xF7, 0x0D, 0x01, 0x01, 0x01
|
||||
};
|
||||
|
||||
// 构建AlgorithmIdentifier: SEQUENCE { OID, NULL }
|
||||
// SEQUENCE (2 bytes: tag + length) + OID + NULL = 15 bytes total
|
||||
byte[] algorithmIdentifier = new byte[15];
|
||||
int idx = 0;
|
||||
algorithmIdentifier[idx++] = 0x30; // SEQUENCE tag
|
||||
algorithmIdentifier[idx++] = 0x0D; // length = 13 bytes for OID + NULL
|
||||
System.arraycopy(rsaOid, 0, algorithmIdentifier, idx, rsaOid.length);
|
||||
idx += rsaOid.length;
|
||||
algorithmIdentifier[idx++] = 0x05; // NULL tag
|
||||
algorithmIdentifier[idx++] = 0x00; // NULL value
|
||||
|
||||
log.info("AlgorithmIdentifier长度: {} bytes", algorithmIdentifier.length);
|
||||
|
||||
// 计算 OCTET STRING 长度编码需要的字节数
|
||||
int octetStringLengthBytes = 1; // tag
|
||||
if (pkcs1Key.length > 127) {
|
||||
if (pkcs1Key.length > 255) {
|
||||
octetStringLengthBytes += 3; // 0x82 + 2 length bytes
|
||||
} else {
|
||||
octetStringLengthBytes += 2; // 0x81 + 1 length byte
|
||||
}
|
||||
} else {
|
||||
octetStringLengthBytes += 1; // 1 length byte
|
||||
}
|
||||
|
||||
// 计算 version INTEGER 编码 (3 bytes: tag + length + value)
|
||||
int versionBytes = 3;
|
||||
|
||||
// 计算 total length of inner content (version + algorithmId + octetString)
|
||||
int innerContentLength = versionBytes + algorithmIdentifier.length + octetStringLengthBytes + pkcs1Key.length;
|
||||
|
||||
// 计算 outer SEQUENCE 长度编码需要的字节数
|
||||
int sequenceLengthBytes = 1; // tag
|
||||
if (innerContentLength > 127) {
|
||||
if (innerContentLength > 255) {
|
||||
sequenceLengthBytes += 3; // 0x82 + 2 length bytes
|
||||
} else {
|
||||
sequenceLengthBytes += 2; // 0x81 + 1 length byte
|
||||
}
|
||||
} else {
|
||||
sequenceLengthBytes += 1;
|
||||
}
|
||||
|
||||
// 分配最终数组
|
||||
int totalLen = sequenceLengthBytes + innerContentLength;
|
||||
byte[] pkcs8Key = new byte[totalLen];
|
||||
log.info("PKCS#8密钥分配: {} bytes", totalLen);
|
||||
|
||||
int offset = 0;
|
||||
|
||||
// outer SEQUENCE
|
||||
pkcs8Key[offset++] = 0x30;
|
||||
if (innerContentLength > 127) {
|
||||
if (innerContentLength > 255) {
|
||||
pkcs8Key[offset++] = (byte)0x82;
|
||||
pkcs8Key[offset++] = (byte)((innerContentLength >> 8) & 0xFF);
|
||||
pkcs8Key[offset++] = (byte)(innerContentLength & 0xFF);
|
||||
} else {
|
||||
pkcs8Key[offset++] = (byte)0x81;
|
||||
pkcs8Key[offset++] = (byte)innerContentLength;
|
||||
}
|
||||
} else {
|
||||
pkcs8Key[offset++] = (byte)innerContentLength;
|
||||
}
|
||||
|
||||
// version = 0
|
||||
pkcs8Key[offset++] = 0x02;
|
||||
pkcs8Key[offset++] = 0x01;
|
||||
pkcs8Key[offset++] = 0x00;
|
||||
|
||||
// algorithmIdentifier
|
||||
System.arraycopy(algorithmIdentifier, 0, pkcs8Key, offset, algorithmIdentifier.length);
|
||||
offset += algorithmIdentifier.length;
|
||||
|
||||
// privateKey as OCTET STRING
|
||||
pkcs8Key[offset++] = 0x04;
|
||||
if (pkcs1Key.length > 127) {
|
||||
if (pkcs1Key.length > 255) {
|
||||
pkcs8Key[offset++] = (byte)0x82;
|
||||
pkcs8Key[offset++] = (byte)((pkcs1Key.length >> 8) & 0xFF);
|
||||
pkcs8Key[offset++] = (byte)(pkcs1Key.length & 0xFF);
|
||||
} else {
|
||||
pkcs8Key[offset++] = (byte)0x81;
|
||||
pkcs8Key[offset++] = (byte)pkcs1Key.length;
|
||||
}
|
||||
} else {
|
||||
pkcs8Key[offset++] = (byte)pkcs1Key.length;
|
||||
}
|
||||
|
||||
// 复制 PKCS#1 私钥
|
||||
System.arraycopy(pkcs1Key, 0, pkcs8Key, offset, pkcs1Key.length);
|
||||
|
||||
log.info("PKCS#1到PKCS#8转换完成,最终长度: {} bytes", pkcs8Key.length);
|
||||
return pkcs8Key;
|
||||
} catch (Exception e) {
|
||||
log.error("PKCS#1到PKCS#8转换失败", e);
|
||||
throw new RuntimeException("密钥格式转换失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 内部缓存类 ====================
|
||||
private static class PaymentInfo {
|
||||
String orderId;
|
||||
Long memberId;
|
||||
String orderType;
|
||||
String tradeType;
|
||||
String transAmt;
|
||||
String goodsDesc;
|
||||
String payStatus;
|
||||
String outTransId;
|
||||
String payUrl;
|
||||
String hfSeqId;
|
||||
LocalDateTime payTime;
|
||||
String remark;
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
cn.novalon.gym.manage.payment.config.HuifuPayConfig
|
||||
Reference in New Issue
Block a user