@equilateral_ai/mindmeld 3.0.0

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 (86) hide show
  1. package/README.md +300 -0
  2. package/hooks/README.md +494 -0
  3. package/hooks/pre-compact.js +392 -0
  4. package/hooks/session-start.js +264 -0
  5. package/package.json +90 -0
  6. package/scripts/harvest.js +561 -0
  7. package/scripts/init-project.js +437 -0
  8. package/scripts/inject.js +388 -0
  9. package/src/collaboration/CollaborationPrompt.js +460 -0
  10. package/src/core/AlertEngine.js +813 -0
  11. package/src/core/AlertNotifier.js +363 -0
  12. package/src/core/CorrelationAnalyzer.js +774 -0
  13. package/src/core/CurationEngine.js +688 -0
  14. package/src/core/LLMPatternDetector.js +508 -0
  15. package/src/core/LoadBearingDetector.js +242 -0
  16. package/src/core/NotificationService.js +1032 -0
  17. package/src/core/PatternValidator.js +355 -0
  18. package/src/core/README.md +160 -0
  19. package/src/core/RapportOrchestrator.js +446 -0
  20. package/src/core/RelevanceDetector.js +577 -0
  21. package/src/core/StandardsIngestion.js +575 -0
  22. package/src/core/TeamLoadBearingDetector.js +431 -0
  23. package/src/database/dbOperations.js +105 -0
  24. package/src/handlers/activity/activityGetMe.js +98 -0
  25. package/src/handlers/activity/activityGetTeam.js +130 -0
  26. package/src/handlers/alerts/alertsAcknowledge.js +91 -0
  27. package/src/handlers/alerts/alertsGet.js +250 -0
  28. package/src/handlers/collaborators/collaboratorAdd.js +201 -0
  29. package/src/handlers/collaborators/collaboratorInvite.js +218 -0
  30. package/src/handlers/collaborators/collaboratorList.js +88 -0
  31. package/src/handlers/collaborators/collaboratorRemove.js +127 -0
  32. package/src/handlers/collaborators/inviteAccept.js +122 -0
  33. package/src/handlers/context/contextGet.js +57 -0
  34. package/src/handlers/context/invariantsGet.js +74 -0
  35. package/src/handlers/context/loopsGet.js +82 -0
  36. package/src/handlers/context/notesCreate.js +74 -0
  37. package/src/handlers/context/purposeGet.js +78 -0
  38. package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
  39. package/src/handlers/correlations/correlationsGet.js +93 -0
  40. package/src/handlers/correlations/correlationsProjectGet.js +161 -0
  41. package/src/handlers/github/githubConnectionStatus.js +49 -0
  42. package/src/handlers/github/githubDiscoverPatterns.js +364 -0
  43. package/src/handlers/github/githubOAuthCallback.js +166 -0
  44. package/src/handlers/github/githubOAuthStart.js +59 -0
  45. package/src/handlers/github/githubPatternsReview.js +109 -0
  46. package/src/handlers/github/githubReposList.js +105 -0
  47. package/src/handlers/helpers/checkSuperAdmin.js +85 -0
  48. package/src/handlers/helpers/dbOperations.js +53 -0
  49. package/src/handlers/helpers/errorHandler.js +49 -0
  50. package/src/handlers/helpers/index.js +106 -0
  51. package/src/handlers/helpers/lambdaWrapper.js +60 -0
  52. package/src/handlers/helpers/responseUtil.js +55 -0
  53. package/src/handlers/helpers/subscriptionTiers.js +1168 -0
  54. package/src/handlers/notifications/getPreferences.js +84 -0
  55. package/src/handlers/notifications/sendNotification.js +170 -0
  56. package/src/handlers/notifications/updatePreferences.js +316 -0
  57. package/src/handlers/patterns/patternUsagePost.js +182 -0
  58. package/src/handlers/patterns/patternViolationPost.js +185 -0
  59. package/src/handlers/projects/projectCreate.js +107 -0
  60. package/src/handlers/projects/projectDelete.js +82 -0
  61. package/src/handlers/projects/projectGet.js +95 -0
  62. package/src/handlers/projects/projectUpdate.js +118 -0
  63. package/src/handlers/reports/aiLeverage.js +206 -0
  64. package/src/handlers/reports/engineeringInvestment.js +132 -0
  65. package/src/handlers/reports/riskForecast.js +186 -0
  66. package/src/handlers/reports/standardsRoi.js +162 -0
  67. package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
  68. package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
  69. package/src/handlers/scheduled/generateAlerts.js +135 -0
  70. package/src/handlers/scheduled/refreshActivity.js +21 -0
  71. package/src/handlers/scheduled/scanCompliance.js +334 -0
  72. package/src/handlers/sessions/sessionEndPost.js +180 -0
  73. package/src/handlers/sessions/sessionStandardsPost.js +135 -0
  74. package/src/handlers/stripe/addonManagePost.js +240 -0
  75. package/src/handlers/stripe/billingPortalPost.js +93 -0
  76. package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
  77. package/src/handlers/stripe/seatsUpdatePost.js +185 -0
  78. package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
  79. package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
  80. package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
  81. package/src/handlers/stripe/webhookPost.js +454 -0
  82. package/src/handlers/users/cognitoPostConfirmation.js +150 -0
  83. package/src/handlers/users/userEntitlementsGet.js +89 -0
  84. package/src/handlers/users/userGet.js +114 -0
  85. package/src/handlers/webhooks/githubWebhook.js +223 -0
  86. package/src/index.js +969 -0
@@ -0,0 +1,1168 @@
1
+ /**
2
+ * Subscription Tiers Configuration
3
+ * MindMeld/Rapport v3
4
+ *
5
+ * Pricing Model (per user/month):
6
+ * - Free: $0 (basic invariants only, no standards)
7
+ * - Team: $49 (community contribution) / $99 (private)
8
+ * - Professional: $149 (community contribution) / $199 (private)
9
+ * - Enterprise: Contact sales
10
+ *
11
+ * Community Contribution:
12
+ * - Lower price tiers contribute learned patterns back to community
13
+ * - Private tiers keep all patterns within the organization
14
+ *
15
+ * Early Adopter Discount:
16
+ * - 80% off first year if subscribed within 30 days of launch
17
+ * - Coupon code: FOUNDER80 (auto-applied at checkout)
18
+ *
19
+ * Standards Hierarchy:
20
+ * - Free: NO standards (basic invariants only)
21
+ * - Team: Community standards (curated set, no picker - all enabled)
22
+ * - Professional: Full library with standards picker (user preferences)
23
+ * - Enterprise: Custom packs + full library + preferences
24
+ *
25
+ * Standards Packages:
26
+ * - Community Standards: Curated patterns from MindMeld community
27
+ * - Compliance Standards: SOC2/HIPAA/PCI patterns
28
+ * - AWS Best Practices: Well-Architected patterns
29
+ * - Custom Packs: Enterprise-specific curated standards
30
+ */
31
+
32
+ /**
33
+ * Early Adopter Discount Configuration
34
+ *
35
+ * Auto-applied at checkout for eligible users (no manual code entry needed)
36
+ * Code can still be shared for referral tracking
37
+ */
38
+ const EARLY_ADOPTER_DISCOUNT = {
39
+ enabled: true,
40
+ discountPercent: 80, // 80% off
41
+ eligibilityWindowDays: 30, // Must subscribe within 30 days of account creation
42
+ durationMonths: 12, // Discount applies for first 12 months
43
+ stripeCouponId: process.env.STRIPE_COUPON_FOUNDER || 'FOUNDER80',
44
+ autoApply: true, // Automatically apply at checkout if eligible
45
+ shareableCode: 'FOUNDER80', // For referral/sharing (same discount)
46
+ description: '80% off your first year - Founding Member Special',
47
+ displayMessage: 'FOUNDER80 applied - Founding Member discount!'
48
+ };
49
+
50
+ /**
51
+ * Standards packages available by tier
52
+ */
53
+ const STANDARDS_PACKAGES = {
54
+ community: {
55
+ name: 'Community Standards',
56
+ description: 'Curated patterns from the MindMeld community',
57
+ source: 'api:mindmeld.dev/standards/community',
58
+ categories: ['patterns', 'integrations', 'frameworks', 'development_principles', 'cost_optimization', 'api_design', 'testing'],
59
+ pickable: false // Team tier gets all, no picking
60
+ },
61
+ compliance: {
62
+ name: 'Compliance Standards',
63
+ description: 'SOC2, HIPAA, PCI-DSS compliance patterns',
64
+ source: 'api:mindmeld.dev/standards/compliance',
65
+ categories: ['soc2', 'hipaa', 'pci', 'gdpr', 'audit'],
66
+ pickable: true // Professional+ can pick which to enable
67
+ },
68
+ aws: {
69
+ name: 'AWS Best Practices',
70
+ description: 'Well-Architected Framework patterns',
71
+ source: 'api:mindmeld.dev/standards/aws',
72
+ categories: ['security', 'reliability', 'performance', 'cost', 'operations'],
73
+ pickable: true // Professional+ can pick which to enable
74
+ },
75
+ custom: {
76
+ name: 'Custom Packs',
77
+ description: 'Enterprise-specific curated standards packages',
78
+ source: 'api:mindmeld.dev/standards/custom',
79
+ categories: ['enterprise_custom'],
80
+ pickable: true // Enterprise can configure custom packs
81
+ }
82
+ };
83
+
84
+ const SUBSCRIPTION_TIERS = {
85
+ free: {
86
+ name: 'Free',
87
+ displayName: 'Solo Developer',
88
+ priceMonthly: 0,
89
+ priceAnnual: 0,
90
+ maxCollaborators: 1, // Just the user
91
+ maxProjects: 3,
92
+ maxInvariants: 10,
93
+ standards: [], // NO standards - basic invariants only
94
+ canPickStandards: false,
95
+ features: ['local_context', 'basic_invariants'],
96
+ stripeProductId: null,
97
+ stripePriceIdMonthly: null,
98
+ stripePriceIdAnnual: null
99
+ },
100
+ team: {
101
+ name: 'Team',
102
+ displayName: 'Pro Solo',
103
+ priceMonthly: 49, // Per user - $49/user/mo (community contribution)
104
+ priceAnnual: 490, // ~2 months free
105
+ perUser: true,
106
+ maxCollaborators: 5,
107
+ maxProjects: 5,
108
+ maxInvariants: 50,
109
+ standards: ['community'], // ~25 community-curated standards
110
+ canPickStandards: false, // No picker - all community standards enabled
111
+ communityContribution: true, // Patterns feed back to community
112
+ features: [
113
+ 'local_context',
114
+ 'shared_invariants',
115
+ 'cross_platform_sync',
116
+ 'team_memory',
117
+ 'community_standards',
118
+ 'community_feedback' // Patterns feed back to community repo
119
+ ],
120
+ stripeProductId: process.env.STRIPE_PRODUCT_TEAM || 'prod_Tn3KhkNBqHwfJ1',
121
+ stripePriceIdMonthly: process.env.STRIPE_PRICE_TEAM_MONTHLY || 'price_1SpTDmQoD4pT2xXuMG8yUqiz',
122
+ stripePriceIdAnnual: process.env.STRIPE_PRICE_TEAM_ANNUAL || 'price_1SpTDrQoD4pT2xXu5qU33axg'
123
+ },
124
+ team_private: {
125
+ name: 'Team Private',
126
+ displayName: 'Pro Solo (Private)',
127
+ priceMonthly: 99, // Per user - $99/user/mo (private standards)
128
+ priceAnnual: 990, // ~2 months free
129
+ perUser: true,
130
+ maxCollaborators: 5,
131
+ maxProjects: 5,
132
+ maxInvariants: 50,
133
+ standards: ['community'], // Access to community standards
134
+ canPickStandards: false,
135
+ communityContribution: false, // Patterns stay private
136
+ features: [
137
+ 'local_context',
138
+ 'shared_invariants',
139
+ 'cross_platform_sync',
140
+ 'team_memory',
141
+ 'community_standards',
142
+ 'private_patterns' // Patterns do NOT feed back to community
143
+ ],
144
+ stripeProductId: process.env.STRIPE_PRODUCT_TEAM_PRIVATE || 'prod_Tn3KEmFEbA3y2T',
145
+ stripePriceIdMonthly: process.env.STRIPE_PRICE_TEAM_PRIVATE_MONTHLY || 'price_1SpTE5QoD4pT2xXu0ry2yL7j',
146
+ stripePriceIdAnnual: process.env.STRIPE_PRICE_TEAM_PRIVATE_ANNUAL || 'price_1SpTE6QoD4pT2xXuCiDZ37Ge'
147
+ },
148
+ professional: {
149
+ name: 'Professional',
150
+ displayName: 'Growing Team',
151
+ priceMonthly: 149, // Per user - $149/user/mo (community contribution)
152
+ priceAnnual: 1490, // ~2 months free
153
+ perUser: true,
154
+ maxCollaborators: 10,
155
+ maxProjects: null, // Unlimited
156
+ maxInvariants: 200,
157
+ standards: ['community', 'compliance', 'aws'], // Full 100+ standards library
158
+ canPickStandards: true, // Can pick which standards to enable
159
+ communityContribution: true, // Patterns feed back to community
160
+ features: [
161
+ 'local_context',
162
+ 'shared_invariants',
163
+ 'cross_platform_sync',
164
+ 'team_memory',
165
+ 'priority_support',
166
+ 'analytics',
167
+ 'standards_picker',
168
+ 'community_standards',
169
+ 'compliance_standards',
170
+ 'aws_standards',
171
+ 'community_feedback'
172
+ ],
173
+ stripeProductId: process.env.STRIPE_PRODUCT_PROFESSIONAL || 'prod_Tn3Kz8u744njE8',
174
+ stripePriceIdMonthly: process.env.STRIPE_PRICE_PROFESSIONAL_MONTHLY || 'price_1SpTELQoD4pT2xXukzY5iodd',
175
+ stripePriceIdAnnual: process.env.STRIPE_PRICE_PROFESSIONAL_ANNUAL || 'price_1SpTELQoD4pT2xXuwoa3zAtA'
176
+ },
177
+ professional_private: {
178
+ name: 'Professional Private',
179
+ displayName: 'Growing Team (Private)',
180
+ priceMonthly: 199, // Per user - $199/user/mo (private standards)
181
+ priceAnnual: 1990, // ~2 months free
182
+ perUser: true,
183
+ maxCollaborators: 10,
184
+ maxProjects: null, // Unlimited
185
+ maxInvariants: 200,
186
+ standards: ['community', 'compliance', 'aws'], // Full 100+ standards library
187
+ canPickStandards: true, // Can pick which standards to enable
188
+ communityContribution: false, // Patterns stay private
189
+ features: [
190
+ 'local_context',
191
+ 'shared_invariants',
192
+ 'cross_platform_sync',
193
+ 'team_memory',
194
+ 'priority_support',
195
+ 'analytics',
196
+ 'standards_picker',
197
+ 'community_standards',
198
+ 'compliance_standards',
199
+ 'aws_standards',
200
+ 'private_patterns'
201
+ ],
202
+ stripeProductId: process.env.STRIPE_PRODUCT_PROFESSIONAL_PRIVATE || 'prod_Tn3LxewfKvzfNc',
203
+ stripePriceIdMonthly: process.env.STRIPE_PRICE_PROFESSIONAL_PRIVATE_MONTHLY || 'price_1SpTEaQoD4pT2xXujb9l87Su',
204
+ stripePriceIdAnnual: process.env.STRIPE_PRICE_PROFESSIONAL_PRIVATE_ANNUAL || 'price_1SpTEaQoD4pT2xXuxQx8CTIv'
205
+ },
206
+ enterprise: {
207
+ name: 'Enterprise',
208
+ displayName: 'Organization',
209
+ priceMonthly: null, // Contact sales
210
+ priceAnnual: null, // Contact sales
211
+ contactSales: true,
212
+ perUser: false,
213
+ maxCollaborators: null, // Unlimited
214
+ maxProjects: null, // Unlimited
215
+ maxInvariants: null, // Unlimited
216
+ standards: ['community', 'compliance', 'aws', 'custom'], // All standards + custom packs
217
+ canPickStandards: true, // Can pick which standards to enable + custom packs
218
+ features: [
219
+ 'local_context',
220
+ 'shared_invariants',
221
+ 'cross_platform_sync',
222
+ 'team_memory',
223
+ 'priority_support',
224
+ 'analytics',
225
+ 'standards_picker',
226
+ 'custom_packs',
227
+ 'community_standards',
228
+ 'compliance_standards',
229
+ 'aws_standards',
230
+ 'sso',
231
+ 'audit_trail',
232
+ 'knowledge_curation',
233
+ 'attribution_tracking',
234
+ 'dedicated_support',
235
+ 'custom_integrations'
236
+ ],
237
+ stripeProductId: process.env.STRIPE_PRODUCT_ENTERPRISE || 'prod_Tn3Lmdaa1yhQtD',
238
+ stripePriceIdMonthly: null, // Contact sales
239
+ stripePriceIdAnnual: null // Contact sales
240
+ }
241
+ };
242
+
243
+ /**
244
+ * Get tier configuration
245
+ * @param {string} tierName - Tier identifier
246
+ * @returns {object|null} Tier configuration
247
+ */
248
+ function getTierConfig(tierName) {
249
+ return SUBSCRIPTION_TIERS[tierName?.toLowerCase()] || null;
250
+ }
251
+
252
+ /**
253
+ * Check if tier has a feature
254
+ * @param {string} tierName - Tier identifier
255
+ * @param {string} feature - Feature name
256
+ * @returns {boolean}
257
+ */
258
+ function hasFeature(tierName, feature) {
259
+ const tier = getTierConfig(tierName);
260
+ return tier ? tier.features.includes(feature) : false;
261
+ }
262
+
263
+ /**
264
+ * Check subscription limits
265
+ * @param {object} client - Client record with subscription info
266
+ * @param {string} action - Action being performed (add_collaborator, create_project, etc.)
267
+ * @param {object} counts - Current counts { collaborators, projects, invariants }
268
+ * @returns {object} { allowed: boolean, message: string, limit: number }
269
+ */
270
+ function checkSubscriptionLimits(client, action, counts) {
271
+ const tier = getTierConfig(client.subscription_tier || 'free');
272
+ if (!tier) {
273
+ return { allowed: false, message: 'Invalid subscription tier', limit: 0 };
274
+ }
275
+
276
+ switch (action) {
277
+ case 'add_collaborator':
278
+ const collabLimit = tier.maxCollaborators;
279
+ if (counts.collaborators >= collabLimit) {
280
+ return {
281
+ allowed: false,
282
+ message: `${tier.displayName} tier allows up to ${collabLimit} collaborators. Upgrade to add more.`,
283
+ limit: collabLimit
284
+ };
285
+ }
286
+ break;
287
+
288
+ case 'create_project':
289
+ const projectLimit = tier.maxProjects;
290
+ if (counts.projects >= projectLimit) {
291
+ return {
292
+ allowed: false,
293
+ message: `${tier.displayName} tier allows up to ${projectLimit} projects. Upgrade to create more.`,
294
+ limit: projectLimit
295
+ };
296
+ }
297
+ break;
298
+
299
+ case 'add_invariant':
300
+ const invariantLimit = tier.maxInvariants;
301
+ if (invariantLimit !== null && counts.invariants >= invariantLimit) {
302
+ return {
303
+ allowed: false,
304
+ message: `${tier.displayName} tier allows up to ${invariantLimit} invariants. Upgrade for more.`,
305
+ limit: invariantLimit
306
+ };
307
+ }
308
+ break;
309
+ }
310
+
311
+ return { allowed: true, message: 'OK', limit: null };
312
+ }
313
+
314
+ /**
315
+ * Get Stripe price ID for tier and billing interval
316
+ * @param {string} tierName - Tier identifier
317
+ * @param {string} billingInterval - 'monthly' or 'annual'
318
+ * @returns {string|null} Stripe price ID
319
+ */
320
+ function getStripePriceId(tierName, billingInterval = 'monthly') {
321
+ const tier = getTierConfig(tierName);
322
+ if (!tier) return null;
323
+
324
+ return billingInterval === 'annual'
325
+ ? tier.stripePriceIdAnnual
326
+ : tier.stripePriceIdMonthly;
327
+ }
328
+
329
+ /**
330
+ * Get Stripe product ID for tier
331
+ * @param {string} tierName - Tier identifier
332
+ * @returns {string|null} Stripe product ID
333
+ */
334
+ function getStripeProductId(tierName) {
335
+ const tier = getTierConfig(tierName);
336
+ return tier ? tier.stripeProductId : null;
337
+ }
338
+
339
+ /**
340
+ * Get recommended upgrade for a limit reason
341
+ * @param {string} currentTier - Current tier name
342
+ * @param {string} reason - Reason for upgrade (collaborators, projects, features)
343
+ * @returns {object} { tier: string, name: string, price: number }
344
+ */
345
+ function getRecommendedUpgrade(currentTier, reason) {
346
+ const tierOrder = ['free', 'team', 'professional', 'enterprise'];
347
+ const currentIndex = tierOrder.indexOf(currentTier?.toLowerCase() || 'free');
348
+
349
+ // Recommend next tier up
350
+ const nextTier = tierOrder[Math.min(currentIndex + 1, tierOrder.length - 1)];
351
+ const tier = getTierConfig(nextTier);
352
+
353
+ return {
354
+ tier: nextTier,
355
+ name: tier.displayName,
356
+ price: tier.priceMonthly,
357
+ perUser: tier.perUser
358
+ };
359
+ }
360
+
361
+ /**
362
+ * Calculate monthly cost for tier based on user count
363
+ * @param {string} tierName - Tier identifier
364
+ * @param {number} userCount - Number of users
365
+ * @param {string} billingInterval - 'monthly' or 'annual'
366
+ * @returns {number} Total monthly cost
367
+ */
368
+ function calculateTierCost(tierName, userCount, billingInterval = 'monthly') {
369
+ const tier = getTierConfig(tierName);
370
+ if (!tier) return 0;
371
+
372
+ const price = billingInterval === 'annual'
373
+ ? tier.priceAnnual / 12
374
+ : tier.priceMonthly;
375
+
376
+ return tier.perUser ? price * userCount : price;
377
+ }
378
+
379
+ /**
380
+ * Get all tiers for display
381
+ * @returns {array} Array of tier configs with tier key
382
+ */
383
+ function getAllTiers() {
384
+ return Object.entries(SUBSCRIPTION_TIERS).map(([key, config]) => ({
385
+ id: key,
386
+ ...config
387
+ }));
388
+ }
389
+
390
+ /**
391
+ * Get standards packages available for a tier
392
+ * @param {string} tierName - Tier identifier
393
+ * @returns {array} Array of standards package configs
394
+ */
395
+ function getTierStandards(tierName) {
396
+ const tier = getTierConfig(tierName);
397
+ if (!tier) return []; // Default to no standards (free tier behavior)
398
+
399
+ return tier.standards.map(std => STANDARDS_PACKAGES[std]).filter(Boolean);
400
+ }
401
+
402
+ /**
403
+ * Check if tier has access to a standards package
404
+ * @param {string} tierName - Tier identifier
405
+ * @param {string} standardsPackage - Package name (community, compliance, aws, custom)
406
+ * @returns {boolean}
407
+ */
408
+ function hasStandardsAccess(tierName, standardsPackage) {
409
+ const tier = getTierConfig(tierName);
410
+ return tier ? tier.standards.includes(standardsPackage) : false;
411
+ }
412
+
413
+ /**
414
+ * Check if tier can pick/select specific standards
415
+ * Only professional and enterprise tiers can pick standards.
416
+ * Team tier gets curated set with no picking.
417
+ * Free tier gets no standards.
418
+ * @param {string} tierName - Tier identifier
419
+ * @returns {boolean}
420
+ */
421
+ function canPickStandards(tierName) {
422
+ const tier = getTierConfig(tierName);
423
+ return tier ? tier.canPickStandards === true : false;
424
+ }
425
+
426
+ /**
427
+ * Get all standards packages info
428
+ * @returns {object} STANDARDS_PACKAGES object
429
+ */
430
+ function getAllStandardsPackages() {
431
+ return STANDARDS_PACKAGES;
432
+ }
433
+
434
+ /**
435
+ * Get enabled standards for a user based on their tier and preferences
436
+ * @param {string} userId - User ID (email)
437
+ * @param {string} companyId - Company ID
438
+ * @param {string} tier - Subscription tier
439
+ * @param {object} options - Optional parameters
440
+ * @param {function} options.queryUserPreferences - Async function to query user standard preferences
441
+ * @param {function} options.queryCustomPack - Async function to query enterprise custom pack
442
+ * @returns {Promise<array>} Array of enabled standard IDs
443
+ */
444
+ async function getEnabledStandardsForUser(userId, companyId, tier, options = {}) {
445
+ const tierConfig = getTierConfig(tier);
446
+
447
+ // Free tier: NO standards
448
+ if (!tierConfig || tier === 'free') {
449
+ return [];
450
+ }
451
+
452
+ // Team tier: All community standards enabled (no picker)
453
+ if (tier === 'team') {
454
+ // Return all community standards - no preferences, all enabled
455
+ const communityPackage = STANDARDS_PACKAGES.community;
456
+ if (options.queryAllCommunityStandards) {
457
+ // Query all community standards from the database/API
458
+ return await options.queryAllCommunityStandards();
459
+ }
460
+ // Fallback: return package info indicating all community standards
461
+ return [{
462
+ package: 'community',
463
+ allEnabled: true,
464
+ categories: communityPackage.categories
465
+ }];
466
+ }
467
+
468
+ // Professional tier: Full library with picker (user preferences)
469
+ if (tier === 'professional') {
470
+ if (options.queryUserPreferences) {
471
+ // Query user's enabled standards preferences
472
+ const preferences = await options.queryUserPreferences(userId, companyId);
473
+ return preferences.enabledStandards || [];
474
+ }
475
+ // Fallback: return available packages with picker info
476
+ return tierConfig.standards.map(pkg => ({
477
+ package: pkg,
478
+ pickable: true,
479
+ enabled: [] // User needs to configure preferences
480
+ }));
481
+ }
482
+
483
+ // Enterprise tier: Custom packs + full library + preferences
484
+ if (tier === 'enterprise') {
485
+ const enabledStandards = [];
486
+
487
+ // Get custom pack if configured
488
+ if (options.queryCustomPack) {
489
+ const customPack = await options.queryCustomPack(companyId);
490
+ if (customPack && customPack.standards) {
491
+ enabledStandards.push({
492
+ package: 'custom',
493
+ standards: customPack.standards,
494
+ packName: customPack.name
495
+ });
496
+ }
497
+ }
498
+
499
+ // Get user preferences for standard library
500
+ if (options.queryUserPreferences) {
501
+ const preferences = await options.queryUserPreferences(userId, companyId);
502
+ if (preferences.enabledStandards) {
503
+ enabledStandards.push({
504
+ package: 'library',
505
+ standards: preferences.enabledStandards
506
+ });
507
+ }
508
+ }
509
+
510
+ // Fallback: return available packages with picker info
511
+ if (enabledStandards.length === 0) {
512
+ return tierConfig.standards.map(pkg => ({
513
+ package: pkg,
514
+ pickable: true,
515
+ enabled: []
516
+ }));
517
+ }
518
+
519
+ return enabledStandards;
520
+ }
521
+
522
+ // Unknown tier: return empty
523
+ return [];
524
+ }
525
+
526
+ /**
527
+ * Normalize tier name (handles legacy 'starter' -> 'team' mapping)
528
+ * @param {string} tierName - Tier identifier (may be legacy)
529
+ * @returns {string} Normalized tier name
530
+ */
531
+ function normalizeTierName(tierName) {
532
+ const normalized = tierName?.toLowerCase();
533
+ // Handle legacy tier names
534
+ if (normalized === 'starter') {
535
+ return 'team';
536
+ }
537
+ return normalized || 'free';
538
+ }
539
+
540
+ /**
541
+ * Check if user is eligible for early adopter discount
542
+ * @param {Date} accountCreatedAt - When user account was created
543
+ * @returns {object} { eligible: boolean, daysRemaining: number, discount: object }
544
+ */
545
+ function checkEarlyAdopterEligibility(accountCreatedAt) {
546
+ if (!EARLY_ADOPTER_DISCOUNT.enabled) {
547
+ return { eligible: false, daysRemaining: 0, discount: null };
548
+ }
549
+
550
+ const createdDate = new Date(accountCreatedAt);
551
+ const now = new Date();
552
+ const daysSinceCreation = Math.floor((now - createdDate) / (1000 * 60 * 60 * 24));
553
+ const daysRemaining = Math.max(0, EARLY_ADOPTER_DISCOUNT.eligibilityWindowDays - daysSinceCreation);
554
+
555
+ return {
556
+ eligible: daysSinceCreation <= EARLY_ADOPTER_DISCOUNT.eligibilityWindowDays,
557
+ daysRemaining,
558
+ daysSinceCreation,
559
+ discount: EARLY_ADOPTER_DISCOUNT
560
+ };
561
+ }
562
+
563
+ /**
564
+ * Get early adopter discount config
565
+ * @returns {object} EARLY_ADOPTER_DISCOUNT config
566
+ */
567
+ function getEarlyAdopterDiscount() {
568
+ return EARLY_ADOPTER_DISCOUNT;
569
+ }
570
+
571
+ /**
572
+ * Calculate discounted price for early adopter
573
+ * @param {string} tierName - Tier identifier
574
+ * @param {number} userCount - Number of users
575
+ * @param {string} billingInterval - 'monthly' or 'annual'
576
+ * @returns {object} { originalPrice, discountedPrice, savings, discountPercent }
577
+ */
578
+ function calculateEarlyAdopterPrice(tierName, userCount, billingInterval = 'monthly') {
579
+ const originalPrice = calculateTierCost(tierName, userCount, billingInterval);
580
+ const discountedPrice = originalPrice * (1 - EARLY_ADOPTER_DISCOUNT.discountPercent / 100);
581
+
582
+ return {
583
+ originalPrice,
584
+ discountedPrice: Math.round(discountedPrice * 100) / 100,
585
+ savings: Math.round((originalPrice - discountedPrice) * 100) / 100,
586
+ discountPercent: EARLY_ADOPTER_DISCOUNT.discountPercent,
587
+ discountDuration: `${EARLY_ADOPTER_DISCOUNT.durationMonths} months`
588
+ };
589
+ }
590
+
591
+ /**
592
+ * Check if tier requires community contribution (lower price tier)
593
+ * @param {string} tierName - Tier identifier
594
+ * @returns {boolean}
595
+ */
596
+ function requiresCommunityContribution(tierName) {
597
+ const tier = getTierConfig(tierName);
598
+ return tier ? tier.communityContribution === true : false;
599
+ }
600
+
601
+ /**
602
+ * Get the private variant of a tier
603
+ * @param {string} tierName - Base tier name (e.g., 'team')
604
+ * @returns {string|null} Private tier name or null
605
+ */
606
+ function getPrivateTierVariant(tierName) {
607
+ const privateTier = `${tierName}_private`;
608
+ return getTierConfig(privateTier) ? privateTier : null;
609
+ }
610
+
611
+ /**
612
+ * Get base tier from private variant
613
+ * @param {string} tierName - Tier name (may be private variant)
614
+ * @returns {string} Base tier name
615
+ */
616
+ function getBaseTier(tierName) {
617
+ return tierName?.replace('_private', '') || tierName;
618
+ }
619
+
620
+ // ==============================================
621
+ // BILLING TYPE FUNCTIONS
622
+ // ==============================================
623
+
624
+ /**
625
+ * Valid billing types
626
+ * - stripe: Standard credit card billing via Stripe
627
+ * - invoice: Enterprise invoice billing (billable_users tracked)
628
+ * - internal: Internal/free clients (no billing)
629
+ */
630
+ const BILLING_TYPES = {
631
+ STRIPE: 'stripe',
632
+ INVOICE: 'invoice',
633
+ INTERNAL: 'internal'
634
+ };
635
+
636
+ /**
637
+ * Check if client uses Stripe billing
638
+ * @param {object} client - Client record
639
+ * @returns {boolean}
640
+ */
641
+ function usesStripeBilling(client) {
642
+ return (client.billing_type || 'stripe') === BILLING_TYPES.STRIPE;
643
+ }
644
+
645
+ /**
646
+ * Check if client is enterprise invoice billing
647
+ * @param {object} client - Client record
648
+ * @returns {boolean}
649
+ */
650
+ function usesInvoiceBilling(client) {
651
+ return client.billing_type === BILLING_TYPES.INVOICE;
652
+ }
653
+
654
+ /**
655
+ * Check if client is internal (free)
656
+ * @param {object} client - Client record
657
+ * @returns {boolean}
658
+ */
659
+ function isInternalClient(client) {
660
+ return client.billing_type === BILLING_TYPES.INTERNAL;
661
+ }
662
+
663
+ /**
664
+ * Check collaborator limits based on billing type
665
+ * @param {object} client - Client record with billing_type and subscription_tier
666
+ * @param {number} currentCollaboratorCount - Current number of collaborators
667
+ * @returns {object} { allowed: boolean, message: string, billingAction: string|null }
668
+ */
669
+ function checkCollaboratorBillingLimits(client, currentCollaboratorCount) {
670
+ const billingType = client.billing_type || 'stripe';
671
+
672
+ switch (billingType) {
673
+ case BILLING_TYPES.INTERNAL:
674
+ // Internal clients have no limits
675
+ return {
676
+ allowed: true,
677
+ message: 'OK',
678
+ billingAction: null,
679
+ unlimited: true
680
+ };
681
+
682
+ case BILLING_TYPES.INVOICE:
683
+ // Enterprise invoice clients - no hard limits, just increment billable users
684
+ return {
685
+ allowed: true,
686
+ message: 'OK',
687
+ billingAction: 'increment_billable_users',
688
+ unlimited: true
689
+ };
690
+
691
+ case BILLING_TYPES.STRIPE:
692
+ default:
693
+ // Standard Stripe billing - use subscription tier limits
694
+ const limitCheck = checkSubscriptionLimits(
695
+ client,
696
+ 'add_collaborator',
697
+ { collaborators: currentCollaboratorCount }
698
+ );
699
+ return {
700
+ ...limitCheck,
701
+ billingAction: limitCheck.allowed ? null : 'upgrade_required'
702
+ };
703
+ }
704
+ }
705
+
706
+ /**
707
+ * Get billing type display name
708
+ * @param {string} billingType - Billing type code
709
+ * @returns {string} Human-readable name
710
+ */
711
+ function getBillingTypeDisplayName(billingType) {
712
+ switch (billingType) {
713
+ case BILLING_TYPES.INVOICE:
714
+ return 'Enterprise Invoice';
715
+ case BILLING_TYPES.INTERNAL:
716
+ return 'Internal (Free)';
717
+ case BILLING_TYPES.STRIPE:
718
+ default:
719
+ return 'Credit Card (Stripe)';
720
+ }
721
+ }
722
+
723
+ // ==============================================
724
+ // ENTERPRISE PACKAGES & ADD-ONS
725
+ // ==============================================
726
+
727
+ /**
728
+ * Enterprise package configurations
729
+ * Sold in seat packs of 25 minimum
730
+ */
731
+ const ENTERPRISE_PACKAGES = {
732
+ base: {
733
+ name: 'Pro+ Base',
734
+ displayName: 'Enterprise Pro+ Base',
735
+ pricePerSeat: 179,
736
+ minSeats: 25,
737
+ seatIncrement: 25,
738
+ features: [
739
+ 'sso',
740
+ 'audit_trail',
741
+ 'dedicated_support',
742
+ '99_9_sla',
743
+ 'admin_dashboard',
744
+ 'all_professional_features'
745
+ ],
746
+ stripeProductId: process.env.STRIPE_PRODUCT_ENTERPRISE || 'prod_Tn3Lmdaa1yhQtD',
747
+ stripePriceIdMonthly25: process.env.STRIPE_PRICE_ENT_BASE_MONTHLY || 'price_1SrXDCQoD4pT2xXuS8RPpS9X',
748
+ stripePriceIdAnnual25: process.env.STRIPE_PRICE_ENT_BASE_ANNUAL || 'price_1SrXDCQoD4pT2xXukOTjeUpv'
749
+ },
750
+ full: {
751
+ name: 'Full Platform',
752
+ displayName: 'Enterprise Full Platform',
753
+ pricePerSeat: 219,
754
+ minSeats: 25,
755
+ seatIncrement: 25,
756
+ includes: ['base', 'engineering_intelligence', 'knowledge_continuity'],
757
+ features: [
758
+ 'sso',
759
+ 'audit_trail',
760
+ 'dedicated_support',
761
+ '99_9_sla',
762
+ 'admin_dashboard',
763
+ 'all_professional_features',
764
+ // Engineering Intelligence features
765
+ 'activity_dashboards',
766
+ 'attention_alerts',
767
+ 'session_commit_correlation',
768
+ 'roi_reporting',
769
+ // Knowledge Continuity features
770
+ 'knowledge_capture',
771
+ 'turnover_protection',
772
+ 'onboarding_acceleration',
773
+ 'knowledge_transfer_docs'
774
+ ],
775
+ stripeProductId: process.env.STRIPE_PRODUCT_ENTERPRISE || 'prod_Tn3Lmdaa1yhQtD',
776
+ stripePriceIdMonthly25: process.env.STRIPE_PRICE_ENT_FULL_MONTHLY || 'price_1SrXDCQoD4pT2xXuBKUuXRsT',
777
+ stripePriceIdAnnual25: process.env.STRIPE_PRICE_ENT_FULL_ANNUAL || 'price_1SrXDDQoD4pT2xXuIeWZ0VDa'
778
+ }
779
+ };
780
+
781
+ /**
782
+ * Enterprise add-on modules
783
+ * Can be added to 'base' package (already included in 'full')
784
+ */
785
+ const ENTERPRISE_ADDONS = {
786
+ engineering_intelligence: {
787
+ id: 'engineering_intelligence',
788
+ name: 'Engineering Intelligence',
789
+ displayName: 'Engineering Intelligence Add-on',
790
+ description: 'Activity dashboards, attention alerts, session-to-commit correlation, ROI reporting',
791
+ pricePerSeat: 29,
792
+ minSeats: 25,
793
+ requires: ['enterprise_base'],
794
+ features: [
795
+ 'activity_dashboards',
796
+ 'attention_alerts',
797
+ 'session_commit_correlation',
798
+ 'roi_reporting'
799
+ ],
800
+ stripeProductId: process.env.STRIPE_PRODUCT_ADDON_ENG_INTEL || 'prod_TpBat7IZySP7MB',
801
+ stripePriceIdMonthly25: process.env.STRIPE_PRICE_ADDON_ENG_INTEL_MONTHLY || 'price_1SrXDXQoD4pT2xXuk4bDjKm8',
802
+ stripePriceIdAnnual25: process.env.STRIPE_PRICE_ADDON_ENG_INTEL_ANNUAL || 'price_1SrXDYQoD4pT2xXuD3EdKBs1'
803
+ },
804
+ knowledge_continuity: {
805
+ id: 'knowledge_continuity',
806
+ name: 'Knowledge Continuity',
807
+ displayName: 'Knowledge Continuity Add-on',
808
+ description: 'Key employee capture, turnover protection, onboarding acceleration, knowledge transfer docs',
809
+ pricePerSeat: 49,
810
+ minSeats: 25,
811
+ requires: ['enterprise_base'],
812
+ features: [
813
+ 'knowledge_capture',
814
+ 'turnover_protection',
815
+ 'onboarding_acceleration',
816
+ 'knowledge_transfer_docs'
817
+ ],
818
+ stripeProductId: process.env.STRIPE_PRODUCT_ADDON_KNOWLEDGE || 'prod_TpBaOkMV4fsctv',
819
+ stripePriceIdMonthly25: process.env.STRIPE_PRICE_ADDON_KNOWLEDGE_MONTHLY || 'price_1SrXDYQoD4pT2xXuFYZy90r0',
820
+ stripePriceIdAnnual25: process.env.STRIPE_PRICE_ADDON_KNOWLEDGE_ANNUAL || 'price_1SrXDYQoD4pT2xXuxULSElxH'
821
+ }
822
+ };
823
+
824
+ /**
825
+ * Volume discount tiers for enterprise
826
+ * Applied based on total seat count
827
+ */
828
+ const VOLUME_DISCOUNTS = [
829
+ { minSeats: 150, discount: 0.15, label: '15% off' },
830
+ { minSeats: 100, discount: 0.12, label: '12% off' },
831
+ { minSeats: 75, discount: 0.08, label: '8% off' },
832
+ { minSeats: 50, discount: 0.05, label: '5% off' },
833
+ { minSeats: 25, discount: 0, label: 'Standard pricing' }
834
+ ];
835
+
836
+ /**
837
+ * Get volume discount for seat count
838
+ * @param {number} seatCount - Number of seats
839
+ * @returns {object} { discount: number, label: string }
840
+ */
841
+ function getVolumeDiscount(seatCount) {
842
+ for (const tier of VOLUME_DISCOUNTS) {
843
+ if (seatCount >= tier.minSeats) {
844
+ return { discount: tier.discount, label: tier.label };
845
+ }
846
+ }
847
+ return { discount: 0, label: 'Standard pricing' };
848
+ }
849
+
850
+ /**
851
+ * Validate enterprise seat count
852
+ * Must be >= 25 and multiple of 25
853
+ * @param {number} seatCount - Requested seat count
854
+ * @returns {object} { valid: boolean, message: string, correctedCount: number }
855
+ */
856
+ function validateSeatCount(seatCount) {
857
+ const minSeats = 25;
858
+ const increment = 25;
859
+
860
+ if (!seatCount || seatCount < minSeats) {
861
+ return {
862
+ valid: false,
863
+ message: `Enterprise requires minimum ${minSeats} seats`,
864
+ correctedCount: minSeats
865
+ };
866
+ }
867
+
868
+ if (seatCount % increment !== 0) {
869
+ const corrected = Math.ceil(seatCount / increment) * increment;
870
+ return {
871
+ valid: false,
872
+ message: `Seats must be in increments of ${increment}. Rounded up to ${corrected}.`,
873
+ correctedCount: corrected
874
+ };
875
+ }
876
+
877
+ return {
878
+ valid: true,
879
+ message: 'OK',
880
+ correctedCount: seatCount
881
+ };
882
+ }
883
+
884
+ /**
885
+ * Calculate enterprise subscription cost
886
+ * @param {string} packageType - 'base' or 'full'
887
+ * @param {number} seatCount - Number of seats (must be multiple of 25)
888
+ * @param {string[]} addons - Array of addon IDs (only for 'base' package)
889
+ * @param {string} billingInterval - 'monthly' or 'annual'
890
+ * @returns {object} { subtotal, discount, discountAmount, total, breakdown }
891
+ */
892
+ function calculateEnterpriseCost(packageType, seatCount, addons = [], billingInterval = 'monthly') {
893
+ const pkg = ENTERPRISE_PACKAGES[packageType];
894
+ if (!pkg) {
895
+ throw new Error(`Invalid enterprise package: ${packageType}`);
896
+ }
897
+
898
+ const volumeDiscount = getVolumeDiscount(seatCount);
899
+ const breakdown = [];
900
+
901
+ // Base package cost
902
+ let subtotal = pkg.pricePerSeat * seatCount;
903
+ breakdown.push({
904
+ item: pkg.name,
905
+ pricePerSeat: pkg.pricePerSeat,
906
+ seats: seatCount,
907
+ amount: pkg.pricePerSeat * seatCount
908
+ });
909
+
910
+ // Add-on costs (only for 'base' package, 'full' already includes everything)
911
+ if (packageType === 'base' && addons.length > 0) {
912
+ for (const addonId of addons) {
913
+ const addon = ENTERPRISE_ADDONS[addonId];
914
+ if (addon) {
915
+ const addonCost = addon.pricePerSeat * seatCount;
916
+ subtotal += addonCost;
917
+ breakdown.push({
918
+ item: addon.name,
919
+ pricePerSeat: addon.pricePerSeat,
920
+ seats: seatCount,
921
+ amount: addonCost
922
+ });
923
+ }
924
+ }
925
+ }
926
+
927
+ // Apply volume discount
928
+ const discountAmount = subtotal * volumeDiscount.discount;
929
+ let total = subtotal - discountAmount;
930
+
931
+ // Annual pricing (2 months free = 10 months for price of 12)
932
+ if (billingInterval === 'annual') {
933
+ total = total * 10; // 10 months worth
934
+ return {
935
+ subtotal: subtotal * 12,
936
+ discount: volumeDiscount.discount,
937
+ discountLabel: volumeDiscount.label,
938
+ discountAmount: discountAmount * 12,
939
+ annualDiscount: subtotal * 2, // 2 months free
940
+ total,
941
+ monthlyEquivalent: total / 12,
942
+ breakdown,
943
+ billingInterval: 'annual'
944
+ };
945
+ }
946
+
947
+ return {
948
+ subtotal,
949
+ discount: volumeDiscount.discount,
950
+ discountLabel: volumeDiscount.label,
951
+ discountAmount,
952
+ total,
953
+ breakdown,
954
+ billingInterval: 'monthly'
955
+ };
956
+ }
957
+
958
+ /**
959
+ * Get enterprise package configuration
960
+ * @param {string} packageType - 'base' or 'full'
961
+ * @returns {object|null} Package configuration
962
+ */
963
+ function getEnterprisePackage(packageType) {
964
+ return ENTERPRISE_PACKAGES[packageType] || null;
965
+ }
966
+
967
+ /**
968
+ * Get enterprise add-on configuration
969
+ * @param {string} addonId - Add-on identifier
970
+ * @returns {object|null} Add-on configuration
971
+ */
972
+ function getEnterpriseAddon(addonId) {
973
+ return ENTERPRISE_ADDONS[addonId] || null;
974
+ }
975
+
976
+ /**
977
+ * Get all enterprise add-ons
978
+ * @returns {object} ENTERPRISE_ADDONS object
979
+ */
980
+ function getAllEnterpriseAddons() {
981
+ return ENTERPRISE_ADDONS;
982
+ }
983
+
984
+ /**
985
+ * Check if client has enterprise feature access
986
+ * @param {object} client - Client record with enterprise_package and subscribed_addons
987
+ * @param {string} feature - Feature to check
988
+ * @returns {boolean}
989
+ */
990
+ function hasEnterpriseFeature(client, feature) {
991
+ if (client.subscription_tier !== 'enterprise') {
992
+ return false;
993
+ }
994
+
995
+ const pkg = ENTERPRISE_PACKAGES[client.enterprise_package];
996
+ if (!pkg) {
997
+ return false;
998
+ }
999
+
1000
+ // Check if feature is in package
1001
+ if (pkg.features.includes(feature)) {
1002
+ return true;
1003
+ }
1004
+
1005
+ // Check add-ons
1006
+ const addons = client.subscribed_addons || [];
1007
+ for (const addonId of addons) {
1008
+ const addon = ENTERPRISE_ADDONS[addonId];
1009
+ if (addon && addon.features.includes(feature)) {
1010
+ return true;
1011
+ }
1012
+ }
1013
+
1014
+ return false;
1015
+ }
1016
+
1017
+ /**
1018
+ * Get all features available to enterprise client
1019
+ * @param {object} client - Client record
1020
+ * @returns {string[]} Array of available feature IDs
1021
+ */
1022
+ function getEnterpriseFeatures(client) {
1023
+ if (client.subscription_tier !== 'enterprise') {
1024
+ return [];
1025
+ }
1026
+
1027
+ const pkg = ENTERPRISE_PACKAGES[client.enterprise_package];
1028
+ if (!pkg) {
1029
+ return [];
1030
+ }
1031
+
1032
+ const features = new Set(pkg.features);
1033
+
1034
+ // Add features from subscribed add-ons
1035
+ const addons = client.subscribed_addons || [];
1036
+ for (const addonId of addons) {
1037
+ const addon = ENTERPRISE_ADDONS[addonId];
1038
+ if (addon) {
1039
+ addon.features.forEach(f => features.add(f));
1040
+ }
1041
+ }
1042
+
1043
+ return Array.from(features);
1044
+ }
1045
+
1046
+ /**
1047
+ * Check enterprise seat limits
1048
+ * @param {object} client - Client record with seat_count
1049
+ * @param {number} currentUserCount - Current number of users
1050
+ * @returns {object} { allowed, message, seatsAvailable, billingAction }
1051
+ */
1052
+ function checkEnterpriseSeatLimits(client, currentUserCount) {
1053
+ if (client.subscription_tier !== 'enterprise') {
1054
+ return { allowed: true, message: 'Not enterprise tier' };
1055
+ }
1056
+
1057
+ const seatCount = client.seat_count || 25;
1058
+ const seatsAvailable = seatCount - currentUserCount;
1059
+
1060
+ if (currentUserCount >= seatCount) {
1061
+ return {
1062
+ allowed: false,
1063
+ message: `All ${seatCount} enterprise seats are in use. Purchase additional seats to add more users.`,
1064
+ seatsAvailable: 0,
1065
+ seatsUsed: currentUserCount,
1066
+ seatCount,
1067
+ billingAction: 'purchase_seats'
1068
+ };
1069
+ }
1070
+
1071
+ return {
1072
+ allowed: true,
1073
+ message: 'OK',
1074
+ seatsAvailable,
1075
+ seatsUsed: currentUserCount,
1076
+ seatCount,
1077
+ billingAction: null
1078
+ };
1079
+ }
1080
+
1081
+ /**
1082
+ * Get Stripe price ID for enterprise package
1083
+ * @param {string} packageType - 'base' or 'full'
1084
+ * @param {string} billingInterval - 'monthly' or 'annual'
1085
+ * @returns {string|null} Stripe price ID
1086
+ */
1087
+ function getEnterprisePriceId(packageType, billingInterval = 'monthly') {
1088
+ const pkg = ENTERPRISE_PACKAGES[packageType];
1089
+ if (!pkg) return null;
1090
+
1091
+ return billingInterval === 'annual'
1092
+ ? pkg.stripePriceIdAnnual25
1093
+ : pkg.stripePriceIdMonthly25;
1094
+ }
1095
+
1096
+ /**
1097
+ * Get Stripe price ID for enterprise add-on
1098
+ * @param {string} addonId - Add-on identifier
1099
+ * @param {string} billingInterval - 'monthly' or 'annual'
1100
+ * @returns {string|null} Stripe price ID
1101
+ */
1102
+ function getAddonPriceId(addonId, billingInterval = 'monthly') {
1103
+ const addon = ENTERPRISE_ADDONS[addonId];
1104
+ if (!addon) return null;
1105
+
1106
+ return billingInterval === 'annual'
1107
+ ? addon.stripePriceIdAnnual25
1108
+ : addon.stripePriceIdMonthly25;
1109
+ }
1110
+
1111
+ module.exports = {
1112
+ // Constants
1113
+ SUBSCRIPTION_TIERS,
1114
+ STANDARDS_PACKAGES,
1115
+ EARLY_ADOPTER_DISCOUNT,
1116
+ ENTERPRISE_PACKAGES,
1117
+ ENTERPRISE_ADDONS,
1118
+ VOLUME_DISCOUNTS,
1119
+
1120
+ // Tier functions
1121
+ getTierConfig,
1122
+ hasFeature,
1123
+ checkSubscriptionLimits,
1124
+ getStripePriceId,
1125
+ getStripeProductId,
1126
+ getRecommendedUpgrade,
1127
+ calculateTierCost,
1128
+ getAllTiers,
1129
+ normalizeTierName,
1130
+
1131
+ // Standards functions
1132
+ getTierStandards,
1133
+ hasStandardsAccess,
1134
+ getAllStandardsPackages,
1135
+ canPickStandards,
1136
+ getEnabledStandardsForUser,
1137
+
1138
+ // Community vs Private functions
1139
+ requiresCommunityContribution,
1140
+ getPrivateTierVariant,
1141
+ getBaseTier,
1142
+
1143
+ // Early adopter discount functions
1144
+ checkEarlyAdopterEligibility,
1145
+ getEarlyAdopterDiscount,
1146
+ calculateEarlyAdopterPrice,
1147
+
1148
+ // Billing type functions
1149
+ BILLING_TYPES,
1150
+ usesStripeBilling,
1151
+ usesInvoiceBilling,
1152
+ isInternalClient,
1153
+ checkCollaboratorBillingLimits,
1154
+ getBillingTypeDisplayName,
1155
+
1156
+ // Enterprise functions
1157
+ getVolumeDiscount,
1158
+ validateSeatCount,
1159
+ calculateEnterpriseCost,
1160
+ getEnterprisePackage,
1161
+ getEnterpriseAddon,
1162
+ getAllEnterpriseAddons,
1163
+ hasEnterpriseFeature,
1164
+ getEnterpriseFeatures,
1165
+ checkEnterpriseSeatLimits,
1166
+ getEnterprisePriceId,
1167
+ getAddonPriceId
1168
+ };