9.7 KiB
CRITICAL GAP: Pricing Page to Paid Plans Signup
Issue Not Covered in Previous Documentation
Discovered: Marketing pricing page analysis
Severity: HIGH - Payment flow is broken
Problem Identified
Current State (Broken)
Pricing Page: frontend/src/marketing/pages/Pricing.tsx:307-316
ALL plan cards (Starter $89, Growth $139, Scale $229) have identical buttons:
<a href="https://app.igny8.com/signup">
Start free trial
</a>
This means:
- ❌ User clicks "Start free trial" on Growth ($139/month)
- ❌ Goes to https://app.igny8.com/signup
- ❌ Gets FREE TRIAL with free-trial plan (0 payment)
- ❌ NO WAY to actually sign up for paid plans from pricing page
What's Missing
There is NO paid plan signup flow at all.
Required Solution
Option A: Query Parameter Routing (RECOMMENDED)
Pricing page buttons:
// Starter
<a href="https://app.igny8.com/signup?plan=starter">
Get Started - $89/mo
</a>
// Growth
<a href="https://app.igny8.com/signup?plan=growth">
Get Started - $139/mo
</a>
// Scale
<a href="https://app.igny8.com/signup?plan=scale">
Get Started - $229/mo
</a>
// Free trial stays same
<a href="https://app.igny8.com/signup">
Start Free Trial
</a>
App signup page logic:
// In SignUpForm.tsx
const searchParams = new URLSearchParams(window.location.search);
const planSlug = searchParams.get('plan');
if (planSlug) {
// Paid plan signup - show payment form
navigate('/payment', { state: { planSlug } });
} else {
// Free trial - current simple form
// Continue with free trial registration
}
Backend:
# RegisterSerializer checks plan query/body
plan_slug = request.data.get('plan_slug') or request.GET.get('plan')
if plan_slug in ['starter', 'growth', 'scale']:
# Paid plan - requires payment
plan = Plan.objects.get(slug=plan_slug)
account.status = 'pending_payment'
# Create Subscription with status='pending_payment'
# Wait for payment confirmation
else:
# Free trial
plan = Plan.objects.get(slug='free-trial')
account.status = 'trial'
# Immediate access
Option B: Separate Payment Route
Pricing page:
// Paid plans go to /payment
<a href="https://app.igny8.com/payment?plan=starter">
Get Started - $89/mo
</a>
// Free trial stays /signup
<a href="https://app.igny8.com/signup">
Start Free Trial
</a>
Create new route:
/signup- Free trial only (current implementation)/payment- Paid plans with payment form
Implementation Required
1. Update Pricing Page CTAs
File: frontend/src/marketing/pages/Pricing.tsx:307
Add plan data to tiers:
const tiers = [
{
name: "Starter",
slug: "starter", // NEW
price: "$89",
// ... rest
},
// ...
];
Update CTA button logic:
<a
href={`https://app.igny8.com/signup?plan=${tier.slug}`}
className={...}
>
{tier.price === "Free" ? "Start free trial" : `Get ${tier.name} - ${tier.price}/mo`}
</a>
2. Create Payment Flow Page
File: frontend/src/pages/Payment.tsx (NEW)
import { useLocation, useNavigate } from 'react-router-dom';
import { useState, useEffect } from 'react';
export default function Payment() {
const location = useLocation();
const navigate = useNavigate();
const [selectedPlan, setSelectedPlan] = useState(null);
useEffect(() => {
const params = new URLSearchParams(location.search);
const planSlug = params.get('plan');
if (!planSlug) {
// No plan selected, redirect to pricing
navigate('/pricing');
return;
}
// Load plan details from API
fetch(`/api/v1/auth/plans/?slug=${planSlug}`)
.then(res => res.json())
.then(data => setSelectedPlan(data.results[0]));
}, [location]);
return (
<div>
<h1>Complete Your Subscription</h1>
{selectedPlan && (
<>
<h2>{selectedPlan.name} - ${selectedPlan.price}/{selectedPlan.billing_cycle}</h2>
{/* Payment method selection */}
<div>
<h3>Select Payment Method</h3>
<button>Credit Card (Stripe) - Coming Soon</button>
<button>PayPal - Coming Soon</button>
<button>Bank Transfer</button>
</div>
{/* If bank transfer selected, show form */}
<form onSubmit={handleBankTransferSubmit}>
<input name="email" placeholder="Your email" required />
<input name="account_name" placeholder="Account name" required />
<button>Submit - We'll send payment details</button>
</form>
</>
)}
</div>
);
}
3. Update Backend Registration
File: backend/igny8_core/auth/serializers.py:276
Add plan_slug handling:
def create(self, validated_data):
from django.db import transaction
from igny8_core.business.billing.models import CreditTransaction
with transaction.atomic():
# Check for plan_slug in request
plan_slug = validated_data.get('plan_slug')
if plan_slug in ['starter', 'growth', 'scale']:
# PAID PLAN - requires payment
plan = Plan.objects.get(slug=plan_slug, is_active=True)
account_status = 'pending_payment'
initial_credits = 0 # No credits until payment
# Do NOT create CreditTransaction yet
else:
# FREE TRIAL - immediate access
try:
plan = Plan.objects.get(slug='free-trial', is_active=True)
except Plan.DoesNotExist:
plan = Plan.objects.get(slug='free', is_active=True)
account_status = 'trial'
initial_credits = plan.get_effective_credits_per_month()
# ... create user and account ...
account = Account.objects.create(
name=account_name,
slug=slug,
owner=user,
plan=plan,
credits=initial_credits,
status=account_status
)
# Only log credits for trial (paid accounts get credits after payment)
if account_status == 'trial' and initial_credits > 0:
CreditTransaction.objects.create(
account=account,
transaction_type='subscription',
amount=initial_credits,
balance_after=initial_credits,
description=f'Free trial credits from {plan.name}',
metadata={'registration': True, 'trial': True}
)
# ... rest of code ...
Current Pricing Page Button Behavior
All buttons currently do this:
igny8.com/pricing
├─ Starter card → "Start free trial" → https://app.igny8.com/signup
├─ Growth card → "Start free trial" → https://app.igny8.com/signup
└─ Scale card → "Start free trial" → https://app.igny8.com/signup
Result: NO WAY to sign up for paid plans.
Recommended Implementation
Marketing Site (igny8.com)
// Pricing.tsx - Update tier CTAs
{tier.price === "Free" ? (
<a href="https://app.igny8.com/signup">
Start Free Trial
</a>
) : (
<a href={`https://app.igny8.com/signup?plan=${tier.slug}`}>
Get {tier.name} - {tier.price}/mo
</a>
)}
App Site (app.igny8.com)
// SignUpForm.tsx - Check for plan parameter
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const planSlug = params.get('plan');
if (planSlug && ['starter', 'growth', 'scale'].includes(planSlug)) {
// Redirect to payment page
navigate(`/payment?plan=${planSlug}`);
}
// Otherwise continue with free trial signup
}, []);
Payment Page (NEW)
- Route:
/payment?plan=starter - Shows: Plan details, payment method selection
- Options: Bank Transfer (active), Stripe (coming soon), PayPal (coming soon)
- Flow: Collect info → Create pending account → Send payment instructions
Update to Requirements
Add to FINAL-IMPLEMENTATION-REQUIREMENTS.md
New Section: E. Paid Plans Signup Flow
### CRITICAL ISSUE E: No Paid Plan Signup Path
#### Problem
Marketing pricing page shows paid plans ($89, $139, $229) but all buttons go to free trial signup.
No way for users to actually subscribe to paid plans.
#### Fix
1. Pricing page buttons must differentiate:
- Free trial: /signup (no params)
- Paid plans: /signup?plan=starter (with plan slug)
2. Signup page must detect plan parameter:
- If plan=paid → Redirect to /payment
- If no plan → Free trial signup
3. Create /payment page:
- Show selected plan details
- Payment method selection (bank transfer active, others coming soon)
- Collect user info + payment details
- Create account with status='pending_payment'
- Send payment instructions
4. Backend must differentiate:
- Free trial: immediate credits and access
- Paid plans: 0 credits, pending_payment status, wait for confirmation
Files That Need Updates
Frontend
frontend/src/marketing/pages/Pricing.tsx:307- Add plan slug to CTAsfrontend/src/components/auth/SignUpForm.tsx- Detect plan param, redirect to paymentfrontend/src/pages/Payment.tsx- NEW FILE - Payment flow pagefrontend/src/App.tsx- Add /payment route
Backend
backend/igny8_core/auth/serializers.py:276- Handle plan_slug for paid plansbackend/igny8_core/auth/views.py:978- Expose plan_slug in RegisterSerializer
This Was Missing From All Previous Documentation
✅ Free trial flow - COVERED
❌ Paid plan subscription flow - NOT COVERED
This is a critical gap that needs to be added to the implementation plan.