diff --git a/ebike-gather/src/main/resources/application-dev.yml b/ebike-gather/src/main/resources/application-dev.yml index 2f6823ff..8ad15ca0 100644 --- a/ebike-gather/src/main/resources/application-dev.yml +++ b/ebike-gather/src/main/resources/application-dev.yml @@ -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: diff --git a/ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/model/pojo/ReqVehicleStatusUpdateDto.java b/ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/model/dto/request/ReqVehicleStatusUpdateDto.java similarity index 83% rename from ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/model/pojo/ReqVehicleStatusUpdateDto.java rename to ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/model/dto/request/ReqVehicleStatusUpdateDto.java index fc9e3fe5..c2f87fdc 100644 --- a/ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/model/pojo/ReqVehicleStatusUpdateDto.java +++ b/ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/model/dto/request/ReqVehicleStatusUpdateDto.java @@ -1,4 +1,4 @@ -package com.cdzy.ebikemaintenance.model.pojo; +package com.cdzy.ebikemaintenance.model.dto.request; import lombok.Data; diff --git a/ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/service/impl/EbikeBikeInfoServiceImpl.java b/ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/service/impl/EbikeBikeInfoServiceImpl.java index 8334e782..97dccef6 100644 --- a/ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/service/impl/EbikeBikeInfoServiceImpl.java +++ b/ebike-maintenance/src/main/java/com/cdzy/ebikemaintenance/service/impl/EbikeBikeInfoServiceImpl.java @@ -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 completeDeployment(Map param) { String orderId = MapUtil.getStr(param, "orderId"); + List> fileLists = (List>) 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 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); diff --git a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRcostsetController.java b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRcostsetController.java index 6fe73eba..c8ec5194 100644 --- a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRcostsetController.java +++ b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRcostsetController.java @@ -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 createSnowflakeId() { + + SnowFlakeIDKeyGenerator snowFlakeIDKeyGenerator = new SnowFlakeIDKeyGenerator(); + long dataCenterId = snowFlakeIDKeyGenerator.nextId(); + return JsonResult.success(dataCenterId); + } } diff --git a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRoperatesetController.java b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRoperatesetController.java index 16d9a25b..7251fbcb 100644 --- a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRoperatesetController.java +++ b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/controller/EbikeSysRoperatesetController.java @@ -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); } } diff --git a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/model/dto/request/ReqEbikeSysRcostsetDto.java b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/model/dto/request/ReqEbikeSysRcostsetDto.java index 1057e4c8..6e6f6de0 100644 --- a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/model/dto/request/ReqEbikeSysRcostsetDto.java +++ b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/model/dto/request/ReqEbikeSysRcostsetDto.java @@ -17,6 +17,9 @@ import java.util.List; @Data public class ReqEbikeSysRcostsetDto implements Serializable { + + private String costConfigId; + /** * 区域id */ diff --git a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/service/impl/EbikeSysRoperatesetServiceImpl.java b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/service/impl/EbikeSysRoperatesetServiceImpl.java index 09b60dfa..022efd15 100644 --- a/ebike-operate/src/main/java/com/cdzy/ebikeoperate/service/impl/EbikeSysRoperatesetServiceImpl.java +++ b/ebike-operate/src/main/java/com/cdzy/ebikeoperate/service/impl/EbikeSysRoperatesetServiceImpl.java @@ -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${mysql.version} runtime + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided + org.springframework.boot diff --git a/ebike-payment/src/main/java/com/cdzy/payment/config/WxPayConfig.java b/ebike-payment/src/main/java/com/cdzy/payment/config/WxPayConfig.java index 1187afc6..e6d0ea68 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/config/WxPayConfig.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/config/WxPayConfig.java @@ -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); } + } diff --git a/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java b/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java index b091125f..50d79def 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java @@ -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(); + } } diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/dto/HandleNotifyResult.java b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/HandleNotifyResult.java new file mode 100644 index 00000000..80214916 --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/dto/HandleNotifyResult.java @@ -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; +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikePayment.java b/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikePayment.java index 60b7aa64..5474ca5f 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikePayment.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/model/entity/EbikePayment.java @@ -37,7 +37,6 @@ public class EbikePayment implements Serializable { /** * 骑行订单号 */ - @Id private String orderId; /** diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/EbikePaymentService.java b/ebike-payment/src/main/java/com/cdzy/payment/service/EbikePaymentService.java index 330393ac..1067bb83 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/service/EbikePaymentService.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/EbikePaymentService.java @@ -14,19 +14,27 @@ import java.util.List; */ public interface EbikePaymentService extends IService { /** - * 查询未支付订单 + * 查询未支付订单, 未超时的 * * @param duration 订单创建时间超过duration分钟,单位分钟 * @return 未支付订单列表 */ List getNoPayOrderByDuration(int duration); + /** + * 查询未支付订单, 超时的 + * + * @param duration 订单创建时间超过duration分钟,单位分钟 + * @return 未支付订单列表 + */ + List getExpireOrderByDuration(int duration); + /** * 更新支付状态 * - * @param recordId 记录ID * @param transaction 支付结果 * @return 更新成功返回true,否则返回false */ - Boolean updatePaymentStatus(String recordId, Transaction transaction); + Boolean updatePaymentStatus(Transaction transaction); + } diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/EbikeRefundService.java b/ebike-payment/src/main/java/com/cdzy/payment/service/EbikeRefundService.java index 1e0b10b1..a793a2fd 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/service/EbikeRefundService.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/EbikeRefundService.java @@ -33,9 +33,8 @@ public interface EbikeRefundService extends IService { /** * 更新退款状态 * - * @param refundId 退款ID * @param refund 退款结果 * @return 更新成功返回true,否则返回false */ - Boolean updateRefundStatus(String refundId, Refund refund); + Boolean updateRefundStatus(Refund refund); } diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java b/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java index e2426ff6..bcba35cf 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java @@ -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); + } diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikePaymentServiceImpl.java b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikePaymentServiceImpl.java index 7a615229..a55879ca 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikePaymentServiceImpl.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/EbikePaymentServiceImpl.java @@ -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 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 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 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); } diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java index 3ab79f41..9e6c8b0d 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java @@ -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()); } diff --git a/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java b/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java index 89ce4fc2..05d3391c 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/task/WsPayTask.java @@ -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 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 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 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); } } } diff --git a/ebike-payment/src/main/java/com/cdzy/payment/utils/HttpServletUtils.java b/ebike-payment/src/main/java/com/cdzy/payment/utils/HttpServletUtils.java new file mode 100644 index 00000000..9cba544e --- /dev/null +++ b/ebike-payment/src/main/java/com/cdzy/payment/utils/HttpServletUtils.java @@ -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(); + } + +} diff --git a/ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtil.java b/ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtils.java similarity index 57% rename from ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtil.java rename to ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtils.java index 2069e6f9..6013f88e 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtil.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/utils/StringUtils.java @@ -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"))); //} }