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:
- Customer completes Stripe checkout
- Stripe fires
checkout.session.completed - Webhook handler checks if product has
generateLicenseKey: true - Calls
notifyPurchase()with email, productId, variant, and durationDays - Stores
licenseServerKeyon the purchase record in Firestore - 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, orrevoked - 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
revokedin 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 > 0causes 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 |