接入微信支付
This commit is contained in:
5
pom.xml
5
pom.xml
@ -221,6 +221,11 @@
|
||||
</dependency>
|
||||
|
||||
|
||||
<!--rabbitmq依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -56,9 +56,6 @@ public class CourseController {
|
||||
@Resource
|
||||
private CommonService commonService;
|
||||
|
||||
@Resource
|
||||
private WechatGetQrcodeService wechatGetQrcodeService;
|
||||
|
||||
|
||||
/**
|
||||
* 小程序端用户查看热门课程列表
|
||||
|
@ -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<PrepayWithRequestPaymentResponse> 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<Boolean> 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<Refund> 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<Boolean> callbackRefundPart(HttpServletRequest request) {
|
||||
// 获取退款信息
|
||||
RefundNotification refundNotification = weChatService.getRefundInfo(request);
|
||||
// 退款回调
|
||||
boolean result = weChatService.refundPartCallback(refundNotification);
|
||||
ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "退款回调失败");
|
||||
return ResultUtils.success(true);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -26,7 +26,6 @@ import java.util.Map;
|
||||
public class WechatPayoutsController {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 微信小程序积分提现到银行卡
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
@ -37,7 +37,6 @@ public class UserInfoVO implements Serializable {
|
||||
@Schema(description = "手机号", example = "15888610253")
|
||||
private String phoneNumber;
|
||||
|
||||
|
||||
/**
|
||||
* 账号
|
||||
*/
|
||||
|
@ -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<UserInfoMapper, UserInfo>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取查询条件
|
||||
*/
|
||||
|
@ -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);
|
||||
|
||||
|
||||
}
|
@ -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<CourseOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CourseOrder::getOrderNumber, orderNumber);
|
||||
CourseOrder courseOrder = courseOrderService.getOne(queryWrapper);
|
||||
|
||||
// 修改订单状态
|
||||
LambdaUpdateWrapper<CourseOrder> 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<CourseOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CourseOrder::getOrderNumber, orderNumber);
|
||||
CourseOrder courseOrder = courseOrderService.getOne(queryWrapper);
|
||||
|
||||
// 修改订单状态
|
||||
LambdaUpdateWrapper<CourseOrder> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<T> implements Serializable {
|
||||
|
||||
/**
|
||||
* 消息体
|
||||
*/
|
||||
private T data;
|
||||
|
||||
|
||||
/**
|
||||
* 记录延时时间的集合
|
||||
*/
|
||||
private List<Long> delayMillis;
|
||||
|
||||
public MultiDelayMessage(T data, List<Long> 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;
|
||||
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user