package billing import ( "context" "errors" "fmt" "strings" "github.com/google/uuid" ) // FeatureKeyFromError extracts the feature key from an ErrFeatureDisabled wrap // ("feature_disabled: marketing.campaigns.generate_ai"). func FeatureKeyFromError(err error) string { if err == nil || !errors.Is(err, ErrFeatureDisabled) { return "" } msg := err.Error() const prefix = "feature_disabled:" idx := strings.Index(strings.ToLower(msg), prefix) if idx < 0 { return "" } return strings.TrimSpace(msg[idx+len(prefix):]) } // FeatureKeysForProcessingType maps a processing job type to registry keys that // must be effective before StartJob may proceed. func FeatureKeysForProcessingType(processingType string) []string { switch strings.ToLower(strings.TrimSpace(processingType)) { case "title": return []string{"capability.ai_processing", "catalog.products.process_ai_titles"} case "description": return []string{"capability.ai_processing", "catalog.products.process_ai_descriptions"} case "enhance", "enhance_only", "enhance-only", "seo", "seo_ai": return []string{"capability.ai_processing"} case "eprel", "eprel_only": return []string{"capability.eprel"} case "email_campaign", "email_campaign_ai", "campaign_ai": return []string{"capability.campaign_ai", "marketing.campaigns.generate_ai"} case "normalize", "specs", "fill", "categories", "attributes", "full", "": return []string{"capability.normalize_specs_fill"} default: return []string{"capability.normalize_specs_fill"} } } // AssertFeatures fails closed on the first disabled key. // Loads Capabilities once for the whole key set (avoids N×CapabilitiesForCompany). func (s *Service) AssertFeatures(ctx context.Context, companyID uuid.UUID, keys ...string) error { if len(keys) == 0 { return nil } caps, err := s.CapabilitiesForCompany(ctx, companyID) if err != nil { return err } for _, key := range keys { key = strings.TrimSpace(key) if key == "" { continue } if caps.Features == nil || !caps.Features[key] { return fmt.Errorf("%w: %s", ErrFeatureDisabled, key) } } return nil } // AssertProcessingFeatures enforces plan ∩ global feature keys for a job type. func (s *Service) AssertProcessingFeatures(ctx context.Context, companyID uuid.UUID, processingType string) error { if s == nil { return nil } return s.AssertFeatures(ctx, companyID, FeatureKeysForProcessingType(processingType)...) }