支付对接

This commit is contained in:
周现广
2026-06-03 11:31:30 +08:00
parent 8ec1fee263
commit 02da52745d
9 changed files with 265 additions and 117 deletions
@@ -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<NameValuePair> formParams = payParams.formParams();
log.info("中国银行微信APP订单支付发送参数:{}", JSONObject.toJSONString(formParams));
@@ -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 + "]");
}
}
@@ -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<NameValuePair> formParams() {
List<NameValuePair> params = Collections.synchronizedList(new ArrayList<NameValuePair>());
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()));
@@ -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;
/**
* 中国银行支付签名
*/
@@ -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() {
@@ -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<MemberPayRecord>
record.setBusinessOrderCode(paymentOrder.getOrderCode());
record.setPayMoney(paymentOrder.getMoney());
record.setMemberId(paymentOrder.getMemberId());
record.setPayRemark("工会会员缴费-订单:" + paymentOrder.getOrderCode());
} else {
throw new RuntimeException("不支持的支付业务");
+37 -26
View File
@@ -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<Certificate> 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");
@@ -178,7 +178,7 @@ layout("/mobile/platform.html"){
</template>
</van-cell>
</van-radio-group>
<van-submit-bar :loading="loading" :price="formData.paymentMoney" @submit="doPay" button-color="#1867b0" button-text="支付"></van-submit-bar>
<van-submit-bar :loading="loading" :price="formData.paymentMoney" @submit="surePayOrder" button-color="#1867b0" button-text="支付"></van-submit-bar>
</div>
</van-action-sheet>
@@ -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()