fix: 修复支付页测试/生产 Product 映射串用与无法保存
切换环境时先暂存再切换;后端 Profile 不再串用另一侧商品;支持一键填充 OpsXshell 与跨环境复制,调试使用当前页映射。
This commit is contained in:
1 parent
9b79eabd34
commit
7c146cb639
5 files changed
+313
-67
No files matched your search
@@ -89,6 +89,7 @@ type WaffoProductMapping struct {
|
|||||||
PlanKey string `json:"plan_key"`
|
PlanKey string `json:"plan_key"`
|
||||||
Label string `json:"label,omitempty"`
|
Label string `json:"label,omitempty"`
|
||||||
ProductID string `json:"product_id"`
|
ProductID string `json:"product_id"`
|
||||||
|
Currency string `json:"currency,omitempty"` // 如 CNY / USD,空则用全局默认 // Logan.Li
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaffoSettingsResponse 管理端 Waffo 配置(私钥不回显) // Logan.Li
|
// WaffoSettingsResponse 管理端 Waffo 配置(私钥不回显) // Logan.Li
|
||||||
@@ -130,6 +131,8 @@ type WaffoSettingsUpdate struct {
|
|||||||
StoreSlug string `json:"store_slug"`
|
StoreSlug string `json:"store_slug"`
|
||||||
Environment string `json:"environment"` // test | live | prod
|
Environment string `json:"environment"` // test | live | prod
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
|
CurrencyYearly string `json:"currency_yearly"`
|
||||||
|
CurrencyPerpetual string `json:"currency_perpetual"`
|
||||||
ProductID string `json:"product_id"`
|
ProductID string `json:"product_id"`
|
||||||
ProductIDYearly string `json:"product_id_yearly"`
|
ProductIDYearly string `json:"product_id_yearly"`
|
||||||
ProductIDPerpetual string `json:"product_id_perpetual"`
|
ProductIDPerpetual string `json:"product_id_perpetual"`
|
||||||
@@ -145,6 +148,13 @@ type WaffoDebugRequest struct {
|
|||||||
PlanType string `json:"plan_type"` // yearly | perpetual | …,默认 yearly
|
PlanType string `json:"plan_type"` // yearly | perpetual | …,默认 yearly
|
||||||
ProductID string `json:"product_id"` // 可选覆盖
|
ProductID string `json:"product_id"` // 可选覆盖
|
||||||
BuyerEmail string `json:"buyer_email"` // 可选
|
BuyerEmail string `json:"buyer_email"` // 可选
|
||||||
|
Environment string `json:"environment"` // 可选:用页面当前环境覆盖 DB
|
||||||
|
ProductIDYearly string `json:"product_id_yearly"`
|
||||||
|
ProductIDPerpetual string `json:"product_id_perpetual"`
|
||||||
|
ProductMappings []WaffoProductMapping `json:"product_mappings"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
CurrencyYearly string `json:"currency_yearly"`
|
||||||
|
CurrencyPerpetual string `json:"currency_perpetual"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaffoDebugResponse 调试结果摘要(绝不含私钥) // Logan.Li
|
// WaffoDebugResponse 调试结果摘要(绝不含私钥) // Logan.Li
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type WaffoProductMapping struct {
|
|||||||
PlanKey string `json:"plan_key"`
|
PlanKey string `json:"plan_key"`
|
||||||
Label string `json:"label,omitempty"`
|
Label string `json:"label,omitempty"`
|
||||||
ProductID string `json:"product_id"`
|
ProductID string `json:"product_id"`
|
||||||
|
Currency string `json:"currency,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaffoPublicMeta 存入 payment_gateway_configs.public_key 的 JSON(无私钥) // Logan.Li
|
// WaffoPublicMeta 存入 payment_gateway_configs.public_key 的 JSON(无私钥) // Logan.Li
|
||||||
@@ -416,6 +417,11 @@ func ResolveProductID(cfg WaffoConfig, planType string) string {
|
|||||||
return productIDForPlan(cfg, planType)
|
return productIDForPlan(cfg, planType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveCurrency 按 plan_type 解析结算货币 // Logan.Li
|
||||||
|
func ResolveCurrency(cfg WaffoConfig, planType string) string {
|
||||||
|
return currencyForPlan(cfg, planType)
|
||||||
|
}
|
||||||
|
|
||||||
func productIDForPlan(cfg WaffoConfig, planType string) string {
|
func productIDForPlan(cfg WaffoConfig, planType string) string {
|
||||||
plan := strings.TrimSpace(strings.ToLower(planType))
|
plan := strings.TrimSpace(strings.ToLower(planType))
|
||||||
if plan == "" {
|
if plan == "" {
|
||||||
|
|||||||
@@ -86,7 +86,11 @@ func CreateCheckoutSession(cfg WaffoConfig, req CreateSessionRequest) (*CreateSe
|
|||||||
if env == "" {
|
if env == "" {
|
||||||
env = strings.TrimSpace(cfg.Environment)
|
env = strings.TrimSpace(cfg.Environment)
|
||||||
}
|
}
|
||||||
if env == "" {
|
// Waffo Store Slug 鉴权要求 X-Environment 为 test|prod(不是 live) // Logan.Li
|
||||||
|
switch strings.ToLower(env) {
|
||||||
|
case "live", "prod", "production":
|
||||||
|
env = "prod"
|
||||||
|
default:
|
||||||
env = "test"
|
env = "test"
|
||||||
}
|
}
|
||||||
httpReq.Header.Set("X-Store-Slug", slug)
|
httpReq.Header.Set("X-Store-Slug", slug)
|
||||||
|
|||||||
@@ -122,10 +122,17 @@ func (s *SettingService) GetWaffo() (*dto.WaffoSettingsResponse, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildEnvUI 组装单环境预览 // Logan.Li
|
// buildEnvUI 组装单环境预览(不得串用另一环境顶层映射) // Logan.Li
|
||||||
func buildEnvUI(meta payment.WaffoPublicMeta, bag payment.WaffoSecretBag, env string) dto.WaffoEnvUI {
|
func buildEnvUI(meta payment.WaffoPublicMeta, bag payment.WaffoSecretBag, env string) dto.WaffoEnvUI {
|
||||||
cp := meta
|
cp := meta
|
||||||
|
// 先清空商品字段,再叠该环境 Profile,避免未配置环境显示成另一侧数据 // Logan.Li
|
||||||
|
cp.ProductID = ""
|
||||||
|
cp.ProductIDYearly = ""
|
||||||
|
cp.ProductIDPerpetual = ""
|
||||||
|
cp.ProductIDs = nil
|
||||||
|
cp.ProductMappings = nil
|
||||||
cp.ApplyEnvProfile(env)
|
cp.ApplyEnvProfile(env)
|
||||||
|
|
||||||
cfgLike := payment.WaffoConfig{
|
cfgLike := payment.WaffoConfig{
|
||||||
ProductID: cp.ProductID,
|
ProductID: cp.ProductID,
|
||||||
ProductIDYearly: cp.ProductIDYearly,
|
ProductIDYearly: cp.ProductIDYearly,
|
||||||
@@ -146,9 +153,9 @@ func buildEnvUI(meta payment.WaffoPublicMeta, bag payment.WaffoSecretBag, env st
|
|||||||
}
|
}
|
||||||
return dto.WaffoEnvUI{
|
return dto.WaffoEnvUI{
|
||||||
ProductMappings: buildProductMappings(cfgLike, cp.ProductMappings),
|
ProductMappings: buildProductMappings(cfgLike, cp.ProductMappings),
|
||||||
ProductID: cp.ProductID,
|
ProductID: firstNonEmpty(cp.ProductID, cfgLike.ProductIDs["perpetual"], cfgLike.ProductIDs["lifetime"], cfgLike.ProductIDs["yearly"]),
|
||||||
ProductIDYearly: cp.ProductIDYearly,
|
ProductIDYearly: firstNonEmpty(cp.ProductIDYearly, cfgLike.ProductIDs["yearly"]),
|
||||||
ProductIDPerpetual: cp.ProductIDPerpetual,
|
ProductIDPerpetual: firstNonEmpty(cp.ProductIDPerpetual, cfgLike.ProductIDs["perpetual"], cfgLike.ProductIDs["lifetime"]),
|
||||||
PrivateKeyConfigured: bag.SecretForEnv(env) != "",
|
PrivateKeyConfigured: bag.SecretForEnv(env) != "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,8 +195,8 @@ func (s *SettingService) UpdateWaffo(req dto.WaffoSettingsUpdate) (*dto.WaffoSet
|
|||||||
if meta.Currency == "" {
|
if meta.Currency == "" {
|
||||||
meta.Currency = "CNY"
|
meta.Currency = "CNY"
|
||||||
}
|
}
|
||||||
meta.CurrencyYearly = meta.Currency
|
meta.CurrencyYearly = firstNonEmpty(strings.TrimSpace(req.CurrencyYearly), currencyFromMappings(mappings, "yearly"), meta.Currency)
|
||||||
meta.CurrencyPerpetual = meta.Currency
|
meta.CurrencyPerpetual = firstNonEmpty(strings.TrimSpace(req.CurrencyPerpetual), currencyFromMappings(mappings, "perpetual"), currencyFromMappings(mappings, "lifetime"), meta.Currency)
|
||||||
meta.Environment = newEnv
|
meta.Environment = newEnv
|
||||||
meta.ProductID = strings.TrimSpace(req.ProductID)
|
meta.ProductID = strings.TrimSpace(req.ProductID)
|
||||||
meta.ProductIDYearly = strings.TrimSpace(req.ProductIDYearly)
|
meta.ProductIDYearly = strings.TrimSpace(req.ProductIDYearly)
|
||||||
@@ -258,6 +265,18 @@ func (s *SettingService) UpdateWaffo(req dto.WaffoSettingsUpdate) (*dto.WaffoSet
|
|||||||
// DebugWaffo 用当前 DB/env 配置轻量探测 create-session(不回显私钥) // Logan.Li
|
// DebugWaffo 用当前 DB/env 配置轻量探测 create-session(不回显私钥) // Logan.Li
|
||||||
func (s *SettingService) DebugWaffo(req dto.WaffoDebugRequest) *dto.WaffoDebugResponse {
|
func (s *SettingService) DebugWaffo(req dto.WaffoDebugRequest) *dto.WaffoDebugResponse {
|
||||||
cfg := payment.LoadWaffoConfig(s.db)
|
cfg := payment.LoadWaffoConfig(s.db)
|
||||||
|
// 页面未保存的环境 / 映射优先覆盖,避免「填了映射却仍用旧 Product」 // Logan.Li
|
||||||
|
if strings.TrimSpace(req.Environment) != "" {
|
||||||
|
env := payment.NormalizeWaffoEnvironment(req.Environment)
|
||||||
|
cfg.Environment = env
|
||||||
|
var gw model.PaymentGatewayConfig
|
||||||
|
if err := s.db.Where("gateway = ?", "waffo").First(&gw).Error; err == nil {
|
||||||
|
bag := payment.ParseWaffoSecretBag(gw.SecretKey, env)
|
||||||
|
cfg.PrivateKey = bag.SecretForEnv(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
applyDebugProductOverrides(&cfg, req)
|
||||||
|
|
||||||
planType := strings.TrimSpace(strings.ToLower(req.PlanType))
|
planType := strings.TrimSpace(strings.ToLower(req.PlanType))
|
||||||
if planType == "" {
|
if planType == "" {
|
||||||
planType = "yearly"
|
planType = "yearly"
|
||||||
@@ -286,7 +305,7 @@ func (s *SettingService) DebugWaffo(req dto.WaffoDebugRequest) *dto.WaffoDebugRe
|
|||||||
|
|
||||||
if productID == "" {
|
if productID == "" {
|
||||||
out.OK = false
|
out.OK = false
|
||||||
out.ErrorSummary = "未配置可用的 Waffo Product ID"
|
out.ErrorSummary = "未配置可用的 Waffo Product ID(请在当前环境映射表填写后先保存,或点「填充 OpsXshell 商品」)"
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
if authMode == "none" {
|
if authMode == "none" {
|
||||||
@@ -304,11 +323,15 @@ func (s *SettingService) DebugWaffo(req dto.WaffoDebugRequest) *dto.WaffoDebugRe
|
|||||||
if successURL == "" {
|
if successURL == "" {
|
||||||
successURL = payment.DefaultSuccessURL
|
successURL = payment.DefaultSuccessURL
|
||||||
}
|
}
|
||||||
|
currency := payment.ResolveCurrency(cfg, planType)
|
||||||
|
if c := strings.TrimSpace(req.Currency); c != "" && planType != "yearly" && planType != "perpetual" && planType != "lifetime" {
|
||||||
|
currency = c
|
||||||
|
}
|
||||||
|
|
||||||
session, err := payment.CreateCheckoutSession(cfg, payment.CreateSessionRequest{
|
session, err := payment.CreateCheckoutSession(cfg, payment.CreateSessionRequest{
|
||||||
ProductID: productID,
|
ProductID: productID,
|
||||||
ProductType: "onetime",
|
ProductType: "onetime",
|
||||||
Currency: firstNonEmpty(cfg.Currency, "CNY"),
|
Currency: currency,
|
||||||
BuyerEmail: buyer,
|
BuyerEmail: buyer,
|
||||||
SuccessURL: successURL,
|
SuccessURL: successURL,
|
||||||
OrderMerchantExternalID: orderNo,
|
OrderMerchantExternalID: orderNo,
|
||||||
@@ -325,6 +348,9 @@ func (s *SettingService) DebugWaffo(req dto.WaffoDebugRequest) *dto.WaffoDebugRe
|
|||||||
out.OK = false
|
out.OK = false
|
||||||
out.ErrorSummary = sanitizeDebugError(err.Error())
|
out.ErrorSummary = sanitizeDebugError(err.Error())
|
||||||
out.HTTPStatus = extractHTTPStatus(err.Error())
|
out.HTTPStatus = extractHTTPStatus(err.Error())
|
||||||
|
if strings.Contains(out.ErrorSummary, "not approved for production") {
|
||||||
|
out.ErrorSummary += "(Waffo 门店尚未开通生产收款:请先在测试环境验证;生产需在 Waffo Dashboard 完成 Go-Live)"
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,6 +363,55 @@ func (s *SettingService) DebugWaffo(req dto.WaffoDebugRequest) *dto.WaffoDebugRe
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyDebugProductOverrides 把调试请求中的映射叠到运行时配置 // Logan.Li
|
||||||
|
func applyDebugProductOverrides(cfg *payment.WaffoConfig, req dto.WaffoDebugRequest) {
|
||||||
|
if cfg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cfg.ProductIDs == nil {
|
||||||
|
cfg.ProductIDs = map[string]string{}
|
||||||
|
}
|
||||||
|
for _, m := range req.ProductMappings {
|
||||||
|
k := strings.TrimSpace(strings.ToLower(m.PlanKey))
|
||||||
|
v := strings.TrimSpace(m.ProductID)
|
||||||
|
if k == "" || v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cfg.ProductIDs[k] = v
|
||||||
|
switch k {
|
||||||
|
case "yearly", "monthly":
|
||||||
|
cfg.ProductIDYearly = v
|
||||||
|
if c := strings.TrimSpace(m.Currency); c != "" {
|
||||||
|
cfg.CurrencyYearly = c
|
||||||
|
}
|
||||||
|
case "perpetual", "lifetime":
|
||||||
|
cfg.ProductIDPerpetual = v
|
||||||
|
cfg.ProductID = v
|
||||||
|
if c := strings.TrimSpace(m.Currency); c != "" {
|
||||||
|
cfg.CurrencyPerpetual = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if y := strings.TrimSpace(req.ProductIDYearly); y != "" {
|
||||||
|
cfg.ProductIDYearly = y
|
||||||
|
cfg.ProductIDs["yearly"] = y
|
||||||
|
}
|
||||||
|
if p := strings.TrimSpace(req.ProductIDPerpetual); p != "" {
|
||||||
|
cfg.ProductIDPerpetual = p
|
||||||
|
cfg.ProductID = p
|
||||||
|
cfg.ProductIDs["perpetual"] = p
|
||||||
|
}
|
||||||
|
if c := strings.TrimSpace(req.Currency); c != "" {
|
||||||
|
cfg.Currency = c
|
||||||
|
}
|
||||||
|
if c := strings.TrimSpace(req.CurrencyYearly); c != "" {
|
||||||
|
cfg.CurrencyYearly = c
|
||||||
|
}
|
||||||
|
if c := strings.TrimSpace(req.CurrencyPerpetual); c != "" {
|
||||||
|
cfg.CurrencyPerpetual = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mergeProductIDInputs(req dto.WaffoSettingsUpdate) (map[string]string, []dto.WaffoProductMapping) {
|
func mergeProductIDInputs(req dto.WaffoSettingsUpdate) (map[string]string, []dto.WaffoProductMapping) {
|
||||||
ids := map[string]string{}
|
ids := map[string]string{}
|
||||||
var mappings []dto.WaffoProductMapping
|
var mappings []dto.WaffoProductMapping
|
||||||
@@ -352,6 +427,7 @@ func mergeProductIDInputs(req dto.WaffoSettingsUpdate) (map[string]string, []dto
|
|||||||
PlanKey: k,
|
PlanKey: k,
|
||||||
Label: strings.TrimSpace(m.Label),
|
Label: strings.TrimSpace(m.Label),
|
||||||
ProductID: v,
|
ProductID: v,
|
||||||
|
Currency: strings.ToUpper(strings.TrimSpace(m.Currency)),
|
||||||
})
|
})
|
||||||
if v != "" {
|
if v != "" {
|
||||||
ids[k] = v
|
ids[k] = v
|
||||||
@@ -387,7 +463,7 @@ func mergeProductIDInputs(req dto.WaffoSettingsUpdate) (map[string]string, []dto
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(mappings) == 0 {
|
if len(mappings) == 0 {
|
||||||
mappings = mappingsFromIDs(ids, nil)
|
mappings = mappingsFromIDs(ids, nil, nil, payment.WaffoConfig{})
|
||||||
} else {
|
} else {
|
||||||
// 用最终 ids 回填表格行 product_id,避免只改了兼容字段 // Logan.Li
|
// 用最终 ids 回填表格行 product_id,避免只改了兼容字段 // Logan.Li
|
||||||
for i := range mappings {
|
for i := range mappings {
|
||||||
@@ -401,6 +477,7 @@ func mergeProductIDInputs(req dto.WaffoSettingsUpdate) (map[string]string, []dto
|
|||||||
|
|
||||||
func buildProductMappings(cfg payment.WaffoConfig, stored []payment.WaffoProductMapping) []dto.WaffoProductMapping {
|
func buildProductMappings(cfg payment.WaffoConfig, stored []payment.WaffoProductMapping) []dto.WaffoProductMapping {
|
||||||
labelHint := map[string]string{}
|
labelHint := map[string]string{}
|
||||||
|
currencyHint := map[string]string{}
|
||||||
for _, m := range stored {
|
for _, m := range stored {
|
||||||
k := strings.TrimSpace(strings.ToLower(m.PlanKey))
|
k := strings.TrimSpace(strings.ToLower(m.PlanKey))
|
||||||
if k == "" {
|
if k == "" {
|
||||||
@@ -409,15 +486,55 @@ func buildProductMappings(cfg payment.WaffoConfig, stored []payment.WaffoProduct
|
|||||||
if l := strings.TrimSpace(m.Label); l != "" {
|
if l := strings.TrimSpace(m.Label); l != "" {
|
||||||
labelHint[k] = l
|
labelHint[k] = l
|
||||||
}
|
}
|
||||||
|
if c := strings.TrimSpace(m.Currency); c != "" {
|
||||||
|
currencyHint[k] = strings.ToUpper(c)
|
||||||
}
|
}
|
||||||
return mappingsFromIDs(cfg.ProductIDs, labelHint)
|
}
|
||||||
|
// 若 stored 有完整行(含空 product 也应展示),优先按 stored 顺序回显 // Logan.Li
|
||||||
|
if len(stored) > 0 {
|
||||||
|
out := make([]dto.WaffoProductMapping, 0, len(stored))
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, m := range stored {
|
||||||
|
k := strings.TrimSpace(strings.ToLower(m.PlanKey))
|
||||||
|
if k == "" || seen[k] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[k] = true
|
||||||
|
pid := strings.TrimSpace(m.ProductID)
|
||||||
|
if pid == "" {
|
||||||
|
pid = strings.TrimSpace(cfg.ProductIDs[k])
|
||||||
|
}
|
||||||
|
cur := strings.TrimSpace(m.Currency)
|
||||||
|
if cur == "" {
|
||||||
|
cur = currencyHint[k]
|
||||||
|
}
|
||||||
|
if cur == "" {
|
||||||
|
switch k {
|
||||||
|
case "yearly", "monthly":
|
||||||
|
cur = cfg.CurrencyYearly
|
||||||
|
case "perpetual", "lifetime":
|
||||||
|
cur = cfg.CurrencyPerpetual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, dto.WaffoProductMapping{
|
||||||
|
PlanKey: k,
|
||||||
|
Label: firstNonEmpty(strings.TrimSpace(m.Label), labelHint[k]),
|
||||||
|
ProductID: pid,
|
||||||
|
Currency: strings.ToUpper(strings.TrimSpace(cur)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(out) > 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mappingsFromIDs(cfg.ProductIDs, labelHint, currencyHint, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mappingsFromIDs(ids map[string]string, labels map[string]string) []dto.WaffoProductMapping {
|
func mappingsFromIDs(ids map[string]string, labels, currencies map[string]string, cfg payment.WaffoConfig) []dto.WaffoProductMapping {
|
||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
return []dto.WaffoProductMapping{
|
return []dto.WaffoProductMapping{
|
||||||
{PlanKey: "yearly", Label: "包年"},
|
{PlanKey: "yearly", Label: "包年", Currency: "CNY"},
|
||||||
{PlanKey: "perpetual", Label: "永久"},
|
{PlanKey: "perpetual", Label: "永久", Currency: "USD"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defaultLabels := map[string]string{
|
defaultLabels := map[string]string{
|
||||||
@@ -445,7 +562,26 @@ func mappingsFromIDs(ids map[string]string, labels map[string]string) []dto.Waff
|
|||||||
if label == "" {
|
if label == "" {
|
||||||
label = defaultLabels[k]
|
label = defaultLabels[k]
|
||||||
}
|
}
|
||||||
out = append(out, dto.WaffoProductMapping{PlanKey: k, Label: label, ProductID: v})
|
cur := ""
|
||||||
|
if currencies != nil {
|
||||||
|
cur = currencies[k]
|
||||||
|
}
|
||||||
|
if cur == "" {
|
||||||
|
switch k {
|
||||||
|
case "yearly", "monthly":
|
||||||
|
cur = cfg.CurrencyYearly
|
||||||
|
case "perpetual", "lifetime":
|
||||||
|
cur = cfg.CurrencyPerpetual
|
||||||
|
default:
|
||||||
|
cur = cfg.Currency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, dto.WaffoProductMapping{
|
||||||
|
PlanKey: k,
|
||||||
|
Label: label,
|
||||||
|
ProductID: v,
|
||||||
|
Currency: strings.ToUpper(strings.TrimSpace(cur)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
for _, k := range order {
|
for _, k := range order {
|
||||||
appendOne(k)
|
appendOne(k)
|
||||||
@@ -470,8 +606,8 @@ func mappingsFromIDs(ids map[string]string, labels map[string]string) []dto.Waff
|
|||||||
}
|
}
|
||||||
if len(out) == 0 {
|
if len(out) == 0 {
|
||||||
return []dto.WaffoProductMapping{
|
return []dto.WaffoProductMapping{
|
||||||
{PlanKey: "yearly", Label: "包年"},
|
{PlanKey: "yearly", Label: "包年", Currency: "CNY"},
|
||||||
{PlanKey: "perpetual", Label: "永久"},
|
{PlanKey: "perpetual", Label: "永久", Currency: "USD"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
@@ -488,11 +624,23 @@ func toPaymentMappings(in []dto.WaffoProductMapping) []payment.WaffoProductMappi
|
|||||||
PlanKey: k,
|
PlanKey: k,
|
||||||
Label: strings.TrimSpace(m.Label),
|
Label: strings.TrimSpace(m.Label),
|
||||||
ProductID: strings.TrimSpace(m.ProductID),
|
ProductID: strings.TrimSpace(m.ProductID),
|
||||||
|
Currency: strings.ToUpper(strings.TrimSpace(m.Currency)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// currencyFromMappings 从映射表取指定 plan 的货币 // Logan.Li
|
||||||
|
func currencyFromMappings(mappings []dto.WaffoProductMapping, plan string) string {
|
||||||
|
plan = strings.TrimSpace(strings.ToLower(plan))
|
||||||
|
for _, m := range mappings {
|
||||||
|
if strings.TrimSpace(strings.ToLower(m.PlanKey)) == plan {
|
||||||
|
return strings.ToUpper(strings.TrimSpace(m.Currency))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func cloneProductIDs(in map[string]string) map[string]string {
|
func cloneProductIDs(in map[string]string) map[string]string {
|
||||||
if len(in) == 0 {
|
if len(in) == 0 {
|
||||||
return map[string]string{}
|
return map[string]string{}
|
||||||
|
|||||||
+128
-50
@@ -8,6 +8,10 @@
|
|||||||
<el-tag size="small" type="info" style="margin-left: 8px">后台配置优先于 .env</el-tag>
|
<el-tag size="small" type="info" style="margin-left: 8px">后台配置优先于 .env</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<el-select v-model="debugPlan" size="default" style="width: 120px">
|
||||||
|
<el-option label="测包年" value="yearly" />
|
||||||
|
<el-option label="测永久" value="perpetual" />
|
||||||
|
</el-select>
|
||||||
<el-button :loading="debugging" @click="testConnection">测试连接 / 调试</el-button>
|
<el-button :loading="debugging" @click="testConnection">测试连接 / 调试</el-button>
|
||||||
<el-button type="primary" :loading="saving" @click="save">保存配置</el-button>
|
<el-button type="primary" :loading="saving" @click="save">保存配置</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,7 +22,7 @@
|
|||||||
type="info"
|
type="info"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
title="测试 / 生产环境的私钥与 Product ID 映射相互隔离。切换到生产后需单独填写生产密钥;保存后才会生效。"
|
title="测试 / 生产的私钥与 Product 映射相互隔离。改映射后需点「保存」;调试不会自动写库。生产 create-session 需 Waffo 门店已 Go-Live。"
|
||||||
style="margin-bottom: 16px"
|
style="margin-bottom: 16px"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -30,7 +34,7 @@
|
|||||||
<div class="env-row">
|
<div class="env-row">
|
||||||
<span :class="{ muted: isLive }">测试</span>
|
<span :class="{ muted: isLive }">测试</span>
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="isLive"
|
:model-value="isLive"
|
||||||
inline-prompt
|
inline-prompt
|
||||||
active-text="生产"
|
active-text="生产"
|
||||||
inactive-text="测试"
|
inactive-text="测试"
|
||||||
@@ -42,7 +46,7 @@
|
|||||||
{{ isLive ? 'live' : 'test' }}
|
{{ isLive ? 'live' : 'test' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">切换环境会载入该环境的 Product 映射;若该环境尚未配置私钥,需重新粘贴保存。</div>
|
<div class="hint">切换前会暂存当前映射到对应环境;未配置私钥的环境需单独粘贴保存。</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="商户 ID">
|
<el-form-item label="商户 ID">
|
||||||
<el-input v-model="waffo.merchant_id" placeholder="MER_…" />
|
<el-input v-model="waffo.merchant_id" placeholder="MER_…" />
|
||||||
@@ -72,8 +76,9 @@
|
|||||||
style="margin-top: 8px"
|
style="margin-top: 8px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="货币">
|
<el-form-item label="默认货币">
|
||||||
<el-input v-model="waffo.currency" style="width: 120px" />
|
<el-input v-model="waffo.currency" style="width: 120px" placeholder="CNY" />
|
||||||
|
<div class="hint">映射表可按计划覆盖(包年 CNY / 永久 USD)</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="SUCCESS_URL">
|
<el-form-item label="SUCCESS_URL">
|
||||||
<el-input v-model="waffo.success_url" placeholder="支付成功回跳" />
|
<el-input v-model="waffo.success_url" placeholder="支付成功回跳" />
|
||||||
@@ -93,41 +98,55 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="title">Product ID 映射({{ isLive ? '生产' : '测试' }})</span>
|
<span class="title">Product ID 映射({{ isLive ? '生产' : '测试' }})</span>
|
||||||
|
<div class="header-actions">
|
||||||
|
<el-button size="small" @click="fillOpsxshellProducts">填充 OpsXshell 商品</el-button>
|
||||||
|
<el-button size="small" @click="copyMappingsFromOther">
|
||||||
|
{{ isLive ? '从测试复制' : '从生产复制' }}
|
||||||
|
</el-button>
|
||||||
<el-button size="small" type="primary" @click="addMapping">新增行</el-button>
|
<el-button size="small" type="primary" @click="addMapping">新增行</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-alert
|
<el-alert
|
||||||
type="warning"
|
type="warning"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
title="测试与生产的 Product ID 通常不同,已按环境分别保存。切换开关即可编辑对应环境映射。"
|
title="调试按钮不会写库。改完映射请点右上角「保存配置」。生产与测试可点「填充 OpsXshell 商品」一键写入包年/永久 Product ID。"
|
||||||
style="margin-bottom: 12px"
|
style="margin-bottom: 12px"
|
||||||
/>
|
/>
|
||||||
<el-table :data="productMappings" border stripe>
|
<el-table :data="productMappings" border stripe>
|
||||||
<el-table-column label="计划类型 / Key" min-width="180">
|
<el-table-column label="计划类型 / Key" min-width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="row.plan_key"
|
v-model="row.plan_key"
|
||||||
filterable
|
filterable
|
||||||
allow-create
|
allow-create
|
||||||
default-first-option
|
default-first-option
|
||||||
placeholder="如 yearly / perpetual / trial"
|
placeholder="yearly / perpetual"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option v-for="opt in planKeyOptions" :key="opt" :label="opt" :value="opt" />
|
<el-option v-for="opt in planKeyOptions" :key="opt" :label="opt" :value="opt" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="说明" min-width="140">
|
<el-table-column label="说明" min-width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.label" placeholder="可选说明" />
|
<el-input v-model="row.label" placeholder="可选" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="Waffo Product ID" min-width="220">
|
<el-table-column label="Waffo Product ID" min-width="240">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.product_id" placeholder="PROD_…" />
|
<el-input v-model="row.product_id" placeholder="PROD_…" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="货币" width="110">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-select v-model="row.currency" allow-create filterable placeholder="CNY" style="width: 100%">
|
||||||
|
<el-option label="CNY" value="CNY" />
|
||||||
|
<el-option label="USD" value="USD" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="90" fixed="right">
|
<el-table-column label="操作" width="90" fixed="right">
|
||||||
<template #default="{ $index }">
|
<template #default="{ $index }">
|
||||||
<el-button size="small" type="danger" link @click="removeMapping($index)">删除</el-button>
|
<el-button size="small" type="danger" link @click="removeMapping($index)">删除</el-button>
|
||||||
@@ -150,9 +169,26 @@ import { getWaffoSettings, updateWaffoSettings, testWaffoConnection } from '@/ap
|
|||||||
|
|
||||||
const planKeyOptions = ['trial', 'yearly', 'perpetual', 'monthly', 'lifetime']
|
const planKeyOptions = ['trial', 'yearly', 'perpetual', 'monthly', 'lifetime']
|
||||||
|
|
||||||
|
// OpsXshell 线上商品(pancake 购买链接对应) // Logan.Li
|
||||||
|
const OPSXSHELL_PRODUCTS = [
|
||||||
|
{
|
||||||
|
plan_key: 'yearly',
|
||||||
|
label: 'OpsXshell Yearly',
|
||||||
|
product_id: 'PROD_5PqVJyEiCM4nDgVoA0RKjN',
|
||||||
|
currency: 'CNY',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plan_key: 'perpetual',
|
||||||
|
label: 'OpsXshell Perpetual',
|
||||||
|
product_id: 'PROD_3BSJqDRkVBoYNgO78NsTAU',
|
||||||
|
currency: 'USD',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const debugging = ref(false)
|
const debugging = ref(false)
|
||||||
|
const debugPlan = ref('yearly')
|
||||||
const showPrivateKey = ref(false)
|
const showPrivateKey = ref(false)
|
||||||
const privateKeyInput = ref('')
|
const privateKeyInput = ref('')
|
||||||
const clearKeyFlag = ref(false)
|
const clearKeyFlag = ref(false)
|
||||||
@@ -177,17 +213,12 @@ const waffo = reactive({
|
|||||||
private_key_configured: false,
|
private_key_configured: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const isLive = computed({
|
const isLive = computed(() => waffo.environment === 'live')
|
||||||
get: () => waffo.environment === 'live',
|
|
||||||
set: (v) => {
|
|
||||||
waffo.environment = v ? 'live' : 'test'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function defaultMappings() {
|
function defaultMappings() {
|
||||||
return [
|
return [
|
||||||
{ plan_key: 'yearly', label: '包年', product_id: '' },
|
{ plan_key: 'yearly', label: '包年', product_id: '', currency: 'CNY' },
|
||||||
{ plan_key: 'perpetual', label: '永久', product_id: '' },
|
{ plan_key: 'perpetual', label: '永久', product_id: '', currency: 'USD' },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,28 +228,43 @@ function normalizeMappings(data = {}) {
|
|||||||
plan_key: m.plan_key || m.key || m.plan_type || '',
|
plan_key: m.plan_key || m.key || m.plan_type || '',
|
||||||
label: m.label || '',
|
label: m.label || '',
|
||||||
product_id: m.product_id || '',
|
product_id: m.product_id || '',
|
||||||
|
currency: (m.currency || '').toUpperCase() || guessCurrency(m.plan_key),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
const rows = []
|
const rows = []
|
||||||
if (data.product_id_yearly) {
|
if (data.product_id_yearly) {
|
||||||
rows.push({ plan_key: 'yearly', label: '包年', product_id: data.product_id_yearly })
|
rows.push({
|
||||||
|
plan_key: 'yearly',
|
||||||
|
label: '包年',
|
||||||
|
product_id: data.product_id_yearly,
|
||||||
|
currency: 'CNY',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (data.product_id_perpetual || data.product_id) {
|
if (data.product_id_perpetual || data.product_id) {
|
||||||
rows.push({
|
rows.push({
|
||||||
plan_key: 'perpetual',
|
plan_key: 'perpetual',
|
||||||
label: '永久',
|
label: '永久',
|
||||||
product_id: data.product_id_perpetual || data.product_id,
|
product_id: data.product_id_perpetual || data.product_id,
|
||||||
|
currency: 'USD',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return rows.length ? rows : defaultMappings()
|
return rows.length ? rows : defaultMappings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function guessCurrency(planKey) {
|
||||||
|
const k = (planKey || '').toLowerCase()
|
||||||
|
if (k === 'perpetual' || k === 'lifetime') return 'USD'
|
||||||
|
return 'CNY'
|
||||||
|
}
|
||||||
|
|
||||||
function mappingsToCompatFields(rows) {
|
function mappingsToCompatFields(rows) {
|
||||||
const map = {}
|
const map = {}
|
||||||
|
const currencyMap = {}
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const key = (row.plan_key || '').trim()
|
const key = (row.plan_key || '').trim()
|
||||||
if (!key) continue
|
if (!key) continue
|
||||||
map[key] = (row.product_id || '').trim()
|
map[key] = (row.product_id || '').trim()
|
||||||
|
currencyMap[key] = (row.currency || '').trim().toUpperCase()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
product_mappings: rows
|
product_mappings: rows
|
||||||
@@ -227,18 +273,21 @@ function mappingsToCompatFields(rows) {
|
|||||||
plan_key: r.plan_key.trim(),
|
plan_key: r.plan_key.trim(),
|
||||||
label: r.label || '',
|
label: r.label || '',
|
||||||
product_id: (r.product_id || '').trim(),
|
product_id: (r.product_id || '').trim(),
|
||||||
|
currency: (r.currency || '').trim().toUpperCase(),
|
||||||
})),
|
})),
|
||||||
product_id_yearly: map.yearly || '',
|
product_id_yearly: map.yearly || '',
|
||||||
product_id_perpetual: map.perpetual || map.lifetime || '',
|
product_id_perpetual: map.perpetual || map.lifetime || '',
|
||||||
product_id: map.perpetual || map.lifetime || map.yearly || '',
|
product_id: map.perpetual || map.lifetime || map.yearly || '',
|
||||||
|
currency_yearly: currencyMap.yearly || '',
|
||||||
|
currency_perpetual: currencyMap.perpetual || currencyMap.lifetime || '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stashCurrentEnv() {
|
function stashEnv(env) {
|
||||||
const env = waffo.environment === 'live' ? 'live' : 'test'
|
const target = env === 'live' ? 'live' : 'test'
|
||||||
profiles.value[env] = {
|
profiles.value[target] = {
|
||||||
product_mappings: productMappings.value.map((r) => ({ ...r })),
|
product_mappings: productMappings.value.map((r) => ({ ...r })),
|
||||||
private_key_configured: waffo.private_key_configured,
|
private_key_configured: !!(keyByEnv.value[target] || waffo.private_key_configured),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,18 +300,21 @@ function applyEnv(env) {
|
|||||||
waffo.private_key_configured = configured
|
waffo.private_key_configured = configured
|
||||||
privateKeyInput.value = ''
|
privateKeyInput.value = ''
|
||||||
clearKeyFlag.value = false
|
clearKeyFlag.value = false
|
||||||
// 未配置私钥时强制展开输入框,避免仍显示另一环境密钥状态 // Logan.Li
|
|
||||||
showPrivateKey.value = !configured
|
showPrivateKey.value = !configured
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEnvSwitch(live) {
|
function onEnvSwitch(live) {
|
||||||
stashCurrentEnv()
|
const next = live ? 'live' : 'test'
|
||||||
applyEnv(live ? 'live' : 'test')
|
const prev = waffo.environment === 'live' ? 'live' : 'test'
|
||||||
ElMessage.info(live ? '已切换到生产环境视图(保存后生效)' : '已切换到测试环境视图(保存后生效)')
|
if (next === prev) return
|
||||||
|
// 必须先把当前表写入「切换前」环境,再加载目标环境(勿用会提前改 environment 的 v-model) // Logan.Li
|
||||||
|
stashEnv(prev)
|
||||||
|
applyEnv(next)
|
||||||
|
ElMessage.info(live ? '已切换到生产映射(记得保存)' : '已切换到测试映射(记得保存)')
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMapping() {
|
function addMapping() {
|
||||||
productMappings.value.push({ plan_key: '', label: '', product_id: '' })
|
productMappings.value.push({ plan_key: '', label: '', product_id: '', currency: 'CNY' })
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeMapping(index) {
|
function removeMapping(index) {
|
||||||
@@ -270,6 +322,46 @@ function removeMapping(index) {
|
|||||||
if (!productMappings.value.length) addMapping()
|
if (!productMappings.value.length) addMapping()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fillOpsxshellProducts() {
|
||||||
|
const byKey = Object.fromEntries(productMappings.value.map((r) => [r.plan_key, r]))
|
||||||
|
for (const item of OPSXSHELL_PRODUCTS) {
|
||||||
|
if (byKey[item.plan_key]) {
|
||||||
|
byKey[item.plan_key].product_id = item.product_id
|
||||||
|
byKey[item.plan_key].label = item.label
|
||||||
|
byKey[item.plan_key].currency = item.currency
|
||||||
|
} else {
|
||||||
|
productMappings.value.push({ ...item })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 保证 yearly / perpetual 在前 // Logan.Li
|
||||||
|
const order = ['yearly', 'perpetual']
|
||||||
|
productMappings.value.sort((a, b) => {
|
||||||
|
const ia = order.indexOf(a.plan_key)
|
||||||
|
const ib = order.indexOf(b.plan_key)
|
||||||
|
if (ia === -1 && ib === -1) return 0
|
||||||
|
if (ia === -1) return 1
|
||||||
|
if (ib === -1) return -1
|
||||||
|
return ia - ib
|
||||||
|
})
|
||||||
|
stashEnv(waffo.environment)
|
||||||
|
ElMessage.success(`已填入当前「${isLive.value ? '生产' : '测试'}」映射,请点保存`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyMappingsFromOther() {
|
||||||
|
const other = isLive.value ? 'test' : 'live'
|
||||||
|
stashEnv(waffo.environment)
|
||||||
|
const src = profiles.value[other]
|
||||||
|
const rows = normalizeMappings(src || {})
|
||||||
|
const hasId = rows.some((r) => (r.product_id || '').trim())
|
||||||
|
if (!hasId) {
|
||||||
|
ElMessage.warning(`${other === 'live' ? '生产' : '测试'}环境尚无 Product ID,无法复制`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
productMappings.value = rows.map((r) => ({ ...r }))
|
||||||
|
stashEnv(waffo.environment)
|
||||||
|
ElMessage.success(`已从${other === 'live' ? '生产' : '测试'}复制到当前,请点保存`)
|
||||||
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -287,14 +379,6 @@ async function load() {
|
|||||||
test: data.profiles?.test || { product_mappings: [] },
|
test: data.profiles?.test || { product_mappings: [] },
|
||||||
live: data.profiles?.live || { product_mappings: [] },
|
live: data.profiles?.live || { product_mappings: [] },
|
||||||
}
|
}
|
||||||
// 当前环境优先用顶层字段(已 ApplyEnvProfile) // Logan.Li
|
|
||||||
profiles.value[waffo.environment] = {
|
|
||||||
product_mappings: data.product_mappings || [],
|
|
||||||
product_id: data.product_id,
|
|
||||||
product_id_yearly: data.product_id_yearly,
|
|
||||||
product_id_perpetual: data.product_id_perpetual,
|
|
||||||
private_key_configured: data.private_key_configured,
|
|
||||||
}
|
|
||||||
applyEnv(waffo.environment)
|
applyEnv(waffo.environment)
|
||||||
debugResult.value = ''
|
debugResult.value = ''
|
||||||
} finally {
|
} finally {
|
||||||
@@ -305,7 +389,7 @@ async function load() {
|
|||||||
async function save() {
|
async function save() {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
stashCurrentEnv()
|
stashEnv(waffo.environment)
|
||||||
const compat = mappingsToCompatFields(productMappings.value)
|
const compat = mappingsToCompatFields(productMappings.value)
|
||||||
const payload = {
|
const payload = {
|
||||||
enabled: waffo.enabled,
|
enabled: waffo.enabled,
|
||||||
@@ -321,9 +405,6 @@ async function save() {
|
|||||||
if (privateKeyInput.value.trim()) {
|
if (privateKeyInput.value.trim()) {
|
||||||
payload.private_key = privateKeyInput.value.trim()
|
payload.private_key = privateKeyInput.value.trim()
|
||||||
payload.clear_private_key = false
|
payload.clear_private_key = false
|
||||||
} else if (!waffo.private_key_configured) {
|
|
||||||
// 当前环境无密钥且未填写:明确清除该环境密钥位 // Logan.Li
|
|
||||||
payload.clear_private_key = true
|
|
||||||
}
|
}
|
||||||
const res = await updateWaffoSettings(payload)
|
const res = await updateWaffoSettings(payload)
|
||||||
Object.assign(waffo, res.data || {})
|
Object.assign(waffo, res.data || {})
|
||||||
@@ -333,12 +414,8 @@ async function save() {
|
|||||||
live: !!(data.private_key_configured_by_env?.live),
|
live: !!(data.private_key_configured_by_env?.live),
|
||||||
}
|
}
|
||||||
profiles.value = {
|
profiles.value = {
|
||||||
test: data.profiles?.test || profiles.value.test,
|
test: data.profiles?.test || { product_mappings: [] },
|
||||||
live: data.profiles?.live || profiles.value.live,
|
live: data.profiles?.live || { product_mappings: [] },
|
||||||
}
|
|
||||||
profiles.value[waffo.environment] = {
|
|
||||||
product_mappings: data.product_mappings || [],
|
|
||||||
private_key_configured: data.private_key_configured,
|
|
||||||
}
|
}
|
||||||
applyEnv(waffo.environment)
|
applyEnv(waffo.environment)
|
||||||
ElMessage.success('支付配置已保存')
|
ElMessage.success('支付配置已保存')
|
||||||
@@ -375,10 +452,11 @@ async function testConnection() {
|
|||||||
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,
|
||||||
...compat,
|
...compat,
|
||||||
})
|
})
|
||||||
debugResult.value = JSON.stringify(res.data ?? res, null, 2)
|
debugResult.value = JSON.stringify(res.data ?? res, null, 2)
|
||||||
ElMessage.success('调试请求已完成')
|
ElMessage.success('调试请求已完成(未写库;映射请手动保存)')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
debugResult.value = JSON.stringify({
|
debugResult.value = JSON.stringify({
|
||||||
error: err?.response?.data || err?.message || String(err),
|
error: err?.response?.data || err?.message || String(err),
|
||||||
@@ -392,9 +470,9 @@ onMounted(load)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.card-header { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
|
.card-header { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||||
.title { font-weight: 600; }
|
.title { font-weight: 600; }
|
||||||
.header-actions { display: flex; gap: 8px; }
|
.header-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
||||||
.secret-row { display: flex; align-items: center; gap: 10px; }
|
.secret-row { display: flex; align-items: center; gap: 10px; }
|
||||||
.env-row { display: flex; align-items: center; }
|
.env-row { display: flex; align-items: center; }
|
||||||
.muted { color: #c0c4cc; }
|
.muted { color: #c0c4cc; }
|
||||||
|
|||||||
Reference in new issue
Block a user