@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,185 +0,0 @@
1
- /**
2
- * Seats Update Handler
3
- * Adjust seat count for enterprise subscriptions
4
- *
5
- * POST /api/stripe/subscription/seats
6
- * Body: { seatCount: 50 }
7
- * Auth: Cognito JWT required
8
- */
9
-
10
- const {
11
- wrapHandler,
12
- executeQuery,
13
- createSuccessResponse,
14
- createErrorResponse,
15
- handleError,
16
- validateSeatCount,
17
- calculateEnterpriseCost,
18
- getVolumeDiscount
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
- * Update enterprise seat count
33
- */
34
- async function updateSeats({ 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 { seatCount } = requestBody;
44
-
45
- // Validate seat count
46
- const seatValidation = validateSeatCount(seatCount);
47
- if (!seatValidation.valid) {
48
- return createErrorResponse(400, seatValidation.message, {
49
- requested: seatCount,
50
- corrected: seatValidation.correctedCount,
51
- minimum: 25,
52
- increment: 25
53
- });
54
- }
55
-
56
- // Check Stripe is configured
57
- if (!stripe) {
58
- return createErrorResponse(503, 'Payment system not configured');
59
- }
60
-
61
- // Get client with enterprise subscription
62
- const clientQuery = `
63
- SELECT c.client_id, c.subscription_tier, c.subscription_status,
64
- c.stripe_subscription_id, c.seat_count, c.enterprise_package,
65
- c.subscribed_addons, c.stripe_customer_id
66
- FROM rapport.users u
67
- JOIN rapport.clients c ON u.client_id = c.client_id
68
- WHERE u.email_address = $1 AND u.active = true
69
- `;
70
- const clientResult = await executeQuery(clientQuery, [email]);
71
-
72
- if (clientResult.rowCount === 0) {
73
- return createErrorResponse(404, 'User not found');
74
- }
75
-
76
- const client = clientResult.rows[0];
77
-
78
- // Verify enterprise subscription
79
- if (client.subscription_tier !== 'enterprise') {
80
- return createErrorResponse(400, 'Seat updates only available for enterprise subscriptions');
81
- }
82
-
83
- if (!client.stripe_subscription_id) {
84
- return createErrorResponse(400, 'No active Stripe subscription found');
85
- }
86
-
87
- const currentSeats = client.seat_count || 25;
88
- const newSeats = seatValidation.correctedCount;
89
-
90
- if (newSeats === currentSeats) {
91
- return createSuccessResponse(
92
- { message: 'No change in seat count', seatCount: currentSeats },
93
- 'Seat count unchanged'
94
- );
95
- }
96
-
97
- // Calculate new seat packs
98
- const currentPacks = currentSeats / 25;
99
- const newPacks = newSeats / 25;
100
-
101
- // Get subscription from Stripe
102
- const subscription = await stripe.subscriptions.retrieve(client.stripe_subscription_id);
103
-
104
- // Update all subscription items with new quantity
105
- const updates = subscription.items.data.map(item => ({
106
- id: item.id,
107
- quantity: newPacks
108
- }));
109
-
110
- // Check if volume discount changes
111
- const currentDiscount = getVolumeDiscount(currentSeats);
112
- const newDiscount = getVolumeDiscount(newSeats);
113
- const discountChanged = currentDiscount.discount !== newDiscount.discount;
114
-
115
- // Update subscription
116
- const updateParams = {
117
- items: updates,
118
- proration_behavior: 'create_prorations'
119
- };
120
-
121
- // Update volume discount coupon if needed
122
- if (discountChanged && newDiscount.discount > 0) {
123
- const couponId = `VOLUME_${Math.round(newDiscount.discount * 100)}`;
124
- try {
125
- await stripe.coupons.retrieve(couponId);
126
- } catch (e) {
127
- if (e.code === 'resource_missing') {
128
- await stripe.coupons.create({
129
- id: couponId,
130
- percent_off: newDiscount.discount * 100,
131
- duration: 'forever',
132
- name: newDiscount.label
133
- });
134
- }
135
- }
136
- updateParams.coupon = couponId;
137
- } else if (discountChanged && newDiscount.discount === 0) {
138
- // Remove discount
139
- updateParams.coupon = '';
140
- }
141
-
142
- await stripe.subscriptions.update(client.stripe_subscription_id, updateParams);
143
-
144
- // Update client record
145
- await executeQuery(`
146
- UPDATE rapport.clients
147
- SET seat_count = $2, last_updated = CURRENT_TIMESTAMP
148
- WHERE client_id = $1
149
- `, [client.client_id, newSeats]);
150
-
151
- // Calculate new pricing
152
- const addons = client.subscribed_addons || [];
153
- const newCost = calculateEnterpriseCost(
154
- client.enterprise_package,
155
- newSeats,
156
- addons,
157
- 'monthly' // Assuming monthly for display
158
- );
159
-
160
- return createSuccessResponse(
161
- {
162
- Records: [{
163
- previousSeatCount: currentSeats,
164
- newSeatCount: newSeats,
165
- change: newSeats - currentSeats,
166
- volumeDiscount: newDiscount.discount > 0 ? newDiscount : null,
167
- discountChanged,
168
- newMonthlyTotal: newCost.total,
169
- prorated: true
170
- }]
171
- },
172
- `Seat count updated from ${currentSeats} to ${newSeats}`,
173
- {
174
- Request_ID,
175
- Timestamp: new Date().toISOString()
176
- }
177
- );
178
-
179
- } catch (error) {
180
- console.error('Handler Error:', error);
181
- return handleError(error);
182
- }
183
- }
184
-
185
- exports.handler = wrapHandler(updateSeats);
@@ -1,169 +0,0 @@
1
- /**
2
- * Subscription Cancel Handler
3
- * Cancels subscription (immediately or at period end)
4
- *
5
- * DELETE /api/stripe/subscription/cancel
6
- * Query: { immediately: boolean }
7
- * Auth: Cognito JWT required
8
- */
9
-
10
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
11
-
12
- // Stripe is optional
13
- let stripe = null;
14
- try {
15
- if (process.env.STRIPE_SECRET_KEY) {
16
- stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
17
- }
18
- } catch (e) {
19
- console.warn('Stripe not available:', e.message);
20
- }
21
-
22
- /**
23
- * Cancel subscription
24
- */
25
- async function cancelSubscription({ queryStringParameters = {}, requestContext }) {
26
- try {
27
- const Request_ID = requestContext.requestId;
28
- // REST API: requestContext.authorizer.claims.email
29
- // HTTP API: requestContext.authorizer.jwt.claims.email
30
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
31
-
32
- if (!email) {
33
- return createErrorResponse(401, 'Authentication required');
34
- }
35
-
36
- const immediately = queryStringParameters.immediately === 'true';
37
-
38
- if (!stripe) {
39
- return createErrorResponse(503, 'Payment system not configured');
40
- }
41
-
42
- // Get user's client and subscription
43
- const userQuery = `
44
- SELECT
45
- u.client_id,
46
- c.stripe_customer_id,
47
- c.subscription_tier,
48
- c.subscription_status
49
- FROM rapport.users u
50
- JOIN rapport.clients c ON u.client_id = c.client_id
51
- WHERE u.email_address = $1 AND u.active = true
52
- `;
53
- const userResult = await executeQuery(userQuery, [email]);
54
-
55
- if (userResult.rowCount === 0) {
56
- return createErrorResponse(404, 'User not found');
57
- }
58
-
59
- const user = userResult.rows[0];
60
-
61
- if (!user.stripe_customer_id || user.subscription_status !== 'active') {
62
- return createErrorResponse(400, 'No active subscription to cancel');
63
- }
64
-
65
- // Get subscription from Stripe
66
- const subscriptions = await stripe.subscriptions.list({
67
- customer: user.stripe_customer_id,
68
- status: 'active',
69
- limit: 1
70
- });
71
-
72
- if (subscriptions.data.length === 0) {
73
- // Update local state to match Stripe
74
- await executeQuery(`
75
- UPDATE rapport.clients
76
- SET subscription_status = 'canceled',
77
- subscription_tier = 'free',
78
- last_updated = CURRENT_TIMESTAMP
79
- WHERE client_id = $1
80
- `, [user.client_id]);
81
-
82
- return createErrorResponse(400, 'No active subscription found in Stripe');
83
- }
84
-
85
- const subscription = subscriptions.data[0];
86
- let canceledSubscription;
87
- let accessEndsAt;
88
-
89
- if (immediately) {
90
- // Cancel immediately with proration
91
- canceledSubscription = await stripe.subscriptions.cancel(subscription.id, {
92
- prorate: true
93
- });
94
- accessEndsAt = new Date();
95
-
96
- // Update client immediately
97
- await executeQuery(`
98
- UPDATE rapport.clients
99
- SET subscription_status = 'canceled',
100
- subscription_tier = 'free',
101
- subscription_ends_at = CURRENT_TIMESTAMP,
102
- last_updated = CURRENT_TIMESTAMP
103
- WHERE client_id = $1
104
- `, [user.client_id]);
105
-
106
- } else {
107
- // Cancel at end of billing period
108
- canceledSubscription = await stripe.subscriptions.update(subscription.id, {
109
- cancel_at_period_end: true
110
- });
111
- accessEndsAt = new Date(subscription.current_period_end * 1000);
112
-
113
- // Update client to canceling status
114
- await executeQuery(`
115
- UPDATE rapport.clients
116
- SET subscription_status = 'canceling',
117
- subscription_ends_at = $2,
118
- last_updated = CURRENT_TIMESTAMP
119
- WHERE client_id = $1
120
- `, [user.client_id, accessEndsAt]);
121
- }
122
-
123
- // Check data limits for free tier
124
- const usageQuery = `
125
- SELECT
126
- (SELECT COUNT(*) FROM rapport.user_entitlements WHERE client_id = $1) as collaborators,
127
- (SELECT COUNT(*) FROM rapport.projects p
128
- JOIN rapport.companies co ON p.company_id = co.company_id
129
- WHERE co.client_id = $1 AND p.archived = false) as projects
130
- `;
131
- const usageResult = await executeQuery(usageQuery, [user.client_id]);
132
- const usage = usageResult.rows[0];
133
-
134
- const warnings = [];
135
- if (parseInt(usage.collaborators) > 1) {
136
- warnings.push(`You have ${usage.collaborators} collaborators. Free tier allows 1.`);
137
- }
138
- if (parseInt(usage.projects) > 3) {
139
- warnings.push(`You have ${usage.projects} projects. Free tier allows 3.`);
140
- }
141
-
142
- return createSuccessResponse(
143
- {
144
- Records: [{
145
- status: immediately ? 'canceled' : 'canceling',
146
- access_ends_at: accessEndsAt.toISOString(),
147
- immediately,
148
- previous_tier: user.subscription_tier,
149
- new_tier: 'free',
150
- warnings
151
- }]
152
- },
153
- immediately
154
- ? 'Subscription canceled immediately'
155
- : 'Subscription will cancel at end of billing period',
156
- {
157
- Total_Records: 1,
158
- Request_ID,
159
- Timestamp: new Date().toISOString()
160
- }
161
- );
162
-
163
- } catch (error) {
164
- console.error('Handler Error:', error);
165
- return handleError(error);
166
- }
167
- }
168
-
169
- exports.handler = wrapHandler(cancelSubscription);
@@ -1,221 +0,0 @@
1
- /**
2
- * Subscription Create Handler
3
- * Creates Stripe Checkout session for new subscription
4
- *
5
- * POST /api/stripe/subscription/create
6
- * Body: { tier, billingPeriod }
7
- * Auth: Cognito JWT required
8
- *
9
- * Following HoneyDo subscriptionCreatePost.js pattern
10
- */
11
-
12
- const {
13
- wrapHandler,
14
- executeQuery,
15
- createSuccessResponse,
16
- createErrorResponse,
17
- handleError,
18
- getTierConfig,
19
- getStripePriceId,
20
- checkEarlyAdopterEligibility,
21
- getEarlyAdopterDiscount,
22
- calculateEarlyAdopterPrice
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 subscription checkout session
37
- */
38
- async function createSubscription({ body: requestBody = {}, requestContext }) {
39
- try {
40
- const Request_ID = requestContext.requestId;
41
- // REST API: requestContext.authorizer.claims.email
42
- // HTTP API: requestContext.authorizer.jwt.claims.email
43
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
44
-
45
- if (!email) {
46
- return createErrorResponse(401, 'Authentication required');
47
- }
48
-
49
- const { tier, billingPeriod = 'monthly' } = requestBody;
50
-
51
- // Validate tier
52
- const tierConfig = getTierConfig(tier);
53
- if (!tierConfig) {
54
- return createErrorResponse(400, 'Invalid subscription tier', {
55
- valid_tiers: ['team', 'professional', 'enterprise']
56
- });
57
- }
58
-
59
- if (tier === 'free') {
60
- return createErrorResponse(400, 'Cannot subscribe to free tier');
61
- }
62
-
63
- // Check Stripe is configured
64
- if (!stripe) {
65
- return createErrorResponse(503, 'Payment system not configured', {
66
- code: 'STRIPE_NOT_CONFIGURED'
67
- });
68
- }
69
-
70
- // Get user's client and account creation date
71
- const userQuery = `
72
- SELECT u.client_id, u.create_date as account_created,
73
- c.client_name, c.stripe_customer_id, c.subscription_status
74
- FROM rapport.users u
75
- JOIN rapport.clients c ON u.client_id = c.client_id
76
- WHERE u.email_address = $1 AND u.active = true
77
- `;
78
- const userResult = await executeQuery(userQuery, [email]);
79
-
80
- if (userResult.rowCount === 0) {
81
- return createErrorResponse(404, 'User not found');
82
- }
83
-
84
- const user = userResult.rows[0];
85
-
86
- // Check early adopter eligibility
87
- const earlyAdopterStatus = checkEarlyAdopterEligibility(user.account_created);
88
- const discountConfig = getEarlyAdopterDiscount();
89
-
90
- // Check for existing active subscription
91
- if (user.subscription_status === 'active' && user.stripe_customer_id) {
92
- return createErrorResponse(400, 'Already have active subscription. Use update to change tier.', {
93
- code: 'SUBSCRIPTION_EXISTS'
94
- });
95
- }
96
-
97
- // Get Stripe price ID
98
- const priceId = getStripePriceId(tier, billingPeriod);
99
- if (!priceId) {
100
- return createErrorResponse(500, 'Stripe price not configured for this tier');
101
- }
102
-
103
- // Count billable users for per-seat pricing
104
- const usageQuery = `
105
- SELECT COUNT(*) as user_count
106
- FROM rapport.user_entitlements
107
- WHERE client_id = $1
108
- `;
109
- const usageResult = await executeQuery(usageQuery, [user.client_id]);
110
- const userCount = parseInt(usageResult.rows[0].user_count) || 1;
111
-
112
- // Create Stripe Checkout session
113
- const sessionParams = {
114
- mode: 'subscription',
115
- payment_method_types: ['card'],
116
- customer_email: email,
117
- line_items: [{
118
- price: priceId,
119
- quantity: tierConfig.perUser ? userCount : 1
120
- }],
121
- success_url: `${process.env.APP_URL || 'https://app.mindmeld.dev'}/dashboard?checkout=success`,
122
- cancel_url: `${process.env.APP_URL || 'https://app.mindmeld.dev'}/signup`,
123
- metadata: {
124
- client_id: user.client_id,
125
- user_email: email,
126
- tier: tier,
127
- billing_period: billingPeriod,
128
- early_adopter: earlyAdopterStatus.eligible ? 'true' : 'false'
129
- },
130
- subscription_data: {
131
- metadata: {
132
- client_id: user.client_id,
133
- tier: tier
134
- }
135
- }
136
- };
137
-
138
- // Auto-apply early adopter discount if eligible
139
- if (earlyAdopterStatus.eligible && discountConfig.autoApply) {
140
- sessionParams.discounts = [{
141
- coupon: discountConfig.stripeCouponId
142
- }];
143
- console.log(`[Subscription] Auto-applying ${discountConfig.stripeCouponId} for ${email}`);
144
- }
145
-
146
- // If customer already exists in Stripe, use that
147
- if (user.stripe_customer_id) {
148
- delete sessionParams.customer_email;
149
- sessionParams.customer = user.stripe_customer_id;
150
- }
151
-
152
- const session = await stripe.checkout.sessions.create(sessionParams);
153
-
154
- // Store pending session
155
- const sessionQuery = `
156
- INSERT INTO rapport.stripe_checkout_sessions (
157
- session_id,
158
- client_id,
159
- user_email,
160
- tier,
161
- billing_period,
162
- status
163
- )
164
- VALUES ($1, $2, $3, $4, $5, 'pending')
165
- `;
166
- await executeQuery(sessionQuery, [
167
- session.id,
168
- user.client_id,
169
- email,
170
- tier,
171
- billingPeriod
172
- ]);
173
-
174
- // Calculate pricing with potential discount
175
- const basePrice = tierConfig.perUser
176
- ? tierConfig.priceMonthly * userCount
177
- : tierConfig.priceMonthly;
178
-
179
- const discountInfo = earlyAdopterStatus.eligible
180
- ? calculateEarlyAdopterPrice(tier, userCount, billingPeriod)
181
- : null;
182
-
183
- return createSuccessResponse(
184
- {
185
- Records: [{
186
- checkoutUrl: session.url,
187
- sessionId: session.id,
188
- tier: tierConfig.displayName,
189
- price: basePrice,
190
- billingPeriod,
191
- userCount: tierConfig.perUser ? userCount : null,
192
- discount: earlyAdopterStatus.eligible ? {
193
- applied: true,
194
- code: discountConfig.shareableCode,
195
- message: discountConfig.displayMessage,
196
- originalPrice: discountInfo.originalPrice,
197
- discountedPrice: discountInfo.discountedPrice,
198
- savings: discountInfo.savings,
199
- percent: discountInfo.discountPercent,
200
- duration: discountInfo.discountDuration,
201
- daysRemaining: earlyAdopterStatus.daysRemaining
202
- } : null
203
- }]
204
- },
205
- earlyAdopterStatus.eligible
206
- ? `Checkout session created - ${discountConfig.displayMessage}`
207
- : 'Checkout session created',
208
- {
209
- Total_Records: 1,
210
- Request_ID,
211
- Timestamp: new Date().toISOString()
212
- }
213
- );
214
-
215
- } catch (error) {
216
- console.error('Handler Error:', error);
217
- return handleError(error);
218
- }
219
- }
220
-
221
- exports.handler = wrapHandler(createSubscription);