Merge remote-tracking branch 'origin/main'

This commit is contained in:
attiya 2025-04-30 10:33:38 +08:00
commit da8e224fae
22 changed files with 415 additions and 63 deletions

View File

@ -9,7 +9,7 @@ spring:
time-zone: GMT+8
cloud:
nacos:
server-addr: 192.168.2.226:8848 # nacos
server-addr: 127.0.0.1:8848 # nacos
username: nacos
password: nacos
kafka:

View File

@ -1,4 +1,4 @@
package com.cdzy.ebikemaintenance.model.pojo;
package com.cdzy.ebikemaintenance.model.dto.request;
import lombok.Data;

View File

@ -16,7 +16,6 @@ import com.cdzy.ebikemaintenance.model.dto.response.*;
import com.cdzy.ebikemaintenance.model.pojo.*;
import com.cdzy.ebikemaintenance.service.EbikeBikeInfoService;
import com.cdzy.ebikemaintenance.service.EbikeBikeOrderService;
import com.cdzy.ebikemaintenance.service.EbikeScheduleWorkOrderExtensionService;
import com.cdzy.ebikemaintenance.utils.GeoCodingUtil;
import com.cdzy.ebikemaintenance.utils.MinioUtil;
import com.cdzy.ebikemaintenance.utils.RedisUtil;
@ -35,7 +34,6 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.common.errors.ResourceNotFoundException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@ -124,6 +122,8 @@ public class EbikeBikeInfoServiceImpl extends ServiceImpl<EbikeBikeInfoMapper, E
private EbikeDispatchRecordsMapper ebikeDispatchRecordsMapper;
@Resource
private EbikeScheduleWorkOrderExtensionMapper ebikeScheduleWorkOrderExtensionMapper;
@Autowired
private EbikeVehicleDispatchFileMapper ebikeVehicleDispatchFileMapper;
@Override
public EbikeBikeInfo getByBikeCode(String bikeCode) {
@ -1347,6 +1347,29 @@ public class EbikeBikeInfoServiceImpl extends ServiceImpl<EbikeBikeInfoMapper, E
public JsonResult<?> completeDeployment(Map<String,Object> param) {
String orderId = MapUtil.getStr(param, "orderId");
List<Map<String,Object>> fileLists = (List<Map<String,Object>>) param.get("fileLists");
//设置一个关联ID
if(fileLists!=null && fileLists.size()>0){
String after_dispatch_photo=UUID.randomUUID().toString();
EbikeScheduleWorkOrderExtension ebikeScheduleWorkOrderExtension = new EbikeScheduleWorkOrderExtension();
ebikeScheduleWorkOrderExtension.setAfterDispatchPhoto(after_dispatch_photo);
ebikeScheduleWorkOrderExtension.setId(orderId);
ebikeScheduleWorkOrderExtensionMapper.update(ebikeScheduleWorkOrderExtension);
for (Map<String,Object> fileList : fileLists) {
EbikeVehicleDispatchFile ebikeVehicleDispatchFile = new EbikeVehicleDispatchFile();
ebikeVehicleDispatchFile.setFileName(fileList.get("fileName") != null ? fileList.get("fileName").toString() : "");
ebikeVehicleDispatchFile.setFileSize(fileList.get("fileSize") != null ? fileList.get("fileSize").toString() : "");
ebikeVehicleDispatchFile.setFileType(fileList.get("fileType") != null ? fileList.get("fileType").toString() : "");
ebikeVehicleDispatchFile.setFileBucket(fileList.get("fileBucket") != null ? fileList.get("fileBucket").toString() : "");
ebikeVehicleDispatchFile.setFileUniqueKey(fileList.get("fileUniqueKey") != null ? fileList.get("fileUniqueKey").toString() : "");
ebikeVehicleDispatchFile.setCreatedAt(LocalDateTime.now());
ebikeVehicleDispatchFile.setUpdatedAt(LocalDateTime.now());
ebikeVehicleDispatchFile.setReportId(after_dispatch_photo);
ebikeVehicleDispatchFileMapper.insert(ebikeVehicleDispatchFile);
}
}
//根据orderId 完成订单
EbikeBikeOrder ebikeBikeOrder = new EbikeBikeOrder();
ebikeBikeOrder.setOrderId(orderId);

View File

@ -15,6 +15,7 @@ import com.cdzy.ebikeoperate.model.pojo.EbikeSysRcostsetWeek;
import com.cdzy.ebikeoperate.service.EbikeSysRcostsetService;
import com.cdzy.ebikeoperate.service.EbikeSysRcostsetTimePeriodService;
import com.cdzy.ebikeoperate.service.EbikeSysRcostsetWeekService;
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
import com.mybatisflex.core.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
@ -210,4 +211,17 @@ public class EbikeSysRcostsetController {
return JsonResult.failed("更新失败");
}
}
/**
* 生成一个雪花id
*
* @return
*/
@RequestMapping("createSnowflakeId")
public JsonResult<Object> createSnowflakeId() {
SnowFlakeIDKeyGenerator snowFlakeIDKeyGenerator = new SnowFlakeIDKeyGenerator();
long dataCenterId = snowFlakeIDKeyGenerator.nextId();
return JsonResult.success(dataCenterId);
}
}

View File

@ -1,5 +1,6 @@
package com.cdzy.ebikeoperate.controller;
import com.cdzy.common.model.JsonResult;
import com.cdzy.ebikeoperate.model.dto.request.ReqEbikeSysRoperatesetsDto;
import com.cdzy.ebikeoperate.model.dto.response.ResEbikeSysRoperatesetsDto;
import com.cdzy.ebikeoperate.model.pojo.EbikeSysRoperateset;
@ -27,8 +28,9 @@ public class EbikeSysRoperatesetController {
* @return {@code true} 添加成功{@code false} 添加失败
*/
@PostMapping("save")
public boolean save(@RequestBody ReqEbikeSysRoperatesetsDto reqEbikeSysRoperatesetsDto) {
return ebikeSysRoperatesetService.save(reqEbikeSysRoperatesetsDto);
public JsonResult<?> save(@RequestBody ReqEbikeSysRoperatesetsDto reqEbikeSysRoperatesetsDto) {
Boolean save = ebikeSysRoperatesetService.save(reqEbikeSysRoperatesetsDto);
return save ? JsonResult.success(true) : JsonResult.failed("");
}
/**
@ -43,9 +45,9 @@ public class EbikeSysRoperatesetController {
}
@RequestMapping("getRegionConfigById")
public ResEbikeSysRoperatesetsDto getRegionConfigById(@RequestParam(value = "regionId")
String regionId) {
public JsonResult<?> getRegionConfigById(@RequestParam(value = "regionId")
String regionId) {
ResEbikeSysRoperatesetsDto resEbikeSysRoperatesetsDto = new ResEbikeSysRoperatesetsDto();
return resEbikeSysRoperatesetsDto;
return JsonResult.success(null);
}
}

View File

@ -17,6 +17,9 @@ import java.util.List;
@Data
public class ReqEbikeSysRcostsetDto implements Serializable {
private String costConfigId;
/**
* 区域id
*/

View File

@ -4,6 +4,7 @@ import com.cdzy.ebikeoperate.mapper.EbikeSysLinktelMapper;
import com.cdzy.ebikeoperate.mapper.EbikeSysOperateSetMapper;
import com.cdzy.ebikeoperate.model.dto.request.ReqEbikeSysRoperatesetDto;
import com.cdzy.ebikeoperate.model.dto.request.ReqEbikeSysRoperatesetsDto;
import com.cdzy.ebikeoperate.model.pojo.EbikeSysRcostset;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.cdzy.ebikeoperate.model.pojo.EbikeSysRoperateset;
@ -11,6 +12,7 @@ import com.cdzy.ebikeoperate.mapper.EbikeSysRoperatesetMapper;
import com.cdzy.ebikeoperate.service.EbikeSysRoperatesetService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -63,7 +65,8 @@ public class EbikeSysRoperatesetServiceImpl extends ServiceImpl<EbikeSysRoperate
public Boolean save(ReqEbikeSysRoperatesetsDto reqEbikeSysRoperatesetsDto) {
ReqEbikeSysRoperatesetDto reqEbikeSysRoperatesetDto = reqEbikeSysRoperatesetsDto.getReqEbikeSysRoperatesetDto();
return null;
EbikeSysRoperateset ebikeSysRoperateset = new EbikeSysRoperateset();
BeanUtils.copyProperties(reqEbikeSysRoperatesetDto,ebikeSysRoperateset);
return true;
}
}

View File

@ -86,8 +86,8 @@ sa-token:
is-log: true
wechat:
url: https://api.weixin.qq.com/sns/jscode2session
appid: wx399ab8cf933bf77f
app-secret: 94947f6a23e456c89e47bb5f6eb32186
appid: wx327d788d7bd6eddf
app-secret: 9adf2539a6c26499c67b5a3829f2e05e3
minio:
endpoint: http://192.168.2.226:9000 # MinIO服务器地址
access-key: eQtGmQBEsGxNHrTd7AkJ # 访问密钥

View File

@ -110,6 +110,12 @@
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!-- for test only -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -8,7 +8,6 @@ import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.refund.RefundService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -57,12 +56,11 @@ public class WxPayConfig {
private String refundNotifyUrl;
/**
* 支付退款过期时间分钟
* 默认30分钟超过30分钟订单会自动关闭
* 默认5分钟超过5分钟订单会自动关闭
*/
private Integer expireMinute = 30;
private Integer expireMinute = 5;
@Bean("certConfig")
private Config certificateConfig() {
public Config certificateConfig() {
return new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
// 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥商户私钥会用来生成请求的签名
@ -73,12 +71,12 @@ public class WxPayConfig {
}
@Bean
public JsapiService wxJsapiService(@Qualifier("certConfig") Config certificateConfig) {
public JsapiService wxJsapiService(Config certificateConfig) {
return new JsapiService.Builder().config(certificateConfig).build();
}
@Bean
public RefundService wxRefundService(@Qualifier("certConfig") Config certificateConfig) {
public RefundService wxRefundService(Config certificateConfig) {
return new RefundService.Builder().config(certificateConfig).build();
}
@ -87,4 +85,5 @@ public class WxPayConfig {
PrivateKey privateKey = PemUtil.loadPrivateKeyFromPath(privateKeyPath);
return new RSASigner(merchantSerialNumber, privateKey);
}
}

View File

@ -4,10 +4,12 @@ import com.alibaba.fastjson2.JSONObject;
import com.cdzy.common.model.JsonResult;
import com.cdzy.payment.model.dto.EbikePaymentDto;
import com.cdzy.payment.model.dto.EbikeRefundDto;
import com.cdzy.payment.model.dto.HandleNotifyResult;
import com.cdzy.payment.service.WxPayService;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -15,12 +17,16 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 用户订单微信支付 控制层
*
* @author dingchao
* @since 2025-04-24
*/
@Slf4j
@RestController
@RequestMapping("/wxPayment")
public class EbikeWxPaymentController {
@ -87,4 +93,54 @@ public class EbikeWxPaymentController {
Refund r = wxPayService.queryRefundByOutNo(outRefundNo);
return r == null?JsonResult.failed(String.format("退款单号{%s}查询退款失败", outRefundNo)):JsonResult.success(r);
}
// ================通知回调接口===============
// TODO 通知接口不能鉴权
/**
* 支付回调通知
*
* @param request 支付回调请求
* @param response 支付回调响应
* @return 支付回调响应
*/
@PostMapping("/pay/notify")
public String payNotify(HttpServletRequest request, HttpServletResponse response) {
HandleNotifyResult r = wxPayService.handlePayNotify(request);
if(!r.isSuccess()) {
response.setStatus(500);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "SYSTEM_ERROR");
jsonObject.put("message", r.getMessage());
return jsonObject.toJSONString();
}
response.setStatus(200);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "SUCCESS");
jsonObject.put("message", "OK");
return jsonObject.toJSONString();
}
/**
* 退款回调通知
*
* @param request 退款回调请求
* @param response 退款回调响应
* @return 退款回调响应
*/
@PostMapping("/refund/notify")
public String refundNotify(HttpServletRequest request, HttpServletResponse response) {
HandleNotifyResult r = wxPayService.handleRefundNotify(request);
if(!r.isSuccess()) {
response.setStatus(500);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "SYSTEM_ERROR");
jsonObject.put("message", r.getMessage());
return jsonObject.toJSONString();
}
response.setStatus(200);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", "SUCCESS");
jsonObject.put("message", "OK");
return jsonObject.toJSONString();
}
}

View File

@ -0,0 +1,26 @@
package com.cdzy.payment.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 回调通知结果
*
* @author dingchao
* @date 2025/4/28
* @modified by:
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HandleNotifyResult {
/**
* 解析是否成功
*/
private boolean success = false;
/**
* 解析错误消息
*/
private String message;
}

View File

@ -37,7 +37,6 @@ public class EbikePayment implements Serializable {
/**
* 骑行订单号
*/
@Id
private String orderId;
/**

View File

@ -14,19 +14,27 @@ import java.util.List;
*/
public interface EbikePaymentService extends IService<EbikePayment> {
/**
* 查询未支付订单
* 查询未支付订单, 未超时的
*
* @param duration 订单创建时间超过duration分钟单位分钟
* @return 未支付订单列表
*/
List<EbikePayment> getNoPayOrderByDuration(int duration);
/**
* 查询未支付订单, 超时的
*
* @param duration 订单创建时间超过duration分钟单位分钟
* @return 未支付订单列表
*/
List<EbikePayment> getExpireOrderByDuration(int duration);
/**
* 更新支付状态
*
* @param recordId 记录ID
* @param transaction 支付结果
* @return 更新成功返回true否则返回false
*/
Boolean updatePaymentStatus(String recordId, Transaction transaction);
Boolean updatePaymentStatus(Transaction transaction);
}

View File

@ -33,9 +33,8 @@ public interface EbikeRefundService extends IService<EbikeRefund> {
/**
* 更新退款状态
*
* @param refundId 退款ID
* @param refund 退款结果
* @return 更新成功返回true否则返回false
*/
Boolean updateRefundStatus(String refundId, Refund refund);
Boolean updateRefundStatus(Refund refund);
}

View File

@ -5,9 +5,12 @@ import com.alibaba.fastjson2.JSONObject;
import com.cdzy.payment.model.dto.AmountDto;
import com.cdzy.payment.model.dto.AmountRefundDto;
import com.cdzy.payment.model.dto.DetailDto;
import com.cdzy.payment.model.dto.HandleNotifyResult;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund;
import jakarta.servlet.http.HttpServletRequest;
/**
* 微信支付服务类JSAPI支付小程序
*
@ -50,6 +53,15 @@ public interface WxPayService {
* @return 支付订单信息
*/
Transaction queryOrderByOutTradeNo(String outTradeNo);
/**
* 处理支付回调
*
* @param request 回调请求
* @return 支付订单信息
*/
HandleNotifyResult handlePayNotify(HttpServletRequest request);
/**
* 退款申请
*
@ -70,4 +82,12 @@ public interface WxPayService {
*/
Refund queryRefundByOutNo(String outRefundNo);
/**
* 处理支退款回调
*
* @param request 回调请求
* @return 退款订单信息
*/
HandleNotifyResult handleRefundNotify(HttpServletRequest request);
}

View File

@ -1,6 +1,7 @@
package com.cdzy.payment.service.impl;
import com.cdzy.payment.model.enums.PayMethod;
import com.cdzy.payment.utils.StringUtils;
import com.ebike.feign.clients.OrdersFeignClient;
import com.ebike.feign.model.res.ResFeignOrderPaymentDto;
import com.mybatisflex.core.query.QueryColumn;
@ -14,7 +15,6 @@ import com.wechat.pay.java.service.payments.model.Transaction;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import static com.cdzy.payment.model.entity.table.EbikePaymentTableDef.EBIKE_PAYMENT;
@ -33,24 +33,35 @@ public class EbikePaymentServiceImpl extends ServiceImpl<EbikePaymentMapper, Ebi
@Override
public List<EbikePayment> getNoPayOrderByDuration(int duration) {
QueryColumn time = QueryMethods.secToTime(String.valueOf(duration * 60));
// trade_state 等于 NOTPAY未支付的 并且创建时间未超过duration分钟的订单
QueryWrapper query = QueryWrapper.create()
.where(EBIKE_PAYMENT.TRADE_STATE.eq(Transaction.TradeStateEnum.USERPAYING.ordinal()))
.and(QueryMethods.addTime(EBIKE_PAYMENT.CREATE_TIME, time).lt(QueryMethods.now()));
return list(query);
}
@Override
public List<EbikePayment> getExpireOrderByDuration(int duration) {
QueryColumn time = QueryMethods.secToTime(String.valueOf(duration * 60));
// trade_state 等于 NOTPAY未支付的 并且创建时间超过duration分钟的订单
QueryWrapper query = QueryWrapper.create()
.where(EBIKE_PAYMENT.TRADE_STATE.eq(Transaction.TradeStateEnum.NOTPAY.ordinal()))
.where(EBIKE_PAYMENT.TRADE_STATE.eq(Transaction.TradeStateEnum.USERPAYING.ordinal()))
.and(QueryMethods.addTime(EBIKE_PAYMENT.CREATE_TIME, time).ge(QueryMethods.now()));
return list(query);
}
@Override
public Boolean updatePaymentStatus(String recordId, Transaction transaction) {
public Boolean updatePaymentStatus(Transaction transaction) {
EbikePayment ebikePayment = new EbikePayment();
ebikePayment.setRecordId(recordId);
//ebikePayment.setRecordId(recordId);
ebikePayment.setTradeState(String.valueOf(transaction.getTradeState().ordinal()));
if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
// 支付成功 更新订单状态
double v = transaction.getAmount().getTotal().doubleValue() / 100.0;
ebikePayment.setTotal(v);
ebikePayment.setPaymentTime(LocalDateTime.now());
String paymentTime = transaction.getSuccessTime();
ebikePayment.setPaymentTime(StringUtils.toLocalDatetime(paymentTime));
// 同步支付状态
ResFeignOrderPaymentDto paymentParam = new ResFeignOrderPaymentDto();
paymentParam.setOrderId(Long.valueOf(transaction.getOutTradeNo()));
@ -58,6 +69,8 @@ public class EbikePaymentServiceImpl extends ServiceImpl<EbikePaymentMapper, Ebi
paymentParam.setPaymentMethod(PayMethod.wechat.name());
ordersFeignClient.payment(paymentParam);
}
return updateById(ebikePayment);
QueryWrapper query = QueryWrapper.create()
.where(EBIKE_PAYMENT.PAYMENT_ID.eq(transaction.getTransactionId()));
return update(ebikePayment, query);
}
}

View File

@ -41,19 +41,21 @@ public class EbikeRefundServiceImpl extends ServiceImpl<EbikeRefundMapper, Ebike
public List<EbikeRefund> getNoSuccessRefundOrderByDuration(int duration) {
// trade_state 不等于 SUCCESS退款成功的 并且创建时间超过duration分钟的订单
QueryWrapper query = QueryWrapper.create()
.where(EBIKE_REFUND.STATUS.ne(Status.SUCCESS.ordinal()))
.where(EBIKE_REFUND.STATUS.eq(Status.PROCESSING.ordinal()))
.and(QueryMethods.addTime(EBIKE_REFUND.CREATE_TIME, QueryMethods.secToTime(String.valueOf(duration * 60))).ge(QueryMethods.now()));
return list(query);
}
@Override
public Boolean updateRefundStatus(String refundId, Refund refund) {
public Boolean updateRefundStatus(Refund refund) {
EbikeRefund ebikeRefund = new EbikeRefund();
ebikeRefund.setRefundId(refundId);
ebikeRefund.setRefundId(refund.getOutRefundNo());
ebikeRefund.setStatus(String.valueOf(refund.getStatus().ordinal()));
if (Status.SUCCESS.equals(refund.getStatus())) {
ebikeRefund.setRefundTime(LocalDateTime.now());
ebikeRefund.setRefund(refund.getAmount().getRefund().doubleValue() / 100.0);
// 退款成功 更新订单状态
ordersFeignClient.doneRefund(Long.valueOf(refund.getOutTradeNo()));
}
return updateById(ebikeRefund);
}

View File

@ -3,21 +3,24 @@ package com.cdzy.payment.service.impl;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSONObject;
import com.cdzy.payment.config.WxPayConfig;
import com.cdzy.payment.model.dto.AmountDto;
import com.cdzy.payment.model.dto.AmountRefundDto;
import com.cdzy.payment.model.dto.DetailDto;
import com.cdzy.payment.model.dto.WxJsapiPromptDto;
import com.cdzy.payment.model.dto.*;
import com.cdzy.payment.model.entity.EbikePayment;
import com.cdzy.payment.model.entity.EbikeRefund;
import com.cdzy.payment.model.enums.PayMethod;
import com.cdzy.payment.service.EbikePaymentService;
import com.cdzy.payment.service.EbikeRefundService;
import com.cdzy.payment.service.WxPayService;
import com.cdzy.payment.utils.StringUtil;
import com.cdzy.payment.utils.HttpServletUtils;
import com.cdzy.payment.utils.StringUtils;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.cipher.RSASigner;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.notification.NotificationConfig;
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.*;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.GoodsDetail;
@ -29,12 +32,12 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import jakarta.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.*;
import static com.wechat.pay.java.core.http.Constant.*;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SERIAL;
/**
* 微信支付服务类JSAPI支付小程序
@ -56,6 +59,8 @@ public class WxPayServiceImpl implements WxPayService {
@Resource
private RSASigner wxRsaSigner;
@Resource
private Config certificateConfig;
@Resource
private EbikePaymentService ebikePaymentService;
@Resource
private EbikeRefundService ebikeRefundService;
@ -68,7 +73,7 @@ public class WxPayServiceImpl implements WxPayService {
request.setOutTradeNo(outTradeNo);
wxJsapiService.closeOrder(request);
log.info("关闭订单成功,订单号:{}", outTradeNo);
//ebikePaymentService.updatePaymentStatus(outTradeNo, 2);
// ebikePaymentService.updatePaymentStatus(outTradeNo, 2);
return true;
} catch (Exception e) {
logError("关闭订单closeOrder", e);
@ -86,7 +91,7 @@ public class WxPayServiceImpl implements WxPayService {
request.setOutTradeNo(outTradeNo);
request.setNotifyUrl(wxPayConfig.getPayNotifyUrl());
LocalDateTime expireTime = LocalDateTime.now().plusMinutes(wxPayConfig.getExpireMinute());
request.setTimeExpire(StringUtil.formatLocalDatetime(expireTime));
request.setTimeExpire(StringUtils.formatLocalDatetime(expireTime));
request.setGoodsTag(goodsTag);
Amount amountReq = new Amount();
amountReq.setTotal(BigDecimal.valueOf(amount.getTotal() * 100.0).intValue());
@ -121,7 +126,7 @@ public class WxPayServiceImpl implements WxPayService {
ebikePayment.setCreateTime(LocalDateTime.now());
ebikePayment.setPaymentMethod(PayMethod.wechat.name());
ebikePayment.setCostPrice(amount.getTotal());
int state = Transaction.TradeStateEnum.NOTPAY.ordinal();
int state = Transaction.TradeStateEnum.USERPAYING.ordinal();
ebikePayment.setTradeState(String.valueOf(state));
ebikePaymentService.save(ebikePayment);
// 返回给小程序的参数调起微信支付
@ -164,11 +169,48 @@ public class WxPayServiceImpl implements WxPayService {
}
}
@Override
public HandleNotifyResult handlePayNotify(HttpServletRequest request) {
HandleNotifyResult result = new HandleNotifyResult();
// 1. 提取请求头参数
String requestId = request.getHeader(REQUEST_ID);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
String signatureType = request.getHeader(WECHAT_PAY_SIGNATURE+"-Type");
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
try {
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.signature(signature)
.signType(signatureType)
.timestamp(timestamp)
.body(HttpServletUtils.getRequestBody(request))
.nonce(nonce)
.build();
// 验签并解析通知
NotificationParser parser = new NotificationParser((NotificationConfig)certificateConfig);
// 解析解密后的通知
Transaction transaction = parser.parse(requestParam, Transaction.class);
// 更新状态
ebikePaymentService.updatePaymentStatus(transaction);
// 处理成功
result.setSuccess(true);
result.setMessage("success");
return result;
}catch (Exception e) {
result.setMessage(String.format("verify failed, request-id=[%s]", requestId));
logError("支付通知回调handlePayNotify", e);
return result;
}
}
@Override
public String refund(String transactionId, String outTradeNo, String reason, AmountRefundDto amount) {
try {
// 雪花算法生成退款单号
String outRefundNo = StringUtil.generateSnowflakeId("refundId");
String outRefundNo = StringUtils.generateSnowflakeId("refundId");
EbikeRefund ebikeRefund = new EbikeRefund();
ebikeRefund.setCreateTime(LocalDateTime.now());
// 发起退款
@ -193,7 +235,6 @@ public class WxPayServiceImpl implements WxPayService {
ebikeRefund.setReason(reason);
ebikeRefund.setStatus(String.valueOf(result.getStatus().ordinal()));
ebikeRefund.setCurrency(amount.getCurrency());
ebikeRefundService.saveRefundResult(ebikeRefund);
if(Status.SUCCESS.equals(result.getStatus())){
ebikeRefund.setRefund(result.getAmount().getRefund().doubleValue() / 100.0);
ebikeRefund.setRefundTime(LocalDateTime.now());
@ -221,6 +262,43 @@ public class WxPayServiceImpl implements WxPayService {
}
}
@Override
public HandleNotifyResult handleRefundNotify(HttpServletRequest request) {
HandleNotifyResult result = new HandleNotifyResult();
// 1. 提取请求头参数
String requestId = request.getHeader(REQUEST_ID);
String nonce = request.getHeader(WECHAT_PAY_NONCE);
String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
String signatureType = request.getHeader(WECHAT_PAY_SIGNATURE+"-Type");
String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
try {
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(serialNo)
.signature(signature)
.signType(signatureType)
.timestamp(timestamp)
.body(HttpServletUtils.getRequestBody(request))
.nonce(nonce)
.build();
// 验签并解析通知
NotificationParser parser = new NotificationParser((NotificationConfig)certificateConfig);
// 解析解密后的通知
Refund refund = parser.parse(requestParam, Refund.class);
// 更新状态
ebikeRefundService.updateRefundStatus(refund);
// 处理成功
result.setSuccess(true);
result.setMessage("success");
return result;
}catch (Exception e) {
result.setMessage(String.format("verify failed, request-id=[%s]", requestId));
logError("退款通知回调handleRefundNotify", e);
return result;
}
}
/**
* 打印日志
*
@ -237,6 +315,9 @@ public class WxPayServiceImpl implements WxPayService {
else if (e instanceof MalformedMessageException malformedMessageException) {
log.error("{} 返回体类型不合法或者解析返回体失败, {}", desc, malformedMessageException.getMessage());
}
else if (e instanceof ValidationException validationException) {
log.error("{} 验签失败,验证签名失败,{}", desc, validationException.getMessage());
}
else {
log.error("{} 执行异常, {}", desc, e.getMessage());
}

View File

@ -36,31 +36,55 @@ public class WsPayTask {
@Resource
private EbikeRefundService ebikeRefundService;
// TODO @Scheduled任务线程默认串行执行需要考虑并发问题
/**
* 从第0秒开始每隔30秒执行1次查询创建超过5分钟并且未支付的订单
* 每隔30秒执行1次查询创建超过5分钟并且未支付的订单
*/
@Scheduled(cron = "0/30 * * * * ?")
public void orderConfirm() throws Exception {
log.info("orderConfirm 执行......");
public void checkOrderStatus() throws Exception {
log.info("checkOrderStatus 执行......");
// 1. 查询未支付的订单
List<EbikePayment> ebikePaymentList = ebikePaymentService.getNoPayOrderByDuration(wxPayConfig.getExpireMinute());
// 2. 遍历订单查询支付状态
for (EbikePayment ebikePayment : ebikePaymentList) {
log.warn("超时未支付的订单号 ===> {}", ebikePayment.getOrderId());
log.warn("未支付的订单号 ===> {}", ebikePayment.getOrderId());
// 调用微信支付查询接口查询支付状态
Transaction transaction = wxPayService.queryOrderByOutTradeNo(ebikePayment.getOrderId());
// 3. 更新订单状态
if (transaction != null)
ebikePaymentService.updatePaymentStatus(ebikePayment.getRecordId(), transaction);
ebikePaymentService.updatePaymentStatus(transaction);
}
}
/**
* 从第0秒开始每隔30秒执行1次查询创建超过5分钟并且未成功的退款
* 每隔30秒执行1次查询创建超过5分钟并且未支付的订
*/
@Scheduled(cron = "0/30 * * * * ?")
public void refundConfirm() throws Exception {
log.info("refundConfirm 执行......");
public void closeOrder() throws Exception {
log.info("closeOrder 执行......");
// 1. 查询未支付的超时订单
List<EbikePayment> ebikePaymentList = ebikePaymentService.getExpireOrderByDuration(wxPayConfig.getExpireMinute());
// 2. 遍历订单关闭订单
for (EbikePayment ebikePayment : ebikePaymentList) {
log.warn("超时未支付的订单号 ===> {}", ebikePayment.getOrderId());
// 调用微信支付关闭接口关闭订单
boolean close = wxPayService.closeOrder(ebikePayment.getOrderId());
if (close) {
// 3. 更新订单状态
Transaction transaction = new Transaction();
transaction.setTradeState(Transaction.TradeStateEnum.CLOSED);
transaction.setTradeStateDesc("订单关闭");
ebikePaymentService.updatePaymentStatus(transaction);
}
}
}
/**
* 每隔30秒执行1次查询创建未超过5分钟并且未成功的退款单
*/
@Scheduled(cron = "0/30 * * * * ?")
public void checkRefundStatus() throws Exception {
log.info("checkRefundStatus 执行......");
// 1. 查询未成功的退款单
List<EbikeRefund> ebikeRefundList = ebikeRefundService.getNoSuccessRefundOrderByDuration(wxPayConfig.getExpireMinute());
@ -69,9 +93,9 @@ public class WsPayTask {
log.warn("超时未退款的退款单号 ===> {}", ebikeRefund.getRefundId());
// 调用微信退款查询接口查询退款状态
Refund refund = wxPayService.queryRefundByOutNo(ebikeRefund.getRefundId());
if (refund!= null&& Status.SUCCESS.equals(refund.getStatus())){
if (refund!= null){
// 3. 更新退款单状态
//ebikeRefundService.updateRefundStatus(ebikeRefund.getRefundId(), 2);
ebikeRefundService.updateRefundStatus(refund);
}
}
}

View File

@ -0,0 +1,46 @@
package com.cdzy.payment.utils;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* HttpServletRequest 获取请求体工具类
*
* @author dingchao
* @date 2025/4/29
* @modified by:
*/
public class HttpServletUtils {
/**
* 获取请求体
*
* @param request HttpServletRequest
* @return 请求体
* @throws IOException 读取返回支付接口数据流出现异常
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
}

View File

@ -2,6 +2,7 @@ package com.cdzy.payment.utils;
import com.mybatisflex.core.keygen.IKeyGenerator;
import com.mybatisflex.core.keygen.KeyGeneratorFactory;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@ -14,12 +15,13 @@ import java.time.format.DateTimeFormatter;
* @date 2025/4/27
* @modified by:
*/
public class StringUtil {
public class StringUtils {
private final static String WXPAY_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX";
/**
* 格式化时间
* 格式yyyy-MM-dd'T'HH:mm:ss.SSSX
* 格式yyyy-MM-dd'T'HH:mm:ssXXX
* 示例2025-04-25T10:10:10.100+08:00
* 注意时区为+8
*
@ -28,10 +30,32 @@ public class StringUtil {
*/
public static String formatLocalDatetime(LocalDateTime localDateTime) {
ZonedDateTime localZoned = localDateTime.atZone(ZoneId.of("+8"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(WXPAY_DATE_FORMAT);
return localZoned.format(formatter);
}
/**
* 格式化时间
* 格式yyyy-MM-dd'T'HH:mm:ssXXX
* 示例2025-04-25T10:10:10.100+08:00
* 注意时区为+8
*
* @param formatTime 时间字符串
* @return
*/
public static LocalDateTime toLocalDatetime(String formatTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(WXPAY_DATE_FORMAT);
ZonedDateTime localZoned = ZonedDateTime.parse(formatTime, formatter);
return localZoned.toLocalDateTime();
}
/**
* 生成雪花算法ID
*
* @param idFieldName 字段名
* @return
*/
public static String generateSnowflakeId(String idFieldName) {
// 获取名为 "snowFlakeId" 的生成器
IKeyGenerator generator = KeyGeneratorFactory.getKeyGenerator("snowFlakeId");
@ -39,8 +63,12 @@ public class StringUtil {
return String.valueOf(key);
}
//public static void main(String[] args) {
// System.out.println(formatLocalDatetime(LocalDateTime.now()));
// System.out.println(generateSnowflakeId("refundId"));
// LocalDateTime localDateTime = toLocalDatetime("2025-04-25T10:10:10+08:00");
// System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//}
}