diff --git a/pom.xml b/pom.xml
index 0e564e3..56193af 100644
--- a/pom.xml
+++ b/pom.xml
@@ -221,6 +221,11 @@
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+
diff --git a/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java b/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java
index cb345ba..bd91b9d 100644
--- a/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java
+++ b/src/main/java/com/greenorange/promotion/aop/PermissionCheck.java
@@ -72,11 +72,15 @@ public class PermissionCheck {
String userRole = userInfo.getUserRole();
UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(userRole);
- // 接口权限只能是 USER,ADMIN,BOSS,用户权限是 ADMIN,BOSS,USER,BAN
+ // 接口权限只能是 USER,ADMIN,BOSS,用户权限是 ADMIN,BOSS,USER,BAN,MANAGER,SUPERVISOR,STAFF
// 校验角色
- ThrowUtils.throwIf(UserRoleEnum.USER.equals(userRoleEnum) && !UserRoleEnum.USER.equals(interfaceRoleEnum), ErrorCode.NO_AUTH_ERROR);
- ThrowUtils.throwIf(UserRoleEnum.BAN.equals(userRoleEnum), ErrorCode.NO_AUTH_ERROR, "用户已被封禁");
- ThrowUtils.throwIf(UserRoleEnum.ADMIN.equals(userRoleEnum) && UserRoleEnum.BOSS.equals(interfaceRoleEnum), ErrorCode.NO_AUTH_ERROR);
+ ThrowUtils.throwIf(UserRoleEnum.BOSS.equals(userRoleEnum) && UserRoleEnum.USER.equals(interfaceRoleEnum), ErrorCode.NO_AUTH_ERROR);
+ ThrowUtils.throwIf(UserRoleEnum.ADMIN.equals(userRoleEnum) && !UserRoleEnum.ADMIN.equals(interfaceRoleEnum), ErrorCode.NO_AUTH_ERROR);
+ ThrowUtils.throwIf(UserRoleEnum.BAN.equals(userRoleEnum), ErrorCode.PARAMS_ERROR, "用户已被封禁");
+ ThrowUtils.throwIf((UserRoleEnum.USER.equals(userRoleEnum)
+ || UserRoleEnum.STAFF.equals(userRoleEnum)
+ || UserRoleEnum.SUPERVISOR.equals(userRoleEnum)
+ || UserRoleEnum.MANAGER.equals(userRoleEnum)) && !UserRoleEnum.USER.equals(interfaceRoleEnum), ErrorCode.NO_AUTH_ERROR);
return joinPoint.proceed();
}
diff --git a/src/main/java/com/greenorange/promotion/config/RabbitMQConfig.java b/src/main/java/com/greenorange/promotion/config/RabbitMQConfig.java
new file mode 100644
index 0000000..14ec82b
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/config/RabbitMQConfig.java
@@ -0,0 +1,25 @@
+package com.greenorange.promotion.config;
+
+import org.springframework.amqp.support.converter.DefaultClassMapper;
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
+import org.springframework.amqp.support.converter.MessageConverter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RabbitMQConfig {
+
+ @Bean
+ public MessageConverter jsonToMapMessageConverter() {
+ DefaultClassMapper defaultClassMapper = new DefaultClassMapper();
+ defaultClassMapper.setTrustedPackages("com.greenorange.promotion.utils.MultiDelayMessage"); // trusted packages
+ Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
+ jackson2JsonMessageConverter.setClassMapper(defaultClassMapper);
+ return jackson2JsonMessageConverter;
+ }
+
+ @Bean
+ public MessageConverter messageConverter(){
+ return new Jackson2JsonMessageConverter();
+ }
+}
diff --git a/src/main/java/com/greenorange/promotion/config/WxOpenConfig.java b/src/main/java/com/greenorange/promotion/config/WxOpenConfig.java
new file mode 100644
index 0000000..6554e50
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/config/WxOpenConfig.java
@@ -0,0 +1,44 @@
+package com.greenorange.promotion.config;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Data
+@Slf4j
+@Configuration
+@ConfigurationProperties(prefix = "wx.mini")
+public class WxOpenConfig {
+
+ private String appId;
+
+ private String appSecret;
+
+ private WxMaService wxMaService;
+
+ /**
+ * 单例模式
+ */
+ public WxMaService getWxMaService() {
+ if (wxMaService != null) {
+ return wxMaService;
+ }
+ synchronized (this) {
+ if (wxMaService != null) {
+ return wxMaService;
+ }
+ WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
+ config.setAppid(appId);
+ config.setSecret(appSecret);
+ WxMaService service = new WxMaServiceImpl();
+ service.setWxMaConfig(config);
+ wxMaService = service;
+ return wxMaService;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/greenorange/promotion/config/WxPayConfig.java b/src/main/java/com/greenorange/promotion/config/WxPayConfig.java
new file mode 100644
index 0000000..bdf99bb
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/config/WxPayConfig.java
@@ -0,0 +1,78 @@
+package com.greenorange.promotion.config;
+
+import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import com.wechat.pay.java.core.util.IOUtil;
+import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
+import com.wechat.pay.java.service.refund.RefundService;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+@Data
+@Slf4j
+@Configuration
+@Component("WxPayConfig")
+@ConfigurationProperties(prefix = "wx.pay")
+public class WxPayConfig {
+
+ private String appId;
+
+ private String apiV3Key;
+
+ private String notifyUrl;
+
+ private String merchantId;
+
+ private String privateKeyPath;
+
+ private String merchantSerialNumber;
+
+ // RSA配置
+ private RSAAutoCertificateConfig RSAConfig;
+
+ // JSAPI支付
+ private JsapiServiceExtension jsapiServiceExtension;
+
+ // 退款
+ private RefundService refundService;
+
+ /**
+ * 初始化配置
+ */
+ @Bean
+ public boolean initWxPayConfig() throws IOException {
+ this.RSAConfig = buildRSAAutoCertificateConfig();
+ this.jsapiServiceExtension = buildJsapiServiceExtension(RSAConfig);
+ this.refundService = buildRefundService(RSAConfig);
+ return true;
+ }
+
+ // 构建并使用自动更新平台证书的RSA配置,一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
+ private RSAAutoCertificateConfig buildRSAAutoCertificateConfig() throws IOException {
+ // 将 resource 目录下的文件转为 InputStream,然后利用 IOUtil.toString(inputStream) 转化为密钥
+ String privateKey = IOUtil.toString(new ClassPathResource(privateKeyPath).getInputStream());
+ return new RSAAutoCertificateConfig.Builder()
+ .merchantId(merchantId)
+ .privateKey(privateKey)
+ .merchantSerialNumber(merchantSerialNumber)
+ .apiV3Key(apiV3Key)
+ .build();
+ }
+
+ // 构建JSAPI支付
+ private JsapiServiceExtension buildJsapiServiceExtension(RSAAutoCertificateConfig config) {
+ return new JsapiServiceExtension.Builder().config(config).build();
+ }
+
+ // 构建退款
+ private RefundService buildRefundService(RSAAutoCertificateConfig config) {
+ return new RefundService.Builder().config(config).build();
+ }
+
+}
diff --git a/src/main/java/com/greenorange/promotion/controller/course/CourseController.java b/src/main/java/com/greenorange/promotion/controller/course/CourseController.java
index 531274f..a0ff09c 100644
--- a/src/main/java/com/greenorange/promotion/controller/course/CourseController.java
+++ b/src/main/java/com/greenorange/promotion/controller/course/CourseController.java
@@ -56,9 +56,6 @@ public class CourseController {
@Resource
private CommonService commonService;
- @Resource
- private WechatGetQrcodeService wechatGetQrcodeService;
-
/**
* 小程序端用户查看热门课程列表
diff --git a/src/main/java/com/greenorange/promotion/controller/wechat/WechatPayController.java b/src/main/java/com/greenorange/promotion/controller/wechat/WechatPayController.java
new file mode 100644
index 0000000..131d679
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/controller/wechat/WechatPayController.java
@@ -0,0 +1,150 @@
+package com.greenorange.promotion.controller.wechat;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
+import com.greenorange.promotion.annotation.RequiresPermission;
+import com.greenorange.promotion.common.BaseResponse;
+import com.greenorange.promotion.common.ErrorCode;
+import com.greenorange.promotion.common.ResultUtils;
+import com.greenorange.promotion.config.WxOpenConfig;
+import com.greenorange.promotion.constant.OrderStatusConstant;
+import com.greenorange.promotion.constant.UserConstant;
+import com.greenorange.promotion.exception.BusinessException;
+import com.greenorange.promotion.exception.ThrowUtils;
+import com.greenorange.promotion.model.dto.CommonRequest;
+import com.greenorange.promotion.model.dto.wxPay.WechatPayRequest;
+import com.greenorange.promotion.model.entity.CourseOrder;
+import com.greenorange.promotion.model.entity.UserInfo;
+import com.greenorange.promotion.service.course.CourseOrderService;
+import com.greenorange.promotion.service.userInfo.UserInfoService;
+import com.greenorange.promotion.service.wechat.WechatPayService;
+import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
+import com.wechat.pay.java.service.payments.model.Transaction;
+import com.wechat.pay.java.service.refund.model.Refund;
+import com.wechat.pay.java.service.refund.model.RefundNotification;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+
+@Slf4j
+@RestController
+@Tag(name = "微信支付")
+@RequestMapping("/wxPay")
+public class WechatPayController {
+
+
+ @Resource
+ private WechatPayService weChatService;
+
+ @Resource
+ private UserInfoService userInfoService;
+
+ @Resource
+ private CourseOrderService courseOrderService;
+
+ @Resource
+ private WxOpenConfig wxOpenConfig;
+
+
+ /**
+ * JSAPI 下单(商品类)
+ */
+ @PostMapping("/payment/create")
+ @Operation(summary = "JSAPI 下单(商品类)", description = "参数:订单id, 权限:所有人, 方法名:createPayment")
+ @RequiresPermission(mustRole = UserConstant.DEFAULT_ROLE)
+ public BaseResponse createPayment(@Valid @RequestBody WechatPayRequest wechatPayRequest, HttpServletRequest request) {
+
+ String code = wechatPayRequest.getCode();
+ WxMaJscode2SessionResult sessionInfo;
+ String miniOpenId;
+ try {
+ WxMaService wxMaService = wxOpenConfig.getWxMaService();
+ sessionInfo = wxMaService.jsCode2SessionInfo(code);
+ miniOpenId = sessionInfo.getOpenid();
+ if (StringUtils.isAnyBlank(miniOpenId)) {
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR);
+ }
+ } catch (WxErrorException e) {
+ log.error("userLoginByWxOpen error", e);
+ throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败,系统错误");
+ }
+ Long userId = (Long) request.getAttribute("userId");
+ UserInfo userInfo = userInfoService.getById(userId);
+
+ Long orderId = wechatPayRequest.getOrderId();
+ CourseOrder courseOrder = courseOrderService.getById(orderId);
+ ThrowUtils.throwIf(courseOrder == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在");
+ ThrowUtils.throwIf(!courseOrder.getOrderStatus().equals(OrderStatusConstant.PENDING), ErrorCode.OPERATION_ERROR, "订单状态错误");
+ if (!userInfo.getId().equals(courseOrder.getUserId())) {
+ throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "你不是该订单用户!");
+ }
+ PrepayWithRequestPaymentResponse response = weChatService.createPayment(String.valueOf(orderId), miniOpenId, courseOrder.getTotalAmount());
+ return ResultUtils.success(response);
+ }
+
+
+
+ /**
+ * JSAPI 下单回调(商品类)
+ */
+ @Hidden
+ @PostMapping("/payment/callback")
+ @Operation(summary = "JSAPI 下单回调(商品类)", description = "参数:订单id, 权限:所有人, 方法名:callbackPayment")
+ public synchronized BaseResponse callbackPayment(HttpServletRequest request) throws IOException {
+ // 获取下单信息
+ Transaction transaction = weChatService.getTransactionInfo(request);
+ System.out.println("下单信息:" + transaction);
+ // 支付回调
+ boolean result = weChatService.paymentCallback(transaction);
+ ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "微信支付回调失败");
+ return ResultUtils.success(true);
+ }
+
+
+ /**
+ * Web管理员部分退款
+ * @param commonRequest 订单id
+ */
+ @PostMapping("/refund/part/create")
+ @Operation(summary = "Web管理员部分退款", description = "参数:订单id, 权限:web端管理员, 方法名:createPartRefund")
+ @RequiresPermission(mustRole = UserConstant.ADMIN_ROLE)
+ public BaseResponse createPartRefund(@Valid @RequestBody CommonRequest commonRequest) {
+ Long courseOrderId = commonRequest.getId();
+ CourseOrder courseOrder = courseOrderService.getById(courseOrderId);
+ ThrowUtils.throwIf(courseOrder == null, ErrorCode.OPERATION_ERROR, "订单不存在");
+
+ Refund refund = weChatService.refundPartPayment(String.valueOf(courseOrderId), courseOrder.getTotalAmount());
+ return ResultUtils.success(refund);
+ }
+
+
+ /**
+ * 部分退款回调
+ */
+ @Hidden
+ @PostMapping("/refund/part/callback")
+ @Operation(summary = "部分退款回调", description = "参数:订单id, 权限:web端管理员, 方法名:callbackRefundPart")
+ public BaseResponse callbackRefundPart(HttpServletRequest request) {
+ // 获取退款信息
+ RefundNotification refundNotification = weChatService.getRefundInfo(request);
+ // 退款回调
+ boolean result = weChatService.refundPartCallback(refundNotification);
+ ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "退款回调失败");
+ return ResultUtils.success(true);
+ }
+
+
+}
diff --git a/src/main/java/com/greenorange/promotion/controller/wechat/WechatPayoutsController.java b/src/main/java/com/greenorange/promotion/controller/wechat/WechatPayoutsController.java
index e2861cd..12247e0 100644
--- a/src/main/java/com/greenorange/promotion/controller/wechat/WechatPayoutsController.java
+++ b/src/main/java/com/greenorange/promotion/controller/wechat/WechatPayoutsController.java
@@ -26,7 +26,6 @@ import java.util.Map;
public class WechatPayoutsController {
-
/**
* 微信小程序积分提现到银行卡
*/
diff --git a/src/main/java/com/greenorange/promotion/model/dto/wxPay/WechatPayRequest.java b/src/main/java/com/greenorange/promotion/model/dto/wxPay/WechatPayRequest.java
new file mode 100644
index 0000000..c0e1ba5
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/model/dto/wxPay/WechatPayRequest.java
@@ -0,0 +1,33 @@
+package com.greenorange.promotion.model.dto.wxPay;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+@Data
+@Schema(description = "微信支付请求体", requiredProperties = {"orderId", "code"})
+public class WechatPayRequest implements Serializable {
+
+ /**
+ * 订单id
+ */
+ @NotNull(message = "id不能为null")
+ @Min(value = 1L, message = "id不能小于1")
+ @Schema(description = "订单id", example = "1")
+ private Long orderId;
+
+ /**
+ * 用户登录凭证
+ */
+ @NotBlank(message = "用户登录凭证不能为空")
+ @Schema(description = "用户登录凭证", example = "23nm5jfds22a2324rr32rr")
+ private String code;
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+}
diff --git a/src/main/java/com/greenorange/promotion/model/vo/userInfo/UserInfoVO.java b/src/main/java/com/greenorange/promotion/model/vo/userInfo/UserInfoVO.java
index 4fb4e53..f249e5d 100644
--- a/src/main/java/com/greenorange/promotion/model/vo/userInfo/UserInfoVO.java
+++ b/src/main/java/com/greenorange/promotion/model/vo/userInfo/UserInfoVO.java
@@ -37,7 +37,6 @@ public class UserInfoVO implements Serializable {
@Schema(description = "手机号", example = "15888610253")
private String phoneNumber;
-
/**
* 账号
*/
diff --git a/src/main/java/com/greenorange/promotion/service/userInfo/impl/UserInfoServiceImpl.java b/src/main/java/com/greenorange/promotion/service/userInfo/impl/UserInfoServiceImpl.java
index 024e608..1dd7839 100644
--- a/src/main/java/com/greenorange/promotion/service/userInfo/impl/UserInfoServiceImpl.java
+++ b/src/main/java/com/greenorange/promotion/service/userInfo/impl/UserInfoServiceImpl.java
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.greenorange.promotion.common.ErrorCode;
+import com.greenorange.promotion.config.WxOpenConfig;
import com.greenorange.promotion.constant.CommonConstant;
import com.greenorange.promotion.constant.SystemConstant;
import com.greenorange.promotion.constant.UserConstant;
@@ -95,6 +96,8 @@ public class UserInfoServiceImpl extends ServiceImpl
+
+
/**
* 获取查询条件
*/
diff --git a/src/main/java/com/greenorange/promotion/service/wechat/WechatPayService.java b/src/main/java/com/greenorange/promotion/service/wechat/WechatPayService.java
new file mode 100644
index 0000000..9de9036
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/service/wechat/WechatPayService.java
@@ -0,0 +1,53 @@
+package com.greenorange.promotion.service.wechat;
+
+import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
+import com.wechat.pay.java.service.payments.model.Transaction;
+import com.wechat.pay.java.service.refund.model.Refund;
+import com.wechat.pay.java.service.refund.model.RefundNotification;
+import jakarta.servlet.http.HttpServletRequest;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+
+/**
+ * @author 陈新知
+ */
+public interface WechatPayService {
+
+ /**
+ * 微信支付
+ */
+ PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount);
+
+
+ /**
+ * 支付回调
+ */
+ boolean paymentCallback(Transaction transaction) throws IOException;
+
+
+ /**
+ * 部分退款申请
+ */
+ Refund refundPartPayment(String orderId, BigDecimal refundAmount);
+
+
+ /**
+ * 部分退款回调
+ */
+ boolean refundPartCallback(RefundNotification refundNotification);
+
+
+ /**
+ * 获取支付回调信息
+ */
+ Transaction getTransactionInfo(HttpServletRequest request);
+
+
+ /**
+ * 获取退款回调信息
+ */
+ RefundNotification getRefundInfo(HttpServletRequest request);
+
+
+}
diff --git a/src/main/java/com/greenorange/promotion/service/wechat/impl/WechatPayServiceImpl.java b/src/main/java/com/greenorange/promotion/service/wechat/impl/WechatPayServiceImpl.java
new file mode 100644
index 0000000..515f40a
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/service/wechat/impl/WechatPayServiceImpl.java
@@ -0,0 +1,252 @@
+package com.greenorange.promotion.service.wechat.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.greenorange.promotion.common.ErrorCode;
+import com.greenorange.promotion.config.WxPayConfig;
+import com.greenorange.promotion.constant.OrderStatusConstant;
+import com.greenorange.promotion.exception.ThrowUtils;
+import com.greenorange.promotion.model.entity.Course;
+import com.greenorange.promotion.model.entity.CourseOrder;
+import com.greenorange.promotion.service.course.CourseOrderService;
+import com.greenorange.promotion.service.course.CourseService;
+import com.greenorange.promotion.service.wechat.WechatPayService;
+import com.greenorange.promotion.utils.RefundUtils;
+import com.wechat.pay.java.core.notification.NotificationParser;
+import com.wechat.pay.java.core.notification.RequestParam;
+import com.wechat.pay.java.service.payments.jsapi.model.Amount;
+import com.wechat.pay.java.service.payments.jsapi.model.Payer;
+import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
+import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
+import com.wechat.pay.java.service.payments.model.Transaction;
+import com.wechat.pay.java.service.refund.model.AmountReq;
+import com.wechat.pay.java.service.refund.model.CreateRequest;
+import com.wechat.pay.java.service.refund.model.Refund;
+import com.wechat.pay.java.service.refund.model.RefundNotification;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.SneakyThrows;
+import org.springframework.stereotype.Service;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.math.BigDecimal;
+
+/**
+ * @author 陈新知
+ */
+@Service
+public class WechatPayServiceImpl implements WechatPayService {
+
+
+ @Resource
+ private WxPayConfig wxPayConfig;
+
+
+ @Resource
+ private CourseOrderService courseOrderService;
+
+
+ @Resource
+ private CourseService courseService;
+
+
+ /**
+ * 请求参数
+ */
+ public static RequestParam requestParam = null;
+
+
+ /**
+ * 微信支付
+ */
+ @Override
+ public PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount) {
+ // request.setXxx(val)设置所需参数,具体参数可见Request定义
+ PrepayRequest request = new PrepayRequest();
+ // 金额
+ Amount WxAmount = new Amount();
+ WxAmount.setTotal(amount.movePointRight(2).intValue());
+ WxAmount.setCurrency("CNY");
+ request.setAmount(WxAmount);
+ // 公众号id
+ request.setAppid(wxPayConfig.getAppId());
+ // 商户号
+ request.setMchid(wxPayConfig.getMerchantId());
+ // 支付者信息
+ Payer payer = new Payer();
+ payer.setOpenid(miniOpenId);
+ request.setPayer(payer);
+ // 获取订单号
+ CourseOrder courseOrder = courseOrderService.getById(orderId);
+ String orderNumber = courseOrder.getOrderNumber();
+ // 描述
+ request.setDescription("订单号:" + orderNumber);
+ // 微信回调地址
+ request.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/wxPay/payment/callback");
+ // 商户订单号
+ request.setOutTradeNo(orderNumber);
+ //返回数据,前端调起支付
+ return wxPayConfig.getJsapiServiceExtension().prepayWithRequestPayment(request);
+ }
+
+
+ /**
+ * 支付回调
+ */
+ @Override
+ public boolean paymentCallback(Transaction transaction) throws IOException {
+ System.out.println("---------------------------微信支付回调(开始)-------------------------------");
+ // 获取订单信息
+ String orderNumber = transaction.getOutTradeNo();
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(CourseOrder::getOrderNumber, orderNumber);
+ CourseOrder courseOrder = courseOrderService.getOne(queryWrapper);
+
+ // 修改订单状态
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.eq(CourseOrder::getId, courseOrder.getId())
+ .set(CourseOrder::getOrderStatus, OrderStatusConstant.SUCCESS);
+ courseOrderService.update(updateWrapper);
+
+ // 修改当前课程下单人数
+ Long courseId = courseOrder.getCourseId();
+ Course course = courseService.getById(courseId);
+ if (course != null) {
+ course.setOrderCount(course.getOrderCount() + 1);
+ courseService.updateById(course);
+ }
+ System.out.println("---------------------------微信支付回调(结束)-------------------------------");
+ return true;
+ }
+
+
+ /**
+ * 部分退款申请
+ */
+ @Override
+ public Refund refundPartPayment(String orderId, BigDecimal refundAmount) {
+ // 获取订单
+ CourseOrder courseOrder = courseOrderService.getById(orderId);
+ ThrowUtils.throwIf(courseOrder == null, ErrorCode.OPERATION_ERROR, "订单不存在");
+ // 判断该订单是否已经退款
+ ThrowUtils.throwIf(courseOrder.getOrderStatus().equals(OrderStatusConstant.REFUNDED), ErrorCode.OPERATION_ERROR, "订单已退款");
+
+ String orderNumber = courseOrder.getOrderNumber();
+ // 退款请求
+ CreateRequest createRequest = new CreateRequest();
+ // 商户订单号
+ createRequest.setOutTradeNo(orderNumber);
+ // 商户退款单号
+ String outRefundNo = RefundUtils.generateRefundNo();
+ createRequest.setOutRefundNo(outRefundNo);
+ // 退款结果回调
+ createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/wxPay/refund/part/callback");
+ // 退款金额
+ AmountReq amountReq = new AmountReq();
+
+ amountReq.setRefund(refundAmount.movePointRight(2).longValue());
+ amountReq.setTotal(courseOrder.getTotalAmount().movePointRight(2).longValue());
+ amountReq.setCurrency("CNY");
+ createRequest.setAmount(amountReq);
+
+ //TODO 生成退款记录
+
+ // 申请退款
+ System.out.println("退款请求:" + createRequest);
+ Refund refund = wxPayConfig.getRefundService().create(createRequest);
+ System.out.println("退款申请结果:" + refund);
+
+ return refund;
+ }
+
+
+ /**
+ * 部分退款回调
+ */
+ @Override
+ public boolean refundPartCallback(RefundNotification refundNotification) {
+ System.out.println("---------------------------微信退款回调(开始)-------------------------------");
+ // 获取订单信息
+ String orderNumber = refundNotification.getOutTradeNo();
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(CourseOrder::getOrderNumber, orderNumber);
+ CourseOrder courseOrder = courseOrderService.getOne(queryWrapper);
+
+ // 修改订单状态
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.eq(CourseOrder::getId, courseOrder.getId())
+ .set(CourseOrder::getOrderStatus, OrderStatusConstant.REFUNDED);
+ courseOrderService.update(updateWrapper);
+
+ // 修改课程下单人数
+ Long courseId = courseOrder.getCourseId();
+ Course course = courseService.getById(courseId);
+ if (course != null) {
+ course.setOrderCount(course.getOrderCount() - 1);
+ courseService.updateById(course);
+ }
+
+ System.out.println("---------------------------微信退款回调(结束)-------------------------------");
+ return true;
+ }
+
+
+ /**
+ * 获取支付回调信息
+ */
+ @Override
+ public Transaction getTransactionInfo(HttpServletRequest request) {
+ NotificationParser notificationParser = getNotificationParser(request);
+ return notificationParser.parse(requestParam, Transaction.class);
+ }
+
+
+ /**
+ * 获取退款回调信息
+ */
+ @Override
+ public RefundNotification getRefundInfo(HttpServletRequest request) {
+ NotificationParser notificationParser = getNotificationParser(request);
+ return notificationParser.parse(requestParam, RefundNotification.class);
+ }
+
+
+ /**
+ * 根据微信官方发送的请求获取信息
+ */
+ @SneakyThrows
+ public NotificationParser getNotificationParser(HttpServletRequest request) {
+ System.out.println("---------------------------获取信息-------------------------------");
+ // 获取RSA配置
+ NotificationParser notificationParser = new NotificationParser(wxPayConfig.getRSAConfig());
+ // 构建请求
+ StringBuilder bodyBuilder = new StringBuilder();
+ BufferedReader reader = request.getReader();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ bodyBuilder.append(line);
+ }
+ String body = bodyBuilder.toString();
+ String timestamp = request.getHeader("Wechatpay-Timestamp");
+ String nonce = request.getHeader("Wechatpay-Nonce");
+ String signature = request.getHeader("Wechatpay-Signature");
+ String singType = request.getHeader("Wechatpay-Signature-Type");
+ String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
+ requestParam = new RequestParam.Builder()
+ .serialNumber(wechatPayCertificateSerialNumber)
+ .nonce(nonce)
+ .signature(signature)
+ .timestamp(timestamp)
+ .signType(singType)
+ .body(body)
+ .build();
+ System.out.println(requestParam.toString());
+ System.out.println("---------------------------信息获取完毕-------------------------------");
+ return notificationParser;
+ }
+
+
+}
diff --git a/src/main/java/com/greenorange/promotion/utils/MultiDelayMessage.java b/src/main/java/com/greenorange/promotion/utils/MultiDelayMessage.java
new file mode 100644
index 0000000..b55c419
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/utils/MultiDelayMessage.java
@@ -0,0 +1,59 @@
+package com.greenorange.promotion.utils;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+public class MultiDelayMessage implements Serializable {
+
+ /**
+ * 消息体
+ */
+ private T data;
+
+
+ /**
+ * 记录延时时间的集合
+ */
+ private List delayMillis;
+
+ public MultiDelayMessage(T data, List delayMillis) {
+ this.data = data;
+ this.delayMillis = delayMillis;
+ }
+
+ public MultiDelayMessage(T data, Long...delayMillis) {
+ this.data = data;
+ this.delayMillis = new ArrayList<>(Arrays.asList(delayMillis));
+ }
+
+ /**
+ * 获取并移除下一个延迟时间
+ * @return 集合中第一个延迟时间
+ */
+ public Long removeNextDelay() {
+ return delayMillis.remove(0);
+ }
+
+
+ /**
+ * 是否有下一个延迟时间
+ */
+ public boolean hasNextDelay() {
+ return !delayMillis.isEmpty();
+ }
+
+
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+
+}
diff --git a/src/main/java/com/greenorange/promotion/utils/RefundUtils.java b/src/main/java/com/greenorange/promotion/utils/RefundUtils.java
new file mode 100644
index 0000000..e33f017
--- /dev/null
+++ b/src/main/java/com/greenorange/promotion/utils/RefundUtils.java
@@ -0,0 +1,21 @@
+package com.greenorange.promotion.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+public class RefundUtils {
+
+ // 生成唯一的全额退款单号,格式为 yyyyMMddHHmmssSSS + 随机数
+ public static String generateRefundNo() {
+ // 获取当前时间的时间戳
+ String timestamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
+
+ // 生成一个 4 位随机数,保证每次退款单号不同
+ int randomNum = new Random().nextInt(9000) + 1000; // 生成1000到9999之间的随机数
+
+ // 拼接退款单号
+ return timestamp + randomNum;
+ }
+
+}