From 02da52745de578ff90bd1a12fb798d9867f0f7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=8E=B0=E5=B9=BF?= <1121477223@qq.com> Date: Wed, 3 Jun 2026 11:31:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 102 ++++-------- .../pay/bank/china/BankChinaWxPayService.java | 6 +- .../controller/BankNotifyController.java | 51 ++++++ .../china/pojo/WxAppPayRequestParams.java | 4 +- .../nutz/pay/bank/china/sign/BankPaySign.java | 4 - .../v/nutz/pay/enums/WxPayTradeTypeEnum.java | 2 +- .../impl/MemberPayRecordServiceImpl.java | 5 +- .../java/io/v/nutz/pay/util/PKCS7Tool.java | 63 ++++---- .../mobile/memberCost/memberCostList.html | 145 +++++++++++++++++- 9 files changed, 265 insertions(+), 117 deletions(-) create mode 100644 src/main/java/io/v/nutz/pay/bank/china/controller/BankNotifyController.java diff --git a/pom.xml b/pom.xml index e8f687c..7b546e6 100644 --- a/pom.xml +++ b/pom.xml @@ -30,10 +30,7 @@ 5.7.20 2.2.2 4.1.2 - 4.0.0 - 2.5.1 2.4 - 1.12.1 1.9.1 4.2.0 9.4.56.v20240826 @@ -143,14 +140,6 @@ org.eclipse.jetty.websocket websocket-server - - org.eclipse.jetty.websocket - websocket-server - - - org.eclipse.jetty.websocket - javax-websocket-server-impl - org.eclipse.jetty.websocket javax-websocket-server-impl @@ -252,7 +241,7 @@ org.nutz nutz-integration-json4excel ${nutz.version} - + org.apache.poi poi @@ -277,13 +266,11 @@ aspose-words 23.1 - com.luhuiguo aspose-cells 23.1 - com.luhuiguo aspose-slides @@ -338,19 +325,16 @@ thumbnailator 0.4.8 - cn.hutool hutool-all ${hutool-all.version} - org.projectlombok lombok ${lombok.version} - com.artofsolving jodconverter @@ -393,7 +377,6 @@ ridl ${openoffice.version} - org.openoffice unoil @@ -409,13 +392,13 @@ com.vdurmont emoji-java - ${emoji-java.veision} + ${emoji-java.version} com.belerweb pinyin4j - ${pinyin4j.veision} + ${pinyin4j.version} org.apache.commons @@ -427,6 +410,8 @@ commons-io ${commons-io.version} + + org.apache.poi poi @@ -442,21 +427,19 @@ poi-ooxml-schemas ${poi.version} - org.apache.poi ooxml-schemas 1.4 + org.slf4j slf4j-api ${slf4j.version} - - cn.afterturn easypoi-base @@ -493,7 +476,7 @@ com.deepoove poi-tl ${poitl.version} - + org.slf4j slf4j-log4j12 @@ -530,19 +513,16 @@ asm-util 7.3.1 - asm asm-all 2.1 - org.openjdk.nashorn nashorn-core 15.3 - com.aliyun darabonba-java-core @@ -563,23 +543,12 @@ mail 1.4.7 - - org.apache.poi - poi - ${poi.version} - - - org.apache.poi - poi-ooxml - ${poi.version} - javax.annotation javax.annotation-api 1.3.2 - ws.schild jave-all-deps @@ -626,7 +595,6 @@ java-jwt 3.1.0 - com.google.zxing javase @@ -678,7 +646,6 @@ - com.tencentcloudapi tencentcloud-sdk-java @@ -695,8 +662,6 @@ - - io.jsonwebtoken jjwt @@ -708,24 +673,18 @@ - org.jsoup jsoup - ${jsoup.veision} + ${jsoup.version} - junit junit test - - org.slf4j - slf4j-api - ${slf4j.version} - + ch.qos.logback logback-core @@ -741,7 +700,6 @@ easy-captcha ${easy-captcha.version} - commons-validator commons-validator @@ -763,6 +721,16 @@ websocket-server ${jetty.version} + + org.bouncycastle + bcprov-jdk15on + 1.69 + + + org.bouncycastle + bcpkix-jdk15on + 1.69 + org.eclipse.jetty.websocket javax-websocket-server-impl @@ -775,6 +743,7 @@ + @@ -789,10 +758,13 @@ jaxb-api ${jaxb-api.version} + + + org.apache.maven.plugins maven-compiler-plugin @@ -803,6 +775,7 @@ false + org.apache.maven.plugins maven-javadoc-plugin @@ -812,14 +785,6 @@ UTF-8 UTF-8 - - - attach-javadocs - - jar - - - org.apache.maven.plugins @@ -828,14 +793,6 @@ true - - - compile - - jar - - - org.apache.maven.plugins @@ -851,16 +808,9 @@ nutzboot-maven-plugin ${nutzboot.version} - - org.apache.maven.plugins - maven-compiler-plugin - - 15 - 15 - - + nutz @@ -891,4 +841,4 @@ - + \ No newline at end of file diff --git a/src/main/java/io/v/nutz/pay/bank/china/BankChinaWxPayService.java b/src/main/java/io/v/nutz/pay/bank/china/BankChinaWxPayService.java index 177b9d8..4b6a936 100644 --- a/src/main/java/io/v/nutz/pay/bank/china/BankChinaWxPayService.java +++ b/src/main/java/io/v/nutz/pay/bank/china/BankChinaWxPayService.java @@ -3,6 +3,7 @@ package io.v.nutz.pay.bank.china; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSONObject; import io.v.nutz.pay.bank.china.pojo.WxAppPayRequestParams; import io.v.nutz.pay.bank.china.pojo.WxAppPayResponse; @@ -27,7 +28,6 @@ import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.w3c.dom.Document; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -138,7 +138,7 @@ public class BankChinaWxPayService { // 交易终端类型 payParams.setTerminalChnl(chnlEnum.getCode()); // 交易类型 - payParams.setTradeType(WxPayTradeTypeEnum.APP_WX.getCode()); + payParams.setTradeType(WxPayTradeTypeEnum.WXAPP.getCode()); // 子商户公众账号ID payParams.setSubAppid(payProperties.getSubAppid()); // 设备号 @@ -163,7 +163,7 @@ public class BankChinaWxPayService { String signData = BankPaySign.getInstance().getSinData(payProperties.getSignKeyFile(), payProperties.getSignkeyPassword(), payProperties.getSignkeyPassword(), businessSignData); log.info("中国银行微信APP订单支付签名:signData={}", signData); payParams.setSignData(signData); - log.info("中国银行微信APP订单支付参数:{}", payParams.toString()); + log.info("中国银行微信APP订单支付参数:{}", JSONUtil.toJsonPrettyStr(payParams)); List formParams = payParams.formParams(); log.info("中国银行微信APP订单支付发送参数:{}", JSONObject.toJSONString(formParams)); diff --git a/src/main/java/io/v/nutz/pay/bank/china/controller/BankNotifyController.java b/src/main/java/io/v/nutz/pay/bank/china/controller/BankNotifyController.java new file mode 100644 index 0000000..29e832d --- /dev/null +++ b/src/main/java/io/v/nutz/pay/bank/china/controller/BankNotifyController.java @@ -0,0 +1,51 @@ +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.cache.ChinaBankOrderWaitPayCacheService; +import io.v.nutz.pay.record.service.IMemberPayRecordService; +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 javax.servlet.http.HttpServletRequest; + +/** + * 支付通知 controller + */ +@IocBean +@Api("支付通知") +@At("/notify") +@Slf4j +public class BankNotifyController +{ + + @Inject + private IMemberPayRecordService memberPayRecordService; + + @Inject + private RedisService redisService; + + private final static String SSK_MEMBER_PAY_ORDER_RECORD = "SSK:MEMBER:PAY:ORDER:RECORD:"; + + @Inject + private ChinaBankOrderWaitPayCacheService chinaBankOrderWaitPayCacheService; + + + @Ok("json") + @At("/china/bank/pay") + @ApiOperation("通过支付商品交易单号查询支付记录") + public void cashRecvDcepNotify(HttpServletRequest request) { + + log.info("---------- Begin ReceiveNB2BRecvOrderNotice ----------"); + String message = request.getParameter("message"); + String signature = request.getParameter("signature"); + log.info("[message]=[" + message + "]"); + log.info("[signature]=[" + signature + "]"); + } + + +} diff --git a/src/main/java/io/v/nutz/pay/bank/china/pojo/WxAppPayRequestParams.java b/src/main/java/io/v/nutz/pay/bank/china/pojo/WxAppPayRequestParams.java index 151890c..4cd0fd1 100644 --- a/src/main/java/io/v/nutz/pay/bank/china/pojo/WxAppPayRequestParams.java +++ b/src/main/java/io/v/nutz/pay/bank/china/pojo/WxAppPayRequestParams.java @@ -59,7 +59,7 @@ public class WxAppPayRequestParams extends CommonRequestParams implements Serial sb.append(this.getOrderTime()).append("|"); sb.append(this.getCurCode()).append("|"); sb.append(this.getOrderAmount()).append("|"); - sb.append(this.getMerchantNo()).append("|"); + sb.append(this.getMerchantNo()); // sb.append(this.getOrderNote()).append("|"); // sb.append(this.getOrderUrl()).append("|"); // if(this.getOrderTimeoutDate() != null) { @@ -92,7 +92,7 @@ public class WxAppPayRequestParams extends CommonRequestParams implements Serial public List formParams() { List params = Collections.synchronizedList(new ArrayList()); params.add(new BasicNameValuePair("merchantNo", this.getMerchantNo())); - params.add(new BasicNameValuePair("payType", this.getPayType())); + params.add(new BasicNameValuePair("payType", "1")); params.add(new BasicNameValuePair("orderNo", this.getOrderNo())); params.add(new BasicNameValuePair("curCode", this.getCurCode())); params.add(new BasicNameValuePair("orderAmount", this.getOrderAmount())); diff --git a/src/main/java/io/v/nutz/pay/bank/china/sign/BankPaySign.java b/src/main/java/io/v/nutz/pay/bank/china/sign/BankPaySign.java index aa1eff7..961044f 100644 --- a/src/main/java/io/v/nutz/pay/bank/china/sign/BankPaySign.java +++ b/src/main/java/io/v/nutz/pay/bank/china/sign/BankPaySign.java @@ -4,10 +4,6 @@ import cn.hutool.core.util.StrUtil; import io.v.nutz.pay.util.PKCS7Tool; import lombok.extern.slf4j.Slf4j; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Base64; - /** * 中国银行支付签名 */ diff --git a/src/main/java/io/v/nutz/pay/enums/WxPayTradeTypeEnum.java b/src/main/java/io/v/nutz/pay/enums/WxPayTradeTypeEnum.java index 6052c58..77d751f 100644 --- a/src/main/java/io/v/nutz/pay/enums/WxPayTradeTypeEnum.java +++ b/src/main/java/io/v/nutz/pay/enums/WxPayTradeTypeEnum.java @@ -2,7 +2,7 @@ package io.v.nutz.pay.enums; public enum WxPayTradeTypeEnum { - APP_WX("APP_WX", "APP支付(微信)"); + WXAPP("WXAPP", "APP支付(微信)"); private String code; public String getCode() { diff --git a/src/main/java/io/v/nutz/pay/record/service/impl/MemberPayRecordServiceImpl.java b/src/main/java/io/v/nutz/pay/record/service/impl/MemberPayRecordServiceImpl.java index 84a89b7..1e7db9c 100644 --- a/src/main/java/io/v/nutz/pay/record/service/impl/MemberPayRecordServiceImpl.java +++ b/src/main/java/io/v/nutz/pay/record/service/impl/MemberPayRecordServiceImpl.java @@ -21,17 +21,13 @@ import io.v.nutz.pay.record.module.MemberPayRecord; import io.v.nutz.pay.record.service.IMemberPayRecordService; import io.v.nutz.pay.util.BillNoUtil; import lombok.extern.slf4j.Slf4j; -import org.nutz.dao.Chain; -import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import java.util.Arrays; import java.util.List; -import java.util.Map; @Slf4j @IocBean(args = {"refer:dao"}) @@ -137,6 +133,7 @@ public class MemberPayRecordServiceImpl extends BaseServiceImpl record.setBusinessOrderCode(paymentOrder.getOrderCode()); record.setPayMoney(paymentOrder.getMoney()); record.setMemberId(paymentOrder.getMemberId()); + record.setPayRemark("工会会员缴费-订单:" + paymentOrder.getOrderCode()); } else { throw new RuntimeException("不支持的支付业务"); diff --git a/src/main/java/io/v/nutz/pay/util/PKCS7Tool.java b/src/main/java/io/v/nutz/pay/util/PKCS7Tool.java index 68be1b7..58eea8d 100644 --- a/src/main/java/io/v/nutz/pay/util/PKCS7Tool.java +++ b/src/main/java/io/v/nutz/pay/util/PKCS7Tool.java @@ -1,34 +1,38 @@ package io.v.nutz.pay.util; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.cert.jcajce.JcaCertStore; +import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSSignedDataGenerator; +import org.bouncycastle.cms.CMSTypedData; +import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.springframework.util.StringUtils; import java.io.File; import java.io.FileInputStream; import java.security.*; import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Base64; import java.util.Enumeration; +import java.util.List; @Slf4j -public class PKCS7Tool { +public class PKCS7Tool +{ - // 加密算法 private static String KEY_ALGORITHM = "SHA256withRSA"; public static PKCS7Tool instance() { return new PKCS7Tool(); } - - - /** - * Pkcs7Detach 签名 - * @param signFilePath 签名证书 - * @param password 证书秘钥 - * @param content 内容 - * @return - */ public String signature(String signFilePath, String password, byte[] content){ if (StringUtils.isEmpty(signFilePath)) { throw new RuntimeException("缺少证书"); @@ -38,11 +42,9 @@ public class PKCS7Tool { } log.info("生成签名信息:signFilePath-->{}, password-->{}", signFilePath, password); try { - Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + Security.addProvider(new BouncyCastleProvider()); - //创建密钥对 KeyStore keyStore = KeyStore.getInstance("PKCS12"); - File file = new File(signFilePath); if(!file.exists()) { throw new RuntimeException("证书路径不存在"); @@ -51,23 +53,32 @@ public class PKCS7Tool { keyStore.load(fis, password.toCharArray()); fis.close(); - //获取别名 Enumeration enumas = keyStore.aliases(); String alias = null; while (enumas.hasMoreElements()) { alias = (String) enumas.nextElement(); } - //准备密钥对 - Key key = keyStore.getKey(alias, password.toCharArray()); - Signature sig = Signature.getInstance(KEY_ALGORITHM); - // 使用私钥初始化 Signature 对象 - sig.initSign((PrivateKey) key); - sig.update(content); - // 生成签名 - byte[] sign = sig.sign(); - log.info("Pkcs7Detach 签名成功,加密后的密文为:{}", Base64.getEncoder().encodeToString(sign)); - return Base64.getEncoder().encodeToString(sign); + PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray()); + Certificate cert = keyStore.getCertificate(alias); + List certList = new ArrayList<>(); + certList.add(cert); + + CMSSignedDataGenerator signer = new CMSSignedDataGenerator(); + ContentSigner contentSigner = new JcaContentSignerBuilder(KEY_ALGORITHM).setProvider("BC").build(privateKey); + signer.addSignerInfoGenerator( + new JcaSignerInfoGeneratorBuilder( + new JcaDigestCalculatorProviderBuilder().setProvider("BC").build() + ).build(contentSigner, (X509Certificate) cert) + ); + signer.addCertificates(new JcaCertStore(certList)); + + CMSTypedData msg = new CMSProcessableByteArray(content); + CMSSignedData signedData = signer.generate(msg, false); + + String signData = Base64.getEncoder().encodeToString(signedData.getEncoded()); + log.info("Pkcs7Detach 签名成功,加密后的密文为:{}", signData); + return signData; } catch (Exception e) { e.printStackTrace(); log.error("Pkcs7Detach 签名异常:"+e.getMessage()); @@ -92,7 +103,7 @@ public class PKCS7Tool { } log.info("验证签名信息:verifySignFilePath-->{}, password-->{}", verifySignFilePath, password); try { - Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); + Security.addProvider(new BouncyCastleProvider()); //创建密钥对 KeyStore keyStore = KeyStore.getInstance("PKCS12"); diff --git a/src/main/resources/views/mobile/memberCost/memberCostList.html b/src/main/resources/views/mobile/memberCost/memberCostList.html index 6e631d4..b334d57 100644 --- a/src/main/resources/views/mobile/memberCost/memberCostList.html +++ b/src/main/resources/views/mobile/memberCost/memberCostList.html @@ -178,7 +178,7 @@ layout("/mobile/platform.html"){ - + @@ -225,6 +225,13 @@ layout("/mobile/platform.html"){ moneyModel: 2, intervalTimer: null, hasPayed: false, + paymentMethods: [{ + title: '微信', + type: 'wx', + payChannel: 1, + channelType: 1, + icon: '/assets/mobile/svg/pay/weixin.svg', + }], } }, methods: { @@ -362,6 +369,142 @@ layout("/mobile/platform.html"){ } } }, + // 确认支付订单 + async surePayOrder() { + let loading = null + vant.Dialog.confirm({ + title: '提示', + message: '您确认要支付当前订单吗?', + }).then(async () => { + loading = this.$toast({ + duration: 0, + message: '正在拉起支付,请稍候', + forbidClick: true, + loadingType: 'spinner', + type: 'loading', + overlay: true + }); + + const payload = {} + payload.channelType = 1 + payload.businessType = 1 + payload.payChannel = 1 + payload.businessId = this.payInfo.id + payload.remark = '测试' + payload.money = 0.01 + console.log('创建支付订单参数->', payload) + // 创建支付订单 获取支付信息 拉起支付页面 + const { code, data, msg } = await $.post('/member/pay/record/pay', payload) + if(code !== 0) { + loading.close() + vant.Toast.fail(msg) + } else { + loading.close() + if(!data.requestResult) { + vant.Toast.fail("订单支付拉起失败") + return; + } + + // 微信支付 初始化微信JS-SDK配置 + if(paymentMethod.type === 'wx') { + await this.loadWxJsConfig(); + + wx.ready(function () { + + // 中国银行 微信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) + } + }); + } + }) + } + } + }).catch((e) => { + if(loading) { + loading.close() + } + console.log("用户支付失败->", e) + vant.Toast("用户支付失败") + }); + }, + + // 支付成功 + async paySuccess(res, goodsTradeCode) { + await $.post('/member/pay/record/check/pay/code?payCode=' + goodsTradeCode) + + const loading = this.$toast({ + duration: 0, + message: '订单支付查询中...', + forbidClick: true, + loadingType: 'spinner', + type: 'loading', + overlay: true + }); + + // 循环拉取支付订单信息 + await this.repeatQueryPay(loading, goodsTradeCode) + }, + + // 循环拉取支付订单信息 + async repeatQueryPay(loading, goodsTradeCode) { + setTimeout(async () => { + const res = await $.post('/member/pay/record/get/by/code?goodsTradeCode=' + goodsTradeCode) + if(res.code !== 0) { + loading.close() + vant.Toast.fail(msg) + } else { + const payRecord = res.data + + // 支付成功 + if(payRecord.status === 1) { + loading.close() + // 支付失败 + } else if(payRecord.status === 2) { + loading.close() + } else { + await this.repeatQueryPay(loading, goodsTradeCode) + } + } + }, 1000); + }, + + // 支付失败 + async payFail(res, goodsTradeCode) { + await $.post('/member/pay/record/del/check/pay/code?payCode=' + goodsTradeCode) + }, }, async created() { let nowYear = new Date().getFullYear()