fix: Stripe USD prices + tier limits alignment
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled

This commit is contained in:
2025-12-13 16:29:06 +01:00
parent f4e4595cde
commit 83aaca0721
3 changed files with 29 additions and 21 deletions

View File

@ -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"]

View File

@ -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' },
]

View File

@ -54,8 +54,8 @@ export default function MyListingsPage() {
const [menuOpen, setMenuOpen] = useState(false)
const tier = subscription?.tier || 'scout'
const listingLimits: Record<string, number> = { scout: 3, trader: 25, tycoon: 100 }
const maxListings = listingLimits[tier] || 3
const listingLimits: Record<string, number> = { scout: 0, trader: 5, tycoon: 50 }
const maxListings = listingLimits[tier] || 0
const canAddMore = listings.length < maxListings
const isTycoon = tier === 'tycoon'