feat: 对齐 Waffo 包年/永久结账与客户端席位 API

同步 OpsXShell 档位定价与 Waffo 商品映射,补齐 checkout/webhook/设备列表契约,便于桌面端购买与满席释放。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Logan.LiandCursor committed 2026-08-12 11:42:56 +08:00
1 parent 4ae318335c
commit 95405de119
14 files changed
+958 -155

No files matched your search

+180 -26
View File
@@ -25,6 +25,12 @@ func SeedDefaults(db *gorm.DB) error {
if err := seedSampleData(db); err != nil {
return err
}
if err := ensureOpsXShellPlanPricing(db); err != nil {
return err
}
if err := ensureWaffoPaymentGateway(db); err != nil {
return err
}
return nil
}
@@ -73,7 +79,7 @@ func seedSetting(db *gorm.DB) error {
return nil
}
// seedSampleData 初始化示例业务数据(产品/客户/计划/支付网关),便于开发联调 // Logan.Li
// seedSampleData 初始化 OpsXShell 产品 / 计划 / Waffo 网关,便于桌面端联调 // Logan.Li
func seedSampleData(db *gorm.DB) error {
var productCount int64
db.Model(&model.Product{}).Count(&productCount)
@@ -81,38 +87,67 @@ func seedSampleData(db *gorm.DB) error {
return nil
}
// 示例产品 // Logan.Li
product := model.Product{
Name: "OpsX Tools Suite",
Code: "opsx-tools",
Version: "1.0.0",
Description: "OpsX 工具集 - 一站式运维工具箱",
Features: `["CLI","GUI","Web Console","API"]`,
Price: 99.00,
PriceModel: "subscription",
Name: "OpsXShell",
Code: "opsxshell",
Version: "2.0.0",
Description: "OpsXShell Pro — SSH / SFTP / RDP / VNC / AI",
Features: `["ssh","sftp","rdp","vnc","ai","portForward","proxy","serial","batchExec"]`,
Price: 199.00,
PriceModel: "perpetual",
Status: 1,
}
if err := db.Create(&product).Error; err != nil {
return err
}
// 示例客户 // Logan.Li
customer := model.Customer{
Name: "示例客户",
Company: "Acme Inc.",
Email: "demo@acme.com",
Phone: "13800000000",
Name: "Demo",
Company: "OpsX",
Email: "demo@opsxshell.local",
Status: 1,
}
if err := db.Create(&customer).Error; err != nil {
return err
}
// 示例定价计划(买断 + 月订阅 + 年订阅) // Logan.Li
plans := []model.Plan{
{ProductID: product.ID, Name: "个人永久版", PlanType: "perpetual", Price: 199, Currency: "USD", Duration: 0, MaxDevices: 1, Features: `["全部功能","永久使用","1 设备"]`, SortOrder: 1, Status: 1},
{ProductID: product.ID, Name: "专业版(月)", PlanType: "monthly", Price: 19, Currency: "USD", Duration: 30, MaxDevices: 3, Features: `["全部功能","按月订阅","3 设备"]`, SortOrder: 2, Status: 1},
{ProductID: product.ID, Name: "企业版(年)", PlanType: "yearly", Price: 199, Currency: "USD", Duration: 365, MaxDevices: 10, Features: `["全部功能","按年订阅优惠","10 设备","优先支持"]`, SortOrder: 3, Status: 1},
{
ProductID: product.ID,
Name: "Yearly",
PlanType: "yearly",
Price: 99,
Currency: "CNY",
Duration: 365,
MaxDevices: 2,
Features: `["专业功能","包年","2 设备"]`,
SortOrder: 1,
Status: 1,
},
{
ProductID: product.ID,
Name: "Perpetual",
PlanType: "perpetual",
Price: 199,
Currency: "CNY",
Duration: 0,
MaxDevices: 5,
Features: `["专业功能","永久授权","5 设备"]`,
SortOrder: 2,
Status: 1,
},
{
ProductID: product.ID,
Name: "Trial 180d",
PlanType: "trial",
Price: 0,
Currency: "USD",
Duration: 180,
MaxDevices: 1,
Features: `["专业功能","半年体验","1 设备"]`,
SortOrder: 0,
Status: 1,
},
}
for i := range plans {
if err := db.Create(&plans[i]).Error; err != nil {
@@ -120,7 +155,6 @@ func seedSampleData(db *gorm.DB) error {
}
}
// 示例 License // Logan.Li
now := time.Now()
license := model.License{
LicenseKey: utils.GenerateLicenseKey(),
@@ -128,18 +162,17 @@ func seedSampleData(db *gorm.DB) error {
ProductID: product.ID,
Status: model.LicenseStatusActive,
StartDate: now,
EndDate: now.AddDate(0, 0, 365),
Duration: 365,
MaxActivations: 3,
Notes: "示例授权,初始化生成",
EndDate: model.PermanentEndDate,
Duration: 0,
MaxActivations: 5,
Notes: "开发示例授权",
}
if err := db.Create(&license).Error; err != nil {
return err
}
// 示例支付网关配置(沙箱) // Logan.Li
gateway := model.PaymentGatewayConfig{
Gateway: "creem",
Gateway: "waffo",
Enabled: true,
SandboxMode: true,
Currency: "USD",
@@ -149,6 +182,127 @@ func seedSampleData(db *gorm.DB) error {
return err
}
log.Println("[初始化] 已创建示例产品、客户、计划、License、支付网关")
log.Printf("[初始化] OpsXShell 产品已就绪 (code=opsxshell),示例 License: %s", license.LicenseKey)
return nil
}
// ensureOpsXShellPlanPricing ensures opsxshell product exists and syncs plan price/seats. // Logan.Li
func ensureOpsXShellPlanPricing(db *gorm.DB) error {
var product model.Product
err := db.Where("code = ?", "opsxshell").First(&product).Error
if err != nil {
// Prefer renaming legacy demo product so existing plans/licenses stay attached.
if renameErr := db.Where("code = ?", "opsx-tools").First(&product).Error; renameErr == nil {
product.Code = "opsxshell"
product.Name = "OpsXShell"
product.Description = "OpsXShell Pro — SSH / SFTP / RDP / VNC / AI"
product.Features = `["ssh","sftp","rdp","vnc","ai","portForward","proxy","serial","batchExec"]`
product.Price = 199
product.PriceModel = "perpetual"
product.Status = 1
if err := db.Save(&product).Error; err != nil {
return err
}
log.Println("[初始化] 已将 opsx-tools 迁移为 opsxshell")
} else {
product = model.Product{
Name: "OpsXShell",
Code: "opsxshell",
Version: "2.0.0",
Description: "OpsXShell Pro — SSH / SFTP / RDP / VNC / AI",
Features: `["ssh","sftp","rdp","vnc","ai","portForward","proxy","serial","batchExec"]`,
Price: 199,
PriceModel: "perpetual",
Status: 1,
}
if err := db.Create(&product).Error; err != nil {
return err
}
log.Println("[初始化] 已创建 opsxshell 产品")
}
}
type planSpec struct {
planType string
name string
price float64
currency string
duration int
maxDevices int
features string
sortOrder int
}
updates := []planSpec{
{"yearly", "Yearly", 99, "CNY", 365, 2, "[\"专业功能\",\"包年\",\"2 设备\"]", 1},
{"perpetual", "Perpetual", 199, "CNY", 0, 5, "[\"专业功能\",\"永久授权\",\"5 设备\"]", 2},
{"trial", "Trial 180d", 0, "CNY", 180, 1, "[\"专业功能\",\"半年体验\",\"1 设备\"]", 0},
}
for _, u := range updates {
var plan model.Plan
err := db.Where("product_id = ? AND plan_type = ?", product.ID, u.planType).First(&plan).Error
if err != nil {
plan = model.Plan{
ProductID: product.ID,
Name: u.name,
PlanType: u.planType,
Price: u.price,
Currency: u.currency,
Duration: u.duration,
MaxDevices: u.maxDevices,
Features: u.features,
SortOrder: u.sortOrder,
Status: 1,
}
if err := db.Create(&plan).Error; err != nil {
return err
}
continue
}
plan.Name = u.name
plan.Price = u.price
plan.Currency = u.currency
plan.Duration = u.duration
plan.MaxDevices = u.maxDevices
plan.Features = u.features
plan.SortOrder = u.sortOrder
plan.Status = 1
if err := db.Save(&plan).Error; err != nil {
return err
}
}
log.Printf("[初始化] OpsXShell 套餐已同步: 包年¥99/2台, 永久¥199/5台, 体验180天/1台")
return nil
}
// ensureWaffoPaymentGateway makes pancake.waffo.ai the preferred checkout gateway. // Logan.Li
func ensureWaffoPaymentGateway(db *gorm.DB) error {
var existing model.PaymentGatewayConfig
err := db.Where("gateway = ?", "waffo").First(&existing).Error
if err != nil {
gw := model.PaymentGatewayConfig{
Gateway: "waffo",
Enabled: true,
SandboxMode: true,
Currency: "CNY",
Priority: 0,
}
if err := db.Create(&gw).Error; err != nil {
return err
}
} else {
existing.Enabled = true
existing.SandboxMode = true
existing.Currency = "CNY"
existing.Priority = 0
if err := db.Save(&existing).Error; err != nil {
return err
}
}
// Lower priority for other enabled gateways (ASC pick prefers Waffo).
if err := db.Model(&model.PaymentGatewayConfig{}).
Where("gateway <> ? AND enabled = ?", "waffo", true).
Update("priority", 100).Error; err != nil {
return err
}
log.Println("[初始化] 支付网关已优先 Waffo(包年 subscription / 永久 onetime)")
return nil
}