// Package wechat_config 提供微信参数配置管理的业务逻辑服务 // 包含配置的 CRUD、激活/停用、Redis 缓存等功能 package wechat_config import ( "context" "fmt" "strconv" "time" "github.com/bytedance/sonic" "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" systemconfigapp "github.com/break/junhong_cmp_fiber/internal/application/systemconfig" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/internal/store" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/auditfailure" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" ) // Redis 缓存键 const redisActiveConfigKey = "wechat:config:active" // Service 微信参数配置业务服务 type Service struct { store *postgres.WechatConfigStore orderStore *postgres.OrderStore rechargeOrderStore *postgres.RechargeOrderStore agentRechargeStore *postgres.AgentRechargeStore paymentStore *postgres.PaymentStore audit systemconfigapp.AuditWriter redis *redis.Client logger *zap.Logger } // New 创建微信参数配置服务实例 func New( store *postgres.WechatConfigStore, orderStore *postgres.OrderStore, rechargeOrderStore *postgres.RechargeOrderStore, agentRechargeStore *postgres.AgentRechargeStore, paymentStore *postgres.PaymentStore, audit systemconfigapp.AuditWriter, rdb *redis.Client, logger *zap.Logger, ) *Service { return &Service{ store: store, orderStore: orderStore, rechargeOrderStore: rechargeOrderStore, agentRechargeStore: agentRechargeStore, paymentStore: paymentStore, audit: audit, redis: rdb, logger: logger, } } // Create 创建微信参数配置 // POST /api/admin/wechat-configs func (s *Service) Create(ctx context.Context, req *dto.CreateWechatConfigRequest) (*dto.WechatConfigResponse, error) { // 根据 provider_type 校验必填字段 if err := s.validateProviderFields(req); err != nil { s.recordAuditFailure(ctx, systemconfigapp.ChangeAudit{ OperatorID: middleware.GetUserIDFromContext(ctx), OperationType: constants.AuditOperationPaymentConfigCreate, Description: "拒绝创建非法支付连接配置", ConfigKey: "payment_config.new:" + req.Name, DisplayName: req.Name, Identity: map[string]any{"name": req.Name, "provider_type": req.ProviderType, "credentials_configured": paymentRequestCredentialsConfigured(req)}, Result: constants.AuditResultDenied, ErrorCode: strconv.Itoa(errors.CodeInvalidParam), ErrorSummary: "支付连接配置字段校验失败", }) return nil, err } if s.audit == nil { return nil, errors.New(errors.CodeInvalidStatus, "支付配置审计接缝未配置") } var desc *string if req.Description != "" { desc = &req.Description } config := &model.WechatConfig{ Name: req.Name, Description: desc, ProviderType: req.ProviderType, IsActive: false, OaAppID: req.OaAppID, OaAppSecret: req.OaAppSecret, OaToken: req.OaToken, OaAesKey: req.OaAesKey, OaOAuthRedirectURL: req.OaOAuthRedirectURL, MiniappAppID: req.MiniappAppID, MiniappAppSecret: req.MiniappAppSecret, WxMchID: req.WxMchID, WxAPIV3Key: req.WxAPIV3Key, WxAPIV2Key: req.WxAPIV2Key, WxCertContent: req.WxCertContent, WxKeyContent: req.WxKeyContent, WxSerialNo: req.WxSerialNo, WxNotifyURL: req.WxNotifyURL, FyInsCd: req.FyInsCd, FyMchntCd: req.FyMchntCd, FyTermID: req.FyTermID, FyPrivateKey: req.FyPrivateKey, FyPublicKey: req.FyPublicKey, FyAPIURL: req.FyAPIURL, FyNotifyURL: req.FyNotifyURL, AliAppID: req.AliAppID, AliPrivateKey: req.AliPrivateKey, AliPublicKey: req.AliPublicKey, AliNotifyURL: req.AliNotifyURL, AliReturnURL: req.AliReturnURL, AliProduction: req.AliProduction, AliPayExpireMinutes: req.AliPayExpireMinutes, } // 支付宝过期分钟数默认值 if config.AliPayExpireMinutes == 0 { config.AliPayExpireMinutes = model.DefaultAliPayExpireMinutes } config.Creator = middleware.GetUserIDFromContext(ctx) err := s.store.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := s.store.WithTx(tx).Create(ctx, config); err != nil { return err } return s.writeAudit(ctx, tx, constants.AuditOperationPaymentConfigCreate, "创建支付连接配置", nil, config) }) if err != nil { s.recordPaymentFailure(ctx, constants.AuditOperationPaymentConfigCreate, "创建支付连接配置失败", config, err) return nil, errors.Wrap(errors.CodeInternalError, err, "创建微信支付配置失败") } return dto.FromWechatConfigModel(config), nil } // List 获取配置列表 // GET /api/admin/wechat-configs func (s *Service) List(ctx context.Context, req *dto.WechatConfigListRequest) ([]*dto.WechatConfigResponse, int64, error) { opts := &store.QueryOptions{ Page: req.Page, PageSize: req.PageSize, OrderBy: "id DESC", } if opts.Page == 0 { opts.Page = 1 } if opts.PageSize == 0 { opts.PageSize = constants.DefaultPageSize } filters := make(map[string]interface{}) if req.ProviderType != nil { filters["provider_type"] = *req.ProviderType } filters["is_active"] = req.IsActive configs, total, err := s.store.List(ctx, opts, filters) if err != nil { return nil, 0, errors.Wrap(errors.CodeInternalError, err, "查询微信支付配置列表失败") } responses := make([]*dto.WechatConfigResponse, len(configs)) for i, c := range configs { responses[i] = dto.FromWechatConfigModel(c) } return responses, total, nil } // Get 获取配置详情 // GET /api/admin/wechat-configs/:id func (s *Service) Get(ctx context.Context, id uint) (*dto.WechatConfigResponse, error) { config, err := s.store.GetByID(ctx, id) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeWechatConfigNotFound) } return nil, errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败") } return dto.FromWechatConfigModel(config), nil } // Update 更新微信参数配置 // PUT /api/admin/wechat-configs/:id func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdateWechatConfigRequest) (*dto.WechatConfigResponse, error) { config, err := s.store.GetByID(ctx, id) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeWechatConfigNotFound) } return nil, errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败") } if s.audit == nil { return nil, errors.New(errors.CodeInvalidStatus, "支付配置审计接缝未配置") } before := *config // 合并字段:指针非 nil 时更新,敏感字段空字符串表示保持原值 if req.Name != nil { config.Name = *req.Name } if req.Description != nil { config.Description = req.Description } if req.ProviderType != nil { config.ProviderType = *req.ProviderType } // OAuth 公众号 s.mergeStringField(&config.OaAppID, req.OaAppID) s.mergeSensitiveField(&config.OaAppSecret, req.OaAppSecret) s.mergeSensitiveField(&config.OaToken, req.OaToken) s.mergeSensitiveField(&config.OaAesKey, req.OaAesKey) s.mergeStringField(&config.OaOAuthRedirectURL, req.OaOAuthRedirectURL) // OAuth 小程序 s.mergeStringField(&config.MiniappAppID, req.MiniappAppID) s.mergeSensitiveField(&config.MiniappAppSecret, req.MiniappAppSecret) // 微信直连支付 s.mergeStringField(&config.WxMchID, req.WxMchID) s.mergeSensitiveField(&config.WxAPIV3Key, req.WxAPIV3Key) s.mergeSensitiveField(&config.WxAPIV2Key, req.WxAPIV2Key) s.mergeSensitiveField(&config.WxCertContent, req.WxCertContent) s.mergeSensitiveField(&config.WxKeyContent, req.WxKeyContent) s.mergeStringField(&config.WxSerialNo, req.WxSerialNo) s.mergeStringField(&config.WxNotifyURL, req.WxNotifyURL) // 富友支付 s.mergeStringField(&config.FyInsCd, req.FyInsCd) s.mergeStringField(&config.FyMchntCd, req.FyMchntCd) s.mergeStringField(&config.FyTermID, req.FyTermID) s.mergeSensitiveField(&config.FyPrivateKey, req.FyPrivateKey) s.mergeSensitiveField(&config.FyPublicKey, req.FyPublicKey) s.mergeStringField(&config.FyAPIURL, req.FyAPIURL) s.mergeStringField(&config.FyNotifyURL, req.FyNotifyURL) // 支付宝支付 s.mergeStringField(&config.AliAppID, req.AliAppID) s.mergeSensitiveField(&config.AliPrivateKey, req.AliPrivateKey) s.mergeSensitiveField(&config.AliPublicKey, req.AliPublicKey) s.mergeStringField(&config.AliNotifyURL, req.AliNotifyURL) s.mergeStringField(&config.AliReturnURL, req.AliReturnURL) if req.AliProduction != nil { config.AliProduction = *req.AliProduction } if req.AliPayExpireMinutes != nil && *req.AliPayExpireMinutes > 0 { config.AliPayExpireMinutes = *req.AliPayExpireMinutes } config.Updater = middleware.GetUserIDFromContext(ctx) err = s.store.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := s.store.WithTx(tx).Update(ctx, config); err != nil { return err } return s.writeAudit(ctx, tx, constants.AuditOperationPaymentConfigUpdate, "更新支付连接配置", &before, config) }) if err != nil { s.recordPaymentFailure(ctx, constants.AuditOperationPaymentConfigUpdate, "更新支付连接配置失败", config, err) return nil, errors.Wrap(errors.CodeInternalError, err, "更新微信支付配置失败") } // 如果当前配置处于激活状态,清除缓存 if config.IsActive { s.clearActiveConfigCache(ctx) } return dto.FromWechatConfigModel(config), nil } // Delete 删除微信参数配置 // DELETE /api/admin/wechat-configs/:id func (s *Service) Delete(ctx context.Context, id uint) error { config, err := s.store.GetByID(ctx, id) if err != nil { if err == gorm.ErrRecordNotFound { return errors.New(errors.CodeWechatConfigNotFound) } return errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败") } if s.audit == nil { return errors.New(errors.CodeInvalidStatus, "支付配置审计接缝未配置") } // 不允许删除正在激活的配置 if config.IsActive { s.recordPaymentDenied(ctx, constants.AuditOperationPaymentConfigDelete, "拒绝删除生效中的支付连接配置", config, errors.CodeWechatConfigActive) return errors.New(errors.CodeWechatConfigActive) } // 检查是否存在待支付订单 pendingOrders, err := s.store.CountPendingOrdersByConfigID(ctx, id) if err != nil { return errors.Wrap(errors.CodeInternalError, err, "检查在途订单失败") } pendingRecharges, err := s.store.CountPendingRechargesByConfigID(ctx, id) if err != nil { return errors.Wrap(errors.CodeInternalError, err, "检查在途充值失败") } if pendingOrders > 0 || pendingRecharges > 0 { s.recordPaymentDenied(ctx, constants.AuditOperationPaymentConfigDelete, "拒绝删除存在在途业务的支付连接配置", config, errors.CodeWechatConfigHasPendingOrders) return errors.New(errors.CodeWechatConfigHasPendingOrders) } err = s.store.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := s.store.WithTx(tx).SoftDelete(ctx, id); err != nil { return err } return s.writeAudit(ctx, tx, constants.AuditOperationPaymentConfigDelete, "删除支付连接配置", config, nil) }) if err != nil { s.recordPaymentFailure(ctx, constants.AuditOperationPaymentConfigDelete, "删除支付连接配置失败", config, err) return errors.Wrap(errors.CodeInternalError, err, "删除微信支付配置失败") } s.clearActiveConfigCache(ctx) return nil } // Activate 激活指定配置(同一时间只有一个激活配置) // POST /api/admin/wechat-configs/:id/activate func (s *Service) Activate(ctx context.Context, id uint) (*dto.WechatConfigResponse, error) { config, err := s.store.GetByID(ctx, id) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeWechatConfigNotFound) } return nil, errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败") } if s.audit == nil { return nil, errors.New(errors.CodeInvalidStatus, "支付配置审计接缝未配置") } before := *config // 保留原激活配置快照,确保自动停用也进入该配置自身时间线。 oldActive, oldErr := s.store.GetActive(ctx) // 事务内激活 db := s.store.DB() if err := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := s.store.ActivateInTx(ctx, tx, id); err != nil { return err } after := before after.IsActive = true if oldErr == nil && oldActive != nil && oldActive.ID != id { oldAfter := *oldActive oldAfter.IsActive = false if err := s.writeAudit(ctx, tx, constants.AuditOperationPaymentConfigDeactivate, "激活其他配置时停用原支付连接配置", oldActive, &oldAfter); err != nil { return err } } return s.writeAudit(ctx, tx, constants.AuditOperationPaymentConfigActivate, "激活支付连接配置", &before, &after) }); err != nil { s.recordPaymentFailure(ctx, constants.AuditOperationPaymentConfigActivate, "激活支付连接配置失败", config, err) return nil, errors.Wrap(errors.CodeInternalError, err, "激活微信支付配置失败") } s.clearActiveConfigCache(ctx) // 重新查询最新状态 config, _ = s.store.GetByID(ctx, id) return dto.FromWechatConfigModel(config), nil } // Deactivate 停用指定配置 // POST /api/admin/wechat-configs/:id/deactivate func (s *Service) Deactivate(ctx context.Context, id uint) (*dto.WechatConfigResponse, error) { config, err := s.store.GetByID(ctx, id) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeWechatConfigNotFound) } return nil, errors.Wrap(errors.CodeInternalError, err, "获取微信支付配置失败") } if s.audit == nil { return nil, errors.New(errors.CodeInvalidStatus, "支付配置审计接缝未配置") } before := *config after := before after.IsActive = false err = s.store.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := s.store.WithTx(tx).Deactivate(ctx, id); err != nil { return err } return s.writeAudit(ctx, tx, constants.AuditOperationPaymentConfigDeactivate, "停用支付连接配置", &before, &after) }) if err != nil { s.recordPaymentFailure(ctx, constants.AuditOperationPaymentConfigDeactivate, "停用支付连接配置失败", config, err) return nil, errors.Wrap(errors.CodeInternalError, err, "停用微信支付配置失败") } s.clearActiveConfigCache(ctx) // 重新查询最新状态 config, _ = s.store.GetByID(ctx, id) return dto.FromWechatConfigModel(config), nil } func (s *Service) writeAudit(ctx context.Context, tx *gorm.DB, operation, description string, before, after *model.WechatConfig) error { config := after if config == nil { config = before } resourceID := strconv.FormatUint(uint64(config.ID), 10) requestID := "" if value := middleware.GetRequestIDFromContext(ctx); value != nil { requestID = *value } return s.audit.WriteConfigChange(ctx, tx, systemconfigapp.ChangeAudit{ OperatorID: middleware.GetUserIDFromContext(ctx), OperationType: operation, Description: description, ConfigKey: "payment_config." + resourceID, Module: "payment", ResourceID: &resourceID, DisplayName: config.Name, Identity: paymentConfigIdentity(config), BeforeData: paymentConfigAuditSnapshot(before), AfterData: paymentConfigAuditSnapshot(after), RequestID: requestID, CorrelationID: requestID, }) } func (s *Service) recordPaymentDenied(ctx context.Context, operation, description string, config *model.WechatConfig, code int) { s.recordAuditFailure(ctx, paymentFailureAudit(ctx, operation, description, config, constants.AuditResultDenied, code)) } func (s *Service) recordPaymentFailure(ctx context.Context, operation, description string, config *model.WechatConfig, _ error) { s.recordAuditFailure(ctx, paymentFailureAudit(ctx, operation, description, config, constants.AuditResultFailed, errors.CodeDatabaseError)) } func (s *Service) recordAuditFailure(ctx context.Context, audit systemconfigapp.ChangeAudit) { if s.audit == nil || s.store == nil || s.store.DB() == nil || audit.OperatorID == 0 || audit.ConfigKey == "" { return } if value := middleware.GetRequestIDFromContext(ctx); value != nil { audit.RequestID = *value audit.CorrelationID = *value } if err := s.store.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { return s.audit.WriteConfigChange(ctx, tx, audit) }); err != nil { auditfailure.RecordSecondaryWriteFailure(audit.OperationType, audit.ConfigKey, audit.RequestID, audit.CorrelationID, audit.ErrorCode, err) } } func paymentFailureAudit(ctx context.Context, operation, description string, config *model.WechatConfig, result string, code int) systemconfigapp.ChangeAudit { resourceID := strconv.FormatUint(uint64(config.ID), 10) return systemconfigapp.ChangeAudit{ OperatorID: middleware.GetUserIDFromContext(ctx), OperationType: operation, Description: description, ConfigKey: "payment_config." + resourceID, Module: "payment", ResourceID: &resourceID, DisplayName: config.Name, Identity: paymentConfigIdentity(config), BeforeData: paymentConfigAuditSnapshot(config), Result: result, ErrorCode: strconv.Itoa(code), ErrorSummary: description, } } func paymentConfigIdentity(config *model.WechatConfig) map[string]any { if config == nil { return nil } return map[string]any{ "id": config.ID, "name": config.Name, "provider_type": config.ProviderType, "is_active": config.IsActive, "credentials_configured": paymentConfigCredentialsConfigured(config), } } func paymentConfigAuditSnapshot(config *model.WechatConfig) map[string]any { if config == nil { return nil } return map[string]any{ "id": config.ID, "name": config.Name, "description": config.Description, "provider_type": config.ProviderType, "is_active": config.IsActive, "oa_app_id": config.OaAppID, "oa_oauth_redirect_url": config.OaOAuthRedirectURL, "miniapp_app_id": config.MiniappAppID, "wx_mch_id": config.WxMchID, "wx_serial_no": config.WxSerialNo, "wx_notify_url": config.WxNotifyURL, "fy_ins_cd": config.FyInsCd, "fy_mchnt_cd": config.FyMchntCd, "fy_term_id": config.FyTermID, "fy_api_url": config.FyAPIURL, "fy_notify_url": config.FyNotifyURL, "ali_app_id": config.AliAppID, "ali_notify_url": config.AliNotifyURL, "ali_return_url": config.AliReturnURL, "ali_production": config.AliProduction, "ali_pay_expire_minutes": config.AliPayExpireMinutes, "credentials_configured": paymentConfigCredentialsConfigured(config), "oauth_configured": config.OaAppSecret != "" || config.OaToken != "" || config.OaAesKey != "" || config.MiniappAppSecret != "", "wechat_payment_configured": config.WxAPIV3Key != "" || config.WxAPIV2Key != "" || config.WxCertContent != "" || config.WxKeyContent != "", "fuiou_configured": config.FyPrivateKey != "" || config.FyPublicKey != "", "alipay_configured": config.AliPrivateKey != "" || config.AliPublicKey != "", } } func paymentConfigCredentialsConfigured(config *model.WechatConfig) bool { if config == nil { return false } return config.OaAppSecret != "" || config.OaToken != "" || config.OaAesKey != "" || config.MiniappAppSecret != "" || config.WxAPIV3Key != "" || config.WxAPIV2Key != "" || config.WxCertContent != "" || config.WxKeyContent != "" || config.FyPrivateKey != "" || config.FyPublicKey != "" || config.AliPrivateKey != "" || config.AliPublicKey != "" } func paymentRequestCredentialsConfigured(request *dto.CreateWechatConfigRequest) bool { return request != nil && (request.OaAppSecret != "" || request.OaToken != "" || request.OaAesKey != "" || request.MiniappAppSecret != "" || request.WxAPIV3Key != "" || request.WxAPIV2Key != "" || request.WxCertContent != "" || request.WxKeyContent != "" || request.FyPrivateKey != "" || request.FyPublicKey != "" || request.AliPrivateKey != "" || request.AliPublicKey != "") } // GetActiveConfig 获取当前生效的支付配置(带 Redis 缓存) // 缓存策略:命中直接返回,未命中查 DB 后缓存 5 分钟,无记录缓存 "none" 1 分钟 func (s *Service) GetActiveConfig(ctx context.Context) (*model.WechatConfig, error) { // 尝试从 Redis 获取 val, err := s.redis.Get(ctx, redisActiveConfigKey).Result() if err == nil { if val == "none" { return nil, nil } var config model.WechatConfig if err := sonic.UnmarshalString(val, &config); err == nil { return &config, nil } } // Redis 未命中,查询数据库 config, err := s.store.GetActive(ctx) if err != nil { if err == gorm.ErrRecordNotFound { // 无激活配置,缓存空标记 1 分钟 s.redis.Set(ctx, redisActiveConfigKey, "none", 1*time.Minute) return nil, nil } return nil, errors.Wrap(errors.CodeInternalError, err, "查询激活配置失败") } // 缓存配置 5 分钟 if data, err := sonic.MarshalString(config); err == nil { s.redis.Set(ctx, redisActiveConfigKey, data, 5*time.Minute) } return config, nil } // GetActiveConfigForAPI 获取当前生效的支付配置(API 响应,已脱敏) func (s *Service) GetActiveConfigForAPI(ctx context.Context) (*dto.WechatConfigResponse, error) { config, err := s.GetActiveConfig(ctx) if err != nil { return nil, err } if config == nil { return nil, nil } return dto.FromWechatConfigModel(config), nil } // clearActiveConfigCache 清除激活配置的 Redis 缓存 func (s *Service) clearActiveConfigCache(ctx context.Context) { if err := s.redis.Del(ctx, redisActiveConfigKey).Err(); err != nil { s.logger.Warn("清除微信支付配置缓存失败", zap.Error(err)) } } // validateProviderFields 根据支付渠道类型校验必填字段 func (s *Service) validateProviderFields(req *dto.CreateWechatConfigRequest) error { switch req.ProviderType { case model.ProviderTypeWechat: if req.WxMchID == "" || req.WxAPIV3Key == "" || req.WxCertContent == "" || req.WxKeyContent == "" || req.WxSerialNo == "" || req.WxNotifyURL == "" { return errors.New(errors.CodeInvalidParam, "微信直连支付必填字段不完整:wx_mch_id, wx_api_v3_key, wx_cert_content, wx_key_content, wx_serial_no, wx_notify_url") } case model.ProviderTypeFuiou: if req.FyInsCd == "" || req.FyMchntCd == "" || req.FyTermID == "" || req.FyPrivateKey == "" || req.FyPublicKey == "" || req.FyAPIURL == "" || req.FyNotifyURL == "" { return errors.New(errors.CodeInvalidParam, "富友支付必填字段不完整:fy_ins_cd, fy_mchnt_cd, fy_term_id, fy_private_key, fy_public_key, fy_api_url, fy_notify_url") } } return nil } // mergeStringField 合并普通字符串字段:指针非 nil 时用新值覆盖 func (s *Service) mergeStringField(target *string, newVal *string) { if newVal != nil { *target = *newVal } } // mergeSensitiveField 合并敏感字段:指针非 nil 且非空字符串时覆盖,空字符串保持原值 func (s *Service) mergeSensitiveField(target *string, newVal *string) { if newVal != nil && *newVal != "" { *target = *newVal } } // GetConfigForCallback 按订单号查找创建该订单时所用的支付配置(含已软删除/停用的记录) // 回调验签必须使用创建订单时的密钥,否则签名不匹配。 // 若找不到 payment_config_id(旧订单或数据缺失),回退到当前激活配置。 func (s *Service) GetConfigForCallback(ctx context.Context, orderNo string) (*model.WechatConfig, error) { configID, err := s.resolvePaymentConfigID(ctx, orderNo) if err != nil { s.logger.Warn("回调:查询订单 payment_config_id 失败,回退激活配置", zap.String("order_no", orderNo), zap.Error(err), ) } if configID != nil { cfg, err := s.store.GetByIDUnscoped(ctx, *configID) if err == nil { return cfg, nil } s.logger.Warn("回调:按 config_id 加载配置失败,回退激活配置", zap.Uint("config_id", *configID), zap.Error(err), ) } return s.GetActiveConfig(ctx) } // resolvePaymentConfigID 按订单号前缀从对应表中取 payment_config_id func (s *Service) resolvePaymentConfigID(ctx context.Context, orderNo string) (*uint, error) { if s.paymentStore != nil { payment, err := s.paymentStore.GetByPaymentNo(ctx, orderNo) if err == nil { if payment.PaymentConfigID != nil { return payment.PaymentConfigID, nil } } else if err != gorm.ErrRecordNotFound { return nil, err } } switch { case len(orderNo) >= 3 && orderNo[:3] == "ORD": order, err := s.orderStore.GetByOrderNo(ctx, orderNo) if err != nil { return nil, err } return order.PaymentConfigID, nil case len(orderNo) >= 4 && orderNo[:4] == constants.AssetRechargeOrderPrefix: record, err := s.rechargeOrderStore.GetByRechargeOrderNo(ctx, orderNo) if err != nil { return nil, err } return record.PaymentConfigID, nil case len(orderNo) >= 4 && orderNo[:4] == constants.AgentRechargeOrderPrefix: record, err := s.agentRechargeStore.GetByRechargeNo(ctx, orderNo) if err != nil { return nil, err } return record.PaymentConfigID, nil default: return nil, fmt.Errorf("未知订单号前缀: %s", orderNo) } }