diff --git a/hiver-admin/test-output/test-report.html b/hiver-admin/test-output/test-report.html index 163ca56c..80c5a240 100644 --- a/hiver-admin/test-output/test-report.html +++ b/hiver-admin/test-output/test-report.html @@ -35,7 +35,7 @@ Hiver
  • -ÁùÔÂ 22, 2026 17:45:41 +ÁùÔÂ 23, 2026 16:55:30
  • @@ -84,7 +84,7 @@

    passTest

    -

    17:45:43 ÏÂÎç / 0.031 secs

    +

    16:55:31 ÏÂÎç / 0.032 secs

    @@ -92,9 +92,9 @@
    #test-id=1
    passTest
    -06.22.2026 17:45:43 -06.22.2026 17:45:43 -0.031 secs +06.23.2026 16:55:31 +06.23.2026 16:55:31 +0.032 secs
    @@ -104,7 +104,7 @@ Pass - 17:45:43 + 16:55:31 Test passed @@ -128,13 +128,13 @@

    Started

    -

    ÁùÔÂ 22, 2026 17:45:41

    +

    ÁùÔÂ 23, 2026 16:55:30

    Ended

    -

    ÁùÔÂ 22, 2026 17:45:43

    +

    ÁùÔÂ 23, 2026 16:55:31

    diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WechatPayController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WechatPayController.java index 4f3eac27..e378bef2 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WechatPayController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WechatPayController.java @@ -30,6 +30,8 @@ import java.io.InputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -110,7 +112,8 @@ public class WechatPayController { } if(description.equals("增加é…é€ä½£é‡‘")){ - outTradeNo = "ORDERDE_" + outTradeNo; + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")); + outTradeNo = "ORDERDE_" + timestamp.substring(timestamp.length() - 2) + outTradeNo ; }else{ outTradeNo = "ORDER_" + outTradeNo; } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderDeliveryFeeRecordMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderDeliveryFeeRecordMapper.java new file mode 100644 index 00000000..16376b40 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/MallOrderDeliveryFeeRecordMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.dao.mapper; + +import cc.hiver.mall.entity.MallOrderDeliveryFeeRecord; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springframework.stereotype.Repository; + +@Repository +public interface MallOrderDeliveryFeeRecordMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrderDeliveryFeeRecord.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrderDeliveryFeeRecord.java new file mode 100644 index 00000000..eee4c548 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallOrderDeliveryFeeRecord.java @@ -0,0 +1,46 @@ +package cc.hiver.mall.entity; + +import cc.hiver.core.common.utils.SnowFlakeUtil; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +@Data +@Entity +@Table(name = "mall_order_delivery_fee_record") +@TableName("mall_order_delivery_fee_record") +@ApiModel(value = "订å•追加é…é€ä½£é‡‘记录表") +public class MallOrderDeliveryFeeRecord implements Serializable { + private static final long serialVersionUID = 1L; + + @Id + @TableId + private String id = SnowFlakeUtil.nextId().toString(); + + @ApiModelProperty(value = "å…³è”æ ¸å¿ƒè®¢å•ID") + private String orderId; + + @ApiModelProperty(value = "微信支付商户订å•å·") + private String wxOrderId; + + @ApiModelProperty(value = "追加é…é€ä½£é‡‘金é¢") + private BigDecimal amount; + + @ApiModelProperty(value = "é€€æ¬¾çŠ¶æ€ 0未退 1已退") + private Integer refundStatus; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "退款时间") + private Date refundTime; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java index 5652332c..e55b37b2 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java @@ -966,6 +966,9 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl goodsList = mallOrderGoodsMapper.selectByOrderId(order.getId()); if (goodsList == null || goodsList.isEmpty()) return; List returnList = goodsList.stream() diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java index 643e80fb..b90008e3 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallOrderServiceImpl.java @@ -146,6 +146,9 @@ public class MallOrderServiceImpl extends ServiceImpl= 1) { @@ -1215,7 +1223,7 @@ public class MallOrderServiceImpl extends ServiceImpl countByShop(String shopId) { Map result = new HashMap(); + //统计ä¸åŒä¸šåŠ¡ç±»åž‹çš„è®¢å•æ•°é‡ List> orderStatusCount = this.baseMapper.selectOrderStatusCount(shopId); result.put("orderStatusCount", orderStatusCount); // 1. èŽ·å–æŒ‡å®šæ—¥æœŸï¼ˆä¾‹å¦‚:今天) @@ -1262,15 +1271,19 @@ public class MallOrderServiceImpl extends ServiceImpl orderRevenueAndCount = this.baseMapper.selectOrderRevenueAndCount(shopId,startTime,endTime); result.put("orderRevenueAndCount", orderRevenueAndCount); + //待处ç†å·®è¯„è®¢å•æ•° Integer pendingBadReviewCount = this.baseMapper.selectPendingBadReviewCount(shopId); result.put("pendingBadReviewCount", pendingBadReviewCount); + //待处ç†é€€æ¬¾/å”®åŽæ•° Integer refundCount = this.baseMapper.selectRefundCount(shopId); result.put("refundCount", refundCount); + //待处ç†å•†å®¶è‡ªé…é€è®¢å•æ•° Integer shopDeliveryPendingCount = this.baseMapper.selectShopDeliveryPendingCount(shopId); result.put("shopDeliveryPendingCount", shopDeliveryPendingCount == null ? 0 : shopDeliveryPendingCount); + //待处ç†ä¸­è½¬é…é€è®¢å•æ•° Integer transferDeliveryPendingCount = this.baseMapper.selectTransferDeliveryPendingCount(shopId); result.put("transferDeliveryPendingCount", transferDeliveryPendingCount == null ? 0 : transferDeliveryPendingCount); return result; @@ -1847,6 +1860,13 @@ public class MallOrderServiceImpl extends ServiceImpl uw = new LambdaUpdateWrapper<>(); + uw.eq(MallOrder::getId, orderId).set(MallOrder::getStatus, status) + .set(MallOrder::getShopMakeTime,new Date()); + this.update(uw); + } + /** * 更新订å•状æ€ä¸ºå•†å®¶æ‹’ç»é€€æ¬¾ */ @@ -2144,7 +2164,7 @@ public class MallOrderServiceImpl extends ServiceImpl goodsList = mallOrderGoodsMapper.selectByOrderId(order.getId()); if (goodsList == null || goodsList.isEmpty()) return; List returnList = goodsList.stream() diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java index 6c88af6a..a531c2e3 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallRefundRecordServiceImpl.java @@ -89,6 +89,9 @@ public class MallRefundRecordServiceImpl extends ServiceImpl item.getRefundAmount()) // æå– BigDecimal é‡‘é¢ .reduce(BigDecimal.ZERO, BigDecimal::add); - wechatPayUtil.refund(mallRefundRecord.getOrderId(), order.getTotalAmount().multiply(new BigDecimal(100)).longValue(), totalRefund.multiply(new BigDecimal(100)).longValue()); + mallOrderDeliveryFeeRefundHelper.refundOrderWithAddedDeliveryFee(order, totalRefund); if(mallRefundRecord.getLinkId().toUpperCase().startsWith("W")){ //é…é€å‘˜åŒæ„é€€æ¬¾ã€‚æ‰£æŽ‰ä½™é¢ Worker worker = workerServiceImpl.findByWorkerId(mallRefundRecord.getLinkId()); @@ -451,7 +454,7 @@ public class MallRefundRecordServiceImpl extends ServiceImpl qw = new LambdaQueryWrapper<>(); + qw.eq(MallOrderDeliveryFeeRecord::getWxOrderId, wxOrderId).last("LIMIT 1"); + return mallOrderDeliveryFeeRecordMapper.selectOne(qw) != null; + } + + public void refundOrder(MallOrder order, BigDecimal refundAmount) { + refundOrder(order, refundAmount, false); + } + + public void refundOrderWithAddedDeliveryFee(MallOrder order, BigDecimal refundAmount) { + refundOrder(order, refundAmount, true); + } + + private void refundOrder(MallOrder order, BigDecimal refundAmount, boolean refundAddedDeliveryFee) { + if (!isDeliveryOrder(order)) { + wechatPayUtil.refund(order.getId(), toCent(order.getTotalAmount()), toCent(refundAmount)); + return; + } + + List records = selectAddedDeliveryFeeRecords(order.getId()); + if (records == null || records.isEmpty()) { + wechatPayUtil.refund(order.getId(), toCent(order.getTotalAmount()), toCent(refundAmount)); + return; + } + + BigDecimal addedTotal = records.stream() + .map(MallOrderDeliveryFeeRecord::getAmount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + BigDecimal mainOrderTotal = subtractNonNegative(order.getTotalAmount(), addedTotal); + BigDecimal mainRefundAmount = refundAddedDeliveryFee + ? subtractNonNegative(refundAmount, addedTotal) + : refundAmount; + + if (mainRefundAmount.compareTo(BigDecimal.ZERO) > 0) { + wechatPayUtil.refund(order.getId(), toCent(mainOrderTotal), toCent(mainRefundAmount)); + } + + if (refundAddedDeliveryFee) { + refundAddedDeliveryFeeRecords(records); + } + } + + private List selectAddedDeliveryFeeRecords(String orderId) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.eq(MallOrderDeliveryFeeRecord::getOrderId, orderId); + return mallOrderDeliveryFeeRecordMapper.selectList(qw); + } + + private void refundAddedDeliveryFeeRecords(List records) { + for (MallOrderDeliveryFeeRecord record : records) { + if (record.getRefundStatus() != null && record.getRefundStatus() == REFUND_STATUS_DONE) { + continue; + } + Boolean success = wechatPayUtil.refundDeliveryFeeRecord(record.getWxOrderId(), toCent(record.getAmount()), toCent(record.getAmount())); + if (Boolean.TRUE.equals(success)) { + LambdaUpdateWrapper uw = new LambdaUpdateWrapper<>(); + uw.eq(MallOrderDeliveryFeeRecord::getId, record.getId()) + .set(MallOrderDeliveryFeeRecord::getRefundStatus, REFUND_STATUS_DONE) + .set(MallOrderDeliveryFeeRecord::getRefundTime, new Date()); + mallOrderDeliveryFeeRecordMapper.update(null, uw); + } + } + } + + private boolean isDeliveryOrder(MallOrder order) { + return order != null && order.getDeliveryType() != null && order.getDeliveryType() == DELIVERY_TYPE_EXPRESS; + } + + private BigDecimal subtractNonNegative(BigDecimal amount, BigDecimal subtractAmount) { + BigDecimal result = safeAmount(amount).subtract(safeAmount(subtractAmount)); + return result.compareTo(BigDecimal.ZERO) > 0 ? result : BigDecimal.ZERO; + } + + private long toCent(BigDecimal amount) { + return safeAmount(amount).multiply(new BigDecimal(100)).longValue(); + } + + private BigDecimal safeAmount(BigDecimal amount) { + return amount == null ? BigDecimal.ZERO : amount; + } +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/WechatPayUtil.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/WechatPayUtil.java index 8268cf99..cfd8848e 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/WechatPayUtil.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/utils/WechatPayUtil.java @@ -28,16 +28,18 @@ public class WechatPayUtil { private final OkHttpClient httpClient = new OkHttpClient(); private final ObjectMapper objectMapper = new ObjectMapper(); public Boolean refund(String orderId,long totalFee,long refundFee) { - try { - // 2. æž„å»ºé€€æ¬¾è¯·æ±‚å‚æ•° - // 原支付交易å·ï¼ˆä¸Žä¸‹å•时一致,带 ORDER_ å‰ç¼€ï¼‰ - String outTradeNo = "ORDER_" + orderId; - // 退款å•å·ï¼ˆå”¯ä¸€ï¼Œä½¿ç”¨ REFUND_ + orderId + 时间戳é¿å…é‡å¤ï¼‰ - String outRefundNo = "REFUND_" + orderId + "_" + System.currentTimeMillis(); + return refundByOutTradeNo("ORDER_" + orderId, "REFUND_" + orderId + "_" + System.currentTimeMillis(), totalFee, refundFee); + } + public Boolean refundDeliveryFeeRecord(String wxOrderId, long totalFee, long refundFee) { + return refundByOutTradeNo(wxOrderId, "REFUND_" + wxOrderId + "_" + System.currentTimeMillis(), totalFee, refundFee); + } + + private Boolean refundByOutTradeNo(String outTradeNo, String outRefundNo, long totalFee, long refundFee) { + try { // 退款原因 String reason = "订å•退款"; - // 3. 构建请求 Body + // 构建请求 Body Map reqBody = new HashMap<>(); reqBody.put("out_trade_no", outTradeNo); reqBody.put("out_refund_no", outRefundNo); @@ -51,11 +53,10 @@ public class WechatPayUtil { String jsonBody = objectMapper.writeValueAsString(reqBody); - // 4. ç­¾å并调用微信退款 API + // ç­¾å并调用微信退款 API String refundUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"; String authorization = signer.sign("POST", refundUrl, jsonBody); - // ã€ä¿®æ”¹ç‚¹ã€‘使用标准的 create æ–¹æ³•ï¼Œå‚æ•°é¡ºåºæ˜¯ (MediaType, String) okhttp3.RequestBody body = okhttp3.RequestBody.create( MediaType.parse("application/json; charset=utf-8"), jsonBody @@ -75,16 +76,16 @@ public class WechatPayUtil { if (response.isSuccessful()) { // 退款申请æˆåŠŸï¼ˆæ³¨æ„:微信退款是异步的,此处åªè¡¨ç¤ºç”³è¯·æäº¤æˆåŠŸï¼‰ - log.info("微信退款申请æˆåŠŸ, orderId={}, outRefundNo={}, response={}", orderId, outRefundNo, responseBody); + log.info("微信退款申请æˆåŠŸ, outTradeNo={}, outRefundNo={}, response={}", outTradeNo, outRefundNo, responseBody); return true; } else { - log.error("微信退款申请失败, orderId={}, httpCode={}, response={}", orderId, response.code(), responseBody); + log.error("微信退款申请失败, outTradeNo={}, httpCode={}, response={}", outTradeNo, response.code(), responseBody); return false; } } } catch (Exception e) { - log.error("微信退款异常, orderId={}", orderId, e); + log.error("微信退款异常, outTradeNo={}", outTradeNo, e); return false; } } diff --git a/hiver-modules/hiver-mall/src/main/resources/db/mall_order_delivery_fee_record.sql b/hiver-modules/hiver-mall/src/main/resources/db/mall_order_delivery_fee_record.sql new file mode 100644 index 00000000..8950797d --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/resources/db/mall_order_delivery_fee_record.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS `mall_order_delivery_fee_record` ( + `id` varchar(64) NOT NULL COMMENT '主键ID', + `order_id` varchar(64) NOT NULL COMMENT 'å…³è”æ ¸å¿ƒè®¢å•ID', + `wx_order_id` varchar(128) NOT NULL COMMENT '微信支付商户订å•å·', + `amount` decimal(10,2) NOT NULL COMMENT '追加é…é€ä½£é‡‘金é¢', + `refund_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'é€€æ¬¾çŠ¶æ€ 0未退 1已退', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `refund_time` datetime DEFAULT NULL COMMENT '退款时间', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`), + UNIQUE KEY `uk_wx_order_id` (`wx_order_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订å•追加é…é€ä½£é‡‘记录表';