From e8be5dee9d46d03089152fec12c79dae910f941c Mon Sep 17 00:00:00 2001 From: gong <1157756119@qq.com> Date: Wed, 14 Jan 2026 10:35:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=9D=A1=E7=A0=81=E6=94=AF?= =?UTF-8?q?=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cash-dependencies/pom.xml | 8 + cash-sdk/aggregation-pay/pom.xml | 7 + .../src/main/java/com/czg/EntryManager.java | 4 +- .../src/main/java/com/czg/PayManager.java | 39 ++- .../java/com/czg/dto/req/PayParamsDto.java | 16 ++ .../czg/third/alipay/AlipayIsvPayManager.java | 33 ++- .../czg/third/wechat/WechatPayManager.java | 238 ++++++++++++++++-- .../com/czg/third/wechat/WechatReqUtils.java | 2 +- .../wechat/dto/config/WechatPayConfigDto.java | 6 + 9 files changed, 324 insertions(+), 29 deletions(-) diff --git a/cash-dependencies/pom.xml b/cash-dependencies/pom.xml index a598172c8..7a4777293 100644 --- a/cash-dependencies/pom.xml +++ b/cash-dependencies/pom.xml @@ -44,6 +44,7 @@ 4.1.128.Final 0.2.17 3.1.65.ALL + 2.2.0 @@ -282,6 +283,13 @@ alipay-sdk-java-v3 ${apipay-v3.version} + + + org.dom4j + dom4j + ${dom4j.version} + compile + diff --git a/cash-sdk/aggregation-pay/pom.xml b/cash-sdk/aggregation-pay/pom.xml index 8eb8d2da1..d201553ec 100644 --- a/cash-sdk/aggregation-pay/pom.xml +++ b/cash-sdk/aggregation-pay/pom.xml @@ -35,6 +35,13 @@ ocr_api20210707 3.1.2 + + + + org.dom4j + dom4j + compile + \ No newline at end of file diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/EntryManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/EntryManager.java index 76d70ecac..7f81f350a 100644 --- a/cash-sdk/aggregation-pay/src/main/java/com/czg/EntryManager.java +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/EntryManager.java @@ -385,8 +385,8 @@ public class EntryManager { // verifyEntryParam(merchantDto); // uploadParamImage(merchantDto); //// System.out.println(merchantDto); - EntryRespDto respDto = entryMerchant(merchantDto, PayCst.Platform.WECHAT); -// entryMerchant(merchantDto, PayCst.Platform.ALIPAY); +// EntryRespDto respDto = entryMerchant(merchantDto, PayCst.Platform.WECHAT); + EntryRespDto respDto = entryMerchant(merchantDto, PayCst.Platform.ALIPAY); // entryMerchant(merchantDto, PayCst.Platform.WECHAT, PayCst.Platform.ALIPAY); System.out.println(respDto); } diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/PayManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/PayManager.java index 06ea794e3..90f5730b3 100644 --- a/cash-sdk/aggregation-pay/src/main/java/com/czg/PayManager.java +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/PayManager.java @@ -1,22 +1,51 @@ package com.czg; import com.czg.dto.req.PayParamsDto; +import com.czg.exception.CzgException; import com.czg.third.alipay.AlipayIsvPayManager; import com.czg.third.wechat.WechatPayManager; +import java.util.Map; + /** * @author yjjie * @date 2026/1/9 11:24 */ public class PayManager { - public static void jsapiPay(PayParamsDto paramsDto) { + /** + * jsapi支付 + * + * @param paramsDto 参数 + * @return 结果 + */ + public static Map jsapiPay(PayParamsDto paramsDto) { paramsDto.verifyParams(); if (PayCst.Platform.WECHAT.equals(paramsDto.getPlatform())) { - WechatPayManager.jsapiPay(null, paramsDto); + return WechatPayManager.jsapiPay(null, paramsDto); } else if (PayCst.Platform.ALIPAY.equals(paramsDto.getPlatform())) { - AlipayIsvPayManager.jsapiPay(null, paramsDto); + return AlipayIsvPayManager.jsapiPay(null, paramsDto); + } else { + throw new CzgException("不支持的支付平台"); + } + } + + /** + * 条码支付 + * + * @param paramsDto 参数 + * @return 结果 + */ + public static Map barPay(PayParamsDto paramsDto) { + paramsDto.verifyParams(); + + if (PayCst.Platform.WECHAT.equals(paramsDto.getPlatform())) { + return WechatPayManager.barPay(null, paramsDto); + } else if (PayCst.Platform.ALIPAY.equals(paramsDto.getPlatform())) { + return AlipayIsvPayManager.barPay(null, paramsDto); + } else { + throw new CzgException("不支持的支付平台"); } } @@ -35,10 +64,10 @@ public class PayManager { jsapiPay(new PayParamsDto() .setPlatform(PayCst.Platform.WECHAT) .setAppId("wxd88fffa983758a30") - .setOpenId("123123123") + .setOpenId("or1l86yipGvwyfPhrKIAcQuSfAV8") .setOrderNo("1111231231213") .setTitle("1213") - .setMerchantId("1665469114") + .setMerchantId("1738216504") .setBody("1213") .setAmount(1000L) .setPayParams("{\"app_auth_token\": \"ssss\"}") diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/PayParamsDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/PayParamsDto.java index 114d45ef2..80365910f 100644 --- a/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/PayParamsDto.java +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/dto/req/PayParamsDto.java @@ -77,6 +77,22 @@ public class PayParamsDto { */ private String body; + /** + * 扩展数据 + */ + private String extData; + + /** + * 设备 ip + */ + private String clientIp; + + /** + * 支付条码 + * 微信:18位纯数字,前缀以10、11、12、13、14、15开头 + */ + private String barCode; + /** * 支付宝 授权信息解析,不用传 */ diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvPayManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvPayManager.java index 50244a798..56214e479 100644 --- a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvPayManager.java +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/alipay/AlipayIsvPayManager.java @@ -14,6 +14,8 @@ import com.czg.third.alipay.dto.config.AlipayConfigDto; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; /** * @author yjjie @@ -22,7 +24,12 @@ import java.math.BigDecimal; @Slf4j public class AlipayIsvPayManager { - public static String jsapiPay(AlipayConfigDto configDto, PayParamsDto paramsDto) { + /** + * H5支付 + * @param configDto 配置 + * @param paramsDto 参数 + */ + public static Map jsapiPay(AlipayConfigDto configDto, PayParamsDto paramsDto) { try { AlipayClient.setApiClient(configDto); AlipayTradeApi api = new AlipayTradeApi(); @@ -41,7 +48,8 @@ public class AlipayIsvPayManager { customizedParams.setAppAuthToken(paramsDto.getAlipayAuthInfo().getAppAuthToken()); AlipayTradeCreateResponseModel responseModel = api.create(model, customizedParams); - return responseModel.getTradeNo(); + log.info("支付宝 jsapi 支付结果: {}", responseModel); + return new HashMap<>(); } catch (ApiException e) { String body = e.getResponseBody(); log.error("支付宝 H5 api 支付异常: {}", body); @@ -53,6 +61,27 @@ public class AlipayIsvPayManager { } } + /** + * 条码支付 + * @param configDto 配置 + * @param paramsDto 参数 + */ + public static Map barPay(AlipayConfigDto configDto, PayParamsDto paramsDto) { + try { + AlipayClient.setApiClient(configDto); + + return new JSONObject(); +// } catch (ApiException e) { +// String body = e.getResponseBody(); +// log.error("支付宝 条码支付异常: {}", body); +// JSONObject object = JSONObject.parseObject(body); +// throw new CzgException(object.getString(PayCst.ALIPAY_ERROR_MSG_KEY)); + } catch (Exception e) { + log.error("支付宝 条码支付异常: {}", e.getMessage()); + throw new CzgException(e.getMessage()); + } + } + /** * 金额转换 */ diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatPayManager.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatPayManager.java index 415edd6d4..7061bb65f 100644 --- a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatPayManager.java +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatPayManager.java @@ -1,9 +1,31 @@ package com.czg.third.wechat; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpUtil; import com.alibaba.fastjson2.JSONObject; import com.czg.dto.req.PayParamsDto; +import com.czg.exception.CzgException; import com.czg.third.wechat.dto.config.WechatPayConfigDto; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.cipher.Signer; +import com.wechat.pay.java.core.util.NonceUtil; import lombok.extern.slf4j.Slf4j; +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; /** * @author yjjie @@ -12,33 +34,211 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class WechatPayManager { + private static final String PAY_SUCCESS = "SUCCESS"; + private static final String BAR_PAY_URL = "https://api.mch.weixin.qq.com/pay/micropay"; + /** * jsapi 小程序支付 - * @param configDto 配置 - * @param paramsDto 参数 + * + * @param configDto 配置 + * @param paramsDto 参数 */ - public static String jsapiPay(WechatPayConfigDto configDto, PayParamsDto paramsDto) { + public static Map jsapiPay(WechatPayConfigDto configDto, PayParamsDto paramsDto) { if (configDto == null) { configDto = WechatPayConfigDto.getDefaultConfig(); } - JSONObject reqData = new JSONObject(); - reqData.put("sp_appid", paramsDto.getAppId()); - reqData.put("sp_mchid", configDto.getMerchantId()); - reqData.put("sub_mchid", paramsDto.getMerchantId()); - reqData.put("description", paramsDto.getTitle()); - reqData.put("out_trade_no", paramsDto.getOrderNo()); - reqData.put("notify_url", paramsDto.getNotifyUrl()); - JSONObject amount = new JSONObject(); - amount.put("total", paramsDto.getAmount()); - reqData.put("amount", amount); + try { + JSONObject reqData = new JSONObject(); + reqData.put("sp_appid", paramsDto.getAppId()); + reqData.put("sp_mchid", configDto.getMerchantId()); + reqData.put("sub_mchid", paramsDto.getMerchantId()); + reqData.put("description", paramsDto.getTitle()); + reqData.put("out_trade_no", paramsDto.getOrderNo()); + reqData.put("notify_url", paramsDto.getNotifyUrl()); + reqData.put("attach", paramsDto.getExtData()); - JSONObject payer = new JSONObject(); - payer.put("sp_openid", paramsDto.getOpenId()); - reqData.put("payer", payer); + JSONObject amount = new JSONObject(); + amount.put("total", paramsDto.getAmount()); + reqData.put("amount", amount); - String string = WechatReqUtils.postReq(configDto, "/v3/pay/partner/transactions/jsapi", reqData.toJSONString()); - log.info("微信支付请求结果: orderNo = {}, res = {}", paramsDto.getOrderNo(), string); - return ""; + JSONObject payer = new JSONObject(); + payer.put("sp_openid", paramsDto.getOpenId()); + reqData.put("payer", payer); + + String string = WechatReqUtils.postReq(configDto, "/v3/pay/partner/transactions/jsapi", reqData.toJSONString()); + log.info("微信支付请求结果: orderNo = {}, res = {}", paramsDto.getOrderNo(), string); + + JSONObject object = JSONObject.parseObject(string); + + long timestamp = Instant.now().getEpochSecond(); + String nonceStr = NonceUtil.createNonce(32); + String packageVal = "prepay_id=" + object.getString("prepay_id"); + String message = + paramsDto.getAppId() + "\n" + timestamp + "\n" + nonceStr + "\n" + packageVal + "\n"; + + Config config = WechatConfig.getRsaConfig(configDto); + Signer signer = config.createSigner(); + String sign = signer.sign(message).getSign(); + + JSONObject res = new JSONObject(); + res.put("appId", paramsDto.getAppId()); + res.put("timeStamp", String.valueOf(timestamp)); + res.put("nonceStr", nonceStr); + res.put("package", packageVal); + res.put("signType", "RSA"); + res.put("paySign", sign); + log.info("微信支付签名结果: orderNo = {}, res = {}", paramsDto.getOrderNo(), res); + return res; + } catch (Exception e) { + log.error("微信支付异常: orderNo = {}, e = {}", paramsDto.getOrderNo(), e.getMessage()); + throw new CzgException("微信支付异常"); + } + } + + /** + * 条码支付 + * 用户付款码规则:18位纯数字,前缀以10、11、12、13、14、15开头 + * @param configDto 配置 + * @param paramsDto 参数 + */ + public static Map barPay(WechatPayConfigDto configDto, PayParamsDto paramsDto) { + try { + if (configDto == null) { + configDto = WechatPayConfigDto.getDefaultConfig(); + } + + String nonceStr = NonceUtil.createNonce(32); + + Map payMap = new HashMap<>(); + Document document = DocumentHelper.createDocument(); + + // 添加根元素 + Element xml = document.addElement("xml"); + + addElementIfNotEmpty(xml, "appid", paramsDto.getAppId()); + payMap.put("appid", paramsDto.getAppId()); + addElementIfNotEmpty(xml, "mch_id", configDto.getMerchantId()); + payMap.put("mch_id", configDto.getMerchantId()); + addElementIfNotEmpty(xml, "sub_mch_id", paramsDto.getMerchantId()); + payMap.put("sub_mch_id", paramsDto.getMerchantId()); + addElementIfNotEmpty(xml, "nonce_str", nonceStr); + payMap.put("nonce_str", nonceStr); + addElementIfNotEmpty(xml, "body", paramsDto.getTitle()); + payMap.put("body", paramsDto.getTitle()); + addElementIfNotEmpty(xml, "out_trade_no", paramsDto.getOrderNo()); + payMap.put("out_trade_no", paramsDto.getOrderNo()); + addElementIfNotEmpty(xml, "total_fee", paramsDto.getAmount()); + payMap.put("total_fee", paramsDto.getAmount()); + addElementIfNotEmpty(xml, "spbill_create_ip", paramsDto.getClientIp()); + payMap.put("spbill_create_ip", paramsDto.getClientIp()); + addElementIfNotEmpty(xml, "auth_code", paramsDto.getBarCode()); + payMap.put("auth_code", paramsDto.getBarCode()); + + if (StrUtil.isNotBlank(paramsDto.getExtData())) { + addElementIfNotEmpty(xml, "attach", paramsDto.getExtData()); + payMap.put("attach", paramsDto.getExtData()); + } + + String sign = signBarPayParam(configDto, payMap); + addElementIfNotEmpty(xml, "sign", sign); + + OutputFormat format = OutputFormat.createPrettyPrint(); + XMLWriter writer = new XMLWriter(System.out, format); + writer.write(document); + + String xmlStr = document.asXML(); + log.info("微信条码支付参数:{}", xmlStr); + + String post = HttpUtil.post(BAR_PAY_URL, xmlStr); + log.info("微信条码支付请求结果: orderNo = {}, res = {}", paramsDto.getOrderNo(), post); + + JSONObject res = new JSONObject(); + res.put("orderNo", paramsDto.getOrderNo()); + + SAXReader saxReader = new SAXReader(); + Document read = saxReader.read(stringToInputStream(post)); + Element rootElement = read.getRootElement(); + String returnCode = rootElement.elementText("return_code"); + + String errCode = rootElement.elementText("err_code"); + String errCodeDes = rootElement.elementText("err_code_des"); + res.put("message", errCodeDes); + + if (StrUtil.isNotBlank(errCode)) { + if ("SYSTEMERROR".equals(errCode) || "BANKERROR".equals(errCode) || "USERPAYING".equals(errCode)) { + // 等待结果 + log.info("微信条码支付等待结果: orderNo = {}, err {}", paramsDto.getOrderNo(), errCodeDes); + res.put("status", "TRADE_AWAIT"); + return res; + } + + res.put("status", "TRADE_FAIL"); + return res; + } + + if (!PAY_SUCCESS.equals(returnCode)) { + log.error("微信条码支付失败: orderNo = {}, msg = {}", paramsDto.getOrderNo(), rootElement.elementText("return_msg")); + res.put("status", "TRADE_FAIL"); + return res; + } + + // 支付成功 + log.info("微信条码支付成功: orderNo = {}, msg = {}", paramsDto.getOrderNo(), rootElement.elementText("return_msg")); + res.put("status", "TRADE_SUCCESS"); + return res; + } catch (CzgException e) { + throw e; + } catch (Exception e) { + log.error("微信条码支付异常: orderNo = {}, e = {}", paramsDto.getOrderNo(), e.getMessage()); + throw new CzgException("微信支付异常"); + } + } + + /** + * 将String转换为InputStream + * @param str 待转换的字符串 + * @return 转换后的InputStream + */ + private static InputStream stringToInputStream(String str) { + if (str == null || str.isEmpty()) { + return new ByteArrayInputStream(new byte[0]); + } + // 核心:先转字节数组,再封装为ByteArrayInputStream + byte[] byteArray = str.getBytes(StandardCharsets.UTF_8); + return new ByteArrayInputStream(byteArray); + } + + private static String signBarPayParam(WechatPayConfigDto configDto, Map params) { + TreeMap sortedParams = new TreeMap<>(params); + String sortParam = sortedParams.entrySet().stream() + // 将每个键值对转换为 "key=value" 格式的字符串 + .map(entry -> entry.getKey() + "=" + (entry.getValue() == null ? "" : entry.getValue())) + // 使用 "&" 连接所有转换后的字符串 + .collect(Collectors.joining("&")); + log.info("微信条码支付 加密前参数: {}", sortParam); + + String signStr = sortParam + "&key=" + configDto.getApiV2Key(); + return SecureUtil.md5(signStr).toUpperCase(); + } + + /** + * 添加元素,包括空值 + */ + private static void addElementIfNotEmpty(Element parent, String name, Object value) { + Element element = parent.addElement(name); + element.addText(value == null ? "" : String.valueOf(value)); + } + + public static void main(String[] args) { + barPay(null, new PayParamsDto() + .setMerchantId("1738216504") + .setOrderNo("sa101293120sss1") + .setTitle("1213") + .setBody("1213") + .setAmount(1L) + .setAppId("wxd88fffa983758a30") + .setClientIp("127.0.0.1") + .setBarCode("130013153082460022")); } } diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatReqUtils.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatReqUtils.java index 6bf217918..ad2885200 100644 --- a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatReqUtils.java +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/WechatReqUtils.java @@ -59,7 +59,7 @@ public class WechatReqUtils { .header("Wechatpay-Serial", configDto.getPublicKeyId()) .charset(StandardCharsets.UTF_8) .contentType("application/json") - .body(body ); + .body(body); case "GET" -> HttpUtil.createGet(configDto.getDomain() + url) .header("Authorization", authorization) diff --git a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/config/WechatPayConfigDto.java b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/config/WechatPayConfigDto.java index 78e544e8b..c1b303ed3 100644 --- a/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/config/WechatPayConfigDto.java +++ b/cash-sdk/aggregation-pay/src/main/java/com/czg/third/wechat/dto/config/WechatPayConfigDto.java @@ -21,6 +21,11 @@ public class WechatPayConfigDto { */ private String apiV3Key; + /** + * Api V2 密钥 + */ + private String apiV2Key; + /** * 商户证书序列号 */ @@ -51,6 +56,7 @@ public class WechatPayConfigDto { return new WechatPayConfigDto() .setMerchantId("1643779408") .setApiV3Key("a92baac5eb7a36ed8ec198113e769a03") + .setApiV2Key("3caf37225b6ea77a624ee03b7e3d03bb") .setSerialNumber("4DE9BAC2EA584C3F274F694C9753CA814C4E9BF4") .setPublicKey(""" -----BEGIN PUBLIC KEY-----