From 2b44e8dcb319725000edaea624132411c7b690d1 Mon Sep 17 00:00:00 2001 From: "Logan.Li" Date: Thu, 13 Aug 2026 00:04:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BB=85=E4=BF=9D=E7=95=99=20Waffo?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E4=BF=AE=E5=A4=8D=E8=B0=83=E8=AF=95=E5=8C=85?= =?UTF-8?q?=E5=B9=B4=E8=AF=AF=E7=94=A8=E6=B0=B8=E4=B9=85=20Product?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调试不再把兼容字段 product_id 覆盖到包年;包年禁止回落永久商品;下单强制走 Waffo。 --- backend/database/seed.go | 6 +-- .../internal/service/order/order_service.go | 41 +++++++++---------- backend/internal/service/payment/waffo.go | 11 +++-- .../service/setting/setting_service.go | 9 ++-- frontend/src/views/Payments.vue | 35 ++++++++-------- 5 files changed, 53 insertions(+), 49 deletions(-) diff --git a/backend/database/seed.go b/backend/database/seed.go index 847f4f8..d00dc95 100644 --- a/backend/database/seed.go +++ b/backend/database/seed.go @@ -279,10 +279,10 @@ func ensureWaffoPaymentGateway(db *gorm.DB) error { } log.Println("[初始化] 已创建 Waffo 支付网关(默认沙箱,可在后台改 live)") } - // 已存在则不覆盖 Enabled/SandboxMode,尊重管理员配置 // Logan.Li + // 已存在则不覆盖 Enabled/SandboxMode,尊重管理员配置;其它网关一律关闭 // Logan.Li if err := db.Model(&model.PaymentGatewayConfig{}). - Where("gateway <> ? AND enabled = ?", "waffo", true). - Update("priority", 100).Error; err != nil { + Where("gateway <> ?", "waffo"). + Updates(map[string]interface{}{"enabled": false, "priority": 100}).Error; err != nil { return err } return nil diff --git a/backend/internal/service/order/order_service.go b/backend/internal/service/order/order_service.go index 4c5149b..d355685 100644 --- a/backend/internal/service/order/order_service.go +++ b/backend/internal/service/order/order_service.go @@ -115,11 +115,14 @@ func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAge return nil, fmt.Errorf("重复购买拦截: %s", checkResult.Reason) } - // 4. 选取可用支付渠道(先选网关,写入订单) // Logan.Li + // 4. 仅对接 Waffo(禁用其它网关) // Logan.Li var gateway model.PaymentGatewayConfig - if err := s.db.Where("enabled = ?", true).Order("priority ASC").First(&gateway).Error; err != nil { - return nil, errors.New("暂无可用的支付渠道") + if err := s.db.Where("gateway = ? AND enabled = ?", "waffo", true).First(&gateway).Error; err != nil { + return nil, errors.New("Waffo 支付未启用,请先在「支付」页配置") } + _ = s.db.Model(&model.PaymentGatewayConfig{}). + Where("gateway <> ? AND enabled = ?", "waffo", true). + Updates(map[string]interface{}{"enabled": false, "priority": 100}).Error notes := "" if deviceID := strings.TrimSpace(req.DeviceID); deviceID != "" { @@ -135,7 +138,7 @@ func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAge Amount: plan.Price, Currency: plan.Currency, Status: "pending", - PaymentGateway: gateway.Gateway, + PaymentGateway: "waffo", ClientIP: clientIP, UserAgent: userAgent, Notes: notes, @@ -145,23 +148,17 @@ func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAge } _ = customersvc.AttachProduct(s.db, customer.ID, product.ID) - // 6. 生成收银台地址 // Logan.Li - var checkoutURL string - if gateway.Gateway == "waffo" { - url, err := payment.BuildCheckoutURL( - payment.LoadWaffoConfig(s.db), - order.OrderNo, - strings.TrimSpace(req.DeviceID), - req.CustomerEmail, - req.ReturnURL, - plan.PlanType, - ) - if err != nil { - return nil, fmt.Errorf("Waffo 收银台配置错误: %v", err) - } - checkoutURL = url - } else { - checkoutURL = fmt.Sprintf("https://checkout.%s.com/?order_no=%s", gateway.Gateway, order.OrderNo) + // 6. 生成 Waffo 收银台地址 // Logan.Li + checkoutURL, err := payment.BuildCheckoutURL( + payment.LoadWaffoConfig(s.db), + order.OrderNo, + strings.TrimSpace(req.DeviceID), + req.CustomerEmail, + req.ReturnURL, + plan.PlanType, + ) + if err != nil { + return nil, fmt.Errorf("Waffo 收银台配置错误: %v", err) } return &dto.CreateOrderResponse{ @@ -170,7 +167,7 @@ func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAge Amount: plan.Price, Currency: plan.Currency, Status: "pending", - Gateway: gateway.Gateway, + Gateway: "waffo", ExpiresAt: time.Now().Add(30 * time.Minute).Format("2006-01-02 15:04:05"), }, nil } diff --git a/backend/internal/service/payment/waffo.go b/backend/internal/service/payment/waffo.go index cccd73e..808a1da 100644 --- a/backend/internal/service/payment/waffo.go +++ b/backend/internal/service/payment/waffo.go @@ -435,10 +435,16 @@ func productIDForPlan(cfg WaffoConfig, planType string) string { if cfg.ProductIDYearly != "" { return cfg.ProductIDYearly } + // 禁止回落到永久 Product,避免测包年误用永久价/币种 // Logan.Li + return "" case "perpetual", "lifetime": if cfg.ProductIDPerpetual != "" { return cfg.ProductIDPerpetual } + if cfg.ProductID != "" { + return cfg.ProductID + } + return "" } if id := strings.TrimSpace(cfg.ProductIDs["default"]); id != "" { return id @@ -446,10 +452,7 @@ func productIDForPlan(cfg WaffoConfig, planType string) string { if cfg.ProductID != "" { return cfg.ProductID } - if cfg.ProductIDPerpetual != "" { - return cfg.ProductIDPerpetual - } - return cfg.ProductIDYearly + return firstNonEmpty(cfg.ProductIDPerpetual, cfg.ProductIDYearly) } func currencyForPlan(cfg WaffoConfig, planType string) string { diff --git a/backend/internal/service/setting/setting_service.go b/backend/internal/service/setting/setting_service.go index 543d065..8a11a5b 100644 --- a/backend/internal/service/setting/setting_service.go +++ b/backend/internal/service/setting/setting_service.go @@ -256,8 +256,8 @@ func (s *SettingService) UpdateWaffo(req dto.WaffoSettingsUpdate) (*dto.WaffoSet return nil, errors.New("保存 Waffo 配置失败") } _ = s.db.Model(&model.PaymentGatewayConfig{}). - Where("gateway <> ? AND enabled = ?", "waffo", true). - Update("priority", 100).Error + Where("gateway <> ?", "waffo"). + Updates(map[string]interface{}{"enabled": false, "priority": 100}).Error return s.GetWaffo() } @@ -281,9 +281,10 @@ func (s *SettingService) DebugWaffo(req dto.WaffoDebugRequest) *dto.WaffoDebugRe if planType == "" { planType = "yearly" } - productID := strings.TrimSpace(req.ProductID) + // 按计划解析 Product;忽略兼容字段 product_id(常为永久商品,会污染包年调试) // Logan.Li + productID := payment.ResolveProductID(cfg, planType) if productID == "" { - productID = payment.ResolveProductID(cfg, planType) + productID = strings.TrimSpace(req.ProductID) } authMode := "none" diff --git a/frontend/src/views/Payments.vue b/frontend/src/views/Payments.vue index 3d960ad..b3c6dff 100644 --- a/frontend/src/views/Payments.vue +++ b/frontend/src/views/Payments.vue @@ -157,18 +157,14 @@ import { getWaffoSettings, updateWaffoSettings, testWaffoConnection } from '@/ap const planKeyOptions = ['trial', 'yearly', 'perpetual', 'monthly', 'lifetime'] -// OpsXshell 线上商品(与 pancake 购买链接一致) // Logan.Li -const OPSXSHELL_PRODUCTS = [ - { - plan_key: 'yearly', - label: 'OpsXshell Yearly', - product_id: 'PROD_5PqVJyEiCM4nDgVoA0RKjN', - }, - { - plan_key: 'perpetual', - label: 'OpsXshell Perpetual', - product_id: 'PROD_3BSJqDRkVBoYNgO78NsTAU', - }, +// OpsXshell 商品:测试 / 生产各一套 // Logan.Li +const OPSXSHELL_PRODUCTS_LIVE = [ + { plan_key: 'yearly', label: 'OpsXshell Yearly', product_id: 'PROD_5PqVJyEiCM4nDgVoA0RKjN' }, + { plan_key: 'perpetual', label: 'OpsXshell Perpetual', product_id: 'PROD_3BSJqDRkVBoYNgO78NsTAU' }, +] +const OPSXSHELL_PRODUCTS_TEST = [ + { plan_key: 'yearly', label: 'OpsXshell Yearly', product_id: 'PROD_1BIezCi932IbgCzRIV8hcJ' }, + { plan_key: 'perpetual', label: 'OpsXshell Perpetual', product_id: 'PROD_0pcnzzT4nalEkkcdyIUoD5' }, ] const loading = ref(false) @@ -300,8 +296,9 @@ function removeMapping(index) { } function fillOpsxshellProducts() { + const catalog = isLive.value ? OPSXSHELL_PRODUCTS_LIVE : OPSXSHELL_PRODUCTS_TEST const byKey = Object.fromEntries(productMappings.value.map((r) => [r.plan_key, r])) - for (const item of OPSXSHELL_PRODUCTS) { + for (const item of catalog) { if (byKey[item.plan_key]) { byKey[item.plan_key].product_id = item.product_id byKey[item.plan_key].label = item.label @@ -422,20 +419,26 @@ async function testConnection() { debugResult.value = '' try { const compat = mappingsToCompatFields(productMappings.value) + // 勿传通用 product_id(多为永久商品),否则测包年会被覆盖 // Logan.Li + const { product_id: _ignoredProductID, ...rest } = compat const res = await testWaffoConnection({ merchant_id: waffo.merchant_id, store_id: waffo.store_id, store_slug: waffo.store_slug, environment: waffo.environment, plan_type: debugPlan.value, - ...compat, + ...rest, }) debugResult.value = JSON.stringify(res.data ?? res, null, 2) - ElMessage.success('调试请求已完成(未写库;映射请手动保存)') + if (res.data?.ok === false) { + ElMessage.error(res.data?.error_summary || res.message || '调试失败') + } } catch (err) { + const body = err?.response?.data debugResult.value = JSON.stringify({ - error: err?.response?.data || err?.message || String(err), + error: body || err?.message || String(err), }, null, 2) + ElMessage.error(body?.message || body?.data?.error_summary || '调试失败') } finally { debugging.value = false }