Files
tenderpilot/BILLING_API_EXAMPLES.md
Peter Foster f969ecae04 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
2026-02-14 14:17:15 +00:00

6.9 KiB

TenderRadar Billing API Examples

Quick reference for testing billing endpoints. Replace AUTH_TOKEN with a real JWT token from /api/auth/login.

Setup

# 1. Register a user
curl -X POST http://localhost:3456/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "testpass123",
    "company_name": "Test Corp"
  }'

# Response includes: user object and JWT token
# Save the token for subsequent calls:
export AUTH_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

1. Create Checkout Session

Start the subscription flow by creating a checkout session:

curl -X POST http://localhost:3456/api/billing/checkout \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -d '{
    "plan": "starter",
    "successUrl": "https://app.example.com/billing/success",
    "cancelUrl": "https://app.example.com/billing/cancel"
  }'

Response:

{
  "sessionId": "cs_test_abc123...",
  "url": "https://checkout.stripe.com/pay/cs_test_abc123..."
}

Next steps:

  1. Open the url in a browser
  2. Complete the payment form (test card: 4242 4242 4242 4242)
  3. User is redirected to successUrl
  4. Stripe sends webhook to /api/billing/webhook

2. Get Subscription Status

Check the current subscription status (after completing checkout):

curl -X GET http://localhost:3456/api/billing/subscription \
  -H "Authorization: Bearer $AUTH_TOKEN"

Response (with active subscription):

{
  "subscription": {
    "id": 1,
    "user_id": 5,
    "stripe_customer_id": "cus_ABC123...",
    "stripe_subscription_id": "sub_ABC123...",
    "plan": "starter",
    "status": "active",
    "trial_start": "2026-02-14T12:49:00.000Z",
    "trial_end": "2026-02-28T12:49:00.000Z",
    "current_period_start": "2026-02-14T12:49:00.000Z",
    "current_period_end": "2026-03-14T12:49:00.000Z",
    "cancel_at_period_end": false,
    "created_at": "2026-02-14T12:49:00.000Z",
    "updated_at": "2026-02-14T12:49:00.000Z"
  }
}

Response (no subscription):

{
  "subscription": null,
  "message": "No active subscription. User is on free tier."
}

3. Create Customer Portal Session

Allow users to manage their subscription (upgrade, downgrade, cancel):

curl -X POST http://localhost:3456/api/billing/portal \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AUTH_TOKEN" \
  -d '{
    "returnUrl": "https://app.example.com/account/billing"
  }'

Response:

{
  "url": "https://billing.stripe.com/session/cs_test_abc123..."
}

Usage:

  1. Open the url in a browser (user must be logged into Stripe or their payment method)
  2. User can upgrade/downgrade plans, update payment method, or cancel
  3. After managing subscription, redirected back to returnUrl

Test Scenarios

Scenario 1: New User Signup → Checkout

# 1. Register
TOKEN=$(curl -s -X POST http://localhost:3456/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newuser@example.com",
    "password": "pass123",
    "company_name": "New Corp"
  }' | jq -r '.token')

# 2. Create checkout session
curl -X POST http://localhost:3456/api/billing/checkout \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "plan": "growth",
    "successUrl": "https://app.example.com/success",
    "cancelUrl": "https://app.example.com/cancel"
  }' | jq '.url'

# 3. Open URL in browser and complete payment
# (Use test card 4242 4242 4242 4242, any future expiry, any 3-digit CVC)

# 4. Check subscription status (after webhook processes)
curl -X GET http://localhost:3456/api/billing/subscription \
  -H "Authorization: Bearer $TOKEN"

Scenario 2: Upgrade Plan

# 1. User is on "starter" plan, wants to upgrade to "pro"

# 2. Create new checkout session (Stripe recognizes customer, handles proration)
curl -X POST http://localhost:3456/api/billing/checkout \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "plan": "pro",
    "successUrl": "https://app.example.com/success",
    "cancelUrl": "https://app.example.com/cancel"
  }'

# 3. Complete payment
# 4. Webhook updates subscription to "pro" plan

Scenario 3: Manage via Customer Portal

# User goes to billing page in app and clicks "Manage Subscription"

# Create portal session
curl -X POST http://localhost:3456/api/billing/portal \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "returnUrl": "https://app.example.com/account/billing"
  }' | jq '.url'

# Open URL in browser
# User can downgrade, upgrade, or cancel without returning to app

Testing with Stripe CLI (Local Webhooks)

To test webhooks locally without exposing your server:

# 1. Install Stripe CLI (if not already installed)
# macOS: brew install stripe/stripe-cli/stripe
# Linux/Windows: See https://stripe.com/docs/stripe-cli

# 2. Login to your Stripe account
stripe login

# 3. Start listening and forwarding to local server
stripe listen --forward-to localhost:3456/api/billing/webhook

# Output will show: Ready! Your webhook signing secret is: whsec_test_abc123...
# Update .env: STRIPE_WEBHOOK_SECRET=whsec_test_abc123...

# 4. In another terminal, trigger test events:
stripe trigger checkout.session.completed

# Webhook will be forwarded to localhost:3456/api/billing/webhook

Test Cards

Stripe provides test cards for different scenarios:

Card Number Result Expiry CVC
4242 4242 4242 4242 Successful charge Any future Any 3 digits
4000 0025 0000 3155 Insufficient funds Any future Any 3 digits
4000 0000 0000 0002 Card declined Any future Any 3 digits
4000 0000 0000 0010 Address verification failed Any future Any 3 digits

Error Responses

Missing Authorization Token

curl -X GET http://localhost:3456/api/billing/subscription

# Response:
{"error": "No token provided"}

Invalid Plan

curl -X POST http://localhost:3456/api/billing/checkout \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"plan": "invalid_plan", ...}'

# Response:
{"error": "Invalid plan: invalid_plan"}

No Active Subscription

curl -X POST http://localhost:3456/api/billing/portal \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"returnUrl": "..."}'

# Response (if user not subscribed):
{"error": "No subscription found for user"}

Debugging

Check server logs for detailed webhook processing:

# Terminal running the server shows:
# Processing webhook event: checkout.session.completed
# Subscription created for user 5 on plan starter

Database query to check subscription status:

# Connect to PostgreSQL
psql -U tenderpilot -d tenderpilot -h localhost

# Check subscriptions
SELECT * FROM subscriptions WHERE user_id = 5;

# Check user tier
SELECT id, email, tier FROM users WHERE id = 5;