fix: yuzhou支付重新对接

This commit is contained in:
old burden 2026-01-22 15:10:47 +08:00
parent 95db7f9e30
commit 6701b8f765
9 changed files with 251 additions and 86 deletions

View File

@ -31,8 +31,8 @@ public class PayController {
*/ */
@GetMapping("/jinsha-pay") @GetMapping("/jinsha-pay")
@ApiOperation("jinsha请求支付") @ApiOperation("jinsha请求支付")
public AjaxResult jinShaPay(Long gearId, String cardno, String cardname) throws Exception { public AjaxResult jinShaPay(Long gearId) throws Exception {
PayResVO payResVO = jinShaService.jinShaPay(gearId, cardno, cardname); PayResVO payResVO = jinShaService.jinShaPay(gearId);
return AjaxResult.success(payResVO); return AjaxResult.success(payResVO);
} }

View File

@ -71,6 +71,12 @@
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
</dependency> </dependency>
<!-- Apache Commons Codec for Base64 encoding/decoding -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<!-- excel工具 --> <!-- excel工具 -->
<dependency> <dependency>
<groupId>org.apache.poi</groupId> <groupId>org.apache.poi</groupId>

View File

@ -1,14 +1,19 @@
package com.ruoyi.common.utils.sign; package com.ruoyi.common.utils.sign;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList; import java.util.ArrayList;
@ -16,25 +21,29 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@Slf4j
public class RSAUtils { public class RSAUtils {
// RSA最加密明 // RSA最大加密明文大小
private static final int MAX_ENCRYPT_BLOCK = 117; private static final int MAX_ENCRYPT_BLOCK = 117;
// 不仅可以使DSA算法同样也可以使RSA算法做数字签名 // 不仅可以使用DSA算法同样也可以使用RSA算法做数字签名
private static final String KEY_ALGORITHM = "RSA"; private static final String KEY_ALGORITHM = "RSA";
public static String encryptByPrivateKey(String str, String privateKey) throws Exception { public static String encryptByPrivateKey(String str, String privateKey)
throws InvalidKeySpecException, NoSuchAlgorithmException, javax.crypto.NoSuchPaddingException, java.security.InvalidKeyException,
javax.crypto.IllegalBlockSizeException, javax.crypto.BadPaddingException, UnsupportedEncodingException {
// base64编码的公钥 // base64编码的公钥
byte[] keyBytes = decryptBASE64(privateKey); byte[] keyBytes = decryptBASE64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(keyBytes)); RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(KEY_ALGORITHM)
.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
// RSA加密 // RSA加密
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, priKey); cipher.init(Cipher.ENCRYPT_MODE, priKey);
byte[] data = str.getBytes("UTF-8"); byte[] data = str.getBytes("UTF-8");
// 加密时超过117字节就报错为此采分段加密的办法来加密 // 加密时超过117字节就报错为此采分段加密的办法来加密
byte[] enBytes = null; byte[] enBytes = null;
for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) { for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
// 注意要使2的倍数否则会出现加密后的内容再解密时为乱码 // 注意要使2的倍数否则会出现加密后的内容再解密时为乱码
byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK)); byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
enBytes = ArrayUtils.addAll(enBytes, doFinal); enBytes = ArrayUtils.addAll(enBytes, doFinal);
} }
@ -43,15 +52,22 @@ public class RSAUtils {
} }
private static String encryptBASE64(byte[] data) { private static String encryptBASE64(byte[] data) {
return new String(Base64.encode(data)); return new String(Base64.encodeBase64(data));
} }
private static byte[] decryptBASE64(String data) { private static byte[] decryptBASE64(String data) {
return Base64.decode(data); return Base64.decodeBase64(data);
} }
/**
* Verify signature
*
* @param params
* @return
*/
public static boolean verifySign(JSONObject params, String publickey) { public static boolean verifySign(JSONObject params, String publickey) {
String platSign = params.getStr("signature"); // sign String platSign = params.getStr("signature"); // sign
log.info("signature:" + platSign);
List<String> paramNameList = new ArrayList<>(); List<String> paramNameList = new ArrayList<>();
for (String key : params.keySet()) { for (String key : params.keySet()) {
if (!"signature".equals(key)) { if (!"signature".equals(key)) {
@ -64,13 +80,14 @@ public class RSAUtils {
String name = paramNameList.get(i); String name = paramNameList.get(i);
stringBuilder.append(params.getStr(name)); stringBuilder.append(params.getStr(name));
} }
log.info("keys:" + stringBuilder);
String decryptSign = ""; String decryptSign = "";
try { try {
decryptSign = publicDecrypt(platSign, getPublicKey(publickey) decryptSign = publicDecrypt(platSign, getPublicKey(publickey));
);
} catch (Exception e) { } catch (Exception e) {
System.out.println(e.toString()); log.error("Error decrypting signature", e);
} }
log.info("decryptSign:" + decryptSign);
if (!stringBuilder.toString().equalsIgnoreCase(decryptSign)) { if (!stringBuilder.toString().equalsIgnoreCase(decryptSign)) {
return false; return false;
} }
@ -89,11 +106,12 @@ public class RSAUtils {
stringBuilder.append(createMap.get(key)); // 拼接参数 stringBuilder.append(createMap.get(key)); // 拼接参数
} }
String keyStr = stringBuilder.toString(); // 得到待加密的字符串 String keyStr = stringBuilder.toString(); // 得到待加密的字符串
log.info("keyStr:" + keyStr);
String signedStr = ""; String signedStr = "";
try { try {
signedStr = privateEncrypt(keyStr, getPrivateKey(MCH_PRIVATE_KEY)); // 私钥加密 signedStr = privateEncrypt(keyStr, getPrivateKey(MCH_PRIVATE_KEY)); // 私钥加密
} catch (Exception e) { } catch (Exception e) {
System.out.println(e.toString()); log.error("Error encrypting signature", e);
} }
return signedStr; return signedStr;
} }
@ -110,47 +128,70 @@ public class RSAUtils {
stringBuilder.append(createMap.get(key)); // 拼接参数 stringBuilder.append(createMap.get(key)); // 拼接参数
} }
String keyStr = stringBuilder.toString(); // 得到待加密的字符串 String keyStr = stringBuilder.toString(); // 得到待加密的字符串
log.info("keyStr:" + keyStr);
String signedStr = ""; String signedStr = "";
try { try {
signedStr = privateEncrypt(keyStr, getPrivateKey(MCH_PRIVATE_KEY)); // 私钥加密 signedStr = privateEncrypt(keyStr, getPrivateKey(MCH_PRIVATE_KEY)); // 私钥加密
} catch (Exception e) { } catch (Exception e) {
System.out.println(e.toString()); log.error("Error encrypting signature", e);
} }
return signedStr; return signedStr;
} }
/**
* private key encryption
*
* @param data
* @param privateKey
* @return
*/
public static String privateEncrypt(String data, RSAPrivateKey privateKey) { public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
try { try {
Cipher cipher = Cipher.getInstance("RSA"); Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey); cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return Base64.encode(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes("UTF-8"), privateKey.getModulus().bitLength())); return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes("UTF-8"), privateKey.getModulus().bitLength()));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Exception encountered while encry pting string [" + data + "]", e); throw new RuntimeException("Exception encountered while encrypting string [" + data + "]", e);
} }
} }
/**
* public key decryption
*/
public static String publicDecrypt(String data, RSAPublicKey publicKey) { public static String publicDecrypt(String data, RSAPublicKey publicKey) {
try { try {
Cipher cipher = Cipher.getInstance("RSA"); Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey); cipher.init(Cipher.DECRYPT_MODE, publicKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decode(data), publicKey.getModulus().bitLength()), "UTF-8"); return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), "UTF-8");
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("An exception was encountered whil e decrypting the string[" + data + "]", e); throw new RuntimeException("An exception was encountered while decrypting the string[" + data + "]", e);
} }
} }
public static RSAPrivateKey getPrivateKey(String privateKey) throws Exception { /**
* get private key
*
* @param privateKey key string (base64 encoded)
* @throws Exception
*/
public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
//Get the private key object through the PKCS#8 encoded Key instruction //Get the private key object through the PKCS#8 encoded Key instruction
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKey)); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec); RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
return key; return key;
} }
public static RSAPublicKey getPublicKey(String publicKey) throws Exception { /**
* get the public key
*
* @param publicKey key string (base64 encoded)
* @throws Exception
*/
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
//Get the public key object through the X509 encoded Key instruction //Get the public key object through the X509 encoded Key instruction
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decode(publicKey)); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec); RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
return key; return key;
} }
@ -179,7 +220,7 @@ public class RSAUtils {
offSet = i * maxBlock; offSet = i * maxBlock;
} }
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("An exception occurred when encryp ting and decrypting data whose threshold is [" + maxBlock + "]", e); throw new RuntimeException("An exception occurred when encrypting and decrypting data whose threshold is [" + maxBlock + "]", e);
} }
byte[] resultDatas = out.toByteArray(); byte[] resultDatas = out.toByteArray();
IOUtils.closeQuietly(out); IOUtils.closeQuietly(out);

View File

@ -43,6 +43,10 @@ public class AiTemplate extends BaseEntity {
@Excel(name = "模版图片URL") @Excel(name = "模版图片URL")
private String imageUrl; private String imageUrl;
/** AI类型 */
@Excel(name = "AI类型")
private Long aiId;
/** 状态0-禁用1-启用 */ /** 状态0-禁用1-启用 */
@Excel(name = "状态0-禁用1-启用") @Excel(name = "状态0-禁用1-启用")
private Integer status; private Integer status;

View File

@ -9,10 +9,8 @@ public interface IJinShaService {
/** /**
* 支付 * 支付
* @param gearId 档位ID * @param gearId 档位ID
* @param cardno 银行卡号
* @param cardname 银行卡姓名
*/ */
PayResVO jinShaPay(Long gearId, String cardno, String cardname) throws Exception; PayResVO jinShaPay(Long gearId) throws Exception;
/** /**

View File

@ -53,15 +53,7 @@ public class JinShaService implements IJinShaService {
private IExchangeRateService exchangeRateService; private IExchangeRateService exchangeRateService;
@Override @Override
public PayResVO jinShaPay(Long gearId, String cardno, String cardname) throws Exception { public PayResVO jinShaPay(Long gearId) throws Exception {
// 验证参数
if (cardno == null || cardno.trim().isEmpty()) {
throw new ServiceException("银行卡号不能为空", -1);
}
if (cardname == null || cardname.trim().isEmpty()) {
throw new ServiceException("银行卡姓名不能为空", -1);
}
//充值挡位查询 //充值挡位查询
AiRechargeGiftGear aiRechargeGiftGear = aiRechargeGiftGearService.selectAiRechargeGiftGearById(gearId); AiRechargeGiftGear aiRechargeGiftGear = aiRechargeGiftGearService.selectAiRechargeGiftGearById(gearId);
@ -77,16 +69,20 @@ public class JinShaService implements IJinShaService {
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date()); String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
String orderNo = dateTime + "js" + uuid; String orderNo = dateTime + "js" + uuid;
// 金额转换为int类型去除小数部分 // 金额转换为number类型保留两位小数
int amountInt = amount.intValue(); String amountStr = String.format("%.2f", amount.doubleValue());
// 1. 配置签名参数按照key排序不包含notify_url和sign // 构建回调地址和返回地址
String notifyUrlFull = notifyUrl + "/api/pay/jinsha-callBack";
String returnUrlFull = returnUrl + "/recharge";
// 1. 配置签名参数按照key排序appid, amount, orderno, notify_url, return_url不包含sign
TreeMap<String, Object> signParams = new TreeMap<>(); TreeMap<String, Object> signParams = new TreeMap<>();
signParams.put("appid", appId); signParams.put("appid", appId);
signParams.put("amount", amountInt); signParams.put("amount", amountStr);
signParams.put("orderno", orderNo); signParams.put("orderno", orderNo);
signParams.put("cardno", cardno); signParams.put("notify_url", notifyUrlFull);
signParams.put("cardname", cardname); signParams.put("return_url", returnUrlFull);
// 2. 生成签名 // 2. 生成签名
String sign = this.generateSign(signParams); String sign = this.generateSign(signParams);
@ -94,20 +90,21 @@ public class JinShaService implements IJinShaService {
// 3. 构建请求参数使用FormBody.Builder自动处理URL编码 // 3. 构建请求参数使用FormBody.Builder自动处理URL编码
FormBody.Builder formBuilder = new FormBody.Builder(); FormBody.Builder formBuilder = new FormBody.Builder();
formBuilder.add("appid", appId); formBuilder.add("appid", appId);
formBuilder.add("amount", String.valueOf(amountInt)); formBuilder.add("amount", amountStr);
formBuilder.add("orderno", orderNo); formBuilder.add("orderno", orderNo);
formBuilder.add("cardno", cardno); formBuilder.add("notify_url", notifyUrlFull);
formBuilder.add("cardname", cardname); formBuilder.add("return_url", returnUrlFull);
formBuilder.add("notify_url", notifyUrl + "/api/pay/jinsha-callBack");
formBuilder.add("sign", sign); formBuilder.add("sign", sign);
RequestBody requestBody = formBuilder.build(); RequestBody requestBody = formBuilder.build();
// 5. 构建 POST 请求
// 4. 构建 POST 请求
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url + "/api/pay") .url(url + "/api/pay")
.header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8") .header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
.post(requestBody) .post(requestBody)
.build(); .build();
// 6. 发送请求并获取响应
// 5. 发送请求并获取响应
Response response = OkHttpUtils.newCall(request).execute(); Response response = OkHttpUtils.newCall(request).execute();
ResponseBody body = response.body(); ResponseBody body = response.body();
if (null == body) { if (null == body) {
@ -126,6 +123,16 @@ public class JinShaService implements IJinShaService {
throw new ServiceException(msg != null ? msg : "支付请求失败", code != null ? code : -1); throw new ServiceException(msg != null ? msg : "支付请求失败", code != null ? code : -1);
} }
// 检查返回的payurl
String payUrl = null;
if (jinShaBodyRes.getData() != null) {
payUrl = jinShaBodyRes.getData().getPayurl();
}
if (payUrl == null || payUrl.trim().isEmpty()) {
log.error("jinsha支付返回的payurl为空订单号: {}", orderNo);
throw new ServiceException("支付返回的支付链接为空", -1);
}
// 创建充值管理 // 创建充值管理
AiUser userInfo = aiUserService.getUserInfo(SecurityUtils.getAiUserId()); AiUser userInfo = aiUserService.getUserInfo(SecurityUtils.getAiUserId());
AiRecharge aiRecharge = new AiRecharge(); AiRecharge aiRecharge = new AiRecharge();
@ -137,12 +144,12 @@ public class JinShaService implements IJinShaService {
aiRecharge.setSource(userInfo.getSource()); aiRecharge.setSource(userInfo.getSource());
AiRechargeGift aiRechargeGift = aiRechargeGiftService.selectAiRechargeGiftById(aiRechargeGiftGear.getRechargeId()); AiRechargeGift aiRechargeGift = aiRechargeGiftService.selectAiRechargeGiftById(aiRechargeGiftGear.getRechargeId());
aiRecharge.setPayType(aiRechargeGift.getPayType()); aiRecharge.setPayType(aiRechargeGift.getPayType());
// jinsha支付成功返回后不需要支付URL订单状态由回调更新 aiRecharge.setPayUrl(payUrl);
aiRechargeService.insertAiRecharge(aiRecharge); aiRechargeService.insertAiRecharge(aiRecharge);
PayResVO payResVO = new PayResVO(); PayResVO payResVO = new PayResVO();
payResVO.setOrderNo(orderNo); payResVO.setOrderNo(orderNo);
// jinsha支付成功后不需要跳转支付页面直接返回订单号 payResVO.setPayUrl(payUrl);
return payResVO; return payResVO;
} }

View File

@ -60,37 +60,111 @@ public class YuZhouService implements IYuZhouService {
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date()); String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
String orderNo = dateTime + "yz" + uuid; String orderNo = dateTime + "yz" + uuid;
// 获取用户信息
AiUser userInfo = aiUserService.getUserInfo(SecurityUtils.getAiUserId());
// 处理用户姓名如果没有则使用默认值
String firstName = "John";
String lastName = "Doe";
if (userInfo.getNickname() != null && !userInfo.getNickname().trim().isEmpty()) {
String[] nameParts = userInfo.getNickname().trim().split("\\s+");
if (nameParts.length >= 2) {
firstName = nameParts[0];
lastName = nameParts[nameParts.length - 1];
} else if (nameParts.length == 1) {
firstName = nameParts[0];
}
}
// 从用户表获取邮箱
String email = userInfo.getEmail();
if (email == null || email.trim().isEmpty()) {
log.warn("用户邮箱为空 userId: {}, nickname: {}", userInfo.getId(), userInfo.getNickname());
// 如果用户邮箱为空使用默认邮箱格式
email = "user" + userInfo.getId() + "@example.com";
} else {
email = email.trim();
}
// 处理电话如果没有则使用默认值
String phone = userInfo.getPhone();
if (phone == null || phone.trim().isEmpty()) {
phone = "1234567890";
} else {
// 确保电话是10位数字
phone = phone.replaceAll("[^0-9]", "");
if (phone.length() < 10) {
// 如果不足10位用0补齐
phone = String.format("%010d", phone.length() > 0 ? Long.parseLong(phone) : 0);
} else if (phone.length() > 10) {
// 如果超过10位取后10位
phone = phone.substring(phone.length() - 10);
}
}
// 构建请求参数
Map<String, Object> createMap = new HashMap<>(); Map<String, Object> createMap = new HashMap<>();
createMap.put("merchantNo", appId); createMap.put("merchantNo", appId);
createMap.put("orderNo", orderNo); createMap.put("orderNo", orderNo);
createMap.put("money", amount.toString()); createMap.put("money", amount.toString());
createMap.put("description", "test"); createMap.put("description", "Recharge");
createMap.put("name", "test"); createMap.put("email", email);
createMap.put("email", "test@gmail.com"); createMap.put("phone", phone);
createMap.put("callbackUrl", callbackUrl + "/api/pay/yuzhou-callBack"); createMap.put("notifyUrl", callbackUrl + "/api/pay/yuzhou-callBack");
createMap.put("phone", "7383442114");
createMap.put("expiredPeriod", "1440"); createMap.put("expiredPeriod", "1440");
createMap.put("redirectUrl", redirectUrl + "/recharge"); createMap.put("currency", "USD");
createMap.put("firstName", firstName);
createMap.put("lastName", lastName);
createMap.put("street", "123 Main Street");
createMap.put("city", "New York");
createMap.put("state", "NY");
createMap.put("country", userInfo.getCountry() != null && !userInfo.getCountry().trim().isEmpty() ? userInfo.getCountry() : "US");
createMap.put("postcode", "10001");
// 生成签名注意签名时不包括signature字段
String signedStr = RSAUtils.getSignStr(createMap, secretKey); String signedStr = RSAUtils.getSignStr(createMap, secretKey);
createMap.put("signature", signedStr); createMap.put("signature", signedStr);
String postStr = JSONUtil.toJsonStr(createMap); String postStr = JSONUtil.toJsonStr(createMap);
String response = HttpRequest.post(url + "/gateway/order/US/payIn").header("Content-Type", "application/json") log.info("yuzhou支付请求参数: {}", postStr);
.body(postStr).execute().body();
// 调用新的接口地址
String response = HttpRequest.post(url + "/gateway/US/payIn")
.header("Content-Type", "application/json; charset=utf-8")
.body(postStr)
.execute()
.body();
log.info("yuzhou支付响应: {}", response);
JSONObject returnObj = JSONUtil.parseObj(response); JSONObject returnObj = JSONUtil.parseObj(response);
Integer code = returnObj.getInt("code"); Integer code = returnObj.getInt("code");
if (200 != code) { if (200 != code) {
log.error("yuzhou支付请求的响应异常 {}", returnObj); String msg = returnObj.getStr("msg");
throw new Exception("yuzhoupay responsebody is null"); log.error("yuzhou支付请求的响应异常 code: {}, msg: {}, response: {}", code, msg, returnObj);
throw new ServiceException("yuzhoupay request failed: " + (msg != null ? msg : "unknown error"), code);
} }
JSONObject data = returnObj.getJSONObject("data"); JSONObject data = returnObj.getJSONObject("data");
if (data == null) {
log.error("yuzhou支付响应数据为空: {}", returnObj);
throw new ServiceException("yuzhoupay response data is null", -1);
}
String money = data.getStr("money"); String money = data.getStr("money");
String payUrl = data.getStr("url"); String payUrl = data.getStr("url");
String platformOrderNo = data.getStr("platformOrderNo");
if (payUrl == null || payUrl.trim().isEmpty()) {
log.error("yuzhou支付URL为空: {}", data);
throw new ServiceException("yuzhoupay url is null", -1);
}
// 创建充值管理 // 创建充值管理
AiUser userInfo = aiUserService.getUserInfo(SecurityUtils.getAiUserId());
AiRecharge aiRecharge = new AiRecharge(); AiRecharge aiRecharge = new AiRecharge();
aiRecharge.setOrderNum(orderNo); aiRecharge.setOrderNum(orderNo);
aiRecharge.setUserId(SecurityUtils.getAiUserId()); aiRecharge.setUserId(SecurityUtils.getAiUserId());
aiRecharge.setAmount(new BigDecimal(money)); aiRecharge.setAmount(new BigDecimal(money != null ? money : amount.toString()));
aiRecharge.setGearId(gearId); aiRecharge.setGearId(gearId);
aiRecharge.setSource(userInfo.getSource()); aiRecharge.setSource(userInfo.getSource());
aiRecharge.setGearAmount(aiRechargeGiftGear.getRechargeAmount()); aiRecharge.setGearAmount(aiRechargeGiftGear.getRechargeAmount());
@ -98,6 +172,7 @@ public class YuZhouService implements IYuZhouService {
aiRecharge.setPayType(aiRechargeGift.getPayType()); aiRecharge.setPayType(aiRechargeGift.getPayType());
aiRecharge.setPayUrl(payUrl); aiRecharge.setPayUrl(payUrl);
aiRechargeService.insertAiRecharge(aiRecharge); aiRechargeService.insertAiRecharge(aiRecharge);
PayResVO payResVO = new PayResVO(); PayResVO payResVO = new PayResVO();
payResVO.setOrderNo(orderNo); payResVO.setOrderNo(orderNo);
payResVO.setPayUrl(payUrl); payResVO.setPayUrl(payUrl);
@ -106,31 +181,60 @@ public class YuZhouService implements IYuZhouService {
@Override @Override
public String callBack(Map<String, Object> map) throws Exception { public String callBack(Map<String, Object> map) throws Exception {
int code = Integer.parseInt(map.get("status").toString()); log.info("yuzhou支付回调接收参数: {}", map);
String orderNo = map.get("orderNo").toString();
if (10 != code) { // 验证必要参数
log.error("yuzhou支付失败 {}", orderNo); if (map == null || map.isEmpty()) {
return null; log.error("yuzhou支付回调参数为空");
return "FAIL";
} }
// Map<String, Object> createMap = new HashMap<>();
// createMap.put("orderNo", orderNo); Object statusObj = map.get("status");
// createMap.put("platOrderNo", map.get("platOrderNo")); Object orderNoObj = map.get("orderNo");
// createMap.put("money", map.get("money"));
// createMap.put("fee", map.get("fee")); if (statusObj == null || orderNoObj == null) {
// createMap.put("status", map.get("status")); log.error("yuzhou支付回调缺少必要参数: status={}, orderNo={}", statusObj, orderNoObj);
// createMap.put("message", map.get("message")); return "FAIL";
String signature = map.get("signature").toString(); }
int status = Integer.parseInt(statusObj.toString());
String orderNo = orderNoObj.toString();
log.info("yuzhou支付回调 orderNo: {}, status: {}", orderNo, status);
// 验证签名
Object signatureObj = map.get("signature");
if (signatureObj == null) {
log.error("yuzhou支付回调缺少签名参数 orderNo: {}", orderNo);
return "FAIL";
}
JSONObject entries = JSONUtil.parseObj(map); JSONObject entries = JSONUtil.parseObj(map);
boolean b = RSAUtils.verifySign(entries, publicKey); boolean verifyResult = RSAUtils.verifySign(entries, publicKey);
if (!b) { if (!verifyResult) {
log.error("yuzhou支付回调签名错误 {}", orderNo); log.error("yuzhou支付回调签名验证失败 orderNo: {}", orderNo);
log.error("yuzhou支付回调签名 {}", signature); log.error("yuzhou支付回调签名: {}", signatureObj);
log.error("yuzhou支付回调报文 {}", map); log.error("yuzhou支付回调完整报文: {}", map);
return null; return "FAIL";
} }
//充值成功处理
log.info("yuzhou支付回调签名验证成功 orderNo: {}", orderNo);
// 判断订单状态10表示成功
if (10 != status) {
log.warn("yuzhou支付订单状态非成功 orderNo: {}, status: {}", orderNo, status);
return "SUCCESS"; // 即使失败也返回SUCCESS避免重复回调
}
// 充值成功处理
try {
aiRechargeService.addRecharge(orderNo); aiRechargeService.addRecharge(orderNo);
log.info("yuzhou支付回调处理成功 orderNo: {}", orderNo);
return "SUCCESS"; return "SUCCESS";
} catch (Exception e) {
log.error("yuzhou支付回调处理失败 orderNo: {}", orderNo, e);
return "FAIL";
}
} }

View File

@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="chineseContent" column="chinese_content" /> <result property="chineseContent" column="chinese_content" />
<result property="englishContent" column="english_content" /> <result property="englishContent" column="english_content" />
<result property="imageUrl" column="image_url" /> <result property="imageUrl" column="image_url" />
<result property="aiId" column="ai_id" />
<result property="status" column="status" /> <result property="status" column="status" />
<result property="remark" column="remark" /> <result property="remark" column="remark" />
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
@ -20,7 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectAiTemplateVo"> <sql id="selectAiTemplateVo">
select id, name, chinese_content, english_content, image_url, status, remark, create_time, create_by, update_by, update_time, del_flag from ai_template select id, name, chinese_content, english_content, image_url, ai_id, status, remark, create_time, create_by, update_by, update_time, del_flag from ai_template
</sql> </sql>
<select id="selectAiTemplateList" parameterType="AiTemplate" resultMap="AiTemplateResult"> <select id="selectAiTemplateList" parameterType="AiTemplate" resultMap="AiTemplateResult">
@ -30,6 +31,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="chineseContent != null and chineseContent != ''"> and chinese_content = #{chineseContent}</if> <if test="chineseContent != null and chineseContent != ''"> and chinese_content = #{chineseContent}</if>
<if test="englishContent != null and englishContent != ''"> and english_content = #{englishContent}</if> <if test="englishContent != null and englishContent != ''"> and english_content = #{englishContent}</if>
<if test="imageUrl != null and imageUrl != ''"> and image_url = #{imageUrl}</if> <if test="imageUrl != null and imageUrl != ''"> and image_url = #{imageUrl}</if>
<if test="aiId != null and aiId != ''"> and ai_id = #{aiId}</if>
<if test="status != null "> and status = #{status}</if> <if test="status != null "> and status = #{status}</if>
</where> </where>
</select> </select>
@ -46,6 +48,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="chineseContent != null">chinese_content,</if> <if test="chineseContent != null">chinese_content,</if>
<if test="englishContent != null">english_content,</if> <if test="englishContent != null">english_content,</if>
<if test="imageUrl != null">image_url,</if> <if test="imageUrl != null">image_url,</if>
<if test="aiId != null">ai_id,</if>
<if test="status != null">status,</if> <if test="status != null">status,</if>
<if test="remark != null">remark,</if> <if test="remark != null">remark,</if>
<if test="createTime != null">create_time,</if> <if test="createTime != null">create_time,</if>
@ -59,6 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="chineseContent != null">#{chineseContent},</if> <if test="chineseContent != null">#{chineseContent},</if>
<if test="englishContent != null">#{englishContent},</if> <if test="englishContent != null">#{englishContent},</if>
<if test="imageUrl != null">#{imageUrl},</if> <if test="imageUrl != null">#{imageUrl},</if>
<if test="aiId != null">#{aiId},</if>
<if test="status != null">#{status},</if> <if test="status != null">#{status},</if>
<if test="remark != null">#{remark},</if> <if test="remark != null">#{remark},</if>
<if test="createTime != null">#{createTime},</if> <if test="createTime != null">#{createTime},</if>
@ -76,6 +80,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="chineseContent != null">chinese_content = #{chineseContent},</if> <if test="chineseContent != null">chinese_content = #{chineseContent},</if>
<if test="englishContent != null">english_content = #{englishContent},</if> <if test="englishContent != null">english_content = #{englishContent},</if>
<if test="imageUrl != null">image_url = #{imageUrl},</if> <if test="imageUrl != null">image_url = #{imageUrl},</if>
<if test="aiId != null">ai_id = #{aiId},</if>
<if test="status != null">status = #{status},</if> <if test="status != null">status = #{status},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
<if test="createTime != null">create_time = #{createTime},</if> <if test="createTime != null">create_time = #{createTime},</if>

View File

@ -2866,7 +2866,7 @@ CREATE TABLE `ai_recharge` (
`gift_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '到账金额', `gift_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '到账金额',
`give_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '赠送金额', `give_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '赠送金额',
`pay_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付方式', `pay_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付方式',
`pay_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付链接', `pay_url` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付链接',
PRIMARY KEY (`id`) USING BTREE PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 118 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '充值记录' ROW_FORMAT = DYNAMIC; ) ENGINE = InnoDB AUTO_INCREMENT = 118 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '充值记录' ROW_FORMAT = DYNAMIC;