Complete the public -> platform naming migration across the codebase. This aligns with the naming convention where "platform" refers to the marketing/public-facing pages of the platform itself. Changes: - Update all imports from public to platform modules - Update template references from public/ to platform/ - Update route registrations to use platform prefix - Update documentation to reflect new naming - Update test files for platform API endpoints Files affected: - app/api/main.py - router imports - app/modules/*/routes/*/platform.py - route definitions - app/modules/*/templates/*/platform/ - template files - app/modules/routes.py - route discovery - docs/* - documentation updates - tests/integration/api/v1/platform/ - test files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
15 KiB
Platform Marketing Homepage
Overview
The Wizamart marketing homepage serves as the main public entry point for Letzshop vendors looking to use the order management platform. It provides a complete self-service signup flow with Stripe payment integration.
Target Audience: Letzshop vendors in Luxembourg seeking order management solutions.
Key Value Proposition: "Lightweight OMS for Letzshop Sellers" - Order management, inventory, and invoicing built for Luxembourg e-commerce.
Features Summary
| Feature | URL | Description |
|---|---|---|
| Marketing Homepage | / |
Hero, pricing, add-ons, vendor finder |
| Pricing Page | /pricing |
Detailed tier comparison |
| Find Your Shop | /find-shop |
Letzshop vendor lookup |
| Signup Wizard | /signup |
4-step registration flow |
| Signup Success | /signup/success |
Welcome & next steps |
Pricing Tiers
Based on docs/marketing/pricing.md:
| Tier | Monthly | Annual | Orders/mo | Products | Users | Key Features |
|---|---|---|---|---|---|---|
| Essential | €49 | €490 | 100 | 200 | 1 | LU invoicing, basic inventory |
| Professional | €99 | €990 | 500 | Unlimited | 3 | EU VAT, warehouse locations |
| Business | €199 | €1,990 | 2,000 | Unlimited | 10 | Analytics, API, automation |
| Enterprise | €399+ | Custom | Unlimited | Unlimited | Unlimited | White-label, SLA, dedicated support |
Annual Discount: 2 months free (17% savings)
Trial Period: 30 days with card collection upfront (no charge until trial ends)
Add-On Products
| Add-On | Price | Billing | Description |
|---|---|---|---|
| Custom Domain | €15 | Annual | Use your own domain (mydomain.com) |
| Premium SSL | €49 | Annual | EV certificate for trust badges |
| Email Package (5) | €5 | Monthly | 5 professional email addresses |
| Email Package (10) | €9 | Monthly | 10 professional email addresses |
| Email Package (25) | €19 | Monthly | 25 professional email addresses |
Page Descriptions
1. Marketing Homepage (/)
Template: app/templates/public/homepage-wizamart.html
Sections:
-
Hero Section
- Headline: "Lightweight OMS for Letzshop Sellers"
- Subheadline: Order management, inventory, and invoicing
- CTAs: "Start Free Trial" and "Find Your Letzshop Shop"
- Badge: "30-Day Free Trial - No Credit Card Required to Start"
-
Pricing Section
- 4 tier cards (Essential, Professional, Business, Enterprise)
- Monthly/Annual toggle with savings indicator
- Feature lists per tier
- "Start Free Trial" buttons linked to signup
-
Add-Ons Section
- 3 add-on cards (Domain, SSL, Email)
- Icon, description, and pricing for each
-
Letzshop Vendor Finder
- Search input for shop URL
- Real-time lookup via API
- "Claim This Shop" button for unclaimed vendors
-
Final CTA Section
- Gradient background
- Strong call to action for trial signup
2. Pricing Page (/pricing)
Template: app/templates/public/pricing.html
Standalone page with:
- Large tier cards
- Monthly/Annual toggle
- Detailed feature lists
- Back to home link
3. Find Your Shop (/find-shop)
Template: app/templates/public/find-shop.html
- URL input with examples
- Real-time Letzshop vendor lookup
- Claim button for unclaimed shops
- Help section with alternatives
4. Signup Wizard (/signup)
Template: app/templates/public/signup.html
4-Step Flow:
| Step | Name | Description |
|---|---|---|
| 1 | Select Plan | Choose tier + billing period |
| 2 | Claim Shop | Optional Letzshop connection |
| 3 | Create Account | User details + company info |
| 4 | Payment | Stripe card collection |
URL Parameters:
?tier=professional- Pre-select tier?annual=true- Pre-select annual billing?letzshop=my-shop- Pre-fill Letzshop slug
5. Signup Success (/signup/success)
Template: app/templates/public/signup-success.html
- Success confirmation
- Next steps checklist
- Dashboard link
- Support contact
API Endpoints
All endpoints under /api/v1/platform/:
Pricing Endpoints
GET /api/v1/platform/tiers
Returns all public subscription tiers
Response: TierResponse[]
GET /api/v1/platform/tiers/{tier_code}
Returns specific tier by code
Response: TierResponse
GET /api/v1/platform/addons
Returns all active add-on products
Response: AddOnResponse[]
GET /api/v1/platform/pricing
Returns complete pricing info (tiers + addons + trial_days)
Response: PricingResponse
Letzshop Vendor Endpoints
GET /api/v1/platform/letzshop-vendors
Query params: ?search=&category=&city=&page=1&limit=20
Returns paginated vendor list (placeholder for future)
Response: LetzshopVendorListResponse
POST /api/v1/platform/letzshop-vendors/lookup
Body: { "url": "letzshop.lu/vendors/my-shop" }
Returns vendor info from URL lookup
Response: LetzshopLookupResponse
GET /api/v1/platform/letzshop-vendors/{slug}
Returns vendor info by slug
Response: LetzshopVendorInfo
Signup Endpoints
POST /api/v1/platform/signup/start
Body: { "tier_code": "professional", "is_annual": false }
Creates signup session
Response: { "session_id": "...", "tier_code": "...", "is_annual": false }
POST /api/v1/platform/signup/claim-vendor
Body: { "session_id": "...", "letzshop_slug": "my-shop" }
Claims Letzshop vendor for session
Response: { "session_id": "...", "letzshop_slug": "...", "vendor_name": "..." }
POST /api/v1/platform/signup/create-account
Body: {
"session_id": "...",
"email": "user@example.com",
"password": "securepassword",
"first_name": "John",
"last_name": "Doe",
"company_name": "My Company"
}
Creates User, Company, Vendor, Stripe Customer
Response: { "session_id": "...", "user_id": 1, "vendor_id": 1, "stripe_customer_id": "cus_..." }
POST /api/v1/platform/signup/setup-payment
Body: { "session_id": "..." }
Creates Stripe SetupIntent
Response: { "session_id": "...", "client_secret": "seti_...", "stripe_customer_id": "cus_..." }
POST /api/v1/platform/signup/complete
Body: { "session_id": "...", "setup_intent_id": "seti_..." }
Completes signup, attaches payment method
Response: { "success": true, "vendor_code": "...", "vendor_id": 1, "redirect_url": "...", "trial_ends_at": "..." }
GET /api/v1/platform/signup/session/{session_id}
Returns session status for resuming signup
Response: { "session_id": "...", "step": "...", ... }
Database Schema Changes
Migration: 404b3e2d2865_add_letzshop_vendor_fields_and_trial_tracking
Vendor Table:
ALTER TABLE vendors ADD COLUMN letzshop_vendor_id VARCHAR(100) UNIQUE;
ALTER TABLE vendors ADD COLUMN letzshop_vendor_slug VARCHAR(200);
CREATE INDEX ix_vendors_letzshop_vendor_id ON vendors(letzshop_vendor_id);
CREATE INDEX ix_vendors_letzshop_vendor_slug ON vendors(letzshop_vendor_slug);
VendorSubscription Table:
ALTER TABLE vendor_subscriptions ADD COLUMN card_collected_at DATETIME;
Model Changes
models/database/vendor.py:
# Letzshop Vendor Identity
letzshop_vendor_id = Column(String(100), unique=True, nullable=True, index=True)
letzshop_vendor_slug = Column(String(200), nullable=True, index=True)
models/database/subscription.py:
# Card collection tracking
card_collected_at = Column(DateTime(timezone=True), nullable=True)
Configuration Change
app/core/config.py:
stripe_trial_days: int = 30 # Changed from 14 to 30
Stripe Integration
Trial Flow with Card Collection
The signup uses Stripe SetupIntent (not PaymentIntent) to collect card details without immediate charge:
1. User selects tier → POST /signup/start
└── Creates signup session
2. User claims Letzshop shop (optional) → POST /signup/claim-vendor
└── Links Letzshop vendor to session
3. User creates account → POST /signup/create-account
├── Creates User in database
├── Creates Company in database
├── Creates Vendor in database
├── Creates Stripe Customer
└── Creates VendorSubscription (status: trial)
4. User enters card → POST /signup/setup-payment
└── Creates Stripe SetupIntent
└── Returns client_secret for frontend
5. Frontend confirms card → stripe.confirmCardSetup(client_secret)
└── Validates card (no charge)
6. Signup completes → POST /signup/complete
├── Retrieves SetupIntent
├── Attaches PaymentMethod to Customer
├── Sets as default payment method
├── Records card_collected_at
└── Subscription starts 30-day trial
7. After 30 days → Stripe automatically charges card
New StripeService Methods
app/services/stripe_service.py:
def create_setup_intent(
self,
customer_id: str,
metadata: dict | None = None,
) -> stripe.SetupIntent:
"""
Create a SetupIntent to collect card without charging.
Used for trial signups where we collect card upfront.
"""
def attach_payment_method_to_customer(
self,
customer_id: str,
payment_method_id: str,
set_as_default: bool = True,
) -> None:
"""
Attach a payment method to customer and set as default.
"""
def create_subscription_with_trial(
self,
customer_id: str,
price_id: str,
trial_days: int = 30,
metadata: dict | None = None,
) -> stripe.Subscription:
"""
Create subscription with trial period.
Card will be charged automatically after trial ends.
"""
def get_setup_intent(self, setup_intent_id: str) -> stripe.SetupIntent:
"""Get a SetupIntent by ID."""
File Structure
app/
├── api/
│ └── v1/
│ └── platform/
│ ├── __init__.py # Router aggregation
│ ├── pricing.py # Tier & addon endpoints
│ ├── letzshop_vendors.py # Vendor lookup endpoints
│ └── signup.py # Signup flow endpoints
├── routes/
│ └── platform_pages.py # Page routes (/, /pricing, etc.)
├── services/
│ └── stripe_service.py # SetupIntent methods (updated)
└── templates/
└── platform/
├── base.html # Base template (Wizamart branding)
├── homepage-wizamart.html # Marketing homepage
├── pricing.html # Pricing page
├── find-shop.html # Letzshop finder
├── signup.html # Signup wizard
└── signup-success.html # Success page
models/database/
├── vendor.py # letzshop_vendor_id, slug fields
└── subscription.py # card_collected_at field
alembic/versions/
└── 404b3e2d2865_add_letzshop_vendor_fields_and_trial_.py
main.py # Platform routes registered
app/api/main.py # Platform API router added
app/core/config.py # stripe_trial_days = 30
Frontend Technology
- Tailwind CSS - Utility-first styling
- Alpine.js - Reactive components
- Stripe.js - Payment form (Stripe Elements)
JavaScript Components (Embedded)
Homepage (homepageData()):
annual- Billing toggle stateshopUrl- Letzshop URL inputvendorResult- Lookup resultlookupVendor()- API call for lookup
Signup Wizard (signupWizard()):
currentStep- Wizard step (1-4)sessionId- Backend session IDselectedTier- Selected tier codeisAnnual- Annual billing toggleletzshopUrl/Vendor- Letzshop claimaccount- User form datastripe/cardElement- Stripe integrationstartSignup()- Step 1 submissionclaimVendor()- Step 2 submissioncreateAccount()- Step 3 submissioninitStripe()- Initialize Stripe ElementssubmitPayment()- Step 4 submission
Configuration Requirements
Environment Variables
# Required for payment step
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_... # For webhook handling
# Trial period (defaults to 30)
STRIPE_TRIAL_DAYS=30
Stripe Dashboard Setup
- Create Products for each tier (Essential, Professional, Business)
- Create Prices for monthly and annual billing
- Configure Customer Portal
- Set up webhook endpoint for subscription events
Testing
Automated Tests
Test files located in tests/integration/api/v1/platform/:
| File | Tests | Description |
|---|---|---|
test_pricing.py |
17 | Tier and add-on pricing endpoints |
test_letzshop_vendors.py |
22 | Vendor lookup and listing endpoints |
test_signup.py |
28 | Multi-step signup flow |
Run tests:
pytest tests/integration/api/v1/platform/ -v
Test categories:
TestPlatformPricingAPI- GET /tiers, /addons, /pricingTestLetzshopVendorLookupAPI- Vendor lookup and claimingTestLetzshopSlugExtraction- URL parsing edge casesTestSignupStartAPI- Signup initiationTestClaimVendorAPI- Letzshop vendor claimingTestCreateAccountAPI- Account creationTestSetupPaymentAPI- Stripe SetupIntentTestCompleteSignupAPI- Signup completionTestSignupFullFlow- End-to-end flow
Manual Testing
- Homepage: Visit
http://localhost:8000/ - Pricing Toggle: Click Monthly/Annual switch
- Vendor Lookup: Enter a Letzshop URL in finder
- Signup Flow:
- Click "Start Free Trial"
- Select tier
- Skip or enter Letzshop URL
- Fill account form
- Enter test card (4242 4242 4242 4242)
- Complete signup
Test Cards (Stripe)
| Card | Scenario |
|---|---|
| 4242 4242 4242 4242 | Success |
| 4000 0000 0000 0002 | Decline |
| 4000 0000 0000 3220 | 3D Secure required |
API Testing
# Get pricing
curl http://localhost:8000/api/v1/platform/pricing
# Lookup vendor
curl -X POST http://localhost:8000/api/v1/platform/letzshop-vendors/lookup \
-H "Content-Type: application/json" \
-d '{"url": "letzshop.lu/vendors/test-shop"}'
# Start signup
curl -X POST http://localhost:8000/api/v1/platform/signup/start \
-H "Content-Type: application/json" \
-d '{"tier_code": "professional", "is_annual": false}'
Future Enhancements
-
Letzshop Vendor Cache
- Periodic sync of Letzshop vendor directory
- Browsable list instead of URL lookup only
-
Email Verification
- Verify email before trial starts
- Confirmation email with onboarding links
-
Referral Program
- Affiliate/referral codes
- Partner commission tracking
-
A/B Testing
- Test different pricing presentations
- Optimize conversion rates
-
Analytics
- Track signup funnel drop-off
- Monitor tier selection patterns
-
Enterprise Contact Form
- Lead capture for enterprise tier
- Sales team notification