# TenderRadar Billing API Examples Quick reference for testing billing endpoints. Replace `AUTH_TOKEN` with a real JWT token from `/api/auth/login`. ## Setup ```bash # 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: ```bash 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:** ```json { "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): ```bash curl -X GET http://localhost:3456/api/billing/subscription \ -H "Authorization: Bearer $AUTH_TOKEN" ``` **Response (with active subscription):** ```json { "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):** ```json { "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): ```bash 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:** ```json { "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 ```bash # 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 ```bash # 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 ```bash # 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: ```bash # 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 ```bash curl -X GET http://localhost:3456/api/billing/subscription # Response: {"error": "No token provided"} ``` ### Invalid Plan ```bash 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 ```bash 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: ```bash # 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: ```bash # 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; ```