72 Commits

Author SHA1 Message Date
4fb1d9b00c 整理代码 抖音接口 2025-10-28 17:40:32 +08:00
af71c55529 增加团购券核销接口 2025-10-28 10:28:06 +08:00
d47caef111 no message 2025-10-25 20:36:47 +08:00
09412ff8fe no message 2025-10-24 01:04:25 +08:00
d9be23b4cf no message 2025-10-24 00:57:56 +08:00
c730010937 meituan 2025-10-24 00:49:09 +08:00
540ecb522c no message 2025-10-23 23:10:23 +08:00
1b7682729c no message 2025-10-23 23:01:00 +08:00
c8e1268e87 no message 2025-10-23 22:34:10 +08:00
44508651b6 no message 2025-10-23 22:32:01 +08:00
fc29003411 no message 2025-10-23 22:09:59 +08:00
362866113a no message 2025-10-23 21:53:31 +08:00
18b9318b0d no message 2025-10-23 21:40:39 +08:00
97444a1520 no message 2025-10-23 21:28:54 +08:00
b5c4e4226a douyin meituan 2025-10-23 20:41:42 +08:00
3a0d707a5a 添加电子围栏redis 2025-10-14 16:32:55 +08:00
b3ba5ef5b1 异步提交充电订单同步 2025-10-03 12:32:43 +08:00
53f98d9174 url 2025-09-30 16:17:23 +08:00
a97513c33f 增加套餐验证接口调用 2025-09-30 15:56:12 +08:00
dab12a4b98 逾期天数有变化则更新 2025-09-30 14:50:15 +08:00
d2ce1120b0 更新逾期小时数 2025-09-30 14:30:30 +08:00
63bb831152 计算逾期天数和小时数 2025-09-30 14:10:01 +08:00
f64bb390dc 逾期小时数取整 2025-09-30 13:37:52 +08:00
151898fdac 计算时租预计还车时间 2025-09-30 11:33:02 +08:00
d627e9ed42 改回统一下单支付 2025-09-30 01:45:55 +08:00
6aa0cb4834 退款查看 2025-09-30 01:26:21 +08:00
79b0012d4e 子订单单号 2025-09-30 00:49:42 +08:00
46fa58a78a 订单号变更 2025-09-30 00:19:31 +08:00
1e95a147e6 免押支付更换子订单支付号 2025-09-30 00:10:48 +08:00
d9fe7405eb 免押支付加openId 2025-09-29 23:59:47 +08:00
19a5bde9b0 增加免押支付扣款逻辑 2025-09-29 23:43:19 +08:00
e2958b47ca 新增租电逻辑 2025-09-29 17:14:17 +08:00
9b85e3a39f 订单统计 2025-09-29 14:53:02 +08:00
dd5603c450 订单编号格式 2025-09-29 14:33:01 +08:00
7751595742 租电子订单returntime 2025-09-29 14:12:39 +08:00
60defa3bb4 门店分润入账 2025-09-29 00:21:13 +08:00
1f06e34d99 驳回解冻免押 2025-09-28 17:42:01 +08:00
70a6b578ef 增加同步租电订单的接口 2025-09-28 17:15:24 +08:00
5f56194f0c 解除免押,取消订单问题 2025-09-28 10:36:18 +08:00
b811e01da6 Merge branch 'main' of http://115.190.8.52:3000/sczx_group/sczx_order 2025-09-27 23:47:32 +08:00
164434b3da 计算重试次数 2025-09-27 23:47:28 +08:00
875ac87316 swagger 2025-09-27 23:24:25 +08:00
9162888023 更新次数 2025-09-27 22:56:42 +08:00
872d1d1b7b 增加处理定时任务的表及接口 2025-09-27 22:37:14 +08:00
eaaf9d4902 免押支付修复 2025-09-27 20:59:36 +08:00
39b793adbb 运营商和门店下单时做下架判断 2025-09-27 20:26:38 +08:00
3937c32c6b 免押逻辑修改 2025-09-27 19:58:11 +08:00
e998dc6651 增加租赁时长 2025-09-27 01:24:39 +08:00
652573f9ef 增加接触免押状态 2025-09-26 22:49:40 +08:00
90559cff06 免押下单和免押支付 2025-09-26 18:20:44 +08:00
b07726c866 Merge branch 'main' into zhangli/2025-9-26_my 2025-09-26 16:51:14 +08:00
6f0bf4d57c 换下日志 2025-09-26 16:36:11 +08:00
0338f3c675 修改下日志 2025-09-26 15:38:53 +08:00
af974e7368 去掉日志 2025-09-26 15:25:35 +08:00
c55b433bcc 冻结 2025-09-26 15:23:53 +08:00
9c4113cb43 Merge branch 'main' into zhangli/2025-9-26_my 2025-09-26 15:17:21 +08:00
a2f8d90eea 续租更新时间 2025-09-26 15:15:47 +08:00
1330b8cb68 免押接口实现 2025-09-26 14:26:11 +08:00
879678cb56 新增免押相关接口 2025-09-26 01:45:44 +08:00
cefafddf5a 续租要计算租电订单 2025-09-25 15:09:43 +08:00
1814c5c199 续租去掉租电订单 2025-09-25 02:06:31 +08:00
edd2d6e728 租电订单做空处理 2025-09-25 01:48:07 +08:00
c3e64ce40f 去掉空值校验 2025-09-25 00:37:55 +08:00
17dddc27a2 计算续租时间和金额 2025-09-25 00:33:26 +08:00
5136b0f940 修复bug 2025-09-23 23:43:28 +08:00
b4a37065c5 还车时间处理 2025-09-22 02:50:26 +08:00
acf125b02a 批量处理待支付定 2025-09-22 02:14:18 +08:00
1d678f2f98 处理待支付的订单 2025-09-22 01:33:10 +08:00
729ae0e19c 修复续租和逾期处理问题 2025-09-21 23:59:55 +08:00
b51e0e1cd9 解决金额计算问题 2025-09-21 13:21:34 +08:00
c08f92cf4f 续租增加租电订单金额 2025-09-21 00:34:09 +08:00
2b250068cf 设置租电订单号 2025-09-19 00:32:04 +08:00
69 changed files with 2849 additions and 298 deletions

59
pom.xml
View File

@ -46,6 +46,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.2</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -185,6 +190,12 @@
<version>2.11.1</version> <!-- 可根据需要选择版本 -->
</dependency>
<!-- Apache Commons Codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<!-- swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
@ -243,8 +254,38 @@
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>4.44.0</version>
</dependency>
<dependency>
<groupId>com.douyin.openapi</groupId>
<artifactId>sdk</artifactId>
<version>1.0.6</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/sdk-1.0.6.jar</systemPath>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.meituan.sdk</groupId>
<artifactId>sdk</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/MtOpJavaSDK-1.0-SNAPSHOT.jar</systemPath>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.2</version> <!-- 或更高稳定版本 -->
</dependency>
</dependencies>
<!-- Build Configuration -->
<build>
<finalName>sczx_order</finalName>
@ -262,6 +303,19 @@
<exclude>*.yml</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>lib/*.jar</include>
</includes>
<targetPath>${project.build.directory}/lib</targetPath>
</resource>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>lib/**</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!-- Compiler Plugin -->
@ -278,6 +332,10 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 确保包含系统作用域的依赖 -->
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
@ -297,6 +355,7 @@
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -7,4 +7,6 @@ public interface RedisKeyConstants {
String BIND_CAR_KEY = SERVICE_PREFIX + "bindCar:";
String E_Fence_KEY = SERVICE_PREFIX + "eFence:";
}

View File

@ -0,0 +1,20 @@
package com.sczx.order.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @Author: 张黎
* @Date: 2024/03/08/17:42
* @Description: 支付方式枚举
*/
@AllArgsConstructor
@Getter
public enum CouponTypeEnum {
MT("MT", "美团"),
DY("DY", "抖音"),
;
private final String code;
private final String msg;
}

View File

@ -11,12 +11,15 @@ import lombok.Getter;
@AllArgsConstructor
@Getter
public enum PayStatusEnum {
INIT("INIT", "初始化"),
USERPAYING("USERPAYING", "用户支付中"),
SUCCESS("SUCCESS", "支付成功"),
UNFREEZE_SUCCESS("UNFREEZE_SUCCESS", "解除免押成功"),
PAYERROR("PAYERROR", "支付失败"),
REFUNDING("REFUNDING", "退款中"),
REFUND_SUCCESS("REFUND_SUCCESS", "退款成功"),
REFUND_ERROR("REFUND_ERROR", "退款失败"),
CLOSE("CLOSE", "订单关闭"),
;
private final String code;

View File

@ -11,6 +11,7 @@ import lombok.Getter;
@AllArgsConstructor
@Getter
public enum SubOrderTypeEnum {
NO_DEPOSIT("NO_DEPOSIT", "免押冻结订单"),
DEPOSIT("DEPOSIT", "押金订单"),
RENTCAR("RENTCAR", "租车订单"),
RENTBATTEY("RENTBATTEY", "租电订单"),

View File

@ -0,0 +1,103 @@
package com.sczx.order.config;
import com.aliyun.tea.TeaException;
import com.douyin.openapi.client.Client;
import com.douyin.openapi.client.models.OauthClientTokenRequest;
import com.douyin.openapi.client.models.OauthClientTokenResponse;
import com.douyin.openapi.credential.models.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 抖音 client_token 管理器
* 负责定时获取和缓存 client_token
*/
@Slf4j
@Component
public class DouyinTokenManager {
// 应用凭证信息
@Value("${coupon.douyin.client_key}")
private String CLIENT_KEY;
@Value("${coupon.douyin.client_secret}")
private String CLIENT_SECRET;
// Token 缓存
private final AtomicReference<OauthClientTokenResponse> tokenCache =
new AtomicReference<>();
// 定时任务执行器
private final ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(1, r -> {
Thread t = new Thread(r, "DouyinTokenRefreshThread");
t.setDaemon(false);
return t;
});
@PostConstruct
public void init() {
// 初始化时立即获取一次 token
refreshClientToken();
// 每小时更新一次 token (3600秒)
scheduler.scheduleAtFixedRate(
this::refreshClientToken,
3600,
3600,
TimeUnit.SECONDS
);
}
/**
* 获取当前有效的 client_token
*
* @return 当前有效的 access_token
*/
public String getCurrentToken() {
OauthClientTokenResponse response = tokenCache.get();
if (response != null && response.getData() != null) {
return response.getData().getAccessToken();
}
return null;
}
/**
* 刷新 client_token
*/
private void refreshClientToken() {
try {
Config config = new Config()
.setClientKey(CLIENT_KEY)
.setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
OauthClientTokenRequest sdkRequest = new OauthClientTokenRequest();
sdkRequest.setClientKey(CLIENT_KEY);
sdkRequest.setClientSecret(CLIENT_SECRET);
sdkRequest.setGrantType("client_credential");
OauthClientTokenResponse sdkResponse = client.OauthClientToken(sdkRequest);
// 更新缓存
tokenCache.set(sdkResponse);
if (sdkResponse.getData() != null) {
log.info("抖音 client_token 更新成功: {},有效期至: {}", sdkResponse.getData().getAccessToken(),
(System.currentTimeMillis() + sdkResponse.getData().getExpiresIn() * 1000));
}
} catch (TeaException e) {
log.error("获取抖音 client_token 失败 (TeaException): " + e.getMessage());
} catch (Exception e) {
log.error("获取抖音 client_token 失败 (Exception): " + e.getMessage());
}
}
}

View File

@ -3,6 +3,7 @@ package com.sczx.order.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
@ -17,6 +18,7 @@ import java.util.Arrays;
@Configuration
@EnableSwagger2
@Profile("!prod") // 除了prod环境外都启用
public class SwaggerConfig {
@Bean
public Docket createRestApi() {

View File

@ -53,6 +53,24 @@ public class ClientOrderController {
return Result.ok(orderService.submitRentCarOrder(rentCarOrderReq));
}
@ApiOperation(value = "免押生成租车订单")
@PostMapping("/depositFreeSubmitRentCarOrder")
public Result<RentCarOrderResultDTO> depositFreeSubmitRentCarOrder(@Valid @RequestBody RentCarOrderReq rentCarOrderReq){
return Result.ok(orderService.depositFreeSubmitRentCarOrder(rentCarOrderReq));
}
@ApiOperation(value = "免押支付")
@PostMapping("/depositFreePayRentCarOrder")
public Result<RentCarOrderResultDTO> depositFreePayRentCarOrder(@Valid @RequestBody RentCarOrderReq rentCarOrderReq){
return Result.ok(orderService.depositFreePayRentCarOrder(rentCarOrderReq));
}
@ApiOperation(value = "第三方订单")
@PostMapping("/thirdPlatformRentCarOrder")
public Result<RentCarOrderResultDTO> thirdPlatformRentCarOrder(@Valid @RequestBody RentCarThirdPlatformOrderReq rentCarOrderReq){
return Result.ok(orderService.thirdPlatformRentCarOrder(rentCarOrderReq));
}
@ApiOperation(value = "续租车")
@PostMapping("/reRentalCarOrder")
public Result<RentCarOrderResultDTO> reRentalCarOrder(@Valid @RequestBody ReRentCarReq rentCarOrderReq){
@ -62,7 +80,7 @@ public class ClientOrderController {
@ApiOperation(value = "取消订单")
@PostMapping("/cancelOrder")
public Result<Boolean> cancelOrder(@Valid @RequestBody PayOrderReq payOrderReq){
orderService.cancelOrder(payOrderReq);
orderService.cancelOrder(payOrderReq,null);
return Result.ok(true);
}

View File

@ -2,6 +2,7 @@ package com.sczx.order.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.sczx.order.common.Result;
import com.sczx.order.dto.VerifyGroupBuyCouponsReq;
import com.sczx.order.dto.OrderDetailDTO;
import com.sczx.order.dto.OrderDistribDTO;
import com.sczx.order.dto.OrderDistribQueryReq;
@ -12,6 +13,8 @@ import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
* @Author: 张黎
* @Date: 2025/07/25/16:42
@ -49,4 +52,10 @@ public class PubOrderController {
orderDistribService.saveOrderDistrib(orderNo);
return Result.ok(true);
}
@ApiOperation(value = "团购券核销")
@PostMapping("/verifyGroupBuyCoupons")
public Result<OrderDetailDTO> verifyGroupBuyCoupons(@Valid @RequestBody VerifyGroupBuyCouponsReq req){
return Result.ok(orderService.verifyGroupBuyCoupons( req));
}
}

View File

@ -60,4 +60,10 @@ public class StoreOrderController {
public Result<OrderDTO> confirmReturnCar(@RequestBody ReturnCarReq returnCarReq){
return Result.ok(orderService.confirmReturnCar(returnCarReq));
}
@ApiOperation(value = "抖音团购核销")
@PostMapping("/verifyCar")
public Result<OrderDTO> verifyCar(@RequestBody ReturnCarReq returnCarReq){
return Result.ok(orderService.confirmReturnCar(returnCarReq));
}
}

View File

@ -0,0 +1,35 @@
package com.sczx.order.controller;
import com.sczx.order.common.Result;
import com.sczx.order.service.DouyinService;
import com.sczx.order.service.MeiTuanService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/verify")
public class VerifyController {
@Autowired
private MeiTuanService meituanService;
@ApiOperation(value = "接收美团Token数据接口")
@GetMapping("/authorization")
public Result<String> authorization(@RequestParam("code") String code, @RequestParam("sign") String sign, @RequestParam("developerId") Long developerId, @RequestParam("businessId") int businessId,
@RequestParam("state") String state){
log.info("接收美团授权数据 - code: {}, sign: {}, developerId: {}, businessId: {}, state: {}",
code, sign, developerId, businessId, state);
return Result.ok(meituanService.getAccessToken(code,state));
}
}

View File

@ -1,9 +1,6 @@
package com.sczx.order.convert;
import com.sczx.order.dto.OrderDTO;
import com.sczx.order.dto.OrderDetailDTO;
import com.sczx.order.dto.RentCarOrderReq;
import com.sczx.order.dto.SimpleUserInfoDTO;
import com.sczx.order.dto.*;
import com.sczx.order.po.OrderMainPO;
import com.sczx.order.po.OrderSubPO;
import com.sczx.order.thirdpart.dto.*;
@ -31,7 +28,6 @@ public interface OrderConvert {
@Mapping(source = "rentCarOrderReq.carModelId", target = "carModelId"),
@Mapping(source = "rentCarOrderReq.rentCarRuleId", target = "rentCarRuleId"),
@Mapping(source = "rentCarOrderReq.rentBatteyRuleId", target = "rentBatteyRuleId"),
@Mapping(source = "rentCarOrderReq.isAutoDeduct", target = "isAutoDeduct"),
@Mapping(source = "rentCarOrderReq.isDepositFree", target = "isDepositFree"),
@Mapping(source = "rentCarOrderReq.batteryType", target = "batteryType"),
@Mapping(source = "userInfoDTO.userId", target = "customerId"),
@ -47,6 +43,31 @@ public interface OrderConvert {
})
OrderMainPO subOrderToPo(RentCarOrderReq rentCarOrderReq, SimpleUserInfoDTO userInfoDTO, RentCarRuleDTO rentCarRuleDTO);
@Mappings({
@Mapping(target = "orderId", ignore = true),
@Mapping(target = "createTime", ignore = true),
@Mapping(target = "updateTime", ignore = true),
@Mapping(target = "delFlag", ignore = true),
@Mapping(source = "rentCarOrderReq.storeId", target = "storeId"),
@Mapping(source = "rentCarOrderReq.operatorId", target = "operatorId"),
@Mapping(source = "rentCarOrderReq.carModelId", target = "carModelId"),
@Mapping(source = "rentCarOrderReq.rentCarRuleId", target = "rentCarRuleId"),
@Mapping(source = "rentCarOrderReq.rentBatteyRuleId", target = "rentBatteyRuleId"),
@Mapping(source = "rentCarOrderReq.isDepositFree", target = "isDepositFree"),
@Mapping(source = "rentCarOrderReq.batteryType", target = "batteryType"),
@Mapping(source = "userInfoDTO.userId", target = "customerId"),
@Mapping(source = "userInfoDTO.userName", target = "customerName"),
@Mapping(source = "userInfoDTO.phoneNumber", target = "customerPhone"),
@Mapping(source = "rentCarRuleDTO.rentalType", target = "rentalType"),
@Mapping(source = "rentCarRuleDTO.rentalDays", target = "rentalDays"),
@Mapping(source = "rentCarRuleDTO.rentalPrice", target = "rentalPrice"),
@Mapping(source = "rentCarRuleDTO.depositPrice", target = "depositPrice"),
@Mapping(source = "rentCarRuleDTO.overdueFee", target = "overdueFee"),
@Mapping(source = "rentCarRuleDTO.overdueType", target = "overdueType")
})
OrderMainPO subOrderToPo(RentCarThirdPlatformOrderReq rentCarOrderReq, SimpleUserInfoDTO userInfoDTO, RentCarRuleDTO rentCarRuleDTO);
@Mappings({
@Mapping(source = "orderMainPO.orderId", target = "orderId"),
@ -87,6 +108,7 @@ public interface OrderConvert {
@Mapping(source = "rentBatteyRuleDTO.categoryName", target = "categoryName"),
@Mapping(source = "rentBatteyRuleDTO.title", target = "rentBatteyTitle"),
@Mapping(source = "rentBatteyRuleDTO.durationType", target = "durationType"),
@Mapping(source = "rentBatteyRuleDTO.rentPrice", target = "rentBatteyPrice"),
@Mapping(source = "rentBatteyOrder.vinBatteryNo", target = "batteyNo"),
})
OrderDetailDTO mainOrderToDetailDTO(OrderMainPO orderMainPO, CompanyStoreDTO companyStoreDTO, RentBatteyRuleDTO rentBatteyRuleDTO, CarDTO carDTO, CarModelSimpleDTO carModelSimpleDTO,OrderSubPO rentBatteyOrder);

View File

@ -0,0 +1,38 @@
package com.sczx.order.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 团购订单必须信息
* @Author: 张黎
* @Date: 2025/10/27/20:42
* @Description:
*/
@Data
public class GroupBuyOrderInfoDto {
@ApiModelProperty(value = "门店id")
@NotNull(message = "门店id不能为空")
private Long storeId;
@ApiModelProperty(value = "客户id")
private Long customerId;
@ApiModelProperty("车型ID")
@NotNull(message = "车型ID不能为空")
private Long carModelId;
@ApiModelProperty(value = "租车套餐id")
@NotNull(message = "租车套餐id不能为空")
private Long rentCarRuleId;
@ApiModelProperty(value = "租电套餐id")
private Long rentBatteyRuleId;
@ApiModelProperty(value = "团购券类型")
private String couponType;
}

View File

@ -119,6 +119,9 @@ public class OrderDetailDTO {
@ApiModelProperty("电池编码")
private String batteyNo;
@ApiModelProperty("租电租金")
private BigDecimal rentBatteyPrice;
@ApiModelProperty("计时方式")
private Integer durationType;
@ -176,4 +179,7 @@ public class OrderDetailDTO {
@ApiModelProperty("支付订单信息")
private List<PayOrderDTO> payOrderDTOList;
@ApiModelProperty("待支付金额")
private BigDecimal waitPayAmount;
}

View File

@ -23,6 +23,4 @@ public class ReRentCarReq {
@ApiModelProperty(value = "客户id")
private Long customerId;
@ApiModelProperty("是否开通代扣")
private Boolean isAutoDeduct = false;
}

View File

@ -5,7 +5,6 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
@ -40,17 +39,12 @@ public class RentCarOrderReq {
@ApiModelProperty(value = "租电套餐id")
@NotNull(message = "租电套餐id不能为空")
private Long rentBatteyRuleId;
@ApiModelProperty("选择的电池类型")
@NotBlank(message = "租电套餐id不能为空")
private String batteryType;
@ApiModelProperty("是否开通免押")
private Boolean isDepositFree = false;
@ApiModelProperty("是否开通代扣")
private Boolean isAutoDeduct = false;
}

View File

@ -0,0 +1,62 @@
package com.sczx.order.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @Author: 张黎
* @Date: 2025/07/25/16:58
* @Description:
*/
@Data
@ApiModel(value = "租车订单请求参数")
public class RentCarThirdPlatformOrderReq {
@ApiModelProperty(value = "订单编号,租车不需要传,续租和逾期处理需要传")
private String orderNo;
@ApiModelProperty(value = "运营商id")
private Long operatorId;
@ApiModelProperty(value = "门店id")
@NotNull(message = "门店id不能为空")
private Long storeId;
@ApiModelProperty(value = "客户id")
private Long customerId;
@ApiModelProperty(value = "客户姓名")
private String customerName;
@ApiModelProperty(value = "客户电话")
private String customerPhone;
@ApiModelProperty(value = "第三方订单号")
private String thirdOrderNo;
@ApiModelProperty(value = "订单来源")
private String orderSource;
@ApiModelProperty("车型ID")
@NotNull(message = "车型ID不能为空")
private Long carModelId;
@ApiModelProperty(value = "租车套餐id")
@NotNull(message = "租车套餐id不能为空")
private Long rentCarRuleId;
@ApiModelProperty(value = "租电套餐id")
private Long rentBatteyRuleId;
@ApiModelProperty("选择的电池类型")
private String batteryType;
@ApiModelProperty("是否开通免押")
private Boolean isDepositFree = false;
}

View File

@ -0,0 +1,34 @@
package com.sczx.order.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* @Author: 张黎
* @Date: 2025/10/27/19:13
* @Description:
*/
@ApiModel(value = "团购券核销请求参数")
@Data
public class VerifyGroupBuyCouponsReq {
@NotEmpty(message = "券码类型不能为空")
@ApiModelProperty(value = "券码类型:美团MT,抖音DY")
private String couponType;
@NotEmpty(message = "券码不能为空")
@ApiModelProperty(value = "券码")
private String couponCode;
@NotNull(message = "门店id不能为空")
@ApiModelProperty(value = "门店id")
private Long storeId;
@NotEmpty(message = "用户手机号不能为空")
@ApiModelProperty(value = "用户完整手机号")
private String mobile;
}

View File

@ -0,0 +1,17 @@
package com.sczx.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sczx.order.po.ElectronicFenceRulePO;
import com.sczx.order.po.OrderCarChangePO;
/**
* <p>
* 订单车辆变更表 Mapper 接口
* </p>
*
* @author zhangli
* @since 2025-09-03 23:12:55
*/
public interface ElectronicFenceRuleMapper extends BaseMapper<ElectronicFenceRulePO> {
}

View File

@ -0,0 +1,16 @@
package com.sczx.order.mapper;
import com.sczx.order.po.OrderProcessPO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 订单异常处理表 Mapper 接口
* </p>
*
* @author zhangli
* @since 2025-09-27 22:05:11
*/
public interface OrderProcessMapper extends BaseMapper<OrderProcessPO> {
}

View File

@ -0,0 +1,66 @@
package com.sczx.order.po;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 套餐规则与电子围栏关联表
* </p>
*
* @author lingma
* @since 2025-07-25 17:17:28
*/
@Getter
@Setter
@TableName("zc_electronic_fence_rule")
@ApiModel(value = "ElectronicFenceRulePO对象", description = "套餐规则与电子围栏关联表")
public class ElectronicFenceRulePO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("规则ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty("车辆规则ID")
private Long carRuleId;
@ApiModelProperty("电子围栏ID")
private Long electronicFenceId;
@ApiModelProperty("排序")
private Integer sortOrder;
@ApiModelProperty("删除标志0代表存在 2代表删除")
private String delFlag;
@ApiModelProperty("创建者")
private String createBy;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新者")
private String updateBy;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("备注")
private String remark;
}

View File

@ -105,11 +105,12 @@ public class OrderMainPO implements Serializable {
@ApiModelProperty("申请还车时间")
private LocalDateTime reqEndRentTime;
@ApiModelProperty("实际还车时间")
private LocalDateTime actEndRentTime;
@ApiModelProperty("租赁时长")
private Integer rentalDuration;
@ApiModelProperty("逾期天数")
private Integer overdueDays;

View File

@ -0,0 +1,56 @@
package com.sczx.order.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 订单异常处理表
* </p>
*
* @author zhangli
* @since 2025-09-27 22:05:11
*/
@Getter
@Setter
@TableName("zc_order_process")
@ApiModel(value = "OrderProcessPO对象", description = "订单异常处理表")
public class OrderProcessPO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("订单ID主键")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty("订单id")
private Long orderId;
@ApiModelProperty("订单编号")
private String orderNo;
@ApiModelProperty("处理类型NO_PAY未支付, RE_FUND退款")
private String processType;
@ApiModelProperty("处理次数")
private Integer retryNum;
@ApiModelProperty("删除标志0代表存在 2代表删除")
private String delFlag;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
}

View File

@ -74,6 +74,9 @@ public class OrderSubPO implements Serializable {
@ApiModelProperty("备注")
private String remark;
@ApiModelProperty("平台流水号")
private String transactionId;
@ApiModelProperty("删除标志0代表存在 2代表删除")
private String delFlag;

View File

@ -0,0 +1,16 @@
package com.sczx.order.repository;
import com.baomidou.mybatisplus.extension.service.IService;
import com.sczx.order.po.ElectronicFenceRulePO;
/**
* <p>
* 套餐规则与电子围栏关联表 服务类
* </p>
*
* @author lingma
* @since 2025-07-25 17:17:28
*/
public interface ElectronicFenceRuleRepo extends IService<ElectronicFenceRulePO> {
}

View File

@ -0,0 +1,16 @@
package com.sczx.order.repository;
import com.sczx.order.po.OrderProcessPO;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 订单异常处理表 服务类
* </p>
*
* @author zhangli
* @since 2025-09-27 22:05:11
*/
public interface OrderProcessRepo extends IService<OrderProcessPO> {
}

View File

@ -0,0 +1,21 @@
package com.sczx.order.repository.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sczx.order.po.ElectronicFenceRulePO;
import com.sczx.order.repository.ElectronicFenceRuleRepo;
import com.sczx.order.mapper.ElectronicFenceRuleMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 套餐规则与电子围栏关联表 服务实现类
* </p>
*
* @author lingma
* @since 2025-07-25 17:17:28
*/
@Service
public class ElectronicFenceRuleRepoImpl extends ServiceImpl<ElectronicFenceRuleMapper, ElectronicFenceRulePO> implements ElectronicFenceRuleRepo {
}

View File

@ -0,0 +1,20 @@
package com.sczx.order.repository.impl;
import com.sczx.order.po.OrderProcessPO;
import com.sczx.order.mapper.OrderProcessMapper;
import com.sczx.order.repository.OrderProcessRepo;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 订单异常处理表 服务实现类
* </p>
*
* @author zhangli
* @since 2025-09-27 22:05:11
*/
@Service
public class OrderProcessRepoImpl extends ServiceImpl<OrderProcessMapper, OrderProcessPO> implements OrderProcessRepo {
}

View File

@ -0,0 +1,17 @@
package com.sczx.order.service;
import com.douyin.openapi.client.models.*;
public interface DouyinService {
CertificatePrepareResponse prepare(CertificatePrepareRequest req);
CertificateVerifyResponse verify(CertificateVerifyRequest req);
CertificateCancelResponse cancel(CertificateCancelRequest req);
}

View File

@ -0,0 +1,27 @@
package com.sczx.order.service;
import com.sczx.order.dto.GroupBuyOrderInfoDto;
/** 团购券服务
* @Author: 张黎
* @Date: 2025/10/27/20:30
* @Description:
*/
public interface GroupBuyCouponService {
/**
* 获取团购券下单信息
* @param couponCode 团购券码
* @param couponType 团购券类型
* @param mobile 手机号
* @return
*/
GroupBuyOrderInfoDto getGroupBuyOrderInfoDto(String couponCode, String couponType, String mobile);
/**
* 校验团购券码
* @param couponCode 团购券码
* @param couponType 团购券类型
* @return
*/
boolean checkCouponCode(String couponCode, String couponType);
}

View File

@ -0,0 +1,17 @@
package com.sczx.order.service;
import com.meituan.sdk.MeituanResponse;
import com.meituan.sdk.auth.MeituanTokenResponse;
import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptConsume.TuangouReceiptConsumeResponse;
public interface MeiTuanService {
MeituanTokenResponse getAccessToken(String code , String state);
MeituanTokenResponse prepare(String code);
MeituanResponse<TuangouReceiptConsumeResponse> consume(String code);
MeituanTokenResponse refreshAccessToken(String refreshToken);
}

View File

@ -15,6 +15,13 @@ public interface OrderService {
*/
OrderMainPO queryOrderMainPoByOrderNo(String orderNo, String delFlag);
/**
* 团购券核销
* @param req
* @return
*/
OrderDetailDTO verifyGroupBuyCoupons(VerifyGroupBuyCouponsReq req) ;
/**
* 提交租车订单
* @param rentCarOrderReq
@ -22,6 +29,28 @@ public interface OrderService {
*/
RentCarOrderResultDTO submitRentCarOrder(RentCarOrderReq rentCarOrderReq);
/**
* 免押下单
* @param rentCarOrderReq
* @return
*/
RentCarOrderResultDTO depositFreeSubmitRentCarOrder(RentCarOrderReq rentCarOrderReq);
/**
* 免押支付
* @param rentCarOrderReq
* @return
*/
RentCarOrderResultDTO depositFreePayRentCarOrder(RentCarOrderReq rentCarOrderReq);
/**
* 第三方订单
* @param rentCarOrderReq
* @return
*/
RentCarOrderResultDTO thirdPlatformRentCarOrder(RentCarThirdPlatformOrderReq rentCarOrderReq);
/**
* 续租车
* @param rentCarOrderReq
@ -40,7 +69,19 @@ public interface OrderService {
* 取消订单
* @param payOrderReq
*/
void cancelOrder(PayOrderReq payOrderReq);
void cancelOrder(PayOrderReq payOrderReq,OrderMainPO orderMainPO);
/**
* 取消续租订单
* @param orderMainPO
*/
void cancelRerentOrOverDueOrder(OrderMainPO orderMainPO);
/**
* 强制删除租车订单
* @param orderMainPO
*/
void forceRemoveRentOrder(OrderMainPO orderMainPO);
/**
* 逾期处理

View File

@ -7,6 +7,8 @@ import java.math.BigDecimal;
public interface PayService {
/**
* 预支付下单
* @param payType
@ -28,4 +30,44 @@ public interface PayService {
* @param refundFee
*/
String refundOrder(String payType,Long companyId,String outTradeNo, BigDecimal totalFee, BigDecimal refundFee);
/**
* 支付宝免押扣款
* @param body
* @param companyId
* @param outTradeNo
* @param authNo
* @param aliPayOpenId
* @param totalFee
* @return
*/
UnifiedPaymentInfoDTO zhimaPayOrder(String body, Long companyId, String outTradeNo, String authNo, String aliPayOpenId,BigDecimal totalFee);
/**
* 冻结
* @param companyId
* @param outOrderNo
* @param freezeFee
* @return
*/
UnifiedPaymentInfoDTO freezeOrder(Long companyId,String outOrderNo, BigDecimal freezeFee);
/**
* 解冻
* @param companyId
* @param outOrderNo
* @param authCode
* @return
*/
boolean finishFreezeOrder(Long companyId,String outOrderNo, String authCode, BigDecimal freezeFee);
/**
* 取消冻结
* @param companyId
* @param outOrderNo
* @return
*/
boolean cancelFreezeOrder(Long companyId,String outOrderNo);
}

View File

@ -0,0 +1,191 @@
package com.sczx.order.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.tea.TeaException;
import com.douyin.openapi.client.Client;
import com.douyin.openapi.client.models.*;
import com.douyin.openapi.credential.models.Config;
import com.meituan.sdk.DefaultMeituanClient;
import com.meituan.sdk.MeituanClient;
import com.meituan.sdk.MeituanResponse;
import com.meituan.sdk.auth.MeituanTokenResponse;
import com.meituan.sdk.internal.utils.SignerUtil;
import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptPrepare.TuangouReceiptPrepareRequest;
import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptPrepare.TuangouReceiptPrepareResponse;
import com.sczx.order.common.Result;
import com.sczx.order.config.DouyinTokenManager;
import com.sczx.order.dto.VerifyGroupBuyCouponsReq;
import com.sczx.order.exception.InnerException;
import com.sczx.order.service.DouyinService;
import com.sczx.order.thirdpart.dto.CompanyStoreDTO;
import com.sczx.order.thirdpart.integration.StoreInteg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j
@Service
public class DouyinServiceImpl implements DouyinService {
@Value("${coupon.douyin.client_key}")
private String CLIENT_KEY;
@Value("${coupon.douyin.client_secret}")
private String CLIENT_SECRET;
@Value("${coupon.douyin.account_id}")
private String ACCOUNT_ID;
@Value("${coupon.douyin.poi_id}")
private String POI_ID;
@Autowired
private DouyinTokenManager douyinTokenManager;
public String resolveShortUrlToGetObjectId(String shortUrl) {
try {
URL url = new URL(shortUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(false); // 禁止自动重定向
connection.connect();
String longUrl = connection.getHeaderField("Location");
connection.disconnect();
if (longUrl == null || longUrl.isEmpty()) {
return null;
}
// 使用正则表达式匹配object_id参数
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("object_id=([^&]*)");
java.util.regex.Matcher matcher = pattern.matcher(longUrl);
if (matcher.find()) {
return matcher.group(1);
}
return null;
} catch (Exception e) {
log.error("二维码不正确");
throw new InnerException("扫码核销失败,二维码不正确");
}
}
/**
* 抖音验券准备返回抖音验券对象
*/
@Override
public CertificatePrepareResponse prepare(CertificatePrepareRequest req) {
// 获取当前有效的access_token
String accessToken = douyinTokenManager.getCurrentToken();
if (accessToken == null || accessToken.isEmpty()) {
throw new RuntimeException("无法获取有效的access_token");
}
//抖音验券请求
try {
Config config = new Config().setClientKey(CLIENT_KEY).setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
CertificatePrepareRequest sdkRequest = new CertificatePrepareRequest();
sdkRequest.setAccessToken(accessToken);
sdkRequest.setAccountId(ACCOUNT_ID);
String encryptedData = resolveShortUrlToGetObjectId(req.getEncryptedData());
sdkRequest.setEncryptedData(encryptedData);
sdkRequest.setPoiId(req.getPoiId());
CertificatePrepareResponse sdkResponse = client.CertificatePrepare(sdkRequest);
System.out.println(JSONObject.toJSONString(sdkResponse));
if (sdkResponse.getExtra().getErrorCode() != 0){
throw new RuntimeException("券验证失败:" + sdkResponse.getExtra().getDescription());
}
return sdkResponse;
} catch (TeaException e) {
throw new RuntimeException("券验证失败");
} catch (Exception e) {
throw new RuntimeException("券验证失败");
}
}
@Override
public CertificateVerifyResponse verify(CertificateVerifyRequest req) {
// 获取当前有效的access_token
String accessToken = douyinTokenManager.getCurrentToken();
if (accessToken == null || accessToken.isEmpty()) {
throw new RuntimeException("无法获取有效的access_token");
}
try{
Config config = new Config().setClientKey(CLIENT_KEY).setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
CertificateVerifyRequest sdkRequest = new CertificateVerifyRequest();
sdkRequest.setAccessToken(accessToken);
sdkRequest.setAccountId(ACCOUNT_ID);
sdkRequest.setPoiId(req.getPoiId());
sdkRequest.setVerifyToken(req.getVerifyToken());
CertificateVerifyResponse sdkResponse = client.CertificateVerify(sdkRequest);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public CertificateCancelResponse cancel(CertificateCancelRequest req) {
// 获取当前有效的access_token
String accessToken = douyinTokenManager.getCurrentToken();
if (accessToken == null || accessToken.isEmpty()) {
throw new RuntimeException("无法获取有效的access_token");
}
try{
Config config = new Config().setClientKey(CLIENT_KEY).setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
CertificateCancelRequest sdkRequest = new CertificateCancelRequest();
sdkRequest.setAccessToken(accessToken);
sdkRequest.setVerifyId(req.getVerifyId());
sdkRequest.setCertificateId(req.getCertificateId());
CertificateCancelResponse sdkResponse = client.CertificateCancel(sdkRequest);
System.out.println(JSONObject.toJSONString(sdkResponse));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,49 @@
package com.sczx.order.service.impl;
import com.sczx.order.common.enums.CouponTypeEnum;
import com.sczx.order.dto.GroupBuyOrderInfoDto;
import com.sczx.order.dto.SimpleUserInfoDTO;
import com.sczx.order.exception.BizException;
import com.sczx.order.service.GroupBuyCouponService;
import com.sczx.order.service.MeiTuanService;
import com.sczx.order.thirdpart.integration.UserInteg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author: 张黎
* @Date: 2025/10/27/20:48
* @Description:
*/
@Slf4j
@Service
public class GroupBuyCouponServiceImpl implements GroupBuyCouponService {
@Autowired
private UserInteg userInteg;
@Autowired
private MeiTuanService meiTuanService;
@Override
public GroupBuyOrderInfoDto getGroupBuyOrderInfoDto(String couponCode, String couponType, String mobile) {
SimpleUserInfoDTO userInfoDTO = userInteg.getUInfoByMobile(mobile);
if(userInfoDTO==null){
throw new BizException("用户不存在");
}
if(userInfoDTO.getAuthed()==0){
throw new BizException("用户未实名认证");
}
return null;
}
@Override
public boolean checkCouponCode(String couponCode, String couponType) {
if(CouponTypeEnum.MT.getCode().equals(couponType)){
}
return false;
}
}

View File

@ -0,0 +1,133 @@
package com.sczx.order.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.meituan.sdk.DefaultMeituanClient;
import com.meituan.sdk.MeituanClient;
import com.meituan.sdk.MeituanResponse;
import com.meituan.sdk.auth.MeituanTokenResponse;
import com.meituan.sdk.internal.exceptions.MtSdkException;
import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptConsume.TuangouReceiptConsumeRequest;
import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptConsume.TuangouReceiptConsumeResponse;
import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptPrepare.TuangouReceiptPrepareRequest;
import com.meituan.sdk.model.ddzh.tuangou.tuangouReceiptPrepare.TuangouReceiptPrepareResponse;
import com.sczx.order.exception.BizException;
import com.sczx.order.service.MeiTuanService;
import com.sczx.order.thirdpart.integration.StoreInteg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class MeiTuanServiceImpl implements MeiTuanService {
private Long DeveloperId = 116997L;
private String Signkey = "n8xlhtshk7t1luvi";
//private String accessToken = "V2-26b8d231854d3fdf6a5c06273377a65e441e4c9f1c8cea4959c510bf0dd19ed7657e34bebe4b74aa455b80d3c757cd8a66e3ece9148e6d6838778db5f9600cf383dbf89c0cc27f07f777c98649bd94fe";
private String accessToken = "V2-549da132ffebe5ce5731a17ae166cb899e13c579900d6cbc4163b60e4d40440ccbe8e56d25c3341cd1003c5529c407085c06af2eb41d2d091353b4d2be6f887582d9d4bf247cf9a511581ad46ade47e8";
@Autowired
private StoreInteg storeInteg;
@Override
public MeituanTokenResponse getAccessToken(String code, String state) {
try {
// 使用美团SDK构建客户端
MeituanClient client = DefaultMeituanClient.builder(DeveloperId, Signkey).build();
// 使用美团SDK的getOAuthToken方法获取token
MeituanTokenResponse response = client.getOAuthToken(58, code);
log.info("获取access_token响应结果: {}", JSONObject.toJSONString(response));
String token = response.getData().getAccessToken();
storeInteg.recordToken(state, token);
return null;
} catch (Exception e) {
throw new BizException("获取access_token失败");
}
}
@Override
public MeituanTokenResponse prepare(String code) {
try {
MeituanClient meituanClient = DefaultMeituanClient.builder(DeveloperId, Signkey).build();
TuangouReceiptPrepareRequest tuangouReceiptPrepareRequest = new TuangouReceiptPrepareRequest();
tuangouReceiptPrepareRequest.setReceiptCode("0106972239359");
String appAuthToken = accessToken;
MeituanResponse<TuangouReceiptPrepareResponse> response = meituanClient.invokeApi(tuangouReceiptPrepareRequest, appAuthToken);
log.info("获取access_token响应结果: {}", JSONObject.toJSONString(response));
if (response.isSuccess()) {
TuangouReceiptPrepareResponse resp = response.getData();
System.out.println(resp);
} else {
System.out.println("调用失败");
}
return null;
} catch (Exception e) {
log.error("获取access_token异常", e);
return null;
}
}
@Override
public MeituanTokenResponse refreshAccessToken(String refreshToken) {
try {
MeituanClient meituanClient = DefaultMeituanClient.builder(DeveloperId, Signkey).build();
MeituanTokenResponse response = meituanClient.refreshToken(58, refreshToken);
return response;
} catch (Exception e) {
log.error("获取access_token异常", e);
return null;
}
}
@Override
public MeituanResponse<TuangouReceiptConsumeResponse> consume (String code) {
try {
MeituanClient meituanClient = DefaultMeituanClient.builder(DeveloperId, Signkey).build();
TuangouReceiptConsumeRequest tuangouReceiptConsumeRequest = new TuangouReceiptConsumeRequest();
tuangouReceiptConsumeRequest.setReceiptCode("0106972239359");
tuangouReceiptConsumeRequest.setCount(1);
tuangouReceiptConsumeRequest.setRequestId("fdae143414");
tuangouReceiptConsumeRequest.setAppShopAccountName("customer");
tuangouReceiptConsumeRequest.setAppShopAccount("customer");
String appAuthToken = accessToken;
MeituanResponse<TuangouReceiptConsumeResponse> response = meituanClient.invokeApi(tuangouReceiptConsumeRequest, appAuthToken);
log.info("获取access_token响应结果: {}", JSONObject.toJSONString(response));
return response;
} catch (MtSdkException e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -5,16 +5,18 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Lists;
import com.sczx.order.common.enums.DistribTypeEnum;
import com.sczx.order.common.enums.WalletChangeTypeEnum;
import com.sczx.order.dto.OrderDistribDTO;
import com.sczx.order.dto.OrderDistribQueryReq;
import com.sczx.order.exception.BizException;
import com.sczx.order.po.*;
import com.sczx.order.po.BaseUserReferralPO;
import com.sczx.order.po.OrderDistribPO;
import com.sczx.order.po.OrderMainPO;
import com.sczx.order.repository.*;
import com.sczx.order.service.OrderDistribService;
import com.sczx.order.thirdpart.dto.CompanyDTO;
import com.sczx.order.thirdpart.dto.CompanyStoreDTO;
import com.sczx.order.thirdpart.dto.SysConfigDTO;
import com.sczx.order.thirdpart.dto.req.DistibIncomeReq;
import com.sczx.order.thirdpart.integration.StoreInteg;
import com.sczx.order.thirdpart.integration.UserInteg;
import lombok.extern.slf4j.Slf4j;
@ -24,7 +26,6 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
@ -99,11 +100,11 @@ public class OrderDistribServiceImpl implements OrderDistribService {
//判断奖励是否过期
if(awardDeadline.isAfter(LocalDate.now())){
String invitationRate = String.valueOf(configList.stream().filter(config -> "sczx.invitation.rate".equals(config.getConfigKey())).findFirst().orElse(null));
referralDistribPO = getOrderDistribPO(orderMainPO.getOrderId(),orderMainPO.getOrderNo(),DistribTypeEnum.REFERRAL.getCode(),invitationRate,orderMainPO.getOrderAmount(),null,null,baseUserReferralPO.getReferralUserId());
referralDistribPO = getOrderDistribPO(orderMainPO.getOrderId(),orderMainPO.getOrderNo(),DistribTypeEnum.REFERRAL.getCode(),invitationRate,orderMainPO.getOrderAmount(),Integer.valueOf(orderMainPO.getOperatorId().toString()),Integer.valueOf(orderMainPO.getStoreId().toString()),baseUserReferralPO.getReferralUserId());
addOrderDistribPOList.add(referralDistribPO);
}
}
BigDecimal alreadyShareRate = addOrderDistribPOList.stream().map(OrderDistribPO::getDistribRate).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
BigDecimal alreadyShareRate = addOrderDistribPOList.stream().map(OrderDistribPO::getDistribRate).reduce(BigDecimal.ZERO,BigDecimal::add);
//运营商分润
String companyRate = String.valueOf(BigDecimal.ONE.subtract(alreadyShareRate).multiply(BigDecimal.valueOf(100)));
OrderDistribPO companyDistribPO = getOrderDistribPO(orderMainPO.getOrderId(),orderMainPO.getOrderNo(),DistribTypeEnum.COMPANY.getCode(),companyRate,orderMainPO.getOrderAmount(),Integer.valueOf(orderMainPO.getOperatorId().toString()),Integer.valueOf(orderMainPO.getStoreId().toString()),null);
@ -115,30 +116,39 @@ public class OrderDistribServiceImpl implements OrderDistribService {
.set(OrderMainPO::getDistribed, 1);
orderMainRepo.update(updateMainWrapper);
//更新推荐信息,防止重复分润
LambdaUpdateWrapper<BaseUserReferralPO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(BaseUserReferralPO::getUserId, orderMainPO.getCustomerId())
.set(BaseUserReferralPO::getReferralOrderNo, storeDistribPO.getOrderNo());
baseUserReferralRepo.update(updateWrapper);
//门店钱包入账
LambdaQueryWrapper<BaseWalletPO> queryWalletWrapper = new LambdaQueryWrapper<>();
queryWalletWrapper.eq(BaseWalletPO::getUserId, orderMainPO.getOperatorId()).last(" limit 1");
BaseWalletPO baseWalletPO = baseWalletRepo.getOne(queryWalletWrapper);
if(baseWalletPO != null){
LambdaUpdateWrapper<BaseWalletPO> updateWalletWrapper = new LambdaUpdateWrapper<>();
updateWalletWrapper.eq(BaseWalletPO::getUserId, orderMainPO.getOperatorId())
.set(BaseWalletPO::getBalance, baseWalletPO.getBalance().add(storeDistribPO.getDistribAmount()));
baseWalletRepo.update(updateWalletWrapper);
DistibIncomeReq distibIncomeReq = new DistibIncomeReq();
distibIncomeReq.setStoreId(orderMainPO.getStoreId());
distibIncomeReq.setAmount(storeDistribPO.getDistribAmount());
storeInteg.distibIncome(distibIncomeReq);
BaseWalletChangePO baseWalletChangePO = new BaseWalletChangePO();
baseWalletChangePO.setUserId(orderMainPO.getOperatorId());
baseWalletChangePO.setChangeType(WalletChangeTypeEnum.REFERRAL.getCode());
baseWalletChangePO.setChangeTime(LocalDateTime.now());
baseWalletChangePO.setChangeAmount(storeDistribPO.getDistribAmount());
baseWalletChangePO.setReferralOrderNo(orderMainPO.getOrderNo());
baseWalletChangeRepo.save(baseWalletChangePO);
// LambdaQueryWrapper<BaseWalletPO> queryWalletWrapper = new LambdaQueryWrapper<>();
// queryWalletWrapper.eq(BaseWalletPO::getUserId, orderMainPO.getStoreId()).last(" limit 1");
// BaseWalletPO baseWalletPO = baseWalletRepo.getOne(queryWalletWrapper);
// if(baseWalletPO != null){
// LambdaUpdateWrapper<BaseWalletPO> updateWalletWrapper = new LambdaUpdateWrapper<>();
// updateWalletWrapper.eq(BaseWalletPO::getUserId, orderMainPO.getOperatorId())
// .set(BaseWalletPO::getBalance, baseWalletPO.getBalance().add(storeDistribPO.getDistribAmount()));
// baseWalletRepo.update(updateWalletWrapper);
// }else {
// baseWalletPO = new BaseWalletPO();
// baseWalletPO.setUserId(Long.valueOf(companyStoreDTO.getId()));
// baseWalletPO.setUserName(companyStoreDTO.getName());
// }
// BaseWalletChangePO baseWalletChangePO = new BaseWalletChangePO();
// baseWalletChangePO.setUserId(orderMainPO.getOperatorId());
// baseWalletChangePO.setChangeType(WalletChangeTypeEnum.REFERRAL.getCode());
// baseWalletChangePO.setChangeTime(LocalDateTime.now());
// baseWalletChangePO.setChangeAmount(storeDistribPO.getDistribAmount());
// baseWalletChangePO.setReferralOrderNo(orderMainPO.getOrderNo());
// baseWalletChangeRepo.save(baseWalletChangePO);
//更新推荐信息,防止重复分润
LambdaUpdateWrapper<BaseUserReferralPO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(BaseUserReferralPO::getUserId, orderMainPO.getCustomerId())
.set(BaseUserReferralPO::getReferralOrderNo, storeDistribPO.getOrderNo());
baseUserReferralRepo.update(updateWrapper);
}
@ -160,14 +170,11 @@ public class OrderDistribServiceImpl implements OrderDistribService {
private static OrderDistribPO getOrderDistribPO(Long orderId,String orderNo,String distribType,String invitationRate, BigDecimal orderAmount,Integer companyId,Integer storeId,Long referralUserId) {
BigDecimal referralRate = new BigDecimal(invitationRate).multiply(new BigDecimal("0.01"));
OrderDistribPO referralDistribPO = new OrderDistribPO();
if(DistribTypeEnum.COMPANY.getCode().equalsIgnoreCase(distribType)){
referralDistribPO.setCompanyId(companyId);
} else if(DistribTypeEnum.STORE.getCode().equalsIgnoreCase(distribType)){
referralDistribPO.setStoreId(storeId);
} else if(DistribTypeEnum.REFERRAL.getCode().equalsIgnoreCase(distribType)){
referralDistribPO.setCompanyId(companyId);
referralDistribPO.setStoreId(storeId);
if(DistribTypeEnum.REFERRAL.getCode().equalsIgnoreCase(distribType)){
referralDistribPO.setReferralUserId(referralUserId);
}
referralDistribPO.setReferralUserId(referralUserId);
referralDistribPO.setOrderId(orderId);
referralDistribPO.setOrderNo(orderNo);
referralDistribPO.setDistribType(distribType);

File diff suppressed because it is too large Load Diff

View File

@ -3,11 +3,10 @@ package com.sczx.order.service.impl;
import com.sczx.order.common.enums.PaymentTypeEnum;
import com.sczx.order.dto.SimpleUserInfoDTO;
import com.sczx.order.service.PayService;
import com.sczx.order.thirdpart.dto.AlipayFundFreezeResponse;
import com.sczx.order.thirdpart.dto.AlipayResponse;
import com.sczx.order.thirdpart.dto.UnifiedPaymentInfoDTO;
import com.sczx.order.thirdpart.dto.req.AlipayCreateRequest;
import com.sczx.order.thirdpart.dto.req.AlipayRefundRequest;
import com.sczx.order.thirdpart.dto.req.PaymentRequest;
import com.sczx.order.thirdpart.dto.req.RefundRequest;
import com.sczx.order.thirdpart.dto.req.*;
import com.sczx.order.thirdpart.integration.PayInteg;
import com.sczx.order.utils.OrderUtil;
import lombok.extern.slf4j.Slf4j;
@ -89,4 +88,52 @@ public class PayServiceImpl implements PayService {
}
return null;
}
@Override
public UnifiedPaymentInfoDTO zhimaPayOrder(String body, Long companyId, String outTradeNo, String authNo, String aliPayOpenId,BigDecimal totalFee) {
AlipayCreateRequest alipayCreateRequest = new AlipayCreateRequest();
alipayCreateRequest.setCompanyId(companyId);
alipayCreateRequest.setSubject(body);
alipayCreateRequest.setBody(body);
alipayCreateRequest.setOutTradeNo(outTradeNo);
alipayCreateRequest.setAuthNo(authNo);
alipayCreateRequest.setOpenId(aliPayOpenId);
alipayCreateRequest.setTotalAmount(totalFee.toString());
return payInteg.zhiMaOrder(alipayCreateRequest);
}
@Override
public UnifiedPaymentInfoDTO freezeOrder(Long companyId, String outOrderNo, BigDecimal freezeFee) {
AlipayFundFreezeRequest request = new AlipayFundFreezeRequest();
request.setCompanyId(companyId.toString());
request.setOutOrderNo(outOrderNo);
request.setTitle("租车免押");
request.setAmount(freezeFee.toString());
AlipayFundFreezeResponse result = payInteg.fundFreeze(request);
UnifiedPaymentInfoDTO unifiedPaymentInfoDTO = new UnifiedPaymentInfoDTO();
unifiedPaymentInfoDTO.setOrderStr(result.getOrderStr());
return unifiedPaymentInfoDTO;
}
@Override
public boolean finishFreezeOrder(Long companyId, String outOrderNo, String authCode, BigDecimal freezeFee) {
log.info("开始完结支付宝押金冻结支付单");
AlipayFinishFreezeRequest finishFreezeRequest = new AlipayFinishFreezeRequest();
finishFreezeRequest.setCompanyId(companyId.toString());
finishFreezeRequest.setAuthNo(authCode);
finishFreezeRequest.setOutRequestNo(OrderUtil.generateSubOrderNo(OrderUtil.FFZ_PREFIX));
finishFreezeRequest.setAmount(freezeFee.toString());
AlipayResponse alipayResponse = payInteg.finishFreeze(finishFreezeRequest);
return alipayResponse.isSuccess();
}
@Override
public boolean cancelFreezeOrder(Long companyId, String outOrderNo) {
log.info("开始取消支付宝押金冻结支付单");
AlipayFundFreezeRequest request = new AlipayFundFreezeRequest();
request.setCompanyId(companyId.toString());
request.setOutOrderNo(outOrderNo);
return payInteg.cancelFundFreeze(request);
}
}

View File

@ -3,17 +3,16 @@ package com.sczx.order.task;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.sczx.order.common.enums.OrderStatusEnum;
import com.sczx.order.common.enums.PayStatusEnum;
import com.sczx.order.common.enums.PaymentTypeEnum;
import com.sczx.order.dto.PayOrderReq;
import com.sczx.order.po.OrderMainPO;
import com.sczx.order.po.OrderSubPO;
import com.sczx.order.po.OrderProcessPO;
import com.sczx.order.repository.OrderMainRepo;
import com.sczx.order.repository.OrderProcessRepo;
import com.sczx.order.repository.OrderSubRepo;
import com.sczx.order.thirdpart.dto.req.AlipayCloseRequest;
import com.sczx.order.service.OrderService;
import com.sczx.order.thirdpart.integration.PayInteg;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@ -22,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@Slf4j
@Component
@ -35,8 +35,14 @@ public class NoPayOrderProcessTask {
@Autowired
private PayInteg payInteg;
@Autowired
private OrderService orderService;
@Autowired
private OrderProcessRepo orderProcessRepo;
/**
* 每30分钟检查一次逾期订单
* 每6分钟检查一次逾期订单
* 使用ShedLock确保在分布式环境下只有一个实例执行
* 分布式锁机制ShedLock使用Redis作为锁存储确保同一时间只有一个服务实例执行定时任务
* 任务名称:@SchedulerLock 注解中的 name 属性标识任务名称,相同名称的任务在分布式环境下互斥执行
@ -45,7 +51,7 @@ public class NoPayOrderProcessTask {
* lockAtLeastFor锁最少持有时间防止任务执行过快导致频繁执行
*/
@Transactional(rollbackFor = Exception.class)
@Scheduled(cron = "0 */15 * * * ?")
@Scheduled(cron = "0 */6 * * * ?")
@SchedulerLock(name = "checkNoPayOrders", lockAtMostFor = "9m", lockAtLeastFor = "1m")
public void checkNoPayOrders() {
log.info("开始执行未支付订单检查任务");
@ -66,7 +72,7 @@ public class NoPayOrderProcessTask {
LambdaQueryWrapper<OrderMainPO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderMainPO::getDelFlag, "0").in(OrderMainPO::getOrderStatus,
Arrays.asList(OrderStatusEnum.WAIT_PAY.getCode(), OrderStatusEnum.RERENT_WAIT_PAY.getCode()))
.lt(OrderMainPO::getUpdateTime, LocalDateTime.now().minusHours(1));
.lt(OrderMainPO::getUpdateTime, LocalDateTime.now().minusMinutes(5));
List<OrderMainPO> orders = orderMainRepo.list(queryWrapper);
@ -100,52 +106,41 @@ public class NoPayOrderProcessTask {
* @param orderMainPO
*/
private void processWayPay(OrderMainPO orderMainPO) {
LambdaQueryWrapper<OrderSubPO> orderSubQueryWrapper = new LambdaQueryWrapper<>();
orderSubQueryWrapper.eq(OrderSubPO::getOrderId, orderMainPO.getOrderId());
orderSubQueryWrapper.eq(OrderSubPO::getPayStatus, PayStatusEnum.USERPAYING.getCode());
orderSubQueryWrapper.orderByDesc(OrderSubPO::getCreatedAt).last(" limit 1");
OrderSubPO orderSubPO = orderSubRepo.getOne(orderSubQueryWrapper);
boolean closePayOrder = false;
//关闭支付单
if (StringUtils.equalsIgnoreCase(orderSubPO.getPaymentMethod(), PaymentTypeEnum.WX_PAY.getCode())) {
log.info("开始关闭支付单");
closePayOrder = payInteg.closeOrder(orderMainPO.getOperatorId(), orderSubPO.getPaymentId());
} else if (StringUtils.equalsIgnoreCase(orderSubPO.getPaymentMethod(), PaymentTypeEnum.ZFB_PAY.getCode())) {
log.info("开始关闭支付宝支付单");
AlipayCloseRequest alipayCloseRequest = new AlipayCloseRequest();
alipayCloseRequest.setCompanyId(orderMainPO.getOperatorId());
alipayCloseRequest.setOutTradeNo(orderSubPO.getPaymentId());
closePayOrder = payInteg.alipayCloseOrder(alipayCloseRequest);
OrderProcessPO orderProcessPO = orderProcessRepo.getOne(new LambdaQueryWrapper<OrderProcessPO>()
.eq(OrderProcessPO::getOrderId, orderMainPO.getOrderId())
.eq(OrderProcessPO::getProcessType, "NO_PAY"));
if(Objects.isNull(orderProcessPO)){
orderProcessPO = new OrderProcessPO();
orderProcessPO.setOrderId(orderMainPO.getOrderId());
orderProcessPO.setProcessType("NO_PAY");
orderProcessPO.setOrderNo(orderMainPO.getOrderNo());
orderProcessPO.setRetryNum(1);
orderProcessRepo.save(orderProcessPO);
}else {
orderProcessPO.setRetryNum(orderProcessPO.getRetryNum()+1);
LambdaUpdateWrapper<OrderProcessPO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(OrderProcessPO::getId, orderProcessPO.getId())
.set(OrderProcessPO::getRetryNum, orderProcessPO.getRetryNum());
orderProcessRepo.update(updateWrapper);
}
if (closePayOrder) {
LambdaUpdateWrapper<OrderSubPO> updateSubWrapper = new LambdaUpdateWrapper<>();
updateSubWrapper.set(OrderSubPO::getDelFlag, "2");
updateSubWrapper.eq(OrderSubPO::getOrderId, orderMainPO.getOrderId());
orderSubRepo.update(updateSubWrapper);
if (StringUtils.equalsIgnoreCase(orderMainPO.getOrderStatus(), OrderStatusEnum.WAIT_PAY.getCode())) {
log.info("开始逻辑删除订单");
//逻辑删除订单
LambdaUpdateWrapper<OrderMainPO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(OrderMainPO::getDelFlag, "2");
updateWrapper.eq(OrderMainPO::getOrderId, orderMainPO.getOrderId());
orderMainRepo.update(updateWrapper);
} else if (StringUtils.equalsIgnoreCase(orderMainPO.getOrderStatus(), OrderStatusEnum.RERENT_WAIT_PAY.getCode())) {
log.info("开始恢复续租订单状态");
//更新订单状态及信息
LambdaUpdateWrapper<OrderMainPO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(OrderMainPO::getOrderStatus, OrderStatusEnum.RENT_ING.getCode());
if(orderMainPO.getOverdueDays()>0){
updateWrapper.set(OrderMainPO::getOrderStatus, OrderStatusEnum.RENT_OVERDUE.getCode());
}else {
updateWrapper.set(OrderMainPO::getOrderStatus, OrderStatusEnum.RENT_ING.getCode());
}
updateWrapper.eq(OrderMainPO::getOrderId, orderMainPO.getOrderId());
orderMainRepo.update(updateWrapper);
if(orderProcessPO.getRetryNum()>5){
log.info("订单{}已超过最大重试次数,强行删除订单", orderMainPO.getOrderNo());
orderService.forceRemoveRentOrder(orderMainPO);
} else {
if(orderMainPO.getOrderStatus().equals(OrderStatusEnum.WAIT_PAY.getCode())){
PayOrderReq payOrderReq = new PayOrderReq();
payOrderReq.setOrderNo(orderMainPO.getOrderNo());
orderService.cancelOrder(payOrderReq,orderMainPO);
} else if(orderMainPO.getOrderStatus().equals(OrderStatusEnum.RERENT_WAIT_PAY.getCode())){
PayOrderReq payOrderReq = new PayOrderReq();
payOrderReq.setOrderNo(orderMainPO.getOrderNo());
orderService.cancelRerentOrOverDueOrder(orderMainPO);
}
}
}
}

View File

@ -81,7 +81,7 @@ public class OrderOverdueTask {
.set(OrderMainPO::getOverdueDays, overdueDaysOrHours)
.eq(OrderMainPO::getOrderId, order.getOrderId())
// 确保状态未被其他节点修改
.eq(OrderMainPO::getOrderStatus, OrderStatusEnum.RENT_ING.getCode());
.in(OrderMainPO::getOrderStatus, Arrays.asList(OrderStatusEnum.RENT_ING.getCode(), OrderStatusEnum.RENT_OVERDUE.getCode()));
boolean updated = orderMainRepo.update(updateWrapper);

View File

@ -6,8 +6,10 @@ import com.sczx.order.common.enums.PayStatusEnum;
import com.sczx.order.common.enums.PaymentTypeEnum;
import com.sczx.order.common.enums.SubOrderTypeEnum;
import com.sczx.order.po.OrderMainPO;
import com.sczx.order.po.OrderProcessPO;
import com.sczx.order.po.OrderSubPO;
import com.sczx.order.repository.OrderMainRepo;
import com.sczx.order.repository.OrderProcessRepo;
import com.sczx.order.repository.OrderSubRepo;
import com.sczx.order.thirdpart.dto.AlipayResponse;
import com.sczx.order.thirdpart.dto.req.AlipayRefundRequest;
@ -23,11 +25,15 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class RefundSubOrderProcessTask {
@Autowired
private OrderProcessRepo orderProcessRepo;
@Autowired
private OrderMainRepo orderMainRepo;
@ -101,27 +107,54 @@ public class RefundSubOrderProcessTask {
* @param subPO
*/
private void processRefund(OrderSubPO subPO) {
OrderMainPO orderMainPO = orderMainRepo.getById(subPO.getOrderId());
boolean fundResult = false;
if(PaymentTypeEnum.WX_PAY.getCode().equalsIgnoreCase(subPO.getPaymentMethod())){
Map<String, String> wxPayResult = payInteg.refundQuery(orderMainPO.getOperatorId(), subPO.getPaymentId());
String returnCode = wxPayResult.get("return_code");
if(StringUtils.isNotBlank(returnCode)){
fundResult = "SUCCESS".equals(returnCode);
}
} else {
AlipayRefundRequest request = new AlipayRefundRequest();
request.setOutTradeNo(subPO.getPaymentId());
request.setCompanyId(orderMainPO.getOperatorId());
AlipayResponse alipayResponse = payInteg.alipayRefundQuery(request);
fundResult = "SUCCESS".equals(alipayResponse.getCode());
OrderProcessPO orderProcessPO = orderProcessRepo.getOne(new LambdaQueryWrapper<OrderProcessPO>()
.eq(OrderProcessPO::getOrderNo, subPO.getSuborderNo())
.eq(OrderProcessPO::getProcessType, "RE_FUND"));
if(Objects.isNull(orderProcessPO)){
orderProcessPO = new OrderProcessPO();
orderProcessPO.setOrderId(subPO.getOrderId());
orderProcessPO.setProcessType("RE_FUND");
orderProcessPO.setOrderNo(subPO.getSuborderNo());
orderProcessPO.setRetryNum(1);
orderProcessRepo.save(orderProcessPO);
}else {
orderProcessPO.setRetryNum(orderProcessPO.getRetryNum()+1);
LambdaUpdateWrapper<OrderProcessPO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(OrderProcessPO::getId, orderProcessPO.getId())
.set(OrderProcessPO::getRetryNum, orderProcessPO.getRetryNum());
orderProcessRepo.update(updateWrapper);
}
if (fundResult) {
if(orderProcessPO.getRetryNum()>5){
log.info("支付单单{}已超过最大重试次数,强行关闭支付单", subPO.getSuborderNo());
LambdaUpdateWrapper<OrderSubPO> updateSubWrapper = new LambdaUpdateWrapper<>();
updateSubWrapper.set(OrderSubPO::getPayStatus, PayStatusEnum.REFUND_SUCCESS.getCode());
updateSubWrapper.set(OrderSubPO::getPayStatus, PayStatusEnum.CLOSE.getCode());
updateSubWrapper.eq(OrderSubPO::getSuborderId, subPO.getSuborderId());
orderSubRepo.update(updateSubWrapper);
} else {
OrderMainPO orderMainPO = orderMainRepo.getById(subPO.getOrderId());
boolean fundResult = false;
if(PaymentTypeEnum.WX_PAY.getCode().equalsIgnoreCase(subPO.getPaymentMethod())){
Map<String, String> wxPayResult = payInteg.refundQuery(orderMainPO.getOperatorId(), subPO.getPaymentId());
String returnCode = wxPayResult.get("return_code");
if(StringUtils.isNotBlank(returnCode)){
fundResult = "SUCCESS".equals(returnCode);
}
} else {
AlipayRefundRequest request = new AlipayRefundRequest();
request.setOutTradeNo(subPO.getPaymentId());
request.setCompanyId(orderMainPO.getOperatorId());
request.setOutRequestNo(subPO.getRefundId());
AlipayResponse alipayResponse = payInteg.alipayRefundQuery(request);
fundResult = "SUCCESS".equals(alipayResponse.getCode());
}
if (fundResult) {
LambdaUpdateWrapper<OrderSubPO> updateSubWrapper = new LambdaUpdateWrapper<>();
updateSubWrapper.set(OrderSubPO::getPayStatus, PayStatusEnum.REFUND_SUCCESS.getCode());
updateSubWrapper.eq(OrderSubPO::getSuborderId, subPO.getSuborderId());
orderSubRepo.update(updateSubWrapper);
}
}
}
}

View File

@ -0,0 +1,14 @@
package com.sczx.order.thirdpart.dto;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@ApiModel("支付宝资金冻结响应参数")
@Data
public class AlipayFundFreezeResponse {
private boolean success;
private String message;
private String outTradeNo; // 商户订单号
private String orderStr; // 支付串(用于前端调起支付)
private String code; // 状态码
}

View File

@ -0,0 +1,14 @@
package com.sczx.order.thirdpart.dto;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@ApiModel("支付宝资金冻结响应参数")
@Data
public class AlipayQueryFreezeResponse {
private boolean success;
private String message;
private String outTradeNo; // 商户订单号
private String authNo; // 支付串(用于前端调起支付)
private String code; // 状态码
}

View File

@ -65,4 +65,10 @@ public class CompanyStoreDTO {
@ApiModelProperty("以租代售分成比例")
private BigDecimal daishouRatio;
@ApiModelProperty("美团token")
private String mtToken;
@ApiModelProperty("抖音门店id")
private String dyStoreId;
}

View File

@ -62,4 +62,7 @@ public class UnifiedPaymentInfoDTO {
@ApiModelProperty(value = "支付宝交易号")
private String tradeNo; // 支付宝交易号
@ApiModelProperty(value = "支付宝芝麻信用免押支付串(用于前端调起支付)")
private String orderStr;
}

View File

@ -11,4 +11,5 @@ public class AlipayCreateRequest {
private String totalAmount; // 订单总金额
private String body; // 订单描述
private String openId; // 用户ID
private String authNo; //支付宝资金授权操作处理号
}

View File

@ -0,0 +1,19 @@
package com.sczx.order.thirdpart.dto.req;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("支付宝资金冻结请求参数")
@Data
public class AlipayFinishFreezeRequest {
private String companyId;
@ApiModelProperty(value = "支付宝资金授权订单号")
private String authNo;
@ApiModelProperty(value = "解冻请求流水号")
private String outRequestNo;
@ApiModelProperty(value = "解冻金额")
private String amount;
}

View File

@ -0,0 +1,13 @@
package com.sczx.order.thirdpart.dto.req;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@ApiModel("支付宝资金冻结请求参数")
@Data
public class AlipayFundFreezeRequest {
private String companyId;
private String outOrderNo;
private String title;
private String amount;
}

View File

@ -0,0 +1,12 @@
package com.sczx.order.thirdpart.dto.req;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@ApiModel("支付宝资金冻结请求参数")
@Data
public class AlipayQueryFreezeRequest {
private String companyId;
private String outOrderNo;
private String operationType;
}

View File

@ -0,0 +1,18 @@
package com.sczx.order.thirdpart.dto.req;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@ApiModel(value = "分润收入查询参数")
@Data
public class DistibIncomeReq {
@ApiModelProperty("门店id")
private Long storeId;
@ApiModelProperty("金额")
private BigDecimal amount ;
}

View File

@ -1,9 +1,6 @@
package com.sczx.order.thirdpart.facade;
import com.sczx.order.thirdpart.dto.AlipayCreateResponse;
import com.sczx.order.thirdpart.dto.AlipayQueryResponse;
import com.sczx.order.thirdpart.dto.AlipayResponse;
import com.sczx.order.thirdpart.dto.PaymentResponse;
import com.sczx.order.thirdpart.dto.*;
import com.sczx.order.thirdpart.dto.req.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
@ -63,6 +60,12 @@ public interface PayFacade {
@PostMapping("/api/alipay/unifiedOrder")
AlipayCreateResponse alipayUnifiedOrder(@RequestBody AlipayCreateRequest request);
/**
* 支付宝统一下单接口
*/
@PostMapping("/api/alipay/zhiMaOrder")
AlipayCreateResponse zhiMaOrder(@RequestBody AlipayCreateRequest request);
/**
* 查询订单接口
*/
@ -86,4 +89,32 @@ public interface PayFacade {
*/
@PostMapping("/api/alipay/refundQuery")
AlipayResponse alipayRefundQuery(@RequestBody AlipayRefundRequest request);
/**
* 冻结接口
*/
@PostMapping("/api/authAlipay/fundFreeze")
AlipayFundFreezeResponse fundFreeze(@RequestBody AlipayFundFreezeRequest request);
/**
* 取消冻结
*/
@PostMapping("/api/authAlipay/cancelFundFreeze")
AlipayFundFreezeResponse cancelFundFreeze(@RequestBody AlipayFundFreezeRequest request);
/**
* 查询冻结
* @param alipayQueryFreezeRequest
* @return
*/
@PostMapping("/api/authAlipay/queryFundFreeze")
AlipayQueryFreezeResponse queryFundFreeze(@RequestBody AlipayQueryFreezeRequest alipayQueryFreezeRequest);
/**
* 完结冻结
* @param alipayFinishFreezeRequest
* @return
*/
@PostMapping("/api/authAlipay/finishFreeze")
AlipayResponse finishFreeze(@RequestBody AlipayFinishFreezeRequest alipayFinishFreezeRequest);
}

View File

@ -5,6 +5,7 @@ import com.sczx.order.thirdpart.dto.CompanyDTO;
import com.sczx.order.thirdpart.dto.CompanyStoreDTO;
import com.sczx.order.thirdpart.dto.SysConfigDTO;
import com.sczx.order.thirdpart.dto.SysDictDataDTO;
import com.sczx.order.thirdpart.dto.req.DistibIncomeReq;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@ -32,4 +33,10 @@ public interface StoreFacade {
@GetMapping("/sys/getConfigByConfigKey")
Result<SysConfigDTO> getConfigByConfigKey(@RequestParam(name = "configKey") String configKey);
@PostMapping("/wallet/distibIncome")
Result<Boolean> distibIncome(@RequestBody DistibIncomeReq distibIncomeReq);
@PostMapping("/store/recordToken")
Result<Boolean> recordToken(@RequestParam(name = "storeNumber") String storeNumber, @RequestParam(name = "token") String token);
}

View File

@ -12,4 +12,7 @@ public interface SyncFacade {
@GetMapping("/send/subOrder/{subOrderId}")
Map<String, String> sendSubOrderId(@PathVariable Long subOrderId);
@GetMapping("/send/OrderMeal/{subOrderId}")
Map<String, String> sendUserMeal(@PathVariable Long subOrderId);
}

View File

@ -1,6 +1,7 @@
package com.sczx.order.thirdpart.facade;
import com.sczx.order.common.Result;
import com.sczx.order.dto.SimpleUserInfoDTO;
import com.sczx.order.thirdpart.dto.BaseUserReferralDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@ -11,4 +12,7 @@ public interface UserFacade {
@GetMapping("/referral/getUserReferralByUserId")
Result<BaseUserReferralDTO> getUserReferralByUserId(@RequestParam("userId") Long userId);
@GetMapping("/auth/getUInfoByMobile")
Result<SimpleUserInfoDTO> getUInfoByMobile(@RequestParam("mobile") String mobile);
}

View File

@ -122,7 +122,7 @@ public class PayInteg {
if(StringUtils.isNotBlank(returnCode)&&StringUtils.equalsIgnoreCase(returnCode, "SUCCESS")){
return true;
} else {
throw new InnerException("关闭订单失败");
return false;
}
}
@ -152,6 +152,26 @@ public class PayInteg {
}
}
/**
* 支付宝免押扣款接口
*/
public UnifiedPaymentInfoDTO zhiMaOrder(AlipayCreateRequest request){
try {
AlipayCreateResponse result = payFacade.zhiMaOrder(request);
if(StringUtils.isNotBlank(result.getCode()) && "SUCCESS".equals(result.getCode())){
UnifiedPaymentInfoDTO unifiedPaymentInfo = new UnifiedPaymentInfoDTO();
unifiedPaymentInfo.setOutTradeNo(result.getOutTradeNo());
unifiedPaymentInfo.setTradeNo(result.getTradeNo());
return unifiedPaymentInfo;
}else {
throw new InnerException("支付宝免押扣款失败");
}
} catch (Exception e){
log.error("支付宝免押扣款失败",e);
throw new InnerException("支付宝免押扣款失败");
}
}
/**
* 查询订单接口
*/
@ -220,4 +240,69 @@ public class PayInteg {
throw new InnerException("支付宝查询退款失败");
}
}
/**
* 免押冻结
*/
public AlipayFundFreezeResponse fundFreeze(AlipayFundFreezeRequest request) {
try {
AlipayFundFreezeResponse result = payFacade.fundFreeze( request);
if(StringUtils.isNotBlank(result.getCode()) && "SUCCESS".equals(result.getCode())){
return result;
} else {
throw new InnerException("免押冻结失败");
}
} catch (Exception e){
log.error("免押冻结失败",e);
throw new InnerException("免押冻结失败");
}
}
/**
* 取消冻结
*/
public boolean cancelFundFreeze(AlipayFundFreezeRequest request) {
try {
AlipayFundFreezeResponse result = payFacade.cancelFundFreeze( request);
if(StringUtils.isNotBlank(result.getCode()) && "SUCCESS".equals(result.getCode())){
return true;
} else {
return false;
}
} catch (Exception e){
log.error("取消免押冻结失败",e);
throw new InnerException("取消免押冻结失败");
}
}
public AlipayQueryFreezeResponse queryFundFreeze(AlipayQueryFreezeRequest alipayQueryFreezeRequest) {
try {
AlipayQueryFreezeResponse result = payFacade.queryFundFreeze( alipayQueryFreezeRequest);
if(StringUtils.isNotBlank(result.getCode()) && "SUCCESS".equals(result.getCode())){
return result;
} else {
return null;
}
} catch (Exception e){
log.error("查询免押押冻结失败",e);
throw new InnerException("查询免押冻结失败");
}
}
public AlipayResponse finishFreeze(AlipayFinishFreezeRequest alipayFinishFreezeRequest) {
try {
AlipayResponse result = payFacade.finishFreeze(alipayFinishFreezeRequest);
if(StringUtils.isNotBlank(result.getCode()) && "SUCCESS".equals(result.getCode())){
return result;
} else {
throw new InnerException("完成免押冻结失败");
}
} catch (Exception e){
log.error("完成免押押冻结失败",e);
throw new InnerException("完成免押冻结失败");
}
}
}

View File

@ -6,6 +6,7 @@ import com.sczx.order.thirdpart.dto.CompanyDTO;
import com.sczx.order.thirdpart.dto.CompanyStoreDTO;
import com.sczx.order.thirdpart.dto.SysConfigDTO;
import com.sczx.order.thirdpart.dto.SysDictDataDTO;
import com.sczx.order.thirdpart.dto.req.DistibIncomeReq;
import com.sczx.order.thirdpart.facade.StoreFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -86,4 +87,36 @@ public class StoreInteg {
}
return null;
}
/**
* 分润收入
* @param distibIncomeReq
* @return
*/
public boolean distibIncome(DistibIncomeReq distibIncomeReq){
try{
Result<Boolean> result = storeFacade.distibIncome(distibIncomeReq);
if(result.isSuccess()){
return result.getData();
}
} catch (Exception e){
log.error("分润收入失败",e);
throw new InnerException("分润收入失败");
}
return false;
}
public boolean recordToken(String storeNumber, String token){
try{
Result<Boolean> result = storeFacade.recordToken(storeNumber, token);
if(result.isSuccess()){
return result.getData();
}
} catch (Exception e){
log.error("记录门店token失败",e);
throw new InnerException("记录门店token失败");
}
return false;
}
}

View File

@ -18,16 +18,29 @@ public class SyncInteg {
public Map<String, String> sendSubOrderId(Long subOrderId){
try{
Map<String, String> result = syncFacade.sendSubOrderId(subOrderId);
if(result.get("code") == "200"){
if( "200".equals(result.get("code"))){
return result;
}else{
return result;
}
} catch (Exception e){
log.error("发送租电订单同步失败",e);
throw new InnerException("发送租电订单同步失败");
}
return null;
}
public Map<String, String> sendUserMeal(Long subOrderId){
try{
Map<String, String> result = syncFacade.sendUserMeal(subOrderId);
if( "200".equals(result.get("code"))){
return result;
}else{
return result;
}
} catch (Exception e){
log.error("发送租电订单查询失败",e);
}
return null;
}
}

View File

@ -1,12 +1,14 @@
package com.sczx.order.thirdpart.integration;
import com.sczx.order.common.Result;
import com.sczx.order.dto.SimpleUserInfoDTO;
import com.sczx.order.exception.InnerException;
import com.sczx.order.thirdpart.dto.BaseUserReferralDTO;
import com.sczx.order.thirdpart.facade.UserFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestParam;
@Slf4j
@Component
@ -27,4 +29,17 @@ public class UserInteg {
}
return null;
}
public SimpleUserInfoDTO getUInfoByMobile(String mobile){
try{
Result<SimpleUserInfoDTO> result = userFacade.getUInfoByMobile(mobile);
if(result.isSuccess()){
return result.getData();
}
} catch (Exception e){
log.error("获取用户信息失败",e);
throw new InnerException("获取用户信息失败");
}
return null;
}
}

View File

@ -0,0 +1,107 @@
package com.sczx.order.utils;
import com.aliyun.tea.TeaException;
import com.douyin.openapi.client.Client;
import com.douyin.openapi.client.models.OauthClientTokenRequest;
import com.douyin.openapi.client.models.OauthClientTokenResponse;
import com.douyin.openapi.credential.models.Config;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 抖音 client_token 管理器
* 负责定时获取和缓存 client_token
*/
public class DouyinTokenManager {
// 应用凭证信息
private static final String CLIENT_KEY = "awomt6nnjlfc491m";
private static final String CLIENT_SECRET = "c678c411c7a68c6f97969f2dbd8ef8fc";
// Token 缓存
private static final AtomicReference<OauthClientTokenResponse> tokenCache =
new AtomicReference<>();
// 定时任务执行器
private static final ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(1, r -> {
Thread t = new Thread(r, "DouyinTokenRefreshThread");
t.setDaemon(false);
return t;
});
static {
// 初始化时立即获取一次 token
refreshClientToken();
// 每小时更新一次 token (3600秒)
scheduler.scheduleAtFixedRate(
DouyinTokenManager::refreshClientToken,
3600,
3600,
TimeUnit.SECONDS
);
}
/**
* 获取当前有效的 client_token
*
* @return 当前有效的 access_token
*/
public static String getCurrentToken() {
OauthClientTokenResponse response = tokenCache.get();
if (response != null && response.getData() != null) {
return response.getData().getAccessToken();
}
return null;
}
/**
* 刷新 client_token
*/
private static void refreshClientToken() {
try {
Config config = new Config()
.setClientKey(CLIENT_KEY)
.setClientSecret(CLIENT_SECRET);
Client client = new Client(config);
OauthClientTokenRequest sdkRequest = new OauthClientTokenRequest();
sdkRequest.setClientKey(CLIENT_KEY);
sdkRequest.setClientSecret(CLIENT_SECRET);
sdkRequest.setGrantType("client_credential");
OauthClientTokenResponse sdkResponse = client.OauthClientToken(sdkRequest);
// 更新缓存
tokenCache.set(sdkResponse);
if (sdkResponse.getData() != null) {
System.out.println("抖音 client_token 更新成功,有效期至: " +
(System.currentTimeMillis() + sdkResponse.getData().getExpiresIn() * 1000));
}
} catch (TeaException e) {
System.err.println("获取抖音 client_token 失败 (TeaException): " + e.getMessage());
} catch (Exception e) {
System.err.println("获取抖音 client_token 失败 (Exception): " + e.getMessage());
}
}
/**
* 关闭定时任务
*/
public static void shutdown() {
scheduler.shutdown();
// 获取当前有效的 client_token
String token = DouyinTokenManager.getCurrentToken();
// 使用 token 调用抖音 API
if (token != null) {
// 调用订单查询等接口
}
}
}

View File

@ -0,0 +1,86 @@
package com.sczx.order.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@Slf4j
public class MeiTuanSignUtils {
private MeiTuanSignUtils() {}
public static String getSign(String signKey, Map<String, String> params) {
try {
String sortedStr = getSortedParamStr(params);
String paraStr = signKey + sortedStr;
return createSign(paraStr);
} catch (UnsupportedEncodingException e) {
log.warn("getSign UnsupportedEncodingException ", e);
}
return StringUtils.EMPTY;
}
/**
* 构造自然排序请求参数
*
* @param params 请求
* @return 字符串
*/
private static String getSortedParamStr(Map<String, String> params) throws UnsupportedEncodingException {
Set<String> sortedParams = new TreeSet<>(params.keySet());
StringBuilder strB = new StringBuilder();
// 排除sign和空值参数
for (String key : sortedParams) {
if ("sign".equalsIgnoreCase(key)) {
continue;
}
String value = params.get(key);
if (StringUtils.isNotEmpty(value)) {
strB.append(key).append(value);
}
}
return strB.toString();
}
/**
* 生成新sign
*
* @param str 字符串
* @return String
*/
private static String createSign(String str) {
if (str == null || str.length() == 0) {
return null;
}
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
mdTemp.update(str.getBytes("UTF-8"));
byte[] md = mdTemp.digest();
int j = md.length;
char[] buf = new char[j * 2];
int k = 0;
int i = 0;
while (i < j) {
byte byte0 = md[i];
buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
buf[k++] = hexDigits[byte0 & 0xf];
i++;
}
return new String(buf);
} catch (Exception e) {
log.warn("create sign was failed", e);
return null;
}
}
}

View File

@ -1,6 +1,7 @@
package com.sczx.order.utils;
import com.sczx.order.common.enums.RentCarTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import java.math.BigDecimal;
@ -9,10 +10,12 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.UUID;
@Slf4j
public class OrderUtil {
public static final String ORDER_PREFIX = "OC";
public static final String MY_PREFIX = "MY";
public static final String YJ_PREFIX = "YJ";
public static final String ZD_PREFIX = "ZD";
@ -25,6 +28,10 @@ public class OrderUtil {
public static final String FD_PREFIX = "FD";
public static final String FZ_PREFIX = "FZ";
public static final String FFZ_PREFIX = "FFZ";
/**
* 生成订单号的方法
* @return 唯一订单号字符串
@ -55,8 +62,10 @@ public class OrderUtil {
public static Integer getOrderOverdueHours(LocalDateTime endRentTime) {
if(endRentTime!=null){
LocalDateTime now = LocalDateTime.now();
log.info("计算间隔小时数,预计还车时间:{}, 当前时间 : {}", endRentTime, now);
if(now.isAfter(endRentTime)){
return (int) ChronoUnit.HOURS.between(endRentTime, now);
int hours = (int) ChronoUnit.HOURS.between(endRentTime, now);
return hours +1;
}
}
return 0;
@ -70,8 +79,14 @@ public class OrderUtil {
public static Integer getOrderOverdueDays(LocalDateTime endRentTime) {
if(endRentTime!=null){
LocalDateTime now = LocalDateTime.now();
log.info("计算间隔天数,预计还车时间:{}, 当前时间 : {}", endRentTime, now);
if(now.isAfter(endRentTime)){
return (int) ChronoUnit.DAYS.between(endRentTime, now);
long days = ChronoUnit.DAYS.between(endRentTime, now);
// 检查是否有不足一天的余数
if (!now.truncatedTo(ChronoUnit.DAYS).equals(endRentTime.plusDays(days).truncatedTo(ChronoUnit.DAYS))) {
return (int) days + 1;
}
return (int) days;
}
}
return 0;
@ -93,6 +108,31 @@ public class OrderUtil {
return 0;
}
/**
* 计算间隔时间
* @param startTime
* @param endTime
* @param rentalType
* @return
*/
public static Integer getDuration(LocalDateTime startTime,LocalDateTime endTime,String rentalType) {
if(startTime!=null&&endTime!=null){
if(startTime.isBefore(endTime)){
if(StringUtils.equalsIgnoreCase(rentalType, RentCarTypeEnum.HOUR_RENTAL.getCode())){
return (int) ChronoUnit.HOURS.between(startTime, endTime) +1;
} else {
long days = ChronoUnit.DAYS.between(startTime, endTime) +1;
// 检查是否有不足一天的余数
if (!endTime.truncatedTo(ChronoUnit.DAYS).equals(endTime.plusDays(days).truncatedTo(ChronoUnit.DAYS))) {
return (int) days + 1;
}
return (int) days;
}
}
}
return 0;
}
/**
* 计算订单逾期金额
@ -114,19 +154,64 @@ public class OrderUtil {
* @param rentalType 租期类型
* @return 预计还车时间
*/
public static LocalDateTime getEndRentTime(LocalDateTime origTime,Integer rentalDays, String rentalType) {
public static LocalDateTime getEndRentTime(LocalDateTime origTime,Integer rerentInterval,Integer rentalDays, String rentalType) {
LocalDateTime endRentTime = null;
//设置预计还车时间
if(StringUtils.equalsIgnoreCase(RentCarTypeEnum.HOUR_RENTAL.getCode(), rentalType)){
endRentTime = origTime.plusHours(1);
endRentTime = origTime.plusHours(rerentInterval);
}else if(StringUtils.equalsIgnoreCase(RentCarTypeEnum.DAILY_RENTAL.getCode(), rentalType)){
endRentTime = origTime.plusDays(1);
endRentTime = origTime.plusDays(rerentInterval);
} else if(StringUtils.equalsIgnoreCase(RentCarTypeEnum.DAYS_RENTAL.getCode(), rentalType)){
endRentTime = origTime.plusDays(rentalDays);
endRentTime = origTime.plusDays((long) rerentInterval * rentalDays);
} else if(StringUtils.equalsIgnoreCase(RentCarTypeEnum.RENT_INSTEAD_SELL.getCode(), rentalType)){
//以租代售默认期限为30天
endRentTime = origTime.plusDays(30);
endRentTime = origTime.plusDays(rerentInterval*30);
}
log.info("query endRentTime : {}", endRentTime);
return endRentTime;
}
/**
* 获取租车订单金额
* @param rentalType
* @param rentalPrice
* @param rentalDays
* @return
*/
public static BigDecimal getRentCarAmount(String rentalType, BigDecimal rentalPrice, Integer rentalDays) {
// BigDecimal rentCarOrderAmount = new BigDecimal(0);
// if(RentCarTypeEnum.HOUR_RENTAL.getCode().equalsIgnoreCase(rentalType)||RentCarTypeEnum.DAILY_RENTAL.getCode().equalsIgnoreCase(rentalType)){
// rentCarOrderAmount = rentCarOrderAmount.add(rentalPrice);
// } else if(RentCarTypeEnum.DAYS_RENTAL.getCode().equalsIgnoreCase(rentalType)){
// rentCarOrderAmount = rentalPrice.multiply(new BigDecimal(rentalDays));
// } else {
// rentCarOrderAmount = rentalPrice.multiply(new BigDecimal(30));
// }
return rentalPrice;
}
/**
* 获取续租订单金额
* @param rerentInterval 续租周期
* @param rentalPrice
* @return
*/
public static BigDecimal getReRentCarAmount(int rerentInterval,BigDecimal rentalPrice) {
//计算续租金额
return rentalPrice.multiply(new BigDecimal(rerentInterval));
}
/**
* 计算续租轮数以覆盖逾期天数
* @param overdueDays 逾期天数
* @param rentalDays 每轮续租天数
* @return 需要的续租轮数
*/
public static int calculateRerentRoundsToCoverOverdue(Integer overdueDays, Integer rentalDays) {
overdueDays = overdueDays == null|| overdueDays == 0 ? 1 : overdueDays;
rentalDays = rentalDays == null|| rentalDays == 0 ? 1 : overdueDays;
// 使用向上取整计算需要的续租轮数
return (int) Math.ceil((double) overdueDays / rentalDays);
}
}

View File

@ -24,6 +24,10 @@ public class RedisUtil {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}

Binary file not shown.

Binary file not shown.

View File

@ -22,16 +22,16 @@
<pattern>${PATTERN}</pattern>
</layout>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>DENY</onMatch>
<onMismatch>NEUTRAL</onMismatch>
</filter>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>NEUTRAL</onMismatch>
</filter>
<!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- <level>WARN</level>-->
<!-- <onMatch>DENY</onMatch>-->
<!-- <onMismatch>NEUTRAL</onMismatch>-->
<!-- </filter>-->
<!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- <level>ERROR</level>-->
<!-- <onMatch>DENY</onMatch>-->
<!-- <onMismatch>NEUTRAL</onMismatch>-->
<!-- </filter>-->
</appender>
<appender name="WARN_FILE_APPENDER" class="ch.qos.logback.core.FileAppender">
@ -45,6 +45,18 @@
</encoder>
</appender>
<!-- 针对 MyBatis SQL 日志 -->
<logger name="org.apache.ibatis" level="DEBUG" additivity="false">
<appender-ref ref="INFO_FILE_APPENDER"/>
<appender-ref ref="STDOUT"/>
</logger>
<!-- 针对 JDBC SQL 日志 -->
<logger name="java.sql" level="DEBUG" additivity="false">
<appender-ref ref="INFO_FILE_APPENDER"/>
<appender-ref ref="STDOUT"/>
</logger>
<root level="info">
<appender-ref ref="INFO_FILE_APPENDER"/>
<appender-ref ref="WARN_FILE_APPENDER"/>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sczx.order.mapper.ElectronicFenceRuleMapper">
</mapper>

View File

@ -6,8 +6,8 @@
SELECT
store_id,
COUNT(*) as total_orders,
SUM(CASE WHEN (order_status = 'AUTO_END' OR order_status = 'MANUAL_END') AND DATE_FORMAT(end_order_time, '%Y-%m') = DATE_FORMAT(NOW(), '%Y-%m') THEN 1 ELSE 0 END) as monthly_completed_orders,
SUM(CASE WHEN (order_status = 'AUTO_END' OR order_status = 'MANUAL_END') AND DATE_FORMAT(end_order_time, '%Y-%m') = DATE_FORMAT(NOW(), '%Y-%m') THEN order_amount ELSE 0 END) as monthly_order_amount,
SUM(CASE WHEN (order_status = 'AUTO_END' OR order_status = 'MANUAL_END') AND DATE_FORMAT(ifnull(end_order_time,act_end_rent_time), '%Y-%m') = DATE_FORMAT(NOW(), '%Y-%m') THEN 1 ELSE 0 END) as monthly_completed_orders,
SUM(CASE WHEN (order_status = 'AUTO_END' OR order_status = 'MANUAL_END') AND DATE_FORMAT(ifnull(end_order_time,act_end_rent_time), '%Y-%m') = DATE_FORMAT(NOW(), '%Y-%m') THEN order_amount ELSE 0 END) as monthly_order_amount,
COUNT(CASE WHEN order_status = 'WAIT_PICK' THEN 1 END) as pending_pickup_count,
COUNT(CASE WHEN order_status = 'WAIT_RETURN' THEN 1 END) as pending_return_count,
COUNT(CASE WHEN order_status = 'RENT_OVERDUE' THEN 1 END) as overdue_count
@ -82,6 +82,7 @@
<where>
zos.del_flag = '0'
and zos.suborder_type = 'RENTBATTEY'
and zom.del_flag = '0'
<if test="customerId != null">
and zom.customer_id = #{customerId}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sczx.order.mapper.OrderProcessMapper">
</mapper>