Compare commits

..

1 Commits

Author SHA1 Message Date
ي
4e0f176842 Add strict Stripe checkout guard via env flag 2026-03-30 07:51:45 +05:30
3 changed files with 207 additions and 174 deletions

View File

@@ -48,8 +48,6 @@ load_dotenv(backend_dir / '.env') # backend/.env
load_dotenv(project_root / '.env') # root .env (fallback)
load_dotenv() # CWD .env (fallback)
PODCAST_ONLY_DEMO_MODE = os.getenv("PODCAST_ONLY_DEMO_MODE", "false").lower() in {"1", "true", "yes", "on"}
# Set up clean logging for end users
from logging_config import setup_clean_logging
setup_clean_logging()
@@ -112,8 +110,7 @@ from services.startup_health import (
# Import OAuth token monitoring routes
from api.oauth_token_monitoring_routes import router as oauth_token_monitoring_router
if not PODCAST_ONLY_DEMO_MODE:
# Import SEO Dashboard endpoints only when non-demo features are enabled
# Import SEO Dashboard endpoints
from api.seo_dashboard import (
get_seo_dashboard_data,
get_seo_health_score,
@@ -262,17 +259,12 @@ async def onboarding_status():
return onboarding_manager.get_onboarding_status()
# Include routers using modular utilities
if not PODCAST_ONLY_DEMO_MODE:
router_manager.include_core_routers()
router_manager.include_optional_routers()
else:
logger.info("PODCAST_ONLY_DEMO_MODE enabled: including only podcast and subscription feature routers.")
app.include_router(subscription_router)
# Include assets serving router (must be mounted to serve generated images)
app.include_router(assets_serving_router)
if not PODCAST_ONLY_DEMO_MODE:
# SEO Dashboard endpoints
@app.get("/api/seo-dashboard/data")
async def seo_dashboard_data():
@@ -340,6 +332,8 @@ if not PODCAST_ONLY_DEMO_MODE:
"""Refresh analytics data by invalidating cache and fetching fresh data."""
return await refresh_analytics_data(current_user, site_url)
@app.get("/api/seo-dashboard/onboarding-task-health")
async def onboarding_task_health_endpoint(current_user: dict = Depends(get_current_user), site_url: str = None):
"""Get consolidated health for onboarding-scheduled SEO tasks."""
@@ -353,17 +347,31 @@ if not PODCAST_ONLY_DEMO_MODE:
# Phase 2B: Semantic health monitoring endpoint (24-hour polling)
@app.get("/api/seo-dashboard/semantic-health")
async def semantic_health_endpoint(current_user: dict = Depends(get_current_user)):
"""Get real-time semantic health metrics for content and competitors."""
"""
Get real-time semantic health metrics for content and competitors.
This endpoint provides Phase 2B semantic intelligence monitoring data.
Returns semantic health score, status, and recommendations.
Data is cached and updated every 24 hours via scheduler.
"""
return await get_semantic_health(current_user)
@app.get("/api/seo-dashboard/cache-stats")
async def semantic_cache_stats_endpoint(current_user: dict = Depends(get_current_user)):
"""Get semantic cache performance statistics."""
"""
Get semantic cache performance statistics.
Returns hit rate, memory usage, and eviction counts.
"""
return await get_semantic_cache_stats(current_user)
@app.get("/api/seo-dashboard/sif-health")
async def sif_indexing_health_endpoint(current_user: dict = Depends(get_current_user)):
"""Get SIF indexing health summary for the current user."""
"""
Get SIF indexing health summary for the current user.
Used by the Semantic Indexing Status widget on the dashboard.
"""
return await get_sif_indexing_health(current_user)
# Comprehensive SEO Analysis endpoints
@@ -416,7 +424,6 @@ if not PODCAST_ONLY_DEMO_MODE:
from api.podcast.router import router as podcast_router
app.include_router(podcast_router)
if not PODCAST_ONLY_DEMO_MODE:
# Include YouTube Creator Studio router
from api.youtube.router import router as youtube_router
app.include_router(youtube_router, prefix="/api")

View File

@@ -16,6 +16,10 @@ REQUIRED_STRIPE_PLAN_KEYS = {
}
def _is_truthy_env(var_name: str) -> bool:
return os.getenv(var_name, "").strip().lower() in {"1", "true", "yes", "on"}
def _detect_stripe_mode() -> str:
configured_mode = os.getenv("STRIPE_MODE", "").strip().lower()
if configured_mode in {"test", "live"}:
@@ -98,7 +102,16 @@ class StripeService:
self.db = db
self.api_key = os.getenv("STRIPE_SECRET_KEY")
self.webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")
self.require_stripe_checkout = _is_truthy_env("REQUIRE_STRIPE_CHECKOUT")
if not self.api_key:
if self.require_stripe_checkout:
raise HTTPException(
status_code=500,
detail=(
"REQUIRE_STRIPE_CHECKOUT=true but STRIPE_SECRET_KEY is missing. "
"Configure STRIPE_SECRET_KEY to enable Stripe checkout."
),
)
logger.warning("STRIPE_SECRET_KEY is not set. Stripe integration will not work.")
else:
stripe.api_key = self.api_key

View File

@@ -52,6 +52,10 @@ export interface SubscriptionPlan {
}
const PricingPage: React.FC = () => {
const requireStripeCheckout = ['1', 'true', 'yes', 'on'].includes(
(process.env.REACT_APP_REQUIRE_STRIPE_CHECKOUT || '').toLowerCase()
);
const stripePublishableKey = process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY;
const navigate = useNavigate();
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
const [loading, setLoading] = useState(true);
@@ -173,7 +177,7 @@ const PricingPage: React.FC = () => {
const userId = localStorage.getItem('user_id') || 'anonymous';
// Check if Stripe is configured
if (process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY) {
if (stripePublishableKey) {
console.log('[PricingPage] Initiating Stripe Checkout');
const response = await apiClient.post('/api/subscription/create-checkout-session', {
@@ -187,6 +191,14 @@ const PricingPage: React.FC = () => {
window.location.href = response.data.url;
return;
}
if (requireStripeCheckout) {
throw new Error('Stripe checkout is required but checkout URL was not returned.');
}
} else if (requireStripeCheckout) {
throw new Error(
'Stripe checkout is required but REACT_APP_STRIPE_PUBLISHABLE_KEY is not configured.'
);
}
console.log('[PricingPage] Making legacy subscription API call:', {
@@ -271,7 +283,8 @@ const PricingPage: React.FC = () => {
}, 3000);
} catch (err) {
console.error('Error subscribing:', err);
setError('Failed to process subscription');
const errorMessage = err instanceof Error ? err.message : 'Failed to process subscription';
setError(errorMessage);
setSuccessSnackbar({ open: false, message: '', countdown: 0 });
} finally {
setSubscribing(false);