Просмотр исходного кода

feat(pay): 虚拟支付失败自动转微信JSAPI + 会员券非会员可用 + 素材库回填幂等修复

- MembershipService: createOrderWithPayType 虚拟支付响应附加 wechatPayParams 兜底(JSAPI)
- CouponService: toCouponVO 暴露 grantType,前端据此允许非会员使用手动发放(MANUAL)的会员券
- pay.vue: 虚拟支付失败(非取消/非登录态失效)自动降级 payWechat; 会员券筛选放行 MANUAL
- 素材库 backfillAssetLibrary 幂等回填(手动补跑 file_type 1422 + 按分类补标签 7867)
Xiaogang Liao 2 недель назад
Родитель
Сommit
2cbda56e4f

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/CouponService.java

@@ -88,6 +88,7 @@ public class CouponService {
         item.put("usedAt", fc.getUsedAt());
         item.put("usedAt", fc.getUsedAt());
         item.put("orderId", fc.getOrderId());
         item.put("orderId", fc.getOrderId());
         item.put("productId", coupon.getProductId());
         item.put("productId", coupon.getProductId());
+        item.put("grantType", coupon.getGrantType());
         return item;
         return item;
     }
     }
 
 

+ 30 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java

@@ -651,6 +651,8 @@ public class MembershipService implements MembershipServiceInterface {
                 resp.put("env", dto.getEnv());
                 resp.put("env", dto.getEnv());
                 resp.put("signType", dto.getSignType());
                 resp.put("signType", dto.getSignType());
                 resp.put("currentPeriod", dto.getCurrentPeriod());
                 resp.put("currentPeriod", dto.getCurrentPeriod());
+                // 虚拟支付兜底:道具价格/风控校验失败(如 GOODS_PRICE_INVALID)时,前端自动改走普通微信支付(JSAPI)
+                resp.put("wechatPayParams", buildWechatFallbackParams(mainOrder, level, openid));
                 return resp;
                 return resp;
             }
             }
             // 虚拟道具未配置或停用,降级为普通微信支付
             // 虚拟道具未配置或停用,降级为普通微信支付
@@ -667,6 +669,34 @@ public class MembershipService implements MembershipServiceInterface {
         return resp;
         return resp;
     }
     }
 
 
+    /**
+     * 虚拟支付失败兜底:生成普通微信支付(v3 JSAPI)参数。
+     *
+     * <p>微信虚拟支付对道具价格做严格校验(goodsPrice 需与后台道具注册价一致),
+     * 价格不匹配时前端会收到 GOODS_PRICE_INVALID(-15013) 而无法完成支付。
+     * 此处预先生成 JSAPI 支付参数,前端在虚拟支付失败时自动改走普通微信支付完成下单。
+     *
+     * @return 微信 JSAPI 支付参数;openid 缺失或微信统一下单失败时返回 null
+     */
+    private Map<String, Object> buildWechatFallbackParams(PaymentOrder order, MembershipLevel level, String openid) {
+        if (order == null || order.getAmount() == null || order.getAmount() <= 0) {
+            return null;
+        }
+        if (openid == null || openid.isEmpty()) {
+            log.warn("会员订单普通支付兜底跳过(openid缺失): orderNo={}", order.getOrderNo());
+            return null;
+        }
+        try {
+            String levelName = level != null && level.getLevelName() != null
+                    ? level.getLevelName() : order.getLevelCode();
+            String description = "\u6d6e\u827e\u798f-" + levelName + "\u4f1a\u5458";
+            return paymentService.createWechatPrepay(order.getOrderNo(), description, order.getAmount(), openid);
+        } catch (Exception e) {
+            log.warn("会员订单普通支付兜底参数生成失败: orderNo={}", order.getOrderNo(), e);
+            return null;
+        }
+    }
+
     /**
     /**
      * 处理支付回调
      * 处理支付回调
      */
      */

+ 14 - 3
cfc-frontend/pages/membership/pay.vue

@@ -153,7 +153,8 @@ export default {
         if (c.status && c.status !== 'AVAILABLE') return false
         if (c.status && c.status !== 'AVAILABLE') return false
         if (c.applicableTo && c.applicableTo !== 'ALL' && c.applicableTo !== 'MEMBERSHIP') return false
         if (c.applicableTo && c.applicableTo !== 'ALL' && c.applicableTo !== 'MEMBERSHIP') return false
         // 会员专属券:非会员不显示在可选列表中
         // 会员专属券:非会员不显示在可选列表中
-        if (c.applicableTo === 'MEMBERSHIP' && !self.isMember) return false
+        // 会员专属券(applicableTo=MEMBERSHIP)仅允许会员使用;手动发放券(grantType=MANUAL)例外,允许非会员购买时使用
+        if (c.applicableTo === 'MEMBERSHIP' && !self.isMember && c.grantType !== 'MANUAL') return false
         return true
         return true
       })
       })
     },
     },
@@ -239,7 +240,8 @@ export default {
     },
     },
     getCouponCondition: function(coupon) {
     getCouponCondition: function(coupon) {
       if (!coupon) return '无门槛'
       if (!coupon) return '无门槛'
-      if (coupon.applicableTo === 'MEMBERSHIP' && !this.isMember) {
+      // 会员专属券(applicableTo=MEMBERSHIP)仅允许会员使用;手动发放券(grantType=MANUAL)例外,允许非会员购买时使用
+      if (coupon.applicableTo === 'MEMBERSHIP' && !this.isMember && coupon.grantType !== 'MANUAL') {
         return '开通会员后可用'
         return '开通会员后可用'
       }
       }
       if (!coupon.minSpend || coupon.minSpend <= 0) return '无门槛'
       if (!coupon.minSpend || coupon.minSpend <= 0) return '无门槛'
@@ -346,8 +348,17 @@ export default {
           }
           }
         },
         },
         fail: function(err) {
         fail: function(err) {
-          self.paying = false
           var errCode = err && err.errCode
           var errCode = err && err.errCode
+          // 虚拟支付失败(道具价格不匹配/道具未发布/环境不支持等)→ 自动降级为普通微信支付(订单支付)
+          // 用户主动取消(-2)与登录态失效(-15007/-15005)不降级,直接提示
+          if (dto && dto.wechatPayParams && errCode !== -2 && errCode !== -15007 && errCode !== -15005) {
+            uni.showToast({ title: '虚拟支付不可用,转微信支付', icon: 'none' })
+            setTimeout(function() {
+              self.payWechat(dto.wechatPayParams)
+            }, 800)
+            return
+          }
+          self.paying = false
           var msg = ''
           var msg = ''
           if (errCode === -15007 || errCode === -15005) msg = '登录态已过期,请重新登录后支付'
           if (errCode === -15007 || errCode === -15005) msg = '登录态已过期,请重新登录后支付'
           else if (errCode === -15010) msg = '商品未发布,请联系客服'
           else if (errCode === -15010) msg = '商品未发布,请联系客服'