fix: 仅保留 Waffo,并修复调试包年误用永久 Product
调试不再把兼容字段 product_id 覆盖到包年;包年禁止回落永久商品;下单强制走 Waffo。
This commit is contained in:
1 parent
d94da2129c
commit
2b44e8dcb3
5 files changed
+53
-49
No files matched your search
@@ -279,10 +279,10 @@ func ensureWaffoPaymentGateway(db *gorm.DB) error {
|
|||||||
}
|
}
|
||||||
log.Println("[初始化] 已创建 Waffo 支付网关(默认沙箱,可在后台改 live)")
|
log.Println("[初始化] 已创建 Waffo 支付网关(默认沙箱,可在后台改 live)")
|
||||||
}
|
}
|
||||||
// 已存在则不覆盖 Enabled/SandboxMode,尊重管理员配置 // Logan.Li
|
// 已存在则不覆盖 Enabled/SandboxMode,尊重管理员配置;其它网关一律关闭 // Logan.Li
|
||||||
if err := db.Model(&model.PaymentGatewayConfig{}).
|
if err := db.Model(&model.PaymentGatewayConfig{}).
|
||||||
Where("gateway <> ? AND enabled = ?", "waffo", true).
|
Where("gateway <> ?", "waffo").
|
||||||
Update("priority", 100).Error; err != nil {
|
Updates(map[string]interface{}{"enabled": false, "priority": 100}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -115,11 +115,14 @@ func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAge
|
|||||||
return nil, fmt.Errorf("重复购买拦截: %s", checkResult.Reason)
|
return nil, fmt.Errorf("重复购买拦截: %s", checkResult.Reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 选取可用支付渠道(先选网关,写入订单) // Logan.Li
|
// 4. 仅对接 Waffo(禁用其它网关) // Logan.Li
|
||||||
var gateway model.PaymentGatewayConfig
|
var gateway model.PaymentGatewayConfig
|
||||||
if err := s.db.Where("enabled = ?", true).Order("priority ASC").First(&gateway).Error; err != nil {
|
if err := s.db.Where("gateway = ? AND enabled = ?", "waffo", true).First(&gateway).Error; err != nil {
|
||||||
return nil, errors.New("暂无可用的支付渠道")
|
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 := ""
|
notes := ""
|
||||||
if deviceID := strings.TrimSpace(req.DeviceID); deviceID != "" {
|
if deviceID := strings.TrimSpace(req.DeviceID); deviceID != "" {
|
||||||
@@ -135,7 +138,7 @@ func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAge
|
|||||||
Amount: plan.Price,
|
Amount: plan.Price,
|
||||||
Currency: plan.Currency,
|
Currency: plan.Currency,
|
||||||
Status: "pending",
|
Status: "pending",
|
||||||
PaymentGateway: gateway.Gateway,
|
PaymentGateway: "waffo",
|
||||||
ClientIP: clientIP,
|
ClientIP: clientIP,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
Notes: notes,
|
Notes: notes,
|
||||||
@@ -145,23 +148,17 @@ func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAge
|
|||||||
}
|
}
|
||||||
_ = customersvc.AttachProduct(s.db, customer.ID, product.ID)
|
_ = customersvc.AttachProduct(s.db, customer.ID, product.ID)
|
||||||
|
|
||||||
// 6. 生成收银台地址 // Logan.Li
|
// 6. 生成 Waffo 收银台地址 // Logan.Li
|
||||||
var checkoutURL string
|
checkoutURL, err := payment.BuildCheckoutURL(
|
||||||
if gateway.Gateway == "waffo" {
|
payment.LoadWaffoConfig(s.db),
|
||||||
url, err := payment.BuildCheckoutURL(
|
order.OrderNo,
|
||||||
payment.LoadWaffoConfig(s.db),
|
strings.TrimSpace(req.DeviceID),
|
||||||
order.OrderNo,
|
req.CustomerEmail,
|
||||||
strings.TrimSpace(req.DeviceID),
|
req.ReturnURL,
|
||||||
req.CustomerEmail,
|
plan.PlanType,
|
||||||
req.ReturnURL,
|
)
|
||||||
plan.PlanType,
|
if err != nil {
|
||||||
)
|
return nil, fmt.Errorf("Waffo 收银台配置错误: %v", err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &dto.CreateOrderResponse{
|
return &dto.CreateOrderResponse{
|
||||||
@@ -170,7 +167,7 @@ func (s *OrderService) CreateOrder(req dto.CreateOrderRequest, clientIP, userAge
|
|||||||
Amount: plan.Price,
|
Amount: plan.Price,
|
||||||
Currency: plan.Currency,
|
Currency: plan.Currency,
|
||||||
Status: "pending",
|
Status: "pending",
|
||||||
Gateway: gateway.Gateway,
|
Gateway: "waffo",
|
||||||
ExpiresAt: time.Now().Add(30 * time.Minute).Format("2006-01-02 15:04:05"),
|
ExpiresAt: time.Now().Add(30 * time.Minute).Format("2006-01-02 15:04:05"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -435,10 +435,16 @@ func productIDForPlan(cfg WaffoConfig, planType string) string {
|
|||||||
if cfg.ProductIDYearly != "" {
|
if cfg.ProductIDYearly != "" {
|
||||||
return cfg.ProductIDYearly
|
return cfg.ProductIDYearly
|
||||||
}
|
}
|
||||||
|
// 禁止回落到永久 Product,避免测包年误用永久价/币种 // Logan.Li
|
||||||
|
return ""
|
||||||
case "perpetual", "lifetime":
|
case "perpetual", "lifetime":
|
||||||
if cfg.ProductIDPerpetual != "" {
|
if cfg.ProductIDPerpetual != "" {
|
||||||
return cfg.ProductIDPerpetual
|
return cfg.ProductIDPerpetual
|
||||||
}
|
}
|
||||||
|
if cfg.ProductID != "" {
|
||||||
|
return cfg.ProductID
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
if id := strings.TrimSpace(cfg.ProductIDs["default"]); id != "" {
|
if id := strings.TrimSpace(cfg.ProductIDs["default"]); id != "" {
|
||||||
return id
|
return id
|
||||||
@@ -446,10 +452,7 @@ func productIDForPlan(cfg WaffoConfig, planType string) string {
|
|||||||
if cfg.ProductID != "" {
|
if cfg.ProductID != "" {
|
||||||
return cfg.ProductID
|
return cfg.ProductID
|
||||||
}
|
}
|
||||||
if cfg.ProductIDPerpetual != "" {
|
return firstNonEmpty(cfg.ProductIDPerpetual, cfg.ProductIDYearly)
|
||||||
return cfg.ProductIDPerpetual
|
|
||||||
}
|
|
||||||
return cfg.ProductIDYearly
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func currencyForPlan(cfg WaffoConfig, planType string) string {
|
func currencyForPlan(cfg WaffoConfig, planType string) string {
|
||||||
|
|||||||
@@ -256,8 +256,8 @@ func (s *SettingService) UpdateWaffo(req dto.WaffoSettingsUpdate) (*dto.WaffoSet
|
|||||||
return nil, errors.New("保存 Waffo 配置失败")
|
return nil, errors.New("保存 Waffo 配置失败")
|
||||||
}
|
}
|
||||||
_ = s.db.Model(&model.PaymentGatewayConfig{}).
|
_ = s.db.Model(&model.PaymentGatewayConfig{}).
|
||||||
Where("gateway <> ? AND enabled = ?", "waffo", true).
|
Where("gateway <> ?", "waffo").
|
||||||
Update("priority", 100).Error
|
Updates(map[string]interface{}{"enabled": false, "priority": 100}).Error
|
||||||
|
|
||||||
return s.GetWaffo()
|
return s.GetWaffo()
|
||||||
}
|
}
|
||||||
@@ -281,9 +281,10 @@ func (s *SettingService) DebugWaffo(req dto.WaffoDebugRequest) *dto.WaffoDebugRe
|
|||||||
if planType == "" {
|
if planType == "" {
|
||||||
planType = "yearly"
|
planType = "yearly"
|
||||||
}
|
}
|
||||||
productID := strings.TrimSpace(req.ProductID)
|
// 按计划解析 Product;忽略兼容字段 product_id(常为永久商品,会污染包年调试) // Logan.Li
|
||||||
|
productID := payment.ResolveProductID(cfg, planType)
|
||||||
if productID == "" {
|
if productID == "" {
|
||||||
productID = payment.ResolveProductID(cfg, planType)
|
productID = strings.TrimSpace(req.ProductID)
|
||||||
}
|
}
|
||||||
|
|
||||||
authMode := "none"
|
authMode := "none"
|
||||||
|
|||||||
@@ -157,18 +157,14 @@ import { getWaffoSettings, updateWaffoSettings, testWaffoConnection } from '@/ap
|
|||||||
|
|
||||||
const planKeyOptions = ['trial', 'yearly', 'perpetual', 'monthly', 'lifetime']
|
const planKeyOptions = ['trial', 'yearly', 'perpetual', 'monthly', 'lifetime']
|
||||||
|
|
||||||
// OpsXshell 线上商品(与 pancake 购买链接一致) // Logan.Li
|
// OpsXshell 商品:测试 / 生产各一套 // Logan.Li
|
||||||
const OPSXSHELL_PRODUCTS = [
|
const OPSXSHELL_PRODUCTS_LIVE = [
|
||||||
{
|
{ plan_key: 'yearly', label: 'OpsXshell Yearly', product_id: 'PROD_5PqVJyEiCM4nDgVoA0RKjN' },
|
||||||
plan_key: 'yearly',
|
{ plan_key: 'perpetual', label: 'OpsXshell Perpetual', product_id: 'PROD_3BSJqDRkVBoYNgO78NsTAU' },
|
||||||
label: 'OpsXshell Yearly',
|
]
|
||||||
product_id: 'PROD_5PqVJyEiCM4nDgVoA0RKjN',
|
const OPSXSHELL_PRODUCTS_TEST = [
|
||||||
},
|
{ plan_key: 'yearly', label: 'OpsXshell Yearly', product_id: 'PROD_1BIezCi932IbgCzRIV8hcJ' },
|
||||||
{
|
{ plan_key: 'perpetual', label: 'OpsXshell Perpetual', product_id: 'PROD_0pcnzzT4nalEkkcdyIUoD5' },
|
||||||
plan_key: 'perpetual',
|
|
||||||
label: 'OpsXshell Perpetual',
|
|
||||||
product_id: 'PROD_3BSJqDRkVBoYNgO78NsTAU',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -300,8 +296,9 @@ function removeMapping(index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fillOpsxshellProducts() {
|
function fillOpsxshellProducts() {
|
||||||
|
const catalog = isLive.value ? OPSXSHELL_PRODUCTS_LIVE : OPSXSHELL_PRODUCTS_TEST
|
||||||
const byKey = Object.fromEntries(productMappings.value.map((r) => [r.plan_key, r]))
|
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]) {
|
if (byKey[item.plan_key]) {
|
||||||
byKey[item.plan_key].product_id = item.product_id
|
byKey[item.plan_key].product_id = item.product_id
|
||||||
byKey[item.plan_key].label = item.label
|
byKey[item.plan_key].label = item.label
|
||||||
@@ -422,20 +419,26 @@ async function testConnection() {
|
|||||||
debugResult.value = ''
|
debugResult.value = ''
|
||||||
try {
|
try {
|
||||||
const compat = mappingsToCompatFields(productMappings.value)
|
const compat = mappingsToCompatFields(productMappings.value)
|
||||||
|
// 勿传通用 product_id(多为永久商品),否则测包年会被覆盖 // Logan.Li
|
||||||
|
const { product_id: _ignoredProductID, ...rest } = compat
|
||||||
const res = await testWaffoConnection({
|
const res = await testWaffoConnection({
|
||||||
merchant_id: waffo.merchant_id,
|
merchant_id: waffo.merchant_id,
|
||||||
store_id: waffo.store_id,
|
store_id: waffo.store_id,
|
||||||
store_slug: waffo.store_slug,
|
store_slug: waffo.store_slug,
|
||||||
environment: waffo.environment,
|
environment: waffo.environment,
|
||||||
plan_type: debugPlan.value,
|
plan_type: debugPlan.value,
|
||||||
...compat,
|
...rest,
|
||||||
})
|
})
|
||||||
debugResult.value = JSON.stringify(res.data ?? res, null, 2)
|
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) {
|
} catch (err) {
|
||||||
|
const body = err?.response?.data
|
||||||
debugResult.value = JSON.stringify({
|
debugResult.value = JSON.stringify({
|
||||||
error: err?.response?.data || err?.message || String(err),
|
error: body || err?.message || String(err),
|
||||||
}, null, 2)
|
}, null, 2)
|
||||||
|
ElMessage.error(body?.message || body?.data?.error_summary || '调试失败')
|
||||||
} finally {
|
} finally {
|
||||||
debugging.value = false
|
debugging.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user