ft-scout 8.0.4 → 8.0.5

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.
@@ -0,0 +1,251 @@
1
+ import crypto from 'crypto';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import dotenv from 'dotenv';
5
+ import { loadAuthConfig, saveAuthConfig } from './auth.js';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ dotenv.config({ path: path.resolve(__dirname, '../../../.env'), quiet: true });
9
+ dotenv.config({ quiet: true });
10
+ export const PLAN_CONFIGS = {
11
+ free: {
12
+ id: 'free',
13
+ name: 'Free Community',
14
+ monthlyPrice: 0,
15
+ annualPrice: 0,
16
+ credits: 100,
17
+ description: 'Get started with codebase onboarding',
18
+ features: ['100 AI credits / month', 'Standard response time', '1 CLI session slot', 'Community support'],
19
+ },
20
+ pro: {
21
+ id: 'pro',
22
+ name: 'Pro Developer',
23
+ monthlyPrice: 19,
24
+ annualPrice: 15,
25
+ credits: 1000,
26
+ description: 'For active engineers navigating complex codebases',
27
+ features: ['1,000 AI credits / month', 'Fast-track reasoning engine', '5 concurrent CLI session slots', 'Priority support response'],
28
+ },
29
+ enterprise: {
30
+ id: 'enterprise',
31
+ name: 'Team & Enterprise',
32
+ monthlyPrice: 49,
33
+ annualPrice: 39,
34
+ credits: 10000,
35
+ description: 'Full organization control and dedicated scale',
36
+ features: ['10,000 AI credits / month', 'Continuous in-memory screen streaming HUD', 'Unlimited CLI session connections', 'Dedicated account SLA support'],
37
+ },
38
+ };
39
+ export function getPayPalBaseUrl() {
40
+ return process.env.PAYPAL_MODE === 'live'
41
+ ? 'https://api-m.paypal.com'
42
+ : 'https://api-m.sandbox.paypal.com';
43
+ }
44
+ export async function getPayPalAccessToken() {
45
+ const clientId = process.env.PAYPAL_CLIENT_ID;
46
+ const clientSecret = process.env.PAYPAL_CLIENT_SECRET;
47
+ if (!clientId || !clientSecret) {
48
+ throw new Error('PAYPAL_CLIENT_ID and PAYPAL_CLIENT_SECRET must be configured in environment variables.');
49
+ }
50
+ const auth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
51
+ const baseUrl = getPayPalBaseUrl();
52
+ const response = await fetch(`${baseUrl}/v1/oauth2/token`, {
53
+ method: 'POST',
54
+ headers: {
55
+ 'Authorization': `Basic ${auth}`,
56
+ 'Content-Type': 'application/x-www-form-urlencoded',
57
+ },
58
+ body: 'grant_type=client_credentials',
59
+ });
60
+ const data = await response.json();
61
+ if (!response.ok || !data.access_token) {
62
+ throw new Error(`PayPal OAuth failed: ${data.error_description || data.error || 'Unable to retrieve access token'}`);
63
+ }
64
+ return data.access_token;
65
+ }
66
+ export function getPaymentProvider() {
67
+ return 'paypal';
68
+ }
69
+ export function getPublicPaymentConfig() {
70
+ const isLive = process.env.PAYPAL_MODE === 'live';
71
+ const hasPayPalKeys = Boolean(process.env.PAYPAL_CLIENT_ID && process.env.PAYPAL_CLIENT_SECRET);
72
+ return {
73
+ provider: 'paypal',
74
+ isSandbox: !isLive || !hasPayPalKeys,
75
+ paypalClientId: process.env.PAYPAL_CLIENT_ID || undefined,
76
+ plans: PLAN_CONFIGS,
77
+ };
78
+ }
79
+ /**
80
+ * Creates a checkout order strictly with PayPal.
81
+ */
82
+ export async function createCheckoutSession(params) {
83
+ const tier = params.tier;
84
+ if (tier === 'free') {
85
+ throw new Error('Cannot create a payment session for the free tier.');
86
+ }
87
+ const plan = PLAN_CONFIGS[tier];
88
+ if (!plan) {
89
+ throw new Error(`Unknown tier: ${tier}`);
90
+ }
91
+ const billingCycle = params.billingCycle === 'annual' ? 'annual' : 'monthly';
92
+ const unitPrice = billingCycle === 'annual' ? plan.annualPrice * 12 : plan.monthlyPrice;
93
+ const DEFAULT_PLANS_URL = process.env.FRONTTERRAIN_DASHBOARD_URL
94
+ ? `${process.env.FRONTTERRAIN_DASHBOARD_URL.replace(/\/$/, '')}/plans`
95
+ : 'https://scout.frontterrain.com/plans';
96
+ const accessToken = await getPayPalAccessToken();
97
+ const baseUrl = getPayPalBaseUrl();
98
+ const returnUrl = params.returnUrl || DEFAULT_PLANS_URL;
99
+ const successReturnUrl = `${returnUrl}${returnUrl.includes('?') ? '&' : '?'}success=true&tier=${tier}`;
100
+ const cancelReturnUrl = params.cancelUrl || `${returnUrl}${returnUrl.includes('?') ? '&' : '?'}canceled=true`;
101
+ const orderPayload = {
102
+ intent: 'CAPTURE',
103
+ purchase_units: [
104
+ {
105
+ reference_id: `scout_${tier}_${Date.now()}`,
106
+ description: `FrontTerrain Scout ${plan.name} (${billingCycle})`,
107
+ amount: {
108
+ currency_code: 'USD',
109
+ value: unitPrice.toFixed(2),
110
+ },
111
+ },
112
+ ],
113
+ application_context: {
114
+ brand_name: 'FrontTerrain Scout',
115
+ landing_page: 'BILLING',
116
+ user_action: 'PAY_NOW',
117
+ return_url: successReturnUrl,
118
+ cancel_url: cancelReturnUrl,
119
+ },
120
+ };
121
+ const orderRes = await fetch(`${baseUrl}/v2/checkout/orders`, {
122
+ method: 'POST',
123
+ headers: {
124
+ 'Authorization': `Bearer ${accessToken}`,
125
+ 'Content-Type': 'application/json',
126
+ },
127
+ body: JSON.stringify(orderPayload),
128
+ });
129
+ const orderData = await orderRes.json();
130
+ if (!orderRes.ok || !orderData.id) {
131
+ throw new Error(`PayPal Order Creation Failed: ${orderData.message || JSON.stringify(orderData)}`);
132
+ }
133
+ const approveLink = orderData.links?.find((l) => l.rel === 'approve')?.href;
134
+ if (!approveLink) {
135
+ throw new Error('PayPal did not return an approval URL for this order.');
136
+ }
137
+ return {
138
+ success: true,
139
+ provider: 'paypal',
140
+ sessionId: orderData.id,
141
+ orderId: orderData.id,
142
+ checkoutUrl: approveLink,
143
+ amount: unitPrice,
144
+ currency: 'USD',
145
+ tier,
146
+ billingCycle,
147
+ isSandbox: process.env.PAYPAL_MODE !== 'live',
148
+ };
149
+ }
150
+ /**
151
+ * Strictly verifies the PayPal transaction using PayPal REST API.
152
+ * Validates order status, currency, and paid amount against the requested plan.
153
+ * Absolutely no simulation or fake tokens allowed.
154
+ */
155
+ export async function verifyAndApplyUpgrade(params) {
156
+ const targetOrderId = (params.orderId || params.token || params.sessionId || '').trim();
157
+ const tier = params.tier;
158
+ if (!targetOrderId) {
159
+ throw new Error('PayPal payment verification failed: Missing order ID or payment token.');
160
+ }
161
+ const plan = PLAN_CONFIGS[tier];
162
+ if (!plan || tier === 'free') {
163
+ throw new Error(`Invalid plan tier specified for upgrade: ${tier}`);
164
+ }
165
+ const billingCycle = params.billingCycle === 'annual' ? 'annual' : 'monthly';
166
+ const expectedPrice = billingCycle === 'annual' ? plan.annualPrice * 12 : plan.monthlyPrice;
167
+ // Strict verification against real PayPal API
168
+ const accessToken = await getPayPalAccessToken();
169
+ const baseUrl = getPayPalBaseUrl();
170
+ let orderData = null;
171
+ let transactionId = '';
172
+ // Step 1: Attempt to capture the PayPal order
173
+ const captureRes = await fetch(`${baseUrl}/v2/checkout/orders/${encodeURIComponent(targetOrderId)}/capture`, {
174
+ method: 'POST',
175
+ headers: {
176
+ 'Authorization': `Bearer ${accessToken}`,
177
+ 'Content-Type': 'application/json',
178
+ },
179
+ });
180
+ const captureJson = await captureRes.json();
181
+ if (captureRes.ok && (captureJson.status === 'COMPLETED' || captureJson.status === 'SAVED')) {
182
+ orderData = captureJson;
183
+ const captureUnit = captureJson.purchase_units?.[0]?.payments?.captures?.[0];
184
+ transactionId = captureUnit?.id || captureJson.id;
185
+ }
186
+ else if (captureJson.status === 'COMPLETED' ||
187
+ captureJson.name === 'UNPROCESSABLE_ENTITY' ||
188
+ captureJson.details?.some((d) => d.issue === 'ORDER_ALREADY_CAPTURED')) {
189
+ // Step 2: If order was already captured, fetch order details to verify
190
+ const getRes = await fetch(`${baseUrl}/v2/checkout/orders/${encodeURIComponent(targetOrderId)}`, {
191
+ method: 'GET',
192
+ headers: {
193
+ 'Authorization': `Bearer ${accessToken}`,
194
+ 'Content-Type': 'application/json',
195
+ },
196
+ });
197
+ const getJson = await getRes.json();
198
+ if (getRes.ok && getJson.status === 'COMPLETED') {
199
+ orderData = getJson;
200
+ const captureUnit = getJson.purchase_units?.[0]?.payments?.captures?.[0];
201
+ transactionId = captureUnit?.id || getJson.id;
202
+ }
203
+ else {
204
+ throw new Error(`PayPal Order Verification Failed: Order status is "${getJson.status || 'UNKNOWN'}" (must be COMPLETED).`);
205
+ }
206
+ }
207
+ else {
208
+ const errorMsg = captureJson.message || captureJson.details?.[0]?.description || JSON.stringify(captureJson);
209
+ throw new Error(`PayPal capture failed: ${errorMsg}`);
210
+ }
211
+ // Step 3: Strict verification of amount, currency, and purchase details
212
+ if (!orderData || orderData.status !== 'COMPLETED') {
213
+ throw new Error(`PayPal payment verification rejected: Order status is ${orderData?.status || 'INCOMPLETE'}.`);
214
+ }
215
+ const purchaseUnit = orderData.purchase_units?.[0];
216
+ const captureInfo = purchaseUnit?.payments?.captures?.[0];
217
+ const paidCurrency = captureInfo?.amount?.currency_code || purchaseUnit?.amount?.currency_code;
218
+ const paidAmount = parseFloat(captureInfo?.amount?.value || purchaseUnit?.amount?.value || '0');
219
+ if (paidCurrency !== 'USD') {
220
+ throw new Error(`PayPal payment verification rejected: Expected currency USD, received ${paidCurrency}.`);
221
+ }
222
+ // Allow a small 0.01 tolerance for floating point representations
223
+ if (paidAmount < expectedPrice - 0.01) {
224
+ throw new Error(`PayPal payment verification rejected: Paid amount ($${paidAmount.toFixed(2)}) does not match required plan price ($${expectedPrice.toFixed(2)}).`);
225
+ }
226
+ // Step 4: Apply verified upgrade to local auth config
227
+ const config = loadAuthConfig();
228
+ const now = new Date();
229
+ const renewalDate = new Date(now);
230
+ renewalDate.setMonth(renewalDate.getMonth() + (billingCycle === 'annual' ? 12 : 1));
231
+ const updatedConfig = {
232
+ ...config,
233
+ currentTier: tier,
234
+ creditsTotal: plan.credits,
235
+ creditsUsed: 0, // Reset used credits upon verified purchase
236
+ resetCredits: true,
237
+ transactionId,
238
+ renewAt: renewalDate.toISOString(),
239
+ };
240
+ saveAuthConfig(updatedConfig);
241
+ return {
242
+ success: true,
243
+ tier,
244
+ creditsAdded: plan.credits,
245
+ totalCredits: plan.credits,
246
+ renewAt: renewalDate.toISOString(),
247
+ auth: updatedConfig,
248
+ transactionId: transactionId || targetOrderId,
249
+ };
250
+ }
251
+ //# sourceMappingURL=payment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment.js","sourceRoot":"","sources":["payment.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ft-scout",
3
- "version": "8.0.4",
3
+ "version": "8.0.5",
4
4
  "type": "module",
5
5
  "description": "FrontTerrain — The Onboarding Co-Pilot for New Codebases",
6
6
  "main": "index.js",
package/web/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* ==========================================================================
2
- FrontTerrain Scout — Multi-Page Application Engine v8.0.4
2
+ FrontTerrain Scout — Multi-Page Application Engine v8.0.5
3
3
  ========================================================================== */
4
4
 
5
5
  const FIREBASE_CONFIG = {
@@ -105,6 +105,50 @@ function highlightActiveNav() {
105
105
  });
106
106
  }
107
107
 
108
+ // Check for payment redirect callback from PayPal / external gateways
109
+ async function checkPaymentRedirectCallback() {
110
+ const params = new URLSearchParams(window.location.search);
111
+ const orderId = params.get('token') || params.get('session_id') || params.get('orderId');
112
+ const isSuccess = params.get('success') === 'true' || Boolean(params.get('token'));
113
+ const tier = params.get('tier') || 'pro';
114
+
115
+ if (orderId && isSuccess) {
116
+ window.history.replaceState({}, document.title, window.location.pathname);
117
+ showToast('Verifying payment with PayPal...', 'info');
118
+
119
+ try {
120
+ const res = await fetch('/api/payment/verify', {
121
+ method: 'POST',
122
+ headers: { 'Content-Type': 'application/json' },
123
+ body: JSON.stringify({
124
+ orderId,
125
+ token: orderId,
126
+ sessionId: orderId,
127
+ tier,
128
+ }),
129
+ });
130
+ const data = await res.json();
131
+ if (data.success) {
132
+ state.currentTier = data.tier;
133
+ state.creditsTotal = data.totalCredits;
134
+ state.creditsUsed = 0;
135
+ state.renewAt = data.renewAt;
136
+ saveSessionToStorage();
137
+ renderAllDynamicComponents();
138
+ const tierName = (TIER_DETAILS[data.tier] || {}).name || data.tier;
139
+ showToast(`🎉 PayPal Payment Verified! Welcome to ${tierName}. Your ${data.totalCredits.toLocaleString()} monthly credits are active!`, 'success');
140
+ } else {
141
+ showToast(data.error || 'PayPal verification failed.', 'error');
142
+ }
143
+ } catch (err) {
144
+ showToast('Payment verification network error.', 'error');
145
+ }
146
+ } else if (params.get('canceled') === 'true') {
147
+ window.history.replaceState({}, document.title, window.location.pathname);
148
+ showToast('Subscription checkout was canceled.', 'warning');
149
+ }
150
+ }
151
+
108
152
  // Initialize Application
109
153
  document.addEventListener('DOMContentLoaded', async () => {
110
154
  highlightActiveNav();
@@ -115,6 +159,7 @@ document.addEventListener('DOMContentLoaded', async () => {
115
159
  initTerminalSimulator();
116
160
  renderAllDynamicComponents();
117
161
  await checkServerStatusAndSession();
162
+ await checkPaymentRedirectCallback();
118
163
 
119
164
  if (getEl('pacman-canvas')) {
120
165
  initPacmanGame();
@@ -258,26 +303,53 @@ function initEventListeners() {
258
303
  getEl('btn-close-checkout')?.addEventListener('click', closeCheckoutModal);
259
304
  getEl('btn-cancel-checkout')?.addEventListener('click', closeCheckoutModal);
260
305
 
261
- getEl('btn-confirm-checkout')?.addEventListener('click', () => {
306
+ getEl('btn-confirm-checkout')?.addEventListener('click', async () => {
262
307
  if (!pendingCheckoutTier) return;
263
308
 
264
309
  const targetTier = pendingCheckoutTier;
265
310
  const tierInfo = TIER_DETAILS[targetTier];
266
311
  if (!tierInfo) return;
267
312
 
268
- state.currentTier = targetTier;
269
- state.creditsTotal = targetTier === 'pro' ? 1000 : targetTier === 'enterprise' ? 10000 : 100;
313
+ const btn = getEl('btn-confirm-checkout');
314
+ const origBtnHtml = btn ? btn.innerHTML : '';
315
+ if (btn) {
316
+ btn.disabled = true;
317
+ btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Initializing Gateway...';
318
+ }
270
319
 
271
- const now = new Date();
272
- state.renewAt = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 0, 0).toISOString();
320
+ try {
321
+ const res = await fetch('/api/payment/create-checkout', {
322
+ method: 'POST',
323
+ headers: { 'Content-Type': 'application/json' },
324
+ body: JSON.stringify({
325
+ tier: targetTier,
326
+ billingCycle: state.billingCycle || 'monthly',
327
+ returnUrl: window.location.origin + '/plans',
328
+ email: state.user?.email,
329
+ }),
330
+ });
273
331
 
274
- saveSessionToStorage();
275
- syncAuthWithServer();
276
- closeCheckoutModal();
277
- renderCreditUsageUI();
278
- renderTiersUI();
279
- updateUserUI();
280
- showToast(`Subscription activated! Welcome to ${tierInfo.name}. Quota upgraded to ${state.creditsTotal.toLocaleString()} credits.`, 'success');
332
+ const data = await res.json();
333
+ if (!res.ok || !data.success) {
334
+ throw new Error(data.error || 'Failed to initialize payment session.');
335
+ }
336
+
337
+ // PayPal Checkout redirect
338
+ if (data.checkoutUrl) {
339
+ if (btn) btn.innerHTML = '<i class="fa-brands fa-paypal"></i> Redirecting to PayPal...';
340
+ window.location.href = data.checkoutUrl;
341
+ return;
342
+ }
343
+
344
+ throw new Error('PayPal checkout URL could not be generated.');
345
+ } catch (err) {
346
+ showToast(err.message || 'Payment processing failed.', 'error');
347
+ } finally {
348
+ if (btn) {
349
+ btn.disabled = false;
350
+ btn.innerHTML = origBtnHtml;
351
+ }
352
+ }
281
353
  });
282
354
 
283
355
  // Password Visibility Toggle Buttons
@@ -461,7 +533,7 @@ function initTerminalSimulator() {
461
533
  `;
462
534
  } else if (lower.includes('status')) {
463
535
  response.innerHTML = `
464
- <span class="term-green">✔ Scout CLI Engine: v8.0.4 Online</span><br>
536
+ <span class="term-green">✔ Scout CLI Engine: v8.0.5 Online</span><br>
465
537
  ↳ User: <strong>${escapeHtml(state.user ? state.user.name : 'Guest Session')}</strong><br>
466
538
  ↳ Active Subscription: <strong>${escapeHtml(TIER_DETAILS[state.currentTier].name)}</strong><br>
467
539
  ↳ Credit Quota: <strong>${state.byokEnabled ? 'BYOK Mode (Unlimited)' : `${state.creditsUsed} / ${state.creditsTotal} Credits Used`}</strong><br>
package/web/styles.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /* ==========================================================================
2
- FrontTerrain Scout — Design System & Visual Engine v8.0.4
2
+ FrontTerrain Scout — Design System & Visual Engine v8.0.5
3
3
  Inspired by HappyRobot, Linear.app & Vercel aesthetics
4
4
  ========================================================================== */
5
5
 
@@ -1063,7 +1063,7 @@ h1, h2, h3, h4, h5, h6 {
1063
1063
  .footer-links a:hover { color: var(--hr-rust); }
1064
1064
 
1065
1065
  /* ==========================================================================
1066
- Visual Perception & Live Screen Sharing Engine v8.0.4
1066
+ Visual Perception & Live Screen Sharing Engine v8.0.5
1067
1067
  ========================================================================== */
1068
1068
  .vision-banner {
1069
1069
  margin-bottom: 28px;