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.
- package/README.md +2 -2
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +6 -0
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/audit.d.ts.map +1 -1
- package/bin/src/commands/audit.js +8 -2
- package/bin/src/commands/audit.js.map +1 -1
- package/bin/src/commands/dashboard.d.ts.map +1 -1
- package/bin/src/commands/dashboard.js +9 -7
- package/bin/src/commands/dashboard.js.map +1 -1
- package/bin/src/commands/plan.d.ts.map +1 -0
- package/bin/src/commands/plan.js +70 -0
- package/bin/src/commands/plan.js.map +1 -0
- package/bin/src/commands/quota.d.ts.map +1 -1
- package/bin/src/commands/quota.js +4 -20
- package/bin/src/commands/quota.js.map +1 -1
- package/bin/src/commands/risky.d.ts.map +1 -1
- package/bin/src/commands/risky.js +8 -2
- package/bin/src/commands/risky.js.map +1 -1
- package/bin/src/index.js +17 -7
- package/bin/src/index.js.map +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +57 -0
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/auth.d.ts.map +1 -1
- package/bin/src/utils/auth.js +382 -7
- package/bin/src/utils/auth.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/payment.d.ts.map +1 -0
- package/bin/src/utils/payment.js +251 -0
- package/bin/src/utils/payment.js.map +1 -0
- package/package.json +1 -1
- package/web/app.js +86 -14
- package/web/styles.css +2 -2
|
@@ -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
package/web/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
FrontTerrain Scout — Multi-Page Application Engine v8.0.
|
|
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
|
-
|
|
269
|
-
|
|
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
|
-
|
|
272
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1066
|
+
Visual Perception & Live Screen Sharing Engine v8.0.5
|
|
1067
1067
|
========================================================================== */
|
|
1068
1068
|
.vision-banner {
|
|
1069
1069
|
margin-bottom: 28px;
|