瀏覽代碼

Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	nightFragrance-massage/src/main/java/com/ylx/giftCard/service/IGiftCardService.java
jinshihui 1 周之前
父節點
當前提交
4bb4937aa9

+ 6 - 0
nightFragrance-common/pom.xml

@@ -206,6 +206,12 @@
             <artifactId>dysmsapi20170525</artifactId>
             <version>3.1.0</version>
         </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>wx-java-pay-spring-boot-starter</artifactId>
+            <version>4.5.0</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 82 - 0
nightFragrance-common/src/main/java/com/ylx/common/config/WxPayBeanConfig.java

@@ -0,0 +1,82 @@
+package com.ylx.common.config;
+
+import cn.hutool.core.util.StrUtil;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+import java.io.InputStream;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+
+@Slf4j
+@Configuration
+public class WxPayBeanConfig {
+
+    @Autowired
+    private WxPayConfig wxPayConfig;
+    @Autowired
+    private ResourceLoader resourceLoader;
+
+    @Bean
+    public WxPayService wxPayService() throws Exception {
+        // SDK原生配置
+        com.github.binarywang.wxpay.config.WxPayConfig sdkConfig = new com.github.binarywang.wxpay.config.WxPayConfig();
+        sdkConfig.setAppId(wxPayConfig.getAppId());
+        sdkConfig.setMchId(wxPayConfig.getMchId());
+        // v3密钥
+        sdkConfig.setApiV3Key(wxPayConfig.getMchKey());
+
+        if (StrUtil.isBlank(wxPayConfig.getCertKeyPath())) {
+            log.warn("wx.cert-key-path未配置,跳过初始化微信支付服务");
+            return null;
+        }
+
+        // 读取私钥文件 apiclient_key.pem
+        Resource keyRes = resourceLoader.getResource(wxPayConfig.getCertKeyPath());
+        if(keyRes.exists()){
+            try (InputStream is = keyRes.getInputStream()) {
+                PrivateKey privateKey = loadPrivateKey(is);
+                sdkConfig.setPrivateKey(privateKey);
+                log.info("商户私钥文件加载成功:{}",wxPayConfig.getCertKeyPath());
+            } catch (Exception e) {
+                log.error("私钥文件存在但读取失败:{}",wxPayConfig.getCertKeyPath(),e);
+            }
+        }else{
+            // 文件不存在,只打日志,不中断启动
+            log.warn("商户私钥文件【{}】不存在,微信支付功能禁用",wxPayConfig.getCertKeyPath());
+        }
+
+        WxPayService payService = new WxPayServiceImpl();
+        payService.setConfig(sdkConfig);
+        return payService;
+    }
+
+    /** 从pem输入流读取RSA私钥 */
+    private PrivateKey loadPrivateKey(InputStream inputStream) throws Exception {
+        try {
+            // JDK8不能用inputStream.readAllBytes(),替换成IOUtils
+            byte[] bytes = IOUtils.toByteArray(inputStream);
+            String pem = new String(bytes)
+                    .replace("-----BEGIN PRIVATE KEY-----", "")
+                    .replace("-----END PRIVATE KEY-----", "")
+                    .replaceAll("\\s+", ""); // \s+ 清除所有换行、空格
+
+            byte[] decode = Base64.getDecoder().decode(pem);
+            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decode);
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            return kf.generatePrivate(spec);
+        } finally {
+            // 关闭流,防止资源泄漏
+            IOUtils.closeQuietly(inputStream);
+        }
+    }
+}

+ 18 - 0
nightFragrance-common/src/main/java/com/ylx/common/weixinPay/enums/WxPayTypeEnum.java

@@ -0,0 +1,18 @@
+package com.ylx.common.weixinPay.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum WxPayTypeEnum {
+
+    GIFT_CARD("GIFT_CARD", "购物卡"),
+    EMOTION_GOODS("EMOTION_GOODS", "情感服务商品");
+
+    private final String code;
+    private final String desc;
+
+    WxPayTypeEnum(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+}

+ 90 - 0
nightFragrance-common/src/main/java/com/ylx/common/weixinPay/service/WxPayV3Service.java

@@ -0,0 +1,90 @@
+package com.ylx.common.weixinPay.service;
+
+import cn.hutool.core.util.StrUtil;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.ylx.common.exception.ServiceException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class WxPayV3Service {
+
+    @Resource
+    private WxPayService wxPayService;
+
+    /**
+     * V3 JSAPI下单 公众号/小程序
+     */
+    public Map<String, Object> createV3JsapiOrder(String orderNo, BigDecimal amount, String body, String openId, String attach) {
+        try {
+            // 1. 获取支付配置
+            WxPayConfig payConfig = wxPayService.getConfig();
+
+            // 2. 组装支付请求
+            WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
+            request.setOutTradeNo(orderNo);
+            request.setDescription(body);
+            request.setNotifyUrl(payConfig.getNotifyUrl());
+
+            // 设置 attach 字段
+            if (StrUtil.isNotBlank(attach)) {
+                request.setAttach(attach);
+            }
+
+            // 金额转换(元转分,精度校验)
+            int total = amount.multiply(new BigDecimal(100))
+                    .setScale(0, RoundingMode.HALF_UP).intValue();
+            WxPayUnifiedOrderV3Request.Amount amountObj = new WxPayUnifiedOrderV3Request.Amount();
+            amountObj.setTotal(total);
+            request.setAmount(amountObj);
+
+            // 付款人信息
+            WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
+            payer.setOpenid(openId);
+            request.setPayer(payer);
+
+            // 3. 发起支付
+            WxPayUnifiedOrderV3Result result = wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
+            log.info("微信支付预支付成功,prepayId: {}", result.getPrepayId());
+
+            // 4. 生成前端JSAPI支付参数
+            WxPayUnifiedOrderV3Result.JsapiResult jsapi = result.getPayInfo(
+                    TradeTypeEnum.JSAPI,
+                    payConfig.getAppId(),
+                    payConfig.getMchId(),
+                    payConfig.getPrivateKey()
+            );
+
+            // 5. 封装返回结果
+            Map<String, Object> payMap = new HashMap<>();
+            payMap.put("appId", jsapi.getAppId());
+            payMap.put("timeStamp", jsapi.getTimeStamp());
+            payMap.put("nonceStr", jsapi.getNonceStr());
+            payMap.put("package", jsapi.getPackageValue());
+            payMap.put("signType", jsapi.getSignType());
+            payMap.put("paySign", jsapi.getPaySign());
+            return payMap;
+
+        } catch (WxPayException e) {
+            // 微信支付业务异常(如签名错误、余额不足)
+            log.error("微信支付业务异常,订单号: {}", orderNo, e);
+            throw new ServiceException("支付失败:" + e.getMessage());
+        } catch (Exception e) {
+            // 系统级异常(如网络超时、配置错误)
+            log.error("微信支付系统异常,订单号: {}", orderNo, e);
+            throw new ServiceException("支付服务异常,请稍后重试");
+        }
+    }
+}

+ 4 - 3
nightFragrance-massage/src/main/java/com/ylx/giftCard/controller/GiftCardController.java

@@ -15,6 +15,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.Map;
 
 @RestController
 @RequestMapping("/gift/card")
@@ -34,9 +35,9 @@ public class GiftCardController {
 
     @PostMapping("/purchase")
     @ApiOperation("购物卡购买接口")
-    public R<String> purchaseGiftCard(@Validated @RequestBody GiftCardPurchaseDTO dto) {
-        boolean success = giftCardService.purchaseGiftCard(dto);
-        return success ? R.ok("购买成功") : R.fail("购买失败,库存不足或商品不存在");
+    public R<Map<String, Object>> purchaseGiftCard(@Validated @RequestBody GiftCardPurchaseDTO dto) {
+        Map<String, Object> data =giftCardService.purchaseGiftCard(dto);
+        return R.ok(data);
     }
 
     @GetMapping("/{id}/detail")

+ 1 - 1
nightFragrance-massage/src/main/java/com/ylx/giftCard/domain/GiftCardOrder.java

@@ -98,7 +98,7 @@ public class GiftCardOrder extends BaseEntity {
     private BigDecimal commissionAmount;
 
     /**
-     * 订单状态:1=已支付,2=已退款,3=已过期
+     * 订单状态:0=待支付,1=已支付,2=已退款,3=已过期
      */
     private Integer status;
 

+ 21 - 0
nightFragrance-massage/src/main/java/com/ylx/giftCard/enums/GiftCardOrderStatusEnum.java

@@ -0,0 +1,21 @@
+package com.ylx.giftCard.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum GiftCardOrderStatusEnum {
+
+    WAIT_PAY(0, "待支付"),
+    PAID(1, "已支付"),
+    REFUNDED(2, "已退款"),
+    EXPIRED(3, "已过期"),
+    CANCEL(4, "已取消");
+
+    private final Integer code;
+    private final String info;
+
+    GiftCardOrderStatusEnum(Integer code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+}

+ 4 - 1
nightFragrance-massage/src/main/java/com/ylx/giftCard/service/IGiftCardOrderService.java

@@ -1,6 +1,7 @@
 package com.ylx.giftCard.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ylx.common.core.domain.model.WxLoginUser;
 import com.ylx.giftCard.domain.GiftCard;
 import com.ylx.giftCard.domain.GiftCardOrder;
 
@@ -8,5 +9,7 @@ import java.util.List;
 
 public interface IGiftCardOrderService extends IService<GiftCardOrder> {
 
-    GiftCardOrder buildOrder(GiftCard card, Integer quantity, String merchantId);
+    GiftCardOrder buildOrder(GiftCard card, Integer quantity, String merchantId, WxLoginUser wxLoginUser);
+
+    void cancelOrder(Long id);
 }

+ 30 - 14
nightFragrance-massage/src/main/java/com/ylx/giftCard/service/impl/GiftCardOrderServiceImpl.java

@@ -7,9 +7,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ylx.common.core.domain.model.WxLoginUser;
 import com.ylx.common.exception.ServiceException;
 import com.ylx.common.utils.DateUtils;
-import com.ylx.common.utils.SecurityUtils;
 import com.ylx.giftCard.domain.GiftCard;
 import com.ylx.giftCard.domain.GiftCardOrder;
+import com.ylx.giftCard.enums.GiftCardOrderStatusEnum;
 import com.ylx.giftCard.mapper.GiftCardOrderMapper;
 import com.ylx.giftCard.service.IGiftCardOrderService;
 import com.ylx.massage.domain.TJs;
@@ -31,24 +31,17 @@ public class GiftCardOrderServiceImpl extends ServiceImpl<GiftCardOrderMapper, G
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public GiftCardOrder buildOrder(GiftCard card, Integer quantity, String merchantId) {
+    public GiftCardOrder buildOrder(GiftCard card, Integer quantity, String merchantId, WxLoginUser wxLoginUser) {
 
         // 1. 参数校验
         if (ObjectUtil.isNull(card)) {
-            throw new IllegalArgumentException("购物卡信息不能为空");
+            throw new ServiceException("购物卡信息不能为空");
         }
-        if (quantity == null || quantity <= 0) {
-            throw new IllegalArgumentException("购买数量必须大于0");
+        if (ObjectUtil.isNull(quantity) || quantity <= 0) {
+            throw new ServiceException("购买数量必须大于0");
         }
 
-        // 2. 获取用户信息
-        WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
-        if (ObjectUtil.isNull(wxLoginUser)) {
-            log.warn("用户未登录,无法创建订单");
-            throw new ServiceException("用户未登录");
-        }
-
-        // 3. 创建订单对象
+        // 2. 创建订单对象
         GiftCardOrder order = new GiftCardOrder();
 
         // 4. 生成唯一订单号(使用更安全的方式)
@@ -69,7 +62,7 @@ public class GiftCardOrderServiceImpl extends ServiceImpl<GiftCardOrderMapper, G
         order.setPurchaseQuantity(quantity);
 
         // 9. 设置订单状态和时间
-        order.setStatus(1); // 已支付
+        order.setStatus(GiftCardOrderStatusEnum.WAIT_PAY.getCode()); // 待支付
         order.setCreateTime(DateUtils.getNowDate());
         order.setUpdateTime(order.getCreateTime());
         int rowsAffected = this.baseMapper.insert(order);
@@ -80,6 +73,29 @@ public class GiftCardOrderServiceImpl extends ServiceImpl<GiftCardOrderMapper, G
         return order;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelOrder(Long id) {
+        // 1. 查询订单
+        GiftCardOrder order = this.getById(id);
+        if (ObjectUtil.isNull(order)) {
+            throw new ServiceException("订单不存在");
+        }
+        // 仅待支付订单可取消
+        if (!GiftCardOrderStatusEnum.PAID.getCode().equals(order.getStatus())) {
+            throw new ServiceException("当前订单状态不支持取消");
+        }
+        // 2. 修改订单状态为已取消
+        order.setStatus(GiftCardOrderStatusEnum.CANCEL.getCode());
+        order.setUpdateTime(DateUtils.getNowDate());
+        boolean update = this.updateById(order);
+        if (!update) {
+            log.error("订单取消失败,订单号:{}", order.getOrderNo());
+            throw new ServiceException("订单取消失败");
+        }
+        log.info("订单取消成功,订单号:{},购物卡ID:{}", order.getOrderNo(), order.getGiftCardId());
+    }
+
     /**
      * 生成唯一订单号
      */

+ 84 - 72
nightFragrance-massage/src/main/java/com/ylx/giftCard/service/impl/GiftCardServiceImpl.java

@@ -11,6 +11,8 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ylx.common.exception.ServiceException;
 import com.ylx.common.utils.DateUtils;
 import com.ylx.common.utils.SecurityUtils;
+import com.ylx.common.weixinPay.enums.WxPayTypeEnum;
+import com.ylx.common.weixinPay.service.WxPayV3Service;
 import com.ylx.giftCard.domain.GiftCard;
 import com.ylx.giftCard.domain.GiftCardOrder;
 import com.ylx.giftCard.domain.dto.GiftCardManageQueryDTO;
@@ -26,10 +28,7 @@ import com.ylx.giftCard.domain.vo.GiftCardVO;
 import com.ylx.giftCard.mapper.GiftCardMapper;
 import com.ylx.giftCard.service.IGiftCardOrderService;
 import com.ylx.giftCard.service.IGiftCardService;
-import com.ylx.shopingfundsdetail.domain.vo.ShoppingFundsDetailAddDto;
-import com.ylx.shopingfundsdetail.service.ShoppingFundsDetailService;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -39,6 +38,7 @@ import java.time.LocalDate;
 import java.time.ZoneId;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -48,7 +48,7 @@ public class GiftCardServiceImpl extends ServiceImpl<GiftCardMapper, GiftCard> i
     @Resource
     private IGiftCardOrderService giftCardOrderService;
     @Resource
-    private ShoppingFundsDetailService shoppingFundsDetailService;
+    private WxPayV3Service wxPayV3Service;
 
     private static final int NOT_DELETE = 0;
     private static final int PUBLISHED = 1;
@@ -82,59 +82,43 @@ public class GiftCardServiceImpl extends ServiceImpl<GiftCardMapper, GiftCard> i
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean purchaseGiftCard(GiftCardPurchaseDTO dto) {
+    public Map<String, Object> purchaseGiftCard(GiftCardPurchaseDTO dto) {
+
+        // 1. 获取当前用户
+        WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
+        if (ObjectUtil.isNull(wxLoginUser)) {
+            log.warn("用户未登录,无法创建订单");
+            throw new ServiceException("用户未登录");
+        }
+
         Long id = dto.getId();
         Integer quantity = dto.getQuantity();
         String merchantId = dto.getMerchantId();
 
+        // 2. 查询并校验购物卡
         GiftCard card = this.getById(id);
-        if (ObjectUtil.isNull(card)) {
-            log.warn("购买失败,购物卡不存在,ID: {}", id);
-            return false;
-        }
-
-        // 2. 校验购物卡状态
-        if (card.getIsDelete() != NOT_DELETE) {
-            log.warn("购买失败,购物卡已删除,ID: {}", id);
-            return false;
-        }
-        if (card.getIsPublished() != PUBLISHED) {
-            log.warn("购买失败,购物卡未上架,ID: {}", id);
-            return false;
-        }
-
-        // 3. 校验库存是否充足
-        if (card.getStock() < quantity) {
-            log.warn("购买失败,库存不足,ID: {},库存: {},需求数量: {}", id, card.getStock(), quantity);
-            return false;
-        }
-
-        // 4. 扣减库存(使用乐观锁防止超卖)
-        String updateExpr = String.format("stock = stock - %d, sales = sales + %d", quantity, quantity);
-
-        LambdaUpdateWrapper<GiftCard> updateWrapper = new LambdaUpdateWrapper<>();
-        updateWrapper.eq(GiftCard::getId, id)
-                .eq(GiftCard::getIsDelete, NOT_DELETE)
-                .eq(GiftCard::getIsPublished, PUBLISHED)
-                .ge(GiftCard::getStock, quantity) // 关键:确保库存充足
-                .setSql(updateExpr);
-
-        int rowsAffected = this.baseMapper.update(null, updateWrapper);
+        validateGiftCard(card, quantity);
 
+        // 3. 乐观锁扣减库存(增加状态校验,防止无效更新)
+        int rowsAffected = deductStockOptimisticLock(card.getId(), dto.getQuantity());
         if (rowsAffected <= 0) {
             log.warn("购买失败,库存不足或商品不存在,购物卡ID: {}", id);
-            return false;
+            throw new ServiceException("库存不足或商品状态异常");
         }
 
         log.info("购买成功,购物卡ID: {}, 数量: {}", id, quantity);
 
-        // 5. 异步创建订单(传入完整的购物卡对象,避免异步方法中再次查询)
-        GiftCardOrder giftCardOrder = createOrderAsync(card, quantity, merchantId);
+        // 4. 创建订单
+        GiftCardOrder order = this.giftCardOrderService.buildOrder(card, quantity, merchantId, wxLoginUser);
+        if(ObjectUtil.isNull(order)){
+            log.warn("购物卡订单创建失败,购物卡ID: {},下单人ID: {}", card.getId(), wxLoginUser.getId());
+            throw new ServiceException("购物卡订单创建失败");
+        }
 
-        // 6. 异步添加购物金明细
-        addShoppingFundsDetailAsync(card, giftCardOrder);
+        log.info("购物卡订单创建,购物卡ID: {}, 订单编号: {}", id, order.getOrderNo());
 
-        return true;
+        //  5. 调用微信支付
+        return createWxPayOrder(order, wxLoginUser);
     }
 
     @Override
@@ -262,44 +246,72 @@ public class GiftCardServiceImpl extends ServiceImpl<GiftCardMapper, GiftCard> i
     }
 
     /**
-     * 异步创建订单
+     * 校验购物卡有效性
      */
-    @Async
-    public GiftCardOrder createOrderAsync(GiftCard card, Integer quantity, String merchantId) {
-        try {
-            // 注意:异步方法中不要依赖主线程的事务,订单创建失败不应影响库存扣减
-            GiftCardOrder order = this.giftCardOrderService.buildOrder(card, quantity, merchantId);
-
-            if (ObjectUtil.isNotNull(order)) {
-                log.info("购物卡订单创建成功,购物卡ID: {}", card.getId());
-            } else {
-                log.warn("购物卡订单创建返回为空,购物卡ID: {}", card.getId());
-            }
-            return order;
-        } catch (Exception e) {
-            log.error("异步创建订单失败,订单数据: cardId={}, quantity={}, merchantId={}",
-                    card.getId(), quantity, merchantId, e);
+    private void validateGiftCard(GiftCard card, Integer quantity) {
+        if (ObjectUtil.isNull(card)) {
+            throw new ServiceException("商品不存在");
         }
-        return null;
+        if (card.getIsDelete() != NOT_DELETE) {
+            log.warn("购买失败,购物卡已删除,ID: {}", card.getId());
+            throw new ServiceException("购物卡已删除");
+        }
+        if (card.getIsPublished() != PUBLISHED) {
+            log.warn("购买失败,购物卡未上架,ID: {}", card.getId());
+            throw new ServiceException("购物卡未上架");
+        }
+        if (card.getStock() < quantity) {
+            log.warn("购买失败,库存不足,ID: {},库存: {},需求数量: {}", card.getId(), card.getStock(), quantity);
+            throw new ServiceException("库存不足");
+        }
+
     }
 
     /**
-     * 异步新增购物金明细
+     * 乐观锁扣减库存(增加状态条件,确保只更新有效记录)
      */
-    @Async
-    public void addShoppingFundsDetailAsync(GiftCard card, GiftCardOrder giftCardOrder) {
+    private int deductStockOptimisticLock(Long cardId, Integer quantity) {
+        LambdaUpdateWrapper<GiftCard> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(GiftCard::getId, cardId)
+                .eq(GiftCard::getIsDelete, NOT_DELETE)
+                .eq(GiftCard::getIsPublished, PUBLISHED)
+                .ge(GiftCard::getStock, quantity) // 库存充足时才更新
+                .setSql("stock = stock - " + quantity + ", sales = sales + " + quantity);
+        return baseMapper.update(null, wrapper);
+    }
 
-        try {
-            ShoppingFundsDetailAddDto dto = new ShoppingFundsDetailAddDto();
-            dto.setGiftCardId(card.getId());
-            dto.setAmount(card.getAmount());
-            dto.setUserId(giftCardOrder.getUserId());
-            dto.setExpenseType(0);
-            this.shoppingFundsDetailService.addShoppingFundsDetail(dto);
+    /**
+     * 补偿回滚库存
+     */
+    private void recoverStock(Long cardId, Integer num) {
+        GiftCard updateEntity = new GiftCard();
+        LambdaUpdateWrapper<GiftCard> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(GiftCard::getId, cardId)
+                .eq(GiftCard::getIsDelete, NOT_DELETE);
+        updateWrapper.setSql("stock = stock + #{num}, sales = sales - #{num}");
+        // 数据放到实体里
+        updateEntity.setStock(num);
+        baseMapper.update(updateEntity, updateWrapper);
+    }
 
+    /**
+     * 创建微信支付订单(事务外执行,减少事务时长)
+     */
+    private Map<String, Object> createWxPayOrder(GiftCardOrder order, WxLoginUser wxLoginUser) {
+        try {
+            return wxPayV3Service.createV3JsapiOrder(
+                    order.getOrderNo(),
+                    order.getPayAmount(),
+                    "购物卡购买",
+                    wxLoginUser.getCOpenid(),
+                    WxPayTypeEnum.GIFT_CARD.getCode()
+            );
         } catch (Exception e) {
-            log.error("异步新增购物金明细失败,订单数据: cardId={}, amount={}, userId={}",
-                    card.getId(), card.getAmount(), giftCardOrder.getUserId(), e);
+            log.error("微信支付下单失败,订单号: {}", order.getOrderNo(), e);
+            // 支付失败:恢复库存、修改订单为取消
+            recoverStock(order.getGiftCardId(), order.getPurchaseQuantity());
+            this.giftCardOrderService.cancelOrder(order.getId());
+            throw new ServiceException("支付服务异常,请稍后重试");
         }
     }