214 lines
6.3 KiB
Go
214 lines
6.3 KiB
Go
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 + pay_way 更新或新增。
|
|
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
|
|
}
|
|
|
|
activeKeys := backupActiveKeys(backup.Configs)
|
|
result := &ImportBackupResult{Total: len(backup.Configs)}
|
|
|
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
for payWay := range activeKeys {
|
|
if err := deactivateOtherConfigs(tx, 0, actorID, payWay); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, cfg := range backup.Configs {
|
|
req := importCreateRequest(cfg, activeKeys)
|
|
if err := r.validateCreateRequest(req); err != nil {
|
|
return err
|
|
}
|
|
payWay := firstNonEmpty(req.PayWay, "ZFBZF")
|
|
|
|
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": payWay,
|
|
"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 = ? AND pay_way = ?", req.Provider, req.MerchantID, payWay).
|
|
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: payWay,
|
|
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
|
|
}
|
|
|
|
// backupActiveKeys 提取备份中每个支付方式的启用配置键。
|
|
func backupActiveKeys(configs []ConfigDTO) map[string]string {
|
|
keys := make(map[string]string)
|
|
for _, cfg := range configs {
|
|
payWay := firstNonEmpty(cfg.PayWay, "ZFBZF")
|
|
if _, exists := keys[payWay]; exists {
|
|
continue
|
|
}
|
|
if cfg.Status == "active" || cfg.IsDefault {
|
|
keys[payWay] = paymentConfigImportKey(cfg.Provider, cfg.MerchantID, payWay)
|
|
}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// importCreateRequest 将备份 DTO 转为创建请求,并保留每个支付方式唯一启用配置。
|
|
func importCreateRequest(cfg ConfigDTO, activeKeys map[string]string) CreateRequest {
|
|
status := cfg.Status
|
|
if status == "" {
|
|
status = "disabled"
|
|
}
|
|
payWay := firstNonEmpty(cfg.PayWay, "ZFBZF")
|
|
activeKey := activeKeys[payWay]
|
|
isActive := activeKey != "" && paymentConfigImportKey(cfg.Provider, cfg.MerchantID, payWay) == 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: payWay,
|
|
JSPayFlag: cfg.JSPayFlag,
|
|
SignType: cfg.SignType,
|
|
ExtraConfig: cfg.ExtraConfig,
|
|
IsDefault: isActive,
|
|
Status: status,
|
|
Environment: cfg.Environment,
|
|
BusinessTags: cfg.BusinessTags,
|
|
}
|
|
}
|
|
|
|
// paymentConfigImportKey 生成导入去重键,同商户可按支付方式拆分为多条配置。
|
|
func paymentConfigImportKey(provider string, merchantID string, payWay string) string {
|
|
return provider + ":" + merchantID + ":" + payWay
|
|
}
|