264 lines
7.4 KiB
Markdown
264 lines
7.4 KiB
Markdown
|
|
# 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
|