fix: yuzhou支付重新对接
This commit is contained in:
parent
95db7f9e30
commit
6701b8f765
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue