list = installTaskService.selectInstallTaskList(installTask);
+ return getDataTable(list);
+ }
+
+ /**
+ * 根据ID查询详情
+ */
+ @GetMapping("/{id}")
+ public AjaxResult getInfo(@PathVariable Long id) {
+ return AjaxResult.success(installTaskService.selectInstallTaskById(id));
+ }
+
+ /**
+ * 修改安装任务
+ */
+ @PreAuthorize("@ss.hasPermi('mall:install:edit')")
+ @PutMapping("/edit")
+ public AjaxResult edit(@RequestBody InstallTask installTask) {
+ installTask.setUpdateBy(SecurityUtils.getUsername());
+ return toAjax(installTaskService.updateInstallTask(installTask));
+ }
+
+ /**
+ * 指派任务
+ */
+ @PreAuthorize("@ss.hasPermi('mall:install:assign')")
+ @PostMapping("/assign")
+ public AjaxResult assign(@RequestParam Long id,
+ @RequestParam Long assigneeId,
+ @RequestParam String assigneeName) {
+ return toAjax(installTaskService.assignTask(id, assigneeId, assigneeName,
+ SecurityUtils.getUsername()));
+ }
+
+ /**
+ * 完成任务安装
+ */
+ @PreAuthorize("@ss.hasPermi('mall:install:complete')")
+ @PostMapping("/complete")
+ public AjaxResult complete(@RequestBody InstallTask installTask) {
+ installTask.setUpdateBy(SecurityUtils.getUsername());
+ return toAjax(installTaskService.completeTask(installTask));
+ }
+
+ /**
+ * 删除安装任务
+ */
+ @PreAuthorize("@ss.hasPermi('mall:install:remove')")
+ @DeleteMapping("/{id}")
+ public AjaxResult remove(@PathVariable Long id) {
+ return toAjax(installTaskService.deleteInstallTaskById(id));
+ }
+
+ /**
+ * 批量删除
+ */
+ @PreAuthorize("@ss.hasPermi('mall:install:remove')")
+ @DeleteMapping("/batch/{ids}")
+ public AjaxResult batchRemove(@PathVariable Long[] ids) {
+ return toAjax(installTaskService.deleteInstallTaskByIds(ids));
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/OrderController.java b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/OrderController.java
index 3ba366cd..ed19f494 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/OrderController.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/OrderController.java
@@ -5,18 +5,26 @@ import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.core.page.TableDataInfo;
+
+import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.domain.Order;
+import com.ruoyi.web.dto.CreateOrderDTO;
+import com.ruoyi.web.dto.ScanPayRequest;
+import com.ruoyi.web.dto.TianquePayCallbackDTO;
+import com.ruoyi.web.dto.TradeQueryDTO;
import com.ruoyi.web.service.OrderService;
-import org.checkerframework.checker.units.qual.A;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
/**
- * 商品订单管理
+ * 商品订单支付管理
*/
@RestController
@RequestMapping("/mall/order")
@@ -25,14 +33,124 @@ public class OrderController extends BaseController {
@Resource
private OrderService orderService;
+ private static final Logger log = LoggerFactory.getLogger(OrderController.class);
+
+
+ /**
+ * 获取指定门店的支付订单数量,和支付金额总额
+ *
+ * @return
+ */
+ @Anonymous
+ @GetMapping(value = "/getOrderCount/{storeId}")
+ public AjaxResult getOrderCount(@PathVariable String storeId) {
+ return AjaxResult.success(orderService.getOrderCount(storeId));
+ }
+
+
+
+ /**
+ * 支付结果主动查询,前端使用去查询支付状态
+ */
+ @Anonymous
+ @PostMapping("/tradeQuery")
+ public AjaxResult tradeQuery(@RequestBody TradeQueryDTO tradeQueryDTO) throws Exception {
+ return orderService.tradeQuery(tradeQueryDTO);
+ }
+
+
+
+ /**
+ * 被扫 创建订单,返回订单号
+ */
+ @Anonymous
+ @PostMapping("/addOrder")
+ public AjaxResult addOrder(@Validated @RequestBody CreateOrderDTO createOrderDTO) {
+ createOrderDTO.setCreateBy(getUsername());
+ return orderService.addOrder(createOrderDTO);
+ }
+
+
+ /**
+ * 被扫 发起支付请求
+ */
+ @Anonymous
+ @PostMapping("/addOrderReverseScan")
+ public AjaxResult addOrderReverseScan(@RequestBody ScanPayRequest scanPayRequest) throws Exception {
+ scanPayRequest.setCreateBy(getUsername());
+ return AjaxResult.success(orderService.addOrderReverseScan(scanPayRequest));
+ }
+
/**
* 查询订单列表
*/
// @PreAuthorize("@ss.hasPermi('mall:order:list')")
@Anonymous
- @GetMapping("/list")
- public AjaxResult list(Order order) {
- return AjaxResult.success( orderService.selectOrderList(order));
+ @PostMapping("/list")
+ public AjaxResult list(@RequestBody Order order) {
+ return AjaxResult.success(orderService.selectOrderList(order));
}
+ /**
+ * 创建订单并支付
+ * 用户点击支付时调用
+ */
+ @Anonymous
+ @PostMapping("/addPay")
+ public AjaxResult addPay(@Validated @RequestBody CreateOrderDTO createOrderDTO) {
+ createOrderDTO.setCreateBy(getUsername());
+ return orderService.addPay(createOrderDTO);
+ }
+
+ /**
+ * 天阙平台支付回调接口
+ * 支付结果异步通知
+ *
+ * 接口说明:
+ * 交易成功后,天阙平台会将成功状态通知到交易上送的回调地址(notifyUrl)
+ * 合作方接收到通知结果后需要返回:{"code":"success", "msg":"成功"}
+ * 天阙平台收到正确应答后将不再进行通知,否则将继续推送10次
+ * * 失败返回:
+ * {
+ * "code": "fail",
+ * "msg": "错误信息"
+ * }
+ * 注:失败的交易不推送回调,交易成功才会回调
+ */
+ @PostMapping("/notify")
+ @Anonymous
+ public Map notify(@RequestBody String callbackData) {
+ log.info("收到天阙平台支付回调, data:{}", callbackData);
+ Map response = new HashMap<>();
+ try {
+ //解析回调数据
+ TianquePayCallbackDTO request = orderService.parseCallback(callbackData);
+ //检查业务编码(0000表示成功)
+ if (!request.isSuccess()) {
+ log.info("交易失败回调, ordNo:{}, bizCode:{}, bizMsg:{}",
+ request.getOrdNo(), request.getBizCode(), request.getBizMsg());
+ response.put("code", "fail");
+ response.put("msg", "交易失败");
+ return response;
+ }
+ //处理支付回调业务逻辑
+ orderService.handleTianquePayCallback(request);
+
+ //天阙平台要求的格式
+ response.put("code", "success");
+ response.put("msg", "成功");
+
+ log.info("天阙平台支付回调处理成功, ordNo:{}, uuid:{}",
+ request.getOrdNo(), request.getUuid());
+ } catch (ServiceException e) {
+ log.error("天阙平台支付回调业务处理失败: {}", e.getMessage());
+ response.put("code", "fail");
+ response.put("msg", e.getMessage());
+ } catch (Exception e) {
+ log.error("天阙平台支付回调处理异常", e);
+ response.put("code", "fail");
+ response.put("msg", "系统异常");
+ }
+ return response;
+ }
}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/ProductStoreController.java b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/ProductStoreController.java
index 05d571fc..33fedbd0 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/controller/ProductStoreController.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/controller/ProductStoreController.java
@@ -13,6 +13,7 @@ import com.ruoyi.common.utils.file.FileUploadUtils;
import com.ruoyi.common.utils.file.MimeTypeUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.web.domain.Product;
+import com.ruoyi.web.dto.BatchBarcodeQueryDTO;
import com.ruoyi.web.dto.BatchStoreDTO;
import com.ruoyi.web.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +34,22 @@ public class ProductStoreController extends BaseController {
@Autowired
private ProductService productService;
+ /**
+ * 批量根据条码查询商品
+ * 支持一次查询多个条码,返回商品列表
+ */
+ @PostMapping("/getByBarcodes")
+ public AjaxResult getByBarcodes(@RequestBody BatchBarcodeQueryDTO dto) {
+ if (dto.getBarcodes() == null || dto.getBarcodes().isEmpty()) {
+ return AjaxResult.error("条码列表不能为空");
+ }
+ if (dto.getStoreId() == null) {
+ return AjaxResult.error("门店id不能为空");
+ }
+ return AjaxResult.success(productService.getByBarcodes(dto));
+ }
+
+
/**
* 获取商品列表
*/
@@ -77,7 +94,6 @@ public class ProductStoreController extends BaseController {
}
//todo 这里可以国码有的时候使用国码的图片路径
-
// TODO: 2026/1/14 这个file 为空的时候 就使用国码里面的图片,不为空代表用户自己选择了图片上传 用自己服务器上的
//国码后续添加
if (file != null) {
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Classification.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Classification.java
index e5f91399..119a6fe0 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Classification.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Classification.java
@@ -5,6 +5,9 @@ import lombok.Data;
import java.util.List;
+/**
+ * 商品分类
+ */
@Data
public class Classification extends BaseEntity {
private static final long serialVersionUID = 1L;
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Device.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Device.java
new file mode 100644
index 00000000..1c5294c0
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Device.java
@@ -0,0 +1,65 @@
+package com.ruoyi.web.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import java.util.Date;
+
+/**
+ * 设备信息表 mall_device_info
+ *
+ */
+@Data
+public class Device extends BaseEntity {
+
+ /** 设备ID */
+ private Long id;
+
+ /** 所属门店ID,关联mall_store表的store_id */
+ private Long storeId;
+
+ /** 设备编号(厂商唯一标识) */
+ private String deviceCode;
+
+ /** 设备名称 */
+ private String deviceName;
+
+ /** 设备类型:1-摄像头 2-扫码枪 3-打印机 4-收银机 */
+ private Integer deviceType;
+
+ /** 设备品牌 */
+ private String deviceBrand;
+
+ /** 设备型号 */
+ private String deviceModel;
+
+ /** 通道 */
+ private String channel;
+
+ /** MAC地址 */
+ private String macAddress;
+
+ /** 设备授权码/密钥(前端调设备用) */
+ private String authCode;
+
+ /** 安装日期 */
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date installDate;
+
+ /** 有效期 */
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date expireDate;
+
+ /** 状态:0-离线 1-在线 2-故障 3-已报废 */
+ private Integer deviceStatus;
+
+ /** 最后心跳时间 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date lastHeartbeat;
+
+ /** 备注 */
+ private String remark;
+
+
+
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/DoorScanRecord.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/DoorScanRecord.java
new file mode 100644
index 00000000..35528646
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/DoorScanRecord.java
@@ -0,0 +1,30 @@
+package com.ruoyi.web.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 扫码记录
+ */
+@Data
+public class DoorScanRecord extends BaseEntity {
+
+ private Long id;
+
+ private String operatorName; // 扫码人姓名
+
+ private String operatorPhone; // 扫码人手机号
+
+ private Integer storeId; // 门店ID
+
+ private String shopName; // 门店名称
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date scanTime; // 扫码时间
+
+ private String scanType; //'扫码类型 0:进门 1:出门'
+
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/InstallTask.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/InstallTask.java
new file mode 100644
index 00000000..722852b5
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/InstallTask.java
@@ -0,0 +1,107 @@
+package com.ruoyi.web.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import java.util.Date;
+
+/**
+ * 安装任务表 mall_install_task
+ *
+ * @author ruoyi
+ */
+@Data
+public class InstallTask extends BaseEntity {
+
+ /** 任务ID */
+ private Long id;
+
+ /** 任务编号 */
+ private String taskNo;
+
+ /** 申请门店ID */
+ private Long storeId;
+
+ /** 门店名称 */
+ private String storeName;
+
+ /** 联系人 */
+ private String contactPerson;
+
+ /** 联系电话 */
+ private String contactPhone;
+
+ /** 设备类型:1-摄像头 2-扫码枪 3-打印机 4-收银机 */
+ private Integer deviceType;
+
+ /** 期望设备型号 */
+ private String deviceModel;
+
+ /** 安装地址 */
+ private String installAddress;
+
+ /** 期望安装时间 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date expectedTime;
+
+ /** 备注/特殊要求 */
+ private String remark;
+
+ /** 状态:0-待处理 1-已接单 2-安装中 3-已完成 4-已取消 */
+ private Integer taskStatus;
+
+ /** 指派人ID */
+ private Long assigneeId;
+
+ /** 指派人姓名 */
+ private String assigneeName;
+
+ /** 指派时间 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date assignTime;
+
+ /** 安装的设备ID */
+ private Long deviceId;
+
+ /** 设备编号 */
+ private String deviceCode;
+
+ /** 设备授权码 */
+ private String authCode;
+
+ /** 实际安装时间 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date installTime;
+
+ /** 安装照片URL */
+ private String installPhoto;
+
+ /** 完成备注 */
+ private String completeRemark;
+
+
+ /** 设备类型描述 */
+ public String getDeviceTypeDesc() {
+ if (deviceType == null) return "";
+ switch (deviceType) {
+ case 1: return "摄像头";
+ case 2: return "扫码枪";
+ case 3: return "打印机";
+ case 4: return "收银机";
+ default: return "未知";
+ }
+ }
+
+ /** 任务状态描述 */
+ public String getTaskStatusDesc() {
+ if (taskStatus == null) return "";
+ switch (taskStatus) {
+ case 0: return "待处理";
+ case 1: return "已接单";
+ case 2: return "安装中";
+ case 3: return "已完成";
+ case 4: return "已取消";
+ default: return "未知";
+ }
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Order.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Order.java
index ebbdef7c..a21e49cb 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Order.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Order.java
@@ -33,7 +33,7 @@ public class Order extends BaseEntity {
/**
* 支付金额
*/
- private BigDecimal payAmount;
+ private String payAmount;
/**
* 支付状态: 0-未支付,1-支付中,2-已支付,3-支付失败,4-已退款
@@ -46,6 +46,11 @@ public class Order extends BaseEntity {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date payTime;
+ /**
+ * 商品条码
+ */
+ private String productBarCode;
+
/**
* 订单商品明细列表
*/
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/OrderItem.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/OrderItem.java
index cce9e2a3..db767d91 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/OrderItem.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/OrderItem.java
@@ -5,6 +5,8 @@ import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@@ -25,9 +27,16 @@ public class OrderItem extends BaseEntity {
*/
private Long orderId;
+
+ /**
+ * 商品条码
+ */
+ private String productBarCode;
+
/**
* 商品ID
*/
+ @NotNull(message = "商品ID不能为空")
private Long productId;
/**
@@ -58,6 +67,8 @@ public class OrderItem extends BaseEntity {
/**
* 购买数量
*/
+ @NotNull(message = "数量不能为空")
+ @Min(value = 1, message = "数量必须大于0")
private Integer quantity;
/**
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Product.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Product.java
index bed02ce2..8d6188bc 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Product.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/Product.java
@@ -85,16 +85,16 @@ public class Product extends BaseEntity {
/**
* 门店销售价
*/
- private BigDecimal storePrice;
+ private String storePrice;
/**
* 成本价
*/
- private BigDecimal costPrice;
+ private String costPrice;
/**
* 市场价
*/
- private Long originalPrice;
+ private String originalPrice;
/**
* 门店ID
@@ -117,4 +117,15 @@ public class Product extends BaseEntity {
*/
private String status;
+ /**
+ * 是否查询临期商品
+ */
+ private Boolean isExpiring;
+
+ /**
+ * 是否查询过期商品
+ */
+ private Boolean isExpired;
+
+
}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/domain/TianquePayResponse.java b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/TianquePayResponse.java
new file mode 100644
index 00000000..268eaaa1
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/domain/TianquePayResponse.java
@@ -0,0 +1,79 @@
+package com.ruoyi.web.domain;
+
+import lombok.Data;
+
+/**
+ * 天阙支付响应
+ */
+@Data
+public class TianquePayResponse {
+
+ /** 返回码:0000-成功 */
+ private String code;
+
+ /** 返回信息 */
+ private String msg;
+
+ /** 签名 */
+ private String sign;
+
+ /** 签名类型 */
+ private String signType;
+
+ /** 组织机构ID */
+ private String orgId;
+
+ /** 请求ID */
+ private String reqId;
+
+ /** 响应数据 */
+ private RespData respData;
+
+ @Data
+ public static class RespData {
+ /** 业务码:0000-成功 */
+ private String bizCode;
+
+ /** 业务信息 */
+ private String bizMsg;
+
+ /** 落单号 */
+ private String sxfUuid;
+
+ /** 商户订单号 */
+ private String ordNo;
+
+ /** 天阙平台订单号 */
+ private String uuid;
+
+ /** 预支付ID */
+ private String prepayId;
+
+ /** 支付AppID */
+ private String payAppId;
+
+ /** 时间戳 */
+ private String payTimeStamp;
+
+ /** 随机字符串 */
+ private String paynonceStr;
+
+ /** 支付包 */
+ private String payPackage;
+
+ /** 签名类型 */
+ private String paySignType;
+
+ /** 支付签名 */
+ private String paySign;
+
+ /** 商户号 */
+ private String partnerId;
+
+ /** 跳转地址 */
+ private String redirectUrl;
+
+ /** 来源 */
+ private String source;
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ApiRequestBean.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ApiRequestBean.java
new file mode 100644
index 00000000..48691512
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ApiRequestBean.java
@@ -0,0 +1,126 @@
+package com.ruoyi.web.dto;
+
+import java.io.Serializable;
+
+
+public class ApiRequestBean implements Serializable{
+
+ private static final long serialVersionUID = -9073910802005694017L;
+
+ /**
+ * 合作机构id
+ */
+ private String orgId;
+
+ /**
+ * 请求id
+ */
+ private String reqId;
+
+ /**
+ * 请求接口版本1.0
+ */
+ private String version;
+
+ /**
+ * 代理商身份标识
+ */
+ private String agentId;
+
+ /**
+ * 业务员编号
+ */
+ private String salesCode;
+
+ /**
+ * 业务数据
+ */
+ private T reqData;
+
+ /**
+ * 签名
+ */
+ private String sign;
+
+ /**
+ * 签名类型
+ */
+ private String signType;
+
+ /**
+ * 请求时间
+ */
+ private String timestamp;
+
+ public String getOrgId() {
+ return orgId;
+ }
+
+ public void setOrgId(String orgId) {
+ this.orgId = orgId;
+ }
+
+ public String getReqId() {
+ return reqId;
+ }
+
+ public void setReqId(String reqId) {
+ this.reqId = reqId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getAgentId() {
+ return agentId;
+ }
+
+ public void setAgentId(String agentId) {
+ this.agentId = agentId;
+ }
+
+ public String getSalesCode() {
+ return salesCode;
+ }
+
+ public void setSalesCode(String salesCode) {
+ this.salesCode = salesCode;
+ }
+
+ public T getReqData() {
+ return reqData;
+ }
+
+ public void setReqData(T reqData) {
+ this.reqData = reqData;
+ }
+
+ public String getSign() {
+ return sign;
+ }
+
+ public void setSign(String sign) {
+ this.sign = sign;
+ }
+
+ public String getSignType() {
+ return signType;
+ }
+
+ public void setSignType(String signType) {
+ this.signType = signType;
+ }
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(String timestamp) {
+ this.timestamp = timestamp;
+ }
+}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/BatchBarcodeQueryDTO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/BatchBarcodeQueryDTO.java
new file mode 100644
index 00000000..d5d304b2
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/BatchBarcodeQueryDTO.java
@@ -0,0 +1,15 @@
+package com.ruoyi.web.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class BatchBarcodeQueryDTO {
+
+ //商品条码
+ private List barcodes;
+
+ // 可选:门店ID,用于过滤该门店的商品
+ private Long storeId;
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/CreateOrderDTO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/CreateOrderDTO.java
new file mode 100644
index 00000000..85e893b7
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/CreateOrderDTO.java
@@ -0,0 +1,31 @@
+package com.ruoyi.web.dto;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import com.ruoyi.web.domain.OrderItem;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+public class CreateOrderDTO extends BaseEntity {
+
+ /** 门店ID */
+ @NotNull(message = "门店ID不能为空")
+ private Integer storeId;
+
+ /** 商品列表 */
+ @NotEmpty(message = "商品列表不能为空")
+ private List items;
+
+ //总金额
+ @NotNull(message = "订单商品总金额不能为空")
+ private String totalAmount;
+
+ /** 用户openId */
+ private String openId;
+
+ /** 备注 */
+ private String remark;
+}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ScanPayRequest.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ScanPayRequest.java
new file mode 100644
index 00000000..fb445995
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/ScanPayRequest.java
@@ -0,0 +1,238 @@
+package com.ruoyi.web.dto;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+import lombok.Data;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.math.BigDecimal;
+import java.util.List;
+
+@Data
+public class ScanPayRequest extends BaseEntity {
+
+ /**
+ * 商户编号,必填,15位
+ */
+ private String mno;
+
+ /**
+ * 商户订单号,必填,64位,字母/数字/下划线,需保证在合作方系统中不重复
+ */
+ private String ordNo;
+
+ /**
+ * 订单总金额(元),必填,格式:#######.##
+ */
+ private BigDecimal amt;
+
+ /**
+ * 授权码(付款码),必填,64位
+ * 通过扫码枪/声波获取设备获取的支付宝/微信/银联付款码
+ */
+ private String authCode;
+
+ /**
+ * 支付渠道,当scene枚举为3或6时必填
+ * WECHAT:微信, ALIPAY:支付宝, UNIONPAY:银联, DCEP:数字人民币, YZF:翼支付
+ */
+ private String payType;
+
+ /**
+ * 支付场景,选填,默认1
+ * 1:付款码, 3:刷脸, 6:离线刷脸
+ */
+ private String scene = "1";
+
+ /**
+ * 订单标题,必填,256位
+ */
+ private String subject;
+
+ /**
+ * 商户交易终端ip地址,必填,16位
+ */
+ private String trmIp;
+
+ /**
+ * 微信subAppId,选填,32位
+ */
+ private String subAppid;
+
+ /**
+ * 微信优惠参数,选填,32位
+ * 微信的商品标记,核销优惠券时必传
+ */
+ private String wxGoodsTag;
+
+ /**
+ * 订单优惠标识,选填,2位
+ * 00:是, 01:否,有优惠参数上传时必传00
+ */
+ private String goodsTag;
+
+ /**
+ * 微信电子发票,选填,2位
+ * 00:是, 01:否
+ */
+ private String needReceipt;
+
+ /**
+ * 支付宝参与优惠金额(元),选填
+ */
+ private BigDecimal discountAmt;
+
+ /**
+ * 支付宝不参与优惠金额(元),选填
+ */
+ private BigDecimal unDiscountAmt;
+
+ /**
+ * 支付宝花呗分期数,选填
+ * 3:分3期, 6:分6期, 12:分12期
+ */
+ private String hbFqNum;
+
+ /**
+ * 商家承担手续费比例,选填
+ * 0:用户承担, 100:商家承担
+ */
+ private String hbFqPercent;
+
+ /**
+ * 限制卡类型,选填,默认00
+ * 00:全部支持, 01:限定不能使用信用卡等
+ */
+ private String limitPay = "00";
+
+ /**
+ * 订单失效时间(单位分钟),选填,1-1440,默认5分钟
+ */
+ private Integer timeExpire = 5;
+
+
+
+ /**
+ * 单次分账类型,选填,默认01
+ * 00:单次分账, 01:不分账, 04:多次分账
+ */
+ private String ledgerAccountFlag = "01";
+
+ /**
+ * 分账有效时间(单位天),选填
+ * ledgerAccountFlag为00或04时必传
+ */
+ private String ledgerAccountEffectTime;
+
+ /**
+ * 同步分账规则,选填,与ledgerAccountFlag互斥
+ */
+ private List fusruleId;
+
+ /**
+ * 支付结果通知地址,选填,256位
+ * 被扫输密时上送,交易成功会异步通知
+ */
+ private String notifyUrl;
+
+ /**
+ * 终端号,选填,32位
+ * 银联:8位,支付宝微信:不超过32位
+ */
+ private String ylTrmNo;
+
+ /**
+ * 证件类型,选填
+ * identityFlag=="00"时必传,IDCARD:大陆
+ */
+ private String buyerIdType;
+
+ /**
+ * 证件号,选填,18位
+ * identityFlag=="00"时必传
+ */
+ private String buyerIdNo;
+
+ /**
+ * 买家姓名,选填,32位
+ * identityFlag=="00"时必传
+ */
+ private String buyerName;
+
+ /**
+ * 手机号,选填,11位
+ * identityFlag=="00"时必传
+ */
+ private String mobileNum;
+
+ /**
+ * 门店编号,选填,32位,对应支付宝store_id
+ */
+ private String storeId;
+
+ /**
+ * 蚂蚁门店编号,选填
+ */
+ private String alipayStoreId;
+
+ /**
+ * 天阙终端编号,选填,128位
+ * 天阙后台绑定终端后生成的终端唯一标识
+ */
+ private String deviceNo;
+
+ /**
+ * 天阙门店编号,选填,32位
+ */
+ private String storeNum;
+
+ /**
+ * 外部业务号,选填
+ */
+ private String senceNo;
+
+ /**
+ * 加密随机因子,选填,10位
+ */
+ private String encryptRandNum;
+
+ /**
+ * 密文数据,选填,16位
+ */
+ private String secretText;
+
+ /**
+ * 终端经度,选填,16位
+ * +表示东经,-表示西经
+ */
+ private String longitude;
+
+ /**
+ * 终端纬度,选填,16位
+ * +表示北纬,-表示南纬
+ */
+ private String latitude;
+
+
+
+ /**
+ * 支付宝校园ID,选填,20位,支付宝未来校园活动必传
+ */
+ private String eduSchoolId;
+
+ /**
+ * 支付宝校园场景,选填,30位,支付宝未来校园活动必传
+ */
+ private String eduScene;
+
+ /**
+ * 支付宝业务拓展字段,选填
+ * 对应支付宝business_params字段
+ */
+ private String zfbBusinessParams;
+
+ /**
+ * 扩展字段,选填,128位
+ */
+ private String extend;
+
+
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TianquePayCallbackDTO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TianquePayCallbackDTO.java
new file mode 100644
index 00000000..c1aade20
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TianquePayCallbackDTO.java
@@ -0,0 +1,171 @@
+package com.ruoyi.web.dto;
+
+import lombok.Data;
+import java.math.BigDecimal;
+
+/**
+ * 天阙平台支付回调请求
+ * 对应文档中的回调参数
+ */
+@Data
+public class TianquePayCallbackDTO {
+
+ /** 响应时间戳 */
+ private String timeStamp;
+
+ /** 业务数据签名结果 */
+ private String sign;
+
+ /** 商户编号 */
+ private String mno;
+
+ /** 商户订单号 */
+ private String ordNo;
+
+ /** 天阙平台订单号 */
+ private String uuid;
+
+ /** 交易支付完成时间,格式:YYYYMMDDHHmmss */
+ private String payTime;
+
+ /** 订单总金额(元),格式:#######.## */
+ private String amt;
+
+ /** 支付渠道 */
+ private String payType;
+
+ /** 交易方式:00 主扫 01 被扫 02 公众号 03 小程序 */
+ private String payWay;
+
+ /** 终端号 */
+ private String ylTrmNo;
+
+ /** 原商户订单号(退款通知返回) */
+ private String origOrdNo;
+
+ /** 原天阙订单号(退款通知返回) */
+ private String origUuid;
+
+ /** 支付场景:1 付款码 2 声波 3 刷脸 */
+ private String scene;
+
+ /** 买家用户号 */
+ private String buyerId;
+
+ /** 支付宝买家登录账号 */
+ private String buyerAccount;
+
+ /** 微信/支付宝流水号 */
+ private String transactionId;
+
+ /** 借贷标识:1 借记卡 2 贷记卡 3 其他 */
+ private String drType;
+
+ /** 消费者付款金额 */
+ private String totalOffstAmt;
+
+ /** 商家入账金额 */
+ private String settleAmt;
+
+ /** 付款银行 */
+ private String payBank;
+
+ /** 代金券金额 */
+ private String pointAmount;
+
+ /** 交易手续费率 */
+ private String recFeeRate;
+
+ /** 交易手续费 */
+ private String recFeeAmt;
+
+ /** 商家出款金额 */
+ private String realRefundAmount;
+
+ /** 渠道商商户号 */
+ private String channelId;
+
+ /** 子商户号 */
+ private String subMechId;
+
+ /** 消费者到账金额 */
+ private String refBuyerAmt;
+
+ /** 微信或支付宝的身份ID */
+ private String openid;
+
+ /** 活动类型 */
+ private String activityNo;
+
+ /** 落单号 */
+ private String sxfUuid;
+
+ /** 扩展字段 */
+ private String extend;
+
+ /** 优惠信息 */
+ private String promotionDetail;
+
+ /** 天阙门店编号 */
+ private String storeNum;
+
+ /** 清算日期 */
+ private String clearDt;
+
+ /** 交易完成时间 */
+ private String finishTime;
+
+ /** 天阙终端编号 */
+ private String deviceNo;
+
+ /** 原交易金额 */
+ private String origAmt;
+
+ /** 原交易订单号 */
+ private String origOrderNo;
+
+ /** 优惠券退款金额 */
+ private String couponRefundFee;
+
+ /** 是否收支两条线标识 */
+ private String szltFlag;
+
+ /** 后收手续费 */
+ private String szltRecfeeAmt;
+
+ /** 切批时间 */
+ private String settlementBatchNo;
+
+ /** 支付宝异步刷脸支付参数 */
+ private String asyncPaymentMode;
+
+ /** 分期信息 */
+ private String hbFqPayInfo;
+
+ /** 业务编码 */
+ private String bizCode;
+
+ /** 业务信息 */
+ private String bizMsg;
+
+ /** 组织机构ID */
+ private String orgId;
+
+ /** 交易类型 */
+ private String tradeType;
+
+ /** 交易信息 */
+ private String tradeMsg;
+
+ /** 原始回调数据 */
+ private String rawData;
+
+
+
+ /**
+ * 判断是否支付成功
+ */
+ public boolean isSuccess() {
+ return "0000".equals(bizCode);
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TradeQueryDTO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TradeQueryDTO.java
new file mode 100644
index 00000000..8eac474b
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/dto/TradeQueryDTO.java
@@ -0,0 +1,12 @@
+package com.ruoyi.web.dto;
+
+import lombok.Data;
+
+@Data
+public class TradeQueryDTO {
+
+ /**
+ * 订单号
+ */
+ private String ordNo;
+}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DeviceMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DeviceMapper.java
new file mode 100644
index 00000000..6d049e97
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DeviceMapper.java
@@ -0,0 +1,17 @@
+package com.ruoyi.web.mapper;
+
+import com.ruoyi.web.domain.Device;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface DeviceMapper {
+
+ List getDevice(@Param("storeId") String storeId);
+
+ int addDevice(Device device);
+
+ List getSheXiangTou(String storeId);
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DoorScanRecordMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DoorScanRecordMapper.java
new file mode 100644
index 00000000..5313840b
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/DoorScanRecordMapper.java
@@ -0,0 +1,20 @@
+package com.ruoyi.web.mapper;
+
+
+import com.ruoyi.web.domain.DoorScanRecord;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+
+@Mapper
+public interface DoorScanRecordMapper {
+
+
+ int addDoorScanRecord(DoorScanRecord doorScanRecord);
+
+ List selectScanRecordList(DoorScanRecord doorScanRecord);
+
+ Integer getEnterCountByDate(@Param("date") String date,@Param("storeId") Integer storeId);
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/InstallTaskMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/InstallTaskMapper.java
new file mode 100644
index 00000000..880c206b
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/InstallTaskMapper.java
@@ -0,0 +1,61 @@
+package com.ruoyi.web.mapper;
+
+import com.ruoyi.web.domain.InstallTask;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+/**
+ * 安装任务Mapper接口
+ */
+public interface InstallTaskMapper {
+
+ /**
+ * 新增安装任务
+ */
+ int insertInstallTask(InstallTask installTask);
+
+ /**
+ * 根据ID查询安装任务
+ */
+ InstallTask selectInstallTaskById(@Param("id") Long id);
+
+ /**
+ * 根据任务编号查询
+ */
+ InstallTask selectInstallTaskByTaskNo(@Param("taskNo") String taskNo);
+
+ /**
+ * 查询安装任务列表
+ */
+ List selectInstallTaskList(InstallTask installTask);
+
+ /**
+ * 更新安装任务
+ */
+ int updateInstallTask(InstallTask installTask);
+
+ /**
+ * 删除安装任务(逻辑删除)
+ */
+ int deleteInstallTaskById(@Param("id") Long id);
+
+ /**
+ * 批量删除安装任务
+ */
+ int deleteInstallTaskByIds(@Param("ids") Long[] ids);
+
+ /**
+ * 指派任务
+ */
+ int assignTask(InstallTask installTask);
+
+ /**
+ * 完成任务安装
+ */
+ int completeTask(InstallTask installTask);
+
+ /**
+ * 查询当天最大任务编号
+ */
+ String selectMaxTaskNoByDate(@Param("prefix") String prefix);
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/OrderMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/OrderMapper.java
index 188b4364..54e142f0 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/OrderMapper.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/OrderMapper.java
@@ -2,7 +2,10 @@ package com.ruoyi.web.mapper;
import com.ruoyi.web.domain.Order;
+import com.ruoyi.web.domain.OrderItem;
+import com.ruoyi.web.vo.OrderCountVO;
import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -10,7 +13,22 @@ import java.util.List;
@Mapper
public interface OrderMapper {
-
-
List selectOrderList(Order order);
+
+
+ int insert(Order order);
+
+
+ int batchInsert(@Param("list") List items);
+
+ Order selectForUpdateByOrderNo(@Param("ordNo") String ordNo);
+
+ List selectByOrderId(@Param("orderId") Long orderId);
+
+ /**
+ * 根据ID更新订单(选择性更新)
+ */
+ int updateById(Order order);
+
+ OrderCountVO getOrderCount(String storeId);
}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/ProductMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/ProductMapper.java
index 197b4cb7..a8d1995b 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/ProductMapper.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/ProductMapper.java
@@ -2,6 +2,8 @@ package com.ruoyi.web.mapper;
import com.ruoyi.web.domain.Product;
import com.ruoyi.web.domain.Store;
+import com.ruoyi.web.dto.BatchBarcodeQueryDTO;
+import com.ruoyi.web.vo.ProductVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -26,4 +28,29 @@ public interface ProductMapper {
int batchProductById(@Param("ids") List ids);
+
+
+ List selectBatchForUpdate(@Param("storeId") Integer storeId,
+ @Param("ids") List ids);
+
+
+ /**
+ * 扣减库存(带库存校验,带门店ID)
+ * @param storeId 门店ID
+ * @param productId 商品ID
+ * @param quantity 扣减数量
+ * @return 影响行数(>0表示成功,=0表示库存不足或商品不存在)
+ */
+ int reduceStock(@Param("storeId") Integer storeId,
+ @Param("productId") Long productId,
+ @Param("quantity") Integer quantity);
+
+ /**
+ * 增加库存(用于回滚,带门店ID)
+ */
+ int addStock(@Param("storeId") Integer storeId,
+ @Param("productId") Long productId,
+ @Param("quantity") Integer quantity);
+
+ List getByBarcodes(BatchBarcodeQueryDTO dto);
}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/StoreMapper.java b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/StoreMapper.java
index 7e9720f9..a6f08705 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/StoreMapper.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/mapper/StoreMapper.java
@@ -2,8 +2,6 @@ package com.ruoyi.web.mapper;
import com.ruoyi.web.domain.Store;
-import com.ruoyi.web.vo.StoreVo;
-import com.ruoyi.web.vo.UserStoreVo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/DeviceService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/DeviceService.java
new file mode 100644
index 00000000..190410da
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/DeviceService.java
@@ -0,0 +1,15 @@
+package com.ruoyi.web.service;
+
+import com.ruoyi.web.domain.Brand;
+import com.ruoyi.web.domain.Device;
+
+import java.util.List;
+
+public interface DeviceService {
+
+ List getDevice(String storeId);
+
+ int addDevice(Device device);
+
+ List getSheXiangTou(String storeId);
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/DoorScanRecordService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/DoorScanRecordService.java
new file mode 100644
index 00000000..8f0d1459
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/DoorScanRecordService.java
@@ -0,0 +1,15 @@
+package com.ruoyi.web.service;
+
+import com.ruoyi.web.domain.DoorScanRecord;
+
+import java.util.List;
+
+
+public interface DoorScanRecordService {
+
+ int addDoorScanRecord(DoorScanRecord doorScanRecord);
+
+ List selectScanRecordList(DoorScanRecord doorScanRecord);
+
+ Integer getEnterCountByDate(String date,Integer storeId);
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/InstallTaskService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/InstallTaskService.java
new file mode 100644
index 00000000..b514e0e2
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/InstallTaskService.java
@@ -0,0 +1,61 @@
+package com.ruoyi.web.service;
+
+import com.ruoyi.web.domain.InstallTask;
+
+import java.util.List;
+
+/**
+ * 安装任务Service接口
+ */
+public interface InstallTaskService {
+
+ /**
+ * 新增安装任务
+ */
+ int insertInstallTask(InstallTask installTask);
+
+ /**
+ * 根据ID查询
+ */
+ InstallTask selectInstallTaskById(Long id);
+
+ /**
+ * 根据任务编号查询
+ */
+ InstallTask selectInstallTaskByTaskNo(String taskNo);
+
+ /**
+ * 查询安装任务列表
+ */
+ List selectInstallTaskList(InstallTask installTask);
+
+ /**
+ * 更新安装任务
+ */
+ int updateInstallTask(InstallTask installTask);
+
+ /**
+ * 删除安装任务
+ */
+ int deleteInstallTaskById(Long id);
+
+ /**
+ * 批量删除
+ */
+ int deleteInstallTaskByIds(Long[] ids);
+
+ /**
+ * 指派任务
+ */
+ int assignTask(Long id, Long assigneeId, String assigneeName, String updateBy);
+
+ /**
+ * 完成任务安装
+ */
+ int completeTask(InstallTask installTask);
+
+ /**
+ * 生成任务编号
+ */
+ String generateTaskNo();
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/OrderService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/OrderService.java
index 90d80c82..a2ef9d2d 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/service/OrderService.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/OrderService.java
@@ -1,7 +1,13 @@
package com.ruoyi.web.service;
+import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.web.domain.Order;
+import com.ruoyi.web.dto.CreateOrderDTO;
+import com.ruoyi.web.dto.ScanPayRequest;
+import com.ruoyi.web.dto.TianquePayCallbackDTO;
+import com.ruoyi.web.dto.TradeQueryDTO;
+import com.ruoyi.web.vo.OrderCountVO;
import java.util.List;
@@ -13,4 +19,21 @@ public interface OrderService {
List selectOrderList(Order order);
+
+ AjaxResult addPay(CreateOrderDTO createOrderDTO);
+
+ TianquePayCallbackDTO parseCallback(String callbackData);
+
+ /**
+ * 处理天阙平台支付回调
+ */
+ void handleTianquePayCallback(TianquePayCallbackDTO request);
+
+ AjaxResult addOrder(CreateOrderDTO createOrderDTO);
+
+ String addOrderReverseScan(ScanPayRequest scanPayRequest) throws Exception;
+
+ AjaxResult tradeQuery(TradeQueryDTO tradeQueryDTO) throws Exception;
+
+ OrderCountVO getOrderCount(String storeId);
}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/ProductService.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/ProductService.java
index d5b8486b..81dfb206 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/service/ProductService.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/ProductService.java
@@ -1,7 +1,9 @@
package com.ruoyi.web.service;
import com.ruoyi.web.domain.Product;
+import com.ruoyi.web.dto.BatchBarcodeQueryDTO;
import com.ruoyi.web.dto.BatchStoreDTO;
+import com.ruoyi.web.vo.ProductVo;
import java.util.List;
@@ -35,4 +37,6 @@ public interface ProductService {
int updateProduct(Product product);
int batchProductById(BatchStoreDTO dto);
+
+ List getByBarcodes(BatchBarcodeQueryDTO dto);
}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DeviceServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DeviceServiceImpl.java
new file mode 100644
index 00000000..789d4e00
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DeviceServiceImpl.java
@@ -0,0 +1,32 @@
+package com.ruoyi.web.service.impl;
+
+
+import com.ruoyi.web.domain.Device;
+import com.ruoyi.web.mapper.DeviceMapper;
+import com.ruoyi.web.service.DeviceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+
+@Service
+public class DeviceServiceImpl implements DeviceService {
+
+ @Autowired
+ private DeviceMapper deviceMapper;
+ @Override
+ public List getDevice(String storeId) {
+ return deviceMapper.getDevice(storeId);
+ }
+
+ @Override
+ public int addDevice(Device device) {
+ return deviceMapper.addDevice(device);
+ }
+
+ @Override
+ public List getSheXiangTou(String storeId) {
+ return deviceMapper.getSheXiangTou(storeId);
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DoorScanRecordServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DoorScanRecordServiceImpl.java
new file mode 100644
index 00000000..032de212
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/DoorScanRecordServiceImpl.java
@@ -0,0 +1,31 @@
+package com.ruoyi.web.service.impl;
+
+import com.ruoyi.web.domain.DoorScanRecord;
+import com.ruoyi.web.mapper.DoorScanRecordMapper;
+import com.ruoyi.web.service.DoorScanRecordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+
+@Service
+public class DoorScanRecordServiceImpl implements DoorScanRecordService {
+
+ @Autowired
+ private DoorScanRecordMapper doorScanRecordMapper;
+ @Override
+ public int addDoorScanRecord(DoorScanRecord doorScanRecord) {
+ return doorScanRecordMapper.addDoorScanRecord(doorScanRecord);
+ }
+
+ @Override
+ public List selectScanRecordList(DoorScanRecord doorScanRecord) {
+ return doorScanRecordMapper.selectScanRecordList(doorScanRecord);
+ }
+
+ @Override
+ public Integer getEnterCountByDate(String date,Integer storeId) {
+ return doorScanRecordMapper.getEnterCountByDate(date,storeId);
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/InstallTaskServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/InstallTaskServiceImpl.java
new file mode 100644
index 00000000..6f8ea8ab
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/InstallTaskServiceImpl.java
@@ -0,0 +1,119 @@
+package com.ruoyi.web.service.impl;
+
+import com.ruoyi.common.core.text.Convert;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.web.domain.InstallTask;
+import com.ruoyi.web.mapper.InstallTaskMapper;
+import com.ruoyi.web.service.InstallTaskService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 安装任务Service实现类
+ */
+@Service
+public class InstallTaskServiceImpl implements InstallTaskService {
+
+ @Resource
+ private InstallTaskMapper installTaskMapper;
+
+ @Override
+ public int insertInstallTask(InstallTask installTask) {
+ // 生成任务编号
+ installTask.setTaskNo(generateTaskNo());
+ // 设置初始状态为待处理
+ installTask.setTaskStatus(0);
+ // 设置创建时间
+ installTask.setCreateTime(DateUtils.getNowDate());
+ return installTaskMapper.insertInstallTask(installTask);
+ }
+
+ @Override
+ public InstallTask selectInstallTaskById(Long id) {
+ return installTaskMapper.selectInstallTaskById(id);
+ }
+
+ @Override
+ public InstallTask selectInstallTaskByTaskNo(String taskNo) {
+ return installTaskMapper.selectInstallTaskByTaskNo(taskNo);
+ }
+
+ @Override
+ public List selectInstallTaskList(InstallTask installTask) {
+ return installTaskMapper.selectInstallTaskList(installTask);
+ }
+
+ @Override
+ public int updateInstallTask(InstallTask installTask) {
+ installTask.setUpdateTime(DateUtils.getNowDate());
+ return installTaskMapper.updateInstallTask(installTask);
+ }
+
+ @Override
+ public int deleteInstallTaskById(Long id) {
+ return installTaskMapper.deleteInstallTaskById(id);
+ }
+
+ @Override
+ public int deleteInstallTaskByIds(Long[] ids) {
+ return installTaskMapper.deleteInstallTaskByIds(ids);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int assignTask(Long id, Long assigneeId, String assigneeName, String updateBy) {
+ InstallTask task = new InstallTask();
+ task.setId(id);
+ task.setAssigneeId(assigneeId);
+ task.setAssigneeName(assigneeName);
+ task.setTaskStatus(1); // 1-已接单
+ task.setAssignTime(DateUtils.getNowDate());
+ task.setUpdateBy(updateBy);
+ task.setUpdateTime(DateUtils.getNowDate());
+
+ return installTaskMapper.assignTask(task);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public int completeTask(InstallTask installTask) {
+ installTask.setTaskStatus(3); // 3-已完成
+ installTask.setInstallTime(DateUtils.getNowDate());
+ installTask.setUpdateTime(DateUtils.getNowDate());
+
+ return installTaskMapper.completeTask(installTask);
+ }
+
+ @Override
+ public String generateTaskNo() {
+ // 格式:INS + 年月日 + 4位序号
+ // 例如:INS202503160001
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+ String today = sdf.format(new Date());
+ String prefix = "INS" + today;
+
+ // 查询当天最大的任务编号
+ String maxTaskNo = installTaskMapper.selectMaxTaskNoByDate(prefix);
+
+ int seq = 1;
+ if (maxTaskNo != null && maxTaskNo.length() >= 14) {
+ // 截取后4位序号
+ String seqStr = maxTaskNo.substring(11);
+ try {
+ seq = Integer.parseInt(seqStr) + 1;
+ } catch (NumberFormatException e) {
+ seq = 1;
+ }
+ }
+
+ // 生成4位序号,不足补0
+ String seqStr = String.format("%04d", seq);
+
+ return prefix + seqStr;
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/OrderServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/OrderServiceImpl.java
index 060ac913..3015326b 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/OrderServiceImpl.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/OrderServiceImpl.java
@@ -1,26 +1,803 @@
package com.ruoyi.web.service.impl;
+import cn.hutool.core.util.IdUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.domain.Order;
+import com.ruoyi.web.domain.OrderItem;
+import com.ruoyi.web.domain.Product;
+import com.ruoyi.web.dto.*;
import com.ruoyi.web.mapper.OrderMapper;
+import com.ruoyi.web.mapper.ProductMapper;
import com.ruoyi.web.service.OrderService;
+import com.ruoyi.web.utils.HttpUtils;
+import com.ruoyi.web.utils.RSASignature;
+import com.ruoyi.web.vo.OrderCountVO;
+import com.ruoyi.web.vo.OrderVo;
+import com.ruoyi.web.vo.PaymentResponseVO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.DigestUtils;
import javax.annotation.Resource;
-import java.util.List;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
/**
* 商品订单 服务层实现
*/
@Service
public class OrderServiceImpl implements OrderService {
+
+ private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
@Resource
private OrderMapper orderMapper;
+ @Resource
+ private ProductMapper productMapper;
+ @Autowired
+ private RedisTemplate redisTemplate;
+
+ private static final String ORDER_LOCK_PREFIX = "order:lock:";
@Override
public List selectOrderList(Order order) {
return orderMapper.selectOrderList(order);
}
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public AjaxResult addPay(CreateOrderDTO createOrderDTO) {
+ //订单号
+ String orderNo = null;
+ //订单id
+ Long orderId = null;
+ //锁key
+ String lockKey = null;
+ //订单明细
+ List orderItems = new ArrayList<>();
+ //金额
+ BigDecimal totalAmount = BigDecimal.ZERO;
+ //支付控件参数
+ String pay = null;
+ try {
+ // 1. 生成锁key(基于门店ID + 商品组合)
+ lockKey = generateLockKey(createOrderDTO.getStoreId(), createOrderDTO.getItems());
+ log.info("尝试获取锁: {}", lockKey);
+
+ // 2. 获取分布式锁
+ Boolean locked = redisTemplate.opsForValue()
+ .setIfAbsent(lockKey, "1", Duration.ofSeconds(6));
+
+ if (Boolean.FALSE.equals(locked)) {
+ log.warn("获取锁失败,该门店的商品组合正在被处理: {}", lockKey);
+ throw new ServiceException("系统繁忙,请稍后重试");
+ }
+
+ try {
+ // 3. 批量查询商品并加行锁(带上门店ID条件)
+ List productIds = createOrderDTO.getItems().stream()
+ .map(OrderItem::getProductId)
+ .collect(Collectors.toList());
+
+ List products = productMapper.selectBatchForUpdate(
+ createOrderDTO.getStoreId(), productIds);
+
+ if (products.size() != productIds.size()) {
+ // 找出不存在的商品
+ Set existIds = products.stream()
+ .map(Product::getId)
+ .collect(Collectors.toSet());
+
+ List notExistIds = productIds.stream()
+ .filter(id -> !existIds.contains(id))
+ .collect(Collectors.toList());
+
+ log.error("部分商品在该门店不存在, 门店id storeId:{}, 商品id productIds:{}",
+ createOrderDTO.getStoreId(), notExistIds);
+// return AjaxResult.error("部分商品在该门店不存在");
+ throw new ServiceException("部分商品在该门店不存在");
+
+ }
+
+ Map productMap = products.stream()
+ .collect(Collectors.toMap(Product::getId, p -> p));
+
+ // 5. 校验库存并计算金额
+ for (OrderItem item : createOrderDTO.getItems()) {
+ Product product = productMap.get(item.getProductId());
+
+ if (product.getStockQuantity() < item.getQuantity()) {
+ log.error("商品[" + product.getProductName() + "]库存不足,当前门店剩余库存数量:" + product.getStockQuantity());
+ return AjaxResult.error("商品[" + product.getProductName() + "]库存不足,当前门店剩余库存数量:" + product.getStockQuantity());
+ }
+
+ // 计算小计
+ BigDecimal storePrice = new BigDecimal(product.getStorePrice());
+ BigDecimal subtotal = storePrice
+ .multiply(new BigDecimal(item.getQuantity()))
+ .setScale(2, RoundingMode.HALF_UP);
+
+ totalAmount = totalAmount.add(subtotal);
+ // 创建订单明细
+ OrderItem orderItem = new OrderItem();
+ orderItem.setProductId(product.getId());
+ orderItem.setProductName(product.getProductName());
+ orderItem.setMainImage(product.getMainImage());
+ orderItem.setQuantity(item.getQuantity());
+ orderItem.setSubtotal(item.getSubtotal());
+ orderItem.setCreateBy(createOrderDTO.getCreateBy());
+ orderItems.add(orderItem);
+ }
+
+ // 6. 校验前端传递的金额和后端计算的金额是否一致
+ BigDecimal frontendAmount = new BigDecimal(createOrderDTO.getTotalAmount());
+ if (frontendAmount.compareTo(totalAmount) != 0) {
+ log.error("金额不一致,前端传参金额:{},后端计算金额:{}",
+ frontendAmount, totalAmount);
+ return AjaxResult.error("订单总金额异常");
+ }
+
+ // 7. 生成订单号
+ orderNo = generateOrderNo();
+
+ // 8. 创建订单主表
+ Order order = new Order();
+ order.setOrderNo(orderNo);
+ order.setStoreId(createOrderDTO.getStoreId());
+ order.setPayAmount(String.valueOf(totalAmount));
+ order.setPayStatus(0); // 未支付
+ order.setCreateBy(createOrderDTO.getCreateBy());
+ int insert = orderMapper.insert(order);
+ if (insert <= 0) {
+ log.error("主订单创建失败");
+ throw new ServiceException("主订单创建失败");
+ }
+ orderId = order.getId();
+
+ // 9. 批量插入订单明细
+ for (OrderItem item : orderItems) {
+ item.setOrderId(orderId);
+ }
+ int batchInsertResult = orderMapper.batchInsert(orderItems);
+
+ if (batchInsertResult != orderItems.size()) {
+ log.error("订单明细创建失败");
+ throw new ServiceException("订单明细创建失败");
+ }
+ } finally {
+ // 释放锁
+ redisTemplate.delete(lockKey);
+ log.info("释放锁: {}", lockKey);
+ }
+ // ============ 锁已释放,开始调用支付接口 ============
+ pay = pay(orderNo, String.valueOf(totalAmount)); //调用第三方支付成功,返回调用支付控件参数
+ } catch (Exception e) {
+ log.error("创建订单失败", e);
+ throw new ServiceException("创建订单失败" + e.getMessage());
+ }
+ log.info("订单创建成功, orderNo:{}, 订单ID:{}, storeId:{},支付控件参数:{}",
+ orderNo, orderId, createOrderDTO.getStoreId(), pay);
+ return AjaxResult.success("订单创建成功: " + pay);
+ }
+
+ @Override
+ public TianquePayCallbackDTO parseCallback(String callbackData) {
+ try {
+ return JSON.parseObject(callbackData, TianquePayCallbackDTO.class);
+ } catch (Exception e) {
+ log.error("解析天阙平台回调数据失败", e);
+ throw new RuntimeException("解析回调数据失败");
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void handleTianquePayCallback(TianquePayCallbackDTO request) {
+ log.info("处理天阙平台支付回调, ordNo:{}, uuid:{}, amt:{}",
+ request.getOrdNo(), request.getUuid(), request.getAmt());
+
+ // 1. 参数校验
+ if (request.getOrdNo() == null || request.getUuid() == null) {
+ log.error("回调参数缺失");
+ throw new ServiceException("回调参数缺失");
+ }
+
+ // 2. 幂等处理(防止重复回调)
+ String idempotentKey = "tianque:callback:" + request.getUuid();
+ Boolean isFirst = redisTemplate.opsForValue()
+ .setIfAbsent(idempotentKey, "1", Duration.ofHours(24));
+
+ if (Boolean.FALSE.equals(isFirst)) {
+ log.info("天阙平台回调已处理过, uuid:{}", request.getUuid());
+ throw new ServiceException("天阙平台回调已处理过");
+ }
+
+ // 3. 获取订单锁(防止并发处理同一个订单)
+ String orderLockKey = ORDER_LOCK_PREFIX + request.getOrdNo();
+ Boolean locked = redisTemplate.opsForValue()
+ .setIfAbsent(orderLockKey, "1", Duration.ofSeconds(5));
+
+ if (Boolean.FALSE.equals(locked)) {
+ log.warn("订单正在处理中, ordNo:{}", request.getOrdNo());
+ throw new ServiceException("订单正在处理中");
+ }
+
+ try {
+ // 4. 查询订单(加行锁)
+ Order order = orderMapper.selectForUpdateByOrderNo(request.getOrdNo());
+ if (order == null) {
+ log.error("订单不存在, orderNo:{}", request.getOrdNo());
+ throw new ServiceException("订单不存在");
+ }
+
+ // 5. 检查订单状态
+ if (order.getPayStatus() == 2) {
+ log.info("订单已支付, orderNo:{}", request.getOrdNo());
+ throw new ServiceException("订单已支付");
+ }
+
+ if (order.getPayStatus() != 0) {
+ log.error("订单状态错误, orderNo:{}, status:{}",
+ request.getOrdNo(), order.getPayStatus());
+ throw new ServiceException("订单状态错误");
+ }
+
+ // 6. 金额校验
+ if (order.getPayAmount().compareTo(request.getAmt()) != 0) {
+ log.error("支付金额不匹配, orderAmount:{}, payAmount:{}",
+ order.getPayAmount(), request.getAmt());
+ throw new ServiceException("支付金额不匹配");
+ }
+
+ // 7. 查询订单明细
+ List items = orderMapper.selectByOrderId(order.getId());
+
+ // 8. 扣减实际库存
+ for (OrderItem item : items) {
+ int result = productMapper.reduceStock(
+ order.getStoreId(),
+ item.getProductId(),
+ item.getQuantity()
+ );
+ if (result <= 0) {
+ log.error("库存扣减失败, storeId:{}, productId:{}, quantity:{}",
+ order.getStoreId(), item.getProductId(), item.getQuantity());
+ throw new ServiceException("库存扣减失败");
+ }
+ }
+
+ // 9. 更新订单状态为已支付
+ Order updateOrder = new Order();
+ updateOrder.setId(order.getId());
+ updateOrder.setPayStatus(2);
+ updateOrder.setPayTime(new Date());
+
+ int updated = orderMapper.updateById(updateOrder);
+ if (updated <= 0) {
+ log.error("订单状态更新失败, orderNo:{}", request.getOrdNo());
+ throw new ServiceException("订单状态更新失败");
+ }
+ log.info("支付回调处理成功, orderNo:{}, storeId:{}",
+ request.getOrdNo(), order.getStoreId());
+ } finally {
+ // 释放订单锁
+ redisTemplate.delete(orderLockKey);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public AjaxResult addOrder(CreateOrderDTO createOrderDTO) {
+ String orderNo = null;
+ BigDecimal totalAmount = BigDecimal.ZERO;
+ //锁key
+ String lockKey = null;
+ orderNo = generateOrderNo();
+ try {
+ // 1. 生成锁key(基于门店ID + 商品组合)
+ lockKey = generateLockKey(createOrderDTO.getStoreId(), createOrderDTO.getItems());
+ log.info("尝试获取锁: {}", lockKey);
+
+ // 2. 获取分布式锁
+ Boolean locked = redisTemplate.opsForValue()
+ .setIfAbsent(lockKey, "1", Duration.ofSeconds(6));
+
+ if (Boolean.FALSE.equals(locked)) {
+ log.warn("获取锁失败,该门店的商品组合正在被处理: {}", lockKey);
+ throw new ServiceException("系统繁忙,请稍后重试");
+ }
+
+ try {
+ // 3. 批量查询商品并加行锁(带上门店ID条件)
+ List productIds = createOrderDTO.getItems().stream()
+ .map(OrderItem::getProductId)
+ .collect(Collectors.toList());
+
+ List products = productMapper.selectBatchForUpdate(
+ createOrderDTO.getStoreId(), productIds);
+
+ if (products.size() != productIds.size()) {
+ // 找出不存在的商品
+ Set existIds = products.stream()
+ .map(Product::getId)
+ .collect(Collectors.toSet());
+
+ List notExistIds = productIds.stream()
+ .filter(id -> !existIds.contains(id))
+ .collect(Collectors.toList());
+
+ log.error("部分商品在该门店不存在, 门店id storeId:{}, 商品id productIds:{}",
+ createOrderDTO.getStoreId(), notExistIds);
+ throw new ServiceException("部分商品在该门店不存在");
+ }
+ Map productMap = products.stream()
+ .collect(Collectors.toMap(Product::getId, p -> p));
+ // 5. 校验库存并计算金额
+ for (OrderItem item : createOrderDTO.getItems()) {
+ Product product = productMap.get(item.getProductId());
+
+ if (product.getStockQuantity() < item.getQuantity()) {
+ log.error("商品[" + product.getProductName() + "]库存不足,当前门店剩余库存数量:" + product.getStockQuantity());
+ return AjaxResult.error("商品[" + product.getProductName() + "]库存不足,当前门店剩余库存数量:" + product.getStockQuantity());
+ }
+ // 计算小计
+ BigDecimal storePrice = new BigDecimal(product.getStorePrice());
+ BigDecimal subtotal = storePrice
+ .multiply(new BigDecimal(item.getQuantity()))
+ .setScale(2, RoundingMode.HALF_UP);
+
+ totalAmount = totalAmount.add(subtotal);
+ }
+
+ // 6. 校验前端传递的金额和后端计算的金额是否一致
+ BigDecimal frontendAmount = new BigDecimal(createOrderDTO.getTotalAmount());
+ if (frontendAmount.compareTo(totalAmount) != 0) {
+ log.error("金额不一致,前端传参金额:{},后端计算金额:{}",
+ frontendAmount, totalAmount);
+ return AjaxResult.error("订单总金额异常");
+ }
+
+ //订单id
+ Long orderId = null;
+ Order order = new Order();
+ order.setOrderNo(orderNo);
+ order.setStoreId(createOrderDTO.getStoreId());
+ order.setPayAmount(String.valueOf(createOrderDTO.getTotalAmount()));
+ order.setPayStatus(0); // 未支付
+ order.setCreateBy(createOrderDTO.getCreateBy());
+ int insert = orderMapper.insert(order);
+ if (insert <= 0) {
+ log.error("主订单创建失败");
+ throw new ServiceException("主订单创建失败");
+ }
+ orderId = order.getId();
+
+ for (OrderItem item : createOrderDTO.getItems()) {
+ item.setOrderId(orderId);
+ }
+ orderMapper.batchInsert(createOrderDTO.getItems());
+ log.info("被扫订单创建成功");
+ } finally {
+ // 释放锁
+ redisTemplate.delete(lockKey);
+ log.info("释放锁: {}", lockKey);
+ }
+ } catch (Exception e) {
+ log.error("创建订单失败", e);
+ throw new ServiceException("创建订单失败" + e.getMessage());
+ }
+ OrderVo orderVo = new OrderVo();
+ orderVo.setOrderNo(orderNo);
+ orderVo.setAmount(String.valueOf(totalAmount));
+ return AjaxResult.success(orderVo);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String addOrderReverseScan(ScanPayRequest scanPayRequest) throws Exception {
+ //再加个校验,根据订单号去查询金额,如果前端传的金额和后端存的不一样,不允许支付
+
+ //合作方私钥(替换成自己的)
+ String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM35qbWVNdBAU+oai8EVE3iSpA9wHAt4wOs+SufbkE/FNoMgp4EMrFNrdNkaKLQq4cRzqoNlJY+DZSL54dm7ekKZP/SLhWwNi9tJvhKsFpw6SRwthlKyA8lCW2mzFffFqLhGAayNoZTmHwkq8dotvecARVbiFtakzzFy/O7ATmzw3i57yaVgX2RtD8WWA7MGg5CQP1vvIDlXqd40SQcrJY6GNNv07Brv3fpyW2nXbvrxbOx+87NnG77/R7o8eFcWJF+yrqE1xeN3hDvHBFMFyf1M39Zig2I75vKRvHGqftAZuvlDG0nInwcSiMTE7wDsXvYzRnOsgJNVET7aFhzOvFAgMBAAECggEATjB4nRl2S2Whasnf0Ab77CoZSjn7HUXv2hymBkJNPq3eV0Hh5Pjjqmi7hIs0cdm9AWXd6RzySKpScQNjYQFxEOIdbayfTdzD24L2ivT6ZBca09Z9J/9Rik0wJ4ULTtny5JRc6VwGgOn9Twdsde8V4sr1nHUvpMaRH6WVxW8mX7+udgbK0Uxm5IFROeKGz0DSuG8SBpDhD2EB6P2Q+1fpna17yP1R08HtZNU6lQx7yMesaJuvKkwf4YiSjIhUtdbUXGhmbG5LFzoj3oRs25W68bIOQjT9i196hPQOYj6C7yz72nuY2rwFHhtsocfQRULWOHtsnWts9epvuFruDTdq8QKBgQD8bZhQkyQm6VTP0Hmv6sV9SXXKH0rb2V8hml7DxcpFl+l2/9UesH3qMoFKZ5C1YdPKZBNlD7fp5RxjtJ8MdwIp8MxRgMf/bZpIOx1Lfum1H+dKh5LF+oC4GGtJ5jqnwozNPog4keUn+bqKhiza9snA+6jy446A1Y8GGWlZwUGkVwKBgQDPxb7FZmOcotNnh8mS5pcx6d22UhBHIHz/X/dcjNQuI8tIRaIvkJm/4lfuxejCz3LY9k+VSQ96ZM2L5OqZGDlR7aMXQ3/FhuS4PEkNcDvccQPoig7U5zUUn2Jdqom6Lngh7Ka8B1v24ght0yXHHNChmMg/DJ42EzCnjpydWs6/QwKBgA9qGd0BvzlpEjbGgkfNzFWEQN8g3g9izL5ekN7fmyR4zFbp9He1S1sbzm1euaV53dcEGXMYbKCpzvv/sZ6vPcCV5cQsWwosBTnX8kgD7f2Tfyo78SiJzYZwZ0zR9E7+QF7gLK1Xq2ivhUakPuT4IQXZ2E1MvAz9/Yff0WEbvghtAoGAejFzC8c2yDUenaHHU+TXgNxor0Q+HIan3M0EvmJ4mxYkBMInK1AgjDBCxMOSK5gzlBPwI/0O5E2KcT7VFeqgM2XN5+2jpHi75PpXgFbEbdXtlYI0TNQZbKJ8CFg2nc+ciV8ThDvTwzOV/3kRm7N/o7ol8qaqGWVZ1QFTbFuugd8CgYBAsNVKCEHGZAwS5Csv/IBopcrJOJknmEHeAT+VwxtZK7yOJ4ZSNizzofu847YGO4uEhfcaC8lYxnj8XskuMG2KChbbxEA+qlnL/oMIwVrpE0ZbFmQ4WRTUfVQT+zCHMPuuuj5tLPTSMjQLGlq+r3XRzwUaX98J9GLKgHG7tXlphw==";
+ //随行付公钥
+ String sxfPublic = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjo1+KBcvwDSIo+nMYLeOJ19Ju4ii0xH66ZxFd869EWFWk/EJa3xIA2+4qGf/Ic7m7zi/NHuCnfUtUDmUdP0JfaZiYwn+1Ek7tYAOc1+1GxhzcexSJLyJlR2JLMfEM+rZooW4Ei7q3a8jdTWUNoak/bVPXnLEVLrbIguXABERQ0Ze0X9Fs0y/zkQFg8UjxUN88g2CRfMC6LldHm7UBo+d+WlpOYH7u0OTzoLLiP/04N1cfTgjjtqTBI7qkOGxYs6aBZHG1DJ6WdP+5w+ho91sBTVajsCxAaMoExWQM2ipf/1qGdsWmkZScPflBqg7m0olOD87ymAVP/3Tcbvi34bDfwIDAQAB";
+
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
+ String dataStr = df.format(new Date());
+
+ ApiRequestBean reqBean = new ApiRequestBean();
+
+ reqBean.setOrgId("57594783"); //机构id
+ reqBean.setReqId(UUID.randomUUID().toString().replaceAll("-", ""));
+ reqBean.setSignType("RSA");
+ reqBean.setTimestamp(dataStr);
+ reqBean.setVersion("1.0");
+ JSONObject reqData = new JSONObject();
+
+ //业务参数
+ reqData.put("ordNo", scanPayRequest.getOrdNo()); //商户订单号
+ reqData.put("mno", "399260311280126"); //商户编号
+ //reqData.put("subMechId", ""); //子商户号
+ reqData.put("authCode", scanPayRequest.getAuthCode()); //授权码 需要前端传
+ reqData.put("amt", scanPayRequest.getAmt()); //订单总金额
+ //reqData.put("discountAmt", ""); //参与优惠金额
+ //reqData.put("unDiscountAmt", ""); //不参与优惠金额
+ reqData.put("payType", scanPayRequest.getPayType()); //支付渠道
+ reqData.put("scene", "1"); //支付场景,1: 刷卡 2:声波 3:刷脸 不上传默认为 1
+ reqData.put("subject", "B扫C测试"); //订单标题
+ reqData.put("tradeSource", "02"); //交易来源 01服务商,02收银台,03硬件
+ reqData.put("trmIp", "172.16.2.1");
+ //reqData.put("limitPay", "00"); //限制卡类型: 00-全部 01-限定不能使 用信用卡支付 默认值 00
+ //reqData.put("hbFqNum", "6"); //花呗分期数,仅可上送 6 或 12
+ //reqData.put("hbFqPercent", "0"); //卖家承担分期 服务费比例,仅支持上送 0 或 100
+ //reqData.put("goodsTag", "00"); //订单优惠标识 00:是,01: 否
+
+ //reqData.put("couponDetail", ""); //优惠详情信息,见下面三个字段
+ //reqData.put("costPrice", "200"); //订单原价保留两 位小数;微信 独有
+ //reqData.put("receiptId ", "123456789"); //商品小票
+
+ //reqData.put("goodsDetail", "123456789"); //单品优惠信息使用 json 数组格式提交
+ //reqData.put("goodsId", "200"); //商品编码
+ //reqData.put("thirdGoodsId", "12345678"); //微信/支付宝侧商品码
+ //reqData.put("goodsName", "苹果电脑"); //商品名称
+ //reqData.put("quantity", "1"); //商品数量
+ //reqData.put("price", "1.01"); //商品单价
+ //reqData.put("goodsCategory", ""); //商品类目;支 付宝独有
+ //reqData.put("categoriesTree", "124868003|126232002|126252004"); //商品类目树
+ //reqData.put("goodsDesc", ""); //商品描述;支 付宝独有
+ //reqData.put("showUrl", ""); //商品展示地址 url;支付宝独有
+
+ //reqData.put("needReceipt", "00"); //电子发票功能 微信开具电子 发票使用
+ //reqData.put("ledgerAccountFlag", "00"); //是否做分账 分账交易使 用;00:做; 01:不做;不传默认为不做分账
+ //reqData.put("ledgerAccountEffectTime", "00"); //分账有效时间 单位为天;是 否做分账选择 00 时该字段必传
+ // TODO: 2026/3/11 后端回调地址
+ reqData.put("notifyUrl", "http://193.112.94.36:8081/mall/order/notify"); //回调地址
+ //reqData.put("ylTrmNo", ""); //银联终端号
+ //reqData.put("terminalId", ""); //TQ机具编号
+ //reqData.put("deviceNo ", ""); //设备号
+ //reqData.put("identityFlag", ""); //是否是实名支付
+ //reqData.put("buyerIdType", "IDCARD"); //证件类型
+ //reqData.put("buyerIdNo", "410523198701054018"); //证件号
+ //reqData.put("buyerName", "张三"); //买家姓名
+ //reqData.put("mobileNum", ""); //手机号
+ //reqData.put("extend", ""); //备用
+
+
+ reqBean.setReqData(reqData);
+ String req = JSONObject.toJSONString(reqBean);
+ //请求数据json字符串示例
+ //String req= "{\"orgId\":\"26680846\",\"reqData\":{\"ordNo\":\"35081904944040745\",\"payType\":\"WECHAT\",\"customerIp\":\"\",\"subject\":\"聚合支付测试\",\"amt\":\"0.01\",\"payWay\":\"02\",\"tradeSource\":\"02\",\"userId\":\"2088101117955611\",\"trmIp\":\"172.16.2.1\",\"mno\":\"399190910000387\"},\"reqId\":\"08794fa24dcb467992a06b126e542be4\",\"signType\":\"RSA\",\"timestamp\":\"2020-04-03 17:51:34\",\"version\":\"1.0\"}";
+ System.out.println("req:" + req);
+ //此处不要改变reqData里面值的顺序用LinkedHashMap
+ HashMap reqMap = JSON.parseObject(req, LinkedHashMap.class, Feature.OrderedField);
+ //组装加密串
+ String signContent = RSASignature.getOrderContent(reqMap);
+ System.out.println("拼接后的参数:" + signContent);
+ //sign
+ String sign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey));
+ System.out.println("============签名:" + sign);
+ reqMap.put("sign", sign);
+
+ String reqStr = JSON.toJSONString(reqMap);
+ System.out.println("请求参数:" + reqMap);
+ System.out.println("请求参数:" + reqStr);
+ String url = "https://openapi.tianquetech.com/order/reverseScan";
+
+ String resultJson = HttpUtils.connectPostUrl(url, reqStr);
+ log.info("返回信息" + resultJson);
+ System.out.println("返回信息:" + resultJson);
+ //不要对reqData排序 所以用LinkedHashMap
+ HashMap result = JSON.parseObject(resultJson, LinkedHashMap.class, Feature.OrderedField);
+ if ("0000".equals(result.get("code"))) {
+ //验签
+ String signResult = result.get("sign").toString();
+ result.remove("sign");
+ String resultStr = RSASignature.getOrderContent(result);
+ System.out.println(resultStr);
+ //sign
+ String resultSign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey));
+ System.out.println("resultSign:" + resultSign);
+ //组装加密串
+ if (RSASignature.doCheck(resultStr, signResult, sxfPublic)) {
+ log.info("===================验签成功==============");
+
+ String respDataStr = result.get("respData").toString();
+ HashMap respData = JSON.parseObject(respDataStr, LinkedHashMap.class);
+ // 获取 tranSts 和 ordNo
+ String tranSts = Optional.ofNullable(respData)
+ .map(m -> m.get("tranSts"))
+ .map(Object::toString)
+ .orElse("");
+
+ String ordNo = Optional.ofNullable(respData)
+ .map(m -> m.get("ordNo"))
+ .map(Object::toString)
+ .orElse("");
+ //被扫免密同步返回支付结果,不推送异步通知。 同步去修改支付状态,扣减库存
+ //被扫输密,推送异步通知。 通过回调接口修改支付状态,扣减库存
+ if ("SUCCESS".equals(tranSts)) {
+ // 4. 查询订单(加行锁)
+ Order order = orderMapper.selectForUpdateByOrderNo(ordNo);
+ if (order == null) {
+ log.error("订单不存在, orderNo:{}", ordNo);
+ throw new ServiceException("订单不存在");
+ }
+
+ // 5. 检查订单状态
+ if (order.getPayStatus() == 2) {
+ log.info("订单已支付, orderNo:{}", ordNo);
+ throw new ServiceException("订单已支付");
+ }
+
+ if (order.getPayStatus() != 0) {
+ log.error("订单状态错误, orderNo:{}, status:{}",
+ ordNo, order.getPayStatus());
+ throw new ServiceException("订单状态错误");
+ }
+
+ // 7. 查询订单明细
+ List items = orderMapper.selectByOrderId(order.getId());
+
+ // 8. 扣减实际库存
+ for (OrderItem item : items) {
+ int result1 = productMapper.reduceStock(
+ order.getStoreId(),
+ item.getProductId(),
+ item.getQuantity()
+ );
+ if (result1 <= 0) {
+ log.error("库存扣减失败, storeId:{}, productId:{}, quantity:{}",
+ order.getStoreId(), item.getProductId(), item.getQuantity());
+ throw new ServiceException("库存扣减失败");
+ }
+ }
+
+ // 9. 更新订单状态为已支付
+ Order updateOrder = new Order();
+ updateOrder.setId(order.getId());
+ updateOrder.setPayStatus(2);
+ updateOrder.setPayTime(new Date());
+
+ int updated = orderMapper.updateById(updateOrder);
+ if (updated <= 0) {
+ log.error("订单状态更新失败, orderNo:{}",ordNo);
+ throw new ServiceException("订单状态更新失败");
+ }
+ log.info("支付同步处理成功, orderNo:{}, storeId:{}",
+ ordNo, order.getStoreId());
+ }
+ System.out.println("===================验签成功==============");
+ }
+ log.info("===================被扫支付==============" + resultJson);
+ return resultJson;
+ }
+ throw new ServiceException(resultJson);
+ }
+
+ @Override
+ public AjaxResult tradeQuery(TradeQueryDTO tradeQueryDTO) throws Exception {
+ //合作方私钥(替换成自己的)
+ String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM35qbWVNdBAU+oai8EVE3iSpA9wHAt4wOs+SufbkE/FNoMgp4EMrFNrdNkaKLQq4cRzqoNlJY+DZSL54dm7ekKZP/SLhWwNi9tJvhKsFpw6SRwthlKyA8lCW2mzFffFqLhGAayNoZTmHwkq8dotvecARVbiFtakzzFy/O7ATmzw3i57yaVgX2RtD8WWA7MGg5CQP1vvIDlXqd40SQcrJY6GNNv07Brv3fpyW2nXbvrxbOx+87NnG77/R7o8eFcWJF+yrqE1xeN3hDvHBFMFyf1M39Zig2I75vKRvHGqftAZuvlDG0nInwcSiMTE7wDsXvYzRnOsgJNVET7aFhzOvFAgMBAAECggEATjB4nRl2S2Whasnf0Ab77CoZSjn7HUXv2hymBkJNPq3eV0Hh5Pjjqmi7hIs0cdm9AWXd6RzySKpScQNjYQFxEOIdbayfTdzD24L2ivT6ZBca09Z9J/9Rik0wJ4ULTtny5JRc6VwGgOn9Twdsde8V4sr1nHUvpMaRH6WVxW8mX7+udgbK0Uxm5IFROeKGz0DSuG8SBpDhD2EB6P2Q+1fpna17yP1R08HtZNU6lQx7yMesaJuvKkwf4YiSjIhUtdbUXGhmbG5LFzoj3oRs25W68bIOQjT9i196hPQOYj6C7yz72nuY2rwFHhtsocfQRULWOHtsnWts9epvuFruDTdq8QKBgQD8bZhQkyQm6VTP0Hmv6sV9SXXKH0rb2V8hml7DxcpFl+l2/9UesH3qMoFKZ5C1YdPKZBNlD7fp5RxjtJ8MdwIp8MxRgMf/bZpIOx1Lfum1H+dKh5LF+oC4GGtJ5jqnwozNPog4keUn+bqKhiza9snA+6jy446A1Y8GGWlZwUGkVwKBgQDPxb7FZmOcotNnh8mS5pcx6d22UhBHIHz/X/dcjNQuI8tIRaIvkJm/4lfuxejCz3LY9k+VSQ96ZM2L5OqZGDlR7aMXQ3/FhuS4PEkNcDvccQPoig7U5zUUn2Jdqom6Lngh7Ka8B1v24ght0yXHHNChmMg/DJ42EzCnjpydWs6/QwKBgA9qGd0BvzlpEjbGgkfNzFWEQN8g3g9izL5ekN7fmyR4zFbp9He1S1sbzm1euaV53dcEGXMYbKCpzvv/sZ6vPcCV5cQsWwosBTnX8kgD7f2Tfyo78SiJzYZwZ0zR9E7+QF7gLK1Xq2ivhUakPuT4IQXZ2E1MvAz9/Yff0WEbvghtAoGAejFzC8c2yDUenaHHU+TXgNxor0Q+HIan3M0EvmJ4mxYkBMInK1AgjDBCxMOSK5gzlBPwI/0O5E2KcT7VFeqgM2XN5+2jpHi75PpXgFbEbdXtlYI0TNQZbKJ8CFg2nc+ciV8ThDvTwzOV/3kRm7N/o7ol8qaqGWVZ1QFTbFuugd8CgYBAsNVKCEHGZAwS5Csv/IBopcrJOJknmEHeAT+VwxtZK7yOJ4ZSNizzofu847YGO4uEhfcaC8lYxnj8XskuMG2KChbbxEA+qlnL/oMIwVrpE0ZbFmQ4WRTUfVQT+zCHMPuuuj5tLPTSMjQLGlq+r3XRzwUaX98J9GLKgHG7tXlphw==";
+ //随行付公钥
+ String sxfPublic = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjo1+KBcvwDSIo+nMYLeOJ19Ju4ii0xH66ZxFd869EWFWk/EJa3xIA2+4qGf/Ic7m7zi/NHuCnfUtUDmUdP0JfaZiYwn+1Ek7tYAOc1+1GxhzcexSJLyJlR2JLMfEM+rZooW4Ei7q3a8jdTWUNoak/bVPXnLEVLrbIguXABERQ0Ze0X9Fs0y/zkQFg8UjxUN88g2CRfMC6LldHm7UBo+d+WlpOYH7u0OTzoLLiP/04N1cfTgjjtqTBI7qkOGxYs6aBZHG1DJ6WdP+5w+ho91sBTVajsCxAaMoExWQM2ipf/1qGdsWmkZScPflBqg7m0olOD87ymAVP/3Tcbvi34bDfwIDAQAB";
+
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
+ String dataStr = df.format(new Date());
+
+ ApiRequestBean reqBean = new ApiRequestBean();
+
+ reqBean.setOrgId("57594783"); //机构id
+ reqBean.setReqId(UUID.randomUUID().toString().replaceAll("-", ""));
+ reqBean.setSignType("RSA");
+ reqBean.setTimestamp(dataStr);
+ reqBean.setVersion("1.0");
+ JSONObject reqData = new JSONObject();
+ //业务参数
+ reqData.put("ordNo", tradeQueryDTO.getOrdNo()); //商户订单号
+ reqData.put("mno", "399260311280126"); //商户编号
+ reqData.put("trmIp", "172.16.2.1");
+ reqBean.setReqData(reqData);
+ String req = JSONObject.toJSONString(reqBean);
+ //此处不要改变reqData里面值的顺序用LinkedHashMap
+ HashMap reqMap = JSON.parseObject(req, LinkedHashMap.class, Feature.OrderedField);
+ //组装加密串
+ String signContent = RSASignature.getOrderContent(reqMap);
+ //sign
+ String sign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey));
+ reqMap.put("sign", sign);
+
+ String reqStr = JSON.toJSONString(reqMap);
+ String url = "https://openapi.tianquetech.com/query/tradeQuery";
+ String resultJson = HttpUtils.connectPostUrl(url, reqStr);
+ //不要对reqData排序 所以用LinkedHashMap
+ HashMap result = JSON.parseObject(resultJson, LinkedHashMap.class, Feature.OrderedField);
+ if ("0000".equals(result.get("code"))) {
+ //验签
+ String signResult = result.get("sign").toString();
+ result.remove("sign");
+ String resultStr = RSASignature.getOrderContent(result);
+ System.out.println(resultStr);
+ //sign
+ String resultSign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey));
+ System.out.println("resultSign:" + resultSign);
+ //组装加密串
+ if (RSASignature.doCheck(resultStr, signResult, sxfPublic)) {
+ log.info("===================验签成功==============");
+ PaymentResponseVO paymentResponseVO = JSON.parseObject(resultJson, PaymentResponseVO.class);
+ return AjaxResult.success(paymentResponseVO);
+ }
+ }
+ return AjaxResult.error(resultJson);
+ }
+
+ @Override
+ public OrderCountVO getOrderCount(String storeId) {
+ return orderMapper.getOrderCount(storeId);
+ }
+
+
+ //调用支付
+ public String pay(String orderNo, String totalAmount) throws Exception {
+ //合作方私钥(替换成自己的)
+ String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDM35qbWVNdBAU+oai8EVE3iSpA9wHAt4wOs+SufbkE/FNoMgp4EMrFNrdNkaKLQq4cRzqoNlJY+DZSL54dm7ekKZP/SLhWwNi9tJvhKsFpw6SRwthlKyA8lCW2mzFffFqLhGAayNoZTmHwkq8dotvecARVbiFtakzzFy/O7ATmzw3i57yaVgX2RtD8WWA7MGg5CQP1vvIDlXqd40SQcrJY6GNNv07Brv3fpyW2nXbvrxbOx+87NnG77/R7o8eFcWJF+yrqE1xeN3hDvHBFMFyf1M39Zig2I75vKRvHGqftAZuvlDG0nInwcSiMTE7wDsXvYzRnOsgJNVET7aFhzOvFAgMBAAECggEATjB4nRl2S2Whasnf0Ab77CoZSjn7HUXv2hymBkJNPq3eV0Hh5Pjjqmi7hIs0cdm9AWXd6RzySKpScQNjYQFxEOIdbayfTdzD24L2ivT6ZBca09Z9J/9Rik0wJ4ULTtny5JRc6VwGgOn9Twdsde8V4sr1nHUvpMaRH6WVxW8mX7+udgbK0Uxm5IFROeKGz0DSuG8SBpDhD2EB6P2Q+1fpna17yP1R08HtZNU6lQx7yMesaJuvKkwf4YiSjIhUtdbUXGhmbG5LFzoj3oRs25W68bIOQjT9i196hPQOYj6C7yz72nuY2rwFHhtsocfQRULWOHtsnWts9epvuFruDTdq8QKBgQD8bZhQkyQm6VTP0Hmv6sV9SXXKH0rb2V8hml7DxcpFl+l2/9UesH3qMoFKZ5C1YdPKZBNlD7fp5RxjtJ8MdwIp8MxRgMf/bZpIOx1Lfum1H+dKh5LF+oC4GGtJ5jqnwozNPog4keUn+bqKhiza9snA+6jy446A1Y8GGWlZwUGkVwKBgQDPxb7FZmOcotNnh8mS5pcx6d22UhBHIHz/X/dcjNQuI8tIRaIvkJm/4lfuxejCz3LY9k+VSQ96ZM2L5OqZGDlR7aMXQ3/FhuS4PEkNcDvccQPoig7U5zUUn2Jdqom6Lngh7Ka8B1v24ght0yXHHNChmMg/DJ42EzCnjpydWs6/QwKBgA9qGd0BvzlpEjbGgkfNzFWEQN8g3g9izL5ekN7fmyR4zFbp9He1S1sbzm1euaV53dcEGXMYbKCpzvv/sZ6vPcCV5cQsWwosBTnX8kgD7f2Tfyo78SiJzYZwZ0zR9E7+QF7gLK1Xq2ivhUakPuT4IQXZ2E1MvAz9/Yff0WEbvghtAoGAejFzC8c2yDUenaHHU+TXgNxor0Q+HIan3M0EvmJ4mxYkBMInK1AgjDBCxMOSK5gzlBPwI/0O5E2KcT7VFeqgM2XN5+2jpHi75PpXgFbEbdXtlYI0TNQZbKJ8CFg2nc+ciV8ThDvTwzOV/3kRm7N/o7ol8qaqGWVZ1QFTbFuugd8CgYBAsNVKCEHGZAwS5Csv/IBopcrJOJknmEHeAT+VwxtZK7yOJ4ZSNizzofu847YGO4uEhfcaC8lYxnj8XskuMG2KChbbxEA+qlnL/oMIwVrpE0ZbFmQ4WRTUfVQT+zCHMPuuuj5tLPTSMjQLGlq+r3XRzwUaX98J9GLKgHG7tXlphw==";
+ //随行付公钥
+ String sxfPublic = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjo1+KBcvwDSIo+nMYLeOJ19Ju4ii0xH66ZxFd869EWFWk/EJa3xIA2+4qGf/Ic7m7zi/NHuCnfUtUDmUdP0JfaZiYwn+1Ek7tYAOc1+1GxhzcexSJLyJlR2JLMfEM+rZooW4Ei7q3a8jdTWUNoak/bVPXnLEVLrbIguXABERQ0Ze0X9Fs0y/zkQFg8UjxUN88g2CRfMC6LldHm7UBo+d+WlpOYH7u0OTzoLLiP/04N1cfTgjjtqTBI7qkOGxYs6aBZHG1DJ6WdP+5w+ho91sBTVajsCxAaMoExWQM2ipf/1qGdsWmkZScPflBqg7m0olOD87ymAVP/3Tcbvi34bDfwIDAQAB";
+
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
+ String dataStr = df.format(new Date());
+
+ ApiRequestBean reqBean = new ApiRequestBean();
+
+ reqBean.setOrgId("57594783"); //机构id
+ reqBean.setReqId(UUID.randomUUID().toString().replaceAll("-", ""));
+ reqBean.setSignType("RSA");
+ reqBean.setTimestamp(dataStr);
+ reqBean.setVersion("1.0");
+ JSONObject reqData = new JSONObject();
+
+ //业务参数
+ reqData.put("ordNo", orderNo); //商户订单号
+ reqData.put("mno", "399260311280126"); //商户编号
+ //reqData.put("subMechId", ""); //子商户号
+ reqData.put("amt", totalAmount); //订单总金额
+ //reqData.put("discountAmt", ""); //参与优惠金额
+ //reqData.put("unDiscountAmt", ""); //不参与优惠金额
+ // TODO: 2026/3/11 后续前端传
+ reqData.put("payType", "WECHAT"); //支付渠道
+ // TODO: 2026/3/11 后续前端传
+ reqData.put("payWay", "02"); //支付方式 02 公众号/服 务窗/js支付 03 小程序
+ //reqData.put("timeExpire", "10"); //订单失效时间, 以分钟为单位
+ //reqData.put("limitPay", "00"); //限制卡类型: 00-全部 01-限定不能使 用信用卡支付 默认值 00
+ reqData.put("subject", "聚合支付测试"); //订单标题
+ //reqData.put("hbFqNum", "6"); //花呗分期数,仅可上送 6 或 12
+ //reqData.put("hbFqPercent", "0"); //卖家承担分期 服务费比例,仅支持上送 0 或 100
+ // TODO: 2026/3/11 后续前端传
+ reqData.put("tradeSource", "02"); //交易来源 01服务商,02收银台,03硬件
+ reqData.put("trmIp", "172.16.2.1");
+ reqData.put("customerIp", ""); //持卡人ip地址,银联js支付时必传
+ // TODO: 2026/3/11 后续前端传code,后端去获取对应的id
+ reqData.put("userId", "o3LXk25WUnC4U2lGeuEvLLu0fjBs"); //用户号 微信:openid; 支付宝:userid;银联:userid;微信&支付宝必传,银联js为非必传
+ //reqData.put("subAppid", "wx24210004370ec43b"); //微信子公众号
+ //reqData.put("outFrontUrl", ""); //js 支付,前台 成功通知地址
+ //reqData.put("outFrontFailUrl", ""); //js 支付,前台 事变通知地址
+ // TODO: 2026/3/11 后端回调地址
+ reqData.put("notifyUrl", "http://193.112.94.36:8081/mall/order/notify"); //回调地址
+ //reqData.put("needReceipt", "00"); //电子发票功能 微信开具电子 发票使用
+ //reqData.put("ledgerAccountFlag", "00"); //是否做分账 分账交易使 用;00:做; 01:不做;不传默认为不做分账
+ //reqData.put("ledgerAccountEffectTime", "00"); //分账有效时间 单位为天;是 否做分账选择 00 时该字段必传
+ //reqData.put("ylTrmNo", ""); //银联终端号
+ //reqData.put("terminalId", ""); //TQ机具编号
+ //reqData.put("identityFlag", ""); //是否是实名支付
+ //reqData.put("buyerIdType", "IDCARD"); //证件类型
+ //reqData.put("buyerIdNo", "410523198701054018"); //证件号
+ //reqData.put("buyerName", "张三"); //买家姓名
+ //reqData.put("mobileNum", ""); //手机号
+ //reqData.put("extend", ""); //备用
+ //reqData.put("goodsTag", "00"); //订单优惠标识 00:是,01: 否
+
+ //reqData.put("couponDetail", ""); //优惠详情信息,见下面三个字段
+ //reqData.put("costPrice", "200"); //订单原价保留两 位小数;微信 独有
+ //reqData.put("receiptId ", "123456789"); //商品小票
+
+ //reqData.put("goodsDetail", "123456789"); //单品优惠信息使用 json 数组格式提交
+ //reqData.put("goodsId", "200"); //商品编码
+ //reqData.put("thirdGoodsId", "12345678"); //微信/支付宝侧商品码
+ //reqData.put("goodsName", "苹果电脑"); //商品名称
+ //reqData.put("quantity", "1"); //商品数量
+ //reqData.put("price", "1.01"); //商品单价
+ //reqData.put("goodsCategory", ""); //商品类目;支 付宝独有
+ //reqData.put("categoriesTree", "124868003|126232002|126252004"); //商品类目树
+ //reqData.put("goodsDesc", ""); //商品描述;支 付宝独有
+ //reqData.put("showUrl", ""); //商品展示地址 url;支付宝独有
+
+ reqBean.setReqData(reqData);
+ String req = JSONObject.toJSONString(reqBean);
+ //请求数据json字符串示例
+ //String req= "{\"orgId\":\"26680846\",\"reqData\":{\"ordNo\":\"35081904944040745\",\"payType\":\"WECHAT\",\"customerIp\":\"\",\"subject\":\"聚合支付测试\",\"amt\":\"0.01\",\"payWay\":\"02\",\"tradeSource\":\"02\",\"userId\":\"2088101117955611\",\"trmIp\":\"172.16.2.1\",\"mno\":\"399190910000387\"},\"reqId\":\"08794fa24dcb467992a06b126e542be4\",\"signType\":\"RSA\",\"timestamp\":\"2020-04-03 17:51:34\",\"version\":\"1.0\"}";
+ System.out.println("req:" + req);
+ //此处不要改变reqData里面值的顺序用LinkedHashMap
+ HashMap reqMap = JSON.parseObject(req, LinkedHashMap.class, Feature.OrderedField);
+ //组装加密串
+ String signContent = RSASignature.getOrderContent(reqMap);
+ System.out.println("拼接后的参数:" + signContent);
+ //sign
+ String sign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey));
+ System.out.println("============签名:" + sign);
+ reqMap.put("sign", sign);
+
+ String reqStr = JSON.toJSONString(reqMap);
+ System.out.println("请求参数:" + reqMap);
+ System.out.println("请求参数:" + reqStr);
+ String url = "https://openapi.tianquetech.com/order/jsapiScan";
+
+ String resultJson = HttpUtils.connectPostUrl(url, reqStr);
+ log.info("返回信息" + resultJson);
+ System.out.println("返回信息:" + resultJson);
+ //不要对reqData排序 所以用LinkedHashMap
+ HashMap result = JSON.parseObject(resultJson, LinkedHashMap.class, Feature.OrderedField);
+ if ("0000".equals(result.get("code"))) {
+ //验签
+ String signResult = result.get("sign").toString();
+ result.remove("sign");
+ String resultStr = RSASignature.getOrderContent(result);
+ System.out.println(resultStr);
+ //sign
+ String resultSign = RSASignature.encryptBASE64(RSASignature.sign(signContent, privateKey));
+ System.out.println("resultSign:" + resultSign);
+ //组装加密串
+ if (RSASignature.doCheck(resultStr, signResult, sxfPublic)) {
+ log.info("===================验签成功==============");
+
+ System.out.println("===================验签成功==============");
+ }
+ return resultJson;
+ }
+ throw new ServiceException(resultJson);
+ }
+
+ /**
+ * 生成锁key(包含门店ID)
+ */
+ private String generateLockKey(Integer storeId, List items) {
+ // 对商品ID排序
+ String productIds = items.stream()
+ .map(item -> String.valueOf(item.getProductId()))
+ .sorted()
+ .collect(Collectors.joining("_"));
+
+ // 组合门店ID和商品ID
+ String originalKey = storeId + ":" + productIds;
+
+ // 计算MD5避免key过长
+ String hash = DigestUtils.md5DigestAsHex(originalKey.getBytes());
+ return "lock:store:products:" + hash;
+ }
+
+ /**
+ * 生成订单号
+ */
+ private String generateOrderNo() {
+ // 格式:年月日时分秒 + 雪花算法ID后8位
+ String dateStr = LocalDateTime.now()
+ .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
+ long snowflakeId = IdUtil.getSnowflake().nextId();
+ String suffix = String.valueOf(snowflakeId).substring(8);
+ return dateStr + suffix;
+ }
}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/ProductServiceImpl.java b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/ProductServiceImpl.java
index 39da8c9c..8a8163a7 100644
--- a/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/ProductServiceImpl.java
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/service/impl/ProductServiceImpl.java
@@ -4,9 +4,11 @@ package com.ruoyi.web.service.impl;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.web.domain.Product;
+import com.ruoyi.web.dto.BatchBarcodeQueryDTO;
import com.ruoyi.web.dto.BatchStoreDTO;
import com.ruoyi.web.mapper.ProductMapper;
import com.ruoyi.web.service.ProductService;
+import com.ruoyi.web.vo.ProductVo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -79,4 +81,9 @@ public class ProductServiceImpl implements ProductService {
}
return productMapper.batchProductById(dto.getIds());
}
+
+ @Override
+ public List getByBarcodes(BatchBarcodeQueryDTO dto) {
+ return productMapper.getByBarcodes(dto);
+ }
}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/utils/HttpUtils.java b/ruoyi-mall/src/main/java/com/ruoyi/web/utils/HttpUtils.java
new file mode 100644
index 00000000..b7843cf1
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/utils/HttpUtils.java
@@ -0,0 +1,144 @@
+package com.ruoyi.web.utils;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLContextBuilder;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+public class HttpUtils {
+
+ /**
+ * 创建没有证书的SSL链接工厂类
+ * @return
+ * @throws NoSuchAlgorithmException
+ * @throws KeyStoreException
+ * @throws KeyManagementException
+ */
+ public static SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws NoSuchAlgorithmException,
+ KeyStoreException, KeyManagementException {
+ SSLContextBuilder context = new SSLContextBuilder().useProtocol("TLSv1.2");
+ context.loadTrustMaterial(null, new TrustStrategy() {
+ @Override
+ public boolean isTrusted(X509Certificate[] arg0, String arg1)
+ throws CertificateException {
+ return true;
+ }
+ });
+ return new SSLConnectionSocketFactory(context.build());
+ }
+
+ /**
+ * 链接GET请求
+ * @param url
+ * @return
+ * @throws KeyManagementException
+ * @throws NoSuchAlgorithmException
+ * @throws KeyStoreException
+ * @throws ClientProtocolException
+ * @throws IOException
+ */
+ public static String connectGetUrl(String url) throws KeyManagementException,
+ NoSuchAlgorithmException, KeyStoreException, ClientProtocolException, IOException {
+ SSLConnectionSocketFactory sslsf = getSSLConnectionSocketFactory();
+ CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build();
+ RequestConfig config = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(5000).build();
+ HttpGet httpGet = null;
+ CloseableHttpResponse resp = null;
+ String jsonString = "";
+ try {
+ httpGet = new HttpGet(url);
+ httpGet.setConfig(config);
+ resp = client.execute(httpGet);
+ HttpEntity entity = resp.getEntity();
+ jsonString = EntityUtils.toString(entity, "UTF-8");
+ } finally {
+ if (resp != null) {
+ try {
+ resp.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (httpGet != null) {
+ httpGet.releaseConnection();
+ }
+ }
+ return jsonString;
+ }
+
+ /**
+ * 链接POST请求
+ * @param url
+ * @param jsonParm
+ * @return
+ * @throws KeyManagementException
+ * @throws NoSuchAlgorithmException
+ * @throws KeyStoreException
+ * @throws ClientProtocolException
+ * @throws IOException
+ */
+ public static String connectPostUrl(String url, String jsonParm) throws KeyManagementException,
+ NoSuchAlgorithmException, KeyStoreException, ClientProtocolException, IOException {
+ SSLConnectionSocketFactory sslsf = getSSLConnectionSocketFactory();
+ CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build();
+ RequestConfig config = RequestConfig.custom().setSocketTimeout(40000).setConnectTimeout(10000).build();
+ HttpPost httpPost = null;
+ CloseableHttpResponse resp = null;
+ try {
+ httpPost = new HttpPost(url);
+ httpPost.setConfig(config);
+ StringEntity params = new StringEntity(jsonParm,"UTF-8");
+ params.setContentType("application/json");
+ httpPost.setEntity(params);
+ resp = client.execute(httpPost);
+ HttpEntity entity = resp.getEntity();
+ String jsonString = EntityUtils.toString(entity, "UTF-8");
+
+ if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+ return jsonString;
+ }
+ }catch (IOException e) {
+ if (e instanceof org.apache.http.conn.ConnectTimeoutException) {
+ throw new org.apache.http.conn.ConnectTimeoutException("connect timed out");
+ }
+ if (e instanceof java.net.SocketTimeoutException) {
+ throw new java.net.SocketTimeoutException("Read timed out");
+ }
+ throw new IOException("IOException");
+ } finally {
+ if (resp != null) {
+ try {
+ resp.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (httpPost != null) {
+ httpPost.releaseConnection();
+ }
+ try {
+ client.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/utils/RSASignature.java b/ruoyi-mall/src/main/java/com/ruoyi/web/utils/RSASignature.java
new file mode 100644
index 00000000..ac16b84b
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/utils/RSASignature.java
@@ -0,0 +1,105 @@
+package com.ruoyi.web.utils;
+
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.*;
+
+
+/**
+ * RSA签名验签类
+ */
+public class RSASignature {
+
+ /**
+ * 签名算法
+ */
+ public static final String SIGN_ALGORITHMS = "SHA1WithRSA";
+
+ /**
+ * 针对参数进行排序拼装
+ * @param sortedParams
+ * @return
+ */
+ public static String getOrderContent(Map requestParam) {
+ Map sortedParams = new TreeMap();
+ if ((requestParam != null) && (requestParam.size() > 0)) {
+ sortedParams.putAll(requestParam);
+ }
+ StringBuffer content = new StringBuffer();
+ List keys = new ArrayList(sortedParams.keySet());
+ Collections.sort(keys);
+ int index = 0;
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ Object value = sortedParams.get(key);
+ if (key!=null &&!"".equals(key)&& value != null) {
+ content.append((index == 0 ? "" : "&") + key + "=" + value);
+ index++;
+ }
+ }
+ return content.toString();
+ }
+
+ /**
+ * rsa 签名
+ * @param content 待签名内容
+ * @param privateKey 私钥
+ * @return
+ */
+ public static byte[] sign(String content, String privateKey) {
+ try {
+ PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(decryptBASE64(privateKey));
+ KeyFactory keyf = KeyFactory.getInstance("RSA");
+ PrivateKey priKey = keyf.generatePrivate(priPKCS8);
+ java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
+ signature.initSign(priKey);
+ signature.update(content.getBytes("UTF-8"));
+ byte[] signed = signature.sign();
+ return signed;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * RSA验签名检查
+ * @param content 待签名数据
+ * @param sign 签名值
+ * @param publicKey 分配给开发商公钥
+ * @return 布尔值
+ */
+ public static boolean doCheck(String content, String sign, String publicKey) {
+ try {
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ byte[] encodedKey = decryptBASE64(publicKey);
+ PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
+
+
+ java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
+
+ signature.initVerify(pubKey);
+ signature.update(content.getBytes("UTF-8"));
+
+ boolean bverify = signature.verify(decryptBASE64(sign));
+ return bverify;
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public static String encryptBASE64(byte[] key) throws Exception {
+ String base64encodedString = Base64.getEncoder().encodeToString(key);
+ return base64encodedString.replaceAll("[\\s*\t\n\r]", "");
+ }
+ public static byte[] decryptBASE64(String key) throws Exception {
+ byte[] base64decodedBytes = Base64.getDecoder().decode(key.replaceAll("[\\s*\t\n\r]", ""));
+ return base64decodedBytes;
+ }
+
+}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderCountVO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderCountVO.java
new file mode 100644
index 00000000..d93070ed
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderCountVO.java
@@ -0,0 +1,13 @@
+package com.ruoyi.web.vo;
+
+import lombok.Data;
+
+@Data
+public class OrderCountVO {
+
+ //支付订单数量
+ private String paidOrderCount;
+
+ //支付订单金额
+ private String totalPayAmount;
+}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderVo.java b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderVo.java
new file mode 100644
index 00000000..cc6e1259
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/OrderVo.java
@@ -0,0 +1,11 @@
+package com.ruoyi.web.vo;
+
+import lombok.Data;
+
+@Data
+public class OrderVo {
+
+ private String orderNo;
+
+ private String amount;
+}
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/vo/PaymentResponseVO.java b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/PaymentResponseVO.java
new file mode 100644
index 00000000..431d5f48
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/PaymentResponseVO.java
@@ -0,0 +1,234 @@
+package com.ruoyi.web.vo;
+
+import lombok.Data;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 支付响应VO
+ */
+@Data
+public class PaymentResponseVO implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 消息
+ */
+ private String msg;
+
+ /**
+ * 业务码
+ */
+ private String code;
+
+ /**
+ * 签名
+ */
+ private String sign;
+
+ /**
+ * 签名类型
+ */
+ private String signType;
+
+ /**
+ * 机构ID
+ */
+ private String orgId;
+
+ /**
+ * 请求ID
+ */
+ private String reqId;
+
+ /**
+ * 响应数据
+ */
+ private RespData respData;
+
+ /**
+ * 响应数据内部类
+ */
+ @Data
+ public static class RespData implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 支付时间 yyyyMMddHHmmss
+ */
+ private String payTime;
+
+ /**
+ * 业务码
+ */
+ private String bizCode;
+
+ /**
+ * 商品标题
+ */
+ private String subject;
+
+ /**
+ * 结算金额
+ */
+ private String settleAmt;
+
+ /**
+ * 支付方式 01-微信 02-支付宝
+ */
+ private String payWay;
+
+ /**
+ * 收单机构流水号
+ */
+ private String sxfUuid;
+
+ /**
+ * 支付银行
+ */
+ private String payBank;
+
+ /**
+ * 交易流水号
+ */
+ private String uuid;
+
+ /**
+ * 场景
+ */
+ private String scene;
+
+ /**
+ * 交易时间 yyyyMMddHHmmss
+ */
+ private String tranTime;
+
+ /**
+ * 结算批次号
+ */
+ private String settlementBatchNo;
+
+ /**
+ * 支付类型 WECHAT/ALIPAY
+ */
+ private String payType;
+
+ /**
+ * 订单号
+ */
+ private String ordNo;
+
+ /**
+ * 收单机构标识
+ */
+ private String szltFlag;
+
+ /**
+ * 业务消息
+ */
+ private String bizMsg;
+
+ /**
+ * 手续费金额
+ */
+ private String recFeeAmt;
+
+ /**
+ * 手续费费率
+ */
+ private String recFeeRate;
+
+ /**
+ * 收单机构手续费
+ */
+ private String szltRecfeeAmt;
+
+ /**
+ * 原始交易金额
+ */
+ private String oriTranAmt;
+
+ /**
+ * 渠道ID
+ */
+ private String channelId;
+
+ /**
+ * 完成时间 yyyyMMddHHmmss
+ */
+ private String finishTime;
+
+ /**
+ * 交易状态 SUCCESS-成功
+ */
+ private String tranSts;
+
+ /**
+ * 积分金额
+ */
+ private String pointAmount;
+
+ /**
+ * 记账标识
+ */
+ private String ledgerAccountFlag;
+
+ /**
+ * 交易码
+ */
+ private String tradeCode;
+
+ /**
+ * 清算日期 yyyyMMdd
+ */
+ private String clearDt;
+
+ /**
+ * 第三方交易流水号
+ */
+ private String transactionId;
+
+ /**
+ * 子商户号
+ */
+ private String subMechId;
+
+ /**
+ * 总抵扣金额
+ */
+ private String totalOffstAmt;
+
+ /**
+ * 交易消息
+ */
+ private String tradeMsg;
+
+ /**
+ * 设备类型
+ */
+ private String drType;
+
+ /**
+ * 获取结算金额(转为BigDecimal)
+ */
+ public BigDecimal getSettleAmtDecimal() {
+ return settleAmt != null ? new BigDecimal(settleAmt) : BigDecimal.ZERO;
+ }
+
+ /**
+ * 获取原始交易金额(转为BigDecimal)
+ */
+ public BigDecimal getOriTranAmtDecimal() {
+ return oriTranAmt != null ? new BigDecimal(oriTranAmt) : BigDecimal.ZERO;
+ }
+
+ /**
+ * 判断是否支付成功
+ */
+ public boolean isSuccess() {
+ return "SUCCESS".equals(tranSts) && "0000".equals(bizCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/java/com/ruoyi/web/vo/ProductVo.java b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/ProductVo.java
new file mode 100644
index 00000000..d9b5289b
--- /dev/null
+++ b/ruoyi-mall/src/main/java/com/ruoyi/web/vo/ProductVo.java
@@ -0,0 +1,12 @@
+package com.ruoyi.web.vo;
+
+import lombok.Data;
+
+@Data
+public class ProductVo {
+ private Long id;
+ private String productName;
+ private String storePrice;
+ private String mainImage;
+ private String productBarCode; // 商品条码
+}
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/DeviceMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/DeviceMapper.xml
new file mode 100644
index 00000000..ae783b82
--- /dev/null
+++ b/ruoyi-mall/src/main/resources/mapper/commodity/DeviceMapper.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ insert into mall_device_info (
+ store_id,
+ device_code,
+ device_name,
+ device_type,
+ device_brand,
+ device_model,
+ channel,
+ mac_address,
+ auth_code,
+ install_date,
+ expire_date,
+ device_status,
+ last_heartbeat,
+ remark,
+ create_by,
+ create_time
+ )values(
+ #{storeId},
+ #{deviceCode},
+ #{deviceName},
+ #{deviceType},
+ #{deviceBrand},
+ #{deviceModel},
+ #{channel},
+ #{macAddress},
+ #{authCode},
+ #{installDate},
+ #{expireDate},
+ #{deviceStatus},
+ #{lastHeartbeat},
+ #{remark},
+ #{createBy},
+ sysdate()
+ )
+
+
+
+
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/DoorScanRecordMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/DoorScanRecordMapper.xml
new file mode 100644
index 00000000..0a659e06
--- /dev/null
+++ b/ruoyi-mall/src/main/resources/mapper/commodity/DoorScanRecordMapper.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ insert into mall_scan_record (
+ store_id,
+ operator_name ,
+ operator_phone ,
+ scan_type ,
+ shop_name ,
+ scan_time ,
+ create_by,
+ create_time
+ )values(
+ #{storeId},
+ #{operatorName},
+ #{operatorPhone},
+ #{scanType},
+ #{shopName},
+ #{scanTime},
+ #{createBy},
+ sysdate()
+ )
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/InstallTaskMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/InstallTaskMapper.xml
new file mode 100644
index 00000000..d90ba813
--- /dev/null
+++ b/ruoyi-mall/src/main/resources/mapper/commodity/InstallTaskMapper.xml
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INSERT INTO mall_install_task (
+ task_no, store_id, store_name, contact_person, contact_phone,
+ device_type, device_model, install_address, expected_time, remark,
+ task_status, del_flag, create_by, create_time
+ ) VALUES (
+ #{taskNo}, #{storeId}, #{storeName}, #{contactPerson}, #{contactPhone},
+ #{deviceType}, #{deviceModel}, #{installAddress}, #{expectedTime}, #{remark},
+ 0, '0', #{createBy}, #{createTime}
+ )
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UPDATE mall_install_task
+
+ contact_person = #{contactPerson},
+ contact_phone = #{contactPhone},
+ device_type = #{deviceType},
+ device_model = #{deviceModel},
+ install_address = #{installAddress},
+ expected_time = #{expectedTime},
+ remark = #{remark},
+ task_status = #{taskStatus},
+ assignee_id = #{assigneeId},
+ assignee_name = #{assigneeName},
+ assign_time = #{assignTime},
+ device_id = #{deviceId},
+ device_code = #{deviceCode},
+ auth_code = #{authCode},
+ install_time = #{installTime},
+ install_photo = #{installPhoto},
+ complete_remark = #{completeRemark},
+ update_by = #{updateBy},
+ update_time = NOW()
+
+ WHERE id = #{id} AND del_flag = '0'
+
+
+
+
+ UPDATE mall_install_task
+
+ task_status = #{taskStatus},
+ assignee_id = #{assigneeId},
+ assignee_name = #{assigneeName},
+ assign_time = #{assignTime},
+ update_by = #{updateBy},
+ update_time = NOW()
+
+ WHERE id = #{id} AND del_flag = '0'
+
+
+
+
+ UPDATE mall_install_task
+
+ task_status = #{taskStatus},
+ device_id = #{deviceId},
+ device_code = #{deviceCode},
+ auth_code = #{authCode},
+ install_time = #{installTime},
+ install_photo = #{installPhoto},
+ complete_remark = #{completeRemark},
+ update_by = #{updateBy},
+ update_time = NOW()
+
+ WHERE id = #{id} AND del_flag = '0'
+
+
+
+
+
+
+
+ UPDATE mall_install_task
+ SET del_flag = '2', update_time = NOW()
+ WHERE id = #{id}
+
+
+
+
+ UPDATE mall_install_task
+ SET del_flag = '2', update_time = NOW()
+ WHERE id IN
+
+ #{id}
+
+
+
+
\ No newline at end of file
diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/OrderMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/OrderMapper.xml
index 2c405cee..bbaf6156 100644
--- a/ruoyi-mall/src/main/resources/mapper/commodity/OrderMapper.xml
+++ b/ruoyi-mall/src/main/resources/mapper/commodity/OrderMapper.xml
@@ -40,41 +40,88 @@
+
+
+ INSERT INTO mall_order (order_no, store_id, pay_amount, pay_status,
+ create_time, create_by)
+ VALUES (#{orderNo}, #{storeId}, #{payAmount}, #{payStatus},
+ NOW(), #{createBy})
+
+
+
+
+ INSERT INTO mall_order_item (
+ order_id,
+ product_id,
+ product_name,
+ product_no,
+ main_image,
+ spec,
+ unit_price,
+ quantity,
+ subtotal,
+ create_time,
+ create_by
+ ) VALUES
+
+ (
+ #{item.orderId},
+ #{item.productId},
+ #{item.productName},
+ #{item.productNo},
+ #{item.mainImage},
+ #{item.spec},
+ #{item.unitPrice},
+ #{item.quantity},
+ #{item.subtotal},
+ NOW(),
+ #{item.createBy}
+ )
+
+
+
+
+
+ UPDATE mall_order
+
+ order_no = #{orderNo},
+ pay_time = #{payTime},
+ pay_status = #{payStatus},
+ update_by = #{updateBy},
+ update_time = NOW()
+
+ WHERE
+ id = #{id}
+ AND del_flag = '0'
+
-
@@ -90,6 +105,28 @@
where id=#{id} and del_flag = '0'
+
+ SELECT * FROM mall_product_store
+ WHERE store_id = #{storeId} and del_flag = 0
+ AND id IN
+
+ #{id}
+
+ FOR UPDATE
+
+
+
+ SELECT * FROM mall_product_store
+ WHERE del_flag = '0'
+
+ AND store_id = #{storeId}
+
+ AND product_bar_code IN
+
+ #{barcode}
+
+
+
insert into mall_product_store (
@@ -134,7 +171,16 @@
sysdate()
)
-
+
+
+ UPDATE mall_product_store
+ SET stock_quantity = stock_quantity + #{quantity},
+ sold_quantity = sold_quantity - #{quantity},
+ update_time = NOW()
+ WHERE store_id = #{storeId}
+ AND id = #{productId}
+ AND del_flag = 0
+
update mall_product_store
@@ -188,6 +234,17 @@
where product_bar_code = #{productBarCode}
+
+ UPDATE mall_product_store
+ SET stock_quantity = stock_quantity - #{quantity}, -- 扣减库存
+ sold_quantity = sold_quantity + #{quantity}, -- 增加销量
+ update_time = NOW() -- 更新时间
+ WHERE store_id = #{storeId}
+ AND id = #{productId}
+ AND stock_quantity >= #{quantity} -- 确保库存充足
+ AND del_flag = 0 -- 未删除
+
+
delete
from sys_config
diff --git a/ruoyi-mall/src/main/resources/mapper/commodity/UserStoreMapper.xml b/ruoyi-mall/src/main/resources/mapper/commodity/UserStoreMapper.xml
index f4458d66..304254a7 100644
--- a/ruoyi-mall/src/main/resources/mapper/commodity/UserStoreMapper.xml
+++ b/ruoyi-mall/src/main/resources/mapper/commodity/UserStoreMapper.xml
@@ -11,6 +11,7 @@
+