ruoyi-api/ruoyi-api-payment/src/main/java/com/ruoyi/payment/api/factory/AliPaymentFallbackFactory.java
New file @@ -0,0 +1,45 @@ package com.ruoyi.payment.api.factory; import com.ruoyi.common.core.domain.R; import com.ruoyi.payment.api.feignClient.AliPaymentClient; import com.ruoyi.payment.api.vo.AliPaymentReq; import com.ruoyi.payment.api.vo.AliPaymentResp; import com.ruoyi.payment.api.vo.AliQueryOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.stereotype.Component; /** * 充电订单服务降级处理 * * @author ruoyi */ @Component public class AliPaymentFallbackFactory implements FallbackFactory<AliPaymentClient> { private static final Logger log = LoggerFactory.getLogger(AliPaymentFallbackFactory.class); @Override public AliPaymentClient create(Throwable throwable) { log.error("支付宝支付调用失败:{}", throwable.getMessage()); return new AliPaymentClient() { @Override public R<AliPaymentResp> payment(AliPaymentReq req) { throw new RuntimeException("调起支付宝小程序支付失败:" + throwable.getMessage()); } @Override public R<AliQueryOrder> query(String outTradeNo) { throw new RuntimeException("查询支付订单失败:" + throwable.getMessage()); } @Override public void close(String outTradeNo) { throw new RuntimeException("关闭支付订单失败:" + throwable.getMessage()); } }; } } ruoyi-api/ruoyi-api-payment/src/main/java/com/ruoyi/payment/api/factory/WxPaymentFallbackFactory.java
@@ -29,7 +29,7 @@ @Override public R<NotifyV3PayDecodeRespBody> queryOrderInfo(String orderId) { return R.fail("查询支付订单信息失败:" + throwable.getMessage()); throw new RuntimeException("查询支付订单信息失败:" + throwable.getMessage()); } @Override @@ -49,7 +49,7 @@ @Override public void close(String outTradeNo) { throw new RuntimeException("关闭支付订单失败:" + throwable.getMessage()); } }; } ruoyi-api/ruoyi-api-payment/src/main/java/com/ruoyi/payment/api/feignClient/AliPaymentClient.java
New file @@ -0,0 +1,43 @@ package com.ruoyi.payment.api.feignClient; import com.ruoyi.common.core.constant.ServiceNameConstants; import com.ruoyi.common.core.domain.R; import com.ruoyi.payment.api.factory.WxPaymentFallbackFactory; import com.ruoyi.payment.api.vo.AliPaymentReq; import com.ruoyi.payment.api.vo.AliPaymentResp; import com.ruoyi.payment.api.vo.AliQueryOrder; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; /** * @author zhibing.pu * @Date 2024/8/24 14:39 */ @FeignClient(contextId = "AliPaymentClient", value = ServiceNameConstants.PAYMENT_SERVICE, fallbackFactory = WxPaymentFallbackFactory.class) public interface AliPaymentClient { /** * 调起支付宝小程序支付 * @param req * @return */ @PostMapping("/ali/payment") R<AliPaymentResp> payment(AliPaymentReq req); /** * 查询支付订单 * @param outTradeNo * @return */ @PostMapping("/ali/query") R<AliQueryOrder> query(String outTradeNo); /** * 关闭订单 * @param outTradeNo */ @PostMapping("/ali/close") void close(String outTradeNo); } ruoyi-api/ruoyi-api-payment/src/main/java/com/ruoyi/payment/api/vo/AliPaymentReq.java
New file @@ -0,0 +1,51 @@ package com.ruoyi.payment.api.vo; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/23 16:52 */ @Data public class AliPaymentReq { /** * 业务流水号 */ private String outTradeNo; /** * 支付总金额 */ private String totalAmount; /** * 订单标题 */ private String subject; /** * 支付用户支付宝openid */ private String buyerOpenId; /** * 订单附加信息 */ private String body; /** * 订单超时时间 * 订单相对超时时间。从交易创建时间开始计算。 * 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。 * 当面付场景默认值为3h */ private String timeoutExpress; /** * 回传参数 * 如果请求时传递了该参数,支付宝会在异步通知时将该参数原样返回。 */ private String passbackParams; /** * 初始化默认数据 */ public AliPaymentReq() { //订单超时默认30分钟 this.timeoutExpress = "30m"; } } ruoyi-api/ruoyi-api-payment/src/main/java/com/ruoyi/payment/api/vo/AliPaymentResp.java
New file @@ -0,0 +1,24 @@ package com.ruoyi.payment.api.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/24 15:09 */ @Data @ApiModel public class AliPaymentResp { /** * 支付单号 */ @ApiModelProperty("支付单号") private String tradeNo; /** * 回调通知地址 */ @ApiModelProperty("回调通知地址") private String notifyUrl; } ruoyi-api/ruoyi-api-payment/src/main/java/com/ruoyi/payment/api/vo/AliQueryOrder.java
New file @@ -0,0 +1,48 @@ package com.ruoyi.payment.api.vo; import com.fasterxml.jackson.annotation.JsonSetter; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/24 12:01 */ @Data public class AliQueryOrder { /** * 支付宝交易号 */ @JsonSetter("trade_no") private String tradeNo; /** * 业务流水号 */ @JsonSetter("out_trade_no") private String outTradeNo; /** * 交易状态 * WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、TRADE_SUCCESS(交易支付成功)、TRADE_FINISHED(交易结束,不可退款) */ @JsonSetter("trade_status") private String tradeStatus; /** * 交易订单金额 */ @JsonSetter("total_amount") private Double totalAmount; /** * 回传参数 */ @JsonSetter("passback_params") private String passbackParams; /** * 订单标题 */ @JsonSetter("subject") private String subject; /** * 订单描述 */ @JsonSetter("body") private String body; } ruoyi-api/ruoyi-api-payment/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1 +1,2 @@ com.ruoyi.payment.api.factory.WxPaymentFallbackFactory com.ruoyi.payment.api.factory.WxPaymentFallbackFactory com.ruoyi.payment.api.factory.AliPaymentFallbackFactory ruoyi-common/ruoyi-common-security/src/main/java/com/ruoyi/common/security/service/TokenService.java
@@ -22,210 +22,200 @@ /** * token验证处理 * * * @author ruoyi */ @Component public class TokenService { @Autowired private RedisService redisService; protected static final long MILLIS_SECOND = 1000; protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; private final static long expireTime = CacheConstants.EXPIRATION; private final static long expireAppletTime = CacheConstants.EXPIRATION_APPLET; private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY; private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; /** * 创建令牌 */ public Map<String, Object> createToken(LoginUser loginUser) { String token = IdUtils.fastUUID(); Long userId = loginUser.getSysUser().getUserId(); String userName = loginUser.getSysUser().getUserName(); loginUser.setToken(token); loginUser.setUserid(userId); loginUser.setUsername(userName); loginUser.setIpaddr(IpUtils.getIpAddr()); refreshToken(loginUser); // Jwt存储信息 Map<String, Object> claimsMap = new HashMap<String, Object>(); claimsMap.put(SecurityConstants.USER_KEY, token); claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); claimsMap.put(SecurityConstants.USER_TYPE, "system"); claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName); // 接口返回信息 Map<String, Object> rspMap = new HashMap<String, Object>(); rspMap.put("access_token", JwtUtils.createToken(claimsMap)); rspMap.put("expires_in", expireTime); return rspMap; } /** * 创建小程序令牌 */ public Map<String, Object> createTokenApplet(LoginUserApplet loginUser) { String token = IdUtils.fastUUID(); Long userId = loginUser.getUserId(); String name = loginUser.getName(); loginUser.setToken(token); loginUser.setIpaddr(IpUtils.getIpAddr()); refreshToken1(loginUser); // Jwt存储信息 Map<String, Object> claimsMap = new HashMap<String, Object>(); claimsMap.put(SecurityConstants.USER_APPLET_KEY, token); claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); claimsMap.put(SecurityConstants.USER_TYPE, "applet"); claimsMap.put(SecurityConstants.DETAILS_USERNAME, name); // 接口返回信息 Map<String, Object> rspMap = new HashMap<String, Object>(); rspMap.put("access_token", JwtUtils.createToken(claimsMap)); rspMap.put("expires_in", expireAppletTime); return rspMap; } public LoginUserApplet getLoginUserApplet() { LoginUserApplet loginUserAppletToken = getLoginUserAppletToken(ServletUtils.getRequest()); if (loginUserAppletToken == null){ throw new UserAppletException("登录失效,请重新登录!", 401); } return loginUserAppletToken; } public LoginUserApplet getLoginUserAppletToken(HttpServletRequest request) { // 获取请求携带的令牌 String token = SecurityUtils.getToken(request); return getLoginUserApplet(token); } /** * 小程序 获取用户身份信息 * * @return 用户信息 */ public LoginUserApplet getLoginUserApplet(String token) { LoginUserApplet user = null; try { if (StringUtils.isNotEmpty(token)) { String userKey = JwtUtils.getUserKeyApplet(token); user = redisService.getCacheObject(getTokenKey(userKey)); return user; } } catch (Exception e) { e.printStackTrace(); } return user; } /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUser getLoginUser() { return getLoginUser(ServletUtils.getRequest()); } /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUser getLoginUser(HttpServletRequest request) { // 获取请求携带的令牌 String token = SecurityUtils.getToken(request); return getLoginUser(token); } /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUser getLoginUser(String token) { LoginUser user = null; try { if (StringUtils.isNotEmpty(token)) { String userkey = JwtUtils.getUserKey(token); user = redisService.getCacheObject(getTokenKey(userkey)); return user; } } catch (Exception e) { } return user; } /** * 设置用户身份信息 */ public void setLoginUser(LoginUser loginUser) { if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { refreshToken(loginUser); } } /** * 删除用户缓存信息 */ public void delLoginUser(String token) { if (StringUtils.isNotEmpty(token)) { String userkey = JwtUtils.getUserKey(token); redisService.deleteObject(getTokenKey(userkey)); } } /** * 验证令牌有效期,相差不足120分钟,自动刷新缓存 * * @param loginUser */ public void verifyToken(LoginUser loginUser) { long expireTime = loginUser.getExpireTime(); long currentTime = System.currentTimeMillis(); if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { refreshToken(loginUser); } } /** * 刷新令牌有效期 * * @param loginUser 登录信息 */ public void refreshToken(LoginUser loginUser) { loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); // 根据uuid将loginUser缓存 String userKey = getTokenKey(loginUser.getToken()); redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); } public void refreshToken1(LoginUserApplet dto) { dto.setLoginTime(System.currentTimeMillis()); dto.setExpireTime(dto.getLoginTime() + expireTime * MILLIS_MINUTE); // 根据uuid将loginUser缓存 String userKey = getTokenKey(dto.getToken()); redisService.setCacheObject(userKey, dto, expireTime, TimeUnit.MINUTES); } private String getTokenKey(String token) { return ACCESS_TOKEN + token; } public class TokenService { @Autowired private RedisService redisService; protected static final long MILLIS_SECOND = 1000; protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; private final static long expireTime = CacheConstants.EXPIRATION; private final static long expireAppletTime = CacheConstants.EXPIRATION_APPLET; private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY; private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE; /** * 创建令牌 */ public Map<String, Object> createToken(LoginUser loginUser) { String token = IdUtils.fastUUID(); Long userId = loginUser.getSysUser().getUserId(); String userName = loginUser.getSysUser().getUserName(); loginUser.setToken(token); loginUser.setUserid(userId); loginUser.setUsername(userName); loginUser.setIpaddr(IpUtils.getIpAddr()); refreshToken(loginUser); // Jwt存储信息 Map<String, Object> claimsMap = new HashMap<String, Object>(); claimsMap.put(SecurityConstants.USER_KEY, token); claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); claimsMap.put(SecurityConstants.USER_TYPE, "system"); claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName); // 接口返回信息 Map<String, Object> rspMap = new HashMap<String, Object>(); rspMap.put("access_token", JwtUtils.createToken(claimsMap)); rspMap.put("expires_in", expireTime); return rspMap; } /** * 创建小程序令牌 */ public Map<String, Object> createTokenApplet(LoginUserApplet loginUser) { String token = IdUtils.fastUUID(); Long userId = loginUser.getUserId(); String name = loginUser.getName(); loginUser.setToken(token); loginUser.setIpaddr(IpUtils.getIpAddr()); refreshToken1(loginUser); // Jwt存储信息 Map<String, Object> claimsMap = new HashMap<String, Object>(); claimsMap.put(SecurityConstants.USER_APPLET_KEY, token); claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId); claimsMap.put(SecurityConstants.USER_TYPE, "applet"); claimsMap.put(SecurityConstants.DETAILS_USERNAME, name); // 接口返回信息 Map<String, Object> rspMap = new HashMap<String, Object>(); rspMap.put("access_token", JwtUtils.createToken(claimsMap)); rspMap.put("expires_in", expireAppletTime); return rspMap; } public LoginUserApplet getLoginUserApplet() { LoginUserApplet loginUserAppletToken = getLoginUserAppletToken(ServletUtils.getRequest()); if (loginUserAppletToken == null) { throw new UserAppletException("登录失效,请重新登录!", 401); } return loginUserAppletToken; } public LoginUserApplet getLoginUserAppletToken(HttpServletRequest request) { // 获取请求携带的令牌 String token = SecurityUtils.getToken(request); return getLoginUserApplet(token); } /** * 小程序 获取用户身份信息 * * @return 用户信息 */ public LoginUserApplet getLoginUserApplet(String token) { LoginUserApplet user = null; try { if (StringUtils.isNotEmpty(token)) { String userKey = JwtUtils.getUserKeyApplet(token); user = redisService.getCacheObject(getTokenKey(userKey)); return user; } } catch (Exception e) { e.printStackTrace(); } return user; } /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUser getLoginUser() { return getLoginUser(ServletUtils.getRequest()); } /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUser getLoginUser(HttpServletRequest request) { // 获取请求携带的令牌 String token = SecurityUtils.getToken(request); return getLoginUser(token); } /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUser getLoginUser(String token) { LoginUser user = null; try { if (StringUtils.isNotEmpty(token)) { String userkey = JwtUtils.getUserKey(token); user = redisService.getCacheObject(getTokenKey(userkey)); return user; } } catch (Exception e) { } return user; } /** * 设置用户身份信息 */ public void setLoginUser(LoginUser loginUser) { if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { refreshToken(loginUser); } } /** * 删除用户缓存信息 */ public void delLoginUser(String token) { if (StringUtils.isNotEmpty(token)) { String userkey = JwtUtils.getUserKey(token); redisService.deleteObject(getTokenKey(userkey)); } } /** * 验证令牌有效期,相差不足120分钟,自动刷新缓存 * * @param loginUser */ public void verifyToken(LoginUser loginUser) { long expireTime = loginUser.getExpireTime(); long currentTime = System.currentTimeMillis(); if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { refreshToken(loginUser); } } /** * 刷新令牌有效期 * * @param loginUser 登录信息 */ public void refreshToken(LoginUser loginUser) { loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); // 根据uuid将loginUser缓存 String userKey = getTokenKey(loginUser.getToken()); redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); } public void refreshToken1(LoginUserApplet dto) { dto.setLoginTime(System.currentTimeMillis()); dto.setExpireTime(dto.getLoginTime() + expireTime * MILLIS_MINUTE); // 根据uuid将loginUser缓存 String userKey = getTokenKey(dto.getToken()); redisService.setCacheObject(userKey, dto, expireTime, TimeUnit.MINUTES); } private String getTokenKey(String token) { return ACCESS_TOKEN + token; } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TChargingOrderController.java
@@ -24,7 +24,9 @@ import com.ruoyi.order.service.TChargingOrderService; import com.ruoyi.order.service.TOrderEvaluateService; import com.ruoyi.order.service.TOrderEvaluateTagService; import com.ruoyi.payment.api.feignClient.AliPaymentClient; import com.ruoyi.payment.api.feignClient.WxPaymentClient; import com.ruoyi.payment.api.vo.AliQueryOrder; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; @@ -35,6 +37,7 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.List; @@ -64,6 +67,9 @@ @Resource private RedisService redisService; @Resource private AliPaymentClient aliPaymentClient; @@ -203,20 +209,23 @@ /** * 支付宝支付成功后的回调 * @param request */ @ResponseBody @PostMapping(value = "/chargingOrderALICallback") public void chargingOrderALICallback(HttpServletRequest request){ Map<String, Object> data = wxPaymentClient.payNotify(request).getData(); if(null != data){ String out_trade_no = data.get("out_trade_no").toString(); String transaction_id = data.get("transaction_id").toString(); String attach = data.get("attach").toString(); public void chargingOrderALICallback(@RequestBody AliQueryOrder aliQueryOrder, HttpServletResponse response){ try { String out_trade_no = aliQueryOrder.getOutTradeNo(); String transaction_id = aliQueryOrder.getTradeNo(); String attach = aliQueryOrder.getPassbackParams(); AjaxResult ajaxResult = chargingOrderService.chargingOrderCallback(2, out_trade_no, transaction_id, attach); if(ajaxResult.isSuccess()){ PrintWriter writer = response.getWriter(); writer.println("success"); writer.flush(); writer.close(); } }catch (Exception e){ e.printStackTrace(); } } ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -1,7 +1,5 @@ package com.ruoyi.order.service.impl; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.account.api.feignClient.AppUserCarClient; @@ -22,7 +20,6 @@ import com.ruoyi.common.core.web.page.PageInfo; import com.ruoyi.common.redis.service.RedisService; import com.ruoyi.common.security.service.TokenService; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.order.api.model.TChargingOrder; import com.ruoyi.order.api.model.TChargingOrderAccountingStrategy; import com.ruoyi.order.api.query.ChargingOrderQuery; @@ -32,13 +29,10 @@ import com.ruoyi.order.mapper.TChargingOrderMapper; import com.ruoyi.order.service.TChargingOrderAccountingStrategyService; import com.ruoyi.order.service.TChargingOrderService; import com.ruoyi.other.api.domain.TCoupon; import com.ruoyi.other.api.domain.TVip; import com.ruoyi.payment.api.feignClient.AliPaymentClient; import com.ruoyi.payment.api.feignClient.WxPaymentClient; import com.ruoyi.payment.api.vo.NotifyV3PayDecodeRespBody; import com.ruoyi.payment.api.vo.PaymentOrder; import com.ruoyi.payment.api.vo.*; import io.seata.spring.annotation.GlobalTransactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -48,7 +42,6 @@ import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** * <p> @@ -80,6 +73,9 @@ @Resource private WxPaymentClient wxPaymentClient; @Resource private AliPaymentClient aliPaymentClient; @Resource private ChargingPileClient chargingPileClient; @@ -231,7 +227,14 @@ } } if(2 == rechargePaymentType){ AliQueryOrder data = aliPaymentClient.query(tChargingOrder.getCode()).getData(); if(null != data){ //支付失败,删除无效的订单 String tradeStatus = data.getTradeStatus(); if(tradeStatus.equals("TRADE_CLOSED")){ this.removeById(tChargingOrder.getId()); } } } } @@ -261,7 +264,19 @@ } } if(2 == one.getRechargePaymentType()){ AliQueryOrder data = aliPaymentClient.query(one.getCode()).getData(); if(null != data){ String trade_state = data.getTradeStatus(); //支付失败,删除无效的订单 if(trade_state.equals("TRADE_CLOSED")){ this.removeById(one.getId()); } if(trade_state.equals("WAIT_BUYER_PAY")){ //结束第三方支付,删除订单 aliPaymentClient.close(one.getCode()); this.removeById(one.getId()); } } } } @@ -313,7 +328,17 @@ return AjaxResult.success(data); } if(2 == addChargingOrder.getPaymentType()){ AliPaymentReq req = new AliPaymentReq(); req.setOutTradeNo(chargingOrder.getCode()); req.setTotalAmount(chargingOrder.getPaymentAmount().toString()); req.setSubject("充电充值"); req.setBuyerOpenId(appUser.getAliOpenid()); req.setBody("充电充值"); AliPaymentResp data = aliPaymentClient.payment(req).getData(); if(null != data){ data.setNotifyUrl(data.getNotifyUrl() + "/t-charging-order/chargingOrderALICallback"); return AjaxResult.success(data); } } throw new RuntimeException("无效的支付方式"); } ruoyi-service/ruoyi-payment/pom.xml
@@ -128,6 +128,12 @@ <groupId>com.ruoyi</groupId> <artifactId>ruoyi-api-payment</artifactId> </dependency> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.39.186.ALL</version> </dependency> </dependencies> <build> ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/config/AliProperties.java
New file @@ -0,0 +1,54 @@ package com.ruoyi.payment.ali.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @author zhibing.pu * @Date 2024/8/23 16:08 */ @Data @Component @ConfigurationProperties(prefix = "payment.ali") public class AliProperties { /** * appid */ private String appId; /** * 加签方式 */ private String signType; /** * 开发者私钥,由开发者自己生成 */ private String privateKey; /** * 支付宝公钥 */ private String alipayPublicKey; /** * 应用公钥证书文件本地路径 */ private String appCertPath; /** * 支付宝公钥证书文件本地路径 */ private String alipayPublicCertPath; /** * 支付宝根证书文件本地路径 */ private String rootCertPath; /** * 回调地址 */ private String notifyUrl; /** * V2接口地址 */ private String v2Path = "https://openapi.alipay.com/gateway.do"; } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/config/SignType.java
New file @@ -0,0 +1,39 @@ package com.ruoyi.payment.ali.config; /** * @author zhibing.pu * @Date 2024/8/23 16:22 */ public enum SignType { /** * V2版本 */ RSA2("RSA2"), /** * V3版本 */ RSA3("RSA2"), /** * 秘钥 */ SECRET_KEY("secret"), /** * 证书 */ CERT("cert"); private String type; public String getType() { return type; } public void setType(String type) { this.type = type; } SignType(String type) { this.type = type; } } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/v2/AppletPayUtil.java
New file @@ -0,0 +1,228 @@ package com.ruoyi.payment.ali.v2; import com.alibaba.fastjson.JSON; import com.alipay.api.AlipayClient; import com.alipay.api.AlipayConfig; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.domain.*; import com.alipay.api.request.*; import com.alipay.api.response.*; import com.ruoyi.payment.ali.config.AliProperties; import com.ruoyi.payment.ali.config.SignType; import com.ruoyi.payment.ali.v2.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * 支付宝小程序支付 * @author zhibing.pu * @Date 2024/8/23 16:05 */ @Component public class AppletPayUtil { private static Logger log = LoggerFactory.getLogger(AppletPayUtil.class); @Resource private AliProperties aliProperties; /** * 创建统一收单交易 * @param pojo */ public PaymentResp payment(PaymentReq pojo){ try { // 初始化SDK AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); // 构造请求参数以调用接口 AlipayTradeCreateRequest request = new AlipayTradeCreateRequest(); AlipayTradeCreateModel model = new AlipayTradeCreateModel(); // 设置商户订单号 model.setOutTradeNo(pojo.getOutTradeNo()); // 设置产品码 model.setProductCode("JSAPI_PAY"); // 设置小程序支付中 model.setOpAppId(aliProperties.getAppId()); // 设置订单总金额 model.setTotalAmount(pojo.getTotalAmount()); // 设置订单标题 model.setSubject(pojo.getSubject()); // 设置订单附加信息 model.setBody(pojo.getBody()); // 设置买家支付宝用户唯一标识 model.setBuyerOpenId(pojo.getBuyerOpenId()); //超时相对时间 model.setTimeoutExpress(pojo.getTimeoutExpress()); //异步返回参数 model.setPassbackParams(pojo.getPassbackParams()); request.setBizModel(model); AlipayTradeCreateResponse response = alipayClient.execute(request); log.info("-----调起支付宝支付-----"); log.info("请求参数:{}", pojo); log.info("返回结果:{}", response.getBody()); if (response.isSuccess()) { return PaymentResp.build(response.getOutTradeNo(), response.getTradeNo()); } }catch (Exception e){ e.printStackTrace(); } return null; } /** * 查询支付订单 * @param outTradeNo 业务流水号 * @return */ public QueryResp query(String outTradeNo){ try { // 初始化SDK AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); // 构造请求参数以调用接口 AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); AlipayTradeQueryModel model = new AlipayTradeQueryModel(); // 设置订单支付时传入的商户订单号 model.setOutTradeNo(outTradeNo); request.setBizModel(model); AlipayTradeQueryResponse response = alipayClient.execute(request); log.info("-----查询支付宝支付-----"); log.info("请求参数:{}", outTradeNo); log.info("返回结果:{}", response.getBody()); if (response.isSuccess()) { return JSON.parseObject(response.getBody(), QueryResp.class); } }catch (Exception e){ e.printStackTrace(); } return null; } /** * 交易退款 * @param req * @return */ public RefundResp refund(RefundReq req){ try { // 初始化SDK AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); // 构造请求参数以调用接口 AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); AlipayTradeRefundModel model = new AlipayTradeRefundModel(); // 设置商户订单号 model.setOutTradeNo(req.getOutTradeNo()); // 设置退款商户订单号 model.setOutRequestNo(req.getOutRequestNo()); // 设置退款金额 model.setRefundAmount(req.getRefundAmount()); // 设置退款原因说明 model.setRefundReason(req.getRefundReason()); request.setBizModel(model); AlipayTradeRefundResponse response = alipayClient.execute(request); log.info("-----支付宝退款-----"); log.info("请求参数:{}", req); log.info("返回结果:{}", response.getBody()); if (response.isSuccess()) { return JSON.parseObject(response.getBody(), RefundResp.class); } }catch (Exception e){ e.printStackTrace(); } return null; } /** * 退款查询 * @param req * @return */ public QueryRefundResp queryRefund(QueryRefundReq req){ try { // 初始化SDK AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); // 构造请求参数以调用接口 AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest(); AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel(); // 设置商户订单号 model.setOutTradeNo(req.getOutTradeNo()); model.setOutRequestNo(req.getOutRequestNo()); request.setBizModel(model); AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request); log.info("-----查询支付宝退款-----"); log.info("请求参数:{}", req); log.info("返回结果:{}", response.getBody()); if (response.isSuccess()) { return JSON.parseObject(response.getBody(), QueryRefundResp.class); } }catch (Exception e){ e.printStackTrace(); } return null; } /** * 支付宝关闭订单 * @param outTradeNo 业务流水号 * @return */ public boolean close(String outTradeNo){ try { AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); AlipayTradeCloseRequest request = new AlipayTradeCloseRequest(); AlipayTradeCloseModel model = new AlipayTradeCloseModel(); model.setOutTradeNo(outTradeNo); request.setBizModel(model); AlipayTradeCloseResponse response = alipayClient.execute(request); log.info("-----关闭支付宝支付订单-----"); log.info("请求参数:{}", outTradeNo); log.info("返回结果:{}", response.getBody()); if(response.isSuccess()){ return true; } }catch (Exception e){ e.printStackTrace(); } return false; } /** * 构建配置数据 * @return */ private AlipayConfig getAlipayConfig() throws Exception { AlipayConfig alipayConfig = new AlipayConfig(); alipayConfig.setServerUrl(aliProperties.getV2Path()); alipayConfig.setAppId(aliProperties.getAppId()); alipayConfig.setFormat("json"); alipayConfig.setCharset("UTF-8"); alipayConfig.setSignType(SignType.RSA2.getType()); //判断加签方式 String signType = aliProperties.getSignType(); if(SignType.CERT.getType().equals(signType)){ alipayConfig.setPrivateKey(aliProperties.getPrivateKey()); alipayConfig.setAlipayPublicKey(aliProperties.getAlipayPublicKey()); return alipayConfig; } if(SignType.SECRET_KEY.getType().equals(signType)){ alipayConfig.setAppCertPath(aliProperties.getAppCertPath()); alipayConfig.setAlipayPublicCertPath(aliProperties.getAlipayPublicCertPath()); alipayConfig.setRootCertPath(aliProperties.getRootCertPath()); return alipayConfig; } throw new RuntimeException("构建配置失败"); } } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/v2/model/PaymentReq.java
New file @@ -0,0 +1,52 @@ package com.ruoyi.payment.ali.v2.model; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/23 16:52 */ @Data public class PaymentReq { /** * 业务流水号 */ private String outTradeNo; /** * 支付总金额 */ private String totalAmount; /** * 订单标题 */ private String subject; /** * 支付用户支付宝openid */ private String buyerOpenId; /** * 订单附加信息 */ private String body; /** * 订单超时时间 * 订单相对超时时间。从交易创建时间开始计算。 * 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。 * 当面付场景默认值为3h */ private String timeoutExpress; /** * 回传参数 * 如果请求时传递了该参数,支付宝会在异步通知时将该参数原样返回。 */ private String passbackParams; /** * 初始化默认数据 */ public PaymentReq() { //订单超时默认30分钟 this.timeoutExpress = "30m"; } } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/v2/model/PaymentResp.java
New file @@ -0,0 +1,30 @@ package com.ruoyi.payment.ali.v2.model; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/23 16:52 */ @Data public class PaymentResp { /** * 业务流水号 */ private String outTradeNo; /** * 支付宝交易号 */ private String tradeNo; private PaymentResp(String outTradeNo, String tradeNo) { this.outTradeNo = outTradeNo; this.tradeNo = tradeNo; } public static PaymentResp build(String outTradeNo, String tradeNo){ PaymentResp info = new PaymentResp(outTradeNo, tradeNo); return info; } } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/v2/model/QueryRefundReq.java
New file @@ -0,0 +1,19 @@ package com.ruoyi.payment.ali.v2.model; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/24 14:00 */ @Data public class QueryRefundReq { /** * 支付业务流水号 */ private String outTradeNo; /** * 退款业务流水号 */ private String outRequestNo; } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/v2/model/QueryRefundResp.java
New file @@ -0,0 +1,43 @@ package com.ruoyi.payment.ali.v2.model; import com.fasterxml.jackson.annotation.JsonSetter; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/24 13:58 */ @Data public class QueryRefundResp { /** * 支付宝交易号 */ @JsonSetter("trade_no") private String tradeNo; /** * 支付业务流水号 */ @JsonSetter("out_trade_no") private String outTradeNo; /** * 退款业务流水号 */ @JsonSetter("out_request_no") private String outRequestNo; /** * 交易金额 */ @JsonSetter("total_amount") private Double totalAmount; /** * 本次退款金额 */ @JsonSetter("refund_amount") private Double refundAmount; /** * 退款状态 * REFUND_SUCCESS 退款处理成功; 未返回该字段表示退款请求未收到或者退款失败; 注:如果退款查询发起时间早于退款时间,或者间隔退款发起时间太短,可能出现退款查询时还没处理成功,后面又处理成功的情况,建议商户在退款发起后间隔10秒以上再发起退款查询请求。 */ @JsonSetter("refund_status") private String refundStatus; } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/v2/model/QueryResp.java
New file @@ -0,0 +1,48 @@ package com.ruoyi.payment.ali.v2.model; import com.fasterxml.jackson.annotation.JsonSetter; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/24 12:01 */ @Data public class QueryResp { /** * 支付宝交易号 */ @JsonSetter("trade_no") private String tradeNo; /** * 业务流水号 */ @JsonSetter("out_trade_no") private String outTradeNo; /** * 交易状态 * WAIT_BUYER_PAY(交易创建,等待买家付款)、TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)、TRADE_SUCCESS(交易支付成功)、TRADE_FINISHED(交易结束,不可退款) */ @JsonSetter("trade_status") private String tradeStatus; /** * 交易订单金额 */ @JsonSetter("total_amount") private Double totalAmount; /** * 回传参数 */ @JsonSetter("passback_params") private String passbackParams; /** * 订单标题 */ @JsonSetter("subject") private String subject; /** * 订单描述 */ @JsonSetter("body") private String body; } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/v2/model/RefundReq.java
New file @@ -0,0 +1,27 @@ package com.ruoyi.payment.ali.v2.model; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/24 13:47 */ @Data public class RefundReq { /** * 支付业务流水号 */ private String outTradeNo; /** * 退款业务流水号 */ private String outRequestNo; /** * 退款金额 */ private String refundAmount; /** * 退款原因 */ private String refundReason; } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/ali/v2/model/RefundResp.java
New file @@ -0,0 +1,27 @@ package com.ruoyi.payment.ali.v2.model; import com.fasterxml.jackson.annotation.JsonSetter; import lombok.Data; /** * @author zhibing.pu * @Date 2024/8/24 13:50 */ @Data public class RefundResp { /** * 支付宝交易号 */ @JsonSetter("trade_no") private String tradeNo; /** * 业务流水号 */ @JsonSetter("out_trade_no") private String outTradeNo; /** * 退款金额 */ @JsonSetter("refund_fee") private Double refundFee; } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/controller/AliPayController.java
New file @@ -0,0 +1,86 @@ package com.ruoyi.payment.controller; import com.ruoyi.common.core.domain.R; import com.ruoyi.payment.ali.config.AliProperties; import com.ruoyi.payment.ali.v2.AppletPayUtil; import com.ruoyi.payment.ali.v2.model.PaymentReq; import com.ruoyi.payment.ali.v2.model.PaymentResp; import com.ruoyi.payment.ali.v2.model.QueryResp; import com.ruoyi.payment.api.vo.AliPaymentReq; import com.ruoyi.payment.api.vo.AliPaymentResp; import com.ruoyi.payment.api.vo.AliQueryOrder; import org.springframework.beans.BeanUtils; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; /** * @author zhibing.pu * @Date 2024/8/24 14:29 */ @RestController @RequestMapping("/ali") public class AliPayController { @Resource private AppletPayUtil appletPayUtil; @Resource private AliProperties aliProperties; /** * 调起支付宝小程序支付 * @param req * @return */ @ResponseBody @PostMapping("/payment") public R<AliPaymentResp> payment(@RequestBody AliPaymentReq req){ PaymentReq pojo = new PaymentReq(); BeanUtils.copyProperties(req, pojo); PaymentResp payment = appletPayUtil.payment(pojo); if(null != payment){ AliPaymentResp aliPaymentResp = new AliPaymentResp(); aliPaymentResp.setTradeNo(payment.getTradeNo()); aliPaymentResp.setNotifyUrl(aliProperties.getNotifyUrl()); return R.ok(aliPaymentResp); } return R.ok(); } /** * 查询订单 * @param outTradeNo * @return */ @ResponseBody @PostMapping("/query") public R<AliQueryOrder> query(@RequestParam("outTradeNo") String outTradeNo){ QueryResp query = appletPayUtil.query(outTradeNo); if(null != query){ AliQueryOrder aliQueryOrder = new AliQueryOrder(); BeanUtils.copyProperties(query, aliQueryOrder); return R.ok(aliQueryOrder); } return R.ok(); } /** * 关闭订单 * @param outTradeNo */ @ResponseBody @PostMapping("/close") public void close(@RequestParam("outTradeNo") String outTradeNo){ boolean close = appletPayUtil.close(outTradeNo); if(!close){ throw new RuntimeException("关闭支付宝订单失败"); } } } ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/controller/WxPayController.java
File was renamed from ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/controller/WxPayController.java @@ -1,4 +1,4 @@ package com.ruoyi.payment.wx.controller; package com.ruoyi.payment.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.ruoyi.common.core.domain.R; ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/config/WxConfig.java
@@ -13,7 +13,6 @@ * * @author lihen */ @ConditionalOnProperty(name = "wx.conf.enabled") @Configuration public class WxConfig { ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/wx/model/WeixinProperties.java
@@ -12,7 +12,7 @@ */ @ToString @Component @ConfigurationProperties(prefix = "wx.conf") @ConfigurationProperties(prefix = "payment.wx") public class WeixinProperties { /** * 默认开启