feat: visual polish, nav login link, pricing badge fix, cursor fix, button contrast
- Hero mockup: enhanced 3D perspective and shadow - Testimonials: illustrated SVG avatars - Growth pricing card: visual prominence (scale, gradient, badge) - Most Popular badge: repositioned to avoid overlapping heading - Nav: added Log In link next to Start Free Trial - Fixed btn-primary text colour on anchor tags (white on blue) - Fixed cursor: default on all non-interactive elements - Disabled user-select on non-form content to prevent text caret
This commit is contained in:
263
STRIPE_SETUP.md
Normal file
263
STRIPE_SETUP.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# TenderRadar Stripe Payment Integration
|
||||
|
||||
This document describes the Stripe payment integration for TenderRadar, including setup instructions and API endpoints.
|
||||
|
||||
## Overview
|
||||
|
||||
TenderRadar now supports three paid subscription tiers via Stripe:
|
||||
- **Starter**: £39/month
|
||||
- **Growth**: £99/month
|
||||
- **Pro**: £249/month
|
||||
|
||||
All plans include a 14-day free trial.
|
||||
|
||||
## Database Schema
|
||||
|
||||
A new `subscriptions` table has been added to track user subscription status:
|
||||
|
||||
```sql
|
||||
CREATE TABLE subscriptions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER UNIQUE REFERENCES users(id) ON DELETE CASCADE,
|
||||
stripe_customer_id VARCHAR(255) UNIQUE NOT NULL,
|
||||
stripe_subscription_id VARCHAR(255),
|
||||
plan VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
||||
trial_start TIMESTAMP,
|
||||
trial_end TIMESTAMP,
|
||||
current_period_start TIMESTAMP,
|
||||
current_period_end TIMESTAMP,
|
||||
cancel_at_period_end BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- `user_id`: Reference to the user account
|
||||
- `stripe_customer_id`: Stripe customer ID for this user
|
||||
- `stripe_subscription_id`: Active Stripe subscription ID
|
||||
- `plan`: Current plan tier (starter, growth, pro)
|
||||
- `status`: Subscription status (active, trialing, past_due, cancelled)
|
||||
- `trial_start/end`: Trial period dates
|
||||
- `current_period_start/end`: Current billing period dates
|
||||
- `cancel_at_period_end`: Whether subscription is scheduled for cancellation
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Add the following to `.env`:
|
||||
|
||||
```env
|
||||
# Stripe API Keys (from Stripe Dashboard)
|
||||
STRIPE_SECRET_KEY=sk_test_placeholder
|
||||
STRIPE_PUBLISHABLE_KEY=pk_test_placeholder
|
||||
STRIPE_WEBHOOK_SECRET=whsec_placeholder
|
||||
|
||||
# Stripe Price IDs (created in Stripe Dashboard)
|
||||
STRIPE_PRICE_STARTER=price_starter_placeholder
|
||||
STRIPE_PRICE_GROWTH=price_growth_placeholder
|
||||
STRIPE_PRICE_PRO=price_pro_placeholder
|
||||
```
|
||||
|
||||
**Peter: Update these placeholder values with your actual Stripe keys.**
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Create Stripe Account and Get API Keys
|
||||
|
||||
1. Sign up at https://dashboard.stripe.com
|
||||
2. Navigate to Developers → API Keys
|
||||
3. Copy your **Secret Key** (starts with `sk_test_` or `sk_live_`)
|
||||
4. Copy your **Publishable Key** (starts with `pk_test_` or `pk_live_`)
|
||||
5. Update `.env` with these keys
|
||||
|
||||
### 2. Create Webhook Endpoint
|
||||
|
||||
1. In Stripe Dashboard, go to Developers → Webhooks
|
||||
2. Click "Add an endpoint"
|
||||
3. Endpoint URL: `https://your-domain.com/api/billing/webhook`
|
||||
4. Select events to listen for:
|
||||
- `checkout.session.completed`
|
||||
- `customer.subscription.updated`
|
||||
- `customer.subscription.deleted`
|
||||
- `invoice.payment_failed`
|
||||
5. Copy the **Signing Secret** (starts with `whsec_`)
|
||||
6. Update `.env` with `STRIPE_WEBHOOK_SECRET`
|
||||
|
||||
### 3. Create Stripe Price Objects
|
||||
|
||||
In Stripe Dashboard, go to Products → Create Product:
|
||||
|
||||
#### Starter Plan
|
||||
- Name: "TenderRadar Starter"
|
||||
- Price: £39.00 GBP / month
|
||||
- Recurring: Monthly
|
||||
- Copy the Price ID (starts with `price_`) → `STRIPE_PRICE_STARTER`
|
||||
|
||||
#### Growth Plan
|
||||
- Name: "TenderRadar Growth"
|
||||
- Price: £99.00 GBP / month
|
||||
- Recurring: Monthly
|
||||
- Copy the Price ID → `STRIPE_PRICE_GROWTH`
|
||||
|
||||
#### Pro Plan
|
||||
- Name: "TenderRadar Pro"
|
||||
- Price: £249.00 GBP / month
|
||||
- Recurring: Monthly
|
||||
- Copy the Price ID → `STRIPE_PRICE_PRO`
|
||||
|
||||
Update `.env` with all three Price IDs.
|
||||
|
||||
### 4. Initialize Database
|
||||
|
||||
If this is a fresh setup, run:
|
||||
```bash
|
||||
node init-db.js
|
||||
```
|
||||
|
||||
This will create the `subscriptions` table and indexes.
|
||||
|
||||
### 5. Restart Server
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### POST /api/billing/checkout
|
||||
Creates a Stripe Checkout session for a selected plan.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"plan": "starter|growth|pro",
|
||||
"successUrl": "https://app.example.com/success",
|
||||
"cancelUrl": "https://app.example.com/cancel"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"sessionId": "cs_test_...",
|
||||
"url": "https://checkout.stripe.com/pay/..."
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
1. Call this endpoint with the desired plan
|
||||
2. Redirect user to the returned `url`
|
||||
3. User completes payment on Stripe Checkout
|
||||
4. Stripe redirects to `successUrl`
|
||||
|
||||
### POST /api/billing/webhook
|
||||
Handles incoming Stripe webhook events. This endpoint is automatically called by Stripe.
|
||||
|
||||
**Handled Events:**
|
||||
- `checkout.session.completed` - Creates subscription record when user completes checkout
|
||||
- `customer.subscription.updated` - Updates subscription status in database
|
||||
- `customer.subscription.deleted` - Marks subscription as cancelled
|
||||
- `invoice.payment_failed` - Logs failed payment (can trigger alerts)
|
||||
|
||||
### GET /api/billing/subscription
|
||||
Retrieves the current subscription status for the authenticated user.
|
||||
|
||||
**Response (with active subscription):**
|
||||
```json
|
||||
{
|
||||
"subscription": {
|
||||
"id": 1,
|
||||
"user_id": 42,
|
||||
"stripe_customer_id": "cus_...",
|
||||
"stripe_subscription_id": "sub_...",
|
||||
"plan": "growth",
|
||||
"status": "active",
|
||||
"trial_start": "2026-02-14T12:00:00Z",
|
||||
"trial_end": "2026-02-28T12:00:00Z",
|
||||
"current_period_start": "2026-03-14T12:00:00Z",
|
||||
"current_period_end": "2026-04-14T12:00:00Z",
|
||||
"cancel_at_period_end": false,
|
||||
"created_at": "2026-02-14T12:00:00Z",
|
||||
"updated_at": "2026-02-14T12:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response (no subscription):**
|
||||
```json
|
||||
{
|
||||
"subscription": null,
|
||||
"message": "No active subscription. User is on free tier."
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/billing/portal
|
||||
Creates a Stripe Customer Portal session for managing subscriptions (upgrade, downgrade, cancel).
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"returnUrl": "https://app.example.com/billing"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"url": "https://billing.stripe.com/session/..."
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
### attachSubscription(pool)
|
||||
Automatically attaches subscription info to `req.subscription` for all authenticated requests. Place after `verifyToken` middleware.
|
||||
|
||||
### requireActiveSubscription
|
||||
Middleware to restrict endpoints to users with active subscriptions. Use for premium features:
|
||||
|
||||
```javascript
|
||||
app.get('/api/premium-feature', verifyToken, requireActiveSubscription, (req, res) => {
|
||||
// This endpoint now requires active subscription
|
||||
});
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- **Stripe Checkout** is used for PCI compliance (no sensitive payment data handled by TenderRadar)
|
||||
- **14-day trial** is automatically applied to all subscriptions via checkout session config
|
||||
- **Webhook validation** ensures events are authentic before processing
|
||||
- **Subscription metadata** includes `user_id` and `plan` for easy lookup
|
||||
- **Raw body parsing** is configured for the webhook endpoint to verify signatures
|
||||
- **Plan mapping** converts plan names to Stripe Price IDs in `stripe-billing.js`
|
||||
|
||||
## Testing Webhook Locally
|
||||
|
||||
For local development, use Stripe CLI:
|
||||
|
||||
```bash
|
||||
# Install Stripe CLI: https://stripe.com/docs/stripe-cli
|
||||
stripe login
|
||||
stripe listen --forward-to localhost:3456/api/billing/webhook
|
||||
```
|
||||
|
||||
This outputs a webhook signing secret — update `.env` with this value for testing.
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
├── server.js # Main Express app with billing routes
|
||||
├── stripe-billing.js # Stripe integration functions
|
||||
├── subscription-middleware.js # Middleware for subscription checks
|
||||
├── init-db.js # Database setup (includes subscriptions table)
|
||||
├── .env # Configuration (update with Stripe keys)
|
||||
└── STRIPE_SETUP.md # This file
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
For questions about Stripe integration, consult:
|
||||
- Stripe API Docs: https://stripe.com/docs/api
|
||||
- Stripe Webhooks: https://stripe.com/docs/webhooks
|
||||
- Stripe Checkout: https://stripe.com/docs/payments/checkout
|
||||
Reference in New Issue
Block a user