Skip to content

Billing & Subscriptions

TendSocial uses Lemon Squeezy as the payment processor for subscription billing.

Architecture

Frontend → POST /api/billing/checkout → Lemon Squeezy → Webhook → Update subscription

API Endpoints

POST /api/billing/checkout

Creates a checkout URL for starting a subscription.

typescript
// Request
{
  plan?: string,       // e.g., 'professional', 'starter'
  variantId?: string,  // Direct Lemon Squeezy variant ID (overrides plan)
  redirectUrl: string  // Where to redirect after checkout
}

// Response
{ url: string }  // Lemon Squeezy checkout URL

Flow:

  1. Frontend calls checkout with plan name
  2. Backend maps plan → variantId via ConfigService.getBillingConfig()
  3. Creates checkout URL with user/company metadata
  4. User completes payment on Lemon Squeezy
  5. Webhook updates subscription status

GET /api/billing/subscription

Returns current subscription details for the company.

typescript
// Response
{
  id: string,
  status: 'active' | 'cancelled' | 'expired' | 'past_due',
  planName: string,
  currentPeriodEnd: Date,
  cancelAtPeriodEnd: boolean
}

POST /api/billing/portal

Returns a customer portal URL for managing subscription.

typescript
// Response
{ url: string }  // Lemon Squeezy customer portal

Webhook Handling

Endpoint: POST /api/billing/webhook (public, no auth)

Webhook signature is verified using LEMONSQUEEZY_WEBHOOK_SECRET.

Events handled:

  • subscription_created - New subscription, update company status
  • subscription_updated - Plan change, status change
  • subscription_cancelled - Mark as cancelled
  • subscription_payment_failed - Mark as past_due

Configuration

Required environment variables:

  • LEMONSQUEEZY_API_KEY - API access
  • LEMONSQUEEZY_STORE_ID - Store identifier
  • LEMONSQUEEZY_WEBHOOK_SECRET - Webhook signature verification

Runtime config (via ConfigService):

  • billingConfig.storeId - Override store ID
  • billingConfig.variantMapping - Map plan names to variant IDs

Database Schema

prisma
model Company {
  subscriptionId         String?
  subscriptionStatus     String?
  subscriptionPlanName   String?
  subscriptionExpiresAt  DateTime?
}

Testing

  • Use Lemon Squeezy test mode
  • Test webhook with ngrok or similar tunnel
  • Mock webhook events for integration tests

TendSocial Documentation