package paymentconfig import ( "context" "errors" "time" "hfb_sys/backend/internal/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) // ExportBackup 导出所有支付配置备份,包含解密后的密钥。 func (r *Repository) ExportBackup(ctx context.Context, actorID uint64, meta AuditMeta) (*ExportBackup, error) { var backup *ExportBackup err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var items []model.PaymentMerchantConfig if err := tx.Order("is_default DESC, id DESC").Find(&items).Error; err != nil { return err } configs := make([]ConfigDTO, 0, len(items)) for _, item := range items { dto, err := r.toDTO(item, true) if err != nil { return err } configs = append(configs, dto) } exportedAt := time.Now().Format(time.RFC3339) backup = &ExportBackup{ Type: "payment_config_backup", Version: 1, ExportedAt: exportedAt, ExportedBy: actorID, Total: len(configs), Configs: configs, } return appendAuditLog(tx, actorID, "payment_config.export", 0, meta, map[string]any{ "total": len(configs), "exported_at": exportedAt, }) }) if err != nil { return nil, err } return backup, nil } // ImportBackup 导入支付配置备份,按 provider + merchant_id 更新或新增。 // ImportBackup 导入支付配置备份,按 provider + merchant_id 更新或新增。 func (r *Repository) ImportBackup(ctx context.Context, backup ExportBackup, actorID uint64, meta AuditMeta) (*ImportBackupResult, error) { if backup.Type != "payment_config_backup" || backup.Version <= 0 { return nil, ErrInvalidBackup } if len(backup.Configs) == 0 { return nil, ErrEmptyBackup } activeKey := backupActiveKey(backup.Configs) result := &ImportBackupResult{Total: len(backup.Configs)} err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if activeKey != "" { if err := deactivateOtherConfigs(tx, 0, actorID); err != nil { return err } } for _, cfg := range backup.Configs { req := importCreateRequest(cfg, activeKey) if err := r.validateCreateRequest(req); err != nil { return err } encryptedSignKey, err := r.encryptor.Encrypt(req.SignKey) if err != nil { return ErrEncryptionFailed } encryptedNotifyKey, err := r.encryptor.Encrypt(req.NotifyKey) if err != nil { return ErrEncryptionFailed } updates := map[string]any{ "name": req.Name, "gateway_url": req.GatewayURL, "sign_key": encryptedSignKey, "notify_key": encryptedNotifyKey, "notify_url": req.NotifyURL, "jump_url": req.JumpURL, "pay_way": firstNonEmpty(req.PayWay, "ZFBZF"), "jspay_flag": firstNonEmpty(req.JSPayFlag, "2"), "sign_type": firstNonEmpty(req.SignType, defaultSignType(req.Provider)), "extra_config": model.JSONMap(req.ExtraConfig), "is_default": req.IsDefault, "status": firstNonEmpty(req.Status, "active"), "environment": firstNonEmpty(req.Environment, "production"), "business_tags": model.JSONArray(req.BusinessTags), "updated_by": actorID, } var existing model.PaymentMerchantConfig err = tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("provider = ? AND merchant_id = ?", req.Provider, req.MerchantID). First(&existing).Error if err == nil { if err := tx.Model(&existing).Updates(updates).Error; err != nil { return err } result.Updated++ continue } if !errors.Is(err, gorm.ErrRecordNotFound) { return err } createdBy := actorID item := model.PaymentMerchantConfig{ Name: req.Name, Provider: req.Provider, MerchantID: req.MerchantID, GatewayURL: req.GatewayURL, SignKey: encryptedSignKey, NotifyKey: encryptedNotifyKey, NotifyURL: req.NotifyURL, JumpURL: req.JumpURL, PayWay: firstNonEmpty(req.PayWay, "ZFBZF"), JSPayFlag: firstNonEmpty(req.JSPayFlag, "2"), SignType: firstNonEmpty(req.SignType, defaultSignType(req.Provider)), ExtraConfig: model.JSONMap(req.ExtraConfig), IsDefault: req.IsDefault, Status: firstNonEmpty(req.Status, "active"), Environment: firstNonEmpty(req.Environment, "production"), BusinessTags: req.BusinessTags, CreatedBy: &createdBy, UpdatedBy: &actorID, } if err := tx.Create(&item).Error; err != nil { return err } result.Created++ } return appendAuditLog(tx, actorID, "payment_config.import", 0, meta, map[string]any{ "total": result.Total, "created": result.Created, "updated": result.Updated, }) }) if err != nil { return nil, err } return result, nil } // FindActiveByProvider 查询提供商的所有激活配置 func backupActiveKey(configs []ConfigDTO) string { for _, cfg := range configs { if cfg.Status == "active" || cfg.IsDefault { return paymentConfigImportKey(cfg.Provider, cfg.MerchantID) } } return "" } func importCreateRequest(cfg ConfigDTO, activeKey string) CreateRequest { status := cfg.Status if status == "" { status = "disabled" } isActive := activeKey != "" && paymentConfigImportKey(cfg.Provider, cfg.MerchantID) == activeKey if isActive { status = "active" } else if status == "active" { status = "disabled" } return CreateRequest{ Name: cfg.Name, Provider: cfg.Provider, MerchantID: cfg.MerchantID, GatewayURL: cfg.GatewayURL, SignKey: cfg.SignKey, NotifyKey: cfg.NotifyKey, NotifyURL: cfg.NotifyURL, JumpURL: cfg.JumpURL, PayWay: cfg.PayWay, JSPayFlag: cfg.JSPayFlag, SignType: cfg.SignType, ExtraConfig: cfg.ExtraConfig, IsDefault: isActive, Status: status, Environment: cfg.Environment, BusinessTags: cfg.BusinessTags, } } func paymentConfigImportKey(provider string, merchantID string) string { return provider + ":" + merchantID }