Skip to content

cglounge Integration

How cglounge.studio integrates with the CG Lounge License Server.

Status: Complete Branch: feature/license-server-integration Last updated: December 2025


Architecture

┌──────────────────────────────────────────────────────────────────────┐
│                          cglounge.studio                              │
│                                                                        │
│  Product Creation         Stripe Webhook          License Server      │
│  (config toggle)    ───▶  (trigger)         ───▶  Client             │
│  AssetContent.tsx         webhook/route.ts         licenseServer.ts   │
│                                                        │              │
│                                    ┌───────────────────┘              │
│                                    ▼                                   │
│  Library (display)  ◀───  Purchase Record  ◀───  CG Lounge License Server   │
│  LibraryCard.tsx           (licenseServerKey)      (Firebase)         │
│                                                                        │
│  License Control Center                                                │
│  LicenseControlCenter.tsx  (creator dashboard sheet)                  │
│                                                                        │
└──────────────────────────────────────────────────────────────────────┘

Implementation Details

1. License Server Client

File: src/lib/licenseServer.ts

Function Purpose
createLicense() Create a new license via admin API
revokeLicense() Revoke a license with a reason string
getLicense() Fetch license detail plus activation list
listLicenses() List licenses with filters (email, productId)
reinstateLicense() Restore a previously revoked license
notifyPurchase() POST purchase.completed to cgloungeWebhook
notifyRefund() POST purchase.refunded to cgloungeWebhook
notifyDispute() POST purchase.disputed to cgloungeWebhook
sendLicenseEmail() Trigger Resend email with license key

Environment Variables:

CG_LICENSE_SERVER_URL=https://us-central1-cg-license-server.cloudfunctions.net
CG_LICENSE_ADMIN_SECRET=<admin-secret>
CG_LICENSE_WEBHOOK_SECRET=<webhook-secret>

2. Stripe Webhook Integration

File: src/app/api/stripe/webhook/route.ts

Events Handled:

Event Action
checkout.session.completed Create license via notifyPurchase()
charge.refunded Revoke license via notifyRefund()
charge.dispute.created Suspend license via notifyDispute()

Purchase Flow:

  1. Customer completes Stripe checkout
  2. Stripe fires checkout.session.completed
  3. Webhook handler checks if product has generateLicenseKey: true
  4. Calls notifyPurchase() with email, productId, variant, and durationDays
  5. Stores licenseServerKey on the purchase record in Firestore
  6. Calls sendLicenseEmail() to deliver the key via Resend

3. Product Configuration

File: src/app/create/components/content/AssetContent.tsx

Creators configure licensing when creating or editing a product.

Settings:

Setting Options
Enable Licensing Toggle on / off
License Type Per-Machine, Floating, Site
Max Machines 1-100 (not applicable for Site)

Product fields (TypeScript):

interface ProductLicenseConfig {
  generateLicenseKey: boolean;
  licenseType: "per-machine" | "floating" | "site";
  maxMachines: number;
}

When the creator saves, setupProductLicensing() is called to sync the product and its variants to the license server.


4. User Library Display

File: src/components/LibraryCard.tsx

When a user views their library, each licensed product card shows:

  • License key (truncated) with a one-click copy button
  • Live status fetched from /api/licenses/[key]
  • Status badge: active, degraded, suspended, or revoked
  • Activation count: e.g. "2/3 machines"

The status fetch is a lightweight GET that proxies to the license server's /getLicense endpoint. No sensitive admin credentials are exposed to the browser.


5. Creator License Control Center

File: src/components/licensing/LicenseControlCenter.tsx

A sheet-based panel triggered from the VendorDashboard product card.

Features:

Section Content
Quick Stats Active, Revoked, Threats, Total license counts
License Config Edit license type and max machine limit
License Table Search by email, filter by status, sort columns
Row Details Full key, list of activated machines, violation history
Actions Revoke, Reinstate, Copy key to clipboard
Threat Alerts Licenses where threatLevel > 0 highlighted

Trigger Button States:

State Appearance
Normal Purple key icon
Has Threats Amber pulsing icon

6. API Endpoints (cglounge-side)

These are cglounge's own Next.js route handlers that proxy calls to the license server. No admin secret is exposed to frontend clients.

Customer-Facing:

Endpoint Method Purpose
/api/licenses/[licenseKey] GET License status and activation count for library display

Creator-Facing:

Endpoint Method Purpose
/api/creator/licenses POST List licenses for a product
/api/creator/licenses/config POST Update product license settings
/api/creator/licenses/revoke POST Revoke a license
/api/creator/licenses/reinstate POST Reinstate a revoked license

License Types

Type Behavior Use Case
Per-Machine Tied to hardware fingerprints; each activation consumes one seat Desktop plugins, indie tools
Floating Concurrent user cap; any machine can check out a seat Studio or team licenses, render farms
Site Unlimited machines; geo-spread checks disabled Enterprise studio-wide deployment

Variation Support

Variant from Product Variation

License variant is automatically derived from the product variation selected at checkout:

notifyPurchase({
  email: purchase.buyerEmail,
  productId: purchase.productSlug,
  variant: variationName || "indie",
  purchaseId: purchase.orderId,
});

Different tiers (Indie, Studio, Enterprise) map to separate variant configurations on the license server, each with distinct machine limits and pricing.

durationDays (Subscription / Time-Limited Licenses)

For subscription or time-limited products, pass durationDays alongside the variant:

notifyPurchase({
  email: purchase.buyerEmail,
  productId: purchase.productSlug,
  variant: variationName || "indie",
  purchaseId: purchase.orderId,
  durationDays: variation.durationDays,  // 30, 90, 365, etc.
});

Priority applied by the license server: explicit durationDays > trialDays > discount code trial > perpetual.

The license server stores durationDays on the license record so that subscription.renewed events can auto-extend by the correct period.

Variant Sync

Product tiers saved on the creator's product edit page are synced to the license server via syncVariantsToLicenseServer() in the onProductUpdated Firestore trigger. This populates the variants collection so plugin tools can query their own tier list via GET /listVariants.

Admin Endpoints for Variants

Endpoint Method Purpose
/listVariants GET / POST List active variants for a product
/createVariant POST Create or overwrite a variant
/updateVariant POST Update variant fields (price, limits, active state)
/resetActivations POST Remove all machine activations from a license

Email Template

License keys are delivered via Resend using a transactional template that includes:

  • Product title
  • License key in monospace formatting (copyable)
  • License type
  • Activation limit
  • Step-by-step activation instructions linking to the creator's docs

File Structure

src/
├── lib/
│   └── licenseServer.ts              # API client for all license server calls
├── components/
│   └── licensing/
│       ├── index.ts                  # Barrel export
│       └── LicenseControlCenter.tsx  # Creator admin sheet panel
├── app/
│   ├── api/
│   │   ├── licenses/
│   │   │   └── [licenseKey]/
│   │   │       └── route.ts          # GET license status (customer-facing)
│   │   ├── creator/
│   │   │   └── licenses/
│   │   │       ├── route.ts          # POST list licenses (creator-facing)
│   │   │       ├── config/
│   │   │       │   └── route.ts      # POST update license config
│   │   │       ├── revoke/
│   │   │       │   └── route.ts      # POST revoke license
│   │   │       └── reinstate/
│   │   │           └── route.ts      # POST reinstate license
│   │   └── stripe/
│   │       └── webhook/
│   │           └── route.ts          # Stripe event handler
│   ├── create/
│   │   └── components/
│   │       └── content/
│   │           └── AssetContent.tsx  # License configuration UI (creator)
│   └── account/
│       └── components/
│           └── VendorDashboard.tsx   # License Control Center trigger
└── types/
    └── index.ts                      # Product type with license fields

Testing Checklist

  • [ ] Create a product with licensing enabled via the creator dashboard
  • [ ] Purchase the product as a test user
  • [ ] Verify license is created in the license server (check Firestore or use /getLicense)
  • [ ] Verify license key appears in the buyer's library
  • [ ] Verify license email is received (check Resend logs)
  • [ ] Test copy-key button in the library card
  • [ ] Open License Control Center as creator, confirm license appears in the table
  • [ ] Revoke the license from the creator panel
  • [ ] Verify status updates to revoked in the user library
  • [ ] Reinstate the license and verify it returns to active
  • [ ] Trigger a test refund and confirm auto-revoke fires
  • [ ] Test with a product that has multiple variations (indie + studio)
  • [ ] Confirm threatLevel > 0 causes the amber pulsing button state

Future Enhancements

Feature Description
User self-deactivation Let buyers free up a machine slot from their library without contacting support
License transfer Allow moving a license to a different email address
Per-variation machine limits UI Expose maxMachines per tier in the product editor (currently set server-side)
Renewal extension UI Show expiry date and days remaining for time-limited licenses in the library
Bulk revoke Creator action to revoke all licenses on a product (DMCA, takedown scenarios)
Webhook retry visibility Show webhook delivery failures in the creator dashboard for debugging