fix(payment): store provider config as plaintext JSON with legacy ciphertext fallback

Without TOTP_ENCRYPTION_KEY, saved payment configs were lost on restart because
the AES round-trip failed silently. Write new records as plaintext JSON; read
path tries JSON first, falls back to legacy AES decrypt when a key is present,
and treats unreadable values as empty so admins can re-enter them via the UI.
This commit is contained in:
erio
2026-04-17 17:00:29 +08:00
parent 6cfdf4ec05
commit fd0c9a1305
4 changed files with 151 additions and 25 deletions

View File

@@ -261,6 +261,9 @@ func (lb *DefaultLoadBalancer) buildSelection(selected *dbent.PaymentProviderIns
if err != nil {
return nil, fmt.Errorf("decrypt instance %d config: %w", selected.ID, err)
}
if config == nil {
config = map[string]string{}
}
if selected.PaymentMode != "" {
config["paymentMode"] = selected.PaymentMode
@@ -275,16 +278,29 @@ func (lb *DefaultLoadBalancer) buildSelection(selected *dbent.PaymentProviderIns
}, nil
}
func (lb *DefaultLoadBalancer) decryptConfig(encrypted string) (map[string]string, error) {
plaintext, err := Decrypt(encrypted, lb.encryptionKey)
if err != nil {
return nil, err
// decryptConfig parses a stored provider config.
// New records are plaintext JSON; legacy records are AES-256-GCM ciphertext.
// Unreadable values (legacy ciphertext without a valid key, or malformed data)
// are treated as empty so the service keeps running while the admin re-enters
// the config via the UI.
func (lb *DefaultLoadBalancer) decryptConfig(stored string) (map[string]string, error) {
if stored == "" {
return nil, nil
}
var config map[string]string
if err := json.Unmarshal([]byte(plaintext), &config); err != nil {
return nil, fmt.Errorf("unmarshal config: %w", err)
if err := json.Unmarshal([]byte(stored), &config); err == nil {
return config, nil
}
return config, nil
if len(lb.encryptionKey) == AES256KeySize {
if plaintext, err := Decrypt(stored, lb.encryptionKey); err == nil {
if err := json.Unmarshal([]byte(plaintext), &config); err == nil {
return config, nil
}
}
}
slog.Warn("payment provider config unreadable, treating as empty for re-entry",
"stored_len", len(stored))
return nil, nil
}
// GetInstanceDailyAmount returns the total completed order amount for an instance today.