This commit is contained in:
zhengziru
2026-08-25 11:24:08 +08:00
10 changed files with 575 additions and 126 deletions
@@ -238,6 +238,7 @@ public class BankChinaWxPayService {
String wcPayDataSignType = XmlUtil.getNodeText(document, "wcPayDataSignType");
// 签名
String wcPayDataPaySign = XmlUtil.getNodeText(document, "wcPayDataPaySign");
String qrCode = XmlUtil.getNodeText(document, "qrCode");
return new WxAppPayResponse().setOrderSeq(orderSeq)
.setTranStatus(tranStatus)
.setTradeType(tradeType)
@@ -251,7 +252,7 @@ public class BankChinaWxPayService {
.setWcPayDataNonceStr(wcPayDataNonceStr)
.setWcPayDataPackage(wcPayDataPackage)
.setWcPayDataSignType(wcPayDataSignType)
.setWcPayDataPaySign(wcPayDataPaySign);
.setWcPayDataPaySign(wcPayDataPaySign).setQrCode(qrCode);
}
private void checkConfigPayProperties() {
@@ -2,24 +2,25 @@ package io.v.nutz.pay.bank.china.controller;
import io.v.nutz.pay.annotations.Api;
import io.v.nutz.pay.annotations.ApiOperation;
import io.v.nutz.pay.bank.china.property.ChinaPayProperties;
import io.v.nutz.pay.bank.china.sign.BankPaySign;
import io.v.nutz.pay.cache.ChinaBankOrderWaitPayCacheService;
import io.v.nutz.pay.record.service.IMemberPayRecordService;
import io.v.nutz.pay.util.XmlUtil;
import lombok.extern.slf4j.Slf4j;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.w3c.dom.Document;
import javax.servlet.http.HttpServletRequest;
/**
* 支付通知 controller
*/
@Slf4j
@IocBean
@Api("支付通知")
@At("/notify")
@Slf4j
public class BankNotifyController
{
@@ -34,6 +35,9 @@ public class BankNotifyController
@Inject
private ChinaBankOrderWaitPayCacheService chinaBankOrderWaitPayCacheService;
@Inject
private ChinaPayProperties chinaPayProperties;
@Ok("json")
@At("/china/bank/pay")
@@ -45,6 +49,42 @@ public class BankNotifyController
String signature = request.getParameter("signature");
log.info("[message]=[" + message + "]");
log.info("[signature]=[" + signature + "]");
try
{
String plainText = new String(io.v.nutz.pay.util.Base64.decode(message), "UTF-8");
log.debug("[base64解码后message]=[" + plainText + "]");
chinaPayProperties.load();
boolean isValid = BankPaySign.getInstance().verifySignature(
chinaPayProperties.getVerifyCerFile(),
signature,
plainText
);
if(!isValid) {
log.error("[VERIFY FAILED] 中国银行支付通知验签失败");
throw new RuntimeException("签名验证失败");
}
log.info("[VERIFY OK] 中国银行支付通知验签成功");
Document document = XmlUtil.createDocument(plainText);
String responseCode = XmlUtil.getNodeText(document, "responseCode");
if("OK".equals(responseCode)){
String orderNo = XmlUtil.getNodeText(document, "orderNo");
String tranStatus = XmlUtil.getNodeText(document, "tranStatus");
String payCatalog = XmlUtil.getNodeText(document, "payCatalog");
log.debug("[orderNo]=["+orderNo+"] [tranStatus]=["+ tranStatus+"] [payCatalog]=["+payCatalog+"]");
request.setAttribute("result",responseCode+",Notice Success!");
}
}
catch (Exception e){
log.error("中国银行支付通知处理异常:{}", e.getMessage());
e.printStackTrace();
}
}
@@ -14,6 +14,9 @@ public class WxAppPayResponse implements Serializable {
// 银行订单流水号 银行的订单流水号(银行产生的订单唯一标识)
private String orderSeq;
// 订单号
private String orderNo;
// 下单状态 1:成功 2:失败
private String tranStatus;
@@ -52,4 +55,7 @@ public class WxAppPayResponse implements Serializable {
// 签名
private String wcPayDataPaySign;
// 二维码
private String qrCode;
}
@@ -4,9 +4,14 @@ import cn.hutool.core.util.StrUtil;
import io.v.nutz.pay.util.PKCS7Tool;
import lombok.extern.slf4j.Slf4j;
/**
* 中国银行支付签名
*/
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.util.Enumeration;
@Slf4j
public class BankPaySign {
@@ -15,14 +20,6 @@ public class BankPaySign {
return new BankPaySign();
}
/**
* 生成签名数据
* @param signKeyStorePath 证书库路径
* @param signKeyStorePassword 证书库口令
* @param signKeyPassword 签名私钥口令,与证书库口令相同
* @param businessParams 业务参数 参数格式 param|param|param
* @return signData
*/
public String getSinData(String signKeyStorePath, String signKeyStorePassword, String signKeyPassword, String businessParams) {
if(StrUtil.isBlank(signKeyStorePath)) {
throw new RuntimeException("缺少证书库路径");
@@ -43,12 +40,59 @@ public class BankPaySign {
byte[] businessDataBytes = businessParams.getBytes("UTF-8");
String signData = tool.signature(signKeyStorePath, signKeyPassword, businessDataBytes);
log.info("签名数据:signData-{}", signData);
// Boolean signDataBoolean = tool.verifySignature(signKeyStorePath, signKeyPassword, businessDataBytes, Base64.getDecoder().decode(signData));
// log.info("签名数据生成状态: signDataBoolean-{}", signDataBoolean);
return signData;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
public boolean verifySignature(String verifyCerFilePath, String signature, String plainText) {
if(StrUtil.isBlank(verifyCerFilePath)) {
throw new RuntimeException("缺少验签证书文件路径");
}
if(StrUtil.isBlank(signature)) {
throw new RuntimeException("缺少签名数据");
}
if(plainText == null) {
plainText = "";
}
log.info("验签信息:verifyCerFilePath-{}, signature-{}, plainText-{}", verifyCerFilePath, signature, plainText);
try {
byte[] plainTextBytes = plainText.getBytes("UTF-8");
byte[] signatureBytes = io.v.nutz.pay.util.Base64.decode(signature);
InputStream fis4cer = new FileInputStream(verifyCerFilePath);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(fis4cer, null);
fis4cer.close();
Enumeration<String> aliases = keyStore.aliases();
String alias = null;
while (aliases.hasMoreElements()) {
alias = aliases.nextElement();
}
Certificate cert = keyStore.getCertificate(alias);
PublicKey publicKey = cert.getPublicKey();
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(publicKey);
sig.update(plainTextBytes);
boolean isValid = sig.verify(signatureBytes);
if(isValid) {
log.info("[VERIFY OK] 中国银行支付验签成功");
} else {
log.error("[VERIFY FAILED] 中国银行支付验签失败");
}
return isValid;
} catch (Exception e) {
e.printStackTrace();
log.error("中国银行支付验签异常:{}", e.getMessage());
throw new RuntimeException("验签失败: " + e.getMessage());
}
}
}
@@ -113,4 +113,9 @@ public class MemberPayRecord implements Serializable {
@Column(value = "device_ip")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String deviceIp;
@Comment("设备IP")
@Column(value = "qr_code")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String qrCode;
}
@@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.v.nutz.base.service.impl.BaseServiceImpl;
import io.v.nutz.pay.bank.china.BankChinaWxPayService;
import io.v.nutz.pay.bank.china.pojo.WxAppPayResponse;
import io.v.nutz.pay.bank.china.pojo.WxPayOrderQueryResponse;
import io.v.nutz.pay.bank.china.property.ChinaPayProperties;
import io.v.nutz.pay.cache.ChinaBankOrderPayNotifyCacheService;
@@ -20,7 +21,11 @@ import io.v.nutz.pay.order.service.IMemberPaymentOrderService;
import io.v.nutz.pay.record.module.MemberPayRecord;
import io.v.nutz.pay.record.service.IMemberPayRecordService;
import io.v.nutz.pay.util.BillNoUtil;
import io.v.nutz.zhgh.memberCost.model.MemberPaymentInfo;
import io.v.nutz.zhgh.memberCost.service.MemberPaymentService;
import io.v.nutz.zhgh.memberCost.service.impl.MemberPaymentServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
@@ -31,7 +36,8 @@ import java.util.List;
@Slf4j
@IocBean(args = {"refer:dao"})
public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord> implements IMemberPayRecordService {
public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord> implements IMemberPayRecordService
{
@Inject
private ChinaPayProperties chinaPayProperties;
@@ -42,12 +48,16 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
@Inject
private BankChinaWxPayService bankChinaWxPayService;
@Inject
private MemberPaymentService memberPaymentService;
@Inject
private ChinaBankOrderWaitPayCacheService chinaBankOrderWaitPayCacheService;
@Inject
private ChinaBankOrderPayNotifyCacheService chinaBankOrderPayNotifyCacheService;
public MemberPayRecordServiceImpl(Dao dao) {
super(dao);
}
@@ -57,9 +67,12 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
Sql sql = Sqls.create("select * from member_pay_record where pay_code = @pay_code limit 0,1");
sql.setParam("pay_code", code);
List<MemberPayRecord> records = this.listEntity(sql);
if(records != null && records.size() > 0) {
if (records != null && records.size() > 0)
{
return records.get(0);
} else {
}
else
{
return null;
}
}
@@ -69,9 +82,12 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
Sql sql = Sqls.create("select * from member_pay_record where id = @id limit 0,1");
sql.params().set("id", id);
List<MemberPayRecord> records = this.listEntity(sql);
if(records != null && records.size() > 0) {
if (records != null && records.size() > 0)
{
return records.get(0);
} else {
}
else
{
return null;
}
}
@@ -81,9 +97,12 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
Sql sql = Sqls.create("select * from member_pay_record where goods_trade_code = @goods_trade_code limit 0,1");
sql.params().set("goods_trade_code", goodsTradeCode);
List<MemberPayRecord> records = this.listEntity(sql);
if(records != null && records.size() > 0) {
if (records != null && records.size() > 0)
{
return records.get(0);
} else {
}
else
{
return null;
}
}
@@ -92,7 +111,8 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
public List<MemberPayRecord> getByGoodsTradeCode(Object... goodsTradeOrders) {
Sql sql = Sqls.create("select * from member_pay_record where $goodsTradeOrders");
StringBuilder where = new StringBuilder();
for(int i = 0; i < goodsTradeOrders.length; i++) {
for (int i = 0; i < goodsTradeOrders.length; i++)
{
where.append("goods_trade_code='" + goodsTradeOrders[i].toString() + "' or ");
}
String whereStr = where.toString().substring(0, where.toString().lastIndexOf("or"));
@@ -117,25 +137,34 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
record.setDeviceIp(deviceIp);
// 中国银行支付中商品交易单号需要特殊处理 该单号用于发起支付-> 商户订单号
if(PayChannelEnum.CHINA_BANK.getCode().equals(payChannel)) {
if (PayChannelEnum.CHINA_BANK.getCode().equals(payChannel))
{
record.setGoodsTradeCode(chinaPayProperties.getMerchantNoAfterChar(6).concat(BillNoUtil.getTimeNo(24)));
} else {
}
else
{
record.setGoodsTradeCode(record.getPayCode());
}
// 会员工会缴费
if(MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(businessType)) {
// 会员工会缴费订单信息
MemberPaymentOrder paymentOrder = memberPaymentOrderService.getById(businessId);
if(paymentOrder == null) {
throw new RuntimeException("未获取到会员工会缴费订单信息");
}
record.setBusinessOrderCode(paymentOrder.getOrderCode());
record.setPayMoney(paymentOrder.getMoney());
record.setMemberId(paymentOrder.getMemberId());
record.setPayRemark("工会会员缴费-订单:" + paymentOrder.getOrderCode());
if (MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(businessType))
{
} else {
List<MemberPaymentInfo> paymentUserList = memberPaymentService.query(Cnd.where("id", "=", businessId));
if (paymentUserList == null || paymentUserList.isEmpty())
{
throw new RuntimeException("缴费记录未找到");
}
MemberPaymentInfo memberPaymentInfo = paymentUserList.get(0);
record.setBusinessOrderCode(memberPaymentInfo.getTradeNo());
record.setPayMoney(Double.valueOf(memberPaymentInfo.getPaymentMoney()));
record.setMemberId(memberPaymentInfo.getUserId());
record.setPayRemark("工会会员缴费-订单:" + memberPaymentInfo.getLoginname());
}
else
{
throw new RuntimeException("不支持的支付业务");
}
this.insert(record);
@@ -145,7 +174,8 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
@Override
public void paySuccess(String goodsTradeCode, Double oriPayAmount) {
MemberPayRecord record = this.getByGoodsTradeCode(goodsTradeCode);
if(record == null) {
if (record == null)
{
throw new RuntimeException("商品交易单号不存在");
}
record.setStatus(MemberPayStatusEnum.SUCCESS.getCode());
@@ -153,10 +183,13 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
this.updateById(record);
// 会员工会缴费
if(MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(record.getBusinessType())) {
if (MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(record.getBusinessType()))
{
// 支付成功
memberPaymentOrderService.paySuccess(record.getBusinessId(), oriPayAmount);
} else {
}
else
{
throw new RuntimeException("不支持的业务支付通知");
}
}
@@ -164,7 +197,8 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
@Override
public void payFail(String goodsTradeCode, String failReason) {
MemberPayRecord record = this.getByGoodsTradeCode(goodsTradeCode);
if(record == null) {
if (record == null)
{
throw new RuntimeException("商品交易单号不存在");
}
record.setStatus(MemberPayStatusEnum.FAIL.getCode());
@@ -173,73 +207,93 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
this.updateById(record);
// 会员工会缴费
if(MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(record.getBusinessType())) {
if (MemberPayBusinessTypeEnum.UNION_PAYMENT.getCode().equals(record.getBusinessType()))
{
// 支付成功
memberPaymentOrderService.payFail(record.getBusinessId());
} else {
}
else
{
throw new RuntimeException("不支持的业务支付通知");
}
}
@Override
public void checkChinaPayOrderStatus() {
try {
try
{
// 初始化配置参数
chinaPayProperties.load();
String goodsTradeOrder = chinaBankOrderWaitPayCacheService.get();
if (StrUtil.isBlank(goodsTradeOrder)) {
if (StrUtil.isBlank(goodsTradeOrder))
{
log.info("未检测中国银行支付订单:null");
return;
}
List<String> goodsTradeOrders = JSON.parseArray(goodsTradeOrder, String.class);
log.info("检测中国银行支付订单:{}", goodsTradeOrders.size());
if (goodsTradeOrders.size() == 0) {
if (goodsTradeOrders.size() == 0)
{
return;
}
List<MemberPayRecord> payRecords = this.getByGoodsTradeCode(goodsTradeOrders.toArray());
if (payRecords == null || payRecords.isEmpty()) {
if (payRecords == null || payRecords.isEmpty())
{
return;
}
for (MemberPayRecord record : payRecords) {
if (!record.getStatus().equals(MemberPayStatusEnum.WAIT.getCode())) {
for (MemberPayRecord record : payRecords)
{
if (!record.getStatus().equals(MemberPayStatusEnum.WAIT.getCode()))
{
continue;
}
if (!record.getPayChannel().equals(PayChannelEnum.CHINA_BANK.getCode())) {
if (!record.getPayChannel().equals(PayChannelEnum.CHINA_BANK.getCode()))
{
continue;
}
WxPayOrderQueryResponse response = bankChinaWxPayService.queryOrder(record.getGoodsTradeCode());
if(chinaBankOrderPayNotifyCacheService.exists(record.getGoodsTradeCode())) {
if (chinaBankOrderPayNotifyCacheService.exists(record.getGoodsTradeCode()))
{
continue;
}
chinaBankOrderPayNotifyCacheService.setex(record.getGoodsTradeCode(), 30, record.getGoodsTradeCode());
if(response.getTranStatus() != null
&&(response.getTranStatus().equals(CommonConstants.ONE.toString()) || response.getTranStatus().equals(CommonConstants.TWO.toString()))) {
if (response.getTranStatus() != null
&& (response.getTranStatus().equals(CommonConstants.ONE.toString()) || response.getTranStatus().equals(CommonConstants.TWO.toString())))
{
// 支付成功
if(response.getTranStatus().equals(CommonConstants.ONE.toString())) {
if (response.getTranStatus().equals(CommonConstants.ONE.toString()))
{
this.paySuccess(response.getOrderNo(), Double.valueOf(response.getTranAmount()));
// 支付失败
} else {
// 支付失败
}
else
{
this.payFail(response.getOrderNo(), "支付失败");
}
chinaBankOrderWaitPayCacheService.delItem(record.getGoodsTradeCode());
}
}
} catch (Exception e) {
}
catch (Exception e)
{
e.printStackTrace();
log.error("中国银行支付检测待支付订单异常:{}", e.getMessage());
}
}
private MemberPayRecord sendPay(MemberPayRecord record) {
try {
Object requestResult = null;
try
{
WxAppPayResponse requestResult = null;
// 中国银行支付渠道
if (PayChannelEnum.CHINA_BANK.getCode().equals(record.getPayChannel())) {
if (PayChannelEnum.CHINA_BANK.getCode().equals(record.getPayChannel()))
{
// 微信APP支付
if (ChannelTypeEnum.WX_APP.getCode().equals(record.getChannelType())) {
if (ChannelTypeEnum.WX_APP.getCode().equals(record.getChannelType()))
{
// 获取中国银行微信支付拉起支付订单信息
requestResult = bankChinaWxPayService.appPay(
record.getGoodsTradeCode(),
@@ -251,14 +305,21 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl<MemberPayRecord>
record.getDeviceIp());
}
} else {
}
else
{
throw new RuntimeException("不支持的支付方式");
}
if (requestResult != null) {
if (requestResult != null)
{
record.setRequestResult(JSONObject.toJSONString(requestResult));
record.setBusinessOrderCode(requestResult.getOrderSeq());
record.setQrCode(requestResult.getQrCode());
this.updateById(record);
}
} catch (Exception e) {
}
catch (Exception e)
{
this.payFail(record.getGoodsTradeCode(), e.getMessage());
throw new RuntimeException(e.getMessage());
}
@@ -0,0 +1,13 @@
package io.v.nutz.pay.util;
public final class Base64 {
private static final Base64Coder coder = new Base64Coder();
public static String encode(byte abyte0[]) {
return coder.encode(abyte0);
}
public static byte[] decode(String s) {
return coder.decode(s);
}
}
@@ -0,0 +1,198 @@
package io.v.nutz.pay.util;
/**
*
* @description
*
* @author
*
* @modified_by
*/
public final class Base64Coder {
protected static boolean isWhiteSpace(char c) {
return c == ' ' || c == '\r' || c == '\n' || c == '\t';
}
protected static boolean isPad(char c) {
return c == '=';
}
protected static boolean isData(char c) {
return c < '\200' && base64Alphabet[c] != -1;
}
protected static boolean isBase64(char c) {
return isWhiteSpace(c) || isPad(c) || isData(c);
}
public String encode(byte abyte0[]) {
if (abyte0 == null)
return null;
int i = abyte0.length * 8;
if (i == 0)
return "";
int j = i % 24;
int k = i / 24;
int l = j == 0 ? k : k + 1;
char ac[] = null;
ac = new char[l * 4];
int i1 = 0;
int j1 = 0;
for (int k1 = 0; k1 < k; k1++) {
byte byte5 = abyte0[j1++];
byte byte8 = abyte0[j1++];
byte byte10 = abyte0[j1++];
byte byte3 = (byte) (byte8 & 15);
byte byte0 = (byte) (byte5 & 3);
byte byte11 = (byte5 & -128) != 0 ? (byte) (byte5 >> 2 ^ 192) : (byte) (byte5 >> 2);
byte byte14 = (byte8 & -128) != 0 ? (byte) (byte8 >> 4 ^ 240) : (byte) (byte8 >> 4);
byte byte16 = (byte10 & -128) != 0 ? (byte) (byte10 >> 6 ^ 252) : (byte) (byte10 >> 6);
ac[i1++] = lookUpBase64Alphabet[byte11];
ac[i1++] = lookUpBase64Alphabet[byte14 | byte0 << 4];
ac[i1++] = lookUpBase64Alphabet[byte3 << 2 | byte16];
ac[i1++] = lookUpBase64Alphabet[byte10 & 63];
}
if (j == 8) {
byte byte6 = abyte0[j1];
byte byte1 = (byte) (byte6 & 3);
byte byte12 = (byte6 & -128) != 0 ? (byte) (byte6 >> 2 ^ 192) : (byte) (byte6 >> 2);
ac[i1++] = lookUpBase64Alphabet[byte12];
ac[i1++] = lookUpBase64Alphabet[byte1 << 4];
ac[i1++] = '=';
ac[i1++] = '=';
} else if (j == 16) {
byte byte7 = abyte0[j1];
byte byte9 = abyte0[j1 + 1];
byte byte4 = (byte) (byte9 & 15);
byte byte2 = (byte) (byte7 & 3);
byte byte13 = (byte7 & -128) != 0 ? (byte) (byte7 >> 2 ^ 192) : (byte) (byte7 >> 2);
byte byte15 = (byte9 & -128) != 0 ? (byte) (byte9 >> 4 ^ 240) : (byte) (byte9 >> 4);
ac[i1++] = lookUpBase64Alphabet[byte13];
ac[i1++] = lookUpBase64Alphabet[byte15 | byte2 << 4];
ac[i1++] = lookUpBase64Alphabet[byte4 << 2];
ac[i1++] = '=';
}
return new String(ac);
}
public byte[] decode(String s) {
if (s == null)
return null;
char ac[] = s.toCharArray();
int i = removeWhiteSpace(ac);
if (i % 4 != 0)
return null;
int j = i / 4;
if (j == 0)
return new byte[0];
byte abyte0[] = null;
byte byte0 = 0;
byte byte1 = 0;
char c = '\0';
char c1 = '\0';
char c2 = '\0';
char c3 = '\0';
int k = 0;
int l = 0;
int i1 = 0;
abyte0 = new byte[j * 3];
for (; k < j - 1; k++) {
if (!isData(c = ac[i1++]) || !isData(c1 = ac[i1++]) || !isData(c2 = ac[i1++]) || !isData(c3 = ac[i1++]))
return null;
byte0 = base64Alphabet[c];
byte1 = base64Alphabet[c1];
byte byte2 = base64Alphabet[c2];
byte byte5 = base64Alphabet[c3];
abyte0[l++] = (byte) (byte0 << 2 | byte1 >> 4);
abyte0[l++] = (byte) ((byte1 & 15) << 4 | byte2 >> 2 & 15);
abyte0[l++] = (byte) (byte2 << 6 | byte5);
}
if (!isData(c = ac[i1++]) || !isData(c1 = ac[i1++]))
return null;
byte0 = base64Alphabet[c];
byte1 = base64Alphabet[c1];
c2 = ac[i1++];
c3 = ac[i1++];
if (!isData(c2) || !isData(c3)) {
if (isPad(c2) && isPad(c3)) {
if ((byte1 & 15) != 0)
return null;
byte abyte1[] = new byte[k * 3 + 1];
System.arraycopy(abyte0, 0, abyte1, 0, k * 3);
abyte1[l] = (byte) (byte0 << 2 | byte1 >> 4);
return abyte1;
}
if (isPad(c2) && isPad(c3))
return null;
byte byte3 = base64Alphabet[c2];
if ((byte3 & 3) != 0)
return null;
byte abyte2[] = new byte[k * 3 + 2];
System.arraycopy(abyte0, 0, abyte2, 0, k * 3);
abyte2[l++] = (byte) (byte0 << 2 | byte1 >> 4);
abyte2[l] = (byte) ((byte1 & 15) << 4 | byte3 >> 2 & 15);
return abyte2;
}
byte byte4 = base64Alphabet[c2];
byte byte6 = base64Alphabet[c3];
abyte0[l++] = (byte) (byte0 << 2 | byte1 >> 4);
abyte0[l++] = (byte) ((byte1 & 15) << 4 | byte4 >> 2 & 15);
abyte0[l++] = (byte) (byte4 << 6 | byte6);
return abyte0;
}
protected static int removeWhiteSpace(char ac[]) {
if (ac == null)
return 0;
int i = 0;
int j = ac.length;
for (int k = 0; k < j; k++)
if (!isWhiteSpace(ac[k]))
ac[i++] = ac[k];
return i;
}
private static final byte base64Alphabet[];
private static final char lookUpBase64Alphabet[];
static {
base64Alphabet = new byte[128];
lookUpBase64Alphabet = new char[64];
for (int i = 0; i < 128; i++)
base64Alphabet[i] = -1;
for (int j = 90; j >= 65; j--)
base64Alphabet[j] = (byte) (j - 65);
for (int k = 122; k >= 97; k--)
base64Alphabet[k] = (byte) ((k - 97) + 26);
for (int l = 57; l >= 48; l--)
base64Alphabet[l] = (byte) ((l - 48) + 52);
base64Alphabet[43] = 62;
base64Alphabet[47] = 63;
for (int i1 = 0; i1 <= 25; i1++)
lookUpBase64Alphabet[i1] = (char) (65 + i1);
int j1 = 26;
for (int k1 = 0; j1 <= 51; k1++) {
lookUpBase64Alphabet[j1] = (char) (97 + k1);
j1++;
}
int l1 = 52;
for (int i2 = 0; l1 <= 61; i2++) {
lookUpBase64Alphabet[l1] = (char) (48 + i2);
l1++;
}
lookUpBase64Alphabet[62] = '+';
lookUpBase64Alphabet[63] = '/';
}
}
+86 -6
View File
@@ -1,8 +1,11 @@
package io.v.nutz.pay.util;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
@@ -11,9 +14,15 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
public class XmlUtil
{
@@ -64,6 +73,9 @@ public class XmlUtil
public static Document createDocument(String s)
throws Exception
{
// 预处理XML,修复未转义的&符号
s = fixUnescapedAmpersands(s);
DocumentBuilder documentbuilder = createDocumentBuilder();
return documentbuilder.parse(new ByteArrayInputStream(s.getBytes("UTF-8")));
}
@@ -71,6 +83,9 @@ public class XmlUtil
public static Document createDocument(String s, String s1)
throws Exception
{
// 预处理XML,修复未转义的&符号
s = fixUnescapedAmpersands(s);
DocumentBuilder documentbuilder = createDocumentBuilder();
return documentbuilder.parse(new ByteArrayInputStream(s.getBytes(s1)));
}
@@ -203,4 +218,69 @@ public class XmlUtil
transformer.transform(domsource, new StreamResult(stringwriter));
return stringwriter.toString();
}
private static String fixUnescapedAmpersands(String xml) {
if (xml == null || xml.isEmpty()) {
return xml;
}
StringBuilder result = new StringBuilder();
int i = 0;
int len = xml.length();
while (i < len) {
char c = xml.charAt(i);
// 如果找到&符号
if (c == '&') {
// 检查是否是已经正确转义的实体(如 &amp; &lt; &#等)
if (i + 1 < len) {
int j = i + 1;
// 查找分号
boolean foundSemicolon = false;
boolean isValidEntity = false;
while (j < len && j < i + 10) { // 实体引用通常不会太长
if (xml.charAt(j) == ';') {
foundSemicolon = true;
// 检查&和;之间的内容是否是合法的实体名或数值引用
String entityContent = xml.substring(i + 1, j);
if (entityContent.startsWith("#") ||
entityContent.equals("amp") ||
entityContent.equals("lt") ||
entityContent.equals("gt") ||
entityContent.equals("quot") ||
entityContent.equals("apos")) {
isValidEntity = true;
}
break;
} else if (xml.charAt(j) == '&' || xml.charAt(j) == '<' || xml.charAt(j) == '>') {
// 遇到其他特殊字符,说明这不是一个有效的实体引用
break;
}
j++;
}
// 如果是合法的实体引用,保持原样
if (foundSemicolon && isValidEntity) {
result.append(c);
i++;
} else {
// 否则将&替换为&amp;
result.append("&amp;");
i++;
}
} else {
// &是最后一个字符,替换为&amp;
result.append("&amp;");
i++;
}
} else {
result.append(c);
i++;
}
}
return result.toString();
}
}
@@ -157,16 +157,16 @@ layout("/mobile/platform.html"){
</template>
<van-radio-group checked-color="#1867b0" v-model="moneyModel">
<van-cell @click="moneyModel = 1" clickable title="支付宝">
<template #icon>
<div class="pay_img" style="margin-right: 4px">
<img src="/assets/mobile/svg/memberPay/zfb.svg"/>
</div>
</template>
<template #right-icon>
<van-radio :name="1"></van-radio>
</template>
</van-cell>
<!-- <van-cell @click="moneyModel = 1" clickable title="支付宝">-->
<!-- <template #icon>-->
<!-- <div class="pay_img" style="margin-right: 4px">-->
<!-- <img src="/assets/mobile/svg/memberPay/zfb.svg"/>-->
<!-- </div>-->
<!-- </template>-->
<!-- <template #right-icon>-->
<!-- <van-radio :name="1"></van-radio>-->
<!-- </template>-->
<!-- </van-cell>-->
<van-cell @click="moneyModel = 2" clickable title="微信">
<template #icon>
<div class="pay_img" style="margin-right: 4px">
@@ -405,52 +405,18 @@ layout("/mobile/platform.html"){
return;
}
// 微信支付 初始化微信JS-SDK配置
if(paymentMethod.type === 'wx') {
await this.loadWxJsConfig();
const requestResult = JSON.parse(data.requestResult)
wx.ready(function () {
if(requestResult.qrCode) {
this.payToOtherPage = true
setTimeout(() => {
window.location.href = requestResult.qrCode
}, 500)
// 中国银行 微信APP支付
if(data.payChannel === 1 && data.channelType === 1) {
const payParams = JSON.parse(data.requestResult)
console.log("服务端获取的支付参数payParams->", payParams)
const prepayData = {
appId: payParams.wcPayDataAppId, // 自己的APPID
timeStamp: payParams.wcPayDataTimeStamp, // 时间戳
nonceStr: payParams.wcPayDataNonceStr, // 随机字符串
package: payParams.wcPayDataPrepayId, // 预支付订单号
signType: payParams.wcPayDataSignType, // 签名类型
paySign: payParams.wcPayDataPaySign // 签名
};
console.log("调用微信API支付参数->", prepayData)
// 调用微信支付API
wx.chooseWXPay({
timestamp: prepayData.timeStamp,
nonceStr: prepayData.nonceStr,
package: prepayData.package,
signType: prepayData.signType,
paySign: prepayData.paySign,
success: function(res) {
// 支付成功后的操作
console.log('支付成功');
setTimeout(()=> {
this.paySuccess(res, data.goodsTradeCode)
}, 500);
},
cancel: function(res) {
// 用户取消支付的操作
console.log('支付取消');
},
fail: function(res) {
// 支付失败的操作
console.log('支付失败');
this.payFail(res, data.goodsTradeCode)
}
});
}
})
}
this.startPaymentPolling(data.goodsTradeCode)
} else {
vant.Toast.fail("订单支付链接不存在")
}
}
}).catch((e) => {
if(loading) {
@@ -505,6 +471,41 @@ layout("/mobile/platform.html"){
async payFail(res, goodsTradeCode) {
await $.post('/member/pay/record/del/check/pay/code?payCode=' + goodsTradeCode)
},
async startPaymentPolling(goodsTradeCode) {
let pollCount = 0
const maxPollCount = 60
const pollInterval = setInterval(async () => {
pollCount++
try {
const res = await $.get('/member/pay/record/get/by/code?goodsTradeCode=' + goodsTradeCode)
if(res.code === 0 && res.data) {
if(res.data.status === 1) {
clearInterval(pollInterval)
this.$toast.success('支付成功')
this.payToOtherPage = false
this.payVisible = false
this.doSearch()
return
} else if(res.data.status === 2) {
clearInterval(pollInterval)
this.$toast.fail('支付失败')
this.payToOtherPage = false
return
}
}
if(pollCount >= maxPollCount) {
clearInterval(pollInterval)
this.$toast('支付结果查询超时,请稍后刷新查看')
}
} catch(e) {
console.error('轮询支付状态失败', e)
}
}, 2000)
},
},
async created() {
let nowYear = new Date().getFullYear()