@taskforcehq/taskforce 0.3.307 → 0.3.308
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.
- package/dist/components/views/PlanComparisonPage.d.ts +20 -15
- package/dist/components/views/PlanComparisonPage.js +214 -83
- package/dist/components/views/PlanComparisonPage.test.js +249 -14
- package/dist/components/views/PlansPage.js +15 -9
- package/dist/components/views/PlansPage.test.js +4 -2
- package/dist/core/GlobalSettingsService.js +18 -0
- package/dist/core/GlobalSettingsService.test.d.ts +1 -0
- package/dist/core/GlobalSettingsService.test.js +45 -0
- package/dist/core/PlanEntitlementService.d.ts +20 -0
- package/dist/core/PlanEntitlementService.js +215 -19
- package/dist/core/PlanFeatureCatalog.test.js +55 -0
- package/dist/core/Taskforce.d.ts +14 -0
- package/dist/core/Taskforce.js +6 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/migrations/taskSchemaMigrations.js +37 -15
- package/dist/server/routes/admin.js +146 -5
- package/dist/server/routes/authSupport.d.ts +1 -0
- package/dist/server/routes/authSupport.js +2 -1
- package/dist/server/routes/billing.d.ts +1 -0
- package/dist/server/routes/billing.js +105 -63
- package/dist/server/routes/billing.test.js +159 -11
- package/dist/server/routes.test.js +91 -10
- package/dist/ui/agent-logos/ChatGPT.png +0 -0
- package/dist/ui/agent-logos/Gemini CLI.jpeg +0 -0
- package/dist/ui/agent-logos/antigravity.jpeg +0 -0
- package/dist/ui/agent-logos/claude-code.jpeg +0 -0
- package/dist/ui/agent-logos/codex.jpeg +0 -0
- package/dist/ui/agent-logos/cursor.png +0 -0
- package/dist/ui/agent-logos/openclaw.jpeg +0 -0
- package/dist/ui/agent-logos/windsurf.png +0 -0
- package/dist/ui/assets/{AgentsModule-2y82_3DO.js → AgentsModule-BS4Pi_nC.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTLZZCiQ.js → AnnotatedAttachmentWorkspace-CCckzWYZ.js} +1 -1
- package/dist/ui/assets/{ContextAttachmentManager-D2epuNnG.js → ContextAttachmentManager-CfG_ER7C.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-sVa-3Do6.js → DocumentWorkspace-BVaWPA25.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-Zods27y_.js → EntityActivityTimeline-DSj0ThaK.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-D4XseTwW.js → InitiativesModule-AxMkWtxH.js} +1 -1
- package/dist/ui/assets/PlansPage-B_z-D6Nh.css +1 -0
- package/dist/ui/assets/PlansPage-Ndt7mYfo.js +1 -0
- package/dist/ui/assets/{TaskContextUpload-uC5qx4Bv.js → TaskContextUpload-Dw4rTF4i.js} +1 -1
- package/dist/ui/assets/{TaskSettings-CqvPAsTv.js → TaskSettings-f2ga42_V.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-oOA9bcdK.js → WorkflowsModule-E4UL3mov.js} +1 -1
- package/dist/ui/assets/documentReferences-BOUcJm6-.js +1 -0
- package/dist/ui/assets/{index-CYyodXsg.css → index-BvAbqx5Q.css} +1 -1
- package/dist/ui/assets/{index-607WPo_X.js → index-MAHKvFqp.js} +4 -4
- package/dist/ui/index.html +2 -2
- package/dist/ui/og-image.png +0 -0
- package/package.json +1 -1
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +0 -1
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +0 -1
- 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
|
|
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
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
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);
|