@taskforcehq/taskforce 0.3.307 → 0.3.309

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/components/views/PlanComparisonPage.d.ts +20 -15
  2. package/dist/components/views/PlanComparisonPage.js +214 -83
  3. package/dist/components/views/PlanComparisonPage.test.js +249 -14
  4. package/dist/components/views/PlansPage.js +15 -9
  5. package/dist/components/views/PlansPage.test.js +4 -2
  6. package/dist/core/GlobalSettingsService.js +18 -0
  7. package/dist/core/GlobalSettingsService.test.d.ts +1 -0
  8. package/dist/core/GlobalSettingsService.test.js +45 -0
  9. package/dist/core/PlanEntitlementService.d.ts +20 -0
  10. package/dist/core/PlanEntitlementService.js +215 -19
  11. package/dist/core/PlanFeatureCatalog.test.js +55 -0
  12. package/dist/core/Taskforce.d.ts +14 -0
  13. package/dist/core/Taskforce.js +6 -0
  14. package/dist/core/types.d.ts +3 -0
  15. package/dist/migrations/taskSchemaMigrations.js +37 -15
  16. package/dist/server/routes/admin.js +146 -5
  17. package/dist/server/routes/authSupport.d.ts +1 -0
  18. package/dist/server/routes/authSupport.js +2 -1
  19. package/dist/server/routes/billing.d.ts +1 -0
  20. package/dist/server/routes/billing.js +105 -63
  21. package/dist/server/routes/billing.test.js +159 -11
  22. package/dist/server/routes.test.js +91 -10
  23. package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
  24. package/dist/ui/agent-logos/antigravity.jpeg +0 -0
  25. package/dist/ui/agent-logos/chatgpt.png +0 -0
  26. package/dist/ui/agent-logos/claude-code.jpeg +0 -0
  27. package/dist/ui/agent-logos/codex.jpeg +0 -0
  28. package/dist/ui/agent-logos/cursor.png +0 -0
  29. package/dist/ui/agent-logos/openclaw.jpeg +0 -0
  30. package/dist/ui/agent-logos/windsurf.png +0 -0
  31. package/dist/ui/assets/{AgentsModule-2y82_3DO.js → AgentsModule-BS4Pi_nC.js} +1 -1
  32. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTLZZCiQ.js → AnnotatedAttachmentWorkspace-CCckzWYZ.js} +1 -1
  33. package/dist/ui/assets/{ContextAttachmentManager-D2epuNnG.js → ContextAttachmentManager-CfG_ER7C.js} +1 -1
  34. package/dist/ui/assets/{DocumentWorkspace-sVa-3Do6.js → DocumentWorkspace-BVaWPA25.js} +1 -1
  35. package/dist/ui/assets/{EntityActivityTimeline-Zods27y_.js → EntityActivityTimeline-DSj0ThaK.js} +1 -1
  36. package/dist/ui/assets/{InitiativesModule-D4XseTwW.js → InitiativesModule-AxMkWtxH.js} +1 -1
  37. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
  38. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
  39. package/dist/ui/assets/{TaskContextUpload-uC5qx4Bv.js → TaskContextUpload-Dw4rTF4i.js} +1 -1
  40. package/dist/ui/assets/{TaskSettings-CqvPAsTv.js → TaskSettings-f2ga42_V.js} +1 -1
  41. package/dist/ui/assets/{WorkflowsModule-oOA9bcdK.js → WorkflowsModule-E4UL3mov.js} +1 -1
  42. package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
  43. package/dist/ui/assets/{index-CYyodXsg.css → index-BvAbqx5Q.css} +1 -1
  44. package/dist/ui/assets/{index-607WPo_X.js → index-MAHKvFqp.js} +4 -4
  45. package/dist/ui/index.html +2 -2
  46. package/dist/ui/og-image.png +0 -0
  47. package/package.json +1 -1
  48. package/dist/ui/assets/PlansPage-D5AcbO0L.js +0 -1
  49. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +0 -1
  50. package/dist/ui/assets/documentReferences-DPZuTgHh.js +0 -1
@@ -24,6 +24,9 @@ function parsePricingType(raw) {
24
24
  return value;
25
25
  return null;
26
26
  }
27
+ function parsePricingAudience(raw) {
28
+ return String(raw || '').trim().toLowerCase() === 'campaign' ? 'campaign' : 'public';
29
+ }
27
30
  function readTableVersionStats(core, tableName) {
28
31
  const db = core.getDatabaseAdapter();
29
32
  const tenantId = core.getTenantId();
@@ -52,6 +55,16 @@ function buildPublicPricingCacheVersion(core) {
52
55
  const prices = readTableVersionStats(core, 'billing_price_mappings');
53
56
  const features = readTableVersionStats(core, 'plan_feature_matrix');
54
57
  const featureCatalogOverrides = readTableVersionStats(core, 'plan_feature_catalog_overrides');
58
+ const siteSettings = typeof core.getGlobalSettings === 'function'
59
+ ? (core.getGlobalSettings().site || {})
60
+ : {};
61
+ const pricingPageTitle = typeof siteSettings.pricingPageTitle === 'string'
62
+ ? siteSettings.pricingPageTitle.trim()
63
+ : '';
64
+ const pricingPageDescription = typeof siteSettings.pricingPageDescription === 'string'
65
+ ? siteSettings.pricingPageDescription.trim()
66
+ : '';
67
+ const pricingPageShowPublicDescriptions = siteSettings.pricingPageShowPublicDescriptions === true ? '1' : '0';
55
68
  return [
56
69
  catalog.count,
57
70
  catalog.maxUpdatedAt,
@@ -62,11 +75,16 @@ function buildPublicPricingCacheVersion(core) {
62
75
  features.count,
63
76
  features.maxUpdatedAt,
64
77
  featureCatalogOverrides.count,
65
- featureCatalogOverrides.maxUpdatedAt
78
+ featureCatalogOverrides.maxUpdatedAt,
79
+ pricingPageTitle,
80
+ pricingPageDescription,
81
+ pricingPageShowPublicDescriptions
66
82
  ].join('|');
67
83
  }
68
- function buildFreePricingSyntheticId(planVersionId, interval) {
69
- return `free:${planVersionId}:${interval}`;
84
+ function buildFreePricingSyntheticId(planVersionId, interval, pricingAudience = 'public') {
85
+ return pricingAudience === 'campaign'
86
+ ? `free:${planVersionId}:${interval}:campaign`
87
+ : `free:${planVersionId}:${interval}`;
70
88
  }
71
89
  function isoFromUnixSeconds(raw) {
72
90
  const value = Number(raw);
@@ -435,15 +453,17 @@ function resolveStripePriceIdForPlanVersion(core, planVersionIdRaw, intervalRaw)
435
453
  WHERE tenant_id = ?
436
454
  AND plan_version_id = ?
437
455
  AND billing_interval = ?
456
+ AND COALESCE(pricing_audience, 'public') = 'public'
438
457
  AND active = 1
439
458
  AND COALESCE(pricing_type, 'stripe') = 'stripe'
440
459
  LIMIT 1
441
460
  `).get(tenantId, planVersionId, interval);
442
461
  return row?.stripe_price_id ? String(row.stripe_price_id) : null;
443
462
  }
444
- function resolveActivePricingForPlanVersion(core, planVersionIdRaw, intervalRaw) {
463
+ function resolveActivePricingForPlanVersion(core, planVersionIdRaw, intervalRaw, pricingAudienceRaw = 'public') {
445
464
  const planVersionId = String(planVersionIdRaw || '').trim();
446
465
  const interval = parseInterval(intervalRaw);
466
+ const pricingAudience = parsePricingAudience(pricingAudienceRaw);
447
467
  if (!planVersionId || !interval)
448
468
  return null;
449
469
  const db = core.getDatabaseAdapter();
@@ -454,9 +474,10 @@ function resolveActivePricingForPlanVersion(core, planVersionIdRaw, intervalRaw)
454
474
  WHERE tenant_id = ?
455
475
  AND plan_version_id = ?
456
476
  AND billing_interval = ?
477
+ AND COALESCE(pricing_audience, 'public') = ?
457
478
  AND active = 1
458
479
  LIMIT 1
459
- `).get(tenantId, planVersionId, interval);
480
+ `).get(tenantId, planVersionId, interval, pricingAudience);
460
481
  if (!row)
461
482
  return null;
462
483
  const pricingType = parsePricingType(row.pricing_type) || 'stripe';
@@ -476,6 +497,7 @@ function resolvePlanVersionIdForPriceIdFallback(core, priceIdRaw) {
476
497
  FROM billing_price_mappings
477
498
  WHERE tenant_id = ?
478
499
  AND stripe_price_id = ?
500
+ AND COALESCE(pricing_audience, 'public') = 'public'
479
501
  AND active = 1
480
502
  ORDER BY plan_version_id ASC
481
503
  `).all(tenantId, priceId);
@@ -1195,9 +1217,9 @@ async function importStripePrices(core) {
1195
1217
  }
1196
1218
  db.prepare(`
1197
1219
  INSERT INTO billing_price_mappings (
1198
- tenant_id, plan_version_id, billing_interval, stripe_price_id, active, unit_amount, currency, created_at, updated_at
1199
- ) VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?)
1200
- ON CONFLICT (tenant_id, plan_version_id, billing_interval, stripe_price_id) DO UPDATE SET
1220
+ tenant_id, plan_version_id, billing_interval, pricing_audience, stripe_price_id, active, unit_amount, currency, created_at, updated_at
1221
+ ) VALUES (?, ?, ?, 'public', ?, 1, ?, ?, ?, ?)
1222
+ ON CONFLICT (tenant_id, plan_version_id, billing_interval, pricing_audience, stripe_price_id) DO UPDATE SET
1201
1223
  active = 1,
1202
1224
  unit_amount = excluded.unit_amount,
1203
1225
  currency = excluded.currency,
@@ -1216,7 +1238,7 @@ async function importStripePrices(core) {
1216
1238
  const existing = db.prepare(`
1217
1239
  SELECT stripe_price_id, pricing_type
1218
1240
  FROM billing_price_mappings
1219
- WHERE tenant_id = ? AND active = 1
1241
+ WHERE tenant_id = ? AND active = 1 AND COALESCE(pricing_audience, 'public') = 'public'
1220
1242
  `).all(tenantId);
1221
1243
  for (const row of existing) {
1222
1244
  const priceId = String(row.stripe_price_id || '').trim();
@@ -1232,7 +1254,7 @@ async function importStripePrices(core) {
1232
1254
  db.prepare(`
1233
1255
  UPDATE billing_price_mappings
1234
1256
  SET active = 0, updated_at = ?
1235
- WHERE tenant_id = ? AND stripe_price_id = ?
1257
+ WHERE tenant_id = ? AND stripe_price_id = ? AND COALESCE(pricing_audience, 'public') = 'public'
1236
1258
  `).run(now, tenantId, priceId);
1237
1259
  summary.deactivated += 1;
1238
1260
  }
@@ -1382,10 +1404,10 @@ export function registerBillingRoutes(deps) {
1382
1404
  const db = core.getDatabaseAdapter();
1383
1405
  const tenantId = core.getTenantId();
1384
1406
  const rows = db.prepare(`
1385
- SELECT plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
1407
+ SELECT plan_version_id, billing_interval, COALESCE(pricing_audience, 'public') AS pricing_audience, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
1386
1408
  FROM billing_price_mappings
1387
1409
  WHERE tenant_id = ?
1388
- ORDER BY plan_version_id ASC, billing_interval ASC, stripe_price_id ASC
1410
+ ORDER BY plan_version_id ASC, billing_interval ASC, pricing_audience ASC, stripe_price_id ASC
1389
1411
  `).all(tenantId);
1390
1412
  if (rows.length === 0) {
1391
1413
  console.warn(`[Taskforce] Cloud billing returned no price mappings for tenant=${tenantId}.`);
@@ -1425,6 +1447,7 @@ export function registerBillingRoutes(deps) {
1425
1447
  const body = await parseJsonBody(req);
1426
1448
  const planVersionId = String(body?.planVersionId || '').trim();
1427
1449
  const interval = parseInterval(body?.interval);
1450
+ const pricingAudience = parsePricingAudience(body?.pricingAudience);
1428
1451
  const pricingType = parsePricingType(body?.pricingType) || 'stripe';
1429
1452
  const stripePriceId = String(body?.stripePriceId || '').trim();
1430
1453
  const setActive = body?.active === false ? false : true;
@@ -1454,7 +1477,7 @@ export function registerBillingRoutes(deps) {
1454
1477
  try {
1455
1478
  const now = new Date().toISOString();
1456
1479
  const targetStripePriceId = pricingType === 'free'
1457
- ? buildFreePricingSyntheticId(planVersionId, interval)
1480
+ ? buildFreePricingSyntheticId(planVersionId, interval, pricingAudience)
1458
1481
  : stripePriceId;
1459
1482
  let unitAmount = 0;
1460
1483
  let currency = null;
@@ -1483,9 +1506,10 @@ export function registerBillingRoutes(deps) {
1483
1506
  WHERE tenant_id = ?
1484
1507
  AND stripe_price_id = ?
1485
1508
  AND active = 1
1509
+ AND COALESCE(pricing_audience, 'public') = ?
1486
1510
  AND plan_version_id <> ?
1487
1511
  LIMIT 1
1488
- `).get(tenantId, stripePriceId, planVersionId);
1512
+ `).get(tenantId, stripePriceId, pricingAudience, planVersionId);
1489
1513
  if (conflictingMapping?.plan_version_id) {
1490
1514
  res.writeHead(409, { 'Content-Type': 'application/json' });
1491
1515
  res.end(JSON.stringify({
@@ -1498,11 +1522,11 @@ export function registerBillingRoutes(deps) {
1498
1522
  currency = stripePrice.currency || null;
1499
1523
  }
1500
1524
  const beforeRows = db.prepare(`
1501
- SELECT plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency
1525
+ SELECT plan_version_id, billing_interval, COALESCE(pricing_audience, 'public') AS pricing_audience, stripe_price_id, pricing_type, active, unit_amount, currency
1502
1526
  FROM billing_price_mappings
1503
- WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ?
1527
+ WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND COALESCE(pricing_audience, 'public') = ?
1504
1528
  ORDER BY stripe_price_id ASC
1505
- `).all(tenantId, planVersionId, interval);
1529
+ `).all(tenantId, planVersionId, interval, pricingAudience);
1506
1530
  if (setActive) {
1507
1531
  db.prepare(`
1508
1532
  UPDATE billing_price_mappings
@@ -1510,36 +1534,38 @@ export function registerBillingRoutes(deps) {
1510
1534
  WHERE tenant_id = ?
1511
1535
  AND plan_version_id = ?
1512
1536
  AND billing_interval = ?
1537
+ AND COALESCE(pricing_audience, 'public') = ?
1513
1538
  AND stripe_price_id <> ?
1514
1539
  AND active = 1
1515
- `).run(now, tenantId, planVersionId, interval, targetStripePriceId);
1540
+ `).run(now, tenantId, planVersionId, interval, pricingAudience, targetStripePriceId);
1516
1541
  }
1517
1542
  db.prepare(`
1518
1543
  INSERT INTO billing_price_mappings (
1519
- tenant_id, plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
1520
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1521
- ON CONFLICT (tenant_id, plan_version_id, billing_interval, stripe_price_id) DO UPDATE SET
1544
+ tenant_id, plan_version_id, billing_interval, pricing_audience, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
1545
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1546
+ ON CONFLICT (tenant_id, plan_version_id, billing_interval, pricing_audience, stripe_price_id) DO UPDATE SET
1522
1547
  pricing_type = excluded.pricing_type,
1523
1548
  active = excluded.active,
1524
1549
  unit_amount = excluded.unit_amount,
1525
1550
  currency = excluded.currency,
1526
1551
  updated_at = excluded.updated_at
1527
- `).run(tenantId, planVersionId, interval, targetStripePriceId, pricingType, setActive ? 1 : 0, unitAmount, currency, now, now);
1552
+ `).run(tenantId, planVersionId, interval, pricingAudience, targetStripePriceId, pricingType, setActive ? 1 : 0, unitAmount, currency, now, now);
1528
1553
  const mapping = db.prepare(`
1529
- SELECT plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
1554
+ SELECT plan_version_id, billing_interval, COALESCE(pricing_audience, 'public') AS pricing_audience, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
1530
1555
  FROM billing_price_mappings
1531
- WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND stripe_price_id = ?
1556
+ WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND COALESCE(pricing_audience, 'public') = ? AND stripe_price_id = ?
1532
1557
  LIMIT 1
1533
- `).get(tenantId, planVersionId, interval, targetStripePriceId);
1558
+ `).get(tenantId, planVersionId, interval, pricingAudience, targetStripePriceId);
1534
1559
  const afterRows = db.prepare(`
1535
- SELECT plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency
1560
+ SELECT plan_version_id, billing_interval, COALESCE(pricing_audience, 'public') AS pricing_audience, stripe_price_id, pricing_type, active, unit_amount, currency
1536
1561
  FROM billing_price_mappings
1537
- WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ?
1562
+ WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND COALESCE(pricing_audience, 'public') = ?
1538
1563
  ORDER BY stripe_price_id ASC
1539
- `).all(tenantId, planVersionId, interval);
1564
+ `).all(tenantId, planVersionId, interval, pricingAudience);
1540
1565
  auditAdminAction(req, 'billing-price-mapping-upsert', {
1541
1566
  planVersionId,
1542
1567
  interval,
1568
+ pricingAudience,
1543
1569
  pricingType,
1544
1570
  stripePriceId: pricingType === 'free' ? null : stripePriceId,
1545
1571
  active: setActive,
@@ -1566,6 +1592,7 @@ export function registerBillingRoutes(deps) {
1566
1592
  const body = await parseJsonBody(req);
1567
1593
  const planVersionId = String(body?.planVersionId || '').trim();
1568
1594
  const interval = parseInterval(body?.interval);
1595
+ const pricingAudience = parsePricingAudience(body?.pricingAudience);
1569
1596
  const stripePriceId = String(body?.stripePriceId || '').trim();
1570
1597
  if (!planVersionId || !interval) {
1571
1598
  res.writeHead(400, { 'Content-Type': 'application/json' });
@@ -1576,31 +1603,32 @@ export function registerBillingRoutes(deps) {
1576
1603
  const tenantId = core.getTenantId();
1577
1604
  const now = new Date().toISOString();
1578
1605
  const beforeRows = db.prepare(`
1579
- SELECT plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency
1606
+ SELECT plan_version_id, billing_interval, COALESCE(pricing_audience, 'public') AS pricing_audience, stripe_price_id, pricing_type, active, unit_amount, currency
1580
1607
  FROM billing_price_mappings
1581
- WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ?
1608
+ WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND COALESCE(pricing_audience, 'public') = ?
1582
1609
  ORDER BY stripe_price_id ASC
1583
- `).all(tenantId, planVersionId, interval);
1610
+ `).all(tenantId, planVersionId, interval, pricingAudience);
1584
1611
  const result = stripePriceId
1585
1612
  ? db.prepare(`
1586
1613
  UPDATE billing_price_mappings
1587
1614
  SET active = 0, updated_at = ?
1588
- WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND stripe_price_id = ?
1589
- `).run(now, tenantId, planVersionId, interval, stripePriceId)
1615
+ WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND COALESCE(pricing_audience, 'public') = ? AND stripe_price_id = ?
1616
+ `).run(now, tenantId, planVersionId, interval, pricingAudience, stripePriceId)
1590
1617
  : db.prepare(`
1591
1618
  UPDATE billing_price_mappings
1592
1619
  SET active = 0, updated_at = ?
1593
- WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ?
1594
- `).run(now, tenantId, planVersionId, interval);
1620
+ WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND COALESCE(pricing_audience, 'public') = ?
1621
+ `).run(now, tenantId, planVersionId, interval, pricingAudience);
1595
1622
  const afterRows = db.prepare(`
1596
- SELECT plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency
1623
+ SELECT plan_version_id, billing_interval, COALESCE(pricing_audience, 'public') AS pricing_audience, stripe_price_id, pricing_type, active, unit_amount, currency
1597
1624
  FROM billing_price_mappings
1598
- WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ?
1625
+ WHERE tenant_id = ? AND plan_version_id = ? AND billing_interval = ? AND COALESCE(pricing_audience, 'public') = ?
1599
1626
  ORDER BY stripe_price_id ASC
1600
- `).all(tenantId, planVersionId, interval);
1627
+ `).all(tenantId, planVersionId, interval, pricingAudience);
1601
1628
  auditAdminAction(req, 'billing-price-mapping-deactivate', {
1602
1629
  planVersionId,
1603
1630
  interval,
1631
+ pricingAudience,
1604
1632
  stripePriceId: stripePriceId || null,
1605
1633
  changedRows: Number(result?.changes || 0),
1606
1634
  before: beforeRows,
@@ -1800,12 +1828,13 @@ export function registerBillingRoutes(deps) {
1800
1828
  const body = await parseJsonBody(req);
1801
1829
  const interval = parseInterval(body?.interval);
1802
1830
  const planVersionId = String(body?.planVersionId || '').trim();
1831
+ const pricingAudience = parsePricingAudience(body?.pricingAudience);
1803
1832
  if (!interval || !planVersionId) {
1804
1833
  res.writeHead(400, { 'Content-Type': 'application/json' });
1805
1834
  res.end(JSON.stringify({ error: 'planVersionId and interval are required.' }));
1806
1835
  return;
1807
1836
  }
1808
- const pricing = resolveActivePricingForPlanVersion(core, planVersionId, interval);
1837
+ const pricing = resolveActivePricingForPlanVersion(core, planVersionId, interval, pricingAudience);
1809
1838
  if (!pricing) {
1810
1839
  res.writeHead(400, { 'Content-Type': 'application/json' });
1811
1840
  res.end(JSON.stringify({ error: 'No active pricing mapping found for requested plan/interval.' }));
@@ -1984,14 +2013,16 @@ export function registerBillingRoutes(deps) {
1984
2013
  checkout: 'cancel',
1985
2014
  planId: String(planId || ''),
1986
2015
  planVersionId,
1987
- interval
2016
+ interval,
2017
+ pricingAudience
1988
2018
  });
1989
2019
  const successParams = new URLSearchParams({
1990
2020
  screen: 'plans',
1991
2021
  checkout: 'success',
1992
2022
  planId: String(planId || ''),
1993
2023
  planVersionId,
1994
- interval
2024
+ interval,
2025
+ pricingAudience
1995
2026
  });
1996
2027
  const session = await stripe.checkout.sessions.create({
1997
2028
  mode: 'subscription',
@@ -2007,7 +2038,8 @@ export function registerBillingRoutes(deps) {
2007
2038
  userId,
2008
2039
  tenantId: core.getTenantId(),
2009
2040
  planVersionId,
2010
- billingInterval: interval
2041
+ billingInterval: interval,
2042
+ pricingAudience
2011
2043
  }
2012
2044
  },
2013
2045
  billing_address_collection: config.billingAddress,
@@ -2017,7 +2049,8 @@ export function registerBillingRoutes(deps) {
2017
2049
  userId,
2018
2050
  tenantId: core.getTenantId(),
2019
2051
  planVersionId,
2020
- billingInterval: interval
2052
+ billingInterval: interval,
2053
+ pricingAudience
2021
2054
  }
2022
2055
  });
2023
2056
  const checkoutEffectiveUntil = isoFromUnixSeconds(session.expires_at);
@@ -2562,6 +2595,7 @@ export function registerBillingRoutes(deps) {
2562
2595
  const pageDescription = typeof siteSettings.pricingPageDescription === 'string'
2563
2596
  ? (siteSettings.pricingPageDescription.trim() || 'Manage your subscription and explore available features. Your current plan is highlighted below.')
2564
2597
  : 'Manage your subscription and explore available features. Your current plan is highlighted below.';
2598
+ const showPublicDescriptions = siteSettings.pricingPageShowPublicDescriptions === true;
2565
2599
  const cacheKey = `${tenantId}:${environment}`;
2566
2600
  const cacheVersion = buildPublicPricingCacheVersion(core);
2567
2601
  const cached = publicPricingCache.get(cacheKey);
@@ -2574,17 +2608,30 @@ export function registerBillingRoutes(deps) {
2574
2608
  const activePlans = core.listPlanCatalog().filter(p => p.status === 'active');
2575
2609
  // Get all active price mappings with their amounts
2576
2610
  const activePrices = db.prepare(`
2577
- SELECT plan_version_id, billing_interval, stripe_price_id, pricing_type, unit_amount, currency
2611
+ SELECT plan_version_id, billing_interval, COALESCE(pricing_audience, 'public') AS pricing_audience, stripe_price_id, pricing_type, unit_amount, currency
2578
2612
  FROM billing_price_mappings
2579
- WHERE tenant_id = ? AND active = 1
2613
+ WHERE tenant_id = ? AND active = 1 AND COALESCE(pricing_audience, 'public') IN ('public', 'campaign')
2580
2614
  `).all(tenantId);
2581
2615
  const pricingByVersionAndInterval = new Map();
2616
+ const buildPricingPayload = (price, interval) => {
2617
+ if (!price)
2618
+ return null;
2619
+ const pricingType = parsePricingType(price.pricing_type) || 'stripe';
2620
+ return {
2621
+ pricingType,
2622
+ stripePriceId: pricingType === 'stripe' ? price.stripe_price_id : null,
2623
+ unitAmount: pricingType === 'free' ? 0 : price.unit_amount,
2624
+ currency: pricingType === 'free' ? null : price.currency,
2625
+ interval
2626
+ };
2627
+ };
2582
2628
  for (const price of activePrices) {
2583
2629
  const planVersionId = String(price.plan_version_id || '').trim();
2584
2630
  const interval = parseInterval(price.billing_interval);
2631
+ const pricingAudience = parsePricingAudience(price.pricing_audience);
2585
2632
  if (!planVersionId || !interval)
2586
2633
  continue;
2587
- pricingByVersionAndInterval.set(`${planVersionId}:${interval}`, price);
2634
+ pricingByVersionAndInterval.set(`${planVersionId}:${interval}:${pricingAudience}`, price);
2588
2635
  }
2589
2636
  const featureCatalog = core.listPlanFeatureCatalog();
2590
2637
  const featureCatalogByKey = new Map(featureCatalog.map((feature) => [feature.featureKey, feature]));
@@ -2596,8 +2643,10 @@ export function registerBillingRoutes(deps) {
2596
2643
  if (!defaultVersion) {
2597
2644
  return null; // Skip if no valid version exists (edge case)
2598
2645
  }
2599
- const monthPrice = pricingByVersionAndInterval.get(`${defaultVersion.planVersionId}:month`);
2600
- const yearPrice = pricingByVersionAndInterval.get(`${defaultVersion.planVersionId}:year`);
2646
+ const monthPrice = pricingByVersionAndInterval.get(`${defaultVersion.planVersionId}:month:public`);
2647
+ const yearPrice = pricingByVersionAndInterval.get(`${defaultVersion.planVersionId}:year:public`);
2648
+ const campaignMonthPrice = pricingByVersionAndInterval.get(`${defaultVersion.planVersionId}:month:campaign`);
2649
+ const campaignYearPrice = pricingByVersionAndInterval.get(`${defaultVersion.planVersionId}:year:campaign`);
2601
2650
  return {
2602
2651
  planId: plan.planId,
2603
2652
  displayName: plan.displayName,
@@ -2630,7 +2679,8 @@ export function registerBillingRoutes(deps) {
2630
2679
  label: catalogEntry?.label ?? null,
2631
2680
  description: catalogEntry?.description ?? null,
2632
2681
  publicLabel: catalogEntry?.publicLabel ?? null,
2633
- publicDescription: catalogEntry?.publicDescription ?? null
2682
+ publicDescription: catalogEntry?.publicDescription ?? null,
2683
+ publicDescriptionVisible: showPublicDescriptions
2634
2684
  });
2635
2685
  return acc;
2636
2686
  }, [])
@@ -2654,20 +2704,12 @@ export function registerBillingRoutes(deps) {
2654
2704
  return a.featureKey.localeCompare(b.featureKey);
2655
2705
  }),
2656
2706
  pricing: {
2657
- month: monthPrice ? {
2658
- pricingType: parsePricingType(monthPrice.pricing_type) || 'stripe',
2659
- stripePriceId: (parsePricingType(monthPrice.pricing_type) || 'stripe') === 'stripe' ? monthPrice.stripe_price_id : null,
2660
- unitAmount: (parsePricingType(monthPrice.pricing_type) || 'stripe') === 'free' ? 0 : monthPrice.unit_amount,
2661
- currency: (parsePricingType(monthPrice.pricing_type) || 'stripe') === 'free' ? null : monthPrice.currency,
2662
- interval: 'month'
2663
- } : null,
2664
- year: yearPrice ? {
2665
- pricingType: parsePricingType(yearPrice.pricing_type) || 'stripe',
2666
- stripePriceId: (parsePricingType(yearPrice.pricing_type) || 'stripe') === 'stripe' ? yearPrice.stripe_price_id : null,
2667
- unitAmount: (parsePricingType(yearPrice.pricing_type) || 'stripe') === 'free' ? 0 : yearPrice.unit_amount,
2668
- currency: (parsePricingType(yearPrice.pricing_type) || 'stripe') === 'free' ? null : yearPrice.currency,
2669
- interval: 'year'
2670
- } : null
2707
+ month: buildPricingPayload(monthPrice, 'month'),
2708
+ year: buildPricingPayload(yearPrice, 'year')
2709
+ },
2710
+ campaignPricing: {
2711
+ month: buildPricingPayload(campaignMonthPrice, 'month'),
2712
+ year: buildPricingPayload(campaignYearPrice, 'year')
2671
2713
  }
2672
2714
  };
2673
2715
  }).filter(Boolean);