Merge branch 'test' into prod

# Conflicts:
#	cash-api/account-server/src/main/java/com/czg/controller/admin/AuthorizationController.java
#	cash-api/order-server/src/main/java/com/czg/controller/DistributionPayController.java
#	cash-service/order-service/src/main/java/com/czg/service/order/service/impl/OrderInfoCustomServiceImpl.java
#	cash-service/order-service/src/main/java/com/czg/service/order/service/impl/PayServiceImpl.java
This commit is contained in:
2026-01-17 09:52:55 +08:00
209 changed files with 11116 additions and 3013 deletions

View File

@@ -0,0 +1,14 @@
package com.czg.service.account.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.account.entity.QuickMenu;
/**
* 悬浮窗配置 映射层。
*
* @author ww
* @since 2025-12-29
*/
public interface QuickMenuMapper extends BaseMapper<QuickMenu> {
}

View File

@@ -14,9 +14,9 @@ import com.czg.config.RedisCst;
import com.czg.constants.ParamCodeCst;
import com.czg.exception.CzgException;
import com.czg.resp.CzgResult;
import com.czg.service.RedisService;
import com.czg.service.account.mapper.CallQueueMapper;
import com.czg.service.account.mapper.CallTableMapper;
import com.czg.service.account.util.FunUtil;
import com.czg.service.account.util.WechatMiniMsgUtil;
import com.czg.system.dto.SysParamsDTO;
import com.czg.system.service.SysParamsService;
@@ -29,6 +29,7 @@ import jakarta.annotation.Resource;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.io.ByteArrayOutputStream;
import java.util.*;
@@ -41,7 +42,7 @@ import java.util.stream.Collectors;
* @author zs
* @since 2025-02-21
*/
@DubboService
@Service
public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable> implements CallTableService {
@DubboReference
private SysParamsService sysParamsService;
@@ -51,10 +52,10 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
@Resource
private ShopUserService shopUserService;
@Resource
private FunUtil funUtil;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedisService redisService;
@Resource
private ShopInfoService shopInfoService;
@Resource
private CallConfigService callConfigService;
@@ -228,13 +229,13 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
}
private String getCallNumber(Long shopId, CallTable callTable) {
return funUtil.runFunAndCheckKey(() -> {
return redisService.runFunAndCheckKey(() -> {
String callNumKey = RedisCst.getTableCallNumKey(shopId, callTable.getId());
String value = stringRedisTemplate.opsForValue().get(callNumKey);
AtomicReference<String> newVal = new AtomicReference<>("");
// 初始化
if (StrUtil.isBlank(value)) {
Boolean setFlag = FunUtil.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
Boolean setFlag = RedisService.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
_ -> newVal.set(stringRedisTemplate.opsForValue().get(callNumKey)));
if (setFlag) {

View File

@@ -0,0 +1,18 @@
package com.czg.service.account.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.account.entity.QuickMenu;
import com.czg.account.service.QuickMenuService;
import com.czg.service.account.mapper.QuickMenuMapper;
import org.springframework.stereotype.Service;
/**
* 悬浮窗配置 服务层实现。
*
* @author ww
* @since 2025-12-29
*/
@Service
public class QuickMenuServiceImpl extends ServiceImpl<QuickMenuMapper, QuickMenu> implements QuickMenuService{
}

View File

@@ -1,55 +0,0 @@
package com.czg.service.account.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.czg.account.dto.merchant.ShopMerchantEditDTO;
import com.czg.account.entity.ShopMerchant;
import com.czg.account.service.ShopMerchantService;
import com.czg.sa.StpKit;
import com.czg.service.account.mapper.ShopMerchantMapper;
import com.czg.utils.AssertUtil;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import java.io.Serializable;
/**
* 第三方商户进件 服务层实现。
*
* @author Administrator
* @since 2025-02-11
*/
@CacheConfig(cacheNames = "shop:merchant")
@DubboService
public class ShopMerchantServiceImpl extends ServiceImpl<ShopMerchantMapper, ShopMerchant> implements ShopMerchantService {
@Override
public ShopMerchant detail(Integer shopId) {
ShopMerchant one = queryChain().eq(ShopMerchant::getShopId, shopId).one();
return one == null ? new ShopMerchant() : one;
}
@CacheEvict(key = "#shopMerchantEditDTO.shopId")
@Override
public Boolean edit(ShopMerchantEditDTO shopMerchantEditDTO) {
ShopMerchant shopMerchant = queryChain().eq(ShopMerchant::getShopId, shopMerchantEditDTO.getShopId()).one();
if (shopMerchant == null) {
shopMerchant = new ShopMerchant();
shopMerchant.setShopId(shopMerchantEditDTO.getShopId());
BeanUtil.copyProperties(shopMerchantEditDTO, shopMerchant);
return save(shopMerchant);
}
BeanUtil.copyProperties(shopMerchantEditDTO, shopMerchant);
return updateById(shopMerchant);
}
@Cacheable(key = "#id")
@Override
public ShopMerchant getById(Serializable id) {
ShopMerchant shopMerchant = getMapper().selectOneById(id);
AssertUtil.isNull(shopMerchant, "暂未开通支付");
return shopMerchant;
}
}

View File

@@ -22,7 +22,6 @@ import com.czg.market.vo.InviteUserVO;
import com.czg.market.vo.MemberConfigVO;
import com.czg.order.entity.OrderInfo;
import com.czg.service.account.mapper.ShopUserMapper;
import com.czg.service.account.util.FunUtil;
import com.czg.utils.FunUtils;
import com.czg.utils.PageUtil;
import com.github.pagehelper.PageHelper;

View File

@@ -1,100 +0,0 @@
package com.czg.service.account.util;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author Administrator
*/
@Slf4j
@Component
public class FunUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public static int retryCount = 5;
/**
* 执行任务并保证锁唯一
* @param supplier 业务逻辑
* @param lockKey Redis锁的Key
* @return 业务逻辑返回值
*/
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
try {
// 尝试获取锁,超时时间 5 秒,防止死锁
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
int count = 0;
// 初始等待 10ms
int retryDelay = 10;
while (!lock) {
// 最多重试 10 次,大约 10 秒
if (count++ > 50) {
throw new RuntimeException("系统繁忙, 稍后再试");
}
Thread.sleep(retryDelay);
// 指数退避,最大等待 200ms
retryDelay = Math.min(retryDelay * 2, 200);
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
}
// 执行任务
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("线程被中断", e);
} catch (Exception e) {
log.error("执行出错:{}", e.getMessage(), e);
throw e;
} finally {
// 释放锁(使用 Lua 脚本确保原子性)
unlock(lockKey, lockValue);
}
}
/**
* 使用 Lua 脚本确保释放锁的原子性
* @param lockKey 锁的 Key
* @param lockValue 当前线程的锁值
*/
private void unlock(String lockKey, String lockValue) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(lockKey), lockValue);
}
public static <T, R> R runFunAndRetry(
Supplier<R> function,
Function<R, Boolean> check, Consumer<R> errFun) {
log.info("工具类开始执行函数");
R result = function.get();
boolean flag = check.apply(result);
log.info("执行结果: {}", result);
while (flag && retryCount-- > 0) {
log.info("执行函数失败, 剩余尝试次数{}", retryCount);
result = function.get();
log.info("执行结果: {}", result);
flag = check.apply(result);
}
if (flag) {
errFun.accept(result);
}
return result;
}
}

View File

@@ -0,0 +1,7 @@
<?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.czg.service.account.mapper.QuickMenuMapper">
</mapper>

View File

@@ -18,9 +18,8 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.czg</groupId>
<artifactId>pay-service</artifactId>
<version>1.0.0</version>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-All</artifactId>
</dependency>
<dependency>

View File

@@ -1,12 +1,12 @@
package com.czg.service.market.service.impl;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.constants.ParamCodeCst;
import com.czg.exception.CzgException;
import com.czg.service.RedisService;
import com.czg.system.service.SysParamsService;
import com.ijpay.core.kit.RsaKit;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
@@ -14,7 +14,6 @@ import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
/**
* 微信支付service
@@ -80,7 +79,7 @@ public class AppWxServiceImpl extends BaseWx {
// api支付证书公钥
config.apiCert = payKeyMap.get(ParamCodeCst.Wechat.Pay.WX_API_CLIENT_CERT);
try {
config.privateKey = RsaKit.loadPrivateKey(config.apiCertKey);
config.privateKey = SecureUtil.rsa(config.apiCertKey, null).getPrivateKey();
} catch (Exception e) {
log.warn("微信加载api证书失败");
}
@@ -98,10 +97,6 @@ public class AppWxServiceImpl extends BaseWx {
config.refundNotifyUrl = "";
config.transferNotifyUrl = payKeyMap.get(ParamCodeCst.System.NATIVE_NOTIFY_URL) + "/wx/transfer";
}
public BaseWx getAppService() {
return this;
}
}

View File

@@ -20,12 +20,12 @@
<dependencies>
<dependency>
<groupId>com.czg</groupId>
<artifactId>pay-service</artifactId>
<artifactId>market-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.czg</groupId>
<artifactId>market-service</artifactId>
<artifactId>pay-service</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 订单服务依赖消息队列 从 market 拿 -->

View File

@@ -0,0 +1,81 @@
package com.czg.service.order.dto;
import com.czg.dto.req.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* @author ww
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class AggregateMerchantVO extends AggregateMerchantDto{
private String shopName;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private String errorMsg;
/**
* {@link com.czg.PayCst.EntryStatus}
* 微信状态
* WAIT 待提交 INIT 待处理 AUDIT 审核中 SIGN 待签约 REJECTED 失败 FINISH 已完成
*/
private String wechatStatus;
/**
* 支付宝账号
*/
private String alipayAccount;
/**
* 商户编号(在当前系统唯一)
*/
private String merchantCode;
/**
* 【必填】
* 商户类型
* 0: 个体商户;
* 1: 企业商户;
* 3: 小微商户 暂不支持
*/
private String userType;
/**
* 【必填】
* 商户简称--企业、个体必填
*/
private String shortName;
/**
* 微信进件错误信息
*/
private String wechatErrorMsg;
/**
* 微信进件签名地址
*/
private String wechatSignUrl;
private String wechatApplyId;
private String alipayOrderId;
/**
* {@link com.czg.PayCst.EntryStatus}
* 支付宝状态
* WAIT 待提交 INIT 待处理 AUDIT 审核中 SIGN 待签约 REJECTED 失败 FINISH 已完成
*/
private String alipayStatus;
/**
* 支付宝进件错误信息
*/
private String alipayErrorMsg;
/**
* 支付宝进件签名地址
*/
private String alipaySignUrl;
}

View File

@@ -1,44 +0,0 @@
package com.czg.service.order.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 创建订单
* @author ww
*/
@Data
public class CreateOrderDTO {
@NotBlank(message = "桌号不能为空")
private String tableCode;
@NotBlank(message = "用餐模式 堂食 dine-in 外带 take-out 外卖 take-away")
private String dineMode;
/**
* 平台类型
* 微信小程序 WX
* 支付宝小程序 ALI
* 收银机客户端 PC
* PC管理端 APC
* APP管理端 APP
*/
@NotBlank(message = "平台类型不能为空")
private String platformType;
/**
* 是否使用了霸王餐
*/
private boolean isFreeDine = false;
private String remark;
// 使用的积分抵扣数量
private Integer pointsNum;
// 使用的优惠券
@Valid
private List<UserCouponInfoDTO> userCouponInfos = new ArrayList<>();
}

View File

@@ -0,0 +1,58 @@
package com.czg.service.order.dto;
import com.czg.BaseQueryParam;
import com.czg.utils.CzgStrUtils;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 进件查询参数
* @author ww
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class MerchantQueryDTO extends BaseQueryParam {
/**
* 商户类型
* 0: 个体商户;
* 1: 企业商户;
*/
private String userType;
/**
* 店铺名称 模糊查询
*/
private String shopName;
/**
* 进件状态
* {@link com.czg.PayCst.EntryStatus}
* WAIT 待提交
* INIT 待处理
* AUDIT 待审核
* SIGN 待签约
* FINISH 已完成
* REJECTED 失败
*/
private String status;
/**
* 支付宝账号
*/
private String alipayAccount;
public String getUserType() {
return CzgStrUtils.getStrOrNull(userType);
}
public String getShopName() {
return CzgStrUtils.getStrOrNull(shopName);
}
public String getStatus() {
return CzgStrUtils.getStrOrNull(status);
}
public String getAlipayAccount() {
return CzgStrUtils.getStrOrNull(alipayAccount);
}
}

View File

@@ -1,56 +0,0 @@
package com.czg.service.order.dto;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
/**
* 支付接收参数 实体类
*
* @author ww
* @description
*/
@Data
public class RechargeDTO {
@NotNull(message = "店铺不能为空")
private Long shopId;
private Long shopUserId;
/**
* 充值金额id
*/
private Long rechargeDetailId;
@DecimalMin("0.01")
private BigDecimal amount;
private Integer allPack;
/**
* 跳转地址
*/
private String returnUrl;
/**
* 平台类型 pc 收银机客户端 wechat 微信小程序 alipay 支付宝小程序 admin-pc PC管理端 admin-app APP管理端
*/
private String platformType;
private String openId;
private String buyerRemark;
private String payType;
private Long orderId;
private Integer seatNum;
/**
* 用户端 使用 全打包 或者 全不打包
*/
private Integer userAllPack;
public boolean isAllPack() {
return allPack != null && allPack == 1;
}
}

View File

@@ -1,12 +0,0 @@
package com.czg.service.order.dto;
import jakarta.validation.constraints.Min;
import lombok.Data;
@Data
public class UserCouponInfoDTO {
private Long userCouponId;
@Min(1)
private Integer num;
}

View File

@@ -0,0 +1,19 @@
package com.czg.service.order.mapper;
import com.czg.service.order.dto.MerchantQueryDTO;
import com.mybatisflex.core.BaseMapper;
import com.czg.order.entity.ShopDirectMerchant;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 商户进件 映射层。
*
* @author ww
* @since 2026-01-07
*/
public interface ShopDirectMerchantMapper extends BaseMapper<ShopDirectMerchant> {
List<ShopDirectMerchant> getEntryList(@Param("queryParam") MerchantQueryDTO queryParam);
}

View File

@@ -1,6 +1,6 @@
package com.czg.service.account.mapper;
package com.czg.service.order.mapper;
import com.czg.account.entity.ShopMerchant;
import com.czg.order.entity.ShopMerchant;
import com.mybatisflex.core.BaseMapper;
/**

View File

@@ -1,17 +1,18 @@
package com.czg.service.order.print;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.czg.account.dto.HandoverRecordDTO;
import com.czg.account.entity.HandoverRecord;
import com.czg.account.entity.PrintMachine;
import com.czg.account.entity.ShopInfo;
import com.czg.account.service.ShopInfoService;
import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import com.czg.service.order.enums.OrderStatusEnums;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.http.HttpEntity;
@@ -20,24 +21,21 @@ import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Administrator
* 接口文档 <a href="https://help.feieyun.com/home/doc/zh;nav=1-1">
*/
@Component
@Slf4j
public class FeiPrinter extends PrinterHandler implements PrinterImpl {
@Resource
private RestTemplate restTemplate;
@Resource
private ShopInfoService shopInfoService;
// API 地址
private static final String URL = "http://api.feieyun.cn/Api/Open/";
@@ -72,10 +70,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
String remark = orderDetail.getRemark();
String content = buildDishPrintData(false, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
Object o = sendPrintRequest(machine.getAddress(), content, null, "1");
printMachineLogService.save(machine, "新订单", content, o);
// printMachineLogService.save(machine, "新订单", , );
String o = sendPrintRequest(machine.getAddress(), content, null, "1");
printMachineLogService.save(orderInfo.getId(), machine, "新订单", content, o);
}
@@ -84,8 +80,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
String remark = orderDetail.getRemark();
String content = buildDishPrintData(true, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getReturnNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
Object o = sendPrintRequest(machine.getAddress(), content, null, "1");
printMachineLogService.save(machine, "退款单", content, o);
String o = sendPrintRequest(machine.getAddress(), content, null, "1");
printMachineLogService.save(orderInfo.getId(), machine, "退款单", content, o);
}
@@ -106,8 +102,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
.setRemark(orderInfo.getRemark())
.setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
String string = buildOrderPrintData(printInfoDTO, detailList);
Object o = sendPrintRequest(machine.getAddress(), string, null, printerNum);
printMachineLogService.save(machine, "退款", string, o);
String o = sendPrintRequest(machine.getAddress(), string, null, printerNum);
printMachineLogService.save(orderInfo.getId(), machine, "结算", string, o);
}
@@ -134,24 +130,24 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
.setRemark(orderInfo.getRemark())
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle());
if(orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0){
if (orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0) {
printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString());
printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
}
if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){
if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) {
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
}
String string = buildOrderPrintData(printInfoDTO, detailList);
Object resp = sendPrintRequest(machine.getAddress(), string, null, printerNum);
printMachineLogService.save(machine, "结算单", string, resp);
String resp = sendPrintRequest(machine.getAddress(), string, null, printerNum);
printMachineLogService.save(orderInfo.getId(), machine, "结算单", string, resp);
}
@Override
protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) {
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一条新的排号记录\"}";
String data = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
Object resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1");
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1");
printMachineLogService.save(machine, "叫号单", data, resp);
}
@@ -170,7 +166,7 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
}
@Override
public <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) {
public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) {
log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData);
String time = String.valueOf(System.currentTimeMillis() / 1000);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
@@ -189,12 +185,68 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
String result = restTemplate.postForObject(URL, requestEntity, String.class);
log.info("打印结果: {}", result);
return (R) result;
log.info("飞鹅打印结果: {}", result);
return result;
} catch (Exception e) {
log.error("打印请求失败: {}", e.getMessage());
throw new RuntimeException("飞蛾打印请求失败", e);
}
}
/**
* 检查飞鹅打印机 打印任务是否已打印
*
* @param printOrderId 打印订单编号
* @return null-未知错误true-已打印false-未打印
*/
public Boolean checkFPrintStatus(String printOrderId) {
String time = String.valueOf(System.currentTimeMillis() / 1000);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("user", USER);
paramMap.put("stime", time);
paramMap.put("sig", signature(time));
paramMap.put("apiname", "Open_queryOrderState");
paramMap.put("orderid", printOrderId);
Boolean ret;
try {
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
//成功 {"msg":"ok","ret":0,"data":true,"serverExecutedTime":4}
//失败 {"msg":"ok","ret":0,"data":false,"serverExecutedTime":4}
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
log.info("飞鹅打印机 打印任务状态响应: {}", json);
ret = json.getBool("data");
} catch (Exception e) {
ret = null;
}
return ret;
}
/**
* 检查飞鹅打印机是否在线
*
* @param sn 设备编号
* @return 在线,工作状态正常。/离线。/未知错误
*/
public String checkOnline(String sn) {
String time = String.valueOf(System.currentTimeMillis() / 1000);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("user", USER);
paramMap.put("stime", time);
paramMap.put("sig", signature(time));
paramMap.put("apiname", "Open_queryPrinterStatus");
paramMap.put("sn", sn);
String msg;
try {
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
//成功 开机 {"msg":"ok","ret":0,"data":"在线,工作状态正常。","serverExecutedTime":4}
//成功 离线 {"msg":"ok","ret":0,"data":"离线。","serverExecutedTime":7}
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
log.info("飞鹅打印机状态响应: {}", json);
msg = json.getStr("data");
} catch (Exception e) {
msg = "未知错误";
}
return msg;
}
}

View File

@@ -34,6 +34,7 @@ import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -49,10 +50,9 @@ public abstract class PrinterHandler {
@Setter
protected PrinterHandler nextPrinter;
protected String printerBrand;
// 创建 ThreadLocal 变量
private static final ThreadLocal<String> ERR_MSG = ThreadLocal.withInitial(() -> "");
@Resource
protected RestTemplate restTemplate;
@Resource
protected OrderDetailService orderDetailService;
@Resource
@@ -84,7 +84,11 @@ public abstract class PrinterHandler {
@Getter
public enum PrintTypeEnum {
HANDOVER("交班", "handover"),
ORDER("订单", "order"), ONE("菜品", "one"), CALL("叫号", "call"), ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"), PRE_ORDER("预结算单", "preOrder");
ORDER("订单", "order"),
ONE("菜品", "one"),
CALL("叫号", "call"),
ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"),
PRE_ORDER("预结算单", "preOrder");
private final String name;
private final String code;
@@ -157,7 +161,7 @@ public abstract class PrinterHandler {
if (StrUtil.isNotEmpty(printMethod)) {
List<String> arrayList = switch (printMethod) {
case "all" ->Arrays.asList("one", "normal", "all");
case "all" -> Arrays.asList("one", "normal", "all");
case "one" -> Arrays.asList("one", "all");
case "normal" -> Arrays.asList("normal", "all");
default -> new ArrayList<>();
@@ -173,10 +177,6 @@ public abstract class PrinterHandler {
wrapper.like(PrintMachine::getPrintType, printType);
}
List<PrintMachine> list = printMachineService.list(wrapper);
// for (PrintMachine item : list) {
// //实际打印以传递的参数为准
// item.setPrintMethod(printMethod);
// }
if (list.isEmpty()) {
log.error("店铺未配置打印机店铺id: {}", shopId);
return list;
@@ -188,7 +188,8 @@ public abstract class PrinterHandler {
/**
* 处理打印
* @param data 传递的数据
*
* @param data 传递的数据
* @param printTypeEnum order returnOrder preOrder one call handover
*/
public void handler(String data, PrintTypeEnum printTypeEnum) {
@@ -314,7 +315,7 @@ public abstract class PrinterHandler {
item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum()));
item.setReturnNum(item.getReturnNum().subtract(printDetailInfo.getPrintReturnNum()));
orderDetails.add(item);
}else {
} else {
orderDetails.add(item);
}
@@ -334,34 +335,37 @@ public abstract class PrinterHandler {
log.info("准备开始打印交班");
if (data instanceof HandoverRecordDTO record) {
handoverPrint(machine, record);
}else {
} else {
throw new RuntimeException("传递数据类型有误");
}
break;
case PrintTypeEnum.ORDER:
log.info("准备开始打印订单");
if (data instanceof OrderInfo orderInfo) {
redisService.set("order:print:" + orderInfo.getId(),"", 180);
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
onlyFrontDesk(machine, false, orderInfo, orderDetailList);
}else {
} else {
throw new RuntimeException("传递数据类型有误");
}
break;
case PrintTypeEnum.PRE_ORDER:
log.info("准备开始打印预结算订单");
if (data instanceof OrderInfo orderInfo) {
redisService.set("order:print:" + orderInfo.getId(),"", 180);
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
onlyFrontDesk(machine, true, orderInfo, orderDetailList);
}else {
} else {
throw new RuntimeException("传递数据类型有误");
}
break;
case PrintTypeEnum.ONE:
log.info("准备开始打印菜品单");
if (data instanceof OrderInfo orderInfo) {
redisService.set("order:print:" + orderInfo.getId(),"", 180);
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
onlyKitchen(machine, orderInfo, orderDetailList);
}else {
} else {
throw new RuntimeException("传递数据类型有误");
}
break;
@@ -369,13 +373,14 @@ public abstract class PrinterHandler {
log.info("准备开始打印叫号单");
if (data instanceof CallQueue queue) {
onlyCallNumPrint(machine, queue);
}else {
} else {
throw new RuntimeException("传递数据类型有误");
}
break;
case PrintTypeEnum.ONE_AND_ORDER:
log.info("准备开始打印菜品以及结算单");
if (data instanceof OrderInfo orderInfo) {
redisService.set("order:print:" + orderInfo.getId(),"", 180);
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
switch (machine.getPrintMethod()) {
case "all":
@@ -393,7 +398,7 @@ public abstract class PrinterHandler {
default:
throw new RuntimeException("打印方法有误");
}
}else {
} else {
throw new RuntimeException("传递数据类型有误");
}
@@ -448,17 +453,7 @@ public abstract class PrinterHandler {
log.info("台位费商品,不打印");
return;
}
// ProdSku sku = prodSkuService.getById(item.getSkuId());
// if (isTemporary == 0 && sku == null) {
// log.error("商品不存在, id: {}", item.getSkuId());
// return;
// } else if (isTemporary == 1) {
// sku = new ProdSku();
// }
// String remark = StrUtil.isNotBlank(sku.getSpecInfo()) ? sku.getSpecInfo() : "";
// item.setRemark(remark);
if (item.getReturnNum().compareTo(BigDecimal.ZERO) > 0) {
returnDishesPrint(orderInfo, item, machine);
}
@@ -469,8 +464,9 @@ public abstract class PrinterHandler {
// 保存已打印信息
OrderDetail orderDetail = detailMap.get(item.getId());
redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()), JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId())
.setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);
redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()),
JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId())
.setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);
});
@@ -582,17 +578,35 @@ public abstract class PrinterHandler {
}
/**
* 菜品打印
*/
protected abstract void normalDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine);
/**
* 菜品 退菜打印
*/
protected abstract void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine);
/**
* 退单打印
*/
protected abstract void returnOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList);
/**
* 订单打印
*/
protected abstract void normalOrderPrint(OrderInfo orderInfo, boolean isPre, PrintMachine machine, String balance, List<OrderDetail> detailList);
/**
* 叫号打印
*/
protected abstract void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum,
String codeUrl, LocalDateTime takeTime, String shopNote);
/**
* 交班打印
*/
protected abstract void handoverPrint(PrintMachine machine, HandoverRecordDTO record);
}

View File

@@ -9,7 +9,6 @@ import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.dto.HandoverRecordDTO;
import com.czg.account.entity.HandoverRecord;
import com.czg.order.entity.OrderDetail;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -69,10 +68,9 @@ public interface PrinterImpl {
* @param metaPrintData 打印元数据
* @param voiceData 语音信息
* @param printNum 打印联数
* @param <R> 返回数据类型
* @return 打印结果
*/
<R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum);
String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum);
/**
* 获取当前打印机标签信息
@@ -560,12 +558,6 @@ public interface PrinterImpl {
return str;
}
public static void main(String[] args) {
System.out.println("水煮肉片".length());
System.out.println(StrUtil.repeat(' ', 8));
System.out.println(StrUtil.fillAfter("水煮肉片", ' ', 21));
}
/**
* 获取填充字符串, 并且换行
*

View File

@@ -4,11 +4,10 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.czg.account.dto.HandoverRecordDTO;
import com.czg.account.entity.HandoverRecord;
import com.czg.account.entity.PrintMachine;
import com.czg.account.entity.ShopInfo;
import com.czg.account.service.ShopInfoService;
import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import com.czg.service.order.enums.OrderStatusEnums;
@@ -20,16 +19,14 @@ import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
/**
* 云享印打印机
*
* 博实结-云享印打印机
* 接口文档 <a href="https://bsj2.yuque.com/bsj/izhmfn/rr8b5g?#ZbE6s">
* @author Administrator
*/
@Slf4j
@@ -45,12 +42,6 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
//APPSECRET
private static final String APP_SECRET = "2022bsjZF544GAH";
@Resource
private ShopInfoService shopInfoService;
@Resource
private RestTemplate restTemplate;
public YxyPrinter() {
super("云想印");
}
@@ -75,7 +66,7 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
@Override
public <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) {
public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) {
log.info("开始请求云享印,请求数据:{}, {}", voiceData, metaPrintData);
//设备名称
//行为方式 1:只打印数据 2:只播放信息 3:打印数据并播放信息
@@ -87,10 +78,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
String time = String.valueOf(System.currentTimeMillis());
String uuid = UUID.randomUUID().toString();
Map<String, String> param = getToken(time, uuid);
//参数
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("token", param.get("TOKEN"));
multiValueMap.add("token", getToken(time, uuid));
multiValueMap.add("devName", address);
multiValueMap.add("actWay", 3);
multiValueMap.add("cn", printNum);
@@ -108,18 +98,17 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
httpEntity, String.class);
log.info("请求云享印成功,响应数据: {}", httpResponse);
return (R) httpResponse;
return httpResponse;
}
@Override
protected void normalDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
String buildDishPrintData = buildDishPrintData(false, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(),
orderDetail.getNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
// sendPrintRequest(voiceJson, 3, 1, machine.getAddress(), data);
printMachineLogService.save(machine, "新订单", buildDishPrintData, resp);
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
printMachineLogService.save(orderInfo.getId(), machine, "新订单", buildDishPrintData, resp);
}
@@ -127,10 +116,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
protected void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
String buildDishPrintData = buildDishPrintData(true, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(),
orderDetail.getReturnNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
printMachineLogService.save(machine, "退款单", buildDishPrintData, resp);
String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
printMachineLogService.save(orderInfo.getId(), machine, "退款单", buildDishPrintData, resp);
}
@@ -138,21 +126,21 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
protected void returnOrderPrint(OrderInfo orderInfo, PrintMachine machine, String balance, List<OrderDetail> detailList) {
ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId());
PrintInfoDTO printInfoDTO = new PrintInfoDTO().setShopName(shopInfo.getShopName()).setPrintType("普通打印").setPickupNum(getPickupNum(orderInfo))
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss"))
.setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
.setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo))
.setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0")
.setOutNumber(orderInfo.getTakeCode()).setPrintTitle("结算")
.setOutNumber(orderInfo.getTakeCode()).setPrintTitle("退款")
.setRemark(orderInfo.getRemark()).setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
String data = buildOrderPrintData(printInfoDTO, detailList);
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
String printerNum = "1";
if (StrUtil.isNotBlank(machine.getPrintQty())) {
printerNum = machine.getPrintQty().split("\\^")[1];
}
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
printMachineLogService.save(machine, "新订", data, resp);
printMachineLogService.save(orderInfo.getId(), machine, "退款", data, resp);
}
@@ -165,7 +153,8 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
.le(OrderInfo::getCreateTime, orderInfo.getCreateTime()));
PrintInfoDTO printInfoDTO = new PrintInfoDTO().setShopName(shopInfo.getShopName())
.setPrintType("普通打印").setPickupNum(getPickupNum(orderInfo))
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss"))
.setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
.setOriginalAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee())).toPlainString()).setReturn(isReturn(orderInfo))
.setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0")
.setOutNumber(orderInfo.getTakeCode()).setPrintTitle(isPre ? "预结算单" : "结算单")
@@ -173,33 +162,38 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
.setRemark(orderInfo.getRemark())
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle());
if(orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0){
if (orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0) {
printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString());
printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
}
if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){
if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) {
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
}
String data = buildOrderPrintData(printInfoDTO, detailList);
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
String printerNum = "1";
if (StrUtil.isNotBlank(machine.getPrintQty())) {
printerNum = machine.getPrintQty().split("\\^")[1];
}
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
// printMachineLogService.save(machine, printType, data, resp);
printMachineLogService.save(machine, "新订单", data, resp);
printMachineLogService.save(orderInfo.getId(), machine, "结算单", data, resp);
}
/**
* 叫号单打印
*/
@Override
protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) {
String resp = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
sendPrintRequest(machine.getAddress(), resp, null, "1");
}
/**
* 交班单打印
*/
@Override
protected void handoverPrint(PrintMachine machine, HandoverRecordDTO record) {
String string = buildHandoverData(record);
@@ -214,9 +208,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
* @param requestId 请求ID自定义
* @return token信息
*/
private static Map<String, String> getToken(String timestamp, String requestId) {
private static String getToken(String timestamp, String requestId) {
StringBuilder token = new StringBuilder();
StringBuilder encode = new StringBuilder();
// StringBuilder encode = new StringBuilder();
SortedMap<String, Object> map = new TreeMap<>();
map.put("appId", APP_ID);
map.put("timestamp", timestamp);
@@ -226,13 +220,38 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
String key = next.getKey();
Object value = next.getValue();
token.append(key).append(value);
encode.append(key).append("=").append(value).append("&");
// encode.append(key).append("=").append(value).append("&");
}
Map<String, String> finalMap = new HashMap<>();
finalMap.put("ENCODE", encode.toString());
finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
return finalMap;
// Map<String, String> finalMap = new HashMap<>();
// finalMap.put("ENCODE", encode.toString());
// finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
// return finalMap;
return SecureUtil.md5(token + APP_SECRET).toUpperCase();
}
/**
* 检查打印状态
*
* @param devName 设备名称,(唯一) 对应配置表中的address字段即IP地址/打印机编号)
* @param taskId 打印任务id用于复查打印状态云想印=orderId
*/
public String checkPrintStatus(String devName, String taskId) {
String time = String.valueOf(System.currentTimeMillis());
String uuid = UUID.randomUUID().toString();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("devName", devName);
paramMap.put("orderId", taskId);
paramMap.put("token", getToken(time, uuid));
paramMap.put("appId", APP_ID);
paramMap.put("timestamp", time);
paramMap.put("requestId", uuid);
paramMap.put("userCode", USER_CODE);
String s = HttpUtil.get("https://ioe.car900.com/v1/openApi/dev/findOrder.json", paramMap, 1000 * 5);
log.info("云想印打印机:{},任务:{}状态:{}", devName, taskId, s);
return s;
}
}

View File

@@ -7,33 +7,20 @@ import java.util.Map;
/**
* 支付
* 分销员支付
*
* @author ww
*/
public interface DistributionPayService {
/**
* 现金支付
*/
CzgResult<Object> cashPayOrder(MkDistributionPayDTO payParam);
/**
* 小程序支付
*/
CzgResult<Map<String, Object>> ltPayOrder(String clintIp, MkDistributionPayDTO payParam);
/**
* PC扫码支付
*/
CzgResult<Map<String, Object>> scanPayOrder(String clintIp, MkDistributionPayDTO payParam);
/**
* 反扫
* 运营端小程序余额充值
*/
CzgResult<Map<String, Object>> microPayOrder(MkDistributionPayDTO payParam);
Map<String, String> mchRecharge(String clientIP, MkDistributionPayDTO payParam);
Map<String, String> mchRecharge(MkDistributionPayDTO payParam);
}

View File

@@ -0,0 +1,73 @@
package com.czg.service.order.service;
import com.czg.order.dto.OrderInfoRefundDTO;
import com.czg.resp.CzgResult;
import com.czg.service.order.dto.OrderPayParamDTO;
import lombok.NonNull;
import java.util.Map;
/**
* @author ww
*/
public interface OrderPayService {
/**
* 挂账
*/
CzgResult<Object> creditPayOrder(OrderPayParamDTO payParam);
/**
* 现金支付
*/
CzgResult<Object> cashPayOrder(OrderPayParamDTO payParam);
/**
* 余额支付
*/
CzgResult<Object> vipPayOrder(OrderPayParamDTO payParam);
/**
* h5支付
*/
CzgResult<Map<String, Object>> h5PayOrder(String clintIp, OrderPayParamDTO payParam);
/**
* js支付
*/
CzgResult<Map<String, Object>> jsPayOrder(String clintIp, OrderPayParamDTO payParam);
/**
* 空订单支付
*/
CzgResult<Map<String, Object>> js2PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam);
/**
* 小程序支付
*/
CzgResult<Map<String, Object>> ltPayOrder(String clintIp, OrderPayParamDTO payParam);
/**
* PC扫码支付
*/
CzgResult<Map<String, Object>> scanPayOrder(String clintIp, OrderPayParamDTO payParam);
/**
* 聚合反扫
*/
CzgResult<Map<String, Object>> microPayOrder(OrderPayParamDTO payParam);
//-----------------------------------------------------------------订单+会员 一起支付-----------------------------------------------------------
/**
* 充值并付款
*/
CzgResult<Map<String, Object>> rechargePayOrder(String clintIp, OrderPayParamDTO payParam);
//-----------------------------------------------------------------退款-----------------------------------------------------------------
//订单退款
CzgResult<Object> refundOrderBefore(OrderInfoRefundDTO param);
}

View File

@@ -1,15 +1,13 @@
package com.czg.service.order.service;
import com.czg.entity.resp.CzgBaseResp;
import com.czg.entity.resp.CzgRefundResp;
import com.czg.enums.CzgPayEnum;
import com.czg.order.dto.LtPayOtherDTO;
import com.czg.order.dto.OrderInfoRefundDTO;
import com.czg.order.entity.OrderPayment;
import com.czg.pay.CzgPayBaseReq;
import com.czg.pay.CzgRefundReq;
import com.czg.pay.QueryOrderRespDTO;
import com.czg.pay.RefundRespDTO;
import com.czg.resp.CzgResult;
import com.czg.service.order.dto.OrderPayParamDTO;
import com.czg.service.order.dto.VipMemberPayParamDTO;
import com.czg.service.order.dto.VipPayParamDTO;
import com.czg.service.order.dto.VipRefundDTO;
import lombok.NonNull;
import java.math.BigDecimal;
@@ -22,129 +20,41 @@ import java.util.Map;
* @author ww
*/
public interface PayService {
//-----------------------------------------------------------------订单支付--------------------------------------------------------------------
/**
* 挂账
*/
CzgResult<Object> creditPayOrder(OrderPayParamDTO payParam);
/**
* 现金支付
*/
CzgResult<Object> cashPayOrder(OrderPayParamDTO payParam);
/**
* 余额支付
*/
CzgResult<Object> vipPayOrder(OrderPayParamDTO payParam);
/**
* h5支付
*/
CzgResult<Map<String, Object>> h5PayOrder(String clintIp, OrderPayParamDTO payParam);
/**
* js支付
*/
CzgResult<Map<String, Object>> jsPayOrder(String clintIp, OrderPayParamDTO payParam);
/**
* 空订单支付
*/
CzgResult<Map<String, Object>> js2PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam);
/**
* 小程序支付
*/
CzgResult<Map<String, Object>> ltPayOrder(String clintIp, OrderPayParamDTO payParam);
/**
* PC扫码支付
*/
CzgResult<Map<String, Object>> scanPayOrder(String clintIp, OrderPayParamDTO payParam);
/**
* 聚合反扫
*/
CzgResult<Map<String, Object>> microPayOrder(OrderPayParamDTO payParam);
//-----------------------------------------------------------------订单+会员 一起支付-----------------------------------------------------------
/**
* 充值并付款
*/
CzgResult<Map<String, Object>> rechargePayOrder(String clintIp, OrderPayParamDTO payParam);
//-----------------------------------------------------------------会员支付--------------------------------------------------------------------
/**
* 现金充值
*/
CzgResult<Object> cashPayVip(VipPayParamDTO payParam);
/**
* js支付
*/
CzgResult<Map<String, Object>> jsPayVip(String clintIp, VipPayParamDTO payParam);
/**
* 小程序支付
*/
CzgResult<Map<String, Object>> ltPayVip(String clintIp, VipPayParamDTO payParam);
/**
* PC扫码支付
*/
CzgResult<Map<String, Object>> scanPayVip(String clintIp, VipPayParamDTO payParam);
/**
* 聚合反扫
*/
CzgResult<Map<String, Object>> microPayVip(VipPayParamDTO payParam);
//-----------------------------------------------------------------会员开通购买 ----------------------------------------------------------
CzgResult<Map<String, Object>> ltPayMember(String clientIP, VipMemberPayParamDTO payParam);
CzgResult<Map<String, Object>> recharge(String clientIP, VipPayParamDTO rechargeDTO, Long userId);
BigDecimal MONEY_RATE = new BigDecimal("100");
Long initPayment(OrderPayment payment);
//-----------------------------------------------------------------付款 ----------------------------------------------------------
CzgResult<Map<String, Object>> pay(@NonNull Long shopId, CzgPayEnum payType, CzgPayBaseReq bizData);
//-----------------------------------------------------------------积分商品/拼团 付款 ----------------------------------------------------------
/**
*
* @param param 支付参数
* @param payType 暂时只有 POINT 积分 和 WARE 拼团商品
* @param payType {@link com.czg.constants.PayTypeConstants.SourceType} 常量
* @param detail 操作描述 如 积分商品购买 / 拼团商品购买
*/
CzgResult<Map<String, Object>> ltPayOther(LtPayOtherDTO param, String payType, String detail);
//-----------------------------------------------------------------退款-----------------------------------------------------------------
/**
* 退款前校验
*/
CzgResult<Map<String, BigDecimal>> refundVipBefore(VipRefundDTO payParam);
/**
* 会员退款
* 退款
* 目前订单 会员 使用
*/
CzgResult<Object> refundVip(VipRefundDTO payParam);
CzgResult<Object> refundOrderBefore(OrderInfoRefundDTO param);
/**
* 订单退款
*/
void refundOrder(Long shopId, Long orderId, Long payOrderId, String refPayOrderNo, String refundReason, BigDecimal refundAmount);
CzgResult<RefundRespDTO> refund(@NonNull Long shopId, CzgRefundReq bizData);
/**
* 统一退款接口
*
* 目前 拼团商品 积分商品 套餐推广 使用
* @param refPayOrderNo 自定义退单号 {@link com.czg.enums.OrderNoPrefixEnum} + 雪花Id
*/
void unifyRefund(Long shopId, Long sourceId, Long payOrderId, String refPayOrderNo, String refundReason, BigDecimal refundAmount);
/**
* 退款补偿使用
* 目前 拼团商品 积分商品 套餐推广 使用
* (退款 账户余额不足时 会失败定时任务)
*/
void unifyRefund(OrderPayment payment, String refPayOrderNo);
@@ -157,7 +67,7 @@ public interface PayService {
* @param payOrderId 平台订单号
* @param mchOrderNo 商户订单号
*/
CzgResult<CzgBaseResp> queryPayOrder(Long shopId, String payOrderId, String mchOrderNo);
CzgResult<QueryOrderRespDTO> queryPayOrder(Long shopId, String payOrderId, String mchOrderNo, String platform);
/**
* 退款查询
@@ -165,5 +75,5 @@ public interface PayService {
* @param mchRefundNo 商户退款订单号 二选一
* @param refundOrderId 平台退款订单号 二选一
*/
CzgResult<CzgRefundResp> queryRefund(Long shopId, String mchRefundNo, String refundOrderId);
CzgResult<RefundRespDTO> queryRefund(Long shopId, String mchRefundNo, String refundOrderId);
}

View File

@@ -0,0 +1,38 @@
package com.czg.service.order.service;
import com.alibaba.fastjson2.JSONObject;
import com.czg.dto.req.AggregateMerchantDto;
import com.czg.service.order.dto.AggregateMerchantVO;
import com.czg.service.order.dto.MerchantQueryDTO;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
import com.czg.order.entity.ShopDirectMerchant;
/**
* 商户进件 服务层。
*
* @author ww
* @since 2026-01-07
*/
public interface ShopDirectMerchantService extends IService<ShopDirectMerchant> {
/**
* ocr识别图片
* @param url 图片地址
* @param type IdCard 身份证
* BankCard 银行卡
* BusinessLicense 营业执照
*/
JSONObject getInfoByImg(String url, String type) throws Exception;
Page<ShopDirectMerchant> getEntryList(MerchantQueryDTO queryParam);
/**
* 获取进件信息
*/
AggregateMerchantVO getEntry(Long shopId);
/**
* 申请进件
*/
boolean entryManager(AggregateMerchantDto reqDto);
}

View File

@@ -0,0 +1,63 @@
package com.czg.service.order.service;
import com.czg.resp.CzgResult;
import com.czg.service.order.dto.VipMemberPayParamDTO;
import com.czg.service.order.dto.VipPayParamDTO;
import com.czg.service.order.dto.VipRefundDTO;
import java.math.BigDecimal;
import java.util.Map;
/**
* 用户余额 充值/会员购买开通
* @author ww
*/
public interface ShopUserPayService {
//-----------------------------------------------------------------会员支付--------------------------------------------------------------------
/**
* 现金充值
*/
CzgResult<Object> cashPayVip(VipPayParamDTO payParam);
/**
* js支付
*/
CzgResult<Map<String, Object>> jsPayVip(String clintIp, VipPayParamDTO payParam);
/**
* 小程序支付
*/
CzgResult<Map<String, Object>> ltPayVip(String clintIp, VipPayParamDTO payParam);
/**
* PC扫码支付
*/
CzgResult<Map<String, Object>> scanPayVip(String clintIp, VipPayParamDTO payParam);
/**
* 聚合反扫
*/
CzgResult<Map<String, Object>> microPayVip(VipPayParamDTO payParam);
//-----------------------------------------------------------------会员开通购买 ----------------------------------------------------------
CzgResult<Map<String, Object>> ltPayMember(String clientIP, VipMemberPayParamDTO payParam);
CzgResult<Map<String, Object>> recharge(String clientIP, VipPayParamDTO rechargeDTO, Long userId);
//-----------------------------------------------------------------退款-----------------------------------------------------------------
/**
* 退款前校验
*/
CzgResult<Map<String, BigDecimal>> refundVipBefore(VipRefundDTO payParam);
/**
* 会员退款
*/
CzgResult<Object> refundVip(VipRefundDTO payParam);
}

View File

@@ -3,18 +3,19 @@ package com.czg.service.order.service.impl;
import cn.hutool.core.util.IdUtil;
import com.czg.account.entity.ShopUser;
import com.czg.account.entity.UserInfo;
import com.czg.account.service.ShopInfoService;
import com.czg.account.service.ShopUserService;
import com.czg.account.service.UserInfoService;
import com.czg.constant.TableValueConstant;
import com.czg.entity.req.CzgLtPayReq;
import com.czg.constants.PayTypeConstants;
import com.czg.constants.SystemConstants;
import com.czg.enums.CzgPayEnum;
import com.czg.exception.CzgException;
import com.czg.market.service.MkDistributionConfigService;
import com.czg.market.vo.MkDistributionConfigVO;
import com.czg.order.dto.MkDistributionPayDTO;
import com.czg.order.entity.OrderPayment;
import com.czg.constants.PayTypeConstants;
import com.czg.order.service.OrderPaymentService;
import com.czg.pay.CzgPayBaseReq;
import com.czg.resp.CzgResult;
import com.czg.service.market.service.impl.WxServiceImpl;
import com.czg.service.order.service.DistributionPayService;
@@ -44,8 +45,6 @@ public class DistributionPayServiceImpl implements DistributionPayService {
@Resource
private PayServiceImpl payService;
@DubboReference
private ShopInfoService shopInfoService;
@DubboReference
private ShopUserService shopUserService;
@DubboReference
@@ -70,7 +69,7 @@ public class DistributionPayServiceImpl implements DistributionPayService {
ShopUser shopUserInfo = shopUserService.getShopUserInfo(payParam.getShopId(), userId);
OrderPayment orderPayment = new OrderPayment().setShopId(payParam.getShopId()).setSourceId(isRecharge ? payParam.getShopId() : shopUserInfo.getId())
.setSourceType(isRecharge ? PayTypeConstants.SourceType.DISTRIBUTION_RECHARGE : PayTypeConstants.SourceType.DISTRIBUTION )
.setSourceType(isRecharge ? PayTypeConstants.SourceType.DISTRIBUTION_RECHARGE : PayTypeConstants.SourceType.DISTRIBUTION)
.setPayType(PayTypeConstants.PayType.PAY)
.setOrderNo(payParam.getPlatformType() + IdUtil.getSnowflakeNextId())
.setAmount(isRecharge ? payParam.getAmount() : detail.getPayAmount());
@@ -84,36 +83,22 @@ public class DistributionPayServiceImpl implements DistributionPayService {
} else {
UserInfo userInfo = userInfoService.getById(userId);
initInfo.setPayment(orderPayment).setShopUser(shopUserInfo)
.setOpenId("aliPay".equals(payParam.getPayType()) ? userInfo.getAlipayOpenId() : userInfo.getWechatOpenId());
.setOpenId(SystemConstants.PayType.ALIPAY.equals(payParam.getPayType()) ? userInfo.getAlipayOpenId() : userInfo.getWechatOpenId());
}
return initInfo;
}
@Override
public CzgResult<Object> cashPayOrder(MkDistributionPayDTO payParam) {
return null;
}
@Override
public CzgResult<Map<String, Object>> ltPayOrder(String clintIp, MkDistributionPayDTO payParam) {
InitInfo initInfo = initPayment(payParam.getUserId(), payParam, false);
return payService.ltPay(payParam.getShopId(), payParam.getPayType(), new CzgLtPayReq(initInfo.payment.getOrderNo(), initInfo.payment.getAmount().multiply(MONEY_RATE).longValue(),
payParam.getPayType(), "分销员开通", initInfo.getOpenId(), clintIp, payParam.getReturnUrl(), payParam.getBuyerRemark(), ""));
return payService.pay(payParam.getShopId(), CzgPayEnum.LT_PAY,
CzgPayBaseReq.ltPayReq(initInfo.payment.getOrderNo(), "分销员开通", initInfo.payment.getAmount().multiply(MONEY_RATE).longValue(),
payParam.getPayType(), initInfo.getOpenId(), clintIp));
}
@Override
public Map<String, String> mchRecharge(String clientIP, MkDistributionPayDTO payParam) {
public Map<String, String> mchRecharge(MkDistributionPayDTO payParam) {
InitInfo initInfo = initPayment(payParam.getUserId() == null ? payParam.getShopId() : payParam.getUserId(), payParam, true);
return wxService.v3Pay(initInfo.openId, payParam.getAmount(), "商户运营余额充值", initInfo.payment.getOrderNo(), initInfo.payment.getSourceType());
}
@Override
public CzgResult<Map<String, Object>> scanPayOrder(String clintIp, MkDistributionPayDTO payParam) {
return null;
}
@Override
public CzgResult<Map<String, Object>> microPayOrder(MkDistributionPayDTO payParam) {
return null;
}
}

View File

@@ -6,7 +6,10 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.*;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
@@ -17,9 +20,8 @@ import com.czg.config.RabbitPublisher;
import com.czg.config.RedisCst;
import com.czg.constant.MarketConstants;
import com.czg.constant.TableValueConstant;
import com.czg.entity.notify.CzgPayNotifyDTO;
import com.czg.entity.notify.CzgRefundNotifyDTO;
import com.czg.constants.PayTypeConstants;
import com.czg.entity.notify.CzgRefundNotifyDTO;
import com.czg.enums.ShopTableStatusEnum;
import com.czg.enums.ShopUserFlowBizEnum;
import com.czg.exception.CzgException;
@@ -40,6 +42,7 @@ import com.czg.order.entity.OrderPayment;
import com.czg.order.enums.PayEnums;
import com.czg.order.service.*;
import com.czg.order.vo.*;
import com.czg.pay.PayNotifyRespDTO;
import com.czg.product.entity.Product;
import com.czg.product.service.ProductRpcService;
import com.czg.product.service.ProductService;
@@ -50,7 +53,6 @@ import com.czg.service.order.enums.OrderStatusEnums;
import com.czg.service.order.mapper.OrderInfoCustomMapper;
import com.czg.service.order.print.PrinterHandler;
import com.czg.utils.*;
import com.czg.utils.PageUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
@@ -299,6 +301,7 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
orderDetails.forEach(item -> {
item.setUrgent(true);
item.setSubStatus(TableValueConstant.OrderDetail.SubStatus.READY_TO_SERVE.getCode());
item.setStartOrderTime(DateUtil.date().toLocalDateTime());
});
} else {
switch (EnumUtil.fromString(TableValueConstant.OrderDetail.SubStatus.class, param.getSubStatus())) {
@@ -1027,13 +1030,12 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
@Override
@Transactional
public void payCallBackOrder(@NotBlank String orderNo, @NotNull JSONObject resultJson, int retryCount) {
CzgPayNotifyDTO czgCallBackDto = JSONObject.parseObject(resultJson.toString(), CzgPayNotifyDTO.class);
public void payCallBackOrder(@NotBlank String orderNo, @NotNull PayNotifyRespDTO notifyRespDTO, String channel, int retryCount) {
OrderPayment payment = paymentService.getOne(new QueryWrapper().eq(OrderPayment::getOrderNo, orderNo));
if (payment == null) {
if (retryCount < MAX_RETRIES) {
log.info("支付记录不存在,第 {} 次重试,将在 {} 秒后进行", retryCount + 1, DELAY);
executorService.schedule(() -> payCallBackOrder(orderNo, resultJson, retryCount + 1), DELAY, TimeUnit.SECONDS);
executorService.schedule(() -> payCallBackOrder(orderNo, notifyRespDTO, channel, retryCount + 1), DELAY, TimeUnit.SECONDS);
} else {
log.error("订单支付回调失败, 达到最大重试次数, 支付记录不存在, orderNo: {}", orderNo);
}
@@ -1043,10 +1045,12 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
log.info("订单处理过payment id{}", payment.getId());
return;
}
payment.setTradeNumber(czgCallBackDto.getPayOrderId());
payment.setRespJson(resultJson.toString());
payment.setTradeNumber(notifyRespDTO.getThirdOrderNo());
payment.setRespJson(notifyRespDTO.getOriginalData());
payment.setPayStatus(PayTypeConstants.PayStatus.FAIL);
if ("TRADE_SUCCESS".equals(czgCallBackDto.getState())) {
payment.setPlatformType(notifyRespDTO.getPlatform());
payment.setChannel(channel);
if ("TRADE_SUCCESS".equals(notifyRespDTO.getStatus())) {
payment.setPayStatus(PayTypeConstants.PayStatus.SUCCESS);
if (PayTypeConstants.SourceType.ORDER.equals(payment.getSourceType())) {
OrderInfo orderInfo = orderInfoService.getById(payment.getSourceId());
@@ -1054,8 +1058,8 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
log.error("订单支付回调失败,订单不存在,支付记录Id,{}", payment.getId());
return;
}
upOrderInfo(orderInfo, new BigDecimal(czgCallBackDto.getAmount()).divide(new BigDecimal(100), 2, RoundingMode.DOWN),
DateUtil.parseLocalDateTime(czgCallBackDto.getPayTime()), payment.getId(), null);
upOrderInfo(orderInfo, new BigDecimal(notifyRespDTO.getAmount()).divide(new BigDecimal(100), 2, RoundingMode.DOWN),
DateUtil.parseLocalDateTime(notifyRespDTO.getPaySuccessTime()), payment.getId(), null);
if (orderInfo.getUserId() != null) {
// 分销员升级等级
distributionUserService.costUpgradeLevelBefore(orderInfo.getUserId(), orderInfo.getShopId());
@@ -1065,14 +1069,14 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
} else if (PayTypeConstants.SourceType.MEMBER_IN.equals(payment.getSourceType()) || PayTypeConstants.SourceType.FREE.equals(payment.getSourceType())) {
boolean isFree = PayTypeConstants.SourceType.FREE.equals(payment.getSourceType());
ShopUser shopUser = shopUserService.getById(payment.getSourceId());
OrderInfo orderInfo = null;
OrderInfo orderInfo;
if (shopUser == null) {
log.error("会员充值失败会员不存在会员id{}", payment.getSourceId());
} else {
ShopUserFlowBizEnum bizEnum;
if ("WECHAT".equals(czgCallBackDto.getPayType())) {
if ("WECHAT".equals(notifyRespDTO.getPlatform())) {
bizEnum = ShopUserFlowBizEnum.WECHAT_IN;
} else if ("ALIPAY".equals(czgCallBackDto.getPayType())) {
} else if ("ALIPAY".equals(notifyRespDTO.getPlatform())) {
bizEnum = ShopUserFlowBizEnum.ALIPAY_IN;
} else {
bizEnum = ShopUserFlowBizEnum.CASH_IN;
@@ -1088,7 +1092,7 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
.setType(1)
.setBizEnum(ShopUserFlowBizEnum.FREE_IN)
.setRelationId(orderInfo.getId())
.setMoney(BigDecimal.valueOf(czgCallBackDto.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN));
.setMoney(BigDecimal.valueOf(notifyRespDTO.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN));
shopUserService.updateMoney(shopUserMoneyEditDTO);
upOrderInfo(orderInfo, BigDecimal.ZERO,
LocalDateTime.now(), null, PayEnums.FREE_PAY);
@@ -1110,26 +1114,21 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
LocalDateTime.now(), null, PayEnums.VIP_PAY);
}
shopRechargeService.recharge(payment.getShopId(), payment.getSourceId(), payment.getRelatedId(),
BigDecimal.valueOf(czgCallBackDto.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN),
BigDecimal.valueOf(notifyRespDTO.getAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN),
payment.getId(), payment.getSourceType(), bizEnum, orderInfo == null);
}
}
}
else if (PayTypeConstants.SourceType.MEMBER_PAY.equals(payment.getSourceType())) {
} else if (PayTypeConstants.SourceType.MEMBER_PAY.equals(payment.getSourceType())) {
//购买会员
ShopUser shopUser = shopUserService.getById(payment.getSourceId());
memberConfigService.joinMember(payment.getShopId(), shopUser.getUserId(), payment.getRelatedId());
}
else if (PayTypeConstants.SourceType.DISTRIBUTION.equals(payment.getSourceType())) {
} else if (PayTypeConstants.SourceType.DISTRIBUTION.equals(payment.getSourceType())) {
distributionUserService.open(payment.getSourceId(), payment.getAmount(), payment.getShopId(), payment.getId());
}
else if (PayTypeConstants.SourceType.POINT.equals(payment.getSourceType())) {
} else if (PayTypeConstants.SourceType.POINT.equals(payment.getSourceType())) {
goodPayService.payCallBack(payment.getSourceId(), payment.getId());
}
else if (PayTypeConstants.SourceType.WARE.equals(payment.getSourceType())) {
} else if (PayTypeConstants.SourceType.WARE.equals(payment.getSourceType())) {
gbOrderService.payCallBack(payment.getSourceId(), payment.getId());
}
else if (PayTypeConstants.SourceType.PP.equals(payment.getSourceType())) {
} else if (PayTypeConstants.SourceType.PP.equals(payment.getSourceType())) {
ppPackageOrderService.paySuccess(payment.getSourceId(), payment.getId());
}
}

View File

@@ -0,0 +1,567 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO;
import com.czg.account.entity.ShopInfo;
import com.czg.account.entity.ShopUser;
import com.czg.account.entity.UserInfo;
import com.czg.account.service.ShopInfoService;
import com.czg.account.service.ShopUserService;
import com.czg.account.service.UserInfoService;
import com.czg.config.RabbitPublisher;
import com.czg.config.RedisCst;
import com.czg.constants.PayTypeConstants;
import com.czg.enums.CzgPayEnum;
import com.czg.enums.ShopUserFlowBizEnum;
import com.czg.exception.CzgException;
import com.czg.exception.PaySuccessException;
import com.czg.market.entity.MkShopRechargeDetail;
import com.czg.market.enums.PointsConstant;
import com.czg.market.service.*;
import com.czg.market.vo.MkShopRechargeVO;
import com.czg.order.dto.CheckOrderPay;
import com.czg.order.dto.OrderInfoRefundDTO;
import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo;
import com.czg.order.entity.OrderPayment;
import com.czg.order.enums.PayEnums;
import com.czg.order.service.CreditBuyerOrderService;
import com.czg.order.service.OrderDetailService;
import com.czg.order.service.OrderInfoCustomService;
import com.czg.order.service.OrderPaymentService;
import com.czg.pay.CzgPayBaseReq;
import com.czg.pay.CzgRefundReq;
import com.czg.pay.RefundRespDTO;
import com.czg.resp.CzgResult;
import com.czg.service.RedisService;
import com.czg.service.order.dto.OrderPayParamDTO;
import com.czg.service.order.enums.OrderStatusEnums;
import com.czg.service.order.service.OrderPayService;
import com.czg.service.order.service.PayService;
import com.czg.utils.AssertUtil;
import com.czg.utils.CzgRandomUtils;
import com.czg.utils.FunUtils;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotBlank;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author ww
*/
@Slf4j
@Service
public class OrderPayServiceImpl implements OrderPayService {
@Resource
private PayService payService;
@Resource
private OrderPaymentService paymentService;
@DubboReference
private ShopUserService shopUserService;
@DubboReference
private UserInfoService userInfoService;
@DubboReference
private ShopInfoService shopInfoService;
@Resource
private OrderInfoCustomService orderInfoCustomService;
@Resource
private OrderInfoService orderInfoService;
@Resource
private CreditBuyerOrderService buyerOrderService;
@Resource
private RedisService redisService;
@Resource
private RabbitPublisher rabbitPublisher;
@Resource
private MkShopRechargeService shopRechargeService;
@Resource
private MkShopRechargeDetailService shopRechargeDetailService;
@Resource
private OrderDetailService orderDetailService;
@Resource
private MkDistributionUserService distributionUserService;
@Resource
private MkShopConsumerCouponService consumerCouponService;
@Resource
private MkPointsUserService mkPointsUserService;
@Resource
private MkConsumeCashbackService consumeCashbackService;
@Resource
private MkPointsConfigService pointsConfigService;
private OrderInfo checkPay(CheckOrderPay checkOrderPay) {
OrderInfo orderInfo = orderInfoCustomService.checkOrderPay(checkOrderPay);
if (orderInfo.getOrderAmount().compareTo(BigDecimal.ZERO) == 0) {
//发送打票信息
//orderId_0_0 订单ID_先付后付(1先付0后付)_订单状态 0未完成 1完成
//orderInfo.getId() + "_" + (!"after-pay".equals(orderInfo.getPayMode()) ? 1 : 0) + "_0"
rabbitPublisher.sendOrderPrintMsg(orderInfo.getId() + "_" + (!"after-pay".equals(orderInfo.getPayMode()) ? 1 : 0) + "_1", orderInfo.getIsPrint() == 1);
redisService.del(RedisCst.classKeyExpired.EXPIRED_ORDER + orderInfo.getId());
throw new PaySuccessException("支付成功");
}
return orderInfo;
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Object> creditPayOrder(OrderPayParamDTO payParam) {
AssertUtil.isNull(payParam.getCreditBuyerId(), "请选择挂账人后支付");
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
orderInfo.setCreditBuyerId(payParam.getCreditBuyerId());
orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(),
LocalDateTime.now(), null, PayEnums.CREDIT_PAY);
//挂账后续逻辑
buyerOrderService.save(payParam.getCreditBuyerId().toString(), orderInfo.getId());
return CzgResult.success();
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Object> cashPayOrder(OrderPayParamDTO payParam) {
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(),
LocalDateTime.now(), null, PayEnums.CASH_PAY);
return CzgResult.success();
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Object> vipPayOrder(OrderPayParamDTO payParam) {
ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId());
AssertUtil.isNull(shopInfo, "店铺不存在");
if (!shopInfo.getIsAccountPay().equals(1)) {
return CzgResult.failure("支付失败,店铺暂未开启会员余额支付。");
}
ShopUser shopUser = new ShopUser();
if ("scanCode".equals(payParam.getPayType())) {
AssertUtil.isBlank(payParam.getAuthCode(), "会员码不能为空");
Object o = redisService.get(RedisCst.SHOP_USER_DYNAMIC_CODE + payParam.getShopId() + ":" + payParam.getAuthCode());
AssertUtil.isNull(o, "会员码已失效");
shopUser = shopUserService.getById(o.toString());
} else {
if ("userPay".equals(payParam.getPayType())) {
AssertUtil.isNull(payParam.getShopUserId(), "请选择付款人后重试");
shopUser = shopUserService.getById(payParam.getShopUserId());
} else if ("accountPay".equals(payParam.getPayType())) {
shopUser = shopUserService.getById(payParam.getShopUserId());
AssertUtil.isNull(shopUser, "会员不存在");
UserInfo userInfo = userInfoService.getById(shopUser.getUserId());
AssertUtil.isNull(userInfo, "用户信息不存在");
if (userInfo.getUsePayPwd() == 1) {
AssertUtil.isBlank(payParam.getPwd(), "支付密码不能为空");
if (userInfo.getPayPwd() == null || !userInfo.getPayPwd().equals(MD5.create().digestHex((payParam.getPwd())))) {
return CzgResult.failure("支付密码错误");
}
}
}
}
if (shopUser == null || shopUser.getId() == null) {
AssertUtil.isNull(shopUser, "会员不存在");
}
payParam.getCheckOrderPay().setUserId(shopUser.getUserId());
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
if (shopInfo.getIsHeadShop().equals(1)) {
if (!shopUser.getMainShopId().equals(orderInfo.getShopId())) {
return CzgResult.failure("违规操作,请确认店铺后重试");
}
} else {
boolean exists = shopInfoService.exists(QueryWrapper.create()
.eq(ShopInfo::getMainId, shopInfo.getMainId())
.eq(ShopInfo::getId, orderInfo.getShopId()));
if (!exists) {
return CzgResult.failure("违规操作,请确认店铺后重试");
}
}
if (shopUser.getAmount().compareTo(orderInfo.getOrderAmount()) < 0) {
return CzgResult.failure("会员余额不足");
}
ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO()
.setId(shopUser.getId())
.setMoney(orderInfo.getOrderAmount())
.setType(0)
.setBizEnum(ShopUserFlowBizEnum.ORDER_PAY)
.setRelationId(orderInfo.getId());
//更新会员余额 并生成流水
if (payParam.getCheckOrderPay() != null && StrUtil.isNotBlank(payParam.getCheckOrderPay().getRemark())) {
orderInfo.setRemark(payParam.getCheckOrderPay().getRemark());
}
Long flowId = shopUserService.updateMoney(shopUserMoneyEditDTO);
orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(),
LocalDateTime.now(), flowId, PayEnums.VIP_PAY);
return CzgResult.success();
}
@Override
public CzgResult<Map<String, Object>> rechargePayOrder(String clintIp, OrderPayParamDTO payParam) {
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId());
AssertUtil.isNull(shopInfo, "店铺不存在");
AssertUtil.isNull(payParam.getShopUserId(), "请选择付款人后重试");
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
AssertUtil.isNull(shopUser, "支付失败 该店铺用户不存在");
AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空");
UserInfo userInfo = userInfoService.getById(shopUser.getUserId());
AssertUtil.isNull(payParam.getRechargeId(), "请选择充值配置后重试");
AssertUtil.isNull(payParam.getRechargeDetailId(), "请选择充值配置后重试");
//查询活动Id 获取金额字段
MkShopRechargeDetail rechargeDetail = shopRechargeDetailService.getOne(
new QueryWrapper().eq(MkShopRechargeDetail::getId, payParam.getRechargeDetailId())
.eq(MkShopRechargeDetail::getShopRechargeId, payParam.getRechargeId()));
AssertUtil.isNull(rechargeDetail, "充值配置不存在");
if (orderInfo.getOrderAmount().compareTo(rechargeDetail.getAmount()) >= 0) {
log.info("充值金额小于订单金额,充值金额:{} 订单金额:{}", rechargeDetail.getAmount(), orderInfo.getOrderAmount());
return CzgResult.failure("支付失败 充值金额必须大雨订单金额");
}
String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake();
Long paymentId = payService.initPayment(OrderPayment.pay(payParam.getShopId(), shopUser.getId(), PayTypeConstants.SourceType.MEMBER_IN,
payOrderNo, rechargeDetail.getAmount(), "", rechargeDetail.getId()));
upOrderPayInfo(orderInfo.getId(), PayEnums.VIP_PAY, paymentId,
payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark());
return payService.pay(payParam.getShopId(), CzgPayEnum.LT_PAY,
CzgPayBaseReq.ltPayReq(
payOrderNo, "充值并支付",
rechargeDetail.getAmount().multiply(PayService.MONEY_RATE).longValue(), payParam.getPayType(),
"wechatPay".equals(payParam.getPayType()) ? userInfo.getWechatOpenId() : userInfo.getAlipayOpenId(), clintIp));
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Map<String, Object>> h5PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) {
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake();
Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), ""));
upOrderPayInfo(orderInfo.getId(), PayEnums.H5_PAY, paymentId, payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark());
return payService.pay(payParam.getShopId(), CzgPayEnum.H5_PAY,
CzgPayBaseReq.h5PayReq(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), clintIp));
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Map<String, Object>> jsPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) {
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空");
AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空");
String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake();
Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), ""));
upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId,
payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark());
return payService.pay(payParam.getShopId(), CzgPayEnum.JS_PAY,
CzgPayBaseReq.jsPayReq(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(),
payParam.getPayType(), payParam.getOpenId(), clintIp));
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Map<String, Object>> js2PayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) {
AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空");
AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空");
OrderInfo orderInfo;
if (payParam.getCheckOrderPay().getOrderId() == null) {
if (payParam.getCheckOrderPay().getOrderAmount() == null || payParam.getCheckOrderPay().getOrderAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new CzgException("支付金额不合法");
}
orderInfo = orderInfoCustomService.createPayOrder(payParam.getShopId(), payParam.getCheckOrderPay().getOrderAmount(),
payParam.getCheckOrderPay().getRemark());
} else {
orderInfo = orderInfoService.getById(payParam.getCheckOrderPay().getOrderId());
}
String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake();
Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), ""));
upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId,
payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark());
return payService.pay(payParam.getShopId(), CzgPayEnum.JS_PAY,
CzgPayBaseReq.jsPayReq(payOrderNo, "扫码支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(),
payParam.getPayType(), payParam.getOpenId(), clintIp));
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Map<String, Object>> ltPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) {
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空");
AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空");
String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake();
Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), ""));
upOrderPayInfo(orderInfo.getId(), "aliPay".equals(payParam.getPayType()) ? PayEnums.ALIPAY_MINI : PayEnums.WECHAT_MINI, paymentId,
payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark());
return payService.pay(payParam.getShopId(), CzgPayEnum.LT_PAY,
CzgPayBaseReq.ltPayReq(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(),
payParam.getPayType(), payParam.getOpenId(), clintIp));
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Map<String, Object>> scanPayOrder(@NonNull String clintIp, OrderPayParamDTO payParam) {
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId());
if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) {
throw new CzgException("未开启自定义充值金额");
}
Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId());
BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount());
payParam.setAmount(amount);
String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake();
Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), ""));
upOrderPayInfo(orderInfo.getId(), PayEnums.MAIN_SCAN, paymentId,
payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark());
return payService.pay(payParam.getShopId(), CzgPayEnum.SCAN_PAY,
CzgPayBaseReq.scanPayReq(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), clintIp));
}
@Override
@Transactional(noRollbackFor = PaySuccessException.class)
public CzgResult<Map<String, Object>> microPayOrder(OrderPayParamDTO payParam) {
OrderInfo orderInfo = checkPay(payParam.getCheckOrderPay());
AssertUtil.isBlank(payParam.getAuthCode(), "扫描码不能为空");
if (payParam.getShopUserId() != null) {
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId());
if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) {
throw new CzgException("未开启自定义充值金额");
}
Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId());
BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount());
payParam.setAmount(amount);
}
String payOrderNo = orderInfo.getPlatformType() + CzgRandomUtils.snowflake();
Long paymentId = payService.initPayment(OrderPayment.orderPay(payParam.getShopId(), orderInfo.getId(), payOrderNo, orderInfo.getOrderAmount(), payParam.getAuthCode()));
CzgResult<Map<String, Object>> mapCzgResult = payService.pay(payParam.getShopId(), CzgPayEnum.MICRO_PAY,
CzgPayBaseReq.microPay(payOrderNo, "点餐支付", orderInfo.getOrderAmount().multiply(PayService.MONEY_RATE).longValue(), payParam.getAuthCode()));
if (mapCzgResult.getCode() == 200) {
orderInfoCustomService.upOrderInfo(orderInfo, orderInfo.getOrderAmount(),
LocalDateTime.now(), paymentId, PayEnums.BACK_SCAN);
} else {
upOrderPayInfo(orderInfo.getId(), PayEnums.BACK_SCAN, paymentId,
payParam.getCheckOrderPay() == null ? null : payParam.getCheckOrderPay().getRemark());
}
return mapCzgResult;
}
@Override
@Transactional
public CzgResult<Object> refundOrderBefore(OrderInfoRefundDTO param) {
OrderInfo orderInfo = orderInfoService.getById(param.getOrderId());
if (orderInfo.getStatus().equals(OrderStatusEnums.CANCELLED.getCode())) {
throw new CzgException("订单已过期不可退单");
}
boolean isFirstRefund = true;
if (orderInfo.getRefundAmount().compareTo(BigDecimal.ZERO) != 0) {
isFirstRefund = false;
}
ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId());
Map<String, BigDecimal> returnProMap = new HashMap<>();
boolean isPay = true;
String refPayOrderNo = "REFO" + IdUtil.getSnowflakeNextId();
if (orderInfo.getStatus().equals(OrderStatusEnums.UNPAID.getCode())) {
isPay = false;
refPayOrderNo = "";
}
if (isPay) {
if (shopInfo.getIsReturnPwd().equals(1)) {
AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用");
if (!SecureUtil.md5(param.getPwd()).equals(shopInfo.getOperationPwd())) {
throw new CzgException("操作密码错误");
}
}
orderInfo.setRefundAmount(orderInfo.getRefundAmount().add(param.getRefundAmount()));
if (orderInfo.getRefundAmount().compareTo(orderInfo.getPayAmount()) > 0) {
throw new CzgException("退单失败,可退金额不足");
}
}
if (CollUtil.isNotEmpty(param.getRefundDetails())) {
for (OrderDetail refundDetail : param.getRefundDetails()) {
AssertUtil.isNull(refundDetail.getNum(), "退单数量不能为空");
//退款数量
BigDecimal refNum = refundDetail.getNum();
OrderDetail orderDetail = orderDetailService.getById(refundDetail.getId());
//可退数量=订单数量-退单数量-退菜数量
BigDecimal returnNum = orderDetail.getNum().subtract(orderDetail.getRefundNum()).subtract(orderDetail.getReturnNum());
if (returnNum.compareTo(BigDecimal.ZERO) <= 0 || returnNum.compareTo(refundDetail.getNum()) < 0) {
throw new CzgException("退单失败," + orderDetail.getProductName() + "可退数量不足");
}
refundDetail.setReturnAmount(refundDetail.getNum().multiply(orderDetail.getUnitPrice()).setScale(2, RoundingMode.UP));
if (isPay) {
orderDetail.setRefundNum(orderDetail.getRefundNum().add(refNum));
if (orderDetail.getNum().compareTo(orderDetail.getRefundNum().add(orderDetail.getReturnNum())) == 0) {
orderDetail.setStatus(OrderStatusEnums.REFUND.getCode());
} else {
orderDetail.setStatus(OrderStatusEnums.PART_REFUND.getCode());
}
} else {
orderDetail.setReturnNum(orderDetail.getReturnNum().add(refNum));
if (orderDetail.getPackNumber().compareTo(BigDecimal.ZERO) > 0 && orderDetail.getPackNumber().compareTo(orderDetail.getNum().subtract(orderDetail.getReturnNum())) > 0) {
orderDetail.setPackNumber(orderDetail.getNum().subtract(orderDetail.getReturnNum()));
}
}
orderDetail.setRefundNo(refPayOrderNo);
orderDetail.setRefundRemark(orderDetail.getRefundRemark() + param.getRefundReason());
// if (isPay) {
orderDetail.setReturnAmount(orderDetail.getReturnAmount().add(refundDetail.getReturnAmount()));
if (orderDetail.getReturnAmount().compareTo(orderDetail.getPayAmount()) > 0) {
orderDetail.setReturnAmount(orderDetail.getPayAmount());
}
// }
orderDetailService.updateById(orderDetail);
if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) {
returnProMap.put(Convert.toStr(orderDetail.getProductId()), refundDetail.getNum());
}
}
long count = orderDetailService.queryChain()
.eq(OrderDetail::getOrderId, orderInfo.getId())
.ne(OrderDetail::getStatus, OrderStatusEnums.REFUND.getCode()).count();
if (count > 0 && isPay) {
orderInfo.setStatus(OrderStatusEnums.PART_REFUND.getCode());
} else if (isPay) {
orderInfo.setStatus(OrderStatusEnums.REFUND.getCode());
}
} else {
orderInfo.setStatus(OrderStatusEnums.REFUND.getCode());
List<OrderDetail> orderDetails = orderDetailService.queryChain()
.select(OrderDetail::getId, OrderDetail::getProductId, OrderDetail::getNum, OrderDetail::getReturnNum, OrderDetail::getPackAmount, OrderDetail::getReturnNum)
.eq(OrderDetail::getOrderId, orderInfo.getId())
.list();
for (OrderDetail orderDetail : orderDetails) {
if (isPay) {
if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) {
returnProMap.put(Convert.toStr(orderDetail.getProductId()), orderDetail.getNum().subtract(orderDetail.getReturnNum()).subtract(orderDetail.getRefundNum()));
}
orderDetail.setReturnAmount(orderDetail.getPayAmount());
orderDetail.setRefundNum(orderDetail.getNum().subtract(orderDetail.getReturnNum()));
orderDetail.setStatus(OrderStatusEnums.REFUND.getCode());
} else {
if (orderDetail.getProductId() != null && orderDetail.getProductId() > 0) {
returnProMap.put(Convert.toStr(orderDetail.getProductId()), orderDetail.getNum().subtract(orderDetail.getReturnNum()));
}
orderDetail.setReturnNum(orderDetail.getNum());
orderDetail.setStatus(OrderStatusEnums.CANCELLED.getCode());
}
}
orderDetailService.updateBatch(orderDetails);
}
//总退款金额
//TODO 退款 券 未处理
if (isPay) {
orderInfo.setRefundType("cash");
//非现金退款
if (!param.isCash()) {
if (orderInfo.getPayType().equals(PayEnums.VIP_PAY.getValue())) {
ShopUser shopUser = shopUserService.getShopUserInfo(orderInfo.getShopId(), orderInfo.getUserId());
//会员支付 退钱
ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO()
.setId(shopUser.getId())
.setMoney(param.getRefundAmount())
.setType(1)
.setRelationId(orderInfo.getId())
.setBizEnum(ShopUserFlowBizEnum.ORDER_REFUND);
shopUserService.updateMoney(shopUserMoneyEditDTO);
} else if (orderInfo.getPayType().equals(PayEnums.CREDIT_PAY.getValue())) {
AssertUtil.isNull(orderInfo.getCreditBuyerId(), "挂账单退款失败,未查询到挂账人");
buyerOrderService.partRefund(orderInfo.getCreditBuyerId().toString(), orderInfo.getId(), param.getRefundAmount());
} else if (!orderInfo.getPayType().equals(PayEnums.CASH_PAY.getValue())) {
//退款 param.getRefundAmount()
if (orderInfo.getPayOrderId() == null) {
throw new CzgException("退款失败,支付记录不存在");
}
refundOrder(orderInfo.getShopId(), orderInfo.getId(), orderInfo.getPayOrderId(),
refPayOrderNo, StrUtil.isBlank(param.getRefundReason()) ? "退款" : param.getRefundReason(), param.getRefundAmount());
}
orderInfo.setRefundType("payBack");
}
} else {
orderInfo.setOrderAmount(orderInfo.getOrderAmount().subtract(param.getRefundAmount()));
}
orderInfo.setRefundRemark(orderInfo.getRefundRemark() + param.getRefundReason());
orderInfoService.updateById(orderInfo);
//退款后续
//退款返还库存
if (!returnProMap.isEmpty()) {
rabbitPublisher.sendOrderRefundMsg(JSONObject.toJSONString(Map.of("orderId", orderInfo.getId(), "returnProMap", returnProMap)));
}
refundOrderAfter(orderInfo.getId(), orderInfo.getShopId(), orderInfo.getUserId(), orderInfo.getOrderNo(),
orderInfo.getPointsNum(), isFirstRefund, orderInfo.getStatus().equals(OrderStatusEnums.REFUND.getCode()));
return CzgResult.success();
}
private void refundOrder(@NonNull Long shopId, @NonNull Long orderId, @NonNull Long payOrderId, @NonNull String refPayOrderNo,
@NonNull String refundReason, @NonNull BigDecimal refundAmount) {
OrderPayment payment = paymentService.getById(payOrderId);
AssertUtil.isNull(payment, "退款失败支付记录不存在");
Long refundId = payService.initPayment(OrderPayment.refund(shopId, orderId, PayTypeConstants.SourceType.ORDER,
refPayOrderNo, refundAmount, payment.getId(), payment.getPlatformType()));
CzgResult<RefundRespDTO> refund = payService.refund(shopId, new CzgRefundReq(refPayOrderNo, refundReason, refundAmount.multiply(PayService.MONEY_RATE).longValue(),
payment.getAmount().multiply(PayService.MONEY_RATE).longValue(), payment.getOrderNo(), "", payment.getPlatformType()));
if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getStatus())) {
if (refund.getData() != null && refund.getData().getErrMessage() != null) {
throw new CzgException(refund.getData().getErrMessage());
}
throw new CzgException(refund.getMsg());
} else {
paymentService.updateChain()
.eq(OrderPayment::getId, refundId)
.set(OrderPayment::getPayTime, refund.getData().getRefundTime())
.set(OrderPayment::getTradeNumber, refund.getData().getThirdRefundNo())
.set(OrderPayment::getRespJson, refund.getData().getOriginalData())
.update();
}
}
//触发订单退款时 后续处理
@Async
public void refundOrderAfter(@NonNull Long orderId, @NonNull Long shopId, Long userId, String orderNo,
Integer pointsNum, boolean isFirstRefund, boolean isAllRefund) {
if (isFirstRefund) {
// 退款分销还原
FunUtils.safeRunVoid(() -> distributionUserService.refund(orderId, orderNo),
"订单id:{} 退款,分销处理失败", orderId);
if (userId == null) {
return;
}
FunUtils.safeRunVoid(() -> consumerCouponService.removeConsumerCoupon(shopId, userId, orderId),
"订单id:{} 退款,消费赠券回撤处理失败", orderId);
FunUtils.safeRunVoid(() -> consumeCashbackService.removeCashback(shopId, userId, orderId, orderNo),
"订单id:{} 退款,消费返现扣除处理失败", orderId);
FunUtils.safeRunVoid(() -> pointsConfigService.removeConsumeAwardPoints(shopId, userId, orderId, orderNo),
"订单id:{} 退款,赠送积分扣除失败", orderId);
}
if (isAllRefund && userId != null && pointsNum != null && pointsNum > 0) {
FunUtils.safeRunVoid(() -> mkPointsUserService.alterPoints(userId, null, shopId, PointsConstant.ADD,
pointsNum, orderId, StrUtil.format("订单退款返还{}积分", pointsNum)),
"订单id:{} 退款,赠送积分扣除失败", orderId);
}
}
private void upOrderPayInfo(@NonNull Long orderId, @NonNull PayEnums payType, @NotBlank Long paymentId, String remark) {
if (paymentId == null) {
throw new CzgException("未获取到支付记录");
}
orderInfoCustomService.updatePayOrderId(orderId, paymentId, payType.getValue(), remark);
}
}

View File

@@ -2,32 +2,36 @@ package com.czg.service.order.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapProxy;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.entity.PrintMachine;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.config.RedisCst;
import com.czg.market.service.OrderInfoService;
import com.czg.order.entity.OrderInfo;
import com.czg.order.entity.PrintMachineLog;
import com.czg.order.service.PrintMachineLogService;
import com.czg.service.RedisService;
import com.czg.service.order.mapper.PrintMachineLogMapper;
import com.czg.service.order.print.FeiPrinter;
import com.czg.service.order.print.YxyPrinter;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.*;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* 店铺小票打印记录ServiceImpl
@@ -38,139 +42,47 @@ import java.util.*;
@Slf4j
@Service
@RequiredArgsConstructor
public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMapper, PrintMachineLog> implements PrintMachineLogService{
//请求地址
private static final String URL_STR = "https://ioe.car900.com/v1/openApi/dev/customPrint.json";
//APPID
private static final String APP_ID = "ZF544";
//USERCODE
private static final String USER_CODE = "ZF544";
//APPSECRET
private static final String APP_SECRET = "2022bsjZF544GAH";
public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMapper, PrintMachineLog> implements PrintMachineLogService {
public static final String URL = "http://api.feieyun.cn/Api/Open/";//不需要修改
@Resource
private OrderInfoService orderInfoService;
@Lazy
@Resource
private YxyPrinter yxyPrinter;
@Lazy
@Resource
private FeiPrinter feiPrinter;
@Resource
private RedisService redisService;
public static final String USER = "chaozhanggui2022@163.com";//*必填*:账号名
public static final String UKEY = "UfWkhXxSkeSSscsU";//*必填*: 飞鹅云后台注册账号后生成的UKEY 【备注这不是填打印机的KEY】
public static final String SN = "960238952";//*必填*打印机编号必须要在管理后台里添加打印机或调用API接口添加之后才能调用API
/**
* 获取TOKEN值
*
* @param timestamp 时间戳13位
* @param requestId 请求ID自定义
* @return
*/
private static Map<String, String> getToken(String timestamp, String requestId) {
StringBuilder token = new StringBuilder();
StringBuilder encode = new StringBuilder();
SortedMap<String, Object> map = new TreeMap<>();
map.put("appId", APP_ID);
map.put("timestamp", timestamp);
map.put("requestId", requestId);
map.put("userCode", USER_CODE);
for (Map.Entry<String, Object> next : map.entrySet()) {
String key = next.getKey();
Object value = next.getValue();
token.append(key).append(value);
encode.append(key).append("=").append(value).append("&");
Map<Integer, String> yxxStatusMap = Map.of(
0, "离线(设备上线后自动补打)",
1, "在线",
2, "获取失败",
3, "未激活",
4, "设备已禁用");
@Async
@Override
public void save(PrintMachine config, String bizType, String printContent, String respJson) {
if (config == null) {
return;
}
System.out.println("token" + token);
Map<String, String> finalMap = new HashMap<>();
finalMap.put("ENCODE", encode.toString());
System.out.println("+++++++++++++++" + token + APP_SECRET);
finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
return finalMap;
save(null, config, bizType, printContent, respJson);
}
/**
* 检查打印状态
*
* @param devName 设备名称,(唯一) 对应配置表中的address字段即IP地址/打印机编号)
* @param taskId 打印任务id用于复查打印状态云想印=orderId
* @return
*/
public static String checkPrintStatus(String devName, String taskId) {
String time = String.valueOf(System.currentTimeMillis());
String uuid = UUID.randomUUID().toString();
Map<String, String> param = getToken(time, uuid);
String token = param.get("TOKEN");
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("devName", devName);
paramMap.put("orderId", taskId);
paramMap.put("token", token);
paramMap.put("appId", APP_ID);
paramMap.put("timestamp", time);
paramMap.put("requestId", uuid);
paramMap.put("userCode", USER_CODE);
return HttpUtil.get("https://ioe.car900.com/v1/openApi/dev/findOrder.json", paramMap, 1000 * 5);
}
private static String signature(String USER, String UKEY, String STIME) {
return DigestUtils.sha1Hex(USER + UKEY + STIME);
}
/**
* 检查飞鹅打印机打印任务是否已打印
*
* @param printOrderId 打印订单编号
* @return null-未知错误true-已打印false-未打印
*/
public static Boolean checkFPrintStatus(String printOrderId) {
String STIME = String.valueOf(System.currentTimeMillis() / 1000);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("user", USER);
paramMap.put("stime", STIME);
paramMap.put("sig", signature(USER, UKEY, STIME));
paramMap.put("apiname", "Open_queryOrderState");
paramMap.put("orderid", printOrderId);
Boolean ret;
try {
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
//成功 {"msg":"ok","ret":0,"data":true,"serverExecutedTime":4}
//失败 {"msg":"ok","ret":0,"data":false,"serverExecutedTime":4}
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
ret = json.getBool("data");
} catch (Exception e) {
ret = null;
}
return ret;
}
/**
* 检查飞鹅打印机是否在线
*
* @param sn 设备编号
* @return 在线,工作状态正常。/离线。/未知错误
*/
public static String checkOnline(String sn) {
String STIME = String.valueOf(System.currentTimeMillis() / 1000);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("user", USER);
paramMap.put("stime", STIME);
paramMap.put("sig", signature(USER, UKEY, STIME));
paramMap.put("apiname", "Open_queryPrinterStatus");
paramMap.put("sn", sn);
String msg;
try {
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
//成功 开机 {"msg":"ok","ret":0,"data":"在线,工作状态正常。","serverExecutedTime":4}
//成功 离线 {"msg":"ok","ret":0,"data":"离线。","serverExecutedTime":7}
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
msg = json.getStr("data");
} catch (Exception e) {
msg = "未知错误";
}
return msg;
}
/**
* 保存打印记录
*
* @param orderId 订单Id
* @param config 打印机配置
* @param bizType 业务类型
* @param printContent 打印内容
* @param respJson 打印机响应结果
*/
@Async
public void save(PrintMachine config, String bizType, String printContent, Object respJson) {
@Override
public void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson) {
if (config == null) {
return;
}
@@ -179,51 +91,41 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
int failFlag = 0;
String respCode = "0";
String respMsg = "打印中";
Map<Integer, String> yxxStatusMap = MapUtil.builder(0, "离线(设备上线后自动补打)").put(1, "在线").put(2, "获取失败").put(3, "未激活").put(4, "设备已禁用").build();
JSONObject resp = JSONObject.parseObject(respJson);
// 云想印
if ("".equals(config.getContentType())) {
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson);
int code = resp.getInt("code");
cn.hutool.json.JSONObject data = resp.getJSONObject("data").getJSONObject("data");
if ("".equals(config.getContentType())) {
int code = resp.getIntValue("code");
JSONObject respData = resp.getJSONObject("data");
JSONObject data = respData.getJSONObject("data");
//设备状态0: 离线, 1: 在线, 2: 获取失败, 3:未激活, 4:设备已禁用
int status = data.getInt("status");
int status = data.getIntValue("status");
if (code != 0) {
failFlag = 1;
respCode = code + "";
respMsg = resp.getStr("msg");
respMsg = resp.getString("msg");
} else if (status != 1) {
failFlag = 1;
respCode = code + "";
respMsg = status + "_" + yxxStatusMap.get(status);
}
if (code == 0) {
String taskId = resp.getJSONObject("data").getStr("orderId");
entity.setTaskId(taskId);
entity.setTaskId(respData.getString("orderId"));
}
// 飞鹅云打印机暂时没有适配先return不做打印记录
} else if ("飞鹅".equals(config.getContentType())) {
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson);
int ret = resp.getInt("ret");
int ret = resp.getIntValue("ret");
if (ret != 0) {
failFlag = 1;
respCode = ret + "";
respMsg = resp.getStr("msg");
respMsg = resp.getString("msg");
} else {
String printOrderId = resp.getStr("data");
entity.setTaskId(printOrderId);
entity.setTaskId(resp.getString("data"));
}
} else {
// 其他打印机暂时没有适配先return不做打印记录
return;
}
entity.setBizType(bizType);
// entity.setCreateUserId(config.getCurrentUserId());
// entity.setCreateUserName(config.getCurrentUserName());
// if (StrUtil.isNotBlank(config.getCurrentUserNickName())) {
// entity.setCreateUserName(StrUtil.concat(true, config.getCurrentUserNickName(), " | ", config.getCurrentUserName()));
// }
entity.setPrintContent(printContent);
entity.setCreateTime(DateUtil.date().toLocalDateTime());
if (failFlag == 0) {
@@ -233,66 +135,213 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
entity.setRespCode(respCode);
entity.setRespMsg(respMsg);
super.save(entity);
ThreadUtil.execAsync(() -> checkPrintStatus(orderId, config, entity));
}
// 云想印
if ("云享印".equals(config.getContentType())) {
// 延迟3ms复查打印状态 (用户可以根据设备信息查询到当前设备的在线情况该接口只能提供参考设备的离线状态是在设备离线3分钟后才会生效)
ThreadUtil.safeSleep(1000 * 5);
String jsonStr = checkPrintStatus(config.getAddress(), entity.getTaskId());
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(jsonStr);
int code = resp.getInt("code");
if (code == 0) {
cn.hutool.json.JSONObject data = resp.getJSONObject("data");
boolean status = data.containsKey("status");
if (!status) {
/**
* 类级别成员变量基于虚拟线程的固定大小5定时线程池
* // Java 21+ 虚拟线程工厂,支持命名
*/
private final ScheduledExecutorService virtualThreadScheduler = Executors.newScheduledThreadPool(
5,
Thread.ofVirtual().name("print-query-vt-", 0).factory()
);
/**
* 打印机状态查询(解决 retryFuture 爆红问题 + 虚拟线程 + 轮询重试)
*
* @param orderId 订单Id
* @param config 打印机配置
* @param entity 打印日志实体
*/
public void checkPrintStatus(Long orderId, PrintMachine config, PrintMachineLog entity) {
// 最大重试次数
int maxRetryTimes = 5;
AtomicInteger executedTimes = new AtomicInteger(0);
// 原子引用包装ScheduledFuture用于后续取消轮询
AtomicReference<ScheduledFuture<?>> retryFutureRef = new AtomicReference<>();
// 核心查询任务(修正后,逻辑内聚)
Runnable printQueryTask = () -> {
int currentTimes = executedTimes.incrementAndGet();
boolean isPrintSuccess = false;
boolean isLastTask = false;
try {
// 1. 云想印打印机状态查询
if ("云想印".equals(config.getContentType())) {
String jsonStr = yxyPrinter.checkPrintStatus(config.getAddress(), entity.getTaskId());
log.info("云想印打印状态查询结果(第{}次,虚拟线程:{}{}",
currentTimes, Thread.currentThread().getName(), jsonStr);
JSONObject resp = JSONObject.parseObject(jsonStr);
int code = resp.getIntValue("code");
if (code == 0) {
JSONObject data = resp.getJSONObject("data");
if (data.containsKey("status")) {
isPrintSuccess = data.getBooleanValue("status", false);
updatePrintLogEntity(entity, isPrintSuccess);
}
}
}
// 2. 飞鹅云打印机状态查询
else if ("飞鹅".equals(config.getContentType())) {
Boolean success = feiPrinter.checkFPrintStatus(entity.getTaskId());
if (success == null) {
entity.setFailFlag(1);
entity.setRespMsg("打印失败,未知错误");
entity.setPrintTime(null);
} else if (success) {
isPrintSuccess = true;
updatePrintLogEntity(entity, true);
} else {
String msg = feiPrinter.checkOnline(entity.getAddress());
if (msg.indexOf("在线,工作状态正常") > 0) {
isPrintSuccess = true;
updatePrintLogEntity(entity, true);
} else {
isPrintSuccess = false;
entity.setFailFlag(1);
entity.setPrintTime(null);
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg));
}
}
} else {
log.info("打印类型为其他类型,终止打印状态查询轮询任务");
ScheduledFuture<?> future = retryFutureRef.get();
if (future != null && !future.isCancelled()) {
boolean cancelSuccess = future.cancel(false); // 取消后续轮询(不中断当前任务)
log.info("其他打印类型,取消轮询任务:{}", cancelSuccess ? "成功" : "失败");
}
}
// 3. 打印成功:取消后续轮询任务
if (isPrintSuccess) {
isLastTask = true;
ScheduledFuture<?> future = retryFutureRef.get();
if (future != null && !future.isCancelled()) {
future.cancel(false); // 不中断当前任务,仅取消后续任务
}
return;
}
boolean success = data.getBool("status", false);
if (entity.getFailFlag() == 0 && success) {
entity.setFailFlag(0);
entity.setRespMsg("打印成功");
entity.setPrintTime(entity.getCreateTime());
} else if (entity.getFailFlag() == 1 && success) {
entity.setFailFlag(0);
entity.setPrintTime(DateUtil.date().toLocalDateTime());
entity.setRespMsg("打印成功");
// 如果设备在线 and 休眠5秒后查询结果是未打印即视为设备已离线云端3分钟后才会同步到离线信息
} else if (entity.getFailFlag() == 0 && !success) {
entity.setFailFlag(1);
entity.setPrintTime(null);
entity.setRespMsg("0_离线(设备上线后自动补打)");
} else {
entity.setFailFlag(1);
entity.setPrintTime(null);
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", entity.getRespMsg()));
// 4. 达到最大重试次数:取消后续轮询任务
if (currentTimes >= maxRetryTimes) {
isLastTask = true;
ScheduledFuture<?> future = retryFutureRef.get();
if (future != null && !future.isCancelled()) {
future.cancel(false);
}
}
} catch (Exception e) {
log.error("第{}次打印机状态查询异常(虚拟线程:{}",
currentTimes, Thread.currentThread().getName(), e);
// 异常时达到最大重试次数,同样取消任务
if (currentTimes >= maxRetryTimes) {
ScheduledFuture<?> future = retryFutureRef.get();
if (future != null && !future.isCancelled()) {
boolean cancelSuccess = future.cancel(false);
log.info("查询异常且达到最大重试次数,取消轮询任务:{}", cancelSuccess ? "成功" : "失败");
}
}
} finally {
//仅当是最后一次任务时,才执行更新操作
if (isLastTask) {
super.updateById(entity);
updateOrderEntity(orderId, config, isPrintSuccess);
}
super.updateById(entity);
}
// 飞鹅云打印机
} else if ("飞鹅".equals(config.getContentType())) {
ThreadUtil.safeSleep(1000 * 5);
Boolean success = checkFPrintStatus(entity.getTaskId());
if (success == null) {
entity.setFailFlag(1);
entity.setRespMsg("打印失败,未知错误");
} else if (success) {
entity.setFailFlag(0);
entity.setPrintTime(DateUtil.date().toLocalDateTime());
entity.setRespMsg("打印成功");
};
// 修正统一使用scheduleAtFixedRate避免任务重复执行
// 首次延迟10秒执行后续每隔30秒执行一次符合原逻辑的首次查询延迟后续轮询间隔
ScheduledFuture<?> retryFuture = virtualThreadScheduler.scheduleAtFixedRate(
printQueryTask,
10,
30,
TimeUnit.SECONDS
);
// 修正先赋值AtomicReference再让任务可能执行避免线程安全隐患
retryFutureRef.set(retryFuture);
// 修正:关闭钩子仅注册一次(通过静态代码块或类初始化时注册,避免重复注册)
registerShutdownHookOnce();
}
/**
* 更新状态
*/
private void updateOrderEntity(Long orderId, PrintMachine config, boolean isPrintSuccess) {
redisService.runFunAndCheckKey(() -> {
OrderInfo orderInfo = orderInfoService.getOne(query().select(OrderInfo::getPrintStatus).eq(OrderInfo::getId, orderId));
if (orderInfo == null) {
orderInfo = new OrderInfo();
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", config.getId());
jsonObject.put("name", config.getName());
jsonObject.put("time", LocalDateTimeUtil.formatNormal(LocalDateTime.now()));
orderInfo.upPrintStatus(jsonObject, isPrintSuccess);
orderInfoService.update(orderInfo, query().eq(OrderInfo::getId, orderId));
return orderInfo;
}, RedisCst.getLockKey("UP_ORDER_PRINT", orderId));
redisService.del("order:print:" + orderId);
}
/**
* 统一更新打印日志实体
*
* @param entity 打印日志实体
* @param isPrintSuccess 是否打印成功
*/
private void updatePrintLogEntity(PrintMachineLog entity, boolean isPrintSuccess) {
if (isPrintSuccess) {
entity.setFailFlag(0);
entity.setRespMsg("打印成功");
entity.setPrintTime(entity.getFailFlag() == 0 ? entity.getCreateTime() : DateUtil.date().toLocalDateTime());
} else {
entity.setFailFlag(1);
entity.setPrintTime(null);
if (entity.getFailFlag() == 0) {
entity.setRespMsg("0_离线(设备上线后自动补打)");
} else {
String msg = checkOnline(entity.getAddress());
if (msg.indexOf("在线,工作状态正常") > 0) {
entity.setFailFlag(0);
entity.setPrintTime(DateUtil.date().toLocalDateTime());
entity.setRespMsg("打印成功");
} else {
entity.setFailFlag(1);
entity.setPrintTime(null);
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg));
}
entity.setRespMsg(entity.getRespMsg());
}
super.updateById(entity);
}
}
// 静态标识,确保关闭钩子仅注册一次
private static volatile boolean shutdownHookRegistered = false;
// 锁对象,保证线程安全
private static final Object HOOK_LOCK = new Object();
/**
* 统一注册JVM关闭钩子仅执行一次
*/
private void registerShutdownHookOnce() {
if (!shutdownHookRegistered) {
synchronized (HOOK_LOCK) {
// 双重校验锁,避免多线程下重复注册
if (!shutdownHookRegistered) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (virtualThreadScheduler != null && !virtualThreadScheduler.isShutdown()) {
virtualThreadScheduler.shutdown();
try {
if (!virtualThreadScheduler.awaitTermination(10, TimeUnit.SECONDS)) {
log.warn("虚拟线程调度器10秒内未关闭强制关闭...");
virtualThreadScheduler.shutdownNow();
}
} catch (InterruptedException e) {
log.error("等待虚拟线程调度器终止时被中断,强制关闭", e);
virtualThreadScheduler.shutdownNow();
Thread.currentThread().interrupt(); // 保留中断状态
}
}
}, "PrinterScheduler-ShutdownHook"));
shutdownHookRegistered = true;
}
}
}
}
}

View File

@@ -0,0 +1,176 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.EntryManager;
import com.czg.OcrUtils;
import com.czg.PayCst;
import com.czg.config.RabbitPublisher;
import com.czg.constants.ParamCodeCst;
import com.czg.dto.req.*;
import com.czg.order.entity.ShopDirectMerchant;
import com.czg.service.order.dto.AggregateMerchantVO;
import com.czg.service.order.dto.MerchantQueryDTO;
import com.czg.service.order.mapper.ShopDirectMerchantMapper;
import com.czg.service.order.service.ShopDirectMerchantService;
import com.czg.system.service.SysParamsService;
import com.czg.utils.FunUtils;
import com.czg.utils.PageUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
/**
* 商户进件 服务层实现。
*
* @author ww
* @since 2026-01-07
*/
@Service
public class ShopDirectMerchantServiceImpl extends ServiceImpl<ShopDirectMerchantMapper, ShopDirectMerchant> implements ShopDirectMerchantService {
@Resource
private RabbitPublisher rabbitPublisher;
@DubboReference
private SysParamsService sysParamsService;
// 全局原子计数器,按天重置(避免数值过大)
private static final AtomicLong COUNTER = new AtomicLong(0);
// 记录上一次的日期,用于重置计数器
private static String LAST_DATE = DateUtil.format(new Date(), "yyyyMMdd");
@Override
public JSONObject getInfoByImg(String url, String type) throws Exception {
Map<String, String> aliOssKeys = sysParamsService.getParamsByMap("ali_oss_key_set", ParamCodeCst.ALI_OSS_KEY_SET);
return OcrUtils.getInfoByImg(aliOssKeys.get(ParamCodeCst.AliYun.ALI_SMS_KEY), aliOssKeys.get(ParamCodeCst.AliYun.ALI_SMS_SECRET), url, type);
}
@Override
public Page<ShopDirectMerchant> getEntryList(MerchantQueryDTO queryParam) {
PageHelper.startPage(queryParam.getPage(), queryParam.getSize());
List<ShopDirectMerchant> entryList = mapper.getEntryList(queryParam);
return PageUtil.convert(new PageInfo<>(entryList));
}
@Override
public AggregateMerchantVO getEntry(Long shopId) {
ShopDirectMerchant merchant = getOne(query()
.eq(ShopDirectMerchant::getShopId, shopId));
if (merchant == null) {
return null;
}
return convertVO(merchant);
}
@Override
public boolean entryManager(AggregateMerchantDto reqDto) {
boolean isSave = false;
boolean result;
if (StrUtil.isBlank(reqDto.getMerchantCode())) {
reqDto.setMerchantCode(getMerchantCode());
isSave = true;
}
EntryManager.verifyEntryParam(reqDto);
ShopDirectMerchant merchant = new ShopDirectMerchant();
merchant.setShopId(reqDto.getShopId());
merchant.setMerchantCode(reqDto.getMerchantCode());
merchant.setLicenceNo(reqDto.getBusinessLicenceInfo().getLicenceNo());
MerchantBaseInfoDto merchantBaseInfo = reqDto.getMerchantBaseInfo();
merchant.setAlipayAccount(merchantBaseInfo.getAlipayAccount());
merchant.setUserType(merchantBaseInfo.getUserType());
merchant.setShortName(merchantBaseInfo.getShortName());
merchant.setMerchantBaseInfo(JSONObject.toJSONString(reqDto.getMerchantBaseInfo()));
merchant.setLegalPersonInfo(JSONObject.toJSONString(reqDto.getLegalPersonInfo()));
merchant.setBusinessLicenceInfo(JSONObject.toJSONString(reqDto.getBusinessLicenceInfo()));
merchant.setStoreInfo(JSONObject.toJSONString(reqDto.getStoreInfo()));
merchant.setSettlementInfo(JSONObject.toJSONString(reqDto.getSettlementInfo()));
if (isSave) {
merchant.setAlipayStatus(PayCst.EntryStatus.WAIT);
merchant.setWechatStatus(PayCst.EntryStatus.WAIT);
result = save(merchant);
} else {
ShopDirectMerchant directMerchant = getById(reqDto.getShopId());
if (directMerchant.getAlipayStatus().equals(PayCst.EntryStatus.INIT) || directMerchant.getAlipayStatus().equals(PayCst.EntryStatus.REJECTED)) {
merchant.setAlipayStatus(PayCst.EntryStatus.WAIT);
}
if (directMerchant.getWechatStatus().equals(PayCst.EntryStatus.INIT) || directMerchant.getWechatStatus().equals(PayCst.EntryStatus.REJECTED)) {
merchant.setWechatStatus(PayCst.EntryStatus.WAIT);
}
result = updateById(merchant);
}
//发送进件队列消息
FunUtils.transactionSafeRun(() -> rabbitPublisher.sendEntryManagerMsg(reqDto.getShopId() + ":" + merchant.getLicenceNo()));
return result;
}
private static String getMerchantCode() {
Date now = new Date();
// 1. 获取当前日期yyyyMMdd
String currentDate = DateUtil.format(now, "yyyyMMdd");
// 2. 每天重置计数器,避免数值溢出
synchronized (COUNTER) {
if (!currentDate.equals(LAST_DATE)) {
COUNTER.set(0);
LAST_DATE = currentDate;
}
}
// 3. 原子递增,获取唯一序号(同一毫秒内不会重复)
long seq = COUNTER.incrementAndGet();
// 4. 时间戳(毫秒级)+ 6位序号补零
String timeStr = DateUtil.format(now, "yyyyMMddHHmmss");
String seqStr = String.format("%03d", seq);
return "CZG" + timeStr + seqStr;
}
public AggregateMerchantVO convertVO(ShopDirectMerchant entity) {
if (entity == null) {
return null;
}
AggregateMerchantVO vo = new AggregateMerchantVO();
vo.setShopId(entity.getShopId());
vo.setMerchantCode(entity.getMerchantCode());
vo.setShopName(entity.getShopName());
// 解析JSON字段
vo.setMerchantBaseInfo(JSONObject.parseObject(entity.getMerchantBaseInfo(), MerchantBaseInfoDto.class));
vo.setLegalPersonInfo(JSONObject.parseObject(entity.getLegalPersonInfo(), LegalPersonInfoDto.class));
vo.setBusinessLicenceInfo(JSONObject.parseObject(entity.getBusinessLicenceInfo(), BusinessLicenceInfoDto.class));
vo.setStoreInfo(JSONObject.parseObject(entity.getStoreInfo(), StoreInfoDto.class));
vo.setSettlementInfo(JSONObject.parseObject(entity.getSettlementInfo(), SettlementInfoDto.class));
// 设置其他字段
vo.setUserType(entity.getUserType());
vo.setShortName(entity.getShortName());
vo.setAlipayAccount(entity.getAlipayAccount());
vo.setCreateTime(entity.getCreateTime());
vo.setUpdateTime(entity.getUpdateTime());
vo.setWechatApplyId(entity.getWechatApplyId());
vo.setWechatStatus(entity.getWechatStatus());
vo.setWechatErrorMsg(entity.getWechatErrorMsg());
vo.setWechatSignUrl(entity.getWechatSignUrl());
vo.setAlipayOrderId(entity.getAlipayOrderId());
vo.setAlipayStatus(entity.getAlipayStatus());
vo.setAlipayErrorMsg(entity.getAlipayErrorMsg());
vo.setAlipaySignUrl(entity.getAlipaySignUrl());
return vo;
}
}

View File

@@ -0,0 +1,148 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.service.ShopInfoService;
import com.czg.constants.SystemConstants;
import com.czg.order.dto.ShopMerchantDTO;
import com.czg.order.entity.ShopDirectMerchant;
import com.czg.order.entity.ShopMerchant;
import com.czg.order.service.ShopMerchantService;
import com.czg.pay.AlipayAuthInfoDto;
import com.czg.pay.NativeMerchantDTO;
import com.czg.pay.PolyMerchantDTO;
import com.czg.service.order.mapper.ShopMerchantMapper;
import com.czg.service.order.service.ShopDirectMerchantService;
import com.czg.utils.AssertUtil;
import com.czg.utils.CzgStrUtils;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
* 第三方商户进件 服务层实现。
*
* @author Administrator
* @since 2025-02-11
*/
@CacheConfig(cacheNames = "shop:merchant")
@Service
public class ShopMerchantServiceImpl extends ServiceImpl<ShopMerchantMapper, ShopMerchant> implements ShopMerchantService {
@Resource
private ShopDirectMerchantService shopDirectMerchantService;
@DubboReference
private ShopInfoService shopInfoService;
@Override
public ShopMerchantDTO detail(Long shopId) {
ShopMerchantDTO shopMerchantVO = new ShopMerchantDTO();
ShopMerchant one = getOne(query().eq(ShopMerchant::getShopId, shopId));
if (one == null) {
return null;
}
shopMerchantVO.setShopId(shopId);
shopMerchantVO.setChannel(one.getChannel());
shopMerchantVO.setRelatedId(one.getRelatedId());
if (StrUtil.isNotBlank(one.getNativePayJson())) {
shopMerchantVO.setNativeMerchantDTO(JSONObject.parseObject(one.getNativePayJson(), NativeMerchantDTO.class));
}
if (StrUtil.isNotBlank(one.getPolyPayJson())) {
shopMerchantVO.setPolyMerchantDTO(JSONObject.parseObject(one.getPolyPayJson(), PolyMerchantDTO.class));
}
if (one.getRelatedId() != null) {
shopMerchantVO.setShopDirectMerchant(shopDirectMerchantService.getOne(query().eq(ShopDirectMerchant::getShopId, one.getRelatedId())));
}
return shopMerchantVO;
}
/**
* * shopId 修改的店铺Id
* * relatedLicenceNo 关联值 原生native必填 对应 tb_shop_direct_merchant 的 licence_no营业执照
* * channel {@link com.czg.constant.PayChannelCst}
* * jsonObject 支付参数 详情
* * isUp 是否要修改 传否时 如果存在支付数据 且与当前渠道不一致 则不修改
*/
@Override
@CacheEvict(key = "#shopMerchantParam.shopId")
public Boolean editEntry(ShopMerchantDTO shopMerchantParam, boolean isUp) {
ShopMerchant shopMerchant = getOne(query().eq(ShopMerchant::getShopId, shopMerchantParam.getShopId()));
if (shopMerchant == null) {
shopMerchant = new ShopMerchant();
shopMerchant.setAlipayAppId(SystemConstants.PayType.ALIPAY_APP_ID);
shopMerchant.setWechatAppId(SystemConstants.PayType.WECHAT_APP_ID);
shopMerchant.setRelatedId(shopMerchantParam.getShopId());
}
shopMerchant.setShopId(shopMerchantParam.getShopId());
if (isUp) {
shopMerchant.setChannel(CzgStrUtils.getStrOrNull(shopMerchantParam.getChannel()));
shopMerchant.setRelatedId(shopMerchantParam.getRelatedId());
if (shopMerchantParam.getRelatedId() == null || !shopMerchantParam.getRelatedId().equals(shopMerchant.getRelatedId())) {
ShopDirectMerchant shopDirectMerchant = shopDirectMerchantService.getById(shopMerchantParam.getRelatedId());
if (shopDirectMerchant != null) {
NativeMerchantDTO nativeMerchantDTO = new NativeMerchantDTO();
nativeMerchantDTO.setWechatMerchantId(shopDirectMerchant.getWechatMerchantId());
nativeMerchantDTO.setAlipayMerchantId(shopDirectMerchant.getAlipayMerchantId());
if (StrUtil.isNotBlank(shopDirectMerchant.getAlipayAuthInfo())) {
AlipayAuthInfoDto alipayAuthInfoDto = JSONObject.parseObject(shopDirectMerchant.getAlipayAuthInfo(), AlipayAuthInfoDto.class);
nativeMerchantDTO.setAlipayAuthInfo(alipayAuthInfoDto);
}
shopMerchant.setNativePayJson(JSONObject.toJSONString(nativeMerchantDTO));
} else {
shopMerchant.setNativePayJson(null);
shopMerchant.setWechatAppId(null);
shopMerchant.setAlipayAppId(null);
}
}
if (shopMerchantParam.getPolyMerchantDTO() != null) {
shopMerchant.setPolyPayJson(JSONObject.toJSONString(shopMerchantParam.getPolyMerchantDTO()));
}
} else {
//只有进件会进入这里
if (shopMerchant.getRelatedId() == null) {
shopMerchant.setRelatedId(shopMerchantParam.getShopId());
}
if (StrUtil.isBlank(shopMerchant.getNativePayJson()) && shopMerchantParam.getNativeMerchantDTO() != null) {
shopMerchant.setNativePayJson(JSONObject.toJSONString(shopMerchantParam.getNativeMerchantDTO()));
}
}
if (shopMerchant.getId() == null) {
return save(shopMerchant);
} else {
return updateById(shopMerchant, false);
}
}
@Override
@CacheEvict(allEntries = true)
public void upMerchant(@NotBlank Long relatedId, @NotNull NativeMerchantDTO nativeMerchantDTO) {
ShopMerchant upShopMerchant = new ShopMerchant();
upShopMerchant.setAlipayAppId(nativeMerchantDTO.getAlipayMerchantId());
upShopMerchant.setWechatAppId(nativeMerchantDTO.getWechatMerchantId());
upShopMerchant.setNativePayJson(JSONObject.toJSONString(nativeMerchantDTO));
update(upShopMerchant, query().eq(ShopMerchant::getRelatedId, relatedId));
}
@Cacheable(key = "#shopId")
@Override
public ShopMerchant getByShopId(Long shopId) {
ShopMerchant shopMerchant = getOne(query().eq(ShopMerchant::getShopId, shopId));
AssertUtil.isNull(shopMerchant, "暂未开通支付.");
return shopMerchant;
}
@Override
public ShopDirectMerchant getMainMerchant(Long shopId) {
Long mainIdByShopId = shopInfoService.getMainIdByShopId(shopId);
if (mainIdByShopId != null) {
return shopDirectMerchantService.getOne(query().eq(ShopDirectMerchant::getShopId, mainIdByShopId));
}
return null;
}
}

View File

@@ -0,0 +1,362 @@
package com.czg.service.order.service.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO;
import com.czg.account.entity.*;
import com.czg.account.service.*;
import com.czg.constants.PayTypeConstants;
import com.czg.enums.CzgPayEnum;
import com.czg.enums.ShopUserFlowBizEnum;
import com.czg.exception.CzgException;
import com.czg.market.dto.MemberOrderDTO;
import com.czg.market.entity.MemberOrder;
import com.czg.market.entity.MkShopCouponRecord;
import com.czg.market.service.MemberOrderService;
import com.czg.market.service.MkShopCouponRecordService;
import com.czg.market.service.MkShopRechargeService;
import com.czg.market.vo.MkShopRechargeVO;
import com.czg.order.dto.CheckOrderPay;
import com.czg.order.dto.LtPayOtherDTO;
import com.czg.order.entity.OrderInfo;
import com.czg.order.entity.OrderPayment;
import com.czg.order.service.OrderInfoCustomService;
import com.czg.order.service.OrderPaymentService;
import com.czg.pay.CzgPayBaseReq;
import com.czg.pay.CzgRefundReq;
import com.czg.pay.RefundRespDTO;
import com.czg.resp.CzgResult;
import com.czg.service.order.dto.VipMemberPayParamDTO;
import com.czg.service.order.dto.VipPayParamDTO;
import com.czg.service.order.dto.VipRefundDTO;
import com.czg.service.order.service.PayService;
import com.czg.service.order.service.ShopUserPayService;
import com.czg.utils.AssertUtil;
import com.czg.utils.CzgRandomUtils;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* @author ww
*/
@Service
public class ShopUserServiceImpl implements ShopUserPayService {
@Resource
private PayService payService;
@DubboReference
private ShopUserService shopUserService;
@DubboReference
private UserInfoService userInfoService;
@DubboReference
private ShopInfoService shopInfoService;
@DubboReference
private FreeDineConfigService freeConfigService;
@Resource
private OrderInfoCustomService orderInfoCustomService;
@DubboReference
private MemberOrderService memberOrderService;
@Resource
private MkShopRechargeService shopRechargeService;
@DubboReference
private ShopUserFlowService userFlowService;
@Resource
private MkShopCouponRecordService recordService;
@Resource
private OrderPaymentService paymentService;
/**
* 会员充值 校验
*
* @return 是否是霸王餐充值
*/
private boolean checkPayVip(VipPayParamDTO payParam) {
if (payParam.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new CzgException("充值金额不正确");
}
if (payParam.getOrderId() != null) {
FreeDineConfig freeConfig = freeConfigService.getById(shopInfoService.getMainIdByShopId(payParam.getShopId()));
AssertUtil.isNull(freeConfig, "该店铺未启用霸王餐");
if (!freeConfig.getEnable()) {
throw new CzgException("该店铺未启用霸王餐");
}
CheckOrderPay checkOrderPay = payParam.getCheckOrderPay();
OrderInfo orderInfo = orderInfoCustomService.checkOrderPay(checkOrderPay.setFreeDine(true).setWithCoupon(freeConfig.getWithCoupon()).setWithPoints(freeConfig.getWithPoints()));
payParam.setAmount(orderInfo.getOrderAmount().multiply(BigDecimal.valueOf(freeConfig.getRechargeTimes())));
return true;
}
return false;
}
@Override
@Transactional
public CzgResult<Object> cashPayVip(VipPayParamDTO payParam) {
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在");
ShopInfo shopInfo = shopInfoService.getById(payParam.getShopId());
AssertUtil.isNull(shopInfo, "店铺不存在");
if (shopInfo.getIsMemberInPwd().equals(1)) {
AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用");
AssertUtil.isBlank(payParam.getPwd(), "请输入操作密码后充值");
if (!shopInfo.getOperationPwd().equals(SecureUtil.md5(payParam.getPwd()))) {
return CzgResult.failure("支付密码错误");
}
}
// if (shopUser.getIsVip().equals(0)) {
// //更新会员
// ShopUser updateInfo = new ShopUser();
// updateInfo.setIsVip(1);
// updateInfo.setJoinTime(LocalDateTime.now());
// updateInfo.setId(payParam.getShopUserId());
// shopUserService.updateById(updateInfo);
// }
shopRechargeService.recharge(shopUser.getMainShopId(), shopUser.getId(), payParam.getRechargeDetailId(),
payParam.getAmount(), null, "cash", ShopUserFlowBizEnum.CASH_IN, true);
return CzgResult.success();
}
@Override
@Transactional
public CzgResult<Map<String, Object>> jsPayVip(String clintIp, VipPayParamDTO payParam) {
boolean isFree = checkPayVip(payParam);
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在");
AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空");
AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空");
String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake();
String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN;
payService.initPayment(OrderPayment.pay(payParam.getShopId(), shopUser.getId(), payType, payOrderNo,
payParam.getAmount(), "", isFree ? payParam.getOrderId() : payParam.getActivateId()));
return payService.pay(payParam.getShopId(), CzgPayEnum.JS_PAY,
CzgPayBaseReq.jsPayReq(payOrderNo, "会员充值", payParam.getAmount().multiply(PayService.MONEY_RATE).longValue(),
payParam.getPayType(), payParam.getOpenId(), clintIp));
}
@Override
@Transactional(rollbackFor = Exception.class)
public CzgResult<Map<String, Object>> ltPayMember(String clientIp, VipMemberPayParamDTO payParam) {
ShopUser shopUser = shopUserService.getOne(new QueryWrapper().eq(ShopUser::getMainShopId, shopInfoService.getMainIdByShopId(payParam.getShopId())).eq(ShopUser::getId, payParam.getShopUserId()));
AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在");
MemberOrder memberOrder = memberOrderService.createMemberOrder(new MemberOrderDTO().setName(payParam.getName())
.setNum(1).setNickName(payParam.getNickName()).setOrderType(payParam.getOrderType())
.setPlatformType(payParam.getPlatformType()).setSex(payParam.getSex()).setUserId(shopUser.getUserId())
.setShopId(payParam.getShopId()).setBirthDay(payParam.getBirthDay()));
AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空");
AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空");
LtPayOtherDTO payParam1 = new LtPayOtherDTO()
.setOpenId(payParam.getOpenId())
.setPayType(payParam.getPayType())
.setShopId(payParam.getShopId())
.setRecordId(shopUser.getId())
.setPrice(memberOrder.getAmount())
.setIp(clientIp);
return payService.ltPayOther(payParam1, PayTypeConstants.SourceType.MEMBER_PAY, "会员充值");
}
@Override
@Transactional
public CzgResult<Map<String, Object>> ltPayVip(String clintIp, VipPayParamDTO payParam) {
// 霸王餐校验
boolean isFree = checkPayVip(payParam);
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在");
AssertUtil.isBlank(payParam.getOpenId(), "用户小程序ID不能为空");
AssertUtil.isBlank(payParam.getPayType(), "支付方式不能为空");
MkShopRechargeVO rechargeVO = shopRechargeService.detail(payParam.getShopId());
if (payParam.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) {
throw new CzgException("未开启自定义充值金额");
}
Long mainShopId = shopInfoService.getMainIdByShopId(payParam.getShopId());
if (isFree) {
BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, payParam.getShopId(), shopUser.getUserId(), payParam.getRechargeDetailId(), payParam.getAmount());
payParam.setAmount(amount);
}
String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN;
LtPayOtherDTO payParam1 = new LtPayOtherDTO()
.setOpenId(payParam.getOpenId())
.setPayType(payParam.getPayType())
.setShopId(payParam.getShopId())
.setRecordId(shopUser.getId())
.setPrice(payParam.getAmount())
.setIp(clintIp);
return payService.ltPayOther(payParam1, payType, "会员充值");
}
@Override
public CzgResult<Map<String, Object>> recharge(String clientIp, VipPayParamDTO rechargeDTO, Long shopUserId) {
boolean isFree = checkPayVip(rechargeDTO);
Long mainShopId = shopInfoService.getMainIdByShopId(rechargeDTO.getShopId());
ShopUser shopUser = shopUserService.getOne(new QueryWrapper().eq(ShopUser::getMainShopId, mainShopId)
.eq(ShopUser::getId, shopUserId));
AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在");
MkShopRechargeVO rechargeVO = shopRechargeService.detail(rechargeDTO.getShopId());
if (rechargeDTO.getRechargeDetailId() == null && rechargeVO.getIsCustom() == 0) {
throw new CzgException("未开启自定义充值金额");
}
UserInfo userInfo = userInfoService.getById(shopUser.getUserId());
BigDecimal amount = shopRechargeService.checkRecharge(mainShopId, rechargeDTO.getShopId(), shopUser.getUserId(), rechargeDTO.getRechargeDetailId(), rechargeDTO.getAmount());
String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN;
LtPayOtherDTO payParam = new LtPayOtherDTO()
.setOpenId("wechatPay".equals(rechargeDTO.getPayType()) ? userInfo.getWechatOpenId() : userInfo.getAlipayOpenId())
.setPayType(rechargeDTO.getPayType())
.setShopId(rechargeDTO.getShopId())
.setRecordId(shopUser.getId())
.setPrice(amount)
.setIp(clientIp);
return payService.ltPayOther(payParam, payType, "会员充值");
}
@Override
@Transactional
public CzgResult<Map<String, Object>> scanPayVip(String clintIp, VipPayParamDTO payParam) {
boolean isFree = checkPayVip(payParam);
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在");
String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake();
String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN;
payService.initPayment(OrderPayment.pay(payParam.getShopId(), shopUser.getId(), payType, payOrderNo,
payParam.getAmount(), "", isFree ? payParam.getOrderId() : payParam.getActivateId()));
return payService.pay(payParam.getShopId(), CzgPayEnum.SCAN_PAY,
CzgPayBaseReq.scanPayReq(payOrderNo, "会员充值", payParam.getAmount().multiply(PayService.MONEY_RATE).longValue(), clintIp));
}
@Override
@Transactional
public CzgResult<Map<String, Object>> microPayVip(VipPayParamDTO payParam) {
boolean isFree = checkPayVip(payParam);
AssertUtil.isBlank(payParam.getAuthCode(), "扫描码不能为空");
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
AssertUtil.isNull(shopUser, "充值失败 该店铺用户不存在");
String payOrderNo = payParam.getPlatformType() + CzgRandomUtils.snowflake();
String payType = isFree ? PayTypeConstants.SourceType.FREE : PayTypeConstants.SourceType.MEMBER_IN;
payService.initPayment(OrderPayment.pay(payParam.getShopId(), shopUser.getId(), payType, payOrderNo, payParam.getAmount(), "", isFree ? payParam.getOrderId() : payParam.getActivateId()));
CzgResult<Map<String, Object>> mapCzgResult = payService.pay(payParam.getShopId(), CzgPayEnum.MICRO_PAY,
CzgPayBaseReq.microPay(payOrderNo, "会员充值", payParam.getAmount().multiply(PayService.MONEY_RATE).longValue(), payParam.getAuthCode()));
mapCzgResult.setData(Map.of("payOrderNo", payOrderNo));
return mapCzgResult;
}
@Override
public CzgResult<Map<String, BigDecimal>> refundVipBefore(VipRefundDTO payParam) {
Map<String, BigDecimal> resultMap = new HashMap<>(5);
ShopUser shopUser = shopUserService.getShopUserInfo(payParam.getShopId(), payParam.getUserId());
AssertUtil.isNull(shopUser, "该店铺用户不存在");
ShopUserFlow inFlow = userFlowService.getById(payParam.getFlowId());
AssertUtil.isNull(inFlow, "充值记录不存在");
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq(ShopUserFlow::getRelationId, payParam.getFlowId());
queryWrapper.eq(ShopUserFlow::getBizCode, ShopUserFlowBizEnum.AWARD_IN.getCode());
ShopUserFlow giftFlow = userFlowService.getOne(queryWrapper);
resultMap.put("amount", shopUser.getAmount());
resultMap.put("inAmount", inFlow.getAmount());
resultMap.put("inRefundAmount", inFlow.getRefundAmount());
resultMap.put("giftAmount", giftFlow == null ? BigDecimal.ZERO : giftFlow.getAmount());
resultMap.put("giftRefundAmount", giftFlow == null ? BigDecimal.ZERO : giftFlow.getRefundAmount());
return CzgResult.success(resultMap);
}
@Override
@Transactional
public CzgResult<Object> refundVip(VipRefundDTO refPayParam) {
ShopInfo shopInfo = shopInfoService.getById(refPayParam.getShopId());
if (shopInfo.getIsReturnPwd().equals(1)) {
AssertUtil.isBlank(shopInfo.getOperationPwd(), "请设置操作密码后使用");
if (!SecureUtil.md5(refPayParam.getPwd()).equals(shopInfo.getOperationPwd())) {
throw new CzgException("操作密码错误");
}
}
ShopUser shopUser = shopUserService.getShopUserInfo(refPayParam.getShopId(), refPayParam.getUserId());
ShopUserFlow inFlow = userFlowService.getById(refPayParam.getFlowId());
AssertUtil.isNull(inFlow, "充值记录不存在");
if ("cashIn".equals(inFlow.getBizCode()) || "adminIn".equals(inFlow.getBizCode())) {
refPayParam.setCashRefund(true);
}
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq(ShopUserFlow::getRelationId, refPayParam.getFlowId());
queryWrapper.eq(ShopUserFlow::getBizCode, ShopUserFlowBizEnum.AWARD_IN.getCode());
ShopUserFlow giftFlow = userFlowService.getOne(queryWrapper);
if ((inFlow.getAmount().subtract(inFlow.getRefundAmount())).compareTo(refPayParam.getRefAmount()) < 0) {
return CzgResult.failure("退款失败,退款金额不可大于可退金额");
}
//用户余额减去赠送金额 小于 退款金额 则需要勾选 超额退款
if (giftFlow != null) {
if (shopUser.getAmount().subtract(giftFlow.getAmount().subtract(giftFlow.getRefundAmount())).compareTo(refPayParam.getRefAmount()) < 0 && !refPayParam.isOutOfRange()) {
return CzgResult.failure("超额退款,请勾选 超额退款后重试");
}
} else {
if (shopUser.getAmount().compareTo(refPayParam.getRefAmount()) < 0 && !refPayParam.isOutOfRange()) {
return CzgResult.failure("超额退款,请勾选 超额退款后重试");
}
}
Long refPaymentId = null;
if (!refPayParam.isCashRefund()) {
OrderPayment payment;
if (inFlow.getRelationId() != null) {
payment = paymentService.getById(inFlow.getRelationId());
} else {
return CzgResult.failure("退款失败,该充值记录不存在");
}
String refPayOrderNo = "REFVIP" + IdUtil.getSnowflakeNextId();
refPaymentId = payService.initPayment(OrderPayment.refund(refPayParam.getShopId(), shopUser.getId(), PayTypeConstants.SourceType.MEMBER_IN,
refPayOrderNo, refPayParam.getRefAmount(), inFlow.getId(), payment.getPlatformType()));
CzgResult<RefundRespDTO> refund = payService.refund(refPayParam.getShopId(), new CzgRefundReq(refPayOrderNo, refPayParam.getRemark(),
refPayParam.getRefAmount().multiply(PayService.MONEY_RATE).longValue(), payment.getAmount().multiply(PayService.MONEY_RATE).longValue(),
payment.getOrderNo(), "", payment.getPlatformType()));
if (refund.getCode() != 200 || refund.getData() == null || !"SUCCESS".equals(refund.getData().getStatus())) {
throw new CzgException(refund.getMsg());
} else {
paymentService.updateChain()
.eq(OrderPayment::getId, refPaymentId)
.set(OrderPayment::getPayTime, refund.getData().getRefundTime())
.set(OrderPayment::getTradeNumber, refund.getData().getThirdRefundNo())
.set(OrderPayment::getPayStatus, PayTypeConstants.PayStatus.SUCCESS)
.set(OrderPayment::getRespJson, refund.getData().getOriginalData())
.update();
}
}
ShopUserMoneyEditDTO shopUserMoneyEditDTO = new ShopUserMoneyEditDTO()
.setId(shopUser.getId())
.setMoney(refPayParam.getRefAmount())
.setType(0)
.setRemark("退款")
.setBizEnum(refPayParam.isCashRefund() ? ShopUserFlowBizEnum.RECHARGE_CASH_REFUND : ShopUserFlowBizEnum.RECHARGE_REFUND)
.setRelationId(refPaymentId)
.setRechargeId(inFlow.getId());
//更新会员余额 并生成流水
shopUserService.updateMoney(shopUserMoneyEditDTO);
userFlowService.updateRefund(inFlow.getId(), refPayParam.getRefAmount());
if (giftFlow != null && (giftFlow.getAmount().subtract(giftFlow.getRefundAmount())).compareTo(BigDecimal.ZERO) > 0) {
ShopUserMoneyEditDTO giftFlowEdit = new ShopUserMoneyEditDTO()
.setId(shopUser.getId())
.setMoney(giftFlow.getAmount())
.setType(0)
.setRemark("退款")
.setBizEnum(ShopUserFlowBizEnum.RECHARGE_REFUND)
.setRelationId(refPaymentId)
.setRechargeId(giftFlow.getId());
//更新会员余额 并生成流水
shopUserService.updateMoney(giftFlowEdit);
userFlowService.updateRefund(giftFlow.getId(), giftFlow.getAmount());
}
//移除优惠券
recordService.remove(QueryWrapper.create()
.eq(MkShopCouponRecord::getSourceFlowId, inFlow.getId())
.eq(MkShopCouponRecord::getSource, "activate")
.eq(MkShopCouponRecord::getStatus, 0));
return CzgResult.success();
}
}

View File

@@ -1,125 +0,0 @@
package com.czg.service.order.utils;
import cn.hutool.core.lang.func.Func0;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author Administrator
*/
@Slf4j
@Component
public class FunUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public static int retryCount = 5;
/**
* 执行任务并保证锁唯一
* @param supplier 业务逻辑
* @param lockKey Redis锁的Key
* @return 业务逻辑返回值
*/
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
try {
// 尝试获取锁,超时时间 5 秒,防止死锁
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
int count = 0;
// 初始等待 10ms
int retryDelay = 10;
while (!lock) {
// 最多重试 10 次,大约 10 秒
if (count++ > 50) {
throw new RuntimeException("系统繁忙, 稍后再试");
}
Thread.sleep(retryDelay);
// 指数退避,最大等待 200ms
retryDelay = Math.min(retryDelay * 2, 200);
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
}
// 执行任务
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("线程被中断", e);
} catch (Exception e) {
log.error("执行出错:{}", e.getMessage(), e);
throw e;
} finally {
// 释放锁(使用 Lua 脚本确保原子性)
unlock(lockKey, lockValue);
}
}
/**
* 使用 Lua 脚本确保释放锁的原子性
* @param lockKey 锁的 Key
* @param lockValue 当前线程的锁值
*/
private void unlock(String lockKey, String lockValue) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(lockKey), lockValue);
}
public static <T, R> R runFunAndRetry(
Supplier<R> function,
Function<R, Boolean> check, Consumer<R> errFun) {
log.info("工具类开始执行函数");
R result = function.get();
boolean flag = check.apply(result);
log.info("执行结果: {}", result);
while (flag && retryCount-- > 0) {
log.info("执行函数失败, 剩余尝试次数{}", retryCount);
result = function.get();
log.info("执行结果: {}", result);
flag = check.apply(result);
}
if (flag) {
errFun.accept(result);
}
return result;
}
/**
* 防抖函数:在指定秒数内相同 Key 的任务只会执行一次
* @param key 防抖使用的 Redis Key
* @param seconds 防抖时间(秒)
* @param task 要执行的业务逻辑
* @return true 执行了任务false 在防抖期内被拦截
*/
public boolean debounce(String key, long seconds, Runnable task) {
try {
Boolean success = redisTemplate.opsForValue().setIfAbsent(
key, "1", seconds, TimeUnit.SECONDS
);
if (Boolean.TRUE.equals(success)) {
task.run();
return true;
}
return false;
} catch (Exception e) {
log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e);
return false;
}
}
}

View File

@@ -6,20 +6,23 @@
<insert id="createOrderDetails">
INSERT INTO tb_order_detail(order_id, shop_id, product_id, product_img, product_name, product_type, sku_id,
sku_name, price,member_price,discount_sale_amount,unit_price, discount_amount, pack_amount, pay_amount,
sku_name, price,member_price,discount_sale_amount,unit_price, discount_amount,is_gift, pack_amount, pay_amount,
return_amount, num, pack_number, coupon_num, is_time_discount,
return_num, refund_num, refund_no, discount_sale_note, status, place_num, is_temporary, is_print, is_wait_call,
pro_group_info, remark, refund_remark, create_time, update_time, is_urgent, sub_status, start_order_time, dish_out_time, food_serve_time, order_time)
pro_group_info, remark, refund_remark, create_time, update_time, is_urgent, sub_status, start_order_time,
dish_out_time, food_serve_time, order_time)
VALUES
<foreach collection="orderDetails" item="entity" separator=",">
(#{orderId}, #{entity.shopId}, #{entity.productId}, #{entity.productImg}, #{entity.productName},
#{entity.productType}, #{entity.skuId}, #{entity.skuName}, #{entity.price},#{entity.memberPrice},
#{entity.discountSaleAmount}, #{entity.unitPrice}, #{entity.discountAmount},
#{entity.discountSaleAmount}, #{entity.unitPrice}, #{entity.discountAmount},#{entity.isGift},
#{entity.packAmount}, #{entity.payAmount}, #{entity.returnAmount}, #{entity.num}, #{entity.packNumber},
#{entity.couponNum}, #{entity.isTimeDiscount}, #{entity.returnNum}, #{entity.refundNum}, #{entity.refundNo},
#{entity.discountSaleNote}, #{entity.status}, #{entity.placeNum}, #{entity.isTemporary}, #{entity.isPrint},
#{entity.isWaitCall}, #{entity.proGroupInfo}, #{entity.remark}, #{entity.refundRemark},
now(), now(), #{entity.isUrgent}, #{entity.subStatus},#{entity.startOrderTime},#{entity.dishOutTime},#{entity.foodServeTime}, #{entity.orderTime})
now(), now(), #{entity.isUrgent},
#{entity.subStatus},#{entity.startOrderTime},#{entity.dishOutTime},#{entity.foodServeTime},
#{entity.orderTime})
</foreach>
</insert>
<select id="getOrderDetailPrint" resultType="com.czg.order.vo.OrderDetailPrintVo">

View File

@@ -0,0 +1,26 @@
<?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.czg.service.order.mapper.ShopDirectMerchantMapper">
<select id="getEntryList" resultType="com.czg.order.entity.ShopDirectMerchant">
select merchant.*,shop.shop_name as shopName
from tb_shop_direct_merchant merchant
left join tb_shop_info shop on merchant.shop_id = shop.id
<where>
<if test="queryParam.userType != null">
and merchant.user_type = #{queryParam.userType}
</if>
<if test="queryParam.shopName != null">
and shop.shop_name like concat('%',#{queryParam.shopName},'%')
</if>
<if test="queryParam.status != null">
and (merchant.wechat_status = #{queryParam.status} or merchant.alipay_status = #{queryParam.status})
</if>
<if test="queryParam.alipayAccount != null">
and merchant.alipay_account like concat('%',#{queryParam.alipayAccount},'%')
</if>
</where>
</select>
</mapper>

View File

@@ -2,6 +2,6 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.account.mapper.ShopMerchantMapper">
<mapper namespace="com.czg.service.order.mapper.ShopMerchantMapper">
</mapper>

View File

@@ -5,8 +5,9 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.czg</groupId>
<artifactId>cash-service</artifactId>
<artifactId>cash</artifactId>
<version>1.0.0</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>pay-service</artifactId>
@@ -18,14 +19,19 @@
</properties>
<dependencies>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-All</artifactId>
<groupId>com.czg</groupId>
<artifactId>cash-common-tools</artifactId>
</dependency>
<dependency>
<groupId>com.czg</groupId>
<artifactId>czg-pay</artifactId>
<artifactId>poly-pay</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.czg</groupId>
<artifactId>aggregation-pay</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
</project>

View File

@@ -0,0 +1,45 @@
package com.czg;
import com.czg.enums.CzgPayEnum;
import com.czg.pay.*;
import com.czg.resp.CzgResult;
import jakarta.validation.constraints.NotBlank;
import lombok.NonNull;
import java.util.Map;
/**
* 支付适配器接口
*
* @author ww
*
*/
public interface PayAdapter {
/**
* 支付渠道
* {@link com.czg.constant.PayChannelCst}
*/
String getChannel();
/**
* 统一支付接口
*
* @param payType 支付类型
* @param payData 支付数据 Json类型
* 对应 聚合支付参数 {@link PolyMerchantDTO}
* 对应 原生支付参数 {@link NativeMerchantDTO}
* @param domain 域名 请求地址
* @param notifyUrl 通知地址
* @param bizData 业务数据
*/
CzgResult<Map<String, Object>> pay(@NonNull CzgPayEnum payType, @NotBlank String payData, @NotBlank String domain,
@NotBlank String notifyUrl, CzgPayBaseReq bizData);
CzgResult<RefundRespDTO> refund(@NotBlank String domain, @NotBlank String payData, CzgRefundReq bizData);
CzgResult<QueryOrderRespDTO> queryPayOrder(@NotBlank String domain, @NotBlank String payData, String payOrderId, String mchOrderNo, String platform);
CzgResult<RefundRespDTO> queryRefund(@NotBlank String domain, @NotBlank String payData, String mchRefundNo, String refundOrderId);
}

View File

@@ -0,0 +1,39 @@
package com.czg;
import com.czg.constant.PayChannelCst;
import com.czg.impl.NativePayAdapter;
import com.czg.impl.PolyPayAdapter;
import java.util.HashMap;
import java.util.Map;
/**
* 支付适配器工厂
*
* @author ww
*/
public class PayAdapterFactory {
private static final Map<String, PayAdapter> ADAPTER_MAP = new HashMap<>();
static {
ADAPTER_MAP.put(PayChannelCst.POLY, new PolyPayAdapter());
ADAPTER_MAP.put(PayChannelCst.NATIVE, new NativePayAdapter());
}
/**
* 获取支付适配器
*
* @param channel 支付渠道,仅支持 {@link PayChannelCst}
*/
public static PayAdapter getAdapter(String channel) {
PayAdapter adapter = ADAPTER_MAP.get(channel);
if (adapter == null) {
throw new IllegalStateException("支付渠道未注册适配器: " + channel);
}
return adapter;
}
}

View File

@@ -0,0 +1,84 @@
package com.czg.impl;
import com.alibaba.fastjson2.JSONObject;
import com.czg.PayAdapter;
import com.czg.PayManager;
import com.czg.constant.PayChannelCst;
import com.czg.enums.CzgPayEnum;
import com.czg.exception.CzgException;
import com.czg.pay.*;
import com.czg.resp.CzgResult;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
* 原生支付适配器
*
* @author ww
* @date 2023/10/20 14:20
*/
@Slf4j
public class NativePayAdapter implements PayAdapter {
@Override
public String getChannel() {
return PayChannelCst.NATIVE;
}
@Override
public CzgResult<Map<String, Object>> pay(@NonNull CzgPayEnum payType, String payData, String domain, String notifyUrl, CzgPayBaseReq bizData) {
try {
NativeMerchantDTO merchantDTO = getMerchantDTO(payData);
return switch (payType) {
// case H5_PAY:
// return h5Pay(merchantDTO, domain, notifyUrl, bizData);
case JS_PAY -> jsPay(merchantDTO, notifyUrl, bizData);
case LT_PAY -> jsPay(merchantDTO, notifyUrl, bizData);
// case SCAN_PAY:
// return scanPay(merchantDTO, domain, notifyUrl, bizData);
case MICRO_PAY ->
//扫码支付 扫描码
barPay(merchantDTO, notifyUrl, bizData);
default -> throw new CzgException("原生支付不支持该支付方式: " + bizData.getPayType());
};
} catch (Exception e) {
log.error("聚合支付处理失败: {}", e.getMessage(), e);
return CzgResult.failure("聚合支付处理失败: " + e.getMessage());
}
}
@Override
public CzgResult<RefundRespDTO> refund(String domain, String payData, CzgRefundReq bizData) {
NativeMerchantDTO merchantDTO = getMerchantDTO(payData);
return CzgResult.success(PayManager.refund(bizData, bizData.getNotifyUrl(), merchantDTO));
}
@Override
public CzgResult<QueryOrderRespDTO> queryPayOrder(String domain, String payData, String payOrderId, String mchOrderNo, String platform) {
NativeMerchantDTO merchantDTO = getMerchantDTO(payData);
QueryOrderRespDTO respDTO = PayManager.queryOrderStatus(platform, mchOrderNo, merchantDTO);
return CzgResult.success(respDTO);
}
@Override
public CzgResult<RefundRespDTO> queryRefund(String domain, String payData, String mchRefundNo, String refundOrderId) {
return null;
}
private CzgResult<Map<String, Object>> jsPay(NativeMerchantDTO merchantDTO, String notifyUrl, CzgPayBaseReq bizData) {
bizData.setNotifyUrl(notifyUrl);
return CzgResult.success(PayManager.jsapiPay(bizData, merchantDTO));
}
private CzgResult<Map<String, Object>> barPay(NativeMerchantDTO merchantDTO, String notifyUrl, CzgPayBaseReq bizData) {
bizData.setNotifyUrl(notifyUrl);
return CzgResult.success(PayManager.barPay(bizData, merchantDTO));
}
private NativeMerchantDTO getMerchantDTO(String payData) {
return JSONObject.parseObject(payData, NativeMerchantDTO.class);
}
}

View File

@@ -0,0 +1,125 @@
package com.czg.impl;
import com.alibaba.fastjson2.JSONObject;
import com.czg.PayAdapter;
import com.czg.PolyPayUtils;
import com.czg.constant.PayChannelCst;
import com.czg.constants.SystemConstants;
import com.czg.entity.resp.CzgBaseResp;
import com.czg.entity.resp.CzgRefundResp;
import com.czg.enums.CzgPayEnum;
import com.czg.exception.CzgException;
import com.czg.pay.*;
import com.czg.resp.CzgResult;
import com.czg.utils.AssertUtil;
import jakarta.validation.constraints.NotBlank;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
* @author ww
*/
@Slf4j
public class PolyPayAdapter implements PayAdapter {
@Override
public String getChannel() {
return PayChannelCst.POLY;
}
@Override
public CzgResult<Map<String, Object>> pay(@NonNull CzgPayEnum payType, @NotBlank String payData, @NotBlank String domain,
@NotBlank String notifyUrl, CzgPayBaseReq bizData) {
try {
PolyMerchantDTO polyMerchantDTO = JSONObject.parseObject(payData, PolyMerchantDTO.class);
return switch (payType) {
case H5_PAY -> h5Pay(polyMerchantDTO, domain, notifyUrl, bizData);
case JS_PAY -> jsPay(polyMerchantDTO, domain, notifyUrl, bizData);
case LT_PAY -> ltPay(polyMerchantDTO, domain, notifyUrl, bizData);
case SCAN_PAY -> scanPay(polyMerchantDTO, domain, notifyUrl, bizData);
case MICRO_PAY -> microPay(polyMerchantDTO, domain, notifyUrl, bizData);
default -> throw new CzgException("聚合支付不支持该支付方式: " + payType);
};
} catch (Exception e) {
log.error("聚合支付处理失败: {}", e.getMessage(), e);
return CzgResult.failure("聚合支付处理失败: " + e.getMessage());
}
}
@Override
public CzgResult<RefundRespDTO> refund(@NotBlank String domain, @NotBlank String payData, CzgRefundReq bizData) {
PolyMerchantDTO shopMerchant = JSONObject.parseObject(payData, PolyMerchantDTO.class);
CzgResult<CzgRefundResp> result = PolyPayUtils.refundOrder(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData);
return convertRefundResp(result);
}
@Override
public CzgResult<QueryOrderRespDTO> queryPayOrder(@NotBlank String payData, @NotBlank String domain, String payOrderId, String mchOrderNo, String platform) {
PolyMerchantDTO shopMerchant = JSONObject.parseObject(payData, PolyMerchantDTO.class);
CzgResult<CzgBaseResp> result = PolyPayUtils.queryPayOrder(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), payOrderId, mchOrderNo);
if (result.isSuccess()) {
QueryOrderRespDTO respDTO = new QueryOrderRespDTO()
.setStatus(result.getData().getState())
.setOrderNo(result.getData().getMchOrderNo())
.setAmount(result.getData().getAmount())
.setErrorMsg("")
.setOriginResp(JSONObject.toJSONString(result.getData()));
return CzgResult.success(respDTO);
}
return CzgResult.failure(result.getMsg());
}
@Override
public CzgResult<RefundRespDTO> queryRefund(@NotBlank String payData, @NotBlank String domain, String mchRefundNo, String refundOrderId) {
PolyMerchantDTO shopMerchant = JSONObject.parseObject(payData, PolyMerchantDTO.class);
CzgResult<CzgRefundResp> result = PolyPayUtils.queryRefundOrder(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), mchRefundNo, refundOrderId);
return convertRefundResp(result);
}
private CzgResult<Map<String, Object>> h5Pay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) {
bizData.polyBase(shopMerchant.getStoreId(), notifyUrl);
return PolyPayUtils.h5Pay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData);
}
private CzgResult<Map<String, Object>> jsPay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) {
AssertUtil.isBlank(bizData.getSubAppid(), "暂不可用,请联系商家配置" + (SystemConstants.PayType.ALIPAY.equals(bizData.getPayType()) ? "支付宝" : "微信") + "小程序");
bizData.setPayType(SystemConstants.PayType.ALIPAY.equals(bizData.getPayType()) ? "ALIPAY" : "WECHAT");
bizData.polyBase(shopMerchant.getStoreId(), notifyUrl);
return PolyPayUtils.jsPay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData);
}
private CzgResult<Map<String, Object>> ltPay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) {
AssertUtil.isBlank(bizData.getSubAppid(), "暂不可用,请联系商家配置" + ("aliPay".equals(bizData.getPayType()) ? "支付宝" : "微信") + "小程序");
bizData.polyBase(shopMerchant.getStoreId(), notifyUrl);
return PolyPayUtils.ltPay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData);
}
private CzgResult<Map<String, Object>> scanPay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) {
bizData.polyBase(shopMerchant.getStoreId(), notifyUrl);
return PolyPayUtils.scanPay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData);
}
private CzgResult<Map<String, Object>> microPay(PolyMerchantDTO shopMerchant, String domain, String notifyUrl, CzgPayBaseReq bizData) {
bizData.polyBase(shopMerchant.getStoreId(), notifyUrl);
return PolyPayUtils.microPay(domain, shopMerchant.getAppId(), shopMerchant.getAppSecret(), bizData);
}
private CzgResult<RefundRespDTO> convertRefundResp(CzgResult<CzgRefundResp> result) {
if (result.isSuccess()) {
RefundRespDTO respDTO = new RefundRespDTO()
.setStatus(result.getData().getState())
.setMerchantRefundNo(result.getData().getMchRefundNo())
.setThirdRefundNo(result.getData().getRefundOrderId())
.setPlatform(result.getData().getPayType())
.setRefundAmount(result.getData().getRefundAmt())
.setRefundTime(result.getData().getRefundTime())
.setOriginalData(result.getData().getExtParam());
return CzgResult.success(respDTO);
}
return CzgResult.failure(result.getMsg());
}
}

View File

@@ -1,87 +0,0 @@
package com.czg.service;
import com.czg.entity.notify.CzgPayNotifyDTO;
import com.czg.entity.notify.CzgRefundNotifyDTO;
import com.czg.entity.req.*;
import com.czg.entity.resp.*;
import com.czg.resp.CzgResult;
import lombok.NonNull;
/**
* @author ww
* @description
*/
public interface CzgPayService {
/**
* h5支付
*
* @param appId 应用id tb_shop_merchant 表中的 app_id
* @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
*/
CzgResult<CzgH5PayResp> h5Pay(@NonNull String appId, @NonNull String appSecret, CzgH5PayReq bizData);
/**
* js支付
*
* @param appId 应用id tb_shop_merchant 表中的 app_id
* @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
*/
CzgResult<CzgJsPayResp> jsPay(@NonNull String appId, @NonNull String appSecret, CzgJsPayReq bizData);
/**
* 小程序支付
*
* @param appId 应用id tb_shop_merchant 表中的 app_id
* @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
*/
CzgResult<CzgLtPayResp> ltPay(@NonNull String appId, @NonNull String appSecret, CzgLtPayReq bizData);
/**
* PC扫码支付
*
* @param appId 应用id tb_shop_merchant 表中的 app_id
* @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
*/
CzgResult<CzgScanPayResp> scanPay(@NonNull String appId, @NonNull String appSecret, CzgScanPayReq bizData);
/**
* 聚合反扫
*
* @param appId 应用id tb_shop_merchant 表中的 app_id
* @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
*/
CzgResult<CzgMicroPayResp> microPay(@NonNull String appId, @NonNull String appSecret, CzgMicroPayReq bizData);
/**
* 订单查询
*
* @param appId 应用id tb_shop_merchant 表中的 app_id
* @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
* @param payOrderId 平台订单号
* @param mchOrderNo 商户订单号
*/
CzgResult<CzgBaseResp> queryPayOrder(@NonNull String appId, @NonNull String appSecret,
String payOrderId, String mchOrderNo);
/**
* 订单退款
*
* @param appId 应用id tb_shop_merchant 表中的 app_id
* @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
*/
CzgResult<CzgRefundResp> refundOrder(@NonNull String appId, @NonNull String appSecret, CzgRefundReq bizData);
/**
* 退款订单查询
*
* @param appId 应用id tb_shop_merchant 表中的 app_id
* @param appSecret 应用密钥 tb_shop_merchant 表中的 app_secret
* @param mchRefundNo 商户退款订单号 二选一
* @param refundOrderId 平台退款订单号 二选一
*/
CzgResult<CzgRefundResp> queryRefundOrder(@NonNull String appId, @NonNull String appSecret,
String mchRefundNo, String refundOrderId);
}

View File

@@ -1,64 +0,0 @@
package com.czg.service.Impl;
import com.czg.CzgPayUtils;
import com.czg.constants.ParamCodeCst;
import com.czg.entity.req.*;
import com.czg.entity.resp.*;
import com.czg.resp.CzgResult;
import com.czg.service.CzgPayService;
import com.czg.system.service.SysParamsService;
import lombok.NonNull;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
/**
* @author ww
* @description 1
*/
@Service
public class CzgPayServiceImpl implements CzgPayService {
@DubboReference
private SysParamsService sysParamsService;
@Override
public CzgResult<CzgH5PayResp> h5Pay(@NonNull String appId, @NonNull String appSecret, CzgH5PayReq bizData) {
return CzgPayUtils.h5Pay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData);
}
@Override
public CzgResult<CzgJsPayResp> jsPay(@NonNull String appId, @NonNull String appSecret, CzgJsPayReq bizData) {
return CzgPayUtils.jsPay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData);
}
@Override
public CzgResult<CzgLtPayResp> ltPay(@NonNull String appId, @NonNull String appSecret, CzgLtPayReq bizData) {
return CzgPayUtils.ltPay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData);
}
@Override
public CzgResult<CzgScanPayResp> scanPay(@NonNull String appId, @NonNull String appSecret, CzgScanPayReq bizData) {
return CzgPayUtils.scanPay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData);
}
@Override
public CzgResult<CzgMicroPayResp> microPay(@NonNull String appId, @NonNull String appSecret, CzgMicroPayReq bizData) {
return CzgPayUtils.microPay(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData);
}
@Override
public CzgResult<CzgBaseResp> queryPayOrder(@NonNull String appId, @NonNull String appSecret, String payOrderId, String mchOrderNo) {
return CzgPayUtils.queryPayOrder(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, payOrderId, mchOrderNo);
}
@Override
public CzgResult<CzgRefundResp> refundOrder(@NonNull String appId, @NonNull String appSecret, CzgRefundReq bizData) {
return CzgPayUtils.refundOrder(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, bizData);
}
@Override
public CzgResult<CzgRefundResp> queryRefundOrder(@NonNull String appId, @NonNull String appSecret, String mchRefundNo, String refundOrderId) {
return CzgPayUtils.queryRefundOrder(sysParamsService.getSysParamValue(ParamCodeCst.System.PAY_CZG_DOMAIN), appId, appSecret, mchRefundNo, refundOrderId);
}
}

View File

@@ -18,7 +18,6 @@
<module>product-service</module>
<module>system-service</module>
<module>code-generator</module>
<module>pay-service</module>
<module>market-service</module>
</modules>

View File

@@ -8,7 +8,6 @@ import cn.hutool.core.util.StrUtil;
import com.czg.product.entity.Product;
import com.czg.product.entity.ProductStockFlow;
import com.czg.product.service.ProductStockFlowService;
import com.czg.sa.StpKit;
import com.czg.service.product.mapper.ConsInfoMapper;
import com.czg.service.product.mapper.ProductMapper;
import com.czg.service.product.mapper.ProductStockFlowMapper;
@@ -44,15 +43,7 @@ public class ProductStockFlowServiceImpl extends ServiceImpl<ProductStockFlowMap
Long shopId = entity.getShopId();
BigDecimal afterNumber = entity.getAfterNumber();
Product product = productMapper.selectOneById(entity.getProductId());
String shopName = "";
try {
shopName = StpKit.USER.getShopName();
} catch (Exception e) {
log.error("获取店铺名称失败");
}
if (StrUtil.isEmpty(shopName)) {
shopName = productMapper.getShopName(shopId);
}
String shopName = productMapper.getShopName(shopId);
BigDecimal warnLine = Convert.toBigDecimal(product.getWarnLine());
// 库存小于警告值,发送消息提醒
if (NumberUtil.isLess(afterNumber, warnLine)) {

View File

@@ -153,6 +153,10 @@
<foreach item="item" collection="idList" separator="," open="(" close=")">
#{item}
</foreach>
or t1.sync_id in
<foreach item="item" collection="idList" separator="," open="(" close=")">
#{item}
</foreach>
</if>
order by t1.sort desc,t1.id desc
</select>

View File

@@ -0,0 +1,14 @@
package com.czg.service.system.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.system.entity.SysBankInfo;
/**
* 银行账户信息表 映射层。
*
* @author ww
* @since 2026-01-06
*/
public interface SysBankInfoMapper extends BaseMapper<SysBankInfo> {
}

View File

@@ -0,0 +1,14 @@
package com.czg.service.system.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.system.entity.SysCategoryInfo;
/**
* 类目信息表 映射层。
*
* @author ww
* @since 2026-01-06
*/
public interface SysCategoryInfoMapper extends BaseMapper<SysCategoryInfo> {
}

View File

@@ -0,0 +1,14 @@
package com.czg.service.system.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.system.entity.SysRegion;
/**
* 行政区表 映射层。
*
* @author ww
* @since 2026-01-06
*/
public interface SysRegionMapper extends BaseMapper<SysRegion> {
}

View File

@@ -0,0 +1,26 @@
package com.czg.service.system.service.impl;
import com.czg.BaseQueryParam;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.system.entity.SysBankInfo;
import com.czg.system.service.SysBankInfoService;
import com.czg.service.system.mapper.SysBankInfoMapper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 银行账户信息表 服务层实现。
*
* @author ww
* @since 2026-01-06
*/
@Service
public class SysBankInfoServiceImpl extends ServiceImpl<SysBankInfoMapper, SysBankInfo> implements SysBankInfoService {
@Override
public Page<SysBankInfo> bankInfoList(BaseQueryParam param, String bankName) {
return page(Page.of(param.getPage(), param.getSize()), query().like(SysBankInfo::getBankAlias, bankName));
}
}

View File

@@ -0,0 +1,43 @@
package com.czg.service.system.service.impl;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import com.czg.system.vo.SysCategoryInfoVO;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.system.entity.SysCategoryInfo;
import com.czg.system.service.SysCategoryInfoService;
import com.czg.service.system.mapper.SysCategoryInfoMapper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 类目信息表 服务层实现。
*
* @author ww
* @since 2026-01-06
*/
@Service
public class SysCategoryInfoServiceImpl extends ServiceImpl<SysCategoryInfoMapper, SysCategoryInfo> implements SysCategoryInfoService {
@Cacheable(value = "common:category", key = "'all'")
@Override
public List<SysCategoryInfoVO> categoryList() {
List<SysCategoryInfo> list = list();
if (CollUtil.isEmpty(list)) {
return List.of();
}
List<SysCategoryInfoVO> result = new ArrayList<>();
Map<String, List<SysCategoryInfo>> stringListMap = CollStreamUtil.groupByKey(list, SysCategoryInfo::getFirstCategory);
stringListMap.forEach((k, v) -> {
SysCategoryInfoVO vo = new SysCategoryInfoVO();
vo.setFirstCategory(k);
vo.setChild(v);
result.add(vo);
});
return result;
}
}

View File

@@ -0,0 +1,56 @@
package com.czg.service.system.service.impl;
import com.czg.service.system.mapper.SysRegionMapper;
import com.czg.system.entity.SysRegion;
import com.czg.system.service.SysRegionService;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 行政区表 服务层实现。
*
* @author ww
* @since 2026-01-06
*/
@Service
public class SysRegionServiceImpl extends ServiceImpl<SysRegionMapper, SysRegion> implements SysRegionService {
@Cacheable(value = "common:region", key = "'all'")
@Override
public List<SysRegion> regionList() {
// 1. 单次查询获取所有数据
List<SysRegion> allRegions = list(query().ne(SysRegion::getRegionLevel, 1));
// 2. 一次性按层级分组,减少流遍历次数
Map<Integer, List<SysRegion>> regionByLevel = allRegions.stream()
.collect(Collectors.groupingBy(SysRegion::getRegionLevel));
// 3. 获取各层级数据,默认空列表避免空指针
List<SysRegion> parents = regionByLevel.getOrDefault(2, List.of());
List<SysRegion> level3Regions = regionByLevel.getOrDefault(3, List.of());
List<SysRegion> level4Regions = regionByLevel.getOrDefault(4, List.of());
// 4. 构建3级地区的子节点映射4级使用HashMap保证性能
Map<String, List<SysRegion>> level4ByParentId = level4Regions.stream()
.collect(Collectors.groupingBy(SysRegion::getParentRegionId, Collectors.toList()));
level3Regions.forEach(level3 -> {
List<SysRegion> children = level4ByParentId.getOrDefault(level3.getRegionId(), List.of());
level3.setChildren(children);
});
Map<String, List<SysRegion>> level3ByParentId = level3Regions.stream()
.collect(Collectors.groupingBy(SysRegion::getParentRegionId));
parents.forEach(parent -> {
List<SysRegion> children = level3ByParentId.getOrDefault(parent.getRegionId(), List.of());
parent.setChildren(children);
});
return parents;
}
}

View File

@@ -0,0 +1,7 @@
<?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.czg.service.system.mapper.SysBankInfoMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?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.czg.service.system.mapper.SysCategoryInfoMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
<?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.czg.service.system.mapper.SysRegionMapper">
</mapper>