更新了商品管理模块

This commit is contained in:
chen-xin-zhi 2024-12-10 09:25:37 +08:00
parent 42a5e98f7a
commit 37c501ebfd
14 changed files with 1393 additions and 522 deletions

12
pom.xml
View File

@ -178,12 +178,20 @@
</dependency> </dependency>
<!-- 微信支付--> <!-- 微信支付-->
<!-- <dependency>-->
<!-- <groupId>com.github.wechatpay-apiv3</groupId>-->
<!-- <artifactId>wechatpay-java</artifactId>-->
<!-- <version>0.2.10</version>-->
<!-- </dependency>-->
<!--微信支付SDK-->
<dependency> <dependency>
<groupId>com.github.wechatpay-apiv3</groupId> <groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId> <artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.10</version> <version>0.3.0</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -1,78 +1,78 @@
package com.cultural.heritage.config; //package com.cultural.heritage.config;
//
import com.wechat.pay.java.core.RSAAutoCertificateConfig; //import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.util.IOUtil; //import com.wechat.pay.java.core.util.IOUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; //import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.refund.RefundService; //import com.wechat.pay.java.service.refund.RefundService;
import lombok.Data; //import lombok.Data;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties; //import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; //import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; //import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource; //import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component; //import org.springframework.stereotype.Component;
//
import java.io.IOException; //import java.io.IOException;
//
@Data //@Data
@Slf4j //@Slf4j
@Configuration //@Configuration
@Component("WxPayConfig") //@Component("WxPayConfig")
@ConfigurationProperties(prefix = "wx.pay") //@ConfigurationProperties(prefix = "wx.pay")
public class WxPayConfig { //public class WxPayConfig {
//
private String appId; // private String appId;
//
private String apiV3Key; // private String apiV3Key;
//
private String notifyUrl; // private String notifyUrl;
//
private String merchantId; // private String merchantId;
//
private String privateKeyPath; // private String privateKeyPath;
//
private String merchantSerialNumber; // private String merchantSerialNumber;
//
// RSA配置 // // RSA配置
private RSAAutoCertificateConfig RSAConfig; // private RSAAutoCertificateConfig RSAConfig;
//
// JSAPI支付 // // JSAPI支付
private JsapiServiceExtension jsapiServiceExtension; // private JsapiServiceExtension jsapiServiceExtension;
//
// 退款 // // 退款
private RefundService refundService; // private RefundService refundService;
//
/** // /**
* 初始化配置 // * 初始化配置
*/ // */
@Bean // @Bean
public boolean initWxPayConfig() throws IOException { // public boolean initWxPayConfig() throws IOException {
this.RSAConfig = buildRSAAutoCertificateConfig(); // this.RSAConfig = buildRSAAutoCertificateConfig();
this.jsapiServiceExtension = buildJsapiServiceExtension(RSAConfig); // this.jsapiServiceExtension = buildJsapiServiceExtension(RSAConfig);
this.refundService = buildRefundService(RSAConfig); // this.refundService = buildRefundService(RSAConfig);
return true; // return true;
} // }
//
// 构建并使用自动更新平台证书的RSA配置一个商户号只能初始化一个配置否则会因为重复的下载任务报错 // // 构建并使用自动更新平台证书的RSA配置一个商户号只能初始化一个配置否则会因为重复的下载任务报错
private RSAAutoCertificateConfig buildRSAAutoCertificateConfig() throws IOException { // private RSAAutoCertificateConfig buildRSAAutoCertificateConfig() throws IOException {
// resource 目录下的文件转为 InputStream然后利用 IOUtil.toString(inputStream) 转化为密钥 // // resource 目录下的文件转为 InputStream然后利用 IOUtil.toString(inputStream) 转化为密钥
String privateKey = IOUtil.toString(new ClassPathResource(privateKeyPath).getInputStream()); // String privateKey = IOUtil.toString(new ClassPathResource(privateKeyPath).getInputStream());
return new RSAAutoCertificateConfig.Builder() // return new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId) // .merchantId(merchantId)
.privateKey(privateKey) // .privateKey(privateKey)
.merchantSerialNumber(merchantSerialNumber) // .merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key) // .apiV3Key(apiV3Key)
.build(); // .build();
} // }
//
// 构建JSAPI支付 // // 构建JSAPI支付
private JsapiServiceExtension buildJsapiServiceExtension(RSAAutoCertificateConfig config) { // private JsapiServiceExtension buildJsapiServiceExtension(RSAAutoCertificateConfig config) {
return new JsapiServiceExtension.Builder().config(config).build(); // return new JsapiServiceExtension.Builder().config(config).build();
} // }
//
// 构建退款 // // 构建退款
private RefundService buildRefundService(RSAAutoCertificateConfig config) { // private RefundService buildRefundService(RSAAutoCertificateConfig config) {
return new RefundService.Builder().config(config).build(); // return new RefundService.Builder().config(config).build();
} // }
//
} //}

View File

@ -160,7 +160,7 @@ public class AppointmentDateController {
@Operation(summary = "Web端管理员添加预约时间段", description = "参数:预约时间段添加请求体,权限:管理员(admin, boss)方法名addAppointmentDate") @Operation(summary = "Web端管理员添加预约时间段", description = "参数:预约时间段添加请求体,权限:管理员(admin, boss)方法名addAppointmentDate")
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE) @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Boolean> addTimePeriod(@RequestBody TimePeriodSingleAddRequest timePeriodSingleAddRequest) { public BaseResponse<Long> addTimePeriod(@RequestBody TimePeriodSingleAddRequest timePeriodSingleAddRequest) {
if (timePeriodSingleAddRequest == null) { if (timePeriodSingleAddRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR); throw new BusinessException(ErrorCode.PARAMS_ERROR);
} }
@ -168,7 +168,7 @@ public class AppointmentDateController {
BeanUtils.copyProperties(timePeriodSingleAddRequest, timePeriod); BeanUtils.copyProperties(timePeriodSingleAddRequest, timePeriod);
boolean save = timePeriodService.save(timePeriod); boolean save = timePeriodService.save(timePeriod);
ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "预约时间段添加失败"); ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "预约时间段添加失败");
return ResultUtils.success(true); return ResultUtils.success(timePeriod.getId());
} }

View File

@ -1,120 +1,120 @@
package com.cultural.heritage.controller.wx; //package com.cultural.heritage.controller.wx;
//
//
import com.cultural.heritage.annotation.AuthCheck; //import com.cultural.heritage.annotation.AuthCheck;
import com.cultural.heritage.common.BaseResponse; //import com.cultural.heritage.common.BaseResponse;
import com.cultural.heritage.common.ErrorCode; //import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.common.ResultUtils; //import com.cultural.heritage.common.ResultUtils;
import com.cultural.heritage.constant.UserConstant; //import com.cultural.heritage.constant.UserConstant;
import com.cultural.heritage.exception.BusinessException; //import com.cultural.heritage.exception.BusinessException;
import com.cultural.heritage.exception.ThrowUtils; //import com.cultural.heritage.exception.ThrowUtils;
import com.cultural.heritage.model.dto.CommonRequest; //import com.cultural.heritage.model.dto.CommonRequest;
import com.cultural.heritage.model.entity.Order; //import com.cultural.heritage.model.entity.Order;
import com.cultural.heritage.model.entity.User; //import com.cultural.heritage.model.entity.User;
import com.cultural.heritage.service.order.OrderService; //import com.cultural.heritage.service.order.OrderService;
import com.cultural.heritage.service.user.UserService; //import com.cultural.heritage.service.user.UserService;
import com.cultural.heritage.service.wxpay.WeChatService; //import com.cultural.heritage.service.wxpay.WeChatService;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; //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.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund; //import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification; //import com.wechat.pay.java.service.refund.model.RefundNotification;
import io.swagger.v3.oas.annotations.tags.Tag; //import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; //import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; //import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional; //import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*; //import org.springframework.web.bind.annotation.*;
//
import java.io.IOException; //import java.io.IOException;
//
/** ///**
* 微信小程序相关接口 // * 微信小程序相关接口
**/ // **/
@Slf4j //@Slf4j
@RestController //@RestController
@Tag(name = "微信支付接口") //@Tag(name = "微信支付接口")
@RequestMapping("/wechat") //@RequestMapping("/wechat")
public class WeChatPayController { //public class WeChatPayController {
//
@Resource // @Resource
private UserService userService; // private UserService userService;
//
@Resource // @Resource
private OrderService ordersService; // private OrderService ordersService;
//
@Resource // @Resource
private WeChatService weChatService; // private WeChatService weChatService;
//
//
/** // /**
* JSAPI 下单 // * JSAPI 下单
*/ // */
@PostMapping("/payment/create") // @PostMapping("/payment/create")
public BaseResponse<PrepayWithRequestPaymentResponse> createPayment(@RequestBody CommonRequest commonRequest, HttpServletRequest request) { // public BaseResponse<PrepayWithRequestPaymentResponse> createPayment(@RequestBody CommonRequest commonRequest, HttpServletRequest request) {
User loginUser = userService.getLoginUser(request); // User loginUser = userService.getLoginUser(request);
String miniOpenId = loginUser.getMiniOpenId(); // String miniOpenId = loginUser.getMiniOpenId();
ThrowUtils.throwIf(miniOpenId == null, ErrorCode.NOT_FOUND_ERROR, "不是小程序用户"); // ThrowUtils.throwIf(miniOpenId == null, ErrorCode.NOT_FOUND_ERROR, "不是小程序用户");
Long orderId = commonRequest.getId(); // Long orderId = commonRequest.getId();
Order order = ordersService.getById(orderId); // Order order = ordersService.getById(orderId);
ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在"); // ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在");
// ThrowUtils.throwIf(order.getState() != 0, ErrorCode.OPERATION_ERROR, "订单状态错误"); //// ThrowUtils.throwIf(order.getState() != 0, ErrorCode.OPERATION_ERROR, "订单状态错误");
if (!loginUser.getId().equals(order.getUserId())) { // if (!loginUser.getId().equals(order.getUserId())) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "你不是该订单用户!"); // throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "你不是该订单用户!");
} // }
PrepayWithRequestPaymentResponse response = weChatService.createPayment(String.valueOf(orderId), miniOpenId, order.getTotalAmount()); // PrepayWithRequestPaymentResponse response = weChatService.createPayment(String.valueOf(orderId), miniOpenId, order.getTotalAmount());
return ResultUtils.success(response); // return ResultUtils.success(response);
} // }
//
/** // /**
* JSAPI 下单回调 // * JSAPI 下单回调
*/ // */
@PostMapping("/payment/callback") // @PostMapping("/payment/callback")
@Transactional(rollbackFor = Exception.class) // @Transactional(rollbackFor = Exception.class)
public synchronized BaseResponse<Boolean> callbackPayment(HttpServletRequest request) throws IOException { // public synchronized BaseResponse<Boolean> callbackPayment(HttpServletRequest request) throws IOException {
// 获取下单信息 // // 获取下单信息
Transaction transaction = weChatService.getTransactionInfo(request); // Transaction transaction = weChatService.getTransactionInfo(request);
System.out.println("下单信息:" + transaction); // System.out.println("下单信息:" + transaction);
// 支付回调 // // 支付回调
boolean result = weChatService.paymentCallback(transaction); // boolean result = weChatService.paymentCallback(transaction);
ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "微信支付回调失败"); // ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "微信支付回调失败");
return ResultUtils.success(true); // return ResultUtils.success(true);
} // }
//
/** // /**
* 退款仅管理员和商家 // * 退款仅管理员和商家
*/ // */
@PostMapping("/refund/create") // @PostMapping("/refund/create")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE) // @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Refund> createRefund(@RequestBody CommonRequest commonRequest) { // public BaseResponse<Refund> createRefund(@RequestBody CommonRequest commonRequest) {
Long orderId = commonRequest.getId(); // Long orderId = commonRequest.getId();
Order order = ordersService.getById(orderId); // Order order = ordersService.getById(orderId);
ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在"); // ThrowUtils.throwIf(order == null, ErrorCode.NOT_FOUND_ERROR, "订单不存在");
Refund refund = weChatService.refundPayment(String.valueOf(orderId), order.getTotalAmount()); // Refund refund = weChatService.refundPayment(String.valueOf(orderId), order.getTotalAmount());
return ResultUtils.success(refund); // return ResultUtils.success(refund);
} // }
//
/** // /**
* 退款回调 // * 退款回调
*/ // */
@PostMapping("/refund/callback") // @PostMapping("/refund/callback")
public BaseResponse<Boolean> callbackRefund(HttpServletRequest request) { // public BaseResponse<Boolean> callbackRefund(HttpServletRequest request) {
// 获取退款信息 // // 获取退款信息
RefundNotification refundNotification = weChatService.getRefundInfo(request); // RefundNotification refundNotification = weChatService.getRefundInfo(request);
// 退款回调 // // 退款回调
boolean result = weChatService.refundCallback(refundNotification); // boolean result = weChatService.refundCallback(refundNotification);
ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "退款回调失败"); // ThrowUtils.throwIf(!result, ErrorCode.SYSTEM_ERROR, "退款回调失败");
return ResultUtils.success(true); // return ResultUtils.success(true);
} // }
//
/** // /**
* 发送订阅消息 // * 发送订阅消息
*/ // */
@GetMapping("/refund/callback") // @GetMapping("/refund/callback")
public BaseResponse<Boolean> testSendMessage() { // public BaseResponse<Boolean> testSendMessage() {
String miniOpenId = "o0o_B5CMLFiOs96dJZwtkyHcJzcM"; // String miniOpenId = "o0o_B5CMLFiOs96dJZwtkyHcJzcM";
String templateId = "MK13FfX0XxsPV6m1vi6J8_8Bf7JT8rsayFs9q3f4FW4"; // String templateId = "MK13FfX0XxsPV6m1vi6J8_8Bf7JT8rsayFs9q3f4FW4";
boolean subscribeMessage = weChatService.sendSubscribeMessage(miniOpenId, templateId); // boolean subscribeMessage = weChatService.sendSubscribeMessage(miniOpenId, templateId);
return ResultUtils.success(subscribeMessage); // return ResultUtils.success(subscribeMessage);
} // }
//
} //}

View File

@ -0,0 +1,43 @@
package com.cultural.heritage.controller.wxPayDemo;
import jakarta.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
/**
* @author weikai
*/
public class HttpUtils {
/**
* 将通知参数转化为字符串
* @param request
* @return
*/
public static String readData(HttpServletRequest request) {
BufferedReader br = null;
try {
StringBuilder result = new StringBuilder();
br = request.getReader();
for (String line; (line = br.readLine()) != null; ) {
if (result.length() > 0) {
result.append("\n");
}
result.append(line);
}
return result.toString();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,110 @@
package com.cultural.heritage.controller.wxPayDemo;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
public class WechatPay2ValidatorForRequest {
protected static final Logger log = LoggerFactory.getLogger(WechatPay2ValidatorForRequest.class);
/**
* 应答超时时间单位为分钟
*/
protected static final long RESPONSE_EXPIRED_MINUTES = 5;
protected final Verifier verifier;
protected final String requestId;
protected final String body;
public WechatPay2ValidatorForRequest(Verifier verifier, String requestId, String body) {
this.verifier = verifier;
this.requestId = requestId;
this.body = body;
}
protected static IllegalArgumentException parameterError(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("parameter error: " + message);
}
protected static IllegalArgumentException verifyFail(String message, Object... args) {
message = String.format(message, args);
return new IllegalArgumentException("signature verify fail: " + message);
}
public final boolean validate(HttpServletRequest request) throws IOException {
try {
//处理请求参数
validateParameters(request);
//构造验签名串
String message = buildMessage(request);
String serial = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
//验签
if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
serial, message, signature, requestId);
}
} catch (IllegalArgumentException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
protected final void validateParameters(HttpServletRequest request) {
// NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};
String header = null;
for (String headerName : headers) {
header = request.getHeader(headerName);
if (header == null) {
throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
}
}
//判断请求是否过期
String timestampStr = header;
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期请求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
}
} catch (DateTimeException | NumberFormatException e) {
throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
}
}
protected final String buildMessage(HttpServletRequest request) throws IOException {
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
protected final String getResponseBody(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
}
}

View File

@ -0,0 +1,55 @@
package com.cultural.heritage.controller.wxPayDemo;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum WxApiType {
/**
*
*/
/**
* Native下单
*/
NATIVE_PAY("/v3/pay/transactions/native"),
/**
* 查询订单
*/
ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),
/**
* 关闭订单
*/
CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),
/**
* 申请退款
*/
DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),
/**
* 查询单笔退款
*/
DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),
/**
* 申请交易账单
*/
TRADE_BILLS("/v3/bill/tradebill"),
/**
* 申请资金账单
*/
FUND_FLOW_BILLS("/v3/bill/fundflowbill");
/**
* 类型
*/
private final String type;
}

View File

@ -0,0 +1,26 @@
package com.cultural.heritage.controller.wxPayDemo;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum WxNotifyType {
/**
* 支付通知
*/
NATIVE_NOTIFY("/api/wx-pay/notify/native"),
/**
* 退款结果通知
*/
REFUND_NOTIFY("/api/wx-pay/notify/refunds");
/**
* 类型
*/
private final String type;
}

View File

@ -0,0 +1,142 @@
package com.cultural.heritage.controller.wxPayDemo;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
@Configuration
@ConfigurationProperties(prefix = "wx.pay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyPath;
// APIv3密钥
private String apiV3Key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址
private String notifyDomain;
/**
* 获取商户的私钥文件
*
* @param filename
* @return
*/
private PrivateKey getPrivateKey(String filename) {
try {
return PemUtil.loadPrivateKey(new FileInputStream(filename));
} catch (FileNotFoundException e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
/**
* 获取签名验证器
*
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier() {
log.info("获取签名验证器");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
// 使用定时更新的签名验证器不需要传入证书
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
apiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
/**
* 获取http请求对象
*
* @param verifier
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
log.info("获取httpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来你仍然可以通过builder设置各种参数来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient会自动的处理签名和验签并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 获取HttpClient无需进行应答签名验证跳过验签的流程
*/
@Bean(name = "wxPayNoSignClient")
public CloseableHttpClient getWxPayNoSignClient() {
log.info("无需进行应答签名验证,获取httpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//用于构造HttpClient
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//设置商户信息
.withMerchant(mchId, mchSerialNo, privateKey)
//无需进行签名验证通过withValidator((response) -> true)实现
.withValidator((response) -> true);
// 通过WechatPayHttpClientBuilder构造的HttpClient会自动的处理签名和验签并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
}

View File

@ -0,0 +1,361 @@
package com.cultural.heritage.controller.wxPayDemo;
import com.google.gson.Gson;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/wx-pay")
@Slf4j
public class WxPayController {
@Resource
private CloseableHttpClient wxPayClient;
@Resource
//无需应答签名
private CloseableHttpClient wxPayNoSignClient;
@Resource
private WxPayConfig wxPayConfig;
/**
* Native下单
*/
@PostMapping("/native")
public void nativePay() throws Exception {
log.info("发起支付请求 v3");
log.info("调用统一下单API");
//调用统一下单API
HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
// 请求body参数
Gson gson = new Gson();
HashMap<String, Object> paramsMap = new HashMap<>();
paramsMap.put("appid", wxPayConfig.getAppid());// APPID
paramsMap.put("mchid", wxPayConfig.getMchId());// 商户id
paramsMap.put("description", "魏凯的小商铺"); // 订单描述
paramsMap.put("out_trade_no", "123456789987"); // 订单号
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));// 二维码扫描支付成功后进行回调
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("total", 1); // 金额 以分为单位 这里写1分钱 订单号123456789987对应的金额为1分
amountMap.put("currency", "CNY");
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}" + jsonParams);
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求这个地方就是mchId与商户证书密钥进行校验
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode == 200) { //处理成功
log.info("成功, 返回结果 = " + bodyAsString);
} else if (statusCode == 204) { //处理成功无返回Body
log.info("成功");
} else {
log.info("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
//响应结果
HashMap resultMap = gson.fromJson(bodyAsString, HashMap.class);
//二维码
String codeUrl = (String) resultMap.get("code_url");
log.info("二维码为: " + codeUrl);
}
}
/**
* 查询订单
*/
@GetMapping("/query/{orderNo}")
public void queryOrder(@PathVariable String orderNo) throws Exception {
log.info("查询订单");
log.info("查单接口调用 ===> {}", orderNo);
// 拼接url
String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode == 200) { //处理成功
log.info("成功, 返回结果 = " + bodyAsString);
} else if (statusCode == 204) { //处理成功无返回Body
log.info("成功");
} else {
log.info("查单接口调用,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
throw new IOException("request failed");
}
}
}
/**
* 用户取消订单
*/
@PostMapping("/cancel/{orderNo}")
public void cancel(@PathVariable String orderNo) throws Exception {
log.info("关单接口的调用,订单号 ===> {}", orderNo);
//创建远程请求对象
String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
url = wxPayConfig.getDomain().concat(url);
HttpPost httpPost = new HttpPost(url);
//组装json请求体
Gson gson = new Gson();
Map<String, String> paramsMap = new HashMap<>();
paramsMap.put("mchid", wxPayConfig.getMchId());
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}", jsonParams);
//将请求参数设置到请求对象中
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
int statusCode = response.getStatusLine().getStatusCode();//响应状态码
if (statusCode == 200) { //处理成功
log.info("成功200");
} else if (statusCode == 204) { //处理成功无返回Body
log.info("成功204");
} else {
log.info("Native下单失败,响应码 = " + statusCode);
throw new IOException("request failed");
}
}
}
/**
* 用户申请退款
*/
@PostMapping("/refunds/{orderNo}")
public void refunds(@PathVariable String orderNo) throws Exception {
log.info("申请退款");
log.info("调用退款API");
//调用统一下单API
String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
HttpPost httpPost = new HttpPost(url);
// 请求body参数
Gson gson = new Gson();
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("out_trade_no", orderNo);//订单编号
paramsMap.put("out_refund_no", "123456");//退款单编号 随便填
paramsMap.put("reason", "随便填一个");//退款原因
paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款成功通知地址
Map<String, java.io.Serializable> amountMap = new HashMap<String, java.io.Serializable>();
amountMap.put("refund", 1);//退款金额 这里的金额应该根据订单id查询出来
amountMap.put("total", 1);//原订单金额 这里的金额应该根据订单id查询出来
amountMap.put("currency", "CNY");//退款币种
paramsMap.put("amount", amountMap);
//将参数转换成json字符串
String jsonParams = gson.toJson(paramsMap);
log.info("请求参数 ===> {}" + jsonParams);
StringEntity entity = new StringEntity(jsonParams, "utf-8");
entity.setContentType("application/json");//设置请求报文格式
httpPost.setEntity(entity);//将请求报文放入请求对象
httpPost.setHeader("Accept", "application/json");//设置响应报文格式
//完成签名并执行请求并完成验签
try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
//解析响应结果
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 退款返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);
}
}
}
/**
* 查询退款
*/
@GetMapping("/query-refund/{refundNo}")
public void queryRefund(@PathVariable String refundNo) throws Exception {
log.info("查询退款");
log.info("查询退款接口调用 ===> {}", refundNo);
String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
url = wxPayConfig.getDomain().concat(url);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 查询退款返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("查询退款异常, 响应码 = " + statusCode + ", 查询退款返回结果 = " + bodyAsString);
}
}
}
/**
* 获取账单url 这个url无法在浏览器打开
*/
@GetMapping("/querybill/{billDate}/{type}")
public void queryTradeBill(@PathVariable String billDate, @PathVariable String type) throws Exception {
log.info("获取账单url");
log.warn("申请账单接口调用 {}", billDate);
String url = "";
if ("tradebill".equals(type)) {
url = WxApiType.TRADE_BILLS.getType();
} else if ("fundflowbill".equals(type)) {
url = WxApiType.FUND_FLOW_BILLS.getType();
} else {
throw new RuntimeException("不支持的账单类型");
}
url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 申请账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("申请账单异常, 响应码 = " + statusCode + ", 申请账单返回结果 = " + bodyAsString);
}
//获取账单下载地址
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
log.info("账单链接;" + resultMap.get("download_url"));
}
}
/**
* 下载账单
*/
@GetMapping("/downloadbill/{billDate}/{type}")
public void downloadBill(@PathVariable String billDate, @PathVariable String type) throws Exception {
log.info("下载账单");
log.warn("下载账单接口调用 {}, {}", billDate, type);
//获取账单url地址
String downloadUrl = this.queryBill(billDate, type);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(downloadUrl);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
try (CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 下载账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("下载账单异常, 响应码 = " + statusCode + ", 下载账单返回结果 = " + bodyAsString);
}
}
}
/**
* 申请账单
*/
public String queryBill(String billDate, String type) throws Exception {
log.warn("申请账单接口调用 {}", billDate);
String url = "";
if ("tradebill".equals(type)) {
url = WxApiType.TRADE_BILLS.getType();
} else if ("fundflowbill".equals(type)) {
url = WxApiType.FUND_FLOW_BILLS.getType();
} else {
throw new RuntimeException("不支持的账单类型");
}
url = wxPayConfig.getDomain().concat(url).concat("?bill_date=").concat(billDate);
//创建远程Get 请求对象
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader("Accept", "application/json");
//使用wxPayClient发送请求得到响应
try (CloseableHttpResponse response = wxPayClient.execute(httpGet)) {
String bodyAsString = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
log.info("成功, 申请账单返回结果 = " + bodyAsString);
} else if (statusCode == 204) {
log.info("成功");
} else {
throw new RuntimeException("申请账单异常, 响应码 = " + statusCode + ", 申请账单返回结果 = " + bodyAsString);
}
//获取账单下载地址
Gson gson = new Gson();
Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
return resultMap.get("download_url");
}
}
}

View File

@ -0,0 +1,124 @@
package com.cultural.heritage.controller.wxPayDemo;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/api/wx-pay/notify")
@Slf4j
public class WxPayNotifyController {
@Resource
private Verifier verifier;
/**
* 订单支付成功回调
*/
@PostMapping("/native")
public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
Gson gson = new Gson();
Map<String, String> map = new HashMap<>();//应答对象
try {
//处理通知参数
String body = HttpUtils.readData(request);
Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String) bodyMap.get("id");
log.info("支付通知的id ===> {}", requestId);
//签名的验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
= new WechatPay2ValidatorForRequest(verifier, requestId, body);
if (!wechatPay2ValidatorForRequest.validate(request)) {
log.error("通知验签失败");
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
log.info("通知验签成功");
//处理订单 这里可以对订单进行处理 比如订单支付成功 修改数据库订单表订单状态为已支付
// processOrder(bodyMap);
//应答超时
//模拟接收微信端的重复通知
TimeUnit.SECONDS.sleep(5);
//成功应答
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "成功");
return gson.toJson(map);
} catch (Exception e) {
e.printStackTrace();
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "失败");
return gson.toJson(map);
}
}
@PostMapping("/refunds")
public String refundsNotify(HttpServletRequest request, HttpServletResponse response){
log.info("退款通知执行");
Gson gson = new Gson();
Map<String, String> map = new HashMap<>();//应答对象
try {
//处理通知参数
String body = HttpUtils.readData(request);
Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
String requestId = (String)bodyMap.get("id");
log.info("支付通知的id ===> {}", requestId);
//签名的验证
WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
= new WechatPay2ValidatorForRequest(verifier, requestId, body);
if(!wechatPay2ValidatorForRequest.validate(request)){
log.error("通知验签失败");
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "通知验签失败");
return gson.toJson(map);
}
log.info("通知验签成功");
//处理退款单 订单退款成功 修改订单表中订单状态为已退款
// processRefund(bodyMap);
//成功应答
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "成功");
return gson.toJson(map);
} catch (Exception e) {
e.printStackTrace();
//失败应答
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "失败");
return gson.toJson(map);
}
}
}

View File

@ -1,53 +1,53 @@
package com.cultural.heritage.service.wxpay; //package com.cultural.heritage.service.wxpay;
//
//
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; //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.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund; //import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification; //import com.wechat.pay.java.service.refund.model.RefundNotification;
import jakarta.servlet.http.HttpServletRequest; //import jakarta.servlet.http.HttpServletRequest;
//
import java.io.IOException; //import java.io.IOException;
import java.math.BigDecimal; //import java.math.BigDecimal;
//
/** ///**
* @author 玄德 // * @author 玄德
*/ // */
public interface WeChatService { //public interface WeChatService {
//
/** // /**
* 微信支付 // * 微信支付
*/ // */
PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount); // PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount);
//
/** // /**
* 获取支付回调信息 // * 获取支付回调信息
*/ // */
Transaction getTransactionInfo(HttpServletRequest request); // Transaction getTransactionInfo(HttpServletRequest request);
//
/** // /**
* 支付回调 // * 支付回调
*/ // */
boolean paymentCallback(Transaction transaction) throws IOException; // boolean paymentCallback(Transaction transaction) throws IOException;
//
/** // /**
* 退款申请 // * 退款申请
*/ // */
Refund refundPayment(String orderId, BigDecimal amount); // Refund refundPayment(String orderId, BigDecimal amount);
//
/** // /**
* 获取退款回调信息 // * 获取退款回调信息
*/ // */
RefundNotification getRefundInfo(HttpServletRequest request); // RefundNotification getRefundInfo(HttpServletRequest request);
//
/** // /**
* 退款回调 // * 退款回调
*/ // */
boolean refundCallback(RefundNotification refundNotification); // boolean refundCallback(RefundNotification refundNotification);
//
/** // /**
* 发送订阅模板消息 // * 发送订阅模板消息
*/ // */
boolean sendSubscribeMessage(String openid, String templateId); // boolean sendSubscribeMessage(String openid, String templateId);
//
} //}

View File

@ -1,265 +1,265 @@
package com.cultural.heritage.service.wxpay.impl; //package com.cultural.heritage.service.wxpay.impl;
//
//
import cn.binarywang.wx.miniapp.api.WxMaMsgService; //import cn.binarywang.wx.miniapp.api.WxMaMsgService;
import cn.binarywang.wx.miniapp.api.WxMaService; //import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; //import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
import cn.hutool.core.util.RandomUtil; //import cn.hutool.core.util.RandomUtil;
import com.cultural.heritage.common.ErrorCode; //import com.cultural.heritage.common.ErrorCode;
import com.cultural.heritage.config.WxOpenConfig; //import com.cultural.heritage.config.WxOpenConfig;
import com.cultural.heritage.config.WxPayConfig; //import com.cultural.heritage.config.WxPayConfig;
import com.cultural.heritage.exception.BusinessException; //import com.cultural.heritage.exception.BusinessException;
import com.cultural.heritage.model.entity.Order; //import com.cultural.heritage.model.entity.Order;
import com.cultural.heritage.service.order.OrderService; //import com.cultural.heritage.service.order.OrderService;
import com.cultural.heritage.service.wxpay.WeChatService; //import com.cultural.heritage.service.wxpay.WeChatService;
import com.wechat.pay.java.core.notification.NotificationParser; //import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam; //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.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer; //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.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; //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.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.AmountReq; //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.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund; //import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification; //import com.wechat.pay.java.service.refund.model.RefundNotification;
import jakarta.annotation.Resource; //import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest; //import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows; //import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; //import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; //import org.springframework.transaction.annotation.Transactional;
//
import java.io.BufferedReader; //import java.io.BufferedReader;
import java.io.IOException; //import java.io.IOException;
import java.math.BigDecimal; //import java.math.BigDecimal;
import java.time.LocalDate; //import java.time.LocalDate;
import java.time.LocalDateTime; //import java.time.LocalDateTime;
import java.time.LocalTime; //import java.time.LocalTime;
import java.util.ArrayList; //import java.util.ArrayList;
import java.util.List; //import java.util.List;
//
/** ///**
* @author 玄德 // * @author 玄德
*/ // */
@Slf4j //@Slf4j
@Service //@Service
public class WeChatServiceImpl implements WeChatService { //public class WeChatServiceImpl implements WeChatService {
//
@Resource // @Resource
private WxPayConfig wxPayConfig; // private WxPayConfig wxPayConfig;
//
@Resource // @Resource
private WxOpenConfig wxOpenConfig; // private WxOpenConfig wxOpenConfig;
//
@Resource // @Resource
private OrderService ordersService; // private OrderService ordersService;
//
/** // /**
* 请求参数 // * 请求参数
*/ // */
public static RequestParam requestParam = null; // public static RequestParam requestParam = null;
//
@Override // @Override
@Transactional(rollbackFor = Exception.class) // @Transactional(rollbackFor = Exception.class)
public synchronized PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount) { // public synchronized PrepayWithRequestPaymentResponse createPayment(String orderId, String miniOpenId, BigDecimal amount) {
// request.setXxx(val)设置所需参数具体参数可见Request定义 // // request.setXxx(val)设置所需参数具体参数可见Request定义
PrepayRequest request = new PrepayRequest(); // PrepayRequest request = new PrepayRequest();
// 金额 // // 金额
Amount WxAmount = new Amount(); // Amount WxAmount = new Amount();
WxAmount.setTotal(amount.movePointRight(2).intValue()); // WxAmount.setTotal(amount.movePointRight(2).intValue());
WxAmount.setCurrency("CNY"); // WxAmount.setCurrency("CNY");
request.setAmount(WxAmount); // request.setAmount(WxAmount);
// 公众号id // // 公众号id
request.setAppid(wxPayConfig.getAppId()); // request.setAppid(wxPayConfig.getAppId());
// 商户号 // // 商户号
request.setMchid(wxPayConfig.getMerchantId()); // request.setMchid(wxPayConfig.getMerchantId());
// 支付者信息 // // 支付者信息
Payer payer = new Payer(); // Payer payer = new Payer();
payer.setOpenid(miniOpenId); // payer.setOpenid(miniOpenId);
request.setPayer(payer); // request.setPayer(payer);
// 描述 // // 描述
request.setDescription("订单号:" + orderId); // request.setDescription("订单号:" + orderId);
// 微信回调地址 // // 微信回调地址
request.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/payment/callback"); // request.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/payment/callback");
// 商户订单号 // // 商户订单号
request.setOutTradeNo(RandomUtil.randomNumbers(12)); // request.setOutTradeNo(RandomUtil.randomNumbers(12));
//返回数据前端调起支付 // //返回数据前端调起支付
return wxPayConfig.getJsapiServiceExtension().prepayWithRequestPayment(request); // return wxPayConfig.getJsapiServiceExtension().prepayWithRequestPayment(request);
} // }
//
@Override // @Override
public Transaction getTransactionInfo(HttpServletRequest request) { // public Transaction getTransactionInfo(HttpServletRequest request) {
NotificationParser notificationParser = getNotificationParser(request); // NotificationParser notificationParser = getNotificationParser(request);
return notificationParser.parse(requestParam, Transaction.class); // return notificationParser.parse(requestParam, Transaction.class);
} // }
//
@Override // @Override
@Transactional(rollbackFor = Exception.class) // @Transactional(rollbackFor = Exception.class)
public synchronized boolean paymentCallback(Transaction transaction) throws IOException { // public synchronized boolean paymentCallback(Transaction transaction) throws IOException {
System.out.println("---------------------------微信支付回调(开始)-------------------------------"); // System.out.println("---------------------------微信支付回调(开始)-------------------------------");
// 获取订单信息 // // 获取订单信息
String orderIdByString = transaction.getOutTradeNo(); // String orderIdByString = transaction.getOutTradeNo();
Order order = ordersService.getById(orderIdByString); // Order order = ordersService.getById(orderIdByString);
if (order == null) { // if (order == null) {
log.error("订单不存在"); // log.error("订单不存在");
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "订单不存在,订单号:" + transaction.getOutTradeNo()); // throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "订单不存在,订单号:" + transaction.getOutTradeNo());
} // }
// 生成取餐码
// 获取当日的日期
LocalDate today = LocalDate.now();
// 获取当日的 00:00 时间
LocalDateTime startTime = today.atStartOfDay();
// 获取当日的 23:59 时间
LocalDateTime endTime = today.atTime(LocalTime.MAX);
// // 生成取餐码 // // 生成取餐码
// QueryWrapper<Orders> queryWrapper = new QueryWrapper<>(); // // 获取当日的日期
// queryWrapper.eq("businessId", order.getBusinessId()); // LocalDate today = LocalDate.now();
// queryWrapper.between("createTime", startTime, endTime); // // 获取当日的 00:00 时间
// queryWrapper.in("state", 1, 2); // LocalDateTime startTime = today.atStartOfDay();
// long count = ordersService.count(queryWrapper); // // 获取当日的 23:59 时间
// String pickupCode = String.format("%04d", count + 1); // LocalDateTime endTime = today.atTime(LocalTime.MAX);
//// // 生成取餐码
//// QueryWrapper<Orders> queryWrapper = new QueryWrapper<>();
//// queryWrapper.eq("businessId", order.getBusinessId());
//// queryWrapper.between("createTime", startTime, endTime);
//// queryWrapper.in("state", 1, 2);
//// long count = ordersService.count(queryWrapper);
//// String pickupCode = String.format("%04d", count + 1);
//// // 修改订单信息
//// order.setState(1);
//// order.setPickupCode(pickupCode);
//// Date date = new Date();
//// order.setUpdateTime(date);
//// boolean update = ordersService.updateById(order);
//// ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "修改订单状态失败");
//// // 通知商家新订单
//// Business business = businessService.getById(order.getBusinessId());
//// Long userId = order.getUserId();
//// Long businessId = business.getUserId();
//// MessageVO messageVO = new MessageVO();
//// messageVO.setType(1);
//// messageVO.setUserId(userId);
//// messageVO.setToUserId(businessId);
//// messageVO.setContent(orderIdByString);
//// String message = JSONUtil.toJsonStr(messageVO);
//// // 通知商家新订单
//// webSocketServer.onMessage(message);
// System.out.println("---------------------------微信支付回调(结束)-------------------------------");
// return true;
// }
//
// @Override
// public Refund refundPayment(String orderId, BigDecimal amount) {
// // 退款请求
// CreateRequest createRequest = new CreateRequest();
// // 商户订单号
// createRequest.setOutTradeNo(orderId);
// // 商户退款单号
// createRequest.setOutRefundNo(orderId);
// // 退款结果回调
// createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/refund/callback");
// // 退款金额
// AmountReq amountReq = new AmountReq();
// long refundAmount = amount.movePointRight(2).intValue();
// amountReq.setRefund(refundAmount);
// amountReq.setTotal(refundAmount);
// amountReq.setCurrency("CNY");
// createRequest.setAmount(amountReq);
// // 申请退款
// System.out.println("退款请求:" + createRequest);
// Refund refund = wxPayConfig.getRefundService().create(createRequest);
// System.out.println("退款申请结果:" + refund);
// return refund;
// }
//
// @Override
// public RefundNotification getRefundInfo(HttpServletRequest request) {
// NotificationParser notificationParser = getNotificationParser(request);
// return notificationParser.parse(requestParam, RefundNotification.class);
// }
//
// @Override
// @Transactional(rollbackFor = Exception.class)
// public synchronized boolean refundCallback(RefundNotification refundNotification) {
// System.out.println("---------------------------微信退款回调(开始)-------------------------------");
// // 获取订单信息
// String orderIdByString = refundNotification.getOutTradeNo();
// Order order = ordersService.getById(orderIdByString);
// // 修改订单信息 // // 修改订单信息
// order.setState(1); // order.setOrderStatus("已退款");
// order.setPickupCode(pickupCode); // ordersService.updateById(order);
// Date date = new Date(); // System.out.println("---------------------------微信退款回调(结束)-------------------------------");
// order.setUpdateTime(date); // return true;
// boolean update = ordersService.updateById(order); // }
// ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "修改订单状态失败"); //
// // 通知商家新订单 // @Override
// Business business = businessService.getById(order.getBusinessId()); // public boolean sendSubscribeMessage(String openid, String templateId) {
// Long userId = order.getUserId(); // try {
// Long businessId = business.getUserId(); // // 获取小程序对象
// MessageVO messageVO = new MessageVO(); // WxMaService wxMaService = wxOpenConfig.getWxMaService();
// messageVO.setType(1); // // 获取消息接口
// messageVO.setUserId(userId); // WxMaMsgService msgService = wxMaService.getMsgService();
// messageVO.setToUserId(businessId); // // 构建订阅消息体
// messageVO.setContent(orderIdByString); // WxMaSubscribeMessage wxMaSubscribeMessage = new WxMaSubscribeMessage();
// String message = JSONUtil.toJsonStr(messageVO); // wxMaSubscribeMessage.setToUser(openid);
// // 通知商家新订单 // wxMaSubscribeMessage.setTemplateId(templateId);
// webSocketServer.onMessage(message); // List<WxMaSubscribeMessage.MsgData> msgDataList = getMsgData();
System.out.println("---------------------------微信支付回调(结束)-------------------------------"); // wxMaSubscribeMessage.setData(msgDataList);
return true; // // 发送消息
} // msgService.sendSubscribeMsg(wxMaSubscribeMessage);
// return true;
@Override // } catch (Exception e) {
public Refund refundPayment(String orderId, BigDecimal amount) { // log.error("发送订阅消息失败:", e);
// 退款请求 // throw new BusinessException(ErrorCode.SYSTEM_ERROR, "发送订阅消息失败");
CreateRequest createRequest = new CreateRequest(); // }
// 商户订单号 // }
createRequest.setOutTradeNo(orderId); //
// 商户退款单号 // /**
createRequest.setOutRefundNo(orderId); // * 提取消息数据
// 退款结果回调 // */
createRequest.setNotifyUrl(wxPayConfig.getNotifyUrl() + "/api/wechat/refund/callback"); // private static List<WxMaSubscribeMessage.MsgData> getMsgData() {
// 退款金额 // List<WxMaSubscribeMessage.MsgData> msgDataList = new ArrayList<>();
AmountReq amountReq = new AmountReq(); // String[][] dataItems = {
long refundAmount = amount.movePointRight(2).intValue(); // {"thing1", "新订单"},
amountReq.setRefund(refundAmount); // {"phrase2", "已支付"},
amountReq.setTotal(refundAmount); // {"amount3", "666"},
amountReq.setCurrency("CNY"); // {"thing10", "666666"},
createRequest.setAmount(amountReq); // {"time8", "2019-12-18 12:12:12"}
// 申请退款 // };
System.out.println("退款请求:" + createRequest); // for (String[] item : dataItems) {
Refund refund = wxPayConfig.getRefundService().create(createRequest); // WxMaSubscribeMessage.MsgData msgData = new WxMaSubscribeMessage.MsgData();
System.out.println("退款申请结果:" + refund); // msgData.setName(item[0]);
return refund; // msgData.setValue(item[1]);
} // msgDataList.add(msgData);
// }
@Override // return msgDataList;
public RefundNotification getRefundInfo(HttpServletRequest request) { // }
NotificationParser notificationParser = getNotificationParser(request); //
return notificationParser.parse(requestParam, RefundNotification.class); // /**
} // * 根据微信官方发送的请求获取信息
// */
@Override // @SneakyThrows
@Transactional(rollbackFor = Exception.class) // public NotificationParser getNotificationParser(HttpServletRequest request) {
public synchronized boolean refundCallback(RefundNotification refundNotification) { // System.out.println("---------------------------获取信息-------------------------------");
System.out.println("---------------------------微信退款回调(开始)-------------------------------"); // // 获取RSA配置
// 获取订单信息 // NotificationParser notificationParser = new NotificationParser(wxPayConfig.getRSAConfig());
String orderIdByString = refundNotification.getOutTradeNo(); // // 构建请求
Order order = ordersService.getById(orderIdByString); // StringBuilder bodyBuilder = new StringBuilder();
// 修改订单信息 // BufferedReader reader = request.getReader();
order.setOrderStatus("已退款"); // String line;
ordersService.updateById(order); // while ((line = reader.readLine()) != null) {
System.out.println("---------------------------微信退款回调(结束)-------------------------------"); // bodyBuilder.append(line);
return true; // }
} // String body = bodyBuilder.toString();
// String timestamp = request.getHeader("Wechatpay-Timestamp");
@Override // String nonce = request.getHeader("Wechatpay-Nonce");
public boolean sendSubscribeMessage(String openid, String templateId) { // String signature = request.getHeader("Wechatpay-Signature");
try { // String singType = request.getHeader("Wechatpay-Signature-Type");
// 获取小程序对象 // String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
WxMaService wxMaService = wxOpenConfig.getWxMaService(); // requestParam = new RequestParam.Builder()
// 获取消息接口 // .serialNumber(wechatPayCertificateSerialNumber)
WxMaMsgService msgService = wxMaService.getMsgService(); // .nonce(nonce)
// 构建订阅消息体 // .signature(signature)
WxMaSubscribeMessage wxMaSubscribeMessage = new WxMaSubscribeMessage(); // .timestamp(timestamp)
wxMaSubscribeMessage.setToUser(openid); // .signType(singType)
wxMaSubscribeMessage.setTemplateId(templateId); // .body(body)
List<WxMaSubscribeMessage.MsgData> msgDataList = getMsgData(); // .build();
wxMaSubscribeMessage.setData(msgDataList); // System.out.println(requestParam.toString());
// 发送消息 // System.out.println("---------------------------信息获取完毕-------------------------------");
msgService.sendSubscribeMsg(wxMaSubscribeMessage); // return notificationParser;
return true; // }
} catch (Exception e) { //}
log.error("发送订阅消息失败:", e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "发送订阅消息失败");
}
}
/**
* 提取消息数据
*/
private static List<WxMaSubscribeMessage.MsgData> getMsgData() {
List<WxMaSubscribeMessage.MsgData> msgDataList = new ArrayList<>();
String[][] dataItems = {
{"thing1", "新订单"},
{"phrase2", "已支付"},
{"amount3", "666"},
{"thing10", "666666"},
{"time8", "2019-12-18 12:12:12"}
};
for (String[] item : dataItems) {
WxMaSubscribeMessage.MsgData msgData = new WxMaSubscribeMessage.MsgData();
msgData.setName(item[0]);
msgData.setValue(item[1]);
msgDataList.add(msgData);
}
return msgDataList;
}
/**
* 根据微信官方发送的请求获取信息
*/
@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;
}
}

View File

@ -70,17 +70,19 @@ wx:
#应用id小程序id #应用id小程序id
appId: wx61b63e27bddf4ea2 appId: wx61b63e27bddf4ea2
#商户号 #商户号
merchantId: 1700326544 #merchantId: 1700326544
mchSerialNo: 1700326544
#商户API私钥 #商户API私钥
privateKeyPath: apiclient_key.pem privateKeyPath: apiclient_key.pem
#商户证书序列号 #商户证书序列号
merchantSerialNumber: 6DC8953AB741D309920DA650B92F837BE38A2757 merchantSerialNumber: 6DC8953AB741D309920DA650B92F837BE38A2757
#商户APIv3密钥 #商户APIv3密钥
apiV3Key: fbemuj4Xql7CYlQJAoTEPYxvPSNgYT2t apiV3Key: fbemuj4Xql7CYlQJAoTEPYxvPSNgYT2t
# 通知地址 #通知地址
notifyUrl: https://winning-mouse-internally.ngrok-free.app notifyUrl: https://winning-mouse-internally.ngrok-free.app
# notifyUrl: http://localhost:9092 #notifyUrl: http://localhost:9092
#微信服务器地址
domain: https://api.mch.weixin.qq.com
knife4j: knife4j: