@equilateral_ai/mindmeld 3.5.2 → 4.0.1

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 (140) hide show
  1. package/hooks/session-end.js +25 -0
  2. package/hooks/session-start.js +363 -83
  3. package/hooks/session-watcher.js +585 -0
  4. package/package.json +19 -13
  5. package/scripts/init-project.js +9 -23
  6. package/src/client/dbShim.js +16 -0
  7. package/src/core/AuthManager.js +3 -2
  8. package/src/handlers/helpers/dbOperations.js +9 -46
  9. package/src/index.js +2 -217
  10. package/src/utils/piiMask.js +16 -0
  11. package/scripts/harvest.js +0 -601
  12. package/scripts/inject.js +0 -409
  13. package/scripts/mcp-bridge.js +0 -220
  14. package/scripts/repo-analyzer.js +0 -870
  15. package/src/collaboration/CollaborationPrompt.js +0 -460
  16. package/src/core/AlertEngine.js +0 -813
  17. package/src/core/AlertNotifier.js +0 -363
  18. package/src/core/CorrelationAnalyzer.js +0 -931
  19. package/src/core/CrossReferenceEngine.js +0 -624
  20. package/src/core/CurationEngine.js +0 -688
  21. package/src/core/DeprecationScheduler.js +0 -183
  22. package/src/core/LoadBearingDetector.js +0 -242
  23. package/src/core/NotificationService.js +0 -1032
  24. package/src/core/RapportOrchestrator.js +0 -632
  25. package/src/core/RelevanceDetector.js +0 -694
  26. package/src/core/StandardLifecycle.js +0 -244
  27. package/src/core/StandardsIngestion.js +0 -991
  28. package/src/core/TeamLoadBearingDetector.js +0 -431
  29. package/src/core/parsers/adrParser.js +0 -479
  30. package/src/core/parsers/cursorRulesParser.js +0 -564
  31. package/src/core/parsers/eslintParser.js +0 -439
  32. package/src/database/dbOperations.js +0 -105
  33. package/src/handlers/activity/activityGetMe.js +0 -98
  34. package/src/handlers/activity/activityGetTeam.js +0 -175
  35. package/src/handlers/admin/adminSetup.js +0 -216
  36. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  37. package/src/handlers/alerts/alertsGet.js +0 -250
  38. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  39. package/src/handlers/analytics/coachingGet.js +0 -361
  40. package/src/handlers/analytics/convergenceGet.js +0 -236
  41. package/src/handlers/analytics/developerScoreGet.js +0 -137
  42. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  43. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  44. package/src/handlers/collaborators/collaboratorList.js +0 -82
  45. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  46. package/src/handlers/collaborators/inviteAccept.js +0 -122
  47. package/src/handlers/company/companyUsersDelete.js +0 -141
  48. package/src/handlers/company/companyUsersGet.js +0 -90
  49. package/src/handlers/company/companyUsersPost.js +0 -267
  50. package/src/handlers/company/companyUsersPut.js +0 -76
  51. package/src/handlers/context/contextGet.js +0 -57
  52. package/src/handlers/context/invariantsGet.js +0 -74
  53. package/src/handlers/context/loopsGet.js +0 -82
  54. package/src/handlers/context/notesCreate.js +0 -74
  55. package/src/handlers/context/purposeGet.js +0 -78
  56. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  57. package/src/handlers/correlations/correlationsGet.js +0 -93
  58. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  59. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  60. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  61. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  62. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  63. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  64. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  65. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  66. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  67. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  68. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  69. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  70. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  71. package/src/handlers/github/githubConnectionStatus.js +0 -49
  72. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  73. package/src/handlers/github/githubOAuthCallback.js +0 -178
  74. package/src/handlers/github/githubOAuthStart.js +0 -59
  75. package/src/handlers/github/githubPatternsReview.js +0 -76
  76. package/src/handlers/github/githubReposList.js +0 -105
  77. package/src/handlers/health/healthGet.js +0 -55
  78. package/src/handlers/helpers/auditLogger.js +0 -201
  79. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  80. package/src/handlers/helpers/decisionFrames.js +0 -29
  81. package/src/handlers/helpers/errorHandler.js +0 -49
  82. package/src/handlers/helpers/index.js +0 -138
  83. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  84. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  85. package/src/handlers/helpers/predictiveCache.js +0 -51
  86. package/src/handlers/helpers/projectAccess.js +0 -88
  87. package/src/handlers/helpers/responseUtil.js +0 -55
  88. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  89. package/src/handlers/mcp/mcpHandler.js +0 -569
  90. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  91. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  92. package/src/handlers/notifications/getPreferences.js +0 -84
  93. package/src/handlers/notifications/sendNotification.js +0 -170
  94. package/src/handlers/notifications/updatePreferences.js +0 -316
  95. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  96. package/src/handlers/patterns/patternUsagePost.js +0 -182
  97. package/src/handlers/patterns/patternViolationPost.js +0 -185
  98. package/src/handlers/projects/projectCreate.js +0 -248
  99. package/src/handlers/projects/projectDelete.js +0 -82
  100. package/src/handlers/projects/projectGet.js +0 -95
  101. package/src/handlers/projects/projectUpdate.js +0 -117
  102. package/src/handlers/reports/aiLeverage.js +0 -210
  103. package/src/handlers/reports/engineeringInvestment.js +0 -132
  104. package/src/handlers/reports/riskForecast.js +0 -206
  105. package/src/handlers/reports/standardsRoi.js +0 -254
  106. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  107. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  108. package/src/handlers/scheduled/generateAlerts.js +0 -135
  109. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  110. package/src/handlers/scheduled/refreshActivity.js +0 -21
  111. package/src/handlers/scheduled/scanCompliance.js +0 -334
  112. package/src/handlers/sessions/sessionEndPost.js +0 -180
  113. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  114. package/src/handlers/standards/catalogGet.js +0 -185
  115. package/src/handlers/standards/catalogSync.js +0 -120
  116. package/src/handlers/standards/discoveriesGet.js +0 -89
  117. package/src/handlers/standards/projectStandardsGet.js +0 -129
  118. package/src/handlers/standards/projectStandardsPut.js +0 -151
  119. package/src/handlers/standards/standardsAuditGet.js +0 -65
  120. package/src/handlers/standards/standardsParseUpload.js +0 -149
  121. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  122. package/src/handlers/standards/standardsTransition.js +0 -161
  123. package/src/handlers/stripe/addonManagePost.js +0 -240
  124. package/src/handlers/stripe/billingPortalPost.js +0 -93
  125. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  126. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  127. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  128. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  129. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  130. package/src/handlers/stripe/webhookPost.js +0 -482
  131. package/src/handlers/user/apiTokenCreate.js +0 -71
  132. package/src/handlers/user/apiTokenList.js +0 -64
  133. package/src/handlers/user/userSplashAck.js +0 -91
  134. package/src/handlers/user/userSplashGet.js +0 -211
  135. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  136. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  137. package/src/handlers/users/userEntitlementsGet.js +0 -89
  138. package/src/handlers/users/userGet.js +0 -118
  139. package/src/handlers/users/userProfilePut.js +0 -77
  140. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -1,240 +0,0 @@
1
- /**
2
- * Add-on Management Handler
3
- * Add or remove enterprise add-ons
4
- *
5
- * POST /api/stripe/subscription/addons
6
- * Body: { action: 'add'|'remove', addonId: 'engineering_intelligence' }
7
- * Auth: Cognito JWT required
8
- */
9
-
10
- const {
11
- wrapHandler,
12
- executeQuery,
13
- createSuccessResponse,
14
- createErrorResponse,
15
- handleError,
16
- getEnterpriseAddon,
17
- getAddonPriceId,
18
- calculateEnterpriseCost
19
- } = require('./helpers');
20
-
21
- // Stripe is optional
22
- let stripe = null;
23
- try {
24
- if (process.env.STRIPE_SECRET_KEY) {
25
- stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
26
- }
27
- } catch (e) {
28
- console.warn('Stripe not available:', e.message);
29
- }
30
-
31
- /**
32
- * Add or remove enterprise add-on
33
- */
34
- async function manageAddon({ body: requestBody = {}, requestContext }) {
35
- try {
36
- const Request_ID = requestContext.requestId;
37
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
38
-
39
- if (!email) {
40
- return createErrorResponse(401, 'Authentication required');
41
- }
42
-
43
- const { action, addonId } = requestBody;
44
-
45
- // Validate action
46
- if (!['add', 'remove'].includes(action)) {
47
- return createErrorResponse(400, 'Invalid action', {
48
- valid_actions: ['add', 'remove']
49
- });
50
- }
51
-
52
- // Validate add-on ID
53
- const addon = getEnterpriseAddon(addonId);
54
- if (!addon) {
55
- return createErrorResponse(400, `Invalid add-on: ${addonId}`, {
56
- valid_addons: ['engineering_intelligence', 'knowledge_continuity']
57
- });
58
- }
59
-
60
- // Check Stripe is configured
61
- if (!stripe) {
62
- return createErrorResponse(503, 'Payment system not configured');
63
- }
64
-
65
- // Get client with enterprise subscription
66
- const clientQuery = `
67
- SELECT c.client_id, c.subscription_tier, c.subscription_status,
68
- c.stripe_subscription_id, c.seat_count, c.enterprise_package,
69
- c.subscribed_addons, c.stripe_customer_id
70
- FROM rapport.users u
71
- JOIN rapport.clients c ON u.client_id = c.client_id
72
- WHERE u.email_address = $1 AND u.active = true
73
- `;
74
- const clientResult = await executeQuery(clientQuery, [email]);
75
-
76
- if (clientResult.rowCount === 0) {
77
- return createErrorResponse(404, 'User not found');
78
- }
79
-
80
- const client = clientResult.rows[0];
81
-
82
- // Verify enterprise subscription
83
- if (client.subscription_tier !== 'enterprise') {
84
- return createErrorResponse(400, 'Add-ons only available for enterprise subscriptions');
85
- }
86
-
87
- if (!client.stripe_subscription_id) {
88
- return createErrorResponse(400, 'No active Stripe subscription found');
89
- }
90
-
91
- // Check if using Full Platform (already includes all add-ons)
92
- if (client.enterprise_package === 'full') {
93
- return createErrorResponse(400, 'Full Platform package already includes all add-ons', {
94
- tip: 'Downgrade to base package to manage add-ons separately'
95
- });
96
- }
97
-
98
- const currentAddons = client.subscribed_addons || [];
99
- const hasAddon = currentAddons.includes(addonId);
100
- const seatCount = client.seat_count || 25;
101
- const seatPacks = seatCount / 25;
102
-
103
- if (action === 'add') {
104
- if (hasAddon) {
105
- return createErrorResponse(400, `Add-on ${addon.name} is already active`);
106
- }
107
-
108
- // Get subscription to add item
109
- const subscription = await stripe.subscriptions.retrieve(client.stripe_subscription_id);
110
-
111
- // Determine billing interval from existing subscription
112
- const existingItem = subscription.items.data[0];
113
- const billingInterval = existingItem?.price?.recurring?.interval || 'month';
114
- const priceId = getAddonPriceId(addonId, billingInterval === 'year' ? 'annual' : 'monthly');
115
-
116
- if (!priceId) {
117
- return createErrorResponse(500, 'Add-on price not configured');
118
- }
119
-
120
- // Add subscription item
121
- const newItem = await stripe.subscriptionItems.create({
122
- subscription: client.stripe_subscription_id,
123
- price: priceId,
124
- quantity: seatPacks,
125
- proration_behavior: 'create_prorations'
126
- });
127
-
128
- // Update client record
129
- const updatedAddons = [...currentAddons, addonId];
130
- await executeQuery(`
131
- UPDATE rapport.clients
132
- SET subscribed_addons = $2, last_updated = CURRENT_TIMESTAMP
133
- WHERE client_id = $1
134
- `, [client.client_id, JSON.stringify(updatedAddons)]);
135
-
136
- // Record in addon_entitlements
137
- await executeQuery(`
138
- INSERT INTO rapport.addon_entitlements (
139
- client_id, addon_id, stripe_subscription_item_id, seat_count, status
140
- )
141
- VALUES ($1, $2, $3, $4, 'active')
142
- ON CONFLICT (client_id, addon_id) DO UPDATE SET
143
- stripe_subscription_item_id = EXCLUDED.stripe_subscription_item_id,
144
- seat_count = EXCLUDED.seat_count,
145
- status = 'active',
146
- updated_at = CURRENT_TIMESTAMP
147
- `, [client.client_id, addonId, newItem.id, seatCount]);
148
-
149
- // Calculate new cost
150
- const newCost = calculateEnterpriseCost(
151
- client.enterprise_package,
152
- seatCount,
153
- updatedAddons,
154
- 'monthly'
155
- );
156
-
157
- return createSuccessResponse(
158
- {
159
- Records: [{
160
- action: 'added',
161
- addon: addon.name,
162
- addonId,
163
- seatCount,
164
- monthlyAddonCost: addon.pricePerSeat * seatCount,
165
- newMonthlyTotal: newCost.total,
166
- prorated: true
167
- }]
168
- },
169
- `Added ${addon.name} add-on`,
170
- { Request_ID, Timestamp: new Date().toISOString() }
171
- );
172
-
173
- } else if (action === 'remove') {
174
- if (!hasAddon) {
175
- return createErrorResponse(400, `Add-on ${addon.name} is not active`);
176
- }
177
-
178
- // Find the subscription item for this add-on
179
- const entitlementQuery = `
180
- SELECT stripe_subscription_item_id
181
- FROM rapport.addon_entitlements
182
- WHERE client_id = $1 AND addon_id = $2 AND status = 'active'
183
- `;
184
- const entitlementResult = await executeQuery(entitlementQuery, [client.client_id, addonId]);
185
-
186
- if (entitlementResult.rowCount > 0 && entitlementResult.rows[0].stripe_subscription_item_id) {
187
- // Remove subscription item from Stripe
188
- await stripe.subscriptionItems.del(
189
- entitlementResult.rows[0].stripe_subscription_item_id,
190
- { proration_behavior: 'create_prorations' }
191
- );
192
- }
193
-
194
- // Update client record
195
- const updatedAddons = currentAddons.filter(id => id !== addonId);
196
- await executeQuery(`
197
- UPDATE rapport.clients
198
- SET subscribed_addons = $2, last_updated = CURRENT_TIMESTAMP
199
- WHERE client_id = $1
200
- `, [client.client_id, JSON.stringify(updatedAddons)]);
201
-
202
- // Update addon_entitlements
203
- await executeQuery(`
204
- UPDATE rapport.addon_entitlements
205
- SET status = 'canceled', updated_at = CURRENT_TIMESTAMP
206
- WHERE client_id = $1 AND addon_id = $2
207
- `, [client.client_id, addonId]);
208
-
209
- // Calculate new cost
210
- const newCost = calculateEnterpriseCost(
211
- client.enterprise_package,
212
- seatCount,
213
- updatedAddons,
214
- 'monthly'
215
- );
216
-
217
- return createSuccessResponse(
218
- {
219
- Records: [{
220
- action: 'removed',
221
- addon: addon.name,
222
- addonId,
223
- monthlyAddonCost: addon.pricePerSeat * seatCount,
224
- newMonthlyTotal: newCost.total,
225
- prorated: true,
226
- creditIssued: true
227
- }]
228
- },
229
- `Removed ${addon.name} add-on`,
230
- { Request_ID, Timestamp: new Date().toISOString() }
231
- );
232
- }
233
-
234
- } catch (error) {
235
- console.error('Handler Error:', error);
236
- return handleError(error);
237
- }
238
- }
239
-
240
- exports.handler = wrapHandler(manageAddon);
@@ -1,93 +0,0 @@
1
- /**
2
- * Billing Portal Handler
3
- * Creates Stripe Customer Portal session for self-service subscription management
4
- *
5
- * POST /api/stripe/billing-portal
6
- * Body: { returnUrl?: string }
7
- * Auth: Cognito JWT required
8
- */
9
-
10
- const {
11
- wrapHandler,
12
- executeQuery,
13
- createSuccessResponse,
14
- createErrorResponse,
15
- handleError
16
- } = require('./helpers');
17
-
18
- // Stripe is optional
19
- let stripe = null;
20
- try {
21
- if (process.env.STRIPE_SECRET_KEY) {
22
- stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
23
- }
24
- } catch (e) {
25
- console.warn('Stripe not available:', e.message);
26
- }
27
-
28
- /**
29
- * Create billing portal session
30
- */
31
- async function createBillingPortal({ body: requestBody = {}, requestContext }) {
32
- try {
33
- const Request_ID = requestContext.requestId;
34
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
35
-
36
- if (!email) {
37
- return createErrorResponse(401, 'Authentication required');
38
- }
39
-
40
- const { returnUrl } = requestBody;
41
-
42
- // Check Stripe is configured
43
- if (!stripe) {
44
- return createErrorResponse(503, 'Payment system not configured');
45
- }
46
-
47
- // Get client with Stripe customer ID
48
- const clientQuery = `
49
- SELECT c.client_id, c.stripe_customer_id, c.subscription_tier,
50
- c.subscription_status
51
- FROM rapport.users u
52
- JOIN rapport.clients c ON u.client_id = c.client_id
53
- WHERE u.email_address = $1 AND u.active = true
54
- `;
55
- const clientResult = await executeQuery(clientQuery, [email]);
56
-
57
- if (clientResult.rowCount === 0) {
58
- return createErrorResponse(404, 'User not found');
59
- }
60
-
61
- const client = clientResult.rows[0];
62
-
63
- if (!client.stripe_customer_id) {
64
- return createErrorResponse(400, 'No billing account found. Subscribe to a plan first.');
65
- }
66
-
67
- // Create billing portal session
68
- const session = await stripe.billingPortal.sessions.create({
69
- customer: client.stripe_customer_id,
70
- return_url: returnUrl || `${process.env.APP_URL || 'https://app.mindmeld.dev'}/settings/billing`
71
- });
72
-
73
- return createSuccessResponse(
74
- {
75
- Records: [{
76
- portalUrl: session.url,
77
- returnUrl: session.return_url
78
- }]
79
- },
80
- 'Billing portal session created',
81
- {
82
- Request_ID,
83
- Timestamp: new Date().toISOString()
84
- }
85
- );
86
-
87
- } catch (error) {
88
- console.error('Handler Error:', error);
89
- return handleError(error);
90
- }
91
- }
92
-
93
- exports.handler = wrapHandler(createBillingPortal);
@@ -1,272 +0,0 @@
1
- /**
2
- * Enterprise Checkout Handler
3
- * Creates Stripe Checkout session for enterprise subscriptions
4
- *
5
- * POST /api/stripe/enterprise/checkout
6
- * Body: { package: 'base'|'full', seatCount: 25, addons: [], billingPeriod: 'monthly'|'annual' }
7
- * Auth: Cognito JWT required
8
- */
9
-
10
- const {
11
- wrapHandler,
12
- executeQuery,
13
- createSuccessResponse,
14
- createErrorResponse,
15
- handleError,
16
- validateSeatCount,
17
- calculateEnterpriseCost,
18
- getEnterprisePackage,
19
- getEnterpriseAddon,
20
- getEnterprisePriceId,
21
- getAddonPriceId,
22
- getVolumeDiscount
23
- } = require('./helpers');
24
-
25
- // Stripe is optional - will be null if not configured
26
- let stripe = null;
27
- try {
28
- if (process.env.STRIPE_SECRET_KEY) {
29
- stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
30
- }
31
- } catch (e) {
32
- console.warn('Stripe not available:', e.message);
33
- }
34
-
35
- /**
36
- * Create enterprise checkout session
37
- */
38
- async function createEnterpriseCheckout({ body: requestBody = {}, requestContext }) {
39
- try {
40
- const Request_ID = requestContext.requestId;
41
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
42
-
43
- if (!email) {
44
- return createErrorResponse(401, 'Authentication required');
45
- }
46
-
47
- const {
48
- package: packageType = 'base',
49
- seatCount = 25,
50
- addons = [],
51
- billingPeriod = 'monthly'
52
- } = requestBody;
53
-
54
- // Validate package type
55
- const pkgConfig = getEnterprisePackage(packageType);
56
- if (!pkgConfig) {
57
- return createErrorResponse(400, 'Invalid enterprise package', {
58
- valid_packages: ['base', 'full'],
59
- provided: packageType
60
- });
61
- }
62
-
63
- // Validate seat count (must be >= 25 and multiple of 25)
64
- const seatValidation = validateSeatCount(seatCount);
65
- if (!seatValidation.valid) {
66
- return createErrorResponse(400, seatValidation.message, {
67
- requested: seatCount,
68
- corrected: seatValidation.correctedCount,
69
- minimum: 25,
70
- increment: 25
71
- });
72
- }
73
-
74
- // Validate add-ons (only valid for 'base' package)
75
- if (packageType === 'full' && addons.length > 0) {
76
- return createErrorResponse(400, 'Full Platform package already includes all add-ons', {
77
- tip: 'Use base package if you want to select specific add-ons'
78
- });
79
- }
80
-
81
- // Validate add-on IDs
82
- for (const addonId of addons) {
83
- const addon = getEnterpriseAddon(addonId);
84
- if (!addon) {
85
- return createErrorResponse(400, `Invalid add-on: ${addonId}`, {
86
- valid_addons: ['engineering_intelligence', 'knowledge_continuity']
87
- });
88
- }
89
- }
90
-
91
- // Check Stripe is configured
92
- if (!stripe) {
93
- return createErrorResponse(503, 'Payment system not configured', {
94
- code: 'STRIPE_NOT_CONFIGURED'
95
- });
96
- }
97
-
98
- // Get user's client
99
- const userQuery = `
100
- SELECT u.client_id, u.create_date as account_created,
101
- c.client_name, c.stripe_customer_id, c.subscription_status,
102
- c.subscription_tier
103
- FROM rapport.users u
104
- JOIN rapport.clients c ON u.client_id = c.client_id
105
- WHERE u.email_address = $1 AND u.active = true
106
- `;
107
- const userResult = await executeQuery(userQuery, [email]);
108
-
109
- if (userResult.rowCount === 0) {
110
- return createErrorResponse(404, 'User not found');
111
- }
112
-
113
- const user = userResult.rows[0];
114
-
115
- // Check for existing active enterprise subscription
116
- if (user.subscription_tier === 'enterprise' && user.subscription_status === 'active') {
117
- return createErrorResponse(400, 'Already have active enterprise subscription. Use seats/addons endpoints to modify.', {
118
- code: 'ENTERPRISE_SUBSCRIPTION_EXISTS'
119
- });
120
- }
121
-
122
- // Calculate pricing
123
- const costBreakdown = calculateEnterpriseCost(packageType, seatCount, addons, billingPeriod);
124
- const volumeDiscount = getVolumeDiscount(seatCount);
125
-
126
- // Build line items for Stripe checkout
127
- const lineItems = [];
128
-
129
- // Get seat pack quantity (1 pack = 25 seats)
130
- const seatPacks = seatCount / 25;
131
-
132
- // Main package line item
133
- const mainPriceId = getEnterprisePriceId(packageType, billingPeriod);
134
- if (!mainPriceId) {
135
- return createErrorResponse(500, 'Enterprise price not configured');
136
- }
137
-
138
- lineItems.push({
139
- price: mainPriceId,
140
- quantity: seatPacks
141
- });
142
-
143
- // Add-on line items (only for 'base' package)
144
- if (packageType === 'base') {
145
- for (const addonId of addons) {
146
- const addonPriceId = getAddonPriceId(addonId, billingPeriod);
147
- if (addonPriceId) {
148
- lineItems.push({
149
- price: addonPriceId,
150
- quantity: seatPacks
151
- });
152
- }
153
- }
154
- }
155
-
156
- // Create Stripe Checkout session
157
- const sessionParams = {
158
- mode: 'subscription',
159
- payment_method_types: ['card'],
160
- customer_email: email,
161
- line_items: lineItems,
162
- success_url: `${process.env.APP_URL || 'https://app.mindmeld.dev'}/enterprise/success?session_id={CHECKOUT_SESSION_ID}`,
163
- cancel_url: `${process.env.APP_URL || 'https://app.mindmeld.dev'}/enterprise/cancel`,
164
- metadata: {
165
- client_id: user.client_id,
166
- user_email: email,
167
- tier: 'enterprise',
168
- enterprise_package: packageType,
169
- seat_count: seatCount.toString(),
170
- addons: JSON.stringify(addons),
171
- billing_period: billingPeriod
172
- },
173
- subscription_data: {
174
- metadata: {
175
- client_id: user.client_id,
176
- tier: 'enterprise',
177
- enterprise_package: packageType,
178
- seat_count: seatCount.toString(),
179
- addons: JSON.stringify(addons)
180
- }
181
- }
182
- };
183
-
184
- // Apply volume discount as coupon if applicable
185
- if (volumeDiscount.discount > 0) {
186
- // Look up or create volume discount coupon
187
- const couponId = `VOLUME_${Math.round(volumeDiscount.discount * 100)}`;
188
- try {
189
- await stripe.coupons.retrieve(couponId);
190
- } catch (e) {
191
- // Create coupon if it doesn't exist
192
- if (e.code === 'resource_missing') {
193
- await stripe.coupons.create({
194
- id: couponId,
195
- percent_off: volumeDiscount.discount * 100,
196
- duration: 'forever',
197
- name: volumeDiscount.label
198
- });
199
- }
200
- }
201
- sessionParams.discounts = [{ coupon: couponId }];
202
- }
203
-
204
- // If customer already exists in Stripe, use that
205
- if (user.stripe_customer_id) {
206
- delete sessionParams.customer_email;
207
- sessionParams.customer = user.stripe_customer_id;
208
- }
209
-
210
- const session = await stripe.checkout.sessions.create(sessionParams);
211
-
212
- // Store pending session with enterprise details
213
- const sessionQuery = `
214
- INSERT INTO rapport.stripe_checkout_sessions (
215
- session_id,
216
- client_id,
217
- user_email,
218
- tier,
219
- billing_period,
220
- seat_count,
221
- enterprise_package,
222
- addons,
223
- status
224
- )
225
- VALUES ($1, $2, $3, 'enterprise', $4, $5, $6, $7, 'pending')
226
- `;
227
- await executeQuery(sessionQuery, [
228
- session.id,
229
- user.client_id,
230
- email,
231
- billingPeriod,
232
- seatCount,
233
- packageType,
234
- JSON.stringify(addons)
235
- ]);
236
-
237
- return createSuccessResponse(
238
- {
239
- Records: [{
240
- checkoutUrl: session.url,
241
- sessionId: session.id,
242
- package: pkgConfig.displayName,
243
- seatCount,
244
- addons: addons.map(id => getEnterpriseAddon(id)?.name),
245
- pricing: {
246
- subtotal: costBreakdown.subtotal,
247
- volumeDiscount: volumeDiscount.discount > 0 ? {
248
- percent: volumeDiscount.discount * 100,
249
- label: volumeDiscount.label,
250
- amount: costBreakdown.discountAmount
251
- } : null,
252
- total: costBreakdown.total,
253
- billingPeriod,
254
- breakdown: costBreakdown.breakdown
255
- }
256
- }]
257
- },
258
- `Enterprise checkout session created - ${pkgConfig.displayName} with ${seatCount} seats`,
259
- {
260
- Total_Records: 1,
261
- Request_ID,
262
- Timestamp: new Date().toISOString()
263
- }
264
- );
265
-
266
- } catch (error) {
267
- console.error('Handler Error:', error);
268
- return handleError(error);
269
- }
270
- }
271
-
272
- exports.handler = wrapHandler(createEnterpriseCheckout);