fix: 包年 CNY / 永久 USD 分币种结账
按档位分别拼 Waffo currency,避免永久商品用 CNY 报不可用。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
d4c5637168
commit
e8d98c993f
3 files changed
+60
-20
No files matched your search
+4
-2
@@ -42,14 +42,16 @@ DB_NAME=db.sqlite3
|
|||||||
# Waffo 支付(OpsXShell 收银台 · pancake.waffo.ai)
|
# Waffo 支付(OpsXShell 收银台 · pancake.waffo.ai)
|
||||||
# ============================================
|
# ============================================
|
||||||
# 店铺: https://pancake.waffo.ai/store/opsxshell-pwsi6m4n
|
# 店铺: https://pancake.waffo.ai/store/opsxshell-pwsi6m4n
|
||||||
# 包年一次性: PROD_1BIezCi932IbgCzRIV8hcJ (type=onetime)
|
# 包年一次性: PROD_1BIezCi932IbgCzRIV8hcJ (type=onetime, currency=CNY)
|
||||||
# 永久买断: PROD_0pcnzzT4nalEkkcdyIUoD5 (type=onetime)
|
# 永久买断: PROD_0pcnzzT4nalEkkcdyIUoD5 (type=onetime, currency=USD)
|
||||||
# 勿设置单一 WAFFO_CHECKOUT_URL,否则无法按档位分流商品
|
# 勿设置单一 WAFFO_CHECKOUT_URL,否则无法按档位分流商品
|
||||||
WAFFO_STORE_SLUG=opsxshell-pwsi6m4n
|
WAFFO_STORE_SLUG=opsxshell-pwsi6m4n
|
||||||
WAFFO_PRODUCT_ID=PROD_0pcnzzT4nalEkkcdyIUoD5
|
WAFFO_PRODUCT_ID=PROD_0pcnzzT4nalEkkcdyIUoD5
|
||||||
WAFFO_PRODUCT_ID_YEARLY=PROD_1BIezCi932IbgCzRIV8hcJ
|
WAFFO_PRODUCT_ID_YEARLY=PROD_1BIezCi932IbgCzRIV8hcJ
|
||||||
WAFFO_PRODUCT_ID_PERPETUAL=PROD_0pcnzzT4nalEkkcdyIUoD5
|
WAFFO_PRODUCT_ID_PERPETUAL=PROD_0pcnzzT4nalEkkcdyIUoD5
|
||||||
WAFFO_CURRENCY=CNY
|
WAFFO_CURRENCY=CNY
|
||||||
|
WAFFO_CURRENCY_YEARLY=CNY
|
||||||
|
WAFFO_CURRENCY_PERPETUAL=USD
|
||||||
# test | live(test 时自动附加 test=true)
|
# test | live(test 时自动附加 test=true)
|
||||||
WAFFO_ENVIRONMENT=test
|
WAFFO_ENVIRONMENT=test
|
||||||
# 支付成功回跳(桌面端 deep link;CreateOrder/Checkout 的 return_url 可覆盖)
|
# 支付成功回跳(桌面端 deep link;CreateOrder/Checkout 的 return_url 可覆盖)
|
||||||
|
|||||||
@@ -9,14 +9,16 @@ import (
|
|||||||
|
|
||||||
// WaffoConfig hosted checkout settings for pancake.waffo.ai // Logan.Li
|
// WaffoConfig hosted checkout settings for pancake.waffo.ai // Logan.Li
|
||||||
type WaffoConfig struct {
|
type WaffoConfig struct {
|
||||||
CheckoutURL string
|
CheckoutURL string
|
||||||
StoreSlug string
|
StoreSlug string
|
||||||
ProductID string
|
ProductID string
|
||||||
ProductIDYearly string
|
ProductIDYearly string
|
||||||
ProductIDPerpetual string
|
ProductIDPerpetual string
|
||||||
Currency string
|
Currency string // fallback / default
|
||||||
Environment string
|
CurrencyYearly string
|
||||||
SuccessURL string
|
CurrencyPerpetual string
|
||||||
|
Environment string
|
||||||
|
SuccessURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadWaffoConfig reads WAFFO_* env vars (same names as OpsXShell legacy license-server). // Logan.Li
|
// LoadWaffoConfig reads WAFFO_* env vars (same names as OpsXShell legacy license-server). // Logan.Li
|
||||||
@@ -33,6 +35,19 @@ func LoadWaffoConfig() WaffoConfig {
|
|||||||
if currency == "" {
|
if currency == "" {
|
||||||
currency = "CNY"
|
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"))
|
defaultProduct := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID"))
|
||||||
yearly := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID_YEARLY"))
|
yearly := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID_YEARLY"))
|
||||||
perpetual := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID_PERPETUAL"))
|
perpetual := strings.TrimSpace(os.Getenv("WAFFO_PRODUCT_ID_PERPETUAL"))
|
||||||
@@ -49,6 +64,8 @@ func LoadWaffoConfig() WaffoConfig {
|
|||||||
ProductIDYearly: yearly,
|
ProductIDYearly: yearly,
|
||||||
ProductIDPerpetual: perpetual,
|
ProductIDPerpetual: perpetual,
|
||||||
Currency: currency,
|
Currency: currency,
|
||||||
|
CurrencyYearly: currencyYearly,
|
||||||
|
CurrencyPerpetual: currencyPerpetual,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
SuccessURL: success,
|
SuccessURL: success,
|
||||||
}
|
}
|
||||||
@@ -103,6 +120,23 @@ func productIDForPlan(cfg WaffoConfig, planType string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func checkoutTypeForPlan(planType string) string {
|
||||||
// OpsXShell 包年 / 永久均为 Waffo onetime 商品(包年不再走 subscription)。 // Logan.Li
|
// OpsXShell 包年 / 永久均为 Waffo onetime 商品(包年不再走 subscription)。 // Logan.Li
|
||||||
_ = planType
|
_ = planType
|
||||||
@@ -110,7 +144,6 @@ func checkoutTypeForPlan(planType string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resolveBaseCheckoutURL(cfg WaffoConfig, planType string) (*url.URL, error) {
|
func resolveBaseCheckoutURL(cfg WaffoConfig, planType string) (*url.URL, error) {
|
||||||
// Single WAFFO_CHECKOUT_URL only as absolute fallback when no store/products configured.
|
|
||||||
// Prefer store + per-plan product IDs so yearly/perpetual map to different Waffo SKUs.
|
// Prefer store + per-plan product IDs so yearly/perpetual map to different Waffo SKUs.
|
||||||
productID := productIDForPlan(cfg, planType)
|
productID := productIDForPlan(cfg, planType)
|
||||||
if cfg.StoreSlug != "" && productID != "" {
|
if cfg.StoreSlug != "" && productID != "" {
|
||||||
@@ -121,7 +154,7 @@ func resolveBaseCheckoutURL(cfg WaffoConfig, planType string) (*url.URL, error)
|
|||||||
}
|
}
|
||||||
q := u.Query()
|
q := u.Query()
|
||||||
q.Set("type", checkoutTypeForPlan(planType))
|
q.Set("type", checkoutTypeForPlan(planType))
|
||||||
q.Set("currency", cfg.Currency)
|
q.Set("currency", currencyForPlan(cfg, planType))
|
||||||
if cfg.Environment == "test" {
|
if cfg.Environment == "test" {
|
||||||
q.Set("test", "true")
|
q.Set("test", "true")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildCheckoutURL_YearlyOnetime(t *testing.T) {
|
func TestBuildCheckoutURL_YearlyOnetimeCNY(t *testing.T) {
|
||||||
cfg := WaffoConfig{
|
cfg := WaffoConfig{
|
||||||
StoreSlug: "opsxshell-pwsi6m4n",
|
StoreSlug: "opsxshell-pwsi6m4n",
|
||||||
ProductIDYearly: "PROD_1BIezCi932IbgCzRIV8hcJ",
|
ProductIDYearly: "PROD_1BIezCi932IbgCzRIV8hcJ",
|
||||||
ProductIDPerpetual: "PROD_0pcnzzT4nalEkkcdyIUoD5",
|
ProductIDPerpetual: "PROD_0pcnzzT4nalEkkcdyIUoD5",
|
||||||
Currency: "CNY",
|
Currency: "CNY",
|
||||||
|
CurrencyYearly: "CNY",
|
||||||
|
CurrencyPerpetual: "USD",
|
||||||
Environment: "test",
|
Environment: "test",
|
||||||
SuccessURL: "opsxshell://pro/activated",
|
SuccessURL: "opsxshell://pro/activated",
|
||||||
}
|
}
|
||||||
@@ -31,19 +33,18 @@ func TestBuildCheckoutURL_YearlyOnetime(t *testing.T) {
|
|||||||
t.Fatalf("type=%s want onetime", q.Get("type"))
|
t.Fatalf("type=%s want onetime", q.Get("type"))
|
||||||
}
|
}
|
||||||
if q.Get("currency") != "CNY" {
|
if q.Get("currency") != "CNY" {
|
||||||
t.Fatalf("currency=%s", q.Get("currency"))
|
t.Fatalf("currency=%s want CNY", q.Get("currency"))
|
||||||
}
|
|
||||||
if q.Get("test") != "true" {
|
|
||||||
t.Fatalf("test=%s", q.Get("test"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildCheckoutURL_PerpetualOnetime(t *testing.T) {
|
func TestBuildCheckoutURL_PerpetualOnetimeUSD(t *testing.T) {
|
||||||
cfg := WaffoConfig{
|
cfg := WaffoConfig{
|
||||||
StoreSlug: "opsxshell-pwsi6m4n",
|
StoreSlug: "opsxshell-pwsi6m4n",
|
||||||
ProductIDYearly: "PROD_1BIezCi932IbgCzRIV8hcJ",
|
ProductIDYearly: "PROD_1BIezCi932IbgCzRIV8hcJ",
|
||||||
ProductIDPerpetual: "PROD_0pcnzzT4nalEkkcdyIUoD5",
|
ProductIDPerpetual: "PROD_0pcnzzT4nalEkkcdyIUoD5",
|
||||||
Currency: "CNY",
|
Currency: "CNY",
|
||||||
|
CurrencyYearly: "CNY",
|
||||||
|
CurrencyPerpetual: "USD",
|
||||||
Environment: "test",
|
Environment: "test",
|
||||||
SuccessURL: "opsxshell://pro/activated",
|
SuccessURL: "opsxshell://pro/activated",
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,11 @@ func TestBuildCheckoutURL_PerpetualOnetime(t *testing.T) {
|
|||||||
if !strings.Contains(u.Path, "PROD_0pcnzzT4nalEkkcdyIUoD5") {
|
if !strings.Contains(u.Path, "PROD_0pcnzzT4nalEkkcdyIUoD5") {
|
||||||
t.Fatalf("perpetual product missing: %s", raw)
|
t.Fatalf("perpetual product missing: %s", raw)
|
||||||
}
|
}
|
||||||
if u.Query().Get("type") != "onetime" {
|
q := u.Query()
|
||||||
t.Fatalf("type=%s want onetime", u.Query().Get("type"))
|
if q.Get("type") != "onetime" {
|
||||||
|
t.Fatalf("type=%s want onetime", q.Get("type"))
|
||||||
|
}
|
||||||
|
if q.Get("currency") != "USD" {
|
||||||
|
t.Fatalf("currency=%s want USD", q.Get("currency"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user