package channel import ( "fmt" "sync" "gorm.io/gorm" "github.com/wangjia/pay/internal/model" ) // Registry 按商户缓存已构造的渠道实例,避免每次下单都重新加载密钥。 type Registry struct { db *gorm.DB mu sync.RWMutex cache map[uint64]Channel } func NewRegistry(db *gorm.DB) *Registry { return &Registry{db: db, cache: make(map[uint64]Channel)} } // ByMerchantID 取(或构造)指定商户的渠道。 func (r *Registry) ByMerchantID(id uint64) (Channel, *model.Merchant, error) { var m model.Merchant if err := r.db.First(&m, "id = ? AND enabled = ?", id, true).Error; err != nil { return nil, nil, fmt.Errorf("商户不存在或已停用: %w", err) } ch, err := r.get(&m) return ch, &m, err } // AlipayByAppID 异步回调时用 app_id 反查商户并取其渠道(用于验签)。 func (r *Registry) AlipayByAppID(appID string) (Channel, *model.Merchant, error) { if appID == "" { return nil, nil, fmt.Errorf("回调缺少 app_id") } var m model.Merchant if err := r.db.First(&m, "channel = ? AND app_id = ? AND enabled = ?", "alipay", appID, true).Error; err != nil { return nil, nil, fmt.Errorf("找不到 app_id=%s 对应的支付宝商户: %w", appID, err) } ch, err := r.get(&m) return ch, &m, err } // FirstWechat 取一个启用中的微信商户及其渠道,用于回调解密验签。 // 微信 V3 回调正文加密,需先用商户凭证(平台证书 + APIv3 密钥)解密才能拿到 out_trade_no; // 同一公司主体共用一个 mch_id/apiv3_key/证书,故取任一启用的微信商户即可解密, // 解密后再按 out_trade_no 定位到订单真正归属的商户入账。 func (r *Registry) FirstWechat() (Channel, *model.Merchant, error) { var m model.Merchant if err := r.db.First(&m, "channel = ? AND enabled = ?", "wechat", true).Error; err != nil { return nil, nil, fmt.Errorf("没有启用的微信商户: %w", err) } ch, err := r.get(&m) return ch, &m, err } func (r *Registry) get(m *model.Merchant) (Channel, error) { r.mu.RLock() if ch, ok := r.cache[m.ID]; ok { r.mu.RUnlock() return ch, nil } r.mu.RUnlock() ch, err := Build(m) if err != nil { return nil, err } r.mu.Lock() r.cache[m.ID] = ch r.mu.Unlock() return ch, nil } // Invalidate 商户凭证变更后清缓存(预留给管理接口)。 func (r *Registry) Invalidate(id uint64) { r.mu.Lock() delete(r.cache, id) r.mu.Unlock() }