diff --git a/pom.xml b/pom.xml index eaa394dd..8ce01868 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ 4.1.2 2.3 0.9.1 + 0.2.12 @@ -181,8 +182,14 @@ ruoyi-common ${ruoyi.version} + + com.github.wechatpay-apiv3 + wechatpay-java + ${wechatpay-java.version} + + diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml index 4c196ea4..fa384811 100644 --- a/ruoyi-admin/pom.xml +++ b/ruoyi-admin/pom.xml @@ -14,6 +14,12 @@ web服务入口 + + + jitpack.io + https://jitpack.io + + @@ -65,6 +71,15 @@ lombok provided + + org.springframework.boot + spring-boot-starter-web + + + com.github.wechatpay-apiv3 + wechatpay-java + 0.2.12 + @@ -94,6 +109,14 @@ ${project.artifactId} + + org.apache.maven.plugins + maven-compiler-plugin + + 9 + 9 + + ${project.artifactId} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsDesignTypeController.java b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsDesignTypeController.java index bd76ef7c..baef1640 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsDesignTypeController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsDesignTypeController.java @@ -2,6 +2,8 @@ package com.ruoyi.system.controller; import java.util.List; import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.common.annotation.Anonymous; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -37,7 +39,8 @@ public class BsDesignTypeController extends BaseController /** * 查询设计类型列表 */ - @PreAuthorize("@ss.hasPermi('system:designType:list')") +// @PreAuthorize("@ss.hasPermi('system:designType:list')") + @Anonymous @GetMapping("/list") public TableDataInfo list(BsDesignType bsDesignType) { @@ -62,7 +65,7 @@ public class BsDesignTypeController extends BaseController /** * 获取设计类型详细信息 */ - @PreAuthorize("@ss.hasPermi('system:designType:query')") +// @PreAuthorize("@ss.hasPermi('system:designType:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { @@ -105,7 +108,7 @@ public class BsDesignTypeController extends BaseController /** * 根据编号查询设计类型名称 */ - @PreAuthorize("@ss.hasPermi('system:designType:query')") +// @PreAuthorize("@ss.hasPermi('system:designType:query')") @GetMapping("nameByType/{type}") public AjaxResult nameByType(@PathVariable("type") int type) { diff --git a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsGoodsController.java b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsGoodsController.java index bc23c5de..7621fff5 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsGoodsController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsGoodsController.java @@ -1,5 +1,6 @@ package com.ruoyi.system.controller; +import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.core.controller.BaseController; @@ -33,7 +34,7 @@ public class BsGoodsController extends BaseController { /** * 查询商品列表 */ - @PreAuthorize("@ss.hasPermi('system:goods:list')") +// @PreAuthorize("@ss.hasPermi('system:goods:list')") @GetMapping("/list") public TableDataInfo list(BsGoods bsGoods) { startPage(); @@ -57,7 +58,7 @@ public class BsGoodsController extends BaseController { /** * 获取商品详细信息 */ - @PreAuthorize("@ss.hasPermi('system:goods:query')") +// @PreAuthorize("@ss.hasPermi('system:goods:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { return success(bsGoodsService.selectBsGoodsById(id)); diff --git a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsNoteController.java b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsNoteController.java index db8ac4a8..57c3b3c6 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsNoteController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsNoteController.java @@ -23,7 +23,7 @@ import com.ruoyi.common.core.page.TableDataInfo; /** * 注意事项Controller - * + * * @author ruoyi * @date 2024-10-25 */ @@ -37,7 +37,7 @@ public class BsNoteController extends BaseController /** * 查询注意事项列表 */ - @PreAuthorize("@ss.hasPermi('system:note:list')") +// @PreAuthorize("@ss.hasPermi('system:note:list')") @GetMapping("/list") public TableDataInfo list(BsNote bsNote) { @@ -62,7 +62,7 @@ public class BsNoteController extends BaseController /** * 获取注意事项详细信息 */ - @PreAuthorize("@ss.hasPermi('system:note:query')") +// @PreAuthorize("@ss.hasPermi('system:note:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { diff --git a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsOrderController.java b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsOrderController.java index 486ca418..6002bc2b 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsOrderController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsOrderController.java @@ -37,7 +37,7 @@ public class BsOrderController extends BaseController /** * 查询订单列表 */ - @PreAuthorize("@ss.hasPermi('system:order:list')") +// @PreAuthorize("@ss.hasPermi('system:order:list')") @GetMapping("/list") public TableDataInfo list(BsOrder bsOrder) { @@ -62,7 +62,7 @@ public class BsOrderController extends BaseController /** * 获取订单详细信息 */ - @PreAuthorize("@ss.hasPermi('system:order:query')") +// @PreAuthorize("@ss.hasPermi('system:order:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { @@ -72,15 +72,13 @@ public class BsOrderController extends BaseController /** * 根据订单号获取订单详细信息 */ - @PreAuthorize("@ss.hasPermi('system:order:query')") +// @PreAuthorize("@ss.hasPermi('system:order:query')") @GetMapping(value = "/orderNo/{num}") public AjaxResult getInfoByOrderNo(@PathVariable("num") String num) { return success(bsOrderService.selectBsOrderByOrderNo(num)); } - - /** * 新增订单 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsTechnicalTypeController.java b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsTechnicalTypeController.java index fdecd4f7..4a0a063b 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsTechnicalTypeController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/system/controller/BsTechnicalTypeController.java @@ -2,6 +2,8 @@ package com.ruoyi.system.controller; import java.util.List; import javax.servlet.http.HttpServletResponse; + +import com.ruoyi.common.annotation.Anonymous; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -23,7 +25,7 @@ import com.ruoyi.common.core.page.TableDataInfo; /** * 技术类型Controller - * + * * @author ruoyi * @date 2024-10-22 */ @@ -37,7 +39,7 @@ public class BsTechnicalTypeController extends BaseController /** * 查询技术类型列表 */ - @PreAuthorize("@ss.hasPermi('system:type:list')") +// @PreAuthorize("@ss.hasPermi('system:type:list')") @GetMapping("/list") public TableDataInfo list(BsTechnicalType bsTechnicalType) { @@ -62,7 +64,7 @@ public class BsTechnicalTypeController extends BaseController /** * 获取技术类型详细信息 */ - @PreAuthorize("@ss.hasPermi('system:type:query')") +// @PreAuthorize("@ss.hasPermi('system:type:query')") @GetMapping(value = "/{id}") public AjaxResult getInfo(@PathVariable("id") Long id) { diff --git a/ruoyi-admin/src/main/java/com/ruoyi/wxPay/WechatPayController.java b/ruoyi-admin/src/main/java/com/ruoyi/wxPay/WechatPayController.java new file mode 100644 index 00000000..f54d1061 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/wxPay/WechatPayController.java @@ -0,0 +1,198 @@ +package com.ruoyi.wxPay; + +import com.alibaba.fastjson2.JSONObject; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.wxpay.sdk.WXPayUtil; +import com.ruoyi.common.annotation.Anonymous; +import com.ruoyi.common.utils.DateUtil; +import com.ruoyi.common.wx.PayService; +import com.ruoyi.common.wx.StreamUtils; +import com.ruoyi.common.wx.WxPayDTO; +import com.ruoyi.common.wx.WxPayProperties; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Api(tags = "微信支付相关接口") // 用于API文档生成,标记该控制器的功能分类 +@RestController // 标记该类为一个Spring MVC控制器,返回对象会自动转换为JSON格式 +@Slf4j // 使用Lombok的注解,提供日志功能 +@Validated // 启用参数校验 +@RequiredArgsConstructor // 自动生成构造函数,注入必要的依赖 +@RequestMapping("/pay") // 定义请求路径的前缀为/pay +public class WechatPayController { + + @Resource // 注入PayService,用于处理支付相关的业务逻辑 + private PayService payService; + + @Resource // 注入WxPayProperties,用于获取微信支付的配置信息 + private WxPayProperties wxPayProperties; + + @Anonymous + + @ApiOperation(value = "微信native下单,返回支付二维码") // 描述该API的功能 + @GetMapping("/nativePay") // 定义GET请求的路径为/pay/nativePay + public Object nativePay(@RequestParam("orderNumber") String orderNumber) { + // todo 业务操作-根据订单编号查询订单信息 + // 将订单信息中的数据存到WxPayDTO + WxPayDTO payDTO = new WxPayDTO(); // 创建WxPayDTO对象用于存储支付信息 + payDTO.setBody("商品描述"); // 设置商品描述 + + // 订单总金额,单位为分 + payDTO.setTotalFee(1); // 设置订单总金额(1分) + + // 支付回调地址 + payDTO.setNotifyUrl(wxPayProperties.getNotifyUrl()); // 设置支付成功后的回调地址 + + // 商品订单编号 + payDTO.setOutTradeNo(orderNumber); // 设置商户订单编号 + + // 获取当前时间 + Date date = new Date(); // 创建Date对象表示当前时间 + String timeStart = DateUtil.formatDateToString(date, "yyyyMMddHHmmss"); // 格式化开始时间 + + // 结束时间设置在30分钟后 + String timeExpire = DateUtil.formatDateToString(DateUtil.addMinutesToDate(date, 30), "yyyyMMddHHmmss"); // 设置结束时间 + + // 交易起始时间 + payDTO.setTimeStart(timeStart); // 设置交易开始时间 + + // 交易结束时间 + payDTO.setTimeExpire(timeExpire); // 设置交易结束时间 + + Object url = payService.transactions(payDTO, WxPayConstants.TradeType.NATIVE); // 调用支付服务进行下单 + + return url; // 返回二维码或支付链接 + } + + @ApiOperation(value = "微信JSAPI下单,返回JS支付相关配置") // 描述该API的功能 + @GetMapping("/jsapiPay") // 定义GET请求的路径为/pay/jsapiPay + public Object jsapiPay(@RequestParam("orderNumber") String orderNumber) { + // todo 业务操作-根据订单编号查询订单信息 + // 将订单信息中的数据存到WxPayDTO + WxPayDTO payDTO = new WxPayDTO(); // 创建WxPayDTO对象用于存储支付信息 + payDTO.setBody("支付测试"); // 设置商品描述 + + // 订单总金额,单位为分 + payDTO.setTotalFee(1); // 设置订单总金额(1分) + + // 支付回调地址 + payDTO.setNotifyUrl(wxPayProperties.getNotifyUrl()); // 设置支付成功后的回调地址 + payDTO.setOutTradeNo("商户订单号"); // 设置商户订单编号 + + // 获取当前时间 + Date date = new Date(); // 创建Date对象表示当前时间 + String timeStart = DateUtil.formatDateToString(date, "yyyyMMddHHmmss"); // 格式化开始时间 + + // 结束时间设置在30分钟后 + String timeExpire = DateUtil.formatDateToString(DateUtil.addMinutesToDate(date, 30), "yyyyMMddHHmmss"); // 设置结束时间 + + // 交易起始时间 + payDTO.setTimeStart(timeStart); // 设置交易开始时间 + + // 交易结束时间 + payDTO.setTimeExpire(timeExpire); // 设置交易结束时间 + + // todo jsapi下单需要用户的openid + payDTO.setOpenId("openid"); // 设置用户的OpenID(需从用户信息中获取) + + Object url = payService.transactions(payDTO, WxPayConstants.TradeType.JSAPI); // 调用支付服务进行下单 + + return url; // 返回JS支付相关配置 + } + + @ApiOperation(value = "微信H5下单,返回跳转链接") // 描述该API的功能 + @GetMapping("/h5Pay") // 定义GET请求的路径为/pay/h5Pay + public Object h5Pay(@RequestParam("orderNumber") String orderNumber, HttpServletRequest request) { + // todo 业务操作-根据订单编号查询订单信息 + // 将订单信息中的数据存到WxPayDTO + WxPayDTO payDTO = new WxPayDTO(); // 创建WxPayDTO对象用于存储支付信息 + payDTO.setBody("商品描述"); // 设置商品描述 + + // 订单总金额,单位为分 + payDTO.setTotalFee(1); // 设置订单总金额(1分) + + // 支付回调地址 + payDTO.setNotifyUrl(wxPayProperties.getNotifyUrl()); // 设置支付成功后的回调地址 + payDTO.setOutTradeNo("商户订单号"); // 设置商户订单编号 + + // 获取当前时间 + Date date = new Date(); // 创建Date对象表示当前时间 + String timeStart = DateUtil.formatDateToString(date, "yyyyMMddHHmmss"); // 格式化开始时间 + + // 结束时间设置在30分钟后 + String timeExpire = DateUtil.formatDateToString(DateUtil.addMinutesToDate(date, 30), "yyyyMMddHHmmss"); // 设置结束时间 + + // 交易起始时间 + payDTO.setTimeStart(timeStart); // 设置交易开始时间 + + // 交易结束时间 + payDTO.setTimeExpire(timeExpire); // 设置交易结束时间 + + // todo H5下单需要用户的客户端IP + String remoteAddr = request.getRemoteAddr(); // 获取用户的IP地址 + payDTO.setPayerClientIp(remoteAddr); // 设置支付者的IP地址 + + Object url = payService.transactions(payDTO, WxPayConstants.TradeType.JSAPI); // 调用支付服务进行下单 + + return url; // 返回H5支付链接 + } + + @ApiOperation(value = "微信支付回调") // 描述该API的功能 + @PostMapping("/wxCallback") // 定义POST请求的路径为/pay/wxCallback + public Object wxCallback(HttpServletRequest request, HttpServletResponse response) { + ServletInputStream inputStream = null; // 定义输入流用于接收请求体 + try { + inputStream = request.getInputStream(); // 获取请求体的输入流 + String notifyXml = StreamUtils.inputStream2String(inputStream, "utf-8"); // 将输入流转换为字符串 + log.info(notifyXml); // 打印接收到的通知内容 + + // 解析返回结果 + Map notifyMap = WXPayUtil.xmlToMap(notifyXml); // 将XML转换为Map + String jsonString = JSONObject.toJSONString(notifyMap); // 将Map转换为JSON字符串 + log.info(jsonString); // 打印解析后的结果 + + // 判断支付是否成功 + if ("SUCCESS".equals(notifyMap.get("result_code"))) { // 如果支付结果为成功 + // todo 修改订单状态 + + // 支付成功:给微信发送我已接收通知的响应 创建响应对象 + Map returnMap = new HashMap<>(); // 创建返回的Map + returnMap.put("return_code", "SUCCESS"); // 设置返回码为SUCCESS + returnMap.put("return_msg", "OK"); // 设置返回消息为OK + + String returnXml = WXPayUtil.mapToXml(returnMap); // 将Map转换为XML格式 + response.setContentType("text/xml"); // 设置响应的内容类型为XML + log.info("支付成功"); // 打印支付成功日志 + return returnXml; // 返回成功的XML响应 + } else { + // 保存回调信息,方便排除问题 + } + // 创建响应对象:微信接收到校验失败的结果后,会反复的调用当前回调函数 + Map returnMap = new HashMap<>(); // 创建返回的Map + returnMap.put("return_code", "FAIL"); // 设置返回码为FAIL + returnMap.put("return_msg", ""); // 设置返回消息为空 + + String returnXml = WXPayUtil.mapToXml(returnMap); // 将Map转换为XML格式 + response.setContentType("text/xml"); // 设置响应的内容类型为XML + log.info("校验失败"); // 打印校验失败日志 + return returnXml; // 返回失败的XML响应 + + } catch (Exception e) { + e.printStackTrace(); // 打印异常栈信息 + } + + return null; // 如果发生异常,返回null + } +} + diff --git a/ruoyi-admin/src/main/resources/apiclient_key.pem b/ruoyi-admin/src/main/resources/apiclient_key.pem new file mode 100644 index 00000000..2010cd56 --- /dev/null +++ b/ruoyi-admin/src/main/resources/apiclient_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDF0mLbDwjEycxc +4rosptSPNXLwJI9c5X2Ztfy3ffVZnlG0Vm8leXSaKQfLw9Fsu1v8qomnbd/3xfpf +wOJCG8sd4nmoJBgFV2mM7GHz9f0eiQ9vo9fpySo6CBm3NJTcUPFr54sJgHzRmIJ4 +ouNif66cs5R2lSP09QeUeNSm0h62OGNb0+0crGy1qqYb5OSOCIrN4Py/MMZZGWgc +9FtXGFWVPviWcOTOd4m64P0kbpU3ST3iJwsYaupwC4/t7JxB1fKrJMvgsCWojPps +ecpFj6YufIt9jiIQeZ+3uHK9IXuWOBhR+aAaPelhc5UBqB+DIdmGFxhIDjwBnixq +7Dnmy9GbAgMBAAECggEAH2d60hPc7ICstMI5lAUYEXQGvyDOsYytF83QRMD2Rjff ++KUQF/7kB+Ujm7GGeaB0xfO8zpuexpLjYHgacyw+XowpjAvO1GpAE2MjWbtfZ42S +qcJ08GRDE9tdWbw7NY6UBPFJHUUMi6mObOBPAMAMVEwd8YVTo/uSLHvSJnmM7phV +CW02o3QLYGebTmyimXUqXF7hnaUZOmYVBRf+j/psCjbnfsd9TVQcIc0ILfABS9g8 +6z7PQDPRceafiTV5qJrtvMuH8/a1KqCtWOlmML3jhE9aDNN2vf5iRgJYSTx/Wprf +FC7FlBwYi4nDGVFk3B7yRwuMWwGUnHF3/BQo8U74QQKBgQD/Mb3s48EglH9MerB4 +vJGq7c67S+skb6Wu9hdQ63Qhdk/kXYHkUupJnSyaW6spsHqXd4TbomdBNyCs11t+ +HQ4n/+ScM3K4khY+uQftZxF3J1mIvLv08cJa0WuDepbAxKjrM1WJh5Kp9ihc3p2s +v8PB2pUeiyAFfz7dCgRm7e8CyQKBgQDGckYHr6aHLDfCNEBOJCViWOewEMgSUvqt +KhYiiPKMZCbX2OWmTnBgWhuumIf9sIBJONZ8QXqdXoOBwFKlhgIynHS54EGhapIY +ebNvXopIkF4Bw7yInj7KvwOmojDBiJKUY7BZCVOicT2rnElix/+L9S6gHA7/V5rx +vVJkTsnfQwKBgQDVTsKuTAGWNgnh53uysAwij/yJWgAGyLv47wK2RNkhTz+gZvi3 +3Qaw2Yv6yjzb8APIr8KBw9IDFQ1e6/QyCh9XF/IDDo4J8TJe41LZAZn7uwx/2yJQ +r/QA7aOslr+ECd4YGySYfJX/Mx6x4fJx/yil1QtoKGpvrdjh8gmT77Yk4QKBgQDD +qeSWq3/8g4KuvyowYb9iitpWZRV/y8VSe867WmAcQJtz823rXie7ON1WdxqO7jpu +99WzSjSFea0cf+59OfZsxIrqwsyzRQqri0N6qbKa/Y1THBWGdtDewxvsbrq399re +6LP19hY6coEl9cD93sh+zM6eG0xGQ7CIbe0Q7gZpVQKBgQCv+sd/JVm4MhTTQBmi +sCLyL53U82481aJIRLsBbqFzLCNxlBge6uSFb2w8ZcQEPswR3ONVW5TpRA6Prk5q +07tqGeibyxHL2A1XFgSa62oaKR3lZHn1/wlZyURFYT3sK0tzgvMCIvvYO8ny3Dn/ +FBKpJ0zO+E0KnZdcwY+Uf0///A== +-----END PRIVATE KEY----- diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index e673ef47..58eb5c62 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -16,7 +16,7 @@ ruoyi: # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 - port: 8080 + port: 40506 servlet: # 应用的访问路径 context-path: / @@ -130,8 +130,16 @@ xss: wx: pay: - appId: #微信公众号或者小程序等的appid - secret: - mchId: #微信支付商户号 - mchKey: #微信支付商户密钥 - notifyUrl: http://8.130.135.74:8053/api/order/student/pay/wxCallback #支付回调地址 + appId: wxb1f71e5e0c5f9ee7 #微信公众号或者小程序等的appid + secret: 2e9864a6b224feb6fba4ab73b70212cd + mchId: 1695785302 #微信支付商户号 + mchKey: Su7vFDrgVScnUqABUrIWLYyrNapWeH1a #微信支付商户密钥 + notifyUrl: https://9qsxf7949501.vicp.fun/pay/wxCallback #支付回调地址 + +wechat: + pay: + merchantId: 1695785302 + privateKeyPath: D:\Code\bishe\RuoYi-Vue\ruoyi-admin\src\main\resources\apiclient_key.pem + merchantSerialNumber: 3E96EC705218574D63ECE2EA792B83B224FE9319 + apiV3Key: Su7vFDrgVScnUqABUrIWLYyrNapWeH1a + appId: wxb1f71e5e0c5f9ee7 diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 56c3f819..dc0c83cd 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -52,13 +52,13 @@ org.apache.commons commons-lang3 - + com.fasterxml.jackson.core jackson-databind - + com.alibaba.fastjson2 @@ -119,6 +119,39 @@ javax.servlet-api + + + + com.github.wxpay + wxpay-sdk + 0.0.3 + + + com.github.binarywang + weixin-java-miniapp + 4.4.2.B + + + com.github.binarywang + weixin-java-pay + 4.2.0 + + + com.github.binarywang + weixin-java-mp + 4.4.0 + + + org.projectlombok + lombok + + + io.swagger + swagger-annotations + 1.6.2 + compile + + - \ No newline at end of file + diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtil.java new file mode 100644 index 00000000..33826b0c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtil.java @@ -0,0 +1,111 @@ +package com.ruoyi.common.utils; + +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; + +public class DateUtil { + + public static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + public static final DateTimeFormatter dateFormatterChinese = DateTimeFormatter.ofPattern("yyyy年M月d日"); + public static final DateTimeFormatter dateFormatterMonthDay = DateTimeFormatter.ofPattern("M-d日"); + public static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + public static final DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM"); + public static final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); + + + /** + * 将给定的 Date 对象往后推指定分钟数。 + * + * @param date 要推后的 Date 对象 + * @param minutes 要推后的分钟数 + * @return 推后后的 Date 对象 + */ + public static Date addMinutesToDate(Date date, int minutes) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + calendar.add(Calendar.MINUTE, minutes); + return calendar.getTime(); + } + + /** + * 将 Date 对象格式化为字符串,指定日期时间格式。 + * + * @param date 要格式化的 Date 对象 + * @param format 日期时间格式 + * @return 格式化后的字符串 + */ + public static String formatDateToString(Date date, String format) { + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(date); + } + + + public static Date asDate(LocalDate localDate) { + return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()); + } + + public static Date asDate(LocalDateTime localDateTime) { + return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); + } + + public static LocalDate asLocalDate(Date date) { + return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDate(); + } + + public static LocalDateTime asLocalDateTime(Date date) { + return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + + /** + * 计算两个时间之间的分钟数 + * + * @param date + * @return + */ + public static Long timeToTimeMin(Date date, Date date2) { + return timeToTimeMin(asLocalDateTime(date), asLocalDateTime(date2)); + } + + public static Long timeToTimeMin(LocalDateTime localDateTime, LocalDateTime localDateTime2) { + Duration duration = Duration.between(localDateTime, localDateTime2); + return duration.toMinutes(); + } + + /** + * 计算两个日期差 + * + * @param localDate + * @param localDate2 + * @return + */ + public static Long DateDifference(LocalDate localDate, LocalDate localDate2) { + return localDate.toEpochDay() - localDate2.toEpochDay(); + } + + // 计算两个LocalDateTime秒数差 + public static Long LocalDateTimeDifference(LocalDateTime localDateTime, LocalDateTime localDateTime2) { + Duration duration = Duration.between(localDateTime, localDateTime2); + return duration.getSeconds(); + } + + /** + * 获取星期 + * + * @param parse + * @return + */ + public static String getWeek(LocalDate parse) { + int value = parse.getDayOfWeek().getValue(); + if (value == 1) return "一"; + if (value == 2) return "二"; + if (value == 3) return "三"; + if (value == 4) return "四"; + if (value == 5) return "五"; + if (value == 6) return "六"; + if (value == 7) return "日"; + return null; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/wx/PayService.java b/ruoyi-common/src/main/java/com/ruoyi/common/wx/PayService.java new file mode 100644 index 00000000..6ff9ffbd --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/wx/PayService.java @@ -0,0 +1,226 @@ +package com.ruoyi.common.wx; + +import com.alibaba.fastjson2.JSONObject; +import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; +import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; +import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.util.SignUtils; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; + +/** + * @author: ghl + * @date: Created in 2023/6/27 0027 + * @description: 支付相关通用服务 + */ +@Service // 表示这是一个服务类,Spring会自动识别并管理 +public class PayService { + + @Autowired // 自动注入WxPayService,处理与微信支付相关的请求 + private WxPayService wxPayService; + + /** + * 根据传入的tradeType来进行不同类型的支付 + * @param dto 商品参数 + * @param tradeType WxPayConstants.TradeType.MWEB为H5支付/WxPayConstants.TradeType.NATIVE为扫码支付/WxPayConstants.TradeType.JSAPI为JSAPI支付 + * @return 返回支付结果 + */ + public Object transactions(WxPayDTO dto, String tradeType) { + try { + // 检查tradeType是否不为空 + if (tradeType != null && !tradeType.isEmpty()) { + // 判断支付类型并调用对应的方法 + if (WxPayConstants.TradeType.MWEB.equals(tradeType)) { + // H5支付 + return noMiniappPay(dto, tradeType); + } else if (WxPayConstants.TradeType.NATIVE.equals(tradeType)) { + // NATIVE支付 + return noMiniappPay(dto, tradeType); + } else if (WxPayConstants.TradeType.JSAPI.equals(tradeType)) { + // JSAPI支付 + return miniappPay(dto); + } + } + } catch (Exception e) { + // 处理异常并打印堆栈跟踪 + e.printStackTrace(); + return null; // 返回null表示出错 + } + return null; // 返回null以处理未匹配的情况 + } + + // 处理非小程序支付的逻辑 + private String noMiniappPay(WxPayDTO dto, String tradeType) throws WxPayException { + // 创建请求对象 + WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); + request.setOutTradeNo(dto.getOutTradeNo()); // 设置订单号 + request.setTotalFee(dto.getTotalFee()); // 设置订单金额 + request.setBody(dto.getBody()); // 设置商品描述 + HttpServletRequest httpServletRequest = getHttpServletRequest(); // 获取当前HTTP请求 + request.setSpbillCreateIp(httpServletRequest.getRemoteAddr()); // 设置发起支付的IP + request.setNotifyUrl(dto.getNotifyUrl()); // 设置支付回调地址 + request.setProductId(dto.getOutTradeNo()); // 设置商品ID + request.setTradeType(tradeType); // 设置交易类型 + // 调用统一下单接口,发起支付请求 + WxPayNativeOrderResult result = wxPayService.createOrder(request); + // 返回的二维码或H5支付跳转链接 + String codeUrl = result.getCodeUrl(); + // 返回二维码链接给前端 + return codeUrl; + } + + /** + * 处理JSAPI支付逻辑 + * @param dto 包含订单信息 + * @return 返回JSAPI支付参数 + */ + @SneakyThrows // 隐藏抛出的异常 + public Map miniappPay(WxPayDTO dto) { + // 设置请求参数 + WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() + .body(dto.getBody()) // 商品描述 + .totalFee(dto.getTotalFee()) // 订单金额 + .spbillCreateIp(this.getIpAddress(this.getHttpServletRequest())) // 发起支付的IP地址 + .notifyUrl(dto.getNotifyUrl()) // 回调地址 + .tradeType(WxPayConstants.TradeType.JSAPI) // 交易类型为JSAPI + .openid(dto.getOpenId()) // 用户的OpenID + .outTradeNo(dto.getOutTradeNo()) // 订单号 + .timeStart(dto.getTimeStart()) // 交易开始时间 + .timeExpire(dto.getTimeExpire()) // 交易结束时间 + .build(); // 构建请求对象 + request.setSignType(WxPayConstants.SignType.MD5); // 设置签名类型 + + // 发起微信统一下单 + WxPayUnifiedOrderResult result = this.wxPayService.unifiedOrder(request); + // 检查prepayId是否为空 + if (StringUtils.isBlank(result.getPrepayId())) { + return null; // 返回null表示下单失败 + } + // 返回前端调用JSAPI支付所需的参数 + return getWxJsapiPayParam(result); + } + + /** + * 微信退款处理 + * @param body 退款请求参数 + */ + public void wxRefund(WxRefundDTO body) { + // 创建退款请求对象 + WxPayRefundRequest refundRequest = new WxPayRefundRequest(); + refundRequest.setOutTradeNo(body.getOutTradeNo()); // 设置订单号 + refundRequest.setOutRefundNo(body.getOutRefundNo()); // 设置退款单号 + refundRequest.setTotalFee(body.getTotalFee()); // 设置订单总金额 + refundRequest.setRefundFee(body.getRefundFee()); // 设置退款金额 + refundRequest.setRefundDesc("商品退款"); // 设置退款说明 + refundRequest.setNotifyUrl(body.getNotifyUrl()); // 设置退款回调地址 + + try { + // 调用微信退款接口 + WxPayRefundResult result = wxPayService.refund(refundRequest); + System.out.println("微信退款成功,返回参数{}" + JSONObject.toJSONString(result)); // 打印退款成功信息 + } catch (WxPayException e) { + // 处理退款异常 + System.out.println("微信退款异常,返回参数{}" + JSONObject.toJSONString(e)); // 打印异常信息 + } + } + + /** + * 获取当前请求对象 + * @return 当前HTTP请求对象 + */ + private HttpServletRequest getHttpServletRequest() { + // 从上下文中获取当前请求的Servlet请求属性 + HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); + // 获取请求头信息 + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String key = headerNames.nextElement(); // 获取请求头名称 + System.out.println("请求头:" + key + "值:" + request.getHeader(key)); // 打印请求头信息 + } + return request; // 返回当前请求对象 + } + + /** + * 获取请求主机的IP地址 + * @param request 当前HTTP请求对象 + * @return 客户端IP地址 + */ + private String getIpAddress(HttpServletRequest request) { + // 尝试获取多个请求头中的真实IP地址 + String ip = request.getHeader("X-Forwarded-For"); + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("REMOTE-HOST"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); // 如果未找到,使用远程地址 + } + + // 处理可能存在的多个IP地址 + if (ip.length() > 15) { + String[] ips = ip.split(","); // 分割多个IP + for (String s : ips) { + if (!("unknown".equalsIgnoreCase(s))) { + ip = s; // 取第一个非unknown的IP + break; + } + } + } + return ip; // 返回真实IP地址 + } + + /** + * 获取到JS支付相关配置 + * @param weiXinPayOrder 微信支付订单结果 + * @return 返回JS支付所需的参数 + */ + private Map getWxJsapiPayParam(WxPayUnifiedOrderResult weiXinPayOrder) { + WxPayWebDTO wxPayParam = new WxPayWebDTO(); + String package_ = "prepay_id=" + weiXinPayOrder.getPrepayId(); // 构建包参数 + wxPayParam.setAppId(weiXinPayOrder.getAppid()); // 设置小程序ID + wxPayParam.setTimeStamp(System.currentTimeMillis() / 1000 + ""); // 设置时间戳 + wxPayParam.setNonceStr(UUID.randomUUID().toString().replace("-", "")); // 设置随机字符串 + wxPayParam.setPackage1(package_); // 设置包参数 + wxPayParam.setSignType(WxPayConstants.SignType.MD5); // 设置签名类型为MD5 + + // 构建签名参数 + Map signParam = new LinkedHashMap<>(); + signParam.put("appId", weiXinPayOrder.getAppid()); + signParam.put("nonceStr", wxPayParam.getNonceStr()); + signParam.put("package", package_); + signParam.put("signType", WxPayConstants.SignType.MD5); + signParam.put("timeStamp", wxPayParam.getTimeStamp()); + + // 生成签名 + String paySign = SignUtils.createSign(signParam, WxPayConstants.SignType.MD5, this.wxPayService.getConfig().getMchKey(), new String[0]); + signParam.put("paySign", paySign); // 将签名加入参数 + return signParam; // 返回包含支付参数和签名的Map + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/wx/StreamUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/wx/StreamUtils.java new file mode 100644 index 00000000..6b614bd1 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/wx/StreamUtils.java @@ -0,0 +1,58 @@ +package com.ruoyi.common.wx; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class StreamUtils { + // 设置缓冲区大小为1024字节 + private static int _buffer_size = 1024; + + /** + * 将InputStream流转换成String字符串 + * @param inStream 输入的InputStream流 + * @param encoding 字符编码格式,例如"UTF-8" + * @return 转换后的String字符串 + */ + public static String inputStream2String(InputStream inStream, String encoding) { + String result = null; // 用于存储最终结果的字符串 + ByteArrayOutputStream outStream = null; // 用于将字节流写入内存中 + try { + // 检查输入流是否为空 + if (inStream != null) { + outStream = new ByteArrayOutputStream(); // 初始化字节数组输出流 + byte[] tempBytes = new byte[_buffer_size]; // 创建一个字节数组作为缓冲区 + int count = -1; // 初始化读取字节数的计数器 + // 循环读取InputStream中的数据 + while ((count = inStream.read(tempBytes, 0, _buffer_size)) != -1) { + // 将读取到的字节写入到ByteArrayOutputStream + outStream.write(tempBytes, 0, count); + } + tempBytes = null; // 释放缓冲区的引用 + outStream.flush(); // 刷新输出流,确保所有数据都被写入 + // 将ByteArrayOutputStream中的字节转换为字符串,使用指定的编码格式 + result = new String(outStream.toByteArray(), encoding); + outStream.close(); // 关闭输出流 + } + } catch (Exception e) { + // 发生异常时,设置结果为null + result = null; + } finally { + try { + // 在finally块中关闭输入流 + if (inStream != null) { + inStream.close(); // 关闭InputStream + inStream = null; // 释放输入流引用 + } + // 关闭输出流 + if (outStream != null) { + outStream.close(); // 关闭ByteArrayOutputStream + outStream = null; // 释放输出流引用 + } + } catch (IOException e) { + e.printStackTrace(); // 打印异常堆栈跟踪 + } + } + return result; // 返回转换后的字符串,若发生异常则为null + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayConfiguration.java b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayConfiguration.java new file mode 100644 index 00000000..349be221 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayConfiguration.java @@ -0,0 +1,41 @@ +package com.ruoyi.common.wx; + +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Binary Wang + */ +@Configuration // 标记该类为配置类,Spring会自动加载 +@ConditionalOnClass(WxPayService.class) // 当WxPayService类存在时,该配置类才会生效 +@EnableConfigurationProperties(WxPayProperties.class) // 启用WxPayProperties类的配置属性 +@AllArgsConstructor // 自动生成一个包含所有字段的构造函数 +public class WxPayConfiguration { + + private WxPayProperties properties; // 注入WxPayProperties属性,包含微信支付的配置信息 + + /** + * 创建WxPayService的Bean + * @return WxPayService实例 + */ + @Bean // 将该方法返回的对象注册为Spring Bean + @ConditionalOnMissingBean // 如果容器中不存在WxPayService类型的Bean,则创建新的实例 + public WxPayService wxService() { + WxPayConfig payConfig = new WxPayConfig(); // 创建WxPayConfig对象用于存储微信支付的配置 + payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId())); // 设置应用ID,去除空格并处理null + payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId())); // 设置商户ID,去除空格并处理null + payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey())); // 设置商户密钥,去除空格并处理null + + WxPayService wxPayService = new WxPayServiceImpl(); // 创建WxPayService的实现类实例 + wxPayService.setConfig(payConfig); // 将配置设置到WxPayService实例中 + return wxPayService; // 返回WxPayService实例 + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayDTO.java b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayDTO.java new file mode 100644 index 00000000..eb00f8cd --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayDTO.java @@ -0,0 +1,95 @@ +package com.ruoyi.common.wx; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @author: ghl + * @date: Created in 2023/6/27 0027 + * @description: 微信统一下单请求参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +public class WxPayDTO { + /** + * openId + */ + private String openId; + + + /** + * 字段名:商品描述. + * 变量名:body + * 是否必填:是 + * 类型:String(128) + * 示例值: 腾讯充值中心-QQ会员充值 + * 描述:商品简单描述,该字段须严格按照规范传递,具体请见参数规定 + */ + private String body; + + /** + * 字段名:总金额. + * 变量名:total_fee + * 是否必填:是 + * 类型:Int + * 示例值: 888 + * 描述:订单总金额,单位为分,详见支付金额 + */ + private Integer totalFee; + + /** + * 字段名:通知地址. + * 变量名:notify_url + * 是否必填:是 + * 类型:String(256) + * 示例值:http://www.weixin.qq.com/wxpay/pay.php + * 描述:接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 + */ + private String notifyUrl; + + /** + * 字段名:商户订单号. + * 变量名:out_trade_no + * 是否必填:是 + * 类型:String(32) + * 示例值:20150806125346 + * 描述:商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号 + */ + private String outTradeNo; + + /** + * 字段名:交易起始时间. + * 变量名:time_start + * 是否必填:否 + * 类型:String(14) + * 示例值:20091225091010 + * 描述:订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 + */ + private String timeStart; + + /** + * 字段名:交易结束时间. + * 变量名:time_expire + * 是否必填:否 + * 类型:String(14) + * 示例值:20091227091010 + * 描述:订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 + * 注意:最短失效时间间隔必须大于5分钟 + */ + private String timeExpire; + + /** + * 字段名:用户的客户端IP + * 变量名:payer_client_ip + * 是否必填:是 + * 类型:String(14) + * 示例值:14.23.150.211 + * 描述:用户的客户端IP,支持IPv4和IPv6两种格式的IP地址。 + * 注意:最短失效时间间隔必须大于5分钟 + */ + private String payerClientIp; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayProperties.java b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayProperties.java new file mode 100644 index 00000000..c466d2e4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayProperties.java @@ -0,0 +1,34 @@ +package com.ruoyi.common.wx; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +/** + * wxpay pay properties. + * + * @author Binary Wang + */ +@Data +@ConfigurationProperties(prefix = "wx.pay") +public class WxPayProperties { + /** + * 设置微信公众号或者小程序等的appid + */ + private String appId; + + private String secret; + + /** + * 微信支付商户号 + */ + private String mchId; + + /** + * 微信支付商户密钥 + */ + private String mchKey; + + + private String notifyUrl; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayWebDTO.java b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayWebDTO.java new file mode 100644 index 00000000..0319b679 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxPayWebDTO.java @@ -0,0 +1,51 @@ +package com.ruoyi.common.wx; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @author: ghl + * @date: Created in 2023/6/27 0027 + * @description: + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +@ApiModel +public class WxPayWebDTO { + /** + * 公众号id + */ + @ApiModelProperty("公众号id") + private String appId; + /** + * 时间戳 + */ + @ApiModelProperty("时间戳") + private String timeStamp; + /** + * 随机字符串 + */ + @ApiModelProperty("随机字符串") + private String nonceStr; + /** + * 订单详情扩展字符串 + */ + @ApiModelProperty("订单详情扩展字符串") + private String package1; + /** + * 签名方式 + */ + @ApiModelProperty("签名方式") + private String signType; + /** + * 签名 + */ + @ApiModelProperty("签名") + private String paySign; +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxRefundDTO.java b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxRefundDTO.java new file mode 100644 index 00000000..731b65b4 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/wx/WxRefundDTO.java @@ -0,0 +1,40 @@ +package com.ruoyi.common.wx; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author: ghl + * @date: Created in 2023/7/1 0001 + * @description: + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WxRefundDTO { + /** + * 商户订单号 + */ + private String outTradeNo; + + /** + * 商户退款单号 + */ + private String outRefundNo; + + /** + * 订单金额 单位:分 + */ + private Integer totalFee; + + /** + * 退款金额 单位:分 + */ + private Integer refundFee; + + /** + * 退款结果通知url + */ + private String notifyUrl; +} diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml index de50de35..45235396 100644 --- a/ruoyi-framework/pom.xml +++ b/ruoyi-framework/pom.xml @@ -59,6 +59,14 @@ ruoyi-system + + com.github.wechatpay-apiv3 + wechatpay-java + + + org.projectlombok + lombok + - \ No newline at end of file + diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index b04beffb..e9dd98c6 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -23,7 +23,7 @@ import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl; /** * spring security配置 - * + * * @author ruoyi */ @EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) @@ -35,7 +35,7 @@ public class SecurityConfig */ @Autowired private UserDetailsService userDetailsService; - + /** * 认证失败处理类 */ @@ -53,7 +53,7 @@ public class SecurityConfig */ @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; - + /** * 跨域过滤器 */ @@ -111,7 +111,7 @@ public class SecurityConfig .authorizeHttpRequests((requests) -> { permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll()); // 对于登录login 注册register 验证码captchaImage 允许匿名访问 - requests.antMatchers("/login", "/register", "/captchaImage").permitAll() + requests.antMatchers("/login", "/register", "/captchaImage","/system/goods/**","/system/designType/**","/system/type/**","/system/order/**","/system/note/**").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/WeChatConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/WeChatConfig.java new file mode 100644 index 00000000..c5bc4754 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/WeChatConfig.java @@ -0,0 +1,99 @@ +package com.ruoyi.framework.config; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import com.wechat.pay.java.core.notification.NotificationConfig; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.service.payments.h5.H5Service; +import com.wechat.pay.java.service.payments.jsapi.JsapiService; +import com.wechat.pay.java.service.payments.nativepay.NativePayService; + +import lombok.Getter; + + +/** + * @desc: 微信config + * @author: shy + * @date: 2024/4/9 10:06 + */ +@Configuration +@Getter +public class WeChatConfig { + + /** + * 商户号 + */ + @Value("${wechat.pay.merchantId}") + public String merchantId; + /** + * 商户API私钥路径 + */ + @Value("${wechat.pay.privateKeyPath}") + public String privateKeyPath; + /** + * 商户证书序列号 + */ + @Value("${wechat.pay.merchantSerialNumber}") + public String merchantSerialNumber; + /** + * 商户APIV3密钥 + */ + @Value("${wechat.pay.apiV3Key}") + public String apiV3Key; + /** + * AppId + */ + @Value("${wechat.pay.appId}") + public String appId; + + private Config config; + + @PostConstruct + public void initConfig() { + // 使用自动更新平台证书的RSA配置 + // 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错 + config = new RSAAutoCertificateConfig.Builder() + .merchantId(merchantId) + .privateKeyFromPath(privateKeyPath) + .merchantSerialNumber(merchantSerialNumber) + .apiV3Key(apiV3Key) + .build(); + } + + @Primary + @Bean() + public H5Service h5Service() { + return new H5Service.Builder() + .config(config) + .build(); + } + + @Primary + @Bean() + public JsapiService jsapiService() { + return new JsapiService.Builder() + .config(config) + .build(); + } + + @Primary + @Bean() + public NativePayService nativePayService() { + return new NativePayService.Builder() + .config(config) + .build(); + } + + @Primary + @Bean + public NotificationParser notificationParser() { + return new NotificationParser((NotificationConfig) config); + } +} diff --git a/ruoyi-ui/src/permission.js b/ruoyi-ui/src/permission.js index e37189ba..2a8d3a23 100644 --- a/ruoyi-ui/src/permission.js +++ b/ruoyi-ui/src/permission.js @@ -8,7 +8,7 @@ import { isRelogin } from '@/utils/request' NProgress.configure({ showSpinner: false }) -const whiteList = ['/login', '/register','/bishe'] +const whiteList = ['/login', '/register','/cus','/cusDetails','/cusDetPay','/userOrder','/note'] router.beforeEach((to, from, next) => { NProgress.start() @@ -32,11 +32,11 @@ router.beforeEach((to, from, next) => { next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 }) }).catch(err => { - store.dispatch('LogOut').then(() => { - Message.error(err) - next({ path: '/' }) - }) + store.dispatch('LogOut').then(() => { + Message.error(err) + next({ path: '/' }) }) + }) } else { next() } diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js index 19577ae8..6c96d1e7 100644 --- a/ruoyi-ui/src/router/index.js +++ b/ruoyi-ui/src/router/index.js @@ -72,12 +72,12 @@ export const constantRoutes = [ hidden: true }, { - path: '/cusDetails/:id', + path: '/cusDetails', component: () => import('@/views/system/userFront/details'), hidden: true }, { - path: '/cusDetPay/:id', + path: '/cusDetPay', component: () => import('@/views/system/userFront/payBefore'), hidden: true }, diff --git a/ruoyi-ui/src/views/system/userFront/details.vue b/ruoyi-ui/src/views/system/userFront/details.vue index 00526b7b..ba1b78d2 100644 --- a/ruoyi-ui/src/views/system/userFront/details.vue +++ b/ruoyi-ui/src/views/system/userFront/details.vue @@ -54,7 +54,8 @@

¥{{ goods.price }}

- + + 支付下载
@@ -130,7 +131,8 @@ export default { mounted() { }, created() { - this.goodsId = this.$route.params.id; + // this.goodsId = this.$route.params.id; + this.goodsId = this.$route.query.id; this.getGood(this.goodsId); this.getTnList() this.getDnList() diff --git a/ruoyi-ui/src/views/system/userFront/index.vue b/ruoyi-ui/src/views/system/userFront/index.vue index 2eefc483..ad506417 100644 --- a/ruoyi-ui/src/views/system/userFront/index.vue +++ b/ruoyi-ui/src/views/system/userFront/index.vue @@ -74,7 +74,8 @@