// Package payment 提供支付配置动态加载功能 // 根据 payment_config_id 按需加载支付实例,支持多商户场景 package payment import ( "context" "net/http" "time" "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" "github.com/bytedance/sonic" "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/wechat" ) // PaymentConfigLoader 支付配置加载器接口 // 根据 payment_config_id 动态加载支付实例,支持多商户场景 type PaymentConfigLoader interface { // LoadConfig 加载指定 ID 的支付配置实例 LoadConfig(ctx context.Context, configID uint) (wechat.PaymentServiceInterface, error) } // paymentConfigLoader PaymentConfigLoader 的具体实现 type paymentConfigLoader struct { wechatConfigStore *postgres.WechatConfigStore redis *redis.Client wechatCache kernel.CacheInterface logger *zap.Logger } // NewPaymentConfigLoader 创建 PaymentConfigLoader 实例 func NewPaymentConfigLoader( store *postgres.WechatConfigStore, rdb *redis.Client, logger *zap.Logger, ) PaymentConfigLoader { return &paymentConfigLoader{ wechatConfigStore: store, redis: rdb, wechatCache: wechat.NewRedisCache(rdb), logger: logger, } } // LoadConfig 根据 configID 加载支付配置实例 // 优先从 Redis 缓存读取 WechatConfig(TTL 1 小时),未命中则查数据库 func (l *paymentConfigLoader) LoadConfig(ctx context.Context, configID uint) (wechat.PaymentServiceInterface, error) { config, err := l.loadConfigFromCacheOrDB(ctx, configID) if err != nil { return nil, err } return l.buildPaymentService(config) } // loadConfigFromCacheOrDB 从 Redis 缓存或数据库加载 WechatConfig // 缓存 key: payment:config:{configID},TTL 1 小时 func (l *paymentConfigLoader) loadConfigFromCacheOrDB(ctx context.Context, configID uint) (*model.WechatConfig, error) { cacheKey := constants.RedisPaymentConfigKey(configID) val, err := l.redis.Get(ctx, cacheKey).Result() if err == nil { var config model.WechatConfig if unmarshalErr := sonic.UnmarshalString(val, &config); unmarshalErr == nil { return &config, nil } } config, dbErr := l.wechatConfigStore.GetByID(ctx, configID) if dbErr != nil { if dbErr == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeWechatConfigNotFound) } return nil, errors.Wrap(errors.CodeInternalError, dbErr, "加载支付配置失败") } if data, marshalErr := sonic.MarshalString(config); marshalErr == nil { l.redis.Set(ctx, cacheKey, data, time.Hour) } return config, nil } // buildPaymentService 根据 WechatConfig 构建对应的支付服务实例 // 支持 wechat(v3)和 wechat_v2(v2)两种渠道类型 func (l *paymentConfigLoader) buildPaymentService(config *model.WechatConfig) (wechat.PaymentServiceInterface, error) { switch config.ProviderType { case model.ProviderTypeWechat: // 微信直连 v3,使用 PowerWeChat SDK app, err := wechat.NewPaymentAppFromConfig(config, config.OaAppID, l.wechatCache, l.logger) if err != nil { return nil, errors.Wrap(errors.CodeWechatPayFailed, err, "构建微信支付 v3 实例失败") } return wechat.NewPaymentService(app, l.logger), nil case model.ProviderTypeWechatV2: // 微信直连 v2,仅需 APIv2Key,不要求证书 svc, err := wechat.NewPaymentV2ServiceFromConfig(config, config.OaAppID, l.logger) if err != nil { return nil, errors.Wrap(errors.CodeWechatPayFailed, err, "构建微信支付 v2 实例失败") } // v2 不实现完整接口,通过适配器包装 return &v2PaymentAdapter{svc: svc}, nil default: return nil, errors.New(errors.CodeWechatConfigNotFound, "不支持的支付渠道类型: "+config.ProviderType) } } // v2PaymentAdapter 微信支付 v2 适配器 // 将 PaymentV2Service 适配为完整的 PaymentServiceInterface // v2 仅支持 JSAPI 支付,其余方法返回不支持错误 type v2PaymentAdapter struct { svc *wechat.PaymentV2Service } func (a *v2PaymentAdapter) CreateJSAPIOrder(ctx context.Context, orderNo, description, openID string, amount int) (*wechat.JSAPIPayResult, error) { return a.svc.CreateJSAPIOrder(ctx, orderNo, description, openID, amount) } func (a *v2PaymentAdapter) CreateH5Order(_ context.Context, _ string, _ string, _ int, _ *wechat.H5SceneInfo) (*wechat.H5PayResult, error) { return nil, errors.New(errors.CodeWechatPayFailed, "微信支付 v2 不支持 H5 支付") } func (a *v2PaymentAdapter) QueryOrder(_ context.Context, _ string) (*wechat.OrderInfo, error) { return nil, errors.New(errors.CodeWechatPayFailed, "微信支付 v2 暂不支持查单接口") } func (a *v2PaymentAdapter) CloseOrder(_ context.Context, _ string) error { return errors.New(errors.CodeWechatPayFailed, "微信支付 v2 暂不支持关单接口") } func (a *v2PaymentAdapter) HandlePaymentNotify(_ *http.Request, _ wechat.PaymentNotifyCallback) (*http.Response, error) { return nil, errors.New(errors.CodeWechatPayFailed, "微信支付 v2 回调请通过 VerifyCallback 处理") }