package paymentconfig import ( "context" "errors" "hfb_sys/backend/internal/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) // Create 创建配置 func (r *Repository) Create(ctx context.Context, req CreateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { // 验证必填字段 if err := r.validateCreateRequest(req); err != nil { return nil, err } var dto ConfigDTO err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 加密密钥 encryptedSignKey, err := r.encryptor.Encrypt(req.SignKey) if err != nil { return ErrEncryptionFailed } encryptedNotifyKey, err := r.encryptor.Encrypt(req.NotifyKey) if err != nil { return ErrEncryptionFailed } status := req.Status if status == "" { status = "active" } environment := req.Environment if environment == "" { environment = "production" } payWay := firstNonEmpty(req.PayWay, "ZFBZF") isDefault := req.IsDefault if status == "active" { if err := deactivateOtherConfigs(tx, 0, actorID, payWay); err != nil { return err } isDefault = true } else { isDefault = false } 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: req.ExtraConfig, IsDefault: isDefault, Status: status, Environment: environment, BusinessTags: req.BusinessTags, CreatedBy: &actorID, UpdatedBy: &actorID, } if err := tx.Create(&item).Error; err != nil { return err } // 记录审计日志 if err := appendAuditLog(tx, actorID, "payment_config.create", item.ID, meta, map[string]any{ "name": item.Name, "provider": item.Provider, }); err != nil { return err } dto, err = r.toDTO(item, false) return err }) if err != nil { return nil, err } return &dto, nil } // Update 更新配置 // Update 更新配置 func (r *Repository) Update(ctx context.Context, id uint64, req UpdateRequest, actorID uint64, meta AuditMeta) (*ConfigDTO, error) { if req.SignType != nil && *req.SignType != "" && !isValidSignType(*req.SignType) { return nil, ErrInvalidSignType } if req.PayWay != nil && !isValidPayWay(*req.PayWay) { return nil, ErrInvalidPayWay } if req.Status != nil && !isValidStatus(*req.Status) { return nil, ErrInvalidStatus } if req.NotifyURL != nil && *req.NotifyURL == "" { return nil, ErrNotifyURLRequired } var dto ConfigDTO err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var item model.PaymentMerchantConfig if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", id).First(&item).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrConfigNotFound } return err } updates := make(map[string]any) if req.Name != nil { updates["name"] = *req.Name } if req.MerchantID != nil { updates["merchant_id"] = *req.MerchantID } if req.GatewayURL != nil { updates["gateway_url"] = *req.GatewayURL } if req.SignKey != nil { encrypted, err := r.encryptor.Encrypt(*req.SignKey) if err != nil { return ErrEncryptionFailed } updates["sign_key"] = encrypted } if req.NotifyKey != nil { encrypted, err := r.encryptor.Encrypt(*req.NotifyKey) if err != nil { return ErrEncryptionFailed } updates["notify_key"] = encrypted } if req.NotifyURL != nil { updates["notify_url"] = *req.NotifyURL } if req.JumpURL != nil { updates["jump_url"] = *req.JumpURL } if req.PayWay != nil { updates["pay_way"] = firstNonEmpty(*req.PayWay, "ZFBZF") } if req.JSPayFlag != nil { updates["jspay_flag"] = *req.JSPayFlag } if req.SignType != nil { updates["sign_type"] = *req.SignType } if req.ExtraConfig != nil { updates["extra_config"] = model.JSONMap(req.ExtraConfig) } if req.IsDefault != nil { updates["is_default"] = *req.IsDefault } if req.Status != nil { updates["status"] = *req.Status } if req.Environment != nil { updates["environment"] = *req.Environment } if req.BusinessTags != nil { updates["business_tags"] = model.JSONArray(req.BusinessTags) } updates["updated_by"] = actorID finalStatus := item.Status if req.Status != nil { finalStatus = *req.Status } finalPayWay := item.PayWay if req.PayWay != nil && *req.PayWay != "" { finalPayWay = *req.PayWay } if finalPayWay == "" { finalPayWay = "ZFBZF" } if finalStatus == "active" { if err := deactivateOtherConfigs(tx, id, actorID, finalPayWay); err != nil { return err } updates["status"] = "active" updates["is_default"] = true } else { updates["is_default"] = false } if err := tx.Model(&item).Updates(updates).Error; err != nil { return err } // 记录审计日志 if err := appendAuditLog(tx, actorID, "payment_config.update", item.ID, meta, map[string]any{ "updates": updates, }); err != nil { return err } // 重新查询 if err := tx.Where("id = ?", id).First(&item).Error; err != nil { return err } var dtoErr error dto, dtoErr = r.toDTO(item, false) return dtoErr }) if err != nil { return nil, err } return &dto, nil } // Delete 删除配置 // Delete 删除配置 func (r *Repository) Delete(ctx context.Context, id uint64, actorID uint64, meta AuditMeta) error { return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var item model.PaymentMerchantConfig if err := tx.Where("id = ?", id).First(&item).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrConfigNotFound } return err } var usedCount int64 usedDB := tx.Model(&model.PaymentOrder{}). Where("payment_config_id = ?", item.ID). Or("(payment_config_id = ? OR payment_config_id IS NULL) AND provider = ? AND merchant_id = ? AND pay_way = ?", 0, item.Provider, item.MerchantID, item.PayWay) if err := usedDB. Count(&usedCount).Error; err != nil { return err } if usedCount > 0 { return ErrCannotDeleteInUse } if err := tx.Delete(&item).Error; err != nil { return err } // 记录审计日志 return appendAuditLog(tx, actorID, "payment_config.delete", item.ID, meta, map[string]any{ "name": item.Name, "provider": item.Provider, }) }) } // deactivateOtherConfigs 保证同一个支付方式同一时间只有一个启用配置。 func deactivateOtherConfigs(tx *gorm.DB, activeID uint64, actorID uint64, payWay string) error { db := tx.Model(&model.PaymentMerchantConfig{}). Where("pay_way = ? AND (status = ? OR is_default = ?)", payWay, "active", true) if activeID > 0 { db = db.Where("id != ?", activeID) } return db.Updates(map[string]any{ "status": "disabled", "is_default": false, "updated_by": actorID, }).Error } // toDTO 转换为 DTO