diff --git a/config/config.go b/config/config.go index baef165..a1b02ed 100644 --- a/config/config.go +++ b/config/config.go @@ -16,6 +16,22 @@ type Config struct { QuerySync QuerySyncConfig `mapstructure:"query_sync"` // Biz 通用:任意业务系统(jiu / dudu / …)在 config 的 biz. 下声明即可接入,无需改代码。 Biz map[string]BizSystemConfig `mapstructure:"biz"` + // Accounts 多账户配置注册表(v2):凭证不写死配置,只存 env 前缀,真值运行时从环境变量取。 + Accounts []AccountConfig `mapstructure:"accounts"` +} + +// AccountConfig 单个收款账户的配置(v2 多账户路由用)。凭证严禁写进 config.yaml, +// 只存 CredentialEnvPrefix,真值由 internal/accounts.Registry 运行时从环境变量 +// `_` 读取。 +type AccountConfig struct { + AccountID string `mapstructure:"account_id"` + Channel string `mapstructure:"channel"` + Weight int `mapstructure:"weight"` + Enabled bool `mapstructure:"enabled"` + DailyLimit int64 `mapstructure:"daily_limit"` + Region string `mapstructure:"region"` + Subject string `mapstructure:"subject"` + CredentialEnvPrefix string `mapstructure:"credential_env_prefix"` } type ServerConfig struct { diff --git a/internal/accounts/registry.go b/internal/accounts/registry.go new file mode 100644 index 0000000..2ccbcb3 --- /dev/null +++ b/internal/accounts/registry.go @@ -0,0 +1,40 @@ +// Package accounts loads the payment-account registry from config. Credentials +// never live in the config file — only an env-var prefix is stored; the real +// secret is read from the environment at call time. +package accounts + +import ( + "os" + "strings" + + "github.com/wangjia/pay/config" +) + +type Registry struct{ accts []config.AccountConfig } + +func New(accts []config.AccountConfig) *Registry { return &Registry{accts: accts} } + +// EnabledFor returns enabled accounts for a channel (+region if non-empty). +func (r *Registry) EnabledFor(channel, region string) []config.AccountConfig { + var out []config.AccountConfig + for _, a := range r.accts { + if a.Channel != channel || !a.Enabled { + continue + } + if region != "" && a.Region != "" && a.Region != region { + continue + } + out = append(out, a) + } + return out +} + +// Credential reads _ from the environment. +func (r *Registry) Credential(accountID, key string) string { + for _, a := range r.accts { + if a.AccountID == accountID { + return os.Getenv(strings.ToUpper(a.CredentialEnvPrefix + "_" + key)) + } + } + return "" +} diff --git a/internal/accounts/registry_test.go b/internal/accounts/registry_test.go new file mode 100644 index 0000000..03b17bf --- /dev/null +++ b/internal/accounts/registry_test.go @@ -0,0 +1,28 @@ +package accounts_test + +import ( + "testing" + + "github.com/wangjia/pay/config" + "github.com/wangjia/pay/internal/accounts" +) + +func TestEnabledForAndCredential(t *testing.T) { + t.Setenv("USDT_A1_XPUB", "xpub-abc") + reg := accounts.New([]config.AccountConfig{ + {AccountID: "usdt-a1", Channel: "crypto", Region: "global", Enabled: true, Weight: 1, CredentialEnvPrefix: "USDT_A1"}, + {AccountID: "usdt-a2", Channel: "crypto", Region: "global", Enabled: false, Weight: 1, CredentialEnvPrefix: "USDT_A2"}, + {AccountID: "ali-cn1", Channel: "alipay", Region: "cn", Enabled: true, Weight: 1, CredentialEnvPrefix: "ALI_CN1"}, + }) + + got := reg.EnabledFor("crypto", "global") + if len(got) != 1 || got[0].AccountID != "usdt-a1" { + t.Fatalf("EnabledFor crypto/global = %+v", got) + } + if reg.Credential("usdt-a1", "XPUB") != "xpub-abc" { + t.Fatalf("Credential xpub 取值失败") + } + if len(reg.EnabledFor("alipay", "global")) != 0 { + t.Fatalf("alipay 在 global 应无账户") + } +}