| """ |
| Subscription management component. |
| |
| This component provides UI for managing subscription plans. |
| """ |
| import os |
| import streamlit as st |
| import pandas as pd |
| from datetime import datetime |
| import json |
|
|
| import stripe |
| from streamlit_extras.colored_header import colored_header |
| from streamlit_extras.metric_cards import style_metric_cards |
|
|
| from src.streamlit_subscription_services import ( |
| get_subscription_plans_df, |
| get_subscription_plan, |
| get_user_current_subscription, |
| subscribe_user_to_plan, |
| cancel_subscription, |
| initialize_default_plans |
| ) |
|
|
| |
| STRIPE_PUBLISHABLE_KEY = os.environ.get("STRIPE_PUBLISHABLE_KEY") |
|
|
|
|
| def format_price(price): |
| """Format price display.""" |
| if price == 0: |
| return "Free" |
| return f"${price:.2f}" |
|
|
|
|
| def render_pricing_card(plan, selected_period="monthly"): |
| """Render a pricing card for a subscription plan.""" |
| plan_id = plan["id"] |
| plan_name = plan["name"] |
| plan_tier = plan["tier"] |
| description = plan["description"] |
| |
| |
| if selected_period == "monthly": |
| price = plan["price_monthly"] |
| period_text = "per month" |
| billing_term = "monthly" |
| else: |
| price = plan["price_annually"] |
| period_text = "per year" |
| billing_term = "annually" |
| |
| |
| price_display = format_price(price) |
| |
| |
| features = [ |
| f"β {plan['max_alerts'] if plan['max_alerts'] > 0 else 'Unlimited'} alerts", |
| f"β {plan['max_reports'] if plan['max_reports'] > 0 else 'Unlimited'} reports", |
| f"β {plan['max_searches_per_day'] if plan['max_searches_per_day'] > 0 else 'Unlimited'} searches per day", |
| f"β {plan['max_monitoring_keywords'] if plan['max_monitoring_keywords'] > 0 else 'Unlimited'} monitoring keywords", |
| f"β {plan['max_data_retention_days']} days data retention" |
| ] |
| |
| if plan["supports_api_access"]: |
| features.append("β API access") |
| |
| if plan["supports_live_feed"]: |
| features.append("β Live feed") |
| |
| if plan["supports_dark_web_monitoring"]: |
| features.append("β Dark web monitoring") |
| |
| if plan["supports_export"]: |
| features.append("β Data export") |
| |
| if plan["supports_advanced_analytics"]: |
| features.append("β Advanced analytics") |
| |
| |
| if plan_tier == "free": |
| border_color = "#3498db" |
| header_color = "#3498db" |
| elif plan_tier == "basic": |
| border_color = "#2ecc71" |
| header_color = "#2ecc71" |
| elif plan_tier == "professional": |
| border_color = "#f39c12" |
| header_color = "#f39c12" |
| else: |
| border_color = "#9b59b6" |
| header_color = "#9b59b6" |
| |
| |
| st.markdown(f""" |
| <div style="border: 2px solid {border_color}; border-radius: 10px; padding: 20px; height: 100%;"> |
| <h3 style="color: {header_color}; text-align: center;">{plan_name}</h3> |
| <h2 style="text-align: center; margin-top: 10px; margin-bottom: 5px;">{price_display}</h2> |
| <p style="text-align: center; color: #999; margin-bottom: 20px;">{period_text}</p> |
| <p style="text-align: center; margin-bottom: 20px;">{description}</p> |
| <div style="margin-bottom: 20px;"> |
| {"<br>".join([f'<div style="margin-bottom: 8px;">{feature}</div>' for feature in features])} |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
| |
| |
| if st.button(f"Choose {plan_name}", key=f"choose_{plan_id}_{selected_period}"): |
| if not plan_tier == "free" and STRIPE_PUBLISHABLE_KEY: |
| st.session_state.show_payment_form = True |
| st.session_state.selected_plan = plan |
| st.session_state.selected_billing_period = billing_term |
| else: |
| |
| |
| user_id = 1 |
| subscription = subscribe_user_to_plan( |
| user_id=user_id, |
| plan_id=plan_id, |
| billing_period=billing_term, |
| create_stripe_subscription=False |
| ) |
| |
| if subscription: |
| st.success(f"You're now subscribed to the {plan_name} plan!") |
| st.session_state.current_user_subscription = subscription |
| else: |
| st.error("Failed to subscribe. Please try again.") |
| |
| st.rerun() |
|
|
|
|
| def render_payment_form(): |
| """Render the payment form for subscription.""" |
| if not STRIPE_PUBLISHABLE_KEY: |
| st.error("Stripe API key is not configured. Payment processing is unavailable.") |
| return |
| |
| plan = st.session_state.selected_plan |
| billing_period = st.session_state.selected_billing_period |
| |
| st.markdown("### Payment Information") |
| |
| |
| if billing_period == "monthly": |
| amount = plan["price_monthly"] |
| else: |
| amount = plan["price_annually"] |
| |
| st.markdown(f"You're subscribing to the **{plan['name']} plan** ({billing_period}) for **{format_price(amount)}**.") |
| |
| |
| col1, col2 = st.columns(2) |
| |
| with col1: |
| name = st.text_input("Full Name") |
| |
| with col2: |
| email = st.text_input("Email Address") |
| |
| |
| st.markdown(""" |
| <div id="card-element" style="padding: 10px; border: 1px solid #ccc; border-radius: 4px; margin-bottom: 20px;"></div> |
| <div id="card-errors" style="color: #e74c3c; margin-bottom: 20px;"></div> |
| |
| <script src="https://js.stripe.com/v3/"></script> |
| <script type="text/javascript"> |
| // Initialize Stripe with publishable key |
| var stripe = Stripe('%s'); |
| var elements = stripe.elements(); |
| |
| // Create card element |
| var card = elements.create('card'); |
| card.mount('#card-element'); |
| |
| // Handle real-time validation errors |
| card.addEventListener('change', function(event) { |
| var displayError = document.getElementById('card-errors'); |
| if (event.error) { |
| displayError.textContent = event.error.message; |
| } else { |
| displayError.textContent = ''; |
| } |
| }); |
| |
| // Set up payment method creation |
| window.createPaymentMethod = function() { |
| stripe.createPaymentMethod({ |
| type: 'card', |
| card: card, |
| billing_details: { |
| name: '%s', |
| email: '%s' |
| } |
| }).then(function(result) { |
| if (result.error) { |
| var errorElement = document.getElementById('card-errors'); |
| errorElement.textContent = result.error.message; |
| } else { |
| // Send payment method ID to Streamlit |
| window.parent.postMessage({ |
| type: 'payment-method-created', |
| paymentMethodId: result.paymentMethod.id |
| }, '*'); |
| } |
| }); |
| } |
| </script> |
| """ % (STRIPE_PUBLISHABLE_KEY, name, email), unsafe_allow_html=True) |
| |
| |
| if st.button("Subscribe Now", key="subscribe_button"): |
| |
| st.markdown(""" |
| <script> |
| window.createPaymentMethod(); |
| </script> |
| """, unsafe_allow_html=True) |
| |
| |
| |
| payment_method_id = "pm_" + "".join([str(i) for i in range(24)]) |
| |
| |
| |
| user_id = 1 |
| subscription = subscribe_user_to_plan( |
| user_id=user_id, |
| plan_id=plan["id"], |
| billing_period=billing_period, |
| create_stripe_subscription=True, |
| payment_method_id=payment_method_id |
| ) |
| |
| if subscription: |
| st.success(f"You're now subscribed to the {plan['name']} plan!") |
| st.session_state.show_payment_form = False |
| st.session_state.current_user_subscription = subscription |
| else: |
| st.error("Failed to subscribe. Please try again.") |
| |
| st.rerun() |
| |
| |
| if st.button("Cancel", key="cancel_payment"): |
| st.session_state.show_payment_form = False |
| st.rerun() |
|
|
|
|
| def render_subscription_dashboard(user_id=1): |
| """Render the subscription dashboard for the current user.""" |
| |
| subscription = get_user_current_subscription(user_id) |
| st.session_state.current_user_subscription = subscription |
| |
| if subscription: |
| plan_tier = subscription.get("plan_tier", "").capitalize() |
| plan_name = subscription.get("plan_name", "Unknown Plan") |
| status = subscription.get("status", "").capitalize() |
| billing_period = subscription.get("billing_period", "").capitalize() |
| |
| period_start = subscription.get("current_period_start") |
| period_end = subscription.get("current_period_end") |
| |
| |
| start_date = period_start.strftime("%B %d, %Y") if period_start else "N/A" |
| end_date = period_end.strftime("%B %d, %Y") if period_end else "N/A" |
| |
| st.markdown(f"### Current Subscription: {plan_name}") |
| |
| col1, col2, col3 = st.columns(3) |
| |
| with col1: |
| st.metric("Status", status) |
| |
| with col2: |
| st.metric("Billing Period", billing_period) |
| |
| with col3: |
| days_left = (period_end - datetime.now()).days if period_end else 0 |
| days_left = max(0, days_left) |
| st.metric("Days Remaining", days_left) |
| |
| style_metric_cards() |
| |
| st.markdown(f""" |
| <div style="margin-top: 20px; padding: 15px; background-color: rgba(44, 62, 80, 0.2); border-radius: 6px;"> |
| <p><strong>Billing Period:</strong> {start_date} to {end_date}</p> |
| </div> |
| """, unsafe_allow_html=True) |
| |
| |
| if status.lower() != "canceled": |
| if st.button("Cancel Subscription", key="cancel_subscription"): |
| if cancel_subscription(subscription["id"]): |
| st.success("Your subscription has been canceled. You'll still have access until the end of your billing period.") |
| st.rerun() |
| else: |
| st.error("Failed to cancel subscription. Please try again.") |
| |
| |
| if st.button("View Available Plans", key="view_plans"): |
| st.session_state.show_pricing_table = True |
| st.rerun() |
|
|
|
|
| def render_subscription_metrics(user_id=1): |
| """Render subscription usage metrics for the current user.""" |
| |
| subscription = st.session_state.get("current_user_subscription") or get_user_current_subscription(user_id) |
| |
| if not subscription: |
| return |
| |
| |
| plan = get_subscription_plan(subscription["plan_id"]) |
| |
| if not plan: |
| return |
| |
| st.markdown("### Usage Metrics") |
| |
| |
| col1, col2 = st.columns(2) |
| col3, col4 = st.columns(2) |
| |
| with col1: |
| max_alerts = plan["max_alerts"] |
| alerts_used = 3 |
| alerts_percent = (alerts_used / max_alerts * 100) if max_alerts > 0 else 0 |
| st.metric("Alerts", f"{alerts_used}/{max_alerts if max_alerts > 0 else 'β'}") |
| st.progress(min(alerts_percent, 100) / 100) |
| |
| with col2: |
| max_reports = plan["max_reports"] |
| reports_used = 1 |
| reports_percent = (reports_used / max_reports * 100) if max_reports > 0 else 0 |
| st.metric("Reports", f"{reports_used}/{max_reports if max_reports > 0 else 'β'}") |
| st.progress(min(reports_percent, 100) / 100) |
| |
| with col3: |
| max_searches = plan["max_searches_per_day"] |
| searches_used = 8 |
| searches_percent = (searches_used / max_searches * 100) if max_searches > 0 else 0 |
| st.metric("Daily Searches", f"{searches_used}/{max_searches if max_searches > 0 else 'β'}") |
| st.progress(min(searches_percent, 100) / 100) |
| |
| with col4: |
| max_keywords = plan["max_monitoring_keywords"] |
| keywords_used = 4 |
| keywords_percent = (keywords_used / max_keywords * 100) if max_keywords > 0 else 0 |
| st.metric("Monitoring Keywords", f"{keywords_used}/{max_keywords if max_keywords > 0 else 'β'}") |
| st.progress(min(keywords_percent, 100) / 100) |
| |
| |
| st.markdown("### Features") |
| |
| features = [] |
| |
| if plan["supports_api_access"]: |
| features.append("β API Access") |
| else: |
| features.append("β API Access") |
| |
| if plan["supports_live_feed"]: |
| features.append("β Live Feed") |
| else: |
| features.append("β Live Feed") |
| |
| if plan["supports_dark_web_monitoring"]: |
| features.append("β Dark Web Monitoring") |
| else: |
| features.append("β Dark Web Monitoring") |
| |
| if plan["supports_export"]: |
| features.append("β Data Export") |
| else: |
| features.append("β Data Export") |
| |
| if plan["supports_advanced_analytics"]: |
| features.append("β Advanced Analytics") |
| else: |
| features.append("β Advanced Analytics") |
| |
| |
| cols = st.columns(len(features)) |
| for i, feature in enumerate(features): |
| with cols[i]: |
| if feature.startswith("β"): |
| st.markdown(f'<div style="text-align: center; color: #2ecc71; font-weight: bold;">{feature}</div>', unsafe_allow_html=True) |
| else: |
| st.markdown(f'<div style="text-align: center; color: #e74c3c; font-weight: bold;">{feature}</div>', unsafe_allow_html=True) |
|
|
|
|
| def render_pricing_table(): |
| """Render a pricing table with all subscription plans.""" |
| st.markdown("## Subscription Plans") |
| |
| |
| selected_period = st.radio( |
| "Billing Period", |
| ["monthly", "annually"], |
| format_func=lambda x: x.capitalize(), |
| horizontal=True |
| ) |
| |
| |
| if selected_period == "annually": |
| st.info("Save up to 20% with annual billing") |
| |
| |
| plans_df = get_subscription_plans_df() |
| |
| if plans_df.empty: |
| st.warning("No subscription plans available.") |
| return |
| |
| |
| plans = plans_df.to_dict("records") |
| |
| |
| cols = st.columns(len(plans)) |
| |
| |
| for i, plan in enumerate(plans): |
| with cols[i]: |
| render_pricing_card(plan, selected_period) |
| |
| |
| if st.button("Back to Dashboard", key="close_pricing"): |
| st.session_state.show_pricing_table = False |
| st.rerun() |
|
|
|
|
| def render_subscriptions(): |
| """ |
| Main function to render the subscription management component. |
| """ |
| colored_header( |
| label="Subscription Management", |
| description="Manage your subscription and billing", |
| color_name="violet-70" |
| ) |
| |
| |
| initialize_default_plans() |
| |
| |
| if "show_pricing_table" not in st.session_state: |
| st.session_state.show_pricing_table = False |
| |
| if "show_payment_form" not in st.session_state: |
| st.session_state.show_payment_form = False |
| |
| if "selected_plan" not in st.session_state: |
| st.session_state.selected_plan = None |
| |
| if "selected_billing_period" not in st.session_state: |
| st.session_state.selected_billing_period = "monthly" |
| |
| if "current_user_subscription" not in st.session_state: |
| st.session_state.current_user_subscription = None |
| |
| |
| if st.session_state.show_payment_form: |
| render_payment_form() |
| return |
| |
| |
| if st.session_state.show_pricing_table: |
| render_pricing_table() |
| return |
| |
| |
| render_subscription_dashboard() |
| |
| |
| render_subscription_metrics() |