diff --git a/cash-api/market-server/src/main/java/com/czg/controller/admin/DistributionController.java b/cash-api/market-server/src/main/java/com/czg/controller/admin/DistributionController.java index af178c13..dc28868b 100644 --- a/cash-api/market-server/src/main/java/com/czg/controller/admin/DistributionController.java +++ b/cash-api/market-server/src/main/java/com/czg/controller/admin/DistributionController.java @@ -107,9 +107,10 @@ public class DistributionController { */ @GetMapping("distributionFlow") public CzgResult> distributionFlow(@RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, - @RequestParam(required = false) String key, @RequestParam(required = false) String status) { + @RequestParam(required = false) String key, @RequestParam(required = false) String status, + @RequestParam(required = false) Long id) { return CzgResult.success(distributionFlowService.pageInfo(StpKit.USER.getShopId(), - StrUtil.isNotBlank(startTime) ? DateUtil.parseLocalDateTime(startTime) : null, StrUtil.isNotBlank(endTime) ? DateUtil.parseLocalDateTime(endTime) : null, key, status)); + StrUtil.isNotBlank(startTime) ? DateUtil.parseLocalDateTime(startTime) : null, StrUtil.isNotBlank(endTime) ? DateUtil.parseLocalDateTime(endTime) : null, key, status, id)); } diff --git a/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java b/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java index 7c033fba..26ebac5a 100644 --- a/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java +++ b/cash-api/order-server/src/main/java/com/czg/controller/NotifyController.java @@ -15,7 +15,6 @@ import com.czg.order.service.OrderInfoService; import com.czg.order.service.OrderPaymentService; import com.czg.order.service.ShopTableOrderStatisticService; import com.czg.service.Impl.WxServiceImpl; -import com.czg.system.service.WxService; import com.czg.task.StatisticTask; import com.czg.utils.AssertUtil; import com.ijpay.core.kit.AesUtil; @@ -50,7 +49,7 @@ public class NotifyController { @Resource private ShopTableOrderStatisticService shopTableOrderStatisticService; @Resource - private WxService wxService; + private WxServiceImpl wxService; @Resource private MkDistributionUserService distributionUserService; @Resource diff --git a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/UserInfo.java b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/UserInfo.java index 2056746a..23e38364 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/account/entity/UserInfo.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/account/entity/UserInfo.java @@ -107,4 +107,7 @@ public class UserInfo implements Serializable { private LocalDateTime updateTime; private Integer usePayPwd; + private String realName; + private String idCard; + } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionFlow.java b/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionFlow.java index 79f27767..1533d86d 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionFlow.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/entity/MkDistributionFlow.java @@ -79,6 +79,7 @@ public class MkDistributionFlow implements Serializable { * 奖励金额 */ private BigDecimal rewardAmount; + private String billNo; /** * order订单 recharge充值 @@ -108,4 +109,7 @@ public class MkDistributionFlow implements Serializable { private Long sourceShopUserId; private String nickName; + + private String resp; + private String packageInfo; } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionFlowService.java b/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionFlowService.java index 3c9cbe12..c083ebf9 100644 --- a/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionFlowService.java +++ b/cash-common/cash-common-service/src/main/java/com/czg/market/service/MkDistributionFlowService.java @@ -14,5 +14,5 @@ import java.util.Map; */ public interface MkDistributionFlowService extends IService { - Map pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String key, String status); + Map pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String key, String status, Long id); } diff --git a/cash-common/cash-common-service/src/main/java/com/czg/system/service/WxService.java b/cash-common/cash-common-service/src/main/java/com/czg/system/service/WxService.java deleted file mode 100644 index bf33438d..00000000 --- a/cash-common/cash-common-service/src/main/java/com/czg/system/service/WxService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.czg.system.service; - -import java.math.BigDecimal; -import java.util.Map; - -/** - * 微信支付相关 - * @author Administrator - */ -public interface WxService { - - String getPhone(String code); - - String getOpenId(String code); - - Map v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type); - - String decryptToString(String associatedData, String nonceStr, String ciphertext); - -} diff --git a/cash-common/cash-common-tools/src/main/java/com/czg/constant/TableValueConstant.java b/cash-common/cash-common-tools/src/main/java/com/czg/constant/TableValueConstant.java index df249050..442edd00 100644 --- a/cash-common/cash-common-tools/src/main/java/com/czg/constant/TableValueConstant.java +++ b/cash-common/cash-common-tools/src/main/java/com/czg/constant/TableValueConstant.java @@ -65,7 +65,7 @@ public interface TableValueConstant { enum Status { PENDING("pending", "待入账"), SUCCESS("sub", "已入账"), - SELF_RECHARGE("self_recharge", "自助充值"); + SELF_RECHARGE("self_recharge", "自助充值"), FAIL("fail", "失败"); private final String code; private final String msg; diff --git a/cash-service/market-service/pom.xml b/cash-service/market-service/pom.xml index 2dd494f2..8120d1b9 100644 --- a/cash-service/market-service/pom.xml +++ b/cash-service/market-service/pom.xml @@ -17,6 +17,11 @@ cash-common-mq ${project.version} + + com.czg + pay-service + 1.0.0 + diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/mapper/MkDistributionFlowMapper.java b/cash-service/market-service/src/main/java/com/czg/service/market/mapper/MkDistributionFlowMapper.java index c54fbb42..589cc3dc 100644 --- a/cash-service/market-service/src/main/java/com/czg/service/market/mapper/MkDistributionFlowMapper.java +++ b/cash-service/market-service/src/main/java/com/czg/service/market/mapper/MkDistributionFlowMapper.java @@ -16,5 +16,5 @@ import java.util.List; */ public interface MkDistributionFlowMapper extends BaseMapper { - List pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String status, String key); + List pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String status, String key, Long id); } diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionFlowServiceImpl.java b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionFlowServiceImpl.java index 9e5ae0c0..bc4c1bca 100644 --- a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionFlowServiceImpl.java +++ b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionFlowServiceImpl.java @@ -38,9 +38,9 @@ public class MkDistributionFlowServiceImpl extends ServiceImpl pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String key, String status) { + public Map pageInfo(Long shopId, LocalDateTime startTime, LocalDateTime endTime, String key, String status, Long id) { PageHelper.startPage(PageUtil.buildPageHelp()); - List list = mapper.pageInfo(shopId, startTime, endTime, status, key); + List list = mapper.pageInfo(shopId, startTime, endTime, status, key, id); Page page = PageUtil.convert(new PageInfo<>(list)); Map map = BeanUtil.beanToMap(page); map.put("successAmount", getOne(new QueryWrapper().eq(MkDistributionFlow::getShopId, shopId) diff --git a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionUserServiceImpl.java b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionUserServiceImpl.java index a93246c4..6faf3c75 100644 --- a/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionUserServiceImpl.java +++ b/cash-service/market-service/src/main/java/com/czg/service/market/service/impl/MkDistributionUserServiceImpl.java @@ -3,10 +3,13 @@ package com.czg.service.market.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; +import com.alibaba.fastjson2.JSONObject; import com.czg.account.entity.ShopInfo; import com.czg.account.entity.ShopUser; +import com.czg.account.entity.UserInfo; import com.czg.account.service.ShopInfoService; import com.czg.account.service.ShopUserService; +import com.czg.account.service.UserInfoService; import com.czg.constant.TableValueConstant; import com.czg.exception.CzgException; import com.czg.market.dto.MkDistributionUserDTO; @@ -27,8 +30,8 @@ import com.czg.order.dto.MkDistributionPayDTO; import com.czg.order.entity.OrderPayment; import com.czg.order.service.OrderPaymentService; import com.czg.sa.StpKit; +import com.czg.service.Impl.AppWxServiceImpl; import com.czg.service.market.mapper.MkDistributionUserMapper; -import com.czg.system.service.WxService; import com.czg.utils.AssertUtil; import com.czg.utils.CzgRandomUtils; import com.czg.utils.PageUtil; @@ -70,15 +73,18 @@ public class MkDistributionUserServiceImpl extends ServiceImpl centerUser(Long userId) { @@ -336,15 +342,27 @@ public class MkDistributionUserServiceImpl extends ServiceImpl select a.*, b.phone, c.phone as sourcePhone from mk_distribution_flow as a + left join mk_distribution_user as d on d.id=a.distribution_user_id left join tb_shop_user as b on a.shop_user_id=b.id left join tb_shop_user as c on c.id=a.shop_user_id where a.shop_id=#{shopId} @@ -26,6 +27,9 @@ or c.nick_name like concat('%',#{key},'%') ) + + and d.id=#{id} + order by a.create_time desc diff --git a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java index c9fce3b8..493c3e3a 100644 --- a/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java +++ b/cash-service/order-service/src/main/java/com/czg/service/order/service/impl/DistributionPayServiceImpl.java @@ -17,8 +17,8 @@ import com.czg.order.dto.MkDistributionPayDTO; import com.czg.order.entity.OrderPayment; import com.czg.order.service.OrderPaymentService; import com.czg.resp.CzgResult; +import com.czg.service.Impl.WxServiceImpl; import com.czg.service.order.service.DistributionPayService; -import com.czg.system.service.WxService; import com.czg.utils.AssertUtil; import jakarta.annotation.Resource; import lombok.Data; @@ -41,7 +41,7 @@ public class DistributionPayServiceImpl implements DistributionPayService { @Resource private MkDistributionConfigService configService; @Resource - private WxService wxService; + private WxServiceImpl wxService; @Resource private PayServiceImpl payService; diff --git a/cash-service/pay-service/src/main/java/com/czg/service/Impl/AppWxServiceImpl.java b/cash-service/pay-service/src/main/java/com/czg/service/Impl/AppWxServiceImpl.java new file mode 100644 index 00000000..5d4a2c12 --- /dev/null +++ b/cash-service/pay-service/src/main/java/com/czg/service/Impl/AppWxServiceImpl.java @@ -0,0 +1,76 @@ +package com.czg.service.Impl; + +import com.czg.service.RedisService; +import com.czg.system.service.SysParamsService; +import com.czg.service.WxService; +import com.ijpay.core.kit.RsaKit; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.apache.dubbo.config.annotation.DubboService; + +/** + * 微信支付service + * @author Administrator + */ +@DubboService +@Slf4j +public class AppWxServiceImpl implements WxService { + + @Resource + private RedisService autoRedisService; + @DubboReference + private SysParamsService paramsService; + private static RedisService redisService; + private WxService.Config config = new Config(); + + @Override + public Config getConfig() { + return config; + } + + @Override + public RedisService getRedisService() { + return redisService; + } + + @PostConstruct + public void init() { + // 小程序id + config.appId = paramsService.getSysParamValue("shop_wx_appid"); + // 小程序secrete + config.appSecret = paramsService.getSysParamValue("shop_wx_secrete"); + config.certPath = ""; + // 微信支付公钥 + config.pubKey = paramsService.getSysParamValue("wx_pub_key"); + // api支付证书私钥 + config.apiCertKey = paramsService.getSysParamValue("wx_apiclient_key"); + // api支付证书公钥 + config.apiCert = paramsService.getSysParamValue("wx_apiclient_cert"); + try { + config.privateKey = RsaKit.loadPrivateKey(config.apiCertKey); + } catch (Exception e) { + log.warn("微信加载api证书失败"); + } + // 平台证书 + config.platformCertPath = ""; + // 平台证书编号 + config.platformCertNo = ""; + // 商户号 + config.mchId = paramsService.getSysParamValue("wx_mch_id"); + // v3密钥 + config.apiV3Key = paramsService.getSysParamValue("wx_v3_key"); + config.apiV2Key = ""; + // 回调地址 + config.notifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/pay"; + config.refundNotifyUrl = ""; + config.transferNotifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/transfer"; + redisService = this.autoRedisService; + } +} + + + + + diff --git a/cash-service/pay-service/src/main/java/com/czg/service/Impl/WxServiceImpl.java b/cash-service/pay-service/src/main/java/com/czg/service/Impl/WxServiceImpl.java index a11fc71c..6528f645 100644 --- a/cash-service/pay-service/src/main/java/com/czg/service/Impl/WxServiceImpl.java +++ b/cash-service/pay-service/src/main/java/com/czg/service/Impl/WxServiceImpl.java @@ -4,43 +4,19 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpUtil; -import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSONObject; import com.czg.exception.CzgException; import com.czg.service.RedisService; import com.czg.system.service.SysParamsService; -import com.czg.system.service.WxService; -import com.ijpay.core.IJPayHttpResponse; -import com.ijpay.core.enums.RequestMethodEnum; -import com.ijpay.core.enums.SignType; -import com.ijpay.core.kit.AesUtil; -import com.ijpay.core.kit.PayKit; +import com.czg.service.WxService; import com.ijpay.core.kit.RsaKit; -import com.ijpay.core.kit.WxPayKit; -import com.ijpay.wxpay.WxPayApi; -import com.ijpay.wxpay.enums.WxDomainEnum; -import com.ijpay.wxpay.enums.v3.BasePayApiEnum; -import com.ijpay.wxpay.enums.v3.OtherApiEnum; -import com.ijpay.wxpay.enums.v3.ProfitSharingApiEnum; -import com.ijpay.wxpay.model.ReceiverModel; -import com.ijpay.wxpay.model.v3.*; + import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.apache.dubbo.config.annotation.DubboService; -import java.io.ByteArrayInputStream; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.Base64; -import java.util.List; -import java.util.Locale; -import java.util.Map; /** * 微信支付service @@ -50,599 +26,56 @@ import java.util.Map; @Slf4j public class WxServiceImpl implements WxService { + @Resource private RedisService autoRedisService; @DubboReference private SysParamsService paramsService; private static RedisService redisService; + private WxService.Config config = new Config(); + + @Override + public Config getConfig() { + return config; + } + + @Override + public RedisService getRedisService() { + return redisService; + } @PostConstruct public void init() { // 小程序id - appId = paramsService.getSysParamValue("shop_wx_appid"); + config.appId = paramsService.getSysParamValue("shop_wx_appid"); // 小程序secrete - appSecret = paramsService.getSysParamValue("shop_wx_secrete"); - certPath = ""; + config.appSecret = paramsService.getSysParamValue("shop_wx_secrete"); + config.certPath = ""; // 微信支付公钥 - pubKey = paramsService.getSysParamValue("wx_pub_key"); + config.pubKey = paramsService.getSysParamValue("wx_pub_key"); // api支付证书私钥 - apiCertKey = paramsService.getSysParamValue("wx_apiclient_key"); + config.apiCertKey = paramsService.getSysParamValue("wx_apiclient_key"); // api支付证书公钥 - apiCert = paramsService.getSysParamValue("wx_apiclient_cert"); + config.apiCert = paramsService.getSysParamValue("wx_apiclient_cert"); try { - privateKey = RsaKit.loadPrivateKey(apiCertKey); + config.privateKey = RsaKit.loadPrivateKey(config.apiCertKey); } catch (Exception e) { log.warn("微信加载api证书失败"); } // 平台证书 - platformCertPath = ""; + config.platformCertPath = ""; // 平台证书编号 - platformCertNo = ""; + config.platformCertNo = ""; // 商户号 - mchId = paramsService.getSysParamValue("wx_mch_id"); + config.mchId = paramsService.getSysParamValue("wx_mch_id"); // v3密钥 - apiV3Key = paramsService.getSysParamValue("wx_v3_key"); - apiV2Key = ""; + config.apiV3Key = paramsService.getSysParamValue("wx_v3_key"); + config.apiV2Key = ""; // 回调地址 - notifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/pay"; - refundNotifyUrl = ""; - transferNotifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/transfer"; + config.notifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/pay"; + config.refundNotifyUrl = ""; + config.transferNotifyUrl = paramsService.getSysParamValue("native_notify_url") + "/wx/transfer"; redisService = this.autoRedisService; } - - - // static copies for use in static methods - private static String appId; - private static String appSecret; - private static String certPath; - private static String pubKey; - private static String apiCertKey; - private static PrivateKey privateKey; - private static String apiCert; - private static String platformCertPath; - private static String platformCertNo; - private static String mchId; - public static String apiV3Key; - private static String apiV2Key; - private static String notifyUrl; - private static String refundNotifyUrl; - private static String transferNotifyUrl; - - private static String getAccessToken(boolean refresh) { - Object token = redisService.get("access_token"); - if (!refresh && token instanceof String) { - return (String) token; - } - - String response = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token", - Map.of("grant_type", "client_credential", "appid", appId, "secret", appSecret) - ); - - log.info("获取access_token响应: {}", response); - JSONObject jsonObject = JSONObject.parseObject(response); - String accessToken = jsonObject.getString("access_token"); - if (accessToken == null) { - throw new RuntimeException("获取access_token失败"); - } - Long expiresIn = jsonObject.getLong("expires_in"); - if (expiresIn == null) { - expiresIn = 7200L; - } - redisService.set("access_token", accessToken, expiresIn - 200); - return accessToken; - } - - @Override - public String getPhone(String code) { - String requestBody = JSONObject.toJSONString(Map.of("code", code)); - String response = HttpUtil.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(false), requestBody); - log.info("获取手机号响应: {}", response); - JSONObject jsonObject = JSONObject.parseObject(response); - Integer errCode = jsonObject.getInteger("errcode"); - - if (Integer.valueOf(0).equals(errCode)) { - return jsonObject.getJSONObject("phone_info").getString("phoneNumber"); - } else if (Integer.valueOf(40001).equals(errCode)) { - getAccessToken(true); - } - throw new RuntimeException("获取手机号失败"); - } - - @Override - public String getOpenId(String code) { - String response = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session", - Map.of("appid", appId, "secret", appSecret, "js_code", code, "grant_type", "authorization_code") - ); - log.info("获取openId响应: {}", response); - JSONObject jsonObject = JSONObject.parseObject(response); - String openId = jsonObject.getString("openid"); - if (openId != null && !openId.isBlank()) { - return openId; - } - throw new RuntimeException("获取openId失败"); - } - - /** - * 使用微信支付平台证书公钥加密敏感信息(OAEP) - */ - private static String encryptByPlatformCert(String content) { - try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertPath)) { - if (certStream == null) { - throw new RuntimeException("未找到微信支付平台证书文件: " + platformCertPath); - } - X509Certificate certificate = PayKit.getCertificate(certStream); - if (certificate == null) throw new RuntimeException("读取证书失败"); - return PayKit.rsaEncryptOAEP(content, certificate); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static String getSerialNumberFromPem(String certContent) { - try { - // 去掉 PEM 头尾并清理空格换行 - String pem = certContent - .replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replaceAll("\\s+", ""); - - // Base64 解码 - byte[] certBytes = Base64.getDecoder().decode(pem); - - try (ByteArrayInputStream bis = new ByteArrayInputStream(certBytes)) { - X509Certificate certificate = PayKit.getCertificate(bis); - - if (certificate != null) { - String serialNo = certificate.getSerialNumber() - .toString(16) - .toUpperCase(Locale.getDefault()); - - System.out.println("证书序列号:" + serialNo); - - return serialNo; - } - } - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("无效的证书", e); - } - return null; - } - - public static void main(String[] args) { -// // 文件路径(支持绝对路径或相对路径) -// String filePath = "D:/certs/apiclient_cert.pem"; -// try (var certStream = new FileInputStream("E:\\workspace\\cashier_server1\\cash-api\\order-server\\src\\main\\resources\\cert.pem")) { -// X509Certificate certificate = PayKit.getCertificate(certStream); -// if (certificate != null) { -// String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); -// boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2); -// log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); -// log.info("证书序列号: {}", serialNo); -// } -// } catch (Exception e) { -// log.error("读取证书失败", e); -// } - - getSerialNumberFromPem("-----BEGIN CERTIFICATE-----\n" + - "MIIEJDCCAwygAwIBAgIUctkT9cVeYPVI02uqwzEw2Xg/QLQwDQYJKoZIhvcNAQEL\n" + - "BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\n" + - "FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\n" + - "Q0EwHhcNMjUxMDI1MDI0NzU3WhcNMzAxMDI0MDI0NzU3WjB+MRMwEQYDVQQDDAox\n" + - "NzMwMDkxMjAzMRswGQYDVQQKDBLlvq7kv6HllYbmiLfns7vnu58xKjAoBgNVBAsM\n" + - "IemZleilv+i2heaOjOafnOenkeaKgOaciemZkOWFrOWPuDELMAkGA1UEBhMCQ04x\n" + - "ETAPBgNVBAcMCFNoZW5aaGVuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" + - "AQEAqCcrDNKTGCBk2euqL6ENu2akxEdmlQQjlf8AVKdGA4nPp0GkWF+WQxFFxAHo\n" + - "jOLSeOUoxqQ5Nr9wa14Nfv3m6R4UIo+JeG0TkNyzum4GRokwoK70vQa8IXZW9vmM\n" + - "lVce/2mxb8sxcnVfAABPfo2A7L14PKPMvAPF4HgJOi5tnMco1sVlg4uwxejoMl7q\n" + - "CiE6lozFW2IRaZZrtmBJMW3oSxCGuLtaWmkXDyj4tLzAJspDxNMQHCVNzYodPREA\n" + - "OP9Z1Wy2v0xIseoxGnxgefE7LpF5jCZiGLkiOg1Xa79tpX02zZ22A8JvTfPWhKJJ\n" + - "nYV0r3PZi/32IU0bBmURYjuh5QIDAQABo4G5MIG2MAkGA1UdEwQCMAAwCwYDVR0P\n" + - "BAQDAgP4MIGbBgNVHR8EgZMwgZAwgY2ggYqggYeGgYRodHRwOi8vZXZjYS5pdHJ1\n" + - "cy5jb20uY24vcHVibGljL2l0cnVzY3JsP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFE\n" + - "Mzk3NTQ5ODQ2QzAxQzNFOEVCRDImc2c9SEFDQzQ3MUI2NTQyMkUxMkIyN0E5RDMz\n" + - "QTg3QUQxQ0RGNTkyNkUxNDAzNzEwDQYJKoZIhvcNAQELBQADggEBAKLBRR+E8hrK\n" + - "hQB+T0GIqQBgkFaCiakylUbw+jdEdQxJD8gO14tHSOTkqOMf5cZONnZiff2rIn+Y\n" + - "kFjR/lO//v5PsTIsby+siwnoc4MOz9tJ5qNUlleuXi3i/Fb1kU9nW6HC8GJdjZxG\n" + - "YqE3UyS23SG5VnrNeBplLHWS3ttmzduNKbl+180R5SoMIyCT35k0EStcZPlVrXnt\n" + - "MqJftxS2Jl1lDHer62tOTgKRGZFfS6z0DOD97mJfyRJ5agUnJTD0L1LCb3lefNRV\n" + - "KgmAkkE7q9cRRIKf8SifdvFDaGhFFi1ezXI/vorSh3PnpMqd8SCc81/IhWmm09RA\n" + - "RLdEZXoPSmM=\n" + - "-----END CERTIFICATE-----\n"); - } - - private static String getSerialNumber() { - try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(certPath)) { - X509Certificate certificate = PayKit.getCertificate(certStream); - if (certificate != null) { - String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); - boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2); - log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); - log.info("证书序列号: {}", serialNo); - return serialNo; - } - } catch (Exception e) { - log.error("读取证书失败", e); - } - return null; - } - - private static String getPlatSerialNumber() { - try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(platformCertNo)) { - X509Certificate certificate = PayKit.getCertificate(certStream); - if (certificate != null) { - String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); - boolean isValid = PayKit.checkCertificateIsValid(certificate, mchId, -2); - log.info("平台证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); - log.info("平台证书序列号: {}", serialNo); - return serialNo; - } - } catch (Exception e) { - log.error("读取平台证书失败", e); - } - return null; - } - - public static JSONObject verifySignature(HttpServletRequest request) { - try { - log.info("开始校验签名并解密"); - String timestamp = request.getHeader("Wechatpay-Timestamp"); - String nonce = request.getHeader("Wechatpay-Nonce"); - String serialNo = request.getHeader("Wechatpay-Serial"); - String signature = request.getHeader("Wechatpay-Signature"); - String result = request.getReader().lines().reduce((a, b) -> a + b).orElse(""); - String resp = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, apiV3Key, platformCertPath); - log.info("解密明文{}", resp); - return JSONObject.parseObject(resp); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static void getPlatCert() throws Exception { - String response = WxPayApi.v3( - RequestMethodEnum.GET, - WxDomainEnum.CHINA.toString(), - OtherApiEnum.GET_CERTIFICATES.toString(), - mchId, - getSerialNumber(), - null, - apiCertKey, - "" - ).getBody(); - - JSONObject jsonObject = JSONObject.parseObject(response); - var dataArray = jsonObject.getJSONArray("data"); - var encryptObject = dataArray.getJSONObject(0); - var encryptCertificate = encryptObject.getJSONObject("encrypt_certificate"); - String associatedData = encryptCertificate.getString("associated_data"); - String cipherText = encryptCertificate.getString("ciphertext"); - String nonce = encryptCertificate.getString("nonce"); - AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)); - String publicKey = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), cipherText); - log.info(publicKey); - } - - @Override - public String decryptToString(String associatedData, String nonceStr, String ciphertext) { - AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)); - try { - return aesUtil.decryptToString( - associatedData.getBytes(StandardCharsets.UTF_8), - nonceStr.getBytes(StandardCharsets.UTF_8), - ciphertext - ); - } catch (GeneralSecurityException e) { - throw new RuntimeException(e); - } - } - - @Override - public Map v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type) { - if (desc == null) desc = "订单支付"; - UnifiedOrderModel model = new UnifiedOrderModel(); - model.setAppid(appId); - model.setMchid(mchId); - model.setDescription(desc); - model.setOut_trade_no(tradeNo); - int total = amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact(); - model.setAmount(new Amount(total, "CNY")); - model.setNotify_url(notifyUrl + "/" + type); - model.setPayer(new Payer(openId, null, null)); - - String payInfo = JSONObject.toJSONString(model); - log.info("统一下单参数: {}", payInfo); - - IJPayHttpResponse resp; - try { - resp = WxPayApi.v3( - RequestMethodEnum.POST, - WxDomainEnum.CHINA.toString(), - BasePayApiEnum.JS_API_PAY.toString(), - mchId, - getSerialNumberFromPem(apiCert), - null, - privateKey, - payInfo - ); - log.info("统一下单响应: {}", resp); - String body = resp.getBody(); - JSONObject jsonObject = JSONObject.parseObject(body); - String prepayId = jsonObject.getString("prepay_id"); - if (StrUtil.isBlank(prepayId)) { - throw new RuntimeException(jsonObject.getString("message")); - } - return WxPayKit.jsApiCreateSign(appId, prepayId, privateKey); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static Map v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) { - Map payModel = com.ijpay.wxpay.model.UnifiedOrderModel.builder() - .appid(appId) - .mch_id(mchId) - .body(desc) - .out_trade_no(tradeNo) - .total_fee(amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact() + "") - .fee_type("CNY") - .notify_url(notifyUrl) - .openid(openId) - .trade_type("JSAPI") - .build() - .createSign(apiV2Key, SignType.MD5); - - log.info("统一下单参数: {}", payModel); - String resp = WxPayApi.pushOrder(false, payModel); - log.info("统一下单响应: {}", resp); - - Map resultMap = WxPayKit.xmlToMap(resp); - String returnCode = resultMap.get("return_code"); - String returnMsg = resultMap.get("return_msg"); - if (!WxPayKit.codeIsOk(returnCode)) { - throw new CzgException(returnMsg); - } - String resultCode = resultMap.get("result_code"); - if (!WxPayKit.codeIsOk(resultCode)) { - throw new CzgException(returnMsg); - } - String prepayId = resultMap.get("prepay_id"); - return WxPayKit.prepayIdCreateSign(prepayId, appId, appSecret, SignType.MD5); - } - - public static String refund(String tradeNo, String refundTradeNo, BigDecimal amount) { - int finalAmount = amount.multiply(new BigDecimal(100)).intValueExact(); - RefundModel model = new RefundModel(); - model.setOut_trade_no(tradeNo); - model.setOut_refund_no(refundTradeNo); - model.setAmount(new RefundAmount(finalAmount, "CNY", finalAmount)); - model.setNotify_url(refundNotifyUrl); - - String info = JSONObject.toJSONString(model); - log.info("统一退款参数: {}", info); - IJPayHttpResponse response; - try { - response = WxPayApi.v3( - RequestMethodEnum.POST, - WxDomainEnum.CHINA.toString(), - BasePayApiEnum.REFUND.toString(), - mchId, - getSerialNumber(), - getSerialNumber(), - apiCertKey, - info - ); - } catch (Exception e) { - throw new RuntimeException(e); - } - log.info("统一退款响应 {}", response); - String body = response.getBody(); - JSONObject jsonObject = JSONObject.parseObject(body); - if ("ABNORMAL".equals(jsonObject.getString("status")) || response.getStatus() != 200) { - throw new CzgException("退款异常," + jsonObject.getString("message")); - } - return jsonObject.getString("refund_id"); - } - - public static String genCode(int shopId, int id, String path) { - Map params = Map.of( - "scene", "id=" + id + "&shopId=" + shopId, - "page", path, - "width", 430 - ); - var response = HttpRequest.post("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(false)) - .body(JSONObject.toJSONString(params)) - .execute(); - - byte[] bodyBytes = response.bodyBytes(); - String str = new String(bodyBytes); - if (str.contains("errmsg")) { - JSONObject json = JSONObject.parseObject(str); - throw new CzgException(json.getString("errmsg")); - } - return "data:image/png;base64," + Base64.getEncoder().encodeToString(bodyBytes); - } - - public static String addProfitSharingUser(AddReceivers params) throws Exception { - getPlatCert(); - params.setAppid(appId); - if (params.getName() != null && !params.getName().isBlank()) { - params.setName(encryptByPlatformCert(params.getName())); - } else { - params.setName(null); - } - log.info("添加分账方参数: {}", JSONObject.toJSONString(params)); - var response = WxPayApi.v3( - RequestMethodEnum.POST, - WxDomainEnum.CHINA.toString(), - ProfitSharingApiEnum.PROFIT_SHARING_RECEIVERS_ADD.toString(), - mchId, - getSerialNumber(), - platformCertNo, - apiCertKey, - JSONObject.toJSONString(params) - ); - log.info("添加分账方响应 {}", response.getBody()); - JSONObject resp = JSONObject.parseObject(response.getBody()); - if (resp.containsKey("code")) { - throw new CzgException("分账添加失败" + resp.getString("message")); - } - return resp.getString("account"); - } - - public static JSONObject getMchConfig(String mchIdParam) throws Exception { - var response = WxPayApi.v3( - RequestMethodEnum.GET, - WxDomainEnum.CHINA.toString(), - String.format(ProfitSharingApiEnum.PROFIT_SHARING_MERCHANT_CONFIGS.toString(), mchIdParam), - mchId, - getSerialNumber(), - getSerialNumber(), - apiCertKey, - "" - ); - log.info("查询分账响应 {}", response.getBody()); - JSONObject resp = JSONObject.parseObject(response.getBody()); - if (resp.containsKey("code")) { - throw new CzgException("分账查询失败" + resp.getString("message")); - } - return resp; - } - - /** - * 获取可分账金额 - */ - public static BigDecimal getRemainAmount(String transactionId) { - IJPayHttpResponse response; - try { - response = WxPayApi.v3( - RequestMethodEnum.GET, - WxDomainEnum.CHINA.toString(), - String.format(ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE_QUERY.toString(), transactionId), - mchId, - getSerialNumber(), - getSerialNumber(), - apiCertKey, - "" - ); - } catch (Exception e) { - throw new RuntimeException(e); - } - log.info("查询可分账响应 {}", response.getBody()); - JSONObject resp = JSONObject.parseObject(response.getBody()); - if (resp.containsKey("code")) { - throw new CzgException("分账查询失败" + resp.getString("message")); - } - return resp.getBigDecimal("unsplit_amount"); - } - - public static void profitSharing(String transactionId, ReceiverModel... receivers) { - for (ReceiverModel r : receivers) { - if (r.getName() != null && !r.getName().isBlank()) { - r.setName(encryptByPlatformCert(r.getName())); - } - } - String tradeNo = IdUtil.simpleUUID(); - ProfitSharingModel model = new ProfitSharingModel(); - model.setAppid(appId); - model.setTransaction_id(transactionId); - model.setReceivers(List.of(receivers)); - model.setUnfreeze_unsplit(true); - model.setOut_order_no(tradeNo); - - String params = JSONObject.toJSONString(model); - log.info("分账参数: {}", params); - IJPayHttpResponse response; - try { - response = WxPayApi.v3( - RequestMethodEnum.POST, - WxDomainEnum.CHINA.toString(), - ProfitSharingApiEnum.PROFIT_SHARING_ORDERS.toString(), - mchId, - getSerialNumber(), - platformCertNo, - apiCertKey, - params - ); - } catch (Exception e) { - throw new RuntimeException(e); - } - log.info("分账响应 {}", response.getBody()); - JSONObject resp = JSONObject.parseObject(response.getBody()); - if (resp.containsKey("code")) { - throw new CzgException("分账失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); - } - } - - public static void unFreezeSharing(String transactionId, String orderNo) { - com.ijpay.wxpay.model.ProfitSharingModel paramsModel = new com.ijpay.wxpay.model.ProfitSharingModel(); - paramsModel.setTransaction_id(transactionId); - paramsModel.setOut_order_no(orderNo); - paramsModel.setDescription("分账解冻"); - String params = JSONObject.toJSONString(paramsModel); - log.info("分账解冻参数: {}", params); - IJPayHttpResponse response; - try { - response = WxPayApi.v3( - RequestMethodEnum.POST, - WxDomainEnum.CHINA.toString(), - ProfitSharingApiEnum.PROFIT_SHARING_UNFREEZE.toString(), - mchId, - getSerialNumber(), - platformCertNo, - apiCertKey, - params - ); - } catch (Exception e) { - throw new RuntimeException(e); - } - log.info("分账解冻响应 {}", response.getBody()); - JSONObject resp = JSONObject.parseObject(response.getBody()); - if (resp.containsKey("code")) { - throw new CzgException("分账解冻失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); - } - } - - public static JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) { - String remark = remarkTxt == null ? "佣金" : remarkTxt; - String billNo = billNoTxt == null ? IdUtil.simpleUUID() : billNoTxt; - Map params = Map.of( - "appid", appId, - "out_bill_no", billNo, - "transfer_scene_id", "1005", - "openid", openId, - "user_name", encryptByPlatformCert(name), - "transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue(), - "transfer_remark", remark, - "notify_url", transferNotifyUrl, - "transfer_scene_report_infos", new Object[]{ - Map.of("info_type", "岗位类型", "info_content", "加盟商"), - Map.of("info_type", "报酬说明", "info_content", "测试") - } - ); - log.info("转账到零钱参数: {}", JSONObject.toJSONString(params)); - IJPayHttpResponse response = null; - try { - response = WxPayApi.v3( - RequestMethodEnum.POST, - WxDomainEnum.CHINA.toString(), - "/v3/fund-app/mch-transfer/transfer-bills", - mchId, - getSerialNumber(), - platformCertNo, - apiCertKey, - JSONObject.toJSONString(params) - ); - } catch (Exception e) { - throw new RuntimeException(e); - } - log.info("转账到零钱响应 {}", response.getBody()); - JSONObject resp = JSONObject.parseObject(response.getBody()); - if (resp.containsKey("code")) { - throw new CzgException("转账到零钱失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); - } - return resp; - } } diff --git a/cash-service/pay-service/src/main/java/com/czg/service/WxService.java b/cash-service/pay-service/src/main/java/com/czg/service/WxService.java new file mode 100644 index 00000000..80cf15a9 --- /dev/null +++ b/cash-service/pay-service/src/main/java/com/czg/service/WxService.java @@ -0,0 +1,392 @@ +package com.czg.service; + +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson2.JSONObject; +import com.czg.exception.CzgException; +import com.ijpay.core.IJPayHttpResponse; +import com.ijpay.core.enums.RequestMethodEnum; +import com.ijpay.core.enums.SignType; +import com.ijpay.core.kit.AesUtil; +import com.ijpay.core.kit.PayKit; +import com.ijpay.core.kit.WxPayKit; +import com.ijpay.wxpay.WxPayApi; +import com.ijpay.wxpay.enums.WxDomainEnum; +import com.ijpay.wxpay.enums.v3.BasePayApiEnum; +import com.ijpay.wxpay.model.v3.*; +import jakarta.servlet.http.HttpServletRequest; +import lombok.Data; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.Locale; +import java.util.Map; + +/** + * 微信支付相关 + * @author Administrator + */ +public interface WxService { + Logger log = LoggerFactory.getLogger(WxService.class); + @Data + class Config { + public String appId; + public String appSecret; + public String certPath; + public String pubKey; + public String apiCertKey; + public PrivateKey privateKey; + public String apiCert; + public String platformCertPath; + public String platformCertNo; + public String mchId; + public String apiV3Key; + public String apiV2Key; + public String notifyUrl; + public String refundNotifyUrl; + public String transferNotifyUrl; + } + Config getConfig(); + RedisService getRedisService(); + + + default String getPhone(String code) { + String requestBody = JSONObject.toJSONString(Map.of("code", code)); + String response = HttpUtil.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(false), requestBody); + log.info("获取手机号响应: {}", response); + JSONObject jsonObject = JSONObject.parseObject(response); + Integer errCode = jsonObject.getInteger("errcode"); + + if (Integer.valueOf(0).equals(errCode)) { + return jsonObject.getJSONObject("phone_info").getString("phoneNumber"); + } else if (Integer.valueOf(40001).equals(errCode)) { + getAccessToken(true); + } + throw new RuntimeException("获取手机号失败"); + } + + default String getAccessToken(boolean refresh) { + Object token = getRedisService().get("access_token"); + Config config = getConfig(); + if (!refresh && token instanceof String) { + return (String) token; + } + + String response = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token", + Map.of("grant_type", "client_credential", "appid", config.appId, "secret", config.appSecret) + ); + + log.info("获取access_token响应: {}", response); + JSONObject jsonObject = JSONObject.parseObject(response); + String accessToken = jsonObject.getString("access_token"); + if (accessToken == null) { + throw new RuntimeException("获取access_token失败"); + } + Long expiresIn = jsonObject.getLong("expires_in"); + if (expiresIn == null) { + expiresIn = 7200L; + } + getRedisService().set("access_token", accessToken, expiresIn - 200); + return accessToken; + } + + /** + * 使用微信支付平台证书公钥加密敏感信息(OAEP) + */ + default String encryptByPlatformCert(String content) { + Config config = getConfig(); + + try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(config.getPlatformCertPath())) { + if (certStream == null) { + throw new RuntimeException("未找到微信支付平台证书文件: " + config.getPlatformCertPath()); + } + X509Certificate certificate = PayKit.getCertificate(certStream); + if (certificate == null) { + throw new RuntimeException("读取证书失败"); + } + return PayKit.rsaEncryptOAEP(content, certificate); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + default String getSerialNumberFromPem(String certContent) { + try { + // 去掉 PEM 头尾并清理空格换行 + String pem = certContent + .replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\s+", ""); + + // Base64 解码 + byte[] certBytes = Base64.getDecoder().decode(pem); + + try (ByteArrayInputStream bis = new ByteArrayInputStream(certBytes)) { + X509Certificate certificate = PayKit.getCertificate(bis); + + if (certificate != null) { + String serialNo = certificate.getSerialNumber() + .toString(16) + .toUpperCase(Locale.getDefault()); + + System.out.println("证书序列号:" + serialNo); + + return serialNo; + } + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("无效的证书", e); + } + return null; + } + + default String getSerialNumber() { + Config config = getConfig(); + try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(config.getCertPath())) { + X509Certificate certificate = PayKit.getCertificate(certStream); + if (certificate != null) { + String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault()); + boolean isValid = PayKit.checkCertificateIsValid(certificate, config.getMchId(), -2); + log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter()); + log.info("证书序列号: {}", serialNo); + return serialNo; + } + } catch (Exception e) { + log.error("读取证书失败", e); + } + return null; + } + + default JSONObject verifySignature(HttpServletRequest request) { + Config config = getConfig(); + + try { + log.info("开始校验签名并解密"); + String timestamp = request.getHeader("Wechatpay-Timestamp"); + String nonce = request.getHeader("Wechatpay-Nonce"); + String serialNo = request.getHeader("Wechatpay-Serial"); + String signature = request.getHeader("Wechatpay-Signature"); + String result = request.getReader().lines().reduce((a, b) -> a + b).orElse(""); + String resp = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, config.getApiV3Key(), config.getPlatformCertPath()); + log.info("解密明文{}", resp); + return JSONObject.parseObject(resp); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + default String decryptToString(String associatedData, String nonceStr, String ciphertext) { + AesUtil aesUtil = new AesUtil(getConfig().getApiV3Key().getBytes(StandardCharsets.UTF_8)); + try { + return aesUtil.decryptToString( + associatedData.getBytes(StandardCharsets.UTF_8), + nonceStr.getBytes(StandardCharsets.UTF_8), + ciphertext + ); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + default String getOpenId(String code) { + Config config = getConfig(); + String response = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session", + Map.of("appid", config.getAppId() , "secret", config.getAppSecret(), "js_code", code, "grant_type", "authorization_code") + ); + log.info("获取openId响应: {}", response); + JSONObject jsonObject = JSONObject.parseObject(response); + String openId = jsonObject.getString("openid"); + if (openId != null && !openId.isBlank()) { + return openId; + } + throw new RuntimeException("获取openId失败"); + } + + default Map v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type) { + Config config = getConfig(); + if (desc == null) desc = "订单支付"; + UnifiedOrderModel model = new UnifiedOrderModel(); + model.setAppid(config.getAppId()); + model.setMchid(config.getMchId()); + model.setDescription(desc); + model.setOut_trade_no(tradeNo); + int total = amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact(); + model.setAmount(new Amount(total, "CNY")); + model.setNotify_url(config.getNotifyUrl() + "/" + type); + model.setPayer(new Payer(openId, null, null)); + + String payInfo = JSONObject.toJSONString(model); + log.info("统一下单参数: {}", payInfo); + + IJPayHttpResponse resp; + try { + resp = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + BasePayApiEnum.JS_API_PAY.toString(), + config.mchId, + getSerialNumberFromPem(config.apiCert), + null, + config.privateKey, + payInfo + ); + log.info("统一下单响应: {}", resp); + String body = resp.getBody(); + JSONObject jsonObject = JSONObject.parseObject(body); + String prepayId = jsonObject.getString("prepay_id"); + if (StrUtil.isBlank(prepayId)) { + throw new RuntimeException(jsonObject.getString("message")); + } + return WxPayKit.jsApiCreateSign(config.appId, prepayId, config.privateKey); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + default Map v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) { + Config config = getConfig(); + Map payModel = com.ijpay.wxpay.model.UnifiedOrderModel.builder() + .appid(config.appId) + .mch_id(config.mchId) + .body(desc) + .out_trade_no(tradeNo) + .total_fee(amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact() + "") + .fee_type("CNY") + .notify_url(config.notifyUrl) + .openid(openId) + .trade_type("JSAPI") + .build() + .createSign(config.apiV2Key, SignType.MD5); + + log.info("统一下单参数: {}", payModel); + String resp = WxPayApi.pushOrder(false, payModel); + log.info("统一下单响应: {}", resp); + + Map resultMap = WxPayKit.xmlToMap(resp); + String returnCode = resultMap.get("return_code"); + String returnMsg = resultMap.get("return_msg"); + if (!WxPayKit.codeIsOk(returnCode)) { + throw new CzgException(returnMsg); + } + String resultCode = resultMap.get("result_code"); + if (!WxPayKit.codeIsOk(resultCode)) { + throw new CzgException(returnMsg); + } + String prepayId = resultMap.get("prepay_id"); + return WxPayKit.prepayIdCreateSign(prepayId, config.appId, config.appSecret, SignType.MD5); + } + + + default String refund(String tradeNo, String refundTradeNo, BigDecimal amount) { + Config config = getConfig(); + int finalAmount = amount.multiply(new BigDecimal(100)).intValueExact(); + RefundModel model = new RefundModel(); + model.setOut_trade_no(tradeNo); + model.setOut_refund_no(refundTradeNo); + model.setAmount(new RefundAmount(finalAmount, "CNY", finalAmount)); + model.setNotify_url(config.refundNotifyUrl); + + String info = JSONObject.toJSONString(model); + log.info("统一退款参数: {}", info); + IJPayHttpResponse response; + try { + response = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + BasePayApiEnum.REFUND.toString(), + config.mchId, + getSerialNumber(), + getSerialNumber(), + config.apiCertKey, + info + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + log.info("统一退款响应 {}", response); + String body = response.getBody(); + JSONObject jsonObject = JSONObject.parseObject(body); + if ("ABNORMAL".equals(jsonObject.getString("status")) || response.getStatus() != 200) { + throw new CzgException("退款异常," + jsonObject.getString("message")); + } + return jsonObject.getString("refund_id"); + } + + default String genCode(int shopId, int id, String path) { + Map params = Map.of( + "scene", "id=" + id + "&shopId=" + shopId, + "page", path, + "width", 430 + ); + var response = HttpRequest.post("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(false)) + .body(JSONObject.toJSONString(params)) + .execute(); + + byte[] bodyBytes = response.bodyBytes(); + String str = new String(bodyBytes); + if (str.contains("errmsg")) { + JSONObject json = JSONObject.parseObject(str); + throw new CzgException(json.getString("errmsg")); + } + return "data:image/png;base64," + Base64.getEncoder().encodeToString(bodyBytes); + } + + + + default JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) { + Config config = getConfig(); + String remark = remarkTxt == null ? "佣金" : remarkTxt; + String billNo = billNoTxt == null ? IdUtil.simpleUUID() : billNoTxt; + Map params = Map.of( + "appid", config.appId, + "out_bill_no", billNo, + "transfer_scene_id", "1005", + "openid", openId, + "user_name", encryptByPlatformCert(name), + "transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue(), + "transfer_remark", remark, + "notify_url", config.transferNotifyUrl, + "transfer_scene_report_infos", new Object[]{ + Map.of("info_type", "岗位类型", "info_content", "加盟商"), + Map.of("info_type", "报酬说明", "info_content", "测试") + } + ); + log.info("转账到零钱参数: {}", JSONObject.toJSONString(params)); + IJPayHttpResponse response = null; + try { + response = WxPayApi.v3( + RequestMethodEnum.POST, + WxDomainEnum.CHINA.toString(), + "/v3/fund-app/mch-transfer/transfer-bills", + config.mchId, + getSerialNumber(), + config.platformCertNo, + config.apiCertKey, + JSONObject.toJSONString(params) + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + log.info("转账到零钱响应 {}", response.getBody()); + JSONObject resp = JSONObject.parseObject(response.getBody()); + if (resp.containsKey("code")) { + throw new CzgException("转账到零钱失败" + resp.getString("message") + ", 响应: " + resp.toJSONString()); + } + return resp; + } + + +}