From 83aaca072137c00a564dffdbe9a82e38b3c18891 Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Sat, 13 Dec 2025 16:29:06 +0100 Subject: [PATCH] fix: Stripe USD prices + tier limits alignment --- backend/app/services/stripe_service.py | 30 ++++++++++++++-------- frontend/src/app/pricing/page.tsx | 16 ++++++------ frontend/src/app/terminal/listing/page.tsx | 4 +-- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/backend/app/services/stripe_service.py b/backend/app/services/stripe_service.py index 762f730..53cfecb 100644 --- a/backend/app/services/stripe_service.py +++ b/backend/app/services/stripe_service.py @@ -5,8 +5,8 @@ Handles subscription payments for pounce.ch Subscription Tiers: - Scout (Free): $0/month -- Trader: €19/month (or ~$21) -- Tycoon: €49/month (or ~$54) +- Trader: $9/month +- Tycoon: $29/month Environment Variables Required: - STRIPE_SECRET_KEY: Stripe API secret key @@ -24,7 +24,7 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.user import User -from app.models.subscription import Subscription +from app.models.subscription import Subscription, SubscriptionTier, SubscriptionStatus logger = logging.getLogger(__name__) @@ -258,7 +258,7 @@ class StripeService: async def _handle_checkout_complete(data: Dict, db: AsyncSession): """Handle successful checkout - activate subscription.""" user_id = data.get("metadata", {}).get("user_id") - plan = data.get("metadata", {}).get("plan") + plan = data.get("metadata", {}).get("plan") # "trader" or "tycoon" customer_id = data.get("customer") subscription_id = data.get("subscription") @@ -266,6 +266,14 @@ class StripeService: logger.error("Missing user_id or plan in checkout metadata") return + # Convert plan string to SubscriptionTier enum + tier_map = { + "trader": SubscriptionTier.TRADER, + "tycoon": SubscriptionTier.TYCOON, + "scout": SubscriptionTier.SCOUT, + } + tier_enum = tier_map.get(plan.lower(), SubscriptionTier.SCOUT) + # Get user result = await db.execute( select(User).where(User.id == int(user_id)) @@ -285,11 +293,11 @@ class StripeService: ) subscription = sub_result.scalar_one_or_none() - tier_info = TIER_FEATURES.get(plan, TIER_FEATURES["scout"]) + tier_info = TIER_FEATURES.get(plan.lower(), TIER_FEATURES["scout"]) if subscription: - subscription.tier = plan - subscription.is_active = True + subscription.tier = tier_enum # Use enum, not string + subscription.status = SubscriptionStatus.ACTIVE subscription.stripe_subscription_id = subscription_id subscription.max_domains = tier_info["max_domains"] subscription.check_frequency = tier_info["check_frequency"] @@ -297,8 +305,8 @@ class StripeService: else: subscription = Subscription( user_id=user.id, - tier=plan, - is_active=True, + tier=tier_enum, # Use enum, not string + status=SubscriptionStatus.ACTIVE, stripe_subscription_id=subscription_id, max_domains=tier_info["max_domains"], check_frequency=tier_info["check_frequency"], @@ -340,8 +348,8 @@ class StripeService: subscription = result.scalar_one_or_none() if subscription: - subscription.tier = "scout" - subscription.is_active = True # Scout is still active + subscription.tier = SubscriptionTier.SCOUT # Use enum + subscription.status = SubscriptionStatus.ACTIVE # Scout is still active subscription.stripe_subscription_id = None subscription.max_domains = TIER_FEATURES["scout"]["max_domains"] subscription.check_frequency = TIER_FEATURES["scout"]["check_frequency"] diff --git a/frontend/src/app/pricing/page.tsx b/frontend/src/app/pricing/page.tsx index ca278a8..25a1f40 100644 --- a/frontend/src/app/pricing/page.tsx +++ b/frontend/src/app/pricing/page.tsx @@ -25,7 +25,7 @@ const tiers = [ { text: 'Market Feed', highlight: false, available: true, sublabel: '🌪️ Raw' }, { text: 'Alert Speed', highlight: false, available: true, sublabel: '🐢 Daily' }, { text: 'Pounce Score', highlight: false, available: false }, - { text: 'Sniper Alerts', highlight: false, available: false }, + { text: '2 Sniper Alerts', highlight: false, available: true }, ], cta: 'Start Free', highlighted: false, @@ -45,8 +45,8 @@ const tiers = [ { text: 'Alert Speed', highlight: true, available: true, sublabel: '🐇 Hourly' }, { text: 'Renewal Price Intel', highlight: true, available: true }, { text: 'Pounce Score', highlight: true, available: true }, - { text: 'List domains', highlight: true, available: true, sublabel: '0% Fee' }, - { text: '5 Sniper Alerts', highlight: true, available: true }, + { text: '5 Listings', highlight: true, available: true, sublabel: '0% Fee' }, + { text: '10 Sniper Alerts', highlight: true, available: true }, { text: 'Portfolio (25 domains)', highlight: true, available: true }, ], cta: 'Upgrade to Trader', @@ -65,10 +65,10 @@ const tiers = [ { text: '500 Watchlist Domains', highlight: true, available: true }, { text: 'Market Feed', highlight: true, available: true, sublabel: '⚡ Priority' }, { text: 'Alert Speed', highlight: true, available: true, sublabel: '⚡ 10 min' }, - { text: 'Full Portfolio Monitor', highlight: true, available: true }, + { text: 'Unlimited Portfolio', highlight: true, available: true }, { text: 'Score + SEO Data', highlight: true, available: true }, - { text: 'Featured Listings', highlight: true, available: true, sublabel: '50 slots' }, - { text: 'Unlimited Sniper Alerts', highlight: true, available: true }, + { text: '50 Listings', highlight: true, available: true, sublabel: 'Featured' }, + { text: '50 Sniper Alerts', highlight: true, available: true }, { text: 'Full Price History', highlight: true, available: true }, ], cta: 'Go Tycoon', @@ -82,10 +82,10 @@ const comparisonFeatures = [ { name: 'Market Feed', scout: 'Raw', trader: 'Curated', tycoon: 'Priority' }, { name: 'Alert Speed', scout: 'Daily', trader: 'Hourly', tycoon: '10 min' }, { name: 'Watchlist', scout: '5 Domains', trader: '50 Domains', tycoon: '500 Domains' }, - { name: 'Marketplace', scout: 'Buy Only', trader: 'Sell (0% Fee)', tycoon: 'Sell + Featured' }, + { name: 'Listings', scout: '—', trader: '5 (0% Fee)', tycoon: '50 Featured' }, { name: 'TLD Intel', scout: 'Public Trends', trader: 'Renewal Prices', tycoon: 'Full History' }, { name: 'Valuation', scout: 'Locked', trader: 'Pounce Score', tycoon: 'Score + SEO' }, - { name: 'Sniper Alerts', scout: '—', trader: '5', tycoon: 'Unlimited' }, + { name: 'Sniper Alerts', scout: '2', trader: '10', tycoon: '50' }, { name: 'Portfolio', scout: '—', trader: '25 Domains', tycoon: 'Unlimited' }, ] diff --git a/frontend/src/app/terminal/listing/page.tsx b/frontend/src/app/terminal/listing/page.tsx index d1bd1b7..3ea32f1 100755 --- a/frontend/src/app/terminal/listing/page.tsx +++ b/frontend/src/app/terminal/listing/page.tsx @@ -54,8 +54,8 @@ export default function MyListingsPage() { const [menuOpen, setMenuOpen] = useState(false) const tier = subscription?.tier || 'scout' - const listingLimits: Record = { scout: 3, trader: 25, tycoon: 100 } - const maxListings = listingLimits[tier] || 3 + const listingLimits: Record = { scout: 0, trader: 5, tycoon: 50 } + const maxListings = listingLimits[tier] || 0 const canAddMore = listings.length < maxListings const isTycoon = tier === 'tycoon'