Commit 0381c12b by Jony.L

新支付功能测试2.0

parent 09884fa0
......@@ -7,6 +7,7 @@ import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppRe
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderPageReqVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderRespVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderInvoiceReqVO;
import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
import com.luhu.computility.module.compute.service.resourceorder.ResourceOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
......@@ -41,9 +42,9 @@ public class AppResourceOrderController {
@PostMapping("/create-wpgj")
@Operation(summary = "创建算力资源订单(旺铺聚合支付)")
public CommonResult<AppResourceOrderCreateRespVO> createResourceOrderWithWpgj(@Valid @RequestBody AppResourceOrderCreateReqVO createReqVO) {
public CommonResult<AppPayOrderSubmitRespVO> createResourceOrderWithWpgj(@Valid @RequestBody AppResourceOrderCreateReqVO createReqVO) {
Long userId = getLoginUserId();
AppResourceOrderCreateRespVO respVO = resourceOrderService.createUserResourceOrderWithWpgj(userId, createReqVO);
AppPayOrderSubmitRespVO respVO = resourceOrderService.createUserResourceOrderWithWpgj(userId, createReqVO);
return success(respVO);
}
......
......@@ -16,4 +16,10 @@ public class AppResourceOrderCreateRespVO {
@Schema(description = "支付订单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "15798")
private Long payOrderId;
@Schema(description = "支付二维码URL(WPGJ聚合支付)")
private String payQrCode;
@Schema(description = "支付展示模式(qr_code=二维码,pay_url=支付链接)")
private String displayMode;
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppRe
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderPageReqVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderRespVO;
import com.luhu.computility.module.compute.controller.app.resourceorder.vo.AppResourceOrderInvoiceReqVO;
import com.luhu.computility.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
import com.luhu.computility.module.compute.dal.dataobject.resourceorder.ResourceOrderDO;
import com.luhu.computility.framework.common.pojo.PageResult;
import com.luhu.computility.framework.common.pojo.PageParam;
......@@ -78,9 +79,9 @@ public interface ResourceOrderService {
*
* @param userId 用户ID
* @param createReqVO 创建信息
* @return 创建响应
* @return 支付提交响应
*/
AppResourceOrderCreateRespVO createUserResourceOrderWithWpgj(Long userId, @Valid AppResourceOrderCreateReqVO createReqVO);
AppPayOrderSubmitRespVO createUserResourceOrderWithWpgj(Long userId, @Valid AppResourceOrderCreateReqVO createReqVO);
/**
* 更新订单为已支付(支付回调使用)
......
......@@ -443,7 +443,7 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
@Override
@Transactional(rollbackFor = Exception.class)
public AppResourceOrderCreateRespVO createUserResourceOrderWithWpgj(Long userId, AppResourceOrderCreateReqVO createReqVO) {
public AppPayOrderSubmitRespVO createUserResourceOrderWithWpgj(Long userId, AppResourceOrderCreateReqVO createReqVO) {
// 1. 构建订单
ResourceOrderDO order = buildResourceOrder(userId, createReqVO);
......@@ -451,23 +451,22 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
resourceOrderMapper.insert(order);
// 3. 创建旺铺聚合支付订单
AppPayOrderSubmitRespVO payRespVO = null;
if (order.getPaymentPrice() > 0) {
createPayOrderWithWpgj(order);
payRespVO = createPayOrderWithWpgj(order);
} else {
// 如果价格为0,返回成功状态
payRespVO = new AppPayOrderSubmitRespVO();
payRespVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus());
}
// 4. 返回结果
AppResourceOrderCreateRespVO respVO = new AppResourceOrderCreateRespVO();
respVO.setId(order.getId());
respVO.setOrderNo(order.getOrderNo());
respVO.setPayOrderId(order.getPayOrderId());
return respVO;
return payRespVO;
}
/**
* 创建旺铺聚合支付订单
*/
private void createPayOrderWithWpgj(ResourceOrderDO order) {
private AppPayOrderSubmitRespVO createPayOrderWithWpgj(ResourceOrderDO order) {
try {
// 1. 创建支付订单(用于数据库记录)
PayOrderCreateReqDTO payOrderCreateReqDTO = new PayOrderCreateReqDTO()
......@@ -495,6 +494,8 @@ public class ResourceOrderServiceImpl implements ResourceOrderService {
log.info("[createPayOrderWithWpgj] 旺铺聚合支付订单创建成功,订单ID: {}, 支付订单ID: {}", order.getId(), payOrderId);
return submitRespVO;
} catch (Exception e) {
log.error("[createPayOrderWithWpgj] 创建旺铺聚合支付订单失败", e);
throw new ServiceException("创建支付订单失败: " + e.getMessage());
......
......@@ -7,6 +7,7 @@ import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjC
import com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj.WpgjPayProperties;
import com.luhu.computility.module.pay.service.order.PayOrderService;
import cn.hutool.json.JSONUtil;
import cn.hutool.json.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
......@@ -24,9 +25,9 @@ import static com.luhu.computility.framework.common.pojo.CommonResult.success;
*/
@Tag(name = "管理后台 - WPGJ旺铺聚合支付回调")
@RestController
@RequestMapping("/pay/wpgj")
@RequestMapping("/admin-api/pay/wpgj")
@Slf4j
public class AdminWpgjPayController {
public class WpgjPayController {
@Resource
private PayOrderService payOrderService;
......@@ -41,8 +42,9 @@ public class AdminWpgjPayController {
log.info("[notifyWpgjPay] 收到WPGJ支付回调,加密数据: {}", encryptedData);
// 1. 解密回调数据
String decryptedJson = WpgjCryptoUtils.decryptResponse(encryptedData, wpgjPayProperties.getPrivateKey());
Map<String, Object> decryptedData = JSONUtil.parseObj(decryptedJson);
JSONObject decryptedResponse = WpgjCryptoUtils.decryptResponse(encryptedData, wpgjPayProperties.getPrivateKey());
Map<String, Object> decryptedData = decryptedResponse.get("data") != null ?
(Map<String, Object>) decryptedResponse.get("data") : decryptedResponse;
log.info("[notifyWpgjPay] 解密后的回调数据: {}", JsonUtils.toJsonString(decryptedData));
......@@ -69,7 +71,7 @@ public class AdminWpgjPayController {
PayOrderRespDTO notify = new PayOrderRespDTO();
// 根据WPGJ回调文档解析字段
notify.setOutTradeNo((String) data.get("out_trade_no")); // 商户订单号
notify.setOutTradeNo((String) data.get("mer_order_id")); // 商户订单号
notify.setChannelOrderNo((String) data.get("transaction_id")); // 第三方交易号
notify.setStatus(parseWpgjStatus((String) data.get("trade_state"))); // 交易状态
......
package com.luhu.computility.module.pay.framework.pay.core.client.impl.wpgj;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Base64;
import java.util.UUID;
/**
* 旺铺聚合支付加解密工具类
* WPGJ旺铺聚合支付加密工具类 - 专门用于动态支付序列码接口
* 按照官方demo代码标准实现
*
* 支持RSA+AES混合加密
* - AES加密请求数据
* - RSA加密AES密钥
*
* @author generated
* @author jonyl
*/
@Slf4j
public class WpgjCryptoUtils {
private static final String AES_ALGORITHM = "AES";
private static final String RSA_ALGORITHM = "RSA";
private static final String RSA_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
/**
* 生成随机AES密钥(16位)
*
* @return AES密钥
* 构建加密的请求数据 - 按照demo代码的标准实现
*/
public static String generateAESKey() {
return RandomUtil.randomString("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 16);
}
public static String buildEncryptedRequest(JSONObject businessData, String organizNo, String publicKey) throws Exception {
// 1. 生成16位随机AES密钥
String aesKey = generateLenString(16);
// 2. 构建完整的请求数据结构
JSONObject requestJson = new JSONObject();
requestJson.set("serialNo", UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());
requestJson.set("version", "1.0");
requestJson.set("timestamp", new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()));
// 3. 用AES加密业务数据
String businessDataStr = businessData.toString();
String encryptedData = encryptData(businessDataStr, aesKey, "UTF-8");
requestJson.set("data", encryptedData);
/**
* AES加密
*
* @param data 明文数据
* @param aesKey AES密钥
* @return Base64编码的密文
*/
public static String aesEncrypt(String data, String aesKey) {
try {
AES aes = new AES(Mode.ECB, Padding.PKCS5Padding, aesKey.getBytes(StandardCharsets.UTF_8));
byte[] encrypted = aes.encrypt(data.getBytes(StandardCharsets.UTF_8));
return Base64.encode(encrypted);
} catch (Exception e) {
log.error("[aesEncrypt] AES加密失败: {}", e.getMessage(), e);
throw new RuntimeException("AES加密失败", e);
}
}
// 4. 用RSA公钥加密AES密钥
String encryptedAesKey = encrtptKey(publicKey, aesKey, "UTF-8");
requestJson.set("signature", encryptedAesKey);
/**
* AES解密
*
* @param encryptedData Base64编码的密文
* @param aesKey AES密钥
* @return 明文数据
*/
public static String aesDecrypt(String encryptedData, String aesKey) {
try {
AES aes = new AES(Mode.ECB, Padding.PKCS5Padding, aesKey.getBytes(StandardCharsets.UTF_8));
byte[] encrypted = Base64.decode(encryptedData);
byte[] decrypted = aes.decrypt(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("[aesDecrypt] AES解密失败: {}", e.getMessage(), e);
throw new RuntimeException("AES解密失败", e);
}
requestJson.set("extras", null);
requestJson.set("organizNo", organizNo);
return requestJson.toString();
}
/**
* RSA公钥加密
*
* @param data 要加密的数据
* @param publicKey RSA公钥
* @return Base64编码的密文
* 解密响应数据
*/
public static String rsaEncrypt(String data, String publicKey) {
try {
// 移除公钥格式
String publicKeyStr = publicKey.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
public static JSONObject decryptResponse(String responseStr, String privateKey) throws Exception {
JSONObject response = new JSONObject(responseStr);
byte[] keyBytes = Base64.decode(publicKeyStr);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PublicKey pubKey = keyFactory.generatePublic(spec);
if ("0000".equals(response.getStr("code"))) {
// 解密data字段
String encryptedData = response.getStr("data");
String signature = response.getStr("signature");
Cipher cipher = Cipher.getInstance(RSA_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 用RSA私钥解密AES密钥
byte[] aesKeyBytes = decryptKey(privateKey, signature, "UTF-8");
return Base64.encode(encrypted);
} catch (Exception e) {
log.error("[rsaEncrypt] RSA加密失败: {}", e.getMessage(), e);
throw new RuntimeException("RSA加密失败", e);
// 用AES密钥解密响应数据
String decryptedData = decryptData(encryptedData, aesKeyBytes, "UTF-8");
response.set("data", new JSONObject(decryptedData));
}
return response;
}
/**
* RSA私钥解密
*
* @param encryptedData Base64编码的密文
* @param privateKey RSA私钥
* @return 明文数据
* 通过AES秘钥加密数据 - 按照demo实现
*/
public static String rsaDecrypt(String encryptedData, String privateKey) {
try {
// 移除私钥格式
String privateKeyStr = privateKey.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = Base64.decode(privateKeyStr);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PrivateKey privKey = keyFactory.generatePrivate(spec);
Cipher cipher = Cipher.getInstance(RSA_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] encrypted = Base64.decode(encryptedData);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("[rsaDecrypt] RSA解密失败: {}", e.getMessage(), e);
throw new RuntimeException("RSA解密失败", e);
}
private static String encryptData(String data, String aesKey, String charset) throws Exception {
byte[] plainBytes = data.getBytes(charset);
byte[] keyBytes = aesKey.getBytes(charset);
byte[] encryptedBytes = AESEncrypt(plainBytes, keyBytes);
return new String(Base64.getEncoder().encode(encryptedBytes), charset);
}
/**
* 构建请求数据
*
* @param data 业务数据
* @param organizNo 机构号
* @return 完整的请求数据
* 通过RSA公钥加密AES秘钥 - 按照demo实现
*/
public static String buildRequestData(JSONObject data, String organizNo) {
JSONObject request = new JSONObject();
request.set("serialNo", generateSerialNo());
request.set("version", "1.0");
request.set("timestamp", generateTimestamp());
request.set("data", data);
request.set("signature", null);
request.set("extras", null);
request.set("organizNo", organizNo);
return JSONUtil.toJsonStr(request);
private static String encrtptKey(String publicRSAKey, String aesKey, String charset) throws Exception {
byte[] keyBytes = aesKey.getBytes(charset);
PublicKey publicKey = buildRSAPublicKeyByStr(publicRSAKey);
byte[] encryptedBytes = RSAEncrypt(keyBytes, publicKey);
return new String(Base64.getEncoder().encode(encryptedBytes), charset);
}
/**
* 加密请求数据
*
* @param requestData 请求数据
* @param publicKey 旺铺公钥
* @return 加密后的请求数据
* 通过RSA私钥解密AES秘钥 - 按照demo实现
*/
public static String encryptRequest(String requestData, String publicKey) {
// 生成随机AES密钥
String aesKey = generateAESKey();
// AES加密请求数据
JSONObject requestJson = JSONUtil.parseObj(requestData);
String dataToEncrypt = JSONUtil.toJsonStr(requestJson.get("data"));
String encryptedData = aesEncrypt(dataToEncrypt, aesKey);
// RSA加密AES密钥
String encryptedAesKey = rsaEncrypt(aesKey, publicKey);
// 组装最终请求数据
requestJson.set("data", encryptedData);
requestJson.set("signature", encryptedAesKey);
return JSONUtil.toJsonStr(requestJson);
private static byte[] decryptKey(String privateRSAKey, String aesEncryptKey, String charset) throws Exception {
byte[] decodeBase64KeyBytes = Base64.getDecoder().decode(aesEncryptKey.getBytes(charset));
PrivateKey privateKey = buildRSAPrivateKeyByStr(privateRSAKey);
return RSADecrypt(decodeBase64KeyBytes, privateKey);
}
/**
* 解密响应数据
*
* @param responseData 响应数据
* @param privateKey 机构私钥
* @return 解密后的业务数据
* 通过AES秘钥解密数据 - 按照demo实现
*/
public static String decryptResponse(String responseData, String privateKey) {
try {
JSONObject responseJson = JSONUtil.parseObj(responseData);
// 获取加密的AES密钥
String encryptedAesKey = responseJson.getStr("signature");
if (StrUtil.isBlank(encryptedAesKey)) {
throw new RuntimeException("响应中缺少加密的AES密钥");
private static String decryptData(String encryptedData, byte[] aesKeyBytes, String charset) throws Exception {
byte[] decodeBase64DataBytes = Base64.getDecoder().decode(encryptedData.getBytes(charset));
byte[] decryptedBytes = AESDecrypt(decodeBase64DataBytes, aesKeyBytes);
return new String(decryptedBytes, charset);
}
// RSA解密得到AES密钥
String aesKey = rsaDecrypt(encryptedAesKey, privateKey);
// ========== AES加密/解密方法 ==========
// 获取加密的业务数据
String encryptedData = responseJson.getStr("data");
if (StrUtil.isBlank(encryptedData)) {
throw new RuntimeException("响应中缺少加密的业务数据");
private static byte[] AESEncrypt(byte[] plainBytes, byte[] keyBytes) throws Exception {
if (keyBytes.length != 16) {
throw new Exception("AES密钥必须是16位");
}
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainBytes);
}
private static byte[] AESDecrypt(byte[] encryptedBytes, byte[] keyBytes) throws Exception {
if (keyBytes.length != 16) {
throw new Exception("AES密钥必须是16位");
}
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(encryptedBytes);
}
// AES解密得到业务数据
return aesDecrypt(encryptedData, aesKey);
// ========== RSA加密/解密方法 ==========
} catch (Exception e) {
log.error("[decryptResponse] 解密响应数据失败: {}", e.getMessage(), e);
throw new RuntimeException("解密响应数据失败", e);
private static byte[] RSAEncrypt(byte[] plainBytes, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(plainBytes);
}
private static byte[] RSADecrypt(byte[] encryptedBytes, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encryptedBytes);
}
// ========== RSA密钥构建方法 ==========
/**
* 生成序列号
*/
private static String generateSerialNo() {
return System.currentTimeMillis() + RandomUtil.randomNumbers(6);
private static PublicKey buildRSAPublicKeyByStr(String key) throws Exception {
String cleanKey = key
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(cleanKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(pubX509);
}
private static PrivateKey buildRSAPrivateKeyByStr(String key) throws Exception {
String cleanKey = key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(cleanKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(priPKCS8);
}
/**
* 生成时间戳
* 生成指定位数的随机字符串 - 按照demo实现
*/
private static String generateTimestamp() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
private static String generateLenString(int length) {
char[] cResult = new char[length];
int[] flag = { 0, 0, 0 }; // A-Z, a-z, 0-9
int i = 0;
while (flag[0] == 0 || flag[1] == 0 || flag[2] == 0 || i < length) {
i = i % length;
int f = (int) (Math.random() * 3 % 3);
if (f == 0)
cResult[i] = (char) ('A' + Math.random() * 26);
else if (f == 1)
cResult[i] = (char) ('a' + Math.random() * 26);
else
cResult[i] = (char) ('0' + Math.random() * 10);
flag[f] = 1;
i++;
}
return new String(cResult);
}
}
\ No newline at end of file
......@@ -228,52 +228,65 @@ public class PayOrderServiceImpl implements PayOrderService {
}
/**
* 调用WPGJ支付API
* 调用WPGJ支付API - 按照demo代码标准实现动态支付序列码接口
*/
private PayOrderRespDTO callWpgjApi(PayOrderDO order, PayOrderExtensionDO orderExtension,
AppPayOrderSubmitReqVO reqVO, String userIp) {
try {
log.info("[callWpgjApi] 开始调用WPGJ支付API,订单号: {}", orderExtension.getNo());
// 1. 构建业务数据
// 1. 构建业务数据 - 按照demo的testDynamicOrder方法
JSONObject businessData = new JSONObject();
businessData.set("mer_no", wpgjPayProperties.getMerNo());
businessData.set("mer_code", wpgjPayProperties.getMerCode());
businessData.set("term_code", wpgjPayProperties.getTermCode());
businessData.set("out_trade_no", orderExtension.getNo());
businessData.set("pay_amount", order.getPrice());
businessData.set("goods_name", order.getSubject());
businessData.set("goods_desc", order.getBody());
businessData.set("notify_url", wpgjPayProperties.getNotifyUrl());
businessData.set("mer_order_id", orderExtension.getNo()); // 商户唯一订单号
businessData.set("order_amt", String.valueOf(order.getPrice() / 100.0)); // 转换为元,保留两位小数
businessData.set("organiz_no", wpgjPayProperties.getOrganizNo());
businessData.set("cashier_type", "1"); // 收银台模板
businessData.set("return_url", reqVO.getReturnUrl());
businessData.set("time_expire", order.getExpireTime());
businessData.set("notifyurl", wpgjPayProperties.getNotifyUrl());
// 2. 构建完整请求数据
String requestData = WpgjCryptoUtils.buildRequestData(businessData, wpgjPayProperties.getOrganizNo());
String encryptedRequest = WpgjCryptoUtils.encryptRequest(requestData, wpgjPayProperties.getPublicKey());
// 2. 构建加密请求数据 - 按照demo标准
String encryptedRequest = WpgjCryptoUtils.buildEncryptedRequest(
businessData,
wpgjPayProperties.getOrganizNo(),
wpgjPayProperties.getPublicKey()
);
// 3. 发送HTTP请求
// 3. URL编码 - 按照demo要求
String encodedRequest = java.net.URLEncoder.encode(encryptedRequest, "UTF-8");
log.info("[callWpgjApi] 发送加密请求数据: {}", encryptedRequest);
log.info("[callWpgjApi] 请求URL: {}", wpgjPayProperties.getApiUrl());
// 4. 发送HTTP请求 - 按照demo方式
HttpResponse response = HttpRequest.post(wpgjPayProperties.getApiUrl())
.header("Content-Type", "application/json")
.body(encryptedRequest)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(encodedRequest)
.timeout(30000)
.execute();
// 4. 处理响应
// 5. 处理响应
if (!response.isOk()) {
log.error("[callWpgjApi] WPGJ API调用失败,HTTP状态码: {}, 响应: {}",
response.getStatus(), response.body());
throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, "HTTP_ERROR", "支付网关调用失败");
throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, "HTTP_ERROR", "支付网关调用失败,状态码: " + response.getStatus());
}
String encryptedResponse = response.body();
String decryptedResponse = WpgjCryptoUtils.decryptResponse(encryptedResponse, wpgjPayProperties.getPrivateKey());
JSONObject responseData = JSONUtil.parseObj(decryptedResponse);
log.info("[callWpgjApi] WPGJ API调用成功,响应: {}", decryptedResponse);
String responseData = response.body();
log.info("[callWpgjApi] WPGJ API调用成功,响应: {}", responseData);
// 5. 解析响应结果
return parseWpgjApiResponse(responseData, orderExtension.getNo());
// 6. 解析响应结果 - 需要解密
JSONObject responseJson = new JSONObject(responseData);
if ("0000".equals(responseJson.getStr("code"))) {
// 解密响应数据
JSONObject decryptedResponse = WpgjCryptoUtils.decryptResponse(responseData, wpgjPayProperties.getPrivateKey());
return parseWpgjApiResponse(decryptedResponse, orderExtension.getNo());
} else {
// 处理错误响应
return parseWpgjError(responseJson, orderExtension.getNo());
}
} catch (Exception e) {
log.error("[callWpgjApi] WPGJ API调用异常,订单号: {}", orderExtension.getNo(), e);
......@@ -282,33 +295,48 @@ public class PayOrderServiceImpl implements PayOrderService {
}
/**
* 解析WPGJ API响应
* 解析WPGJ API成功响应 - 根据demo动态支付接口
*/
private PayOrderRespDTO parseWpgjApiResponse(JSONObject response, String outTradeNo) {
PayOrderRespDTO respDTO = new PayOrderRespDTO();
// 根据WPGJ API文档解析响应字段
respDTO.setOutTradeNo(outTradeNo);
respDTO.setChannelCode("wpgj_dynamic");
respDTO.setStatus(PayOrderStatusEnum.WAITING.getStatus());
// 解析二维码信息(假设WPGJ返回二维码URL)
if (response.containsKey("qr_code")) {
respDTO.setDisplayMode("qr_code");
respDTO.setDisplayContent(response.getStr("qr_code"));
} else if (response.containsKey("pay_url")) {
// 解析data字段中的payUrl(动态支付URL)
JSONObject data = response.getJSONObject("data");
if (data != null && data.containsKey("payUrl")) {
String payUrl = data.getStr("payUrl");
respDTO.setDisplayMode("pay_url");
respDTO.setDisplayContent(response.getStr("pay_url"));
respDTO.setDisplayContent(payUrl);
log.info("[parseWpgjApiResponse] 获取到支付URL: {}", payUrl);
}
// 检查是否有错误码
if (response.containsKey("resp_code") && !"00".equals(response.getStr("resp_code"))) {
respDTO.setChannelErrorCode(response.getStr("resp_code"));
respDTO.setChannelErrorMsg(response.getStr("resp_msg"));
respDTO.setRawData(JSONUtil.toJsonStr(response));
return respDTO;
}
/**
* 解析WPGJ API错误响应
*/
private PayOrderRespDTO parseWpgjError(JSONObject response, String outTradeNo) {
PayOrderRespDTO respDTO = new PayOrderRespDTO();
respDTO.setOutTradeNo(outTradeNo);
respDTO.setChannelCode("wpgj_dynamic");
respDTO.setStatus(PayOrderStatusEnum.CLOSED.getStatus()); // 错误时设置为关闭状态
// 设置错误信息
respDTO.setChannelErrorCode(response.getStr("code"));
respDTO.setChannelErrorMsg(response.getStr("msg"));
respDTO.setRawData(JSONUtil.toJsonStr(response));
log.error("[parseWpgjError] WPGJ API返回错误: code={}, msg={}",
response.getStr("code"), response.getStr("msg"));
return respDTO;
}
......
......@@ -165,16 +165,16 @@ computility:
captcha:
enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试;
pay:
# order-notify-url: https://phslgld.hnluchuan.com/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
# refund-notify-url: https://phslgld.hnluchuan.com/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
# transfer-notify-url: https://phslgld.hnluchuan.com/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
order-notify-url: https://phslgld.hnluchuan.com/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: https://phslgld.hnluchuan.com/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
transfer-notify-url: https://phslgld.hnluchuan.com/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
wpgj-pay:
# WPGJ旺铺聚合支付配置
organiz-no: 105549
mer-no: 99911325651RE1R
mer-code: K20241200111267
term-code: 1011215692596
api-url: https://stg5-qr.wpgjcs.com
api-url: https://stg5-qr.wpgjcs.com/industrial/payment/dynamic
notify-url: https://phslgld.hnluchuan.com/admin-api/pay/wpgj/notify
public-key: |
-----BEGIN PUBLIC KEY-----
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment