web-agent-bridge 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +84 -72
- package/README.ar.md +1304 -1152
- package/README.md +298 -1635
- package/bin/agent-runner.js +474 -474
- package/bin/cli.js +237 -138
- package/bin/wab-init.js +223 -0
- package/bin/wab.js +80 -80
- package/examples/azure-dns-wab.js +83 -0
- package/examples/bidi-agent.js +119 -119
- package/examples/cloudflare-wab-dns.js +121 -0
- package/examples/cpanel-wab-dns.js +114 -0
- package/examples/cross-site-agent.js +91 -91
- package/examples/dns-discovery-agent.js +166 -0
- package/examples/gcp-dns-wab.js +76 -0
- package/examples/governance-agent.js +169 -0
- package/examples/mcp-agent.js +94 -94
- package/examples/next-app-router/README.md +44 -44
- package/examples/plesk-wab-dns.js +103 -0
- package/examples/puppeteer-agent.js +108 -108
- package/examples/route53-wab-dns.js +144 -0
- package/examples/saas-dashboard/README.md +55 -55
- package/examples/safe-mode-agent.js +96 -0
- package/examples/shopify-hydrogen/README.md +74 -74
- package/examples/vision-agent.js +171 -171
- package/examples/wab-sign.js +74 -0
- package/examples/wab-verify.js +60 -0
- package/examples/wordpress-elementor/README.md +77 -77
- package/package.json +19 -6
- package/public/.well-known/agent-tools.json +180 -180
- package/public/.well-known/ai-assets.json +59 -59
- package/public/.well-known/security.txt +8 -0
- package/public/.well-known/wab.json +28 -0
- package/public/activate.html +368 -0
- package/public/adoption-metrics.html +188 -0
- package/public/agent-workspace.html +349 -349
- package/public/ai.html +198 -198
- package/public/api.html +413 -412
- package/public/azure-dns-integration.html +289 -0
- package/public/browser.html +486 -486
- package/public/cloudflare-integration.html +380 -0
- package/public/commander-dashboard.html +243 -243
- package/public/cookies.html +210 -210
- package/public/cpanel-integration.html +398 -0
- package/public/css/agent-workspace.css +1713 -1713
- package/public/css/premium.css +317 -317
- package/public/css/styles.css +1263 -1235
- package/public/dashboard.html +707 -706
- package/public/dns.html +436 -0
- package/public/docs.html +588 -587
- package/public/feed.xml +89 -89
- package/public/gcp-dns-integration.html +318 -0
- package/public/growth.html +465 -463
- package/public/index.html +1266 -982
- package/public/integrations.html +556 -0
- package/public/js/activate.js +145 -0
- package/public/js/agent-workspace.js +1740 -1740
- package/public/js/auth-nav.js +65 -31
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/dns.js +438 -0
- package/public/js/wab-demo-page.js +721 -721
- package/public/js/ws-client.js +74 -74
- package/public/llms-full.txt +360 -360
- package/public/llms.txt +125 -125
- package/public/login.html +85 -85
- package/public/mesh-dashboard.html +328 -328
- package/public/openapi.json +669 -580
- package/public/phone-shield.html +281 -0
- package/public/plesk-integration.html +375 -0
- package/public/premium-dashboard.html +2489 -2489
- package/public/premium.html +793 -793
- package/public/privacy.html +297 -297
- package/public/provider-onboarding.html +172 -0
- package/public/provider-sandbox.html +134 -0
- package/public/providers.html +359 -0
- package/public/register.html +105 -105
- package/public/registrar-integrations.html +141 -0
- package/public/robots.txt +99 -87
- package/public/route53-integration.html +531 -0
- package/public/script/wab-consent.d.ts +36 -36
- package/public/script/wab-consent.js +104 -104
- package/public/script/wab-schema.js +131 -131
- package/public/script/wab.d.ts +108 -108
- package/public/script/wab.min.js +580 -580
- package/public/security.txt +8 -0
- package/public/shieldqr.html +231 -0
- package/public/sitemap.xml +6 -0
- package/public/terms.html +256 -256
- package/public/wab-trust.html +200 -0
- package/public/wab-vs-protocols.html +210 -0
- package/public/whitepaper.html +449 -0
- package/script/ai-agent-bridge.js +1754 -1754
- package/sdk/README.md +99 -99
- package/sdk/agent-mesh.js +449 -449
- package/sdk/auto-discovery.js +288 -0
- package/sdk/commander.js +262 -262
- package/sdk/governance.js +262 -0
- package/sdk/index.d.ts +464 -464
- package/sdk/index.js +25 -1
- package/sdk/multi-agent.js +318 -318
- package/sdk/package.json +2 -2
- package/sdk/safe-mode.js +221 -0
- package/sdk/safety-shield.js +219 -0
- package/sdk/schema-discovery.js +83 -83
- package/server/adapters/index.js +520 -520
- package/server/config/plans.js +367 -367
- package/server/config/secrets.js +102 -102
- package/server/control-plane/index.js +301 -301
- package/server/data-plane/index.js +354 -354
- package/server/index.js +670 -427
- package/server/llm/index.js +404 -404
- package/server/middleware/adminAuth.js +35 -35
- package/server/middleware/auth.js +50 -50
- package/server/middleware/featureGate.js +88 -88
- package/server/middleware/rateLimits.js +100 -100
- package/server/middleware/sensitiveAction.js +157 -0
- package/server/migrations/001_add_analytics_indexes.sql +7 -7
- package/server/migrations/002_premium_features.sql +418 -418
- package/server/migrations/003_ads_integer_cents.sql +33 -33
- package/server/migrations/004_agent_os.sql +158 -158
- package/server/migrations/005_marketplace_metering.sql +126 -126
- package/server/migrations/007_governance.sql +106 -0
- package/server/migrations/008_plans.sql +144 -0
- package/server/migrations/009_shieldqr.sql +30 -0
- package/server/migrations/010_extended_trust.sql +33 -0
- package/server/models/adapters/index.js +33 -33
- package/server/models/adapters/mysql.js +183 -183
- package/server/models/adapters/postgresql.js +172 -172
- package/server/models/adapters/sqlite.js +7 -7
- package/server/models/db.js +740 -681
- package/server/observability/failure-analysis.js +337 -337
- package/server/observability/index.js +394 -394
- package/server/protocol/capabilities.js +223 -223
- package/server/protocol/index.js +243 -243
- package/server/protocol/schema.js +584 -584
- package/server/registry/certification.js +271 -271
- package/server/registry/index.js +326 -326
- package/server/routes/admin-plans.js +76 -0
- package/server/routes/admin-premium.js +673 -671
- package/server/routes/admin-shieldqr.js +90 -0
- package/server/routes/admin-trust-monitor.js +83 -0
- package/server/routes/admin.js +549 -261
- package/server/routes/ads.js +130 -130
- package/server/routes/agent-workspace.js +540 -540
- package/server/routes/api.js +150 -150
- package/server/routes/auth.js +71 -71
- package/server/routes/billing.js +57 -45
- package/server/routes/commander.js +316 -316
- package/server/routes/demo-showcase.js +332 -332
- package/server/routes/demo-store.js +154 -0
- package/server/routes/discovery.js +2348 -417
- package/server/routes/gateway.js +173 -157
- package/server/routes/governance.js +208 -0
- package/server/routes/license.js +251 -240
- package/server/routes/mesh.js +469 -469
- package/server/routes/noscript.js +543 -543
- package/server/routes/plans.js +33 -0
- package/server/routes/premium-v2.js +686 -686
- package/server/routes/premium.js +724 -724
- package/server/routes/providers.js +650 -0
- package/server/routes/runtime.js +2148 -2147
- package/server/routes/shieldqr.js +88 -0
- package/server/routes/sovereign.js +465 -385
- package/server/routes/universal.js +200 -185
- package/server/routes/wab-api.js +850 -501
- package/server/runtime/container-worker.js +111 -111
- package/server/runtime/container.js +448 -448
- package/server/runtime/distributed-worker.js +362 -362
- package/server/runtime/event-bus.js +210 -210
- package/server/runtime/index.js +253 -253
- package/server/runtime/queue.js +599 -599
- package/server/runtime/replay.js +666 -666
- package/server/runtime/sandbox.js +266 -266
- package/server/runtime/scheduler.js +534 -534
- package/server/runtime/session-engine.js +293 -293
- package/server/runtime/state-manager.js +188 -188
- package/server/security/cross-site-redactor.js +196 -0
- package/server/security/dry-run.js +180 -0
- package/server/security/human-gate-rate-limit.js +147 -0
- package/server/security/human-gate-transports.js +178 -0
- package/server/security/human-gate.js +281 -0
- package/server/security/index.js +368 -368
- package/server/security/intent-engine.js +245 -0
- package/server/security/reward-guard.js +171 -0
- package/server/security/rollback-store.js +239 -0
- package/server/security/token-scope.js +404 -0
- package/server/security/url-policy.js +139 -0
- package/server/services/agent-chat.js +506 -506
- package/server/services/agent-learning.js +601 -575
- package/server/services/agent-memory.js +625 -625
- package/server/services/agent-mesh.js +555 -539
- package/server/services/agent-symphony.js +717 -717
- package/server/services/agent-tasks.js +1807 -1807
- package/server/services/api-key-engine.js +292 -261
- package/server/services/cluster.js +894 -894
- package/server/services/commander.js +738 -738
- package/server/services/edge-compute.js +440 -440
- package/server/services/email.js +233 -204
- package/server/services/governance.js +466 -0
- package/server/services/hosted-runtime.js +205 -205
- package/server/services/lfd.js +635 -635
- package/server/services/local-ai.js +389 -389
- package/server/services/marketplace.js +270 -270
- package/server/services/metering.js +182 -182
- package/server/services/modules/affiliate-intelligence.js +93 -93
- package/server/services/modules/agent-firewall.js +90 -90
- package/server/services/modules/bounty.js +89 -89
- package/server/services/modules/collective-bargaining.js +92 -92
- package/server/services/modules/dark-pattern.js +66 -66
- package/server/services/modules/gov-intelligence.js +45 -45
- package/server/services/modules/neural.js +55 -55
- package/server/services/modules/notary.js +49 -49
- package/server/services/modules/price-time-machine.js +86 -86
- package/server/services/modules/protocol.js +104 -104
- package/server/services/negotiation.js +439 -439
- package/server/services/plans.js +214 -0
- package/server/services/plugins.js +771 -771
- package/server/services/premium.js +1 -1
- package/server/services/price-intelligence.js +566 -566
- package/server/services/price-shield.js +1137 -1137
- package/server/services/provider-clients.js +740 -0
- package/server/services/reputation.js +465 -465
- package/server/services/search-engine.js +357 -357
- package/server/services/security.js +513 -513
- package/server/services/self-healing.js +843 -843
- package/server/services/shieldqr.js +322 -0
- package/server/services/sovereign-shield.js +542 -0
- package/server/services/ssl-inspector.js +42 -0
- package/server/services/ssl-monitor.js +167 -0
- package/server/services/stripe.js +205 -192
- package/server/services/swarm.js +788 -788
- package/server/services/universal-scraper.js +662 -661
- package/server/services/verification.js +481 -481
- package/server/services/vision.js +1163 -1163
- package/server/services/wab-crypto.js +178 -0
- package/server/utils/cache.js +125 -125
- package/server/utils/migrate.js +81 -81
- package/server/utils/safe-fetch.js +228 -0
- package/server/utils/secureFields.js +50 -50
- package/server/ws.js +161 -161
- package/templates/artisan-marketplace.yaml +104 -104
- package/templates/book-price-scout.yaml +98 -98
- package/templates/electronics-price-tracker.yaml +108 -108
- package/templates/flight-deal-hunter.yaml +113 -113
- package/templates/freelancer-direct.yaml +116 -116
- package/templates/grocery-price-compare.yaml +93 -93
- package/templates/hotel-direct-booking.yaml +113 -113
- package/templates/local-services.yaml +98 -98
- package/templates/olive-oil-tunisia.yaml +88 -88
- package/templates/organic-farm-fresh.yaml +101 -101
- package/templates/restaurant-direct.yaml +97 -97
- package/public/score.html +0 -263
- package/server/migrations/006_growth_suite.sql +0 -138
- package/server/routes/growth.js +0 -962
- package/server/services/fairness-engine.js +0 -409
- package/server/services/fairness.js +0 -420
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* server/services/ssl-monitor.js
|
|
3
|
+
* Extended Trust Layer — Certificate Companion & SSL Health Monitoring.
|
|
4
|
+
*
|
|
5
|
+
* Periodically inspects SSL certificates for every active site, persists state
|
|
6
|
+
* in `ssl_monitor`, appends new certs to `cert_history` (CT log), and emails
|
|
7
|
+
* the site owner when the certificate is within 7 days of expiry. The cron
|
|
8
|
+
* runs once per day inside the main process; can be disabled via env
|
|
9
|
+
* `WAB_SSL_MONITOR=off`.
|
|
10
|
+
*/
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const path = require('node:path');
|
|
14
|
+
const Database = require('better-sqlite3');
|
|
15
|
+
const ssl = require('./ssl-inspector');
|
|
16
|
+
|
|
17
|
+
const DATA_DIR = process.env.NODE_ENV === 'test'
|
|
18
|
+
? path.join(__dirname, '..', '..', 'data-test')
|
|
19
|
+
: (process.env.DATA_DIR || path.join(__dirname, '..', '..', 'data'));
|
|
20
|
+
const DB_FILE = process.env.NODE_ENV === 'test' ? 'wab-test.db' : 'wab.db';
|
|
21
|
+
|
|
22
|
+
let _db = null;
|
|
23
|
+
function db() {
|
|
24
|
+
if (!_db) { _db = new Database(path.join(DATA_DIR, DB_FILE)); }
|
|
25
|
+
return _db;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ALERT_DAYS = Number(process.env.WAB_SSL_ALERT_DAYS || 7);
|
|
29
|
+
const ALERT_REPEAT_HOURS = Number(process.env.WAB_SSL_ALERT_REPEAT_HOURS || 24);
|
|
30
|
+
|
|
31
|
+
function classify(daysLeft) {
|
|
32
|
+
if (daysLeft == null) return 'error';
|
|
33
|
+
if (daysLeft < 0) return 'expired';
|
|
34
|
+
if (daysLeft <= ALERT_DAYS) return 'expiring';
|
|
35
|
+
return 'active';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Inspect one host, persist state + CT log entry + alert if needed.
|
|
40
|
+
* Returns { host, status, days_until_expiry, alerted }.
|
|
41
|
+
*/
|
|
42
|
+
async function checkHost(host, opts = {}) {
|
|
43
|
+
const info = await ssl.inspect(host, 443);
|
|
44
|
+
const now = new Date().toISOString();
|
|
45
|
+
|
|
46
|
+
if (!info.ok) {
|
|
47
|
+
db().prepare(`
|
|
48
|
+
INSERT INTO ssl_monitor (host, status, error, last_checked_at, enabled, owner_user_id)
|
|
49
|
+
VALUES (?, 'error', ?, ?, 1, ?)
|
|
50
|
+
ON CONFLICT(host) DO UPDATE SET
|
|
51
|
+
status='error', error=excluded.error, last_checked_at=excluded.last_checked_at
|
|
52
|
+
`).run(host, info.error || 'unknown', now, opts.userId || null);
|
|
53
|
+
return { host, status: 'error', error: info.error };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const status = classify(info.days_until_expiry);
|
|
57
|
+
|
|
58
|
+
db().prepare(`
|
|
59
|
+
INSERT INTO ssl_monitor (host, fingerprint_sha256, issuer, valid_to, days_until_expiry,
|
|
60
|
+
status, error, last_checked_at, enabled, owner_user_id)
|
|
61
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL, ?, 1, ?)
|
|
62
|
+
ON CONFLICT(host) DO UPDATE SET
|
|
63
|
+
fingerprint_sha256=excluded.fingerprint_sha256,
|
|
64
|
+
issuer=excluded.issuer,
|
|
65
|
+
valid_to=excluded.valid_to,
|
|
66
|
+
days_until_expiry=excluded.days_until_expiry,
|
|
67
|
+
status=excluded.status,
|
|
68
|
+
error=NULL,
|
|
69
|
+
last_checked_at=excluded.last_checked_at
|
|
70
|
+
`).run(host, info.fingerprint_sha256, info.issuer, info.valid_to,
|
|
71
|
+
info.days_until_expiry, status, now, opts.userId || null);
|
|
72
|
+
|
|
73
|
+
// Append to CT log if fingerprint not seen for this host yet.
|
|
74
|
+
try {
|
|
75
|
+
db().prepare(`
|
|
76
|
+
INSERT OR IGNORE INTO cert_history
|
|
77
|
+
(host, fingerprint_sha256, issuer, subject, serial, valid_from, valid_to, source)
|
|
78
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
79
|
+
`).run(host, info.fingerprint_sha256, info.issuer, info.subject, info.serial,
|
|
80
|
+
info.valid_from, info.valid_to, opts.source || 'monitor');
|
|
81
|
+
} catch (_) { /* table may not exist yet */ }
|
|
82
|
+
|
|
83
|
+
let alerted = false;
|
|
84
|
+
if (status === 'expiring' || status === 'expired') {
|
|
85
|
+
alerted = await maybeSendAlert(host, info, status, opts);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { host, status, days_until_expiry: info.days_until_expiry, alerted };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function maybeSendAlert(host, info, status, opts) {
|
|
92
|
+
const row = db().prepare(`SELECT last_alert_at, owner_user_id FROM ssl_monitor WHERE host = ?`).get(host);
|
|
93
|
+
if (row && row.last_alert_at) {
|
|
94
|
+
const last = new Date(row.last_alert_at).getTime();
|
|
95
|
+
if (Date.now() - last < ALERT_REPEAT_HOURS * 3600 * 1000) return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let to = opts.alertEmail || process.env.WAB_SSL_ALERT_EMAIL;
|
|
99
|
+
try {
|
|
100
|
+
if (!to && row && row.owner_user_id) {
|
|
101
|
+
const u = db().prepare('SELECT email FROM users WHERE id = ?').get(row.owner_user_id);
|
|
102
|
+
if (u && u.email) to = u.email;
|
|
103
|
+
}
|
|
104
|
+
} catch (_) { /* users table may not exist in tests */ }
|
|
105
|
+
|
|
106
|
+
if (!to) return false;
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const { sendEmail } = require('./email');
|
|
110
|
+
await sendEmail({
|
|
111
|
+
to, template: 'sslExpiringAlert',
|
|
112
|
+
data: {
|
|
113
|
+
host,
|
|
114
|
+
daysLeft: Math.max(0, info.days_until_expiry),
|
|
115
|
+
validTo: info.valid_to,
|
|
116
|
+
issuer: info.issuer,
|
|
117
|
+
fingerprint: info.fingerprint_sha256,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
db().prepare(`UPDATE ssl_monitor SET last_alert_at = ? WHERE host = ?`)
|
|
121
|
+
.run(new Date().toISOString(), host);
|
|
122
|
+
return true;
|
|
123
|
+
} catch (_) { return false; }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Run a sweep across every active site domain (and optional extra hosts). */
|
|
127
|
+
async function runSweep({ extraHosts = [] } = {}) {
|
|
128
|
+
let hosts = [...extraHosts];
|
|
129
|
+
try {
|
|
130
|
+
const rows = db().prepare(
|
|
131
|
+
"SELECT DISTINCT LOWER(REPLACE(domain, 'http://', '')) AS host, user_id FROM sites WHERE active = 1"
|
|
132
|
+
).all();
|
|
133
|
+
for (const r of rows) {
|
|
134
|
+
const h = (r.host || '').replace(/^https?:\/\//, '').replace(/\/.*$/, '').replace(/^www\./, 'www.');
|
|
135
|
+
if (!h) continue;
|
|
136
|
+
hosts.push({ host: h, userId: r.user_id });
|
|
137
|
+
}
|
|
138
|
+
} catch (_) { /* sites table may not exist */ }
|
|
139
|
+
|
|
140
|
+
const results = [];
|
|
141
|
+
for (const item of hosts) {
|
|
142
|
+
const host = typeof item === 'string' ? item : item.host;
|
|
143
|
+
const userId = typeof item === 'string' ? null : item.userId;
|
|
144
|
+
try {
|
|
145
|
+
results.push(await checkHost(host, { userId }));
|
|
146
|
+
} catch (e) {
|
|
147
|
+
results.push({ host, status: 'error', error: e.message });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let _interval = null;
|
|
154
|
+
function start() {
|
|
155
|
+
if (_interval || process.env.WAB_SSL_MONITOR === 'off') return;
|
|
156
|
+
if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID) return;
|
|
157
|
+
// Run immediately, then once per day.
|
|
158
|
+
setTimeout(() => runSweep().catch(() => {}), 30_000);
|
|
159
|
+
_interval = setInterval(() => runSweep().catch(() => {}), 24 * 3600 * 1000);
|
|
160
|
+
if (_interval.unref) _interval.unref();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function stop() {
|
|
164
|
+
if (_interval) { clearInterval(_interval); _interval = null; }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = { checkHost, runSweep, classify, start, stop };
|
|
@@ -1,192 +1,205 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stripe Payment Integration Service
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const {
|
|
6
|
-
getPlatformSetting,
|
|
7
|
-
saveStripeCustomer,
|
|
8
|
-
getStripeCustomer,
|
|
9
|
-
saveStripeSubscription,
|
|
10
|
-
updateStripeSubscription,
|
|
11
|
-
getStripeSubscriptionBySubId,
|
|
12
|
-
savePayment,
|
|
13
|
-
updateSiteTier,
|
|
14
|
-
findSiteById
|
|
15
|
-
} = require('../models/db');
|
|
16
|
-
|
|
17
|
-
let stripe = null;
|
|
18
|
-
|
|
19
|
-
function getStripe() {
|
|
20
|
-
if (stripe) return stripe;
|
|
21
|
-
const key = process.env.STRIPE_SECRET_KEY || getPlatformSetting('stripe_secret_key');
|
|
22
|
-
if (!key) return null;
|
|
23
|
-
stripe = require('stripe')(key);
|
|
24
|
-
return stripe;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getStripePrices() {
|
|
28
|
-
return {
|
|
29
|
-
starter: process.env.STRIPE_PRICE_STARTER || getPlatformSetting('stripe_price_starter'),
|
|
30
|
-
pro: process.env.STRIPE_PRICE_PRO || getPlatformSetting('stripe_price_pro'),
|
|
31
|
-
enterprise: process.env.STRIPE_PRICE_ENTERPRISE || getPlatformSetting('stripe_price_enterprise')
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Stripe Payment Integration Service
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
getPlatformSetting,
|
|
7
|
+
saveStripeCustomer,
|
|
8
|
+
getStripeCustomer,
|
|
9
|
+
saveStripeSubscription,
|
|
10
|
+
updateStripeSubscription,
|
|
11
|
+
getStripeSubscriptionBySubId,
|
|
12
|
+
savePayment,
|
|
13
|
+
updateSiteTier,
|
|
14
|
+
findSiteById
|
|
15
|
+
} = require('../models/db');
|
|
16
|
+
|
|
17
|
+
let stripe = null;
|
|
18
|
+
|
|
19
|
+
function getStripe() {
|
|
20
|
+
if (stripe) return stripe;
|
|
21
|
+
const key = process.env.STRIPE_SECRET_KEY || getPlatformSetting('stripe_secret_key');
|
|
22
|
+
if (!key) return null;
|
|
23
|
+
stripe = require('stripe')(key);
|
|
24
|
+
return stripe;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getStripePrices() {
|
|
28
|
+
return {
|
|
29
|
+
starter: process.env.STRIPE_PRICE_STARTER || getPlatformSetting('stripe_price_starter'),
|
|
30
|
+
pro: process.env.STRIPE_PRICE_PRO || getPlatformSetting('stripe_price_pro'),
|
|
31
|
+
enterprise: process.env.STRIPE_PRICE_ENTERPRISE || getPlatformSetting('stripe_price_enterprise')
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Resolve a Stripe price id from either a plan id (DB) or a legacy tier name.
|
|
36
|
+
// Order: DB plan.stripe_price_id → env STRIPE_PRICE_<UPPER> → platform_settings → null.
|
|
37
|
+
function resolvePriceId(planOrTier) {
|
|
38
|
+
if (!planOrTier) return null;
|
|
39
|
+
try {
|
|
40
|
+
const plans = require('./plans');
|
|
41
|
+
const p = plans.getPlan(planOrTier);
|
|
42
|
+
if (p && p.stripe_price_id) return p.stripe_price_id;
|
|
43
|
+
} catch { /* DB layer not ready (tests) — fall through to legacy lookup */ }
|
|
44
|
+
const envKey = `STRIPE_PRICE_${String(planOrTier).toUpperCase()}`;
|
|
45
|
+
return process.env[envKey] || getPlatformSetting(`stripe_price_${planOrTier}`) || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function createCheckoutSession({ userId, userEmail, siteId, tier, planId }) {
|
|
49
|
+
const site = findSiteById.get(siteId);
|
|
50
|
+
if (!site || site.user_id !== userId) {
|
|
51
|
+
throw new Error('Site not found or access denied');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const s = getStripe();
|
|
55
|
+
if (!s) throw new Error('Stripe not configured');
|
|
56
|
+
|
|
57
|
+
const planRef = planId || tier;
|
|
58
|
+
const priceId = resolvePriceId(planRef);
|
|
59
|
+
if (!priceId) throw new Error(`No price configured for plan: ${planRef}`);
|
|
60
|
+
|
|
61
|
+
// Get or create Stripe customer
|
|
62
|
+
let customer = getStripeCustomer(userId);
|
|
63
|
+
if (!customer) {
|
|
64
|
+
const stripeCustomer = await s.customers.create({ email: userEmail, metadata: { wab_user_id: userId } });
|
|
65
|
+
saveStripeCustomer(userId, stripeCustomer.id);
|
|
66
|
+
customer = { stripe_customer_id: stripeCustomer.id };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const baseUrl = process.env.BASE_URL || 'https://webagentbridge.com';
|
|
70
|
+
|
|
71
|
+
const session = await s.checkout.sessions.create({
|
|
72
|
+
customer: customer.stripe_customer_id,
|
|
73
|
+
mode: 'subscription',
|
|
74
|
+
payment_method_types: ['card'],
|
|
75
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
76
|
+
metadata: { wab_user_id: userId, wab_site_id: siteId, tier: planRef, plan_id: planRef },
|
|
77
|
+
success_url: `${baseUrl}/dashboard?payment=success&session_id={CHECKOUT_SESSION_ID}`,
|
|
78
|
+
cancel_url: `${baseUrl}/dashboard?payment=cancelled`
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return { sessionId: session.id, url: session.url };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function createPortalSession(userId) {
|
|
85
|
+
const s = getStripe();
|
|
86
|
+
if (!s) throw new Error('Stripe not configured');
|
|
87
|
+
|
|
88
|
+
const customer = getStripeCustomer(userId);
|
|
89
|
+
if (!customer) throw new Error('No Stripe customer found');
|
|
90
|
+
|
|
91
|
+
const baseUrl = process.env.BASE_URL || 'https://webagentbridge.com';
|
|
92
|
+
|
|
93
|
+
const session = await s.billingPortal.sessions.create({
|
|
94
|
+
customer: customer.stripe_customer_id,
|
|
95
|
+
return_url: `${baseUrl}/dashboard`
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return { url: session.url };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handleWebhookEvent(event) {
|
|
102
|
+
switch (event.type) {
|
|
103
|
+
case 'checkout.session.completed': {
|
|
104
|
+
const session = event.data.object;
|
|
105
|
+
const { wab_user_id, wab_site_id, tier } = session.metadata || {};
|
|
106
|
+
if (wab_user_id && wab_site_id && session.subscription) {
|
|
107
|
+
saveStripeSubscription({
|
|
108
|
+
userId: wab_user_id,
|
|
109
|
+
siteId: wab_site_id,
|
|
110
|
+
stripeSubId: session.subscription,
|
|
111
|
+
stripePriceId: null,
|
|
112
|
+
tier: tier || 'starter',
|
|
113
|
+
status: 'active',
|
|
114
|
+
periodStart: new Date().toISOString(),
|
|
115
|
+
periodEnd: null
|
|
116
|
+
});
|
|
117
|
+
updateSiteTier.run(tier || 'starter', wab_site_id, wab_user_id);
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case 'invoice.payment_succeeded': {
|
|
123
|
+
const invoice = event.data.object;
|
|
124
|
+
if (invoice.subscription) {
|
|
125
|
+
const sub = getStripeSubscriptionBySubId(invoice.subscription);
|
|
126
|
+
if (sub) {
|
|
127
|
+
savePayment({
|
|
128
|
+
userId: sub.user_id,
|
|
129
|
+
stripePaymentId: invoice.payment_intent,
|
|
130
|
+
amount: invoice.amount_paid,
|
|
131
|
+
currency: invoice.currency,
|
|
132
|
+
status: 'succeeded',
|
|
133
|
+
description: `Subscription payment - ${sub.tier}`
|
|
134
|
+
});
|
|
135
|
+
updateStripeSubscription(invoice.subscription, {
|
|
136
|
+
status: 'active',
|
|
137
|
+
periodStart: new Date(invoice.period_start * 1000).toISOString(),
|
|
138
|
+
periodEnd: new Date(invoice.period_end * 1000).toISOString()
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'customer.subscription.updated': {
|
|
146
|
+
const subscription = event.data.object;
|
|
147
|
+
updateStripeSubscription(subscription.id, {
|
|
148
|
+
status: subscription.status === 'active' ? 'active' : subscription.status === 'past_due' ? 'past_due' : subscription.status === 'trialing' ? 'trialing' : 'cancelled'
|
|
149
|
+
});
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'customer.subscription.deleted': {
|
|
154
|
+
const subscription = event.data.object;
|
|
155
|
+
const sub = getStripeSubscriptionBySubId(subscription.id);
|
|
156
|
+
if (sub) {
|
|
157
|
+
updateStripeSubscription(subscription.id, { status: 'cancelled' });
|
|
158
|
+
updateSiteTier.run('free', sub.site_id, sub.user_id);
|
|
159
|
+
}
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 'invoice.payment_failed': {
|
|
164
|
+
const invoice = event.data.object;
|
|
165
|
+
if (invoice.subscription) {
|
|
166
|
+
updateStripeSubscription(invoice.subscription, { status: 'past_due' });
|
|
167
|
+
}
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isStripeConfigured() {
|
|
174
|
+
const key = process.env.STRIPE_SECRET_KEY || getPlatformSetting('stripe_secret_key');
|
|
175
|
+
return !!key;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Express webhook handler: verifies Stripe signature, then dispatches business logic.
|
|
180
|
+
*/
|
|
181
|
+
function handleWebhookRequest(req) {
|
|
182
|
+
const sig = req.headers['stripe-signature'];
|
|
183
|
+
const raw = req.body;
|
|
184
|
+
const whSecret = process.env.STRIPE_WEBHOOK_SECRET || getPlatformSetting('stripe_webhook_secret');
|
|
185
|
+
if (!whSecret) {
|
|
186
|
+
throw new Error('Stripe webhook secret not configured (STRIPE_WEBHOOK_SECRET or platform stripe_webhook_secret)');
|
|
187
|
+
}
|
|
188
|
+
if (!sig) {
|
|
189
|
+
throw new Error('Missing Stripe-Signature header');
|
|
190
|
+
}
|
|
191
|
+
const s = getStripe();
|
|
192
|
+
if (!s) throw new Error('Stripe not configured');
|
|
193
|
+
const event = s.webhooks.constructEvent(raw, sig, whSecret);
|
|
194
|
+
handleWebhookEvent(event);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
getStripe,
|
|
199
|
+
createCheckoutSession,
|
|
200
|
+
createPortalSession,
|
|
201
|
+
handleWebhookEvent,
|
|
202
|
+
handleWebhookRequest,
|
|
203
|
+
isStripeConfigured,
|
|
204
|
+
getStripePrices
|
|
205
|
+
};
|