package payment import ( "fmt" "net/url" "os" "strings" ) // WaffoConfig hosted checkout settings for pancake.waffo.ai // Logan.Li type WaffoConfig struct { CheckoutURL string StoreSlug string ProductID string ProductIDYearly string ProductIDPerpetual string Currency string // fallback / default CurrencyYearly string CurrencyPerpetual string Environment string SuccessURL string } // LoadWaffoConfig reads WAFFO_* env vars (same names as OpsXShell legacy license-server). // Logan.Li func LoadWaffoConfig() WaffoConfig { env := strings.TrimSpace(os.Getenv("WAFFO_ENVIRONMENT")) if env == "" { env = "test" } success := strings.TrimSpace(os.Getenv("SUCCESS_URL")) if success == "" { success = "opsxshell://pro/activated" } currency := strings.TrimSpace(os.Getenv("WAFFO_CURRENCY")) if currency == "" { currency = "CNY" } currencyYearly := strings.TrimSpace(os.Getenv("WAFFO_CURRENCY_YEARLY")) if currencyYearly == "" { currencyYearly = currency } currencyPerpetual := strings.TrimSpace(os.Getenv("WAFFO_CURRENCY_PERPETUAL")) if currencyPerpetual == "" { // 永久默认 USD(与 pancake 商品一致);可用环境变量覆盖。 // Logan.Li if currency == "CNY" { currencyPerpetual = "USD" } else { currencyPerpetual = currency } } defaultProduct := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID")) yearly := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID_YEARLY")) perpetual := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID_PERPETUAL")) if yearly == "" { yearly = defaultProduct } if perpetual == "" { perpetual = defaultProduct } return WaffoConfig{ CheckoutURL: strings.TrimSpace(os.Getenv("WAFFO_CHECKOUT_URL")), StoreSlug: strings.TrimSpace(os.Getenv("WAFFO_STORE_SLUG")), ProductID: defaultProduct, ProductIDYearly: yearly, ProductIDPerpetual: perpetual, Currency: currency, CurrencyYearly: currencyYearly, CurrencyPerpetual: currencyPerpetual, Environment: env, SuccessURL: success, } } // BuildCheckoutURL returns a Waffo hosted checkout URL with order/device/email metadata. // Logan.Li func BuildCheckoutURL(cfg WaffoConfig, orderNo, deviceID, email, returnURL, planType string) (string, error) { success := cfg.SuccessURL if strings.TrimSpace(returnURL) != "" { success = strings.TrimSpace(returnURL) } u, err := resolveBaseCheckoutURL(cfg, planType) if err != nil { return "", err } q := u.Query() if q.Get("successUrl") == "" { q.Set("successUrl", appendQuery(success, map[string]string{ "orderNo": orderNo, "deviceId": deviceID, "email": email, "planType": planType, })) } if deviceID != "" { q.Set("deviceId", deviceID) } if orderNo != "" { q.Set("orderNo", orderNo) } if email != "" { q.Set("email", email) } if planType != "" { q.Set("planType", planType) } u.RawQuery = q.Encode() return u.String(), nil } func productIDForPlan(cfg WaffoConfig, planType string) string { switch strings.TrimSpace(planType) { case "yearly", "monthly": return cfg.ProductIDYearly case "perpetual", "lifetime": return cfg.ProductIDPerpetual default: if cfg.ProductIDPerpetual != "" { return cfg.ProductIDPerpetual } return cfg.ProductID } } func currencyForPlan(cfg WaffoConfig, planType string) string { switch strings.TrimSpace(planType) { case "yearly", "monthly": if cfg.CurrencyYearly != "" { return cfg.CurrencyYearly } case "perpetual", "lifetime": if cfg.CurrencyPerpetual != "" { return cfg.CurrencyPerpetual } } if cfg.Currency != "" { return cfg.Currency } return "CNY" } func checkoutTypeForPlan(planType string) string { // OpsXShell 包年 / 永久均为 Waffo onetime 商品(包年不再走 subscription)。 // Logan.Li _ = planType return "onetime" } func resolveBaseCheckoutURL(cfg WaffoConfig, planType string) (*url.URL, error) { // Prefer store + per-plan product IDs so yearly/perpetual map to different Waffo SKUs. productID := productIDForPlan(cfg, planType) if cfg.StoreSlug != "" && productID != "" { raw := fmt.Sprintf("https://pancake.waffo.ai/store/%s/product/%s", cfg.StoreSlug, productID) u, err := url.Parse(raw) if err != nil { return nil, err } q := u.Query() q.Set("type", checkoutTypeForPlan(planType)) q.Set("currency", currencyForPlan(cfg, planType)) if cfg.Environment == "test" { q.Set("test", "true") } u.RawQuery = q.Encode() return u, nil } if cfg.CheckoutURL != "" { return url.Parse(cfg.CheckoutURL) } return nil, fmt.Errorf("未配置 WAFFO_STORE_SLUG 与 WAFFO_PRODUCT_ID_YEARLY / WAFFO_PRODUCT_ID_PERPETUAL(或 WAFFO_CHECKOUT_URL)") } func appendQuery(raw string, values map[string]string) string { u, err := url.Parse(raw) if err != nil { return raw } q := u.Query() for k, v := range values { if v != "" { q.Set(k, v) } } u.RawQuery = q.Encode() return u.String() }