@sonicjs-cms/core 2.14.0 → 2.15.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/dist/admin-layout-catalyst.template-HFD37TY5.cjs +17 -0
- package/dist/admin-layout-catalyst.template-HFD37TY5.cjs.map +1 -0
- package/dist/admin-layout-catalyst.template-UMTIN66R.js +7 -0
- package/dist/admin-layout-catalyst.template-UMTIN66R.js.map +1 -0
- package/dist/{chunk-DRWSHIFG.cjs → chunk-26HYU7MX.cjs} +228 -658
- package/dist/chunk-26HYU7MX.cjs.map +1 -0
- package/dist/{chunk-AFGOH2F6.js → chunk-2BL2A62D.js} +4 -4
- package/dist/{chunk-AFGOH2F6.js.map → chunk-2BL2A62D.js.map} +1 -1
- package/dist/{chunk-I6FFGQIT.cjs → chunk-43AB4EH4.cjs} +723 -211
- package/dist/chunk-43AB4EH4.cjs.map +1 -0
- package/dist/{chunk-3QCEYJLK.cjs → chunk-4ZSNJDLS.cjs} +9 -9
- package/dist/{chunk-3QCEYJLK.cjs.map → chunk-4ZSNJDLS.cjs.map} +1 -1
- package/dist/chunk-55RDMDOP.js +684 -0
- package/dist/chunk-55RDMDOP.js.map +1 -0
- package/dist/{chunk-JKNKO6LA.js → chunk-5SOFMH66.js} +2 -2
- package/dist/{chunk-JKNKO6LA.js.map → chunk-5SOFMH66.js.map} +1 -1
- package/dist/{chunk-6FHNRRJ3.cjs → chunk-635JAMSE.cjs} +76 -17
- package/dist/chunk-635JAMSE.cjs.map +1 -0
- package/dist/{chunk-23DP6TO5.js → chunk-7MMD5WMK.js} +44 -474
- package/dist/chunk-7MMD5WMK.js.map +1 -0
- package/dist/{chunk-56GUBLJE.cjs → chunk-ABB34XUS.cjs} +13 -13
- package/dist/{chunk-56GUBLJE.cjs.map → chunk-ABB34XUS.cjs.map} +1 -1
- package/dist/{chunk-GAVTTYMC.js → chunk-EWXV2KG2.js} +3 -3
- package/dist/{chunk-GAVTTYMC.js.map → chunk-EWXV2KG2.js.map} +1 -1
- package/dist/{chunk-J5WGMRSU.js → chunk-EXNEW5US.js} +76 -17
- package/dist/chunk-EXNEW5US.js.map +1 -0
- package/dist/{chunk-H3XXBAMO.js → chunk-G7XSN72O.js} +722 -212
- package/dist/chunk-G7XSN72O.js.map +1 -0
- package/dist/{chunk-QP3OHHON.cjs → chunk-OHYBNCVL.cjs} +18 -696
- package/dist/chunk-OHYBNCVL.cjs.map +1 -0
- package/dist/{chunk-CB7ONLGB.js → chunk-ON5ZMSU4.js} +3 -3
- package/dist/{chunk-CB7ONLGB.js.map → chunk-ON5ZMSU4.js.map} +1 -1
- package/dist/{chunk-KZ2MFGET.cjs → chunk-RVD7PLMU.cjs} +2 -2
- package/dist/{chunk-KZ2MFGET.cjs.map → chunk-RVD7PLMU.cjs.map} +1 -1
- package/dist/{chunk-2MXF4RYZ.js → chunk-TFNTM3OA.js} +3 -3
- package/dist/{chunk-2MXF4RYZ.js.map → chunk-TFNTM3OA.js.map} +1 -1
- package/dist/{chunk-YYMPHM3I.cjs → chunk-UFPT5KCQ.cjs} +8 -8
- package/dist/{chunk-YYMPHM3I.cjs.map → chunk-UFPT5KCQ.cjs.map} +1 -1
- package/dist/chunk-UYJ6TJHX.cjs +691 -0
- package/dist/chunk-UYJ6TJHX.cjs.map +1 -0
- package/dist/{chunk-YULUPQZV.cjs → chunk-VUISYUHY.cjs} +3 -3
- package/dist/{chunk-YULUPQZV.cjs.map → chunk-VUISYUHY.cjs.map} +1 -1
- package/dist/{chunk-JTUCC6WZ.js → chunk-XWIA3HVX.js} +9 -683
- package/dist/chunk-XWIA3HVX.js.map +1 -0
- package/dist/index.cjs +910 -233
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +765 -88
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +29 -29
- package/dist/middleware.js +3 -3
- package/dist/migrations-APFGYCB6.cjs +13 -0
- package/dist/{migrations-F7KVA74T.cjs.map → migrations-APFGYCB6.cjs.map} +1 -1
- package/dist/migrations-YB52SLW7.js +4 -0
- package/dist/{migrations-WKONKRN7.js.map → migrations-YB52SLW7.js.map} +1 -1
- package/dist/{plugin-bootstrap-BGwBraaN.d.cts → plugin-bootstrap-DfVerYV4.d.cts} +2 -1
- package/dist/{plugin-bootstrap-Drns7X9w.d.ts → plugin-bootstrap-P_ciLp_C.d.ts} +2 -1
- package/dist/plugins.cjs +11 -11
- package/dist/plugins.js +2 -2
- package/dist/routes.cjs +31 -30
- package/dist/routes.js +8 -7
- package/dist/services.cjs +23 -23
- package/dist/services.js +2 -2
- package/dist/templates.cjs +26 -25
- package/dist/templates.js +3 -2
- package/dist/utils.cjs +11 -11
- package/dist/utils.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-23DP6TO5.js.map +0 -1
- package/dist/chunk-6FHNRRJ3.cjs.map +0 -1
- package/dist/chunk-DRWSHIFG.cjs.map +0 -1
- package/dist/chunk-H3XXBAMO.js.map +0 -1
- package/dist/chunk-I6FFGQIT.cjs.map +0 -1
- package/dist/chunk-J5WGMRSU.js.map +0 -1
- package/dist/chunk-JTUCC6WZ.js.map +0 -1
- package/dist/chunk-QP3OHHON.cjs.map +0 -1
- package/dist/migrations-F7KVA74T.cjs +0 -13
- package/dist/migrations-WKONKRN7.js +0 -4
package/dist/index.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { renderConfirmationDialog, getConfirmationDialogScript, api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminFormsRoutes, adminSettingsRoutes, public_forms_default, router2, admin_content_default, adminMediaRoutes, userProfilesPlugin, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-
|
|
2
|
-
export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes, createUserProfilesPlugin, defineUserProfile, getUserProfileConfig, userProfilesPlugin } from './chunk-
|
|
1
|
+
import { renderConfirmationDialog, getConfirmationDialogScript, api_default, api_media_default, api_system_default, admin_api_default, router, adminCollectionsRoutes, adminFormsRoutes, adminSettingsRoutes, public_forms_default, router2, admin_content_default, adminMediaRoutes, userProfilesPlugin, adminPluginRoutes, adminLogsRoutes, userRoutes, auth_default, test_cleanup_default } from './chunk-7MMD5WMK.js';
|
|
2
|
+
export { ROUTES_INFO, admin_api_default as adminApiRoutes, adminCheckboxRoutes, admin_code_examples_default as adminCodeExamplesRoutes, adminCollectionsRoutes, admin_content_default as adminContentRoutes, router as adminDashboardRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_testimonials_default as adminTestimonialsRoutes, userRoutes as adminUsersRoutes, api_content_crud_default as apiContentCrudRoutes, api_media_default as apiMediaRoutes, api_default as apiRoutes, api_system_default as apiSystemRoutes, auth_default as authRoutes, createUserProfilesPlugin, defineUserProfile, getUserProfileConfig, userProfilesPlugin } from './chunk-7MMD5WMK.js';
|
|
3
3
|
import { SettingsService, setAppInstance, schema_exports } from './chunk-TBJY2FF7.js';
|
|
4
4
|
export { Logger, apiTokens, collections, content, contentVersions, getLogger, initLogger, insertCollectionSchema, insertContentSchema, insertLogConfigSchema, insertMediaSchema, insertPluginActivityLogSchema, insertPluginAssetSchema, insertPluginHookSchema, insertPluginRouteSchema, insertPluginSchema, insertSystemLogSchema, insertUserSchema, insertWorkflowHistorySchema, logConfig, media, pluginActivityLog, pluginAssets, pluginHooks, pluginRoutes, plugins, selectCollectionSchema, selectContentSchema, selectLogConfigSchema, selectMediaSchema, selectPluginActivityLogSchema, selectPluginAssetSchema, selectPluginHookSchema, selectPluginRouteSchema, selectPluginSchema, selectSystemLogSchema, selectUserSchema, selectWorkflowHistorySchema, systemLogs, users, workflowHistory } from './chunk-TBJY2FF7.js';
|
|
5
|
-
import { requireAuth, AuthManager, metricsMiddleware, bootstrapMiddleware, securityHeadersMiddleware, csrfProtection } from './chunk-
|
|
6
|
-
export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeadersMiddleware as securityHeaders, securityLoggingMiddleware } from './chunk-
|
|
7
|
-
import { PluginService } from './chunk-
|
|
8
|
-
export { PluginBootstrapService, PluginService as PluginServiceClass, backfillFormSubmissions, cleanupRemovedCollections, createContentFromSubmission, deriveCollectionSchemaFromFormio, deriveSubmissionTitle, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, mapFormStatusToContentStatus, registerCollections, syncAllFormCollections, syncCollection, syncCollections, syncFormCollection, validateCollectionConfig } from './chunk-
|
|
9
|
-
export { MigrationService } from './chunk-
|
|
10
|
-
export { renderFilterBar } from './chunk-
|
|
11
|
-
import {
|
|
12
|
-
export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
import { requireAuth, AuthManager, metricsMiddleware, bootstrapMiddleware, securityHeadersMiddleware, csrfProtection } from './chunk-2BL2A62D.js';
|
|
6
|
+
export { AuthManager, PermissionManager, bootstrapMiddleware, cacheHeaders, compressionMiddleware, detailedLoggingMiddleware, getActivePlugins, isPluginActive, logActivity, loggingMiddleware, optionalAuth, performanceLoggingMiddleware, requireActivePlugin, requireActivePlugins, requireAnyPermission, requireAuth, requirePermission, requireRole, securityHeadersMiddleware as securityHeaders, securityLoggingMiddleware } from './chunk-2BL2A62D.js';
|
|
7
|
+
import { PluginService, PLUGIN_REGISTRY } from './chunk-G7XSN72O.js';
|
|
8
|
+
export { PluginBootstrapService, PluginService as PluginServiceClass, backfillFormSubmissions, cleanupRemovedCollections, createContentFromSubmission, deriveCollectionSchemaFromFormio, deriveSubmissionTitle, fullCollectionSync, getAvailableCollectionNames, getManagedCollections, isCollectionManaged, loadCollectionConfig, loadCollectionConfigs, mapFormStatusToContentStatus, registerCollections, syncAllFormCollections, syncCollection, syncCollections, syncFormCollection, validateCollectionConfig } from './chunk-G7XSN72O.js';
|
|
9
|
+
export { MigrationService } from './chunk-5SOFMH66.js';
|
|
10
|
+
export { renderFilterBar } from './chunk-ON5ZMSU4.js';
|
|
11
|
+
import { renderAdminLayout } from './chunk-XWIA3HVX.js';
|
|
12
|
+
export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderForm, renderFormField, renderPagination, renderTable } from './chunk-XWIA3HVX.js';
|
|
13
|
+
import { init_admin_layout_catalyst_template, renderAdminLayoutCatalyst } from './chunk-55RDMDOP.js';
|
|
14
|
+
export { HookSystemImpl, HookUtils, PluginManager as PluginManagerClass, PluginRegistryImpl, PluginValidator as PluginValidatorClass, ScopedHookSystem as ScopedHookSystemClass } from './chunk-TFNTM3OA.js';
|
|
15
|
+
import { PluginBuilder } from './chunk-EXNEW5US.js';
|
|
16
|
+
export { PluginBuilder, PluginHelpers } from './chunk-EXNEW5US.js';
|
|
17
|
+
import { package_default, getCoreVersion } from './chunk-EWXV2KG2.js';
|
|
18
|
+
export { QueryFilterBuilder, SONICJS_VERSION, TemplateRenderer, buildQuery, getCoreVersion, renderTemplate, templateRenderer } from './chunk-EWXV2KG2.js';
|
|
18
19
|
import './chunk-X7ZAEI5S.js';
|
|
19
20
|
export { metricsTracker } from './chunk-FICTAGD4.js';
|
|
20
21
|
export { escapeHtml, sanitizeInput, sanitizeObject } from './chunk-TQABQWOP.js';
|
|
@@ -6328,6 +6329,33 @@ var SubscriptionService = class {
|
|
|
6328
6329
|
).first();
|
|
6329
6330
|
return this.mapRow(result);
|
|
6330
6331
|
}
|
|
6332
|
+
/**
|
|
6333
|
+
* Upsert a subscription by stripe_subscription_id (INSERT or UPDATE on conflict)
|
|
6334
|
+
*/
|
|
6335
|
+
async upsert(data) {
|
|
6336
|
+
const result = await this.db.prepare(`
|
|
6337
|
+
INSERT INTO subscriptions (user_id, stripe_customer_id, stripe_subscription_id, stripe_price_id, status, current_period_start, current_period_end, cancel_at_period_end)
|
|
6338
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
6339
|
+
ON CONFLICT(stripe_subscription_id) DO UPDATE SET
|
|
6340
|
+
status = excluded.status,
|
|
6341
|
+
stripe_price_id = excluded.stripe_price_id,
|
|
6342
|
+
current_period_start = excluded.current_period_start,
|
|
6343
|
+
current_period_end = excluded.current_period_end,
|
|
6344
|
+
cancel_at_period_end = excluded.cancel_at_period_end,
|
|
6345
|
+
updated_at = unixepoch()
|
|
6346
|
+
RETURNING *
|
|
6347
|
+
`).bind(
|
|
6348
|
+
data.userId,
|
|
6349
|
+
data.stripeCustomerId,
|
|
6350
|
+
data.stripeSubscriptionId,
|
|
6351
|
+
data.stripePriceId,
|
|
6352
|
+
data.status,
|
|
6353
|
+
data.currentPeriodStart,
|
|
6354
|
+
data.currentPeriodEnd,
|
|
6355
|
+
data.cancelAtPeriodEnd ? 1 : 0
|
|
6356
|
+
).first();
|
|
6357
|
+
return this.mapRow(result);
|
|
6358
|
+
}
|
|
6331
6359
|
/**
|
|
6332
6360
|
* Update a subscription by its Stripe subscription ID
|
|
6333
6361
|
*/
|
|
@@ -6482,24 +6510,183 @@ var SubscriptionService = class {
|
|
|
6482
6510
|
}
|
|
6483
6511
|
};
|
|
6484
6512
|
|
|
6513
|
+
// src/plugins/core-plugins/stripe-plugin/services/stripe-event-service.ts
|
|
6514
|
+
var StripeEventService = class {
|
|
6515
|
+
constructor(db) {
|
|
6516
|
+
this.db = db;
|
|
6517
|
+
}
|
|
6518
|
+
async ensureTable() {
|
|
6519
|
+
await this.db.prepare(`
|
|
6520
|
+
CREATE TABLE IF NOT EXISTS stripe_events (
|
|
6521
|
+
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
6522
|
+
stripe_event_id TEXT NOT NULL UNIQUE,
|
|
6523
|
+
type TEXT NOT NULL,
|
|
6524
|
+
object_id TEXT NOT NULL DEFAULT '',
|
|
6525
|
+
object_type TEXT NOT NULL DEFAULT '',
|
|
6526
|
+
data TEXT NOT NULL DEFAULT '{}',
|
|
6527
|
+
processed_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
6528
|
+
status TEXT NOT NULL DEFAULT 'processed',
|
|
6529
|
+
error TEXT
|
|
6530
|
+
)
|
|
6531
|
+
`).run();
|
|
6532
|
+
await this.db.prepare(`
|
|
6533
|
+
CREATE INDEX IF NOT EXISTS idx_stripe_events_type ON stripe_events(type)
|
|
6534
|
+
`).run();
|
|
6535
|
+
await this.db.prepare(`
|
|
6536
|
+
CREATE INDEX IF NOT EXISTS idx_stripe_events_status ON stripe_events(status)
|
|
6537
|
+
`).run();
|
|
6538
|
+
await this.db.prepare(`
|
|
6539
|
+
CREATE INDEX IF NOT EXISTS idx_stripe_events_processed_at ON stripe_events(processed_at DESC)
|
|
6540
|
+
`).run();
|
|
6541
|
+
}
|
|
6542
|
+
async log(event) {
|
|
6543
|
+
await this.db.prepare(`
|
|
6544
|
+
INSERT INTO stripe_events (stripe_event_id, type, object_id, object_type, data, status, error)
|
|
6545
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
6546
|
+
ON CONFLICT(stripe_event_id) DO UPDATE SET
|
|
6547
|
+
status = excluded.status,
|
|
6548
|
+
error = excluded.error,
|
|
6549
|
+
processed_at = unixepoch()
|
|
6550
|
+
`).bind(
|
|
6551
|
+
event.stripeEventId,
|
|
6552
|
+
event.type,
|
|
6553
|
+
event.objectId,
|
|
6554
|
+
event.objectType,
|
|
6555
|
+
JSON.stringify(event.data),
|
|
6556
|
+
event.status,
|
|
6557
|
+
event.error || null
|
|
6558
|
+
).run();
|
|
6559
|
+
}
|
|
6560
|
+
async list(filters = {}) {
|
|
6561
|
+
const where = [];
|
|
6562
|
+
const values = [];
|
|
6563
|
+
if (filters.type) {
|
|
6564
|
+
where.push("type = ?");
|
|
6565
|
+
values.push(filters.type);
|
|
6566
|
+
}
|
|
6567
|
+
if (filters.status) {
|
|
6568
|
+
where.push("status = ?");
|
|
6569
|
+
values.push(filters.status);
|
|
6570
|
+
}
|
|
6571
|
+
if (filters.objectId) {
|
|
6572
|
+
where.push("object_id = ?");
|
|
6573
|
+
values.push(filters.objectId);
|
|
6574
|
+
}
|
|
6575
|
+
const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
|
|
6576
|
+
const limit = Math.min(filters.limit || 50, 100);
|
|
6577
|
+
const page = filters.page || 1;
|
|
6578
|
+
const offset = (page - 1) * limit;
|
|
6579
|
+
const countResult = await this.db.prepare(
|
|
6580
|
+
`SELECT COUNT(*) as count FROM stripe_events ${whereClause}`
|
|
6581
|
+
).bind(...values).first();
|
|
6582
|
+
const results = await this.db.prepare(
|
|
6583
|
+
`SELECT * FROM stripe_events ${whereClause} ORDER BY processed_at DESC LIMIT ? OFFSET ?`
|
|
6584
|
+
).bind(...values, limit, offset).all();
|
|
6585
|
+
return {
|
|
6586
|
+
events: (results.results || []).map((r) => this.mapRow(r)),
|
|
6587
|
+
total: countResult?.count || 0
|
|
6588
|
+
};
|
|
6589
|
+
}
|
|
6590
|
+
async getStats() {
|
|
6591
|
+
const result = await this.db.prepare(`
|
|
6592
|
+
SELECT
|
|
6593
|
+
COUNT(*) as total,
|
|
6594
|
+
SUM(CASE WHEN status = 'processed' THEN 1 ELSE 0 END) as processed,
|
|
6595
|
+
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed,
|
|
6596
|
+
SUM(CASE WHEN status = 'ignored' THEN 1 ELSE 0 END) as ignored
|
|
6597
|
+
FROM stripe_events
|
|
6598
|
+
`).first();
|
|
6599
|
+
return {
|
|
6600
|
+
total: result?.total || 0,
|
|
6601
|
+
processed: result?.processed || 0,
|
|
6602
|
+
failed: result?.failed || 0,
|
|
6603
|
+
ignored: result?.ignored || 0
|
|
6604
|
+
};
|
|
6605
|
+
}
|
|
6606
|
+
async getDistinctTypes() {
|
|
6607
|
+
const results = await this.db.prepare(
|
|
6608
|
+
"SELECT DISTINCT type FROM stripe_events ORDER BY type"
|
|
6609
|
+
).all();
|
|
6610
|
+
return (results.results || []).map((r) => r.type);
|
|
6611
|
+
}
|
|
6612
|
+
mapRow(row) {
|
|
6613
|
+
return {
|
|
6614
|
+
id: row.id,
|
|
6615
|
+
stripeEventId: row.stripe_event_id,
|
|
6616
|
+
type: row.type,
|
|
6617
|
+
objectId: row.object_id,
|
|
6618
|
+
objectType: row.object_type,
|
|
6619
|
+
data: row.data,
|
|
6620
|
+
processedAt: row.processed_at,
|
|
6621
|
+
status: row.status,
|
|
6622
|
+
error: row.error || void 0
|
|
6623
|
+
};
|
|
6624
|
+
}
|
|
6625
|
+
};
|
|
6626
|
+
|
|
6485
6627
|
// src/plugins/core-plugins/stripe-plugin/components/subscriptions-page.ts
|
|
6486
|
-
|
|
6628
|
+
init_admin_layout_catalyst_template();
|
|
6629
|
+
|
|
6630
|
+
// src/plugins/core-plugins/stripe-plugin/components/tab-bar.ts
|
|
6631
|
+
var TABS = [
|
|
6632
|
+
{ label: "Subscriptions", path: "/admin/plugins/stripe" },
|
|
6633
|
+
{ label: "Events", path: "/admin/plugins/stripe/events" },
|
|
6634
|
+
{ label: "Settings", path: "/admin/plugins/stripe/settings" }
|
|
6635
|
+
];
|
|
6636
|
+
function renderStripeTabBar(currentPath) {
|
|
6637
|
+
const tabs = TABS.map((tab) => {
|
|
6638
|
+
const isActive = currentPath === tab.path || tab.path === "/admin/plugins/stripe" && currentPath === "/admin/plugins/stripe/";
|
|
6639
|
+
return `
|
|
6640
|
+
<a href="${tab.path}"
|
|
6641
|
+
class="${isActive ? "border-cyan-500 text-zinc-950 dark:text-white" : "border-transparent text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300 hover:border-zinc-300 dark:hover:border-zinc-600"} whitespace-nowrap border-b-2 px-4 py-3 text-sm font-medium transition-colors">
|
|
6642
|
+
${tab.label}
|
|
6643
|
+
</a>`;
|
|
6644
|
+
}).join("");
|
|
6487
6645
|
return `
|
|
6488
|
-
<div class="
|
|
6646
|
+
<div class="border-b border-zinc-950/5 dark:border-white/10 mb-6">
|
|
6647
|
+
<nav class="-mb-px flex gap-x-2" aria-label="Stripe tabs">
|
|
6648
|
+
${tabs}
|
|
6649
|
+
</nav>
|
|
6650
|
+
</div>
|
|
6651
|
+
`;
|
|
6652
|
+
}
|
|
6653
|
+
|
|
6654
|
+
// src/plugins/core-plugins/stripe-plugin/components/subscriptions-page.ts
|
|
6655
|
+
function renderSubscriptionsPage(data) {
|
|
6656
|
+
const { subscriptions, stats, filters, user, version, dynamicMenuItems } = data;
|
|
6657
|
+
const content2 = `
|
|
6658
|
+
<div>
|
|
6659
|
+
<div class="sm:flex sm:items-center sm:justify-between mb-6">
|
|
6660
|
+
<div class="sm:flex-auto">
|
|
6661
|
+
<h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">Stripe</h1>
|
|
6662
|
+
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
|
|
6663
|
+
Manage subscriptions, view billing status, and monitor payment events.
|
|
6664
|
+
</p>
|
|
6665
|
+
</div>
|
|
6666
|
+
<div class="mt-4 sm:mt-0 sm:ml-16">
|
|
6667
|
+
<button id="sync-btn" onclick="syncSubscriptions()"
|
|
6668
|
+
class="inline-flex items-center justify-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm">
|
|
6669
|
+
Sync from Stripe
|
|
6670
|
+
</button>
|
|
6671
|
+
</div>
|
|
6672
|
+
</div>
|
|
6673
|
+
|
|
6674
|
+
${renderStripeTabBar("/admin/plugins/stripe")}
|
|
6675
|
+
|
|
6489
6676
|
<!-- Stats Cards -->
|
|
6490
|
-
<div class="grid grid-cols-1
|
|
6491
|
-
${statsCard("Total", stats.total, "text-
|
|
6492
|
-
${statsCard("Active", stats.active, "text-
|
|
6493
|
-
${statsCard("Trialing", stats.trialing, "text-blue-600")}
|
|
6494
|
-
${statsCard("Past Due", stats.pastDue, "text-
|
|
6495
|
-
${statsCard("Canceled", stats.canceled, "text-red-600")}
|
|
6677
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5 mb-6">
|
|
6678
|
+
${statsCard("Total", stats.total, "text-zinc-950 dark:text-white")}
|
|
6679
|
+
${statsCard("Active", stats.active, "text-emerald-600 dark:text-emerald-400")}
|
|
6680
|
+
${statsCard("Trialing", stats.trialing, "text-blue-600 dark:text-blue-400")}
|
|
6681
|
+
${statsCard("Past Due", stats.pastDue, "text-amber-600 dark:text-amber-400")}
|
|
6682
|
+
${statsCard("Canceled", stats.canceled, "text-red-600 dark:text-red-400")}
|
|
6496
6683
|
</div>
|
|
6497
6684
|
|
|
6498
6685
|
<!-- Filters -->
|
|
6499
|
-
<div class="bg-white
|
|
6686
|
+
<div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl p-4 ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm mb-6">
|
|
6500
6687
|
<form method="GET" class="flex items-center gap-4">
|
|
6501
|
-
<label class="text-sm font-medium text-
|
|
6502
|
-
<select name="status" class="border
|
|
6688
|
+
<label class="text-sm font-medium text-zinc-500 dark:text-zinc-400">Status:</label>
|
|
6689
|
+
<select name="status" class="rounded-lg border-0 bg-white dark:bg-zinc-800 px-3 py-1.5 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10" onchange="this.form.submit()">
|
|
6503
6690
|
<option value="">All</option>
|
|
6504
6691
|
${statusOption("active", filters.status)}
|
|
6505
6692
|
${statusOption("trialing", filters.status)}
|
|
@@ -6512,33 +6699,72 @@ function renderSubscriptionsPage(subscriptions, stats, filters) {
|
|
|
6512
6699
|
</div>
|
|
6513
6700
|
|
|
6514
6701
|
<!-- Subscriptions Table -->
|
|
6515
|
-
<div class="bg-white
|
|
6516
|
-
<table class="min-w-full divide-y divide-
|
|
6517
|
-
<thead
|
|
6702
|
+
<div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm overflow-hidden">
|
|
6703
|
+
<table class="min-w-full divide-y divide-zinc-950/5 dark:divide-white/5">
|
|
6704
|
+
<thead>
|
|
6518
6705
|
<tr>
|
|
6519
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-
|
|
6520
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-
|
|
6521
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-
|
|
6522
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-
|
|
6523
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-
|
|
6524
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-
|
|
6706
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">User</th>
|
|
6707
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Status</th>
|
|
6708
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Price ID</th>
|
|
6709
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Current Period</th>
|
|
6710
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Cancel at End</th>
|
|
6711
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Stripe</th>
|
|
6525
6712
|
</tr>
|
|
6526
6713
|
</thead>
|
|
6527
|
-
<tbody class="
|
|
6528
|
-
${subscriptions.length === 0 ? '<tr><td colspan="6" class="px-6 py-8 text-center text-
|
|
6714
|
+
<tbody class="divide-y divide-zinc-950/5 dark:divide-white/5">
|
|
6715
|
+
${subscriptions.length === 0 ? '<tr><td colspan="6" class="px-6 py-8 text-center text-zinc-500 dark:text-zinc-400">No subscriptions found</td></tr>' : subscriptions.map(renderRow).join("")}
|
|
6529
6716
|
</tbody>
|
|
6530
6717
|
</table>
|
|
6531
6718
|
|
|
6532
6719
|
${renderPagination2(filters.page, filters.totalPages, filters.status)}
|
|
6533
6720
|
</div>
|
|
6721
|
+
|
|
6722
|
+
<div id="sync-message" class="hidden mt-4 rounded-lg p-4 text-sm"></div>
|
|
6534
6723
|
</div>
|
|
6724
|
+
|
|
6725
|
+
<script>
|
|
6726
|
+
async function syncSubscriptions() {
|
|
6727
|
+
const btn = document.getElementById('sync-btn')
|
|
6728
|
+
const msg = document.getElementById('sync-message')
|
|
6729
|
+
btn.disabled = true
|
|
6730
|
+
btn.textContent = 'Syncing...'
|
|
6731
|
+
msg.className = 'hidden mt-4 rounded-lg p-4 text-sm'
|
|
6732
|
+
try {
|
|
6733
|
+
const res = await fetch('/api/stripe/sync-subscriptions', { method: 'POST' })
|
|
6734
|
+
const result = await res.json()
|
|
6735
|
+
if (result.success) {
|
|
6736
|
+
msg.className = 'mt-4 rounded-lg p-4 text-sm bg-emerald-400/10 text-emerald-500 dark:text-emerald-400 ring-1 ring-inset ring-emerald-400/20'
|
|
6737
|
+
msg.textContent = 'Synced ' + result.synced + ' of ' + result.total + ' subscriptions from Stripe.' + (result.errors > 0 ? ' (' + result.errors + ' errors)' : '')
|
|
6738
|
+
setTimeout(() => location.reload(), 1500)
|
|
6739
|
+
} else {
|
|
6740
|
+
msg.className = 'mt-4 rounded-lg p-4 text-sm bg-red-400/10 text-red-500 dark:text-red-400 ring-1 ring-inset ring-red-400/20'
|
|
6741
|
+
msg.textContent = result.error || 'Sync failed.'
|
|
6742
|
+
}
|
|
6743
|
+
} catch {
|
|
6744
|
+
msg.className = 'mt-4 rounded-lg p-4 text-sm bg-red-400/10 text-red-500 dark:text-red-400 ring-1 ring-inset ring-red-400/20'
|
|
6745
|
+
msg.textContent = 'Network error. Please try again.'
|
|
6746
|
+
}
|
|
6747
|
+
btn.disabled = false
|
|
6748
|
+
btn.textContent = 'Sync from Stripe'
|
|
6749
|
+
}
|
|
6750
|
+
</script>
|
|
6535
6751
|
`;
|
|
6752
|
+
const layoutData = {
|
|
6753
|
+
title: "Stripe Subscriptions",
|
|
6754
|
+
pageTitle: "Stripe Subscriptions",
|
|
6755
|
+
currentPath: "/admin/plugins/stripe",
|
|
6756
|
+
user,
|
|
6757
|
+
content: content2,
|
|
6758
|
+
version,
|
|
6759
|
+
dynamicMenuItems
|
|
6760
|
+
};
|
|
6761
|
+
return renderAdminLayoutCatalyst(layoutData);
|
|
6536
6762
|
}
|
|
6537
6763
|
function statsCard(label, value, colorClass) {
|
|
6538
6764
|
return `
|
|
6539
|
-
<div class="bg-white
|
|
6540
|
-
<
|
|
6541
|
-
<
|
|
6765
|
+
<div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl p-5 ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm">
|
|
6766
|
+
<p class="text-sm font-medium text-zinc-500 dark:text-zinc-400">${label}</p>
|
|
6767
|
+
<p class="mt-2 text-3xl font-bold ${colorClass}">${value}</p>
|
|
6542
6768
|
</div>
|
|
6543
6769
|
`;
|
|
6544
6770
|
}
|
|
@@ -6549,18 +6775,18 @@ function statusOption(value, current) {
|
|
|
6549
6775
|
}
|
|
6550
6776
|
function statusBadge(status) {
|
|
6551
6777
|
const colors = {
|
|
6552
|
-
active: "bg-
|
|
6553
|
-
trialing: "bg-blue-
|
|
6554
|
-
past_due: "bg-
|
|
6555
|
-
canceled: "bg-red-
|
|
6556
|
-
unpaid: "bg-orange-
|
|
6557
|
-
paused: "bg-
|
|
6558
|
-
incomplete: "bg-
|
|
6559
|
-
incomplete_expired: "bg-red-
|
|
6778
|
+
active: "bg-emerald-400/10 text-emerald-500 dark:text-emerald-400 ring-emerald-400/20",
|
|
6779
|
+
trialing: "bg-blue-400/10 text-blue-500 dark:text-blue-400 ring-blue-400/20",
|
|
6780
|
+
past_due: "bg-amber-400/10 text-amber-500 dark:text-amber-400 ring-amber-400/20",
|
|
6781
|
+
canceled: "bg-red-400/10 text-red-500 dark:text-red-400 ring-red-400/20",
|
|
6782
|
+
unpaid: "bg-orange-400/10 text-orange-500 dark:text-orange-400 ring-orange-400/20",
|
|
6783
|
+
paused: "bg-zinc-400/10 text-zinc-500 dark:text-zinc-400 ring-zinc-400/20",
|
|
6784
|
+
incomplete: "bg-zinc-400/10 text-zinc-500 dark:text-zinc-400 ring-zinc-400/20",
|
|
6785
|
+
incomplete_expired: "bg-red-400/10 text-red-500 dark:text-red-400 ring-red-400/20"
|
|
6560
6786
|
};
|
|
6561
|
-
const color = colors[status] || "bg-
|
|
6787
|
+
const color = colors[status] || "bg-zinc-400/10 text-zinc-500 ring-zinc-400/20";
|
|
6562
6788
|
const label = status.replace("_", " ");
|
|
6563
|
-
return `<span class="inline-flex items-center px-2
|
|
6789
|
+
return `<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ring-1 ring-inset ${color}">${label}</span>`;
|
|
6564
6790
|
}
|
|
6565
6791
|
function formatDate(timestamp) {
|
|
6566
6792
|
if (!timestamp) return "-";
|
|
@@ -6572,23 +6798,23 @@ function formatDate(timestamp) {
|
|
|
6572
6798
|
}
|
|
6573
6799
|
function renderRow(sub) {
|
|
6574
6800
|
return `
|
|
6575
|
-
<tr>
|
|
6801
|
+
<tr class="hover:bg-zinc-950/[0.025] dark:hover:bg-white/[0.025]">
|
|
6576
6802
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
6577
|
-
<div class="text-sm font-medium text-
|
|
6578
|
-
<div class="text-xs text-
|
|
6803
|
+
<div class="text-sm font-medium text-zinc-950 dark:text-white">${sub.userEmail || sub.userId}</div>
|
|
6804
|
+
<div class="text-xs text-zinc-500 dark:text-zinc-400">${sub.stripeCustomerId}</div>
|
|
6579
6805
|
</td>
|
|
6580
6806
|
<td class="px-6 py-4 whitespace-nowrap">${statusBadge(sub.status)}</td>
|
|
6581
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
|
6582
|
-
<td class="px-6 py-4 whitespace-nowrap text-sm text-
|
|
6807
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-500 dark:text-zinc-400">${sub.stripePriceId}</td>
|
|
6808
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-500 dark:text-zinc-400">
|
|
6583
6809
|
${formatDate(sub.currentPeriodStart)} - ${formatDate(sub.currentPeriodEnd)}
|
|
6584
6810
|
</td>
|
|
6585
6811
|
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
|
6586
|
-
${sub.cancelAtPeriodEnd ? '<span class="text-
|
|
6812
|
+
${sub.cancelAtPeriodEnd ? '<span class="text-amber-500 dark:text-amber-400 font-medium">Yes</span>' : '<span class="text-zinc-400 dark:text-zinc-500">No</span>'}
|
|
6587
6813
|
</td>
|
|
6588
6814
|
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
|
6589
6815
|
<a href="https://dashboard.stripe.com/subscriptions/${sub.stripeSubscriptionId}"
|
|
6590
6816
|
target="_blank" rel="noopener noreferrer"
|
|
6591
|
-
class="text-
|
|
6817
|
+
class="text-cyan-600 dark:text-cyan-400 hover:text-cyan-500 dark:hover:text-cyan-300">
|
|
6592
6818
|
View in Stripe
|
|
6593
6819
|
</a>
|
|
6594
6820
|
</td>
|
|
@@ -6599,13 +6825,161 @@ function renderPagination2(page, totalPages, status) {
|
|
|
6599
6825
|
if (totalPages <= 1) return "";
|
|
6600
6826
|
const params = status ? `&status=${status}` : "";
|
|
6601
6827
|
return `
|
|
6602
|
-
<div class="
|
|
6603
|
-
<div class="text-sm text-
|
|
6828
|
+
<div class="px-6 py-3 flex items-center justify-between border-t border-zinc-950/5 dark:border-white/5">
|
|
6829
|
+
<div class="text-sm text-zinc-500 dark:text-zinc-400">
|
|
6830
|
+
Page ${page} of ${totalPages}
|
|
6831
|
+
</div>
|
|
6832
|
+
<div class="flex gap-2">
|
|
6833
|
+
${page > 1 ? `<a href="?page=${page - 1}${params}" class="px-3 py-1 rounded-lg text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-800">Previous</a>` : ""}
|
|
6834
|
+
${page < totalPages ? `<a href="?page=${page + 1}${params}" class="px-3 py-1 rounded-lg text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-800">Next</a>` : ""}
|
|
6835
|
+
</div>
|
|
6836
|
+
</div>
|
|
6837
|
+
`;
|
|
6838
|
+
}
|
|
6839
|
+
|
|
6840
|
+
// src/plugins/core-plugins/stripe-plugin/components/events-page.ts
|
|
6841
|
+
init_admin_layout_catalyst_template();
|
|
6842
|
+
function renderEventsPage(data) {
|
|
6843
|
+
const { events, stats, types, filters, user, version, dynamicMenuItems } = data;
|
|
6844
|
+
const content2 = `
|
|
6845
|
+
<div>
|
|
6846
|
+
<div class="sm:flex sm:items-center sm:justify-between mb-6">
|
|
6847
|
+
<div class="sm:flex-auto">
|
|
6848
|
+
<h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">Stripe</h1>
|
|
6849
|
+
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
|
|
6850
|
+
Webhook event log showing all processed, failed, and ignored Stripe events.
|
|
6851
|
+
</p>
|
|
6852
|
+
</div>
|
|
6853
|
+
</div>
|
|
6854
|
+
|
|
6855
|
+
${renderStripeTabBar("/admin/plugins/stripe/events")}
|
|
6856
|
+
|
|
6857
|
+
<!-- Stats Cards -->
|
|
6858
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4 mb-6">
|
|
6859
|
+
${eventStatsCard("Total Events", stats.total, "text-zinc-950 dark:text-white")}
|
|
6860
|
+
${eventStatsCard("Processed", stats.processed, "text-emerald-600 dark:text-emerald-400")}
|
|
6861
|
+
${eventStatsCard("Failed", stats.failed, "text-red-600 dark:text-red-400")}
|
|
6862
|
+
${eventStatsCard("Ignored", stats.ignored, "text-zinc-500 dark:text-zinc-400")}
|
|
6863
|
+
</div>
|
|
6864
|
+
|
|
6865
|
+
<!-- Filters -->
|
|
6866
|
+
<div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl p-4 ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm mb-6">
|
|
6867
|
+
<form method="GET" class="flex items-center gap-4 flex-wrap">
|
|
6868
|
+
<label class="text-sm font-medium text-zinc-500 dark:text-zinc-400">Type:</label>
|
|
6869
|
+
<select name="type" class="rounded-lg border-0 bg-white dark:bg-zinc-800 px-3 py-1.5 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10" onchange="this.form.submit()">
|
|
6870
|
+
<option value="">All</option>
|
|
6871
|
+
${types.map((t) => `<option value="${t}" ${t === filters.type ? "selected" : ""}>${t}</option>`).join("")}
|
|
6872
|
+
</select>
|
|
6873
|
+
|
|
6874
|
+
<label class="text-sm font-medium text-zinc-500 dark:text-zinc-400">Status:</label>
|
|
6875
|
+
<select name="status" class="rounded-lg border-0 bg-white dark:bg-zinc-800 px-3 py-1.5 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10" onchange="this.form.submit()">
|
|
6876
|
+
<option value="">All</option>
|
|
6877
|
+
${eventStatusOption("processed", filters.status)}
|
|
6878
|
+
${eventStatusOption("failed", filters.status)}
|
|
6879
|
+
${eventStatusOption("ignored", filters.status)}
|
|
6880
|
+
</select>
|
|
6881
|
+
</form>
|
|
6882
|
+
</div>
|
|
6883
|
+
|
|
6884
|
+
<!-- Events Table -->
|
|
6885
|
+
<div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm overflow-hidden">
|
|
6886
|
+
<table class="min-w-full divide-y divide-zinc-950/5 dark:divide-white/5">
|
|
6887
|
+
<thead>
|
|
6888
|
+
<tr>
|
|
6889
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Time</th>
|
|
6890
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Type</th>
|
|
6891
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Object</th>
|
|
6892
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Status</th>
|
|
6893
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider">Event ID</th>
|
|
6894
|
+
</tr>
|
|
6895
|
+
</thead>
|
|
6896
|
+
<tbody class="divide-y divide-zinc-950/5 dark:divide-white/5">
|
|
6897
|
+
${events.length === 0 ? '<tr><td colspan="5" class="px-6 py-8 text-center text-zinc-500 dark:text-zinc-400">No events recorded yet</td></tr>' : events.map(renderEventRow).join("")}
|
|
6898
|
+
</tbody>
|
|
6899
|
+
</table>
|
|
6900
|
+
|
|
6901
|
+
${renderEventPagination(filters.page, filters.totalPages, filters.type, filters.status)}
|
|
6902
|
+
</div>
|
|
6903
|
+
</div>
|
|
6904
|
+
`;
|
|
6905
|
+
const layoutData = {
|
|
6906
|
+
title: "Stripe Events",
|
|
6907
|
+
pageTitle: "Stripe Events",
|
|
6908
|
+
currentPath: "/admin/plugins/stripe",
|
|
6909
|
+
user,
|
|
6910
|
+
content: content2,
|
|
6911
|
+
version,
|
|
6912
|
+
dynamicMenuItems
|
|
6913
|
+
};
|
|
6914
|
+
return renderAdminLayoutCatalyst(layoutData);
|
|
6915
|
+
}
|
|
6916
|
+
function eventStatsCard(label, value, colorClass) {
|
|
6917
|
+
return `
|
|
6918
|
+
<div class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl p-5 ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm">
|
|
6919
|
+
<p class="text-sm font-medium text-zinc-500 dark:text-zinc-400">${label}</p>
|
|
6920
|
+
<p class="mt-2 text-3xl font-bold ${colorClass}">${value}</p>
|
|
6921
|
+
</div>
|
|
6922
|
+
`;
|
|
6923
|
+
}
|
|
6924
|
+
function eventStatusOption(value, current) {
|
|
6925
|
+
const selected = value === current ? "selected" : "";
|
|
6926
|
+
const label = value.charAt(0).toUpperCase() + value.slice(1);
|
|
6927
|
+
return `<option value="${value}" ${selected}>${label}</option>`;
|
|
6928
|
+
}
|
|
6929
|
+
function eventStatusBadge(status) {
|
|
6930
|
+
const colors = {
|
|
6931
|
+
processed: "bg-emerald-400/10 text-emerald-500 dark:text-emerald-400 ring-emerald-400/20",
|
|
6932
|
+
failed: "bg-red-400/10 text-red-500 dark:text-red-400 ring-red-400/20",
|
|
6933
|
+
ignored: "bg-zinc-400/10 text-zinc-500 dark:text-zinc-400 ring-zinc-400/20"
|
|
6934
|
+
};
|
|
6935
|
+
const color = colors[status] || "bg-zinc-400/10 text-zinc-500 ring-zinc-400/20";
|
|
6936
|
+
return `<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ring-1 ring-inset ${color}">${status}</span>`;
|
|
6937
|
+
}
|
|
6938
|
+
function formatTimestamp3(timestamp) {
|
|
6939
|
+
if (!timestamp) return "-";
|
|
6940
|
+
const d = new Date(timestamp * 1e3);
|
|
6941
|
+
return d.toLocaleString("en-US", {
|
|
6942
|
+
month: "short",
|
|
6943
|
+
day: "numeric",
|
|
6944
|
+
year: "numeric",
|
|
6945
|
+
hour: "2-digit",
|
|
6946
|
+
minute: "2-digit",
|
|
6947
|
+
second: "2-digit"
|
|
6948
|
+
});
|
|
6949
|
+
}
|
|
6950
|
+
function renderEventRow(event) {
|
|
6951
|
+
const errorTooltip = event.error ? ` title="${event.error.replace(/"/g, """)}"` : "";
|
|
6952
|
+
return `
|
|
6953
|
+
<tr class="hover:bg-zinc-950/[0.025] dark:hover:bg-white/[0.025]"${errorTooltip}>
|
|
6954
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-zinc-500 dark:text-zinc-400">
|
|
6955
|
+
${formatTimestamp3(event.processedAt)}
|
|
6956
|
+
</td>
|
|
6957
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
6958
|
+
<span class="text-sm font-mono text-zinc-950 dark:text-white">${event.type}</span>
|
|
6959
|
+
</td>
|
|
6960
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
6961
|
+
<div class="text-sm font-mono text-zinc-500 dark:text-zinc-400">${event.objectId || "-"}</div>
|
|
6962
|
+
<div class="text-xs text-zinc-400 dark:text-zinc-500">${event.objectType}</div>
|
|
6963
|
+
</td>
|
|
6964
|
+
<td class="px-6 py-4 whitespace-nowrap">${eventStatusBadge(event.status)}</td>
|
|
6965
|
+
<td class="px-6 py-4 whitespace-nowrap text-xs font-mono text-zinc-400 dark:text-zinc-500">${event.stripeEventId}</td>
|
|
6966
|
+
</tr>
|
|
6967
|
+
`;
|
|
6968
|
+
}
|
|
6969
|
+
function renderEventPagination(page, totalPages, type, status) {
|
|
6970
|
+
if (totalPages <= 1) return "";
|
|
6971
|
+
const params = [];
|
|
6972
|
+
if (type) params.push(`type=${type}`);
|
|
6973
|
+
if (status) params.push(`status=${status}`);
|
|
6974
|
+
const extra = params.length > 0 ? `&${params.join("&")}` : "";
|
|
6975
|
+
return `
|
|
6976
|
+
<div class="px-6 py-3 flex items-center justify-between border-t border-zinc-950/5 dark:border-white/5">
|
|
6977
|
+
<div class="text-sm text-zinc-500 dark:text-zinc-400">
|
|
6604
6978
|
Page ${page} of ${totalPages}
|
|
6605
6979
|
</div>
|
|
6606
6980
|
<div class="flex gap-2">
|
|
6607
|
-
${page > 1 ? `<a href="?page=${page - 1}${
|
|
6608
|
-
${page < totalPages ? `<a href="?page=${page + 1}${
|
|
6981
|
+
${page > 1 ? `<a href="?page=${page - 1}${extra}" class="px-3 py-1 rounded-lg text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-800">Previous</a>` : ""}
|
|
6982
|
+
${page < totalPages ? `<a href="?page=${page + 1}${extra}" class="px-3 py-1 rounded-lg text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-800">Next</a>` : ""}
|
|
6609
6983
|
</div>
|
|
6610
6984
|
</div>
|
|
6611
6985
|
`;
|
|
@@ -6613,6 +6987,7 @@ function renderPagination2(page, totalPages, status) {
|
|
|
6613
6987
|
|
|
6614
6988
|
// src/plugins/core-plugins/stripe-plugin/types.ts
|
|
6615
6989
|
var DEFAULT_SETTINGS3 = {
|
|
6990
|
+
stripePublishableKey: "",
|
|
6616
6991
|
stripeSecretKey: "",
|
|
6617
6992
|
stripeWebhookSecret: "",
|
|
6618
6993
|
stripePriceId: "",
|
|
@@ -6630,8 +7005,21 @@ adminRoutes3.use("*", async (c, next) => {
|
|
|
6630
7005
|
}
|
|
6631
7006
|
return next();
|
|
6632
7007
|
});
|
|
7008
|
+
async function getSettings3(db) {
|
|
7009
|
+
try {
|
|
7010
|
+
const pluginService = new PluginService(db);
|
|
7011
|
+
const plugin2 = await pluginService.getPlugin("stripe");
|
|
7012
|
+
if (plugin2?.settings) {
|
|
7013
|
+
const settings = typeof plugin2.settings === "string" ? JSON.parse(plugin2.settings) : plugin2.settings;
|
|
7014
|
+
return { ...DEFAULT_SETTINGS3, ...settings };
|
|
7015
|
+
}
|
|
7016
|
+
} catch {
|
|
7017
|
+
}
|
|
7018
|
+
return DEFAULT_SETTINGS3;
|
|
7019
|
+
}
|
|
6633
7020
|
adminRoutes3.get("/", async (c) => {
|
|
6634
7021
|
const db = c.env.DB;
|
|
7022
|
+
const user = c.get("user");
|
|
6635
7023
|
const subscriptionService = new SubscriptionService(db);
|
|
6636
7024
|
await subscriptionService.ensureTable();
|
|
6637
7025
|
const page = parseInt(c.req.query("page") || "1");
|
|
@@ -6642,13 +7030,152 @@ adminRoutes3.get("/", async (c) => {
|
|
|
6642
7030
|
subscriptionService.getStats()
|
|
6643
7031
|
]);
|
|
6644
7032
|
const totalPages = Math.ceil(total / limit);
|
|
6645
|
-
const html = renderSubscriptionsPage(
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
totalPages
|
|
7033
|
+
const html = renderSubscriptionsPage({
|
|
7034
|
+
subscriptions,
|
|
7035
|
+
stats,
|
|
7036
|
+
filters: { status: statusFilter, page, totalPages },
|
|
7037
|
+
user: user ? { name: user.email, email: user.email, role: user.role } : void 0,
|
|
7038
|
+
version: c.get("appVersion"),
|
|
7039
|
+
dynamicMenuItems: c.get("pluginMenuItems")
|
|
6649
7040
|
});
|
|
6650
7041
|
return c.html(html);
|
|
6651
7042
|
});
|
|
7043
|
+
adminRoutes3.get("/events", async (c) => {
|
|
7044
|
+
const db = c.env.DB;
|
|
7045
|
+
const user = c.get("user");
|
|
7046
|
+
const eventService = new StripeEventService(db);
|
|
7047
|
+
await eventService.ensureTable();
|
|
7048
|
+
const page = parseInt(c.req.query("page") || "1");
|
|
7049
|
+
const limit = 50;
|
|
7050
|
+
const typeFilter = c.req.query("type") || void 0;
|
|
7051
|
+
const statusFilter = c.req.query("status");
|
|
7052
|
+
const [{ events, total }, stats, types] = await Promise.all([
|
|
7053
|
+
eventService.list({ type: typeFilter, status: statusFilter, page, limit }),
|
|
7054
|
+
eventService.getStats(),
|
|
7055
|
+
eventService.getDistinctTypes()
|
|
7056
|
+
]);
|
|
7057
|
+
const totalPages = Math.ceil(total / limit);
|
|
7058
|
+
const html = renderEventsPage({
|
|
7059
|
+
events,
|
|
7060
|
+
stats,
|
|
7061
|
+
types,
|
|
7062
|
+
filters: { type: typeFilter, status: statusFilter, page, totalPages },
|
|
7063
|
+
user: user ? { name: user.email, email: user.email, role: user.role } : void 0,
|
|
7064
|
+
version: c.get("appVersion"),
|
|
7065
|
+
dynamicMenuItems: c.get("pluginMenuItems")
|
|
7066
|
+
});
|
|
7067
|
+
return c.html(html);
|
|
7068
|
+
});
|
|
7069
|
+
adminRoutes3.get("/settings", async (c) => {
|
|
7070
|
+
const db = c.env.DB;
|
|
7071
|
+
const user = c.get("user");
|
|
7072
|
+
const settings = await getSettings3(db);
|
|
7073
|
+
const { renderAdminLayoutCatalyst: renderAdminLayoutCatalyst2 } = await import('./admin-layout-catalyst.template-UMTIN66R.js');
|
|
7074
|
+
const content2 = `
|
|
7075
|
+
<div>
|
|
7076
|
+
<div class="mb-6">
|
|
7077
|
+
<h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">Stripe</h1>
|
|
7078
|
+
<p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
|
|
7079
|
+
Configure your Stripe API keys and checkout options.
|
|
7080
|
+
</p>
|
|
7081
|
+
</div>
|
|
7082
|
+
|
|
7083
|
+
${renderStripeTabBar("/admin/plugins/stripe/settings")}
|
|
7084
|
+
|
|
7085
|
+
<div id="settings-message" class="hidden mb-4 rounded-lg p-4 text-sm"></div>
|
|
7086
|
+
|
|
7087
|
+
<form id="stripe-settings-form" class="rounded-xl bg-white/80 dark:bg-zinc-900/80 backdrop-blur-xl ring-1 ring-zinc-950/5 dark:ring-white/10 shadow-sm divide-y divide-zinc-950/5 dark:divide-white/5">
|
|
7088
|
+
<div class="p-6 space-y-5">
|
|
7089
|
+
<div>
|
|
7090
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-1.5">Publishable Key</label>
|
|
7091
|
+
<input type="text" name="stripePublishableKey" value="${settings.stripePublishableKey}"
|
|
7092
|
+
placeholder="pk_..."
|
|
7093
|
+
class="w-full rounded-lg border-0 bg-white dark:bg-zinc-800 px-3.5 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:ring-2 focus:ring-cyan-500" />
|
|
7094
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Your Stripe publishable key (starts with pk_)</p>
|
|
7095
|
+
</div>
|
|
7096
|
+
|
|
7097
|
+
<div>
|
|
7098
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-1.5">Secret Key</label>
|
|
7099
|
+
<input type="password" name="stripeSecretKey" value="${settings.stripeSecretKey}"
|
|
7100
|
+
placeholder="sk_..."
|
|
7101
|
+
class="w-full rounded-lg border-0 bg-white dark:bg-zinc-800 px-3.5 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:ring-2 focus:ring-cyan-500" />
|
|
7102
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Your Stripe secret API key (starts with sk_)</p>
|
|
7103
|
+
</div>
|
|
7104
|
+
|
|
7105
|
+
<div>
|
|
7106
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-1.5">Webhook Signing Secret</label>
|
|
7107
|
+
<input type="password" name="stripeWebhookSecret" value="${settings.stripeWebhookSecret}"
|
|
7108
|
+
placeholder="whsec_..."
|
|
7109
|
+
class="w-full rounded-lg border-0 bg-white dark:bg-zinc-800 px-3.5 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:ring-2 focus:ring-cyan-500" />
|
|
7110
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Stripe webhook endpoint signing secret (starts with whsec_)</p>
|
|
7111
|
+
</div>
|
|
7112
|
+
|
|
7113
|
+
<div>
|
|
7114
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-1.5">Default Price ID</label>
|
|
7115
|
+
<input type="text" name="stripePriceId" value="${settings.stripePriceId || ""}"
|
|
7116
|
+
placeholder="price_..."
|
|
7117
|
+
class="w-full rounded-lg border-0 bg-white dark:bg-zinc-800 px-3.5 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:ring-2 focus:ring-cyan-500" />
|
|
7118
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Default Stripe Price ID for checkout sessions (optional)</p>
|
|
7119
|
+
</div>
|
|
7120
|
+
|
|
7121
|
+
<div>
|
|
7122
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-1.5">Checkout Success URL</label>
|
|
7123
|
+
<input type="text" name="successUrl" value="${settings.successUrl}"
|
|
7124
|
+
class="w-full rounded-lg border-0 bg-white dark:bg-zinc-800 px-3.5 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:ring-2 focus:ring-cyan-500" />
|
|
7125
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Redirect URL after successful checkout</p>
|
|
7126
|
+
</div>
|
|
7127
|
+
|
|
7128
|
+
<div>
|
|
7129
|
+
<label class="block text-sm font-medium text-zinc-950 dark:text-white mb-1.5">Checkout Cancel URL</label>
|
|
7130
|
+
<input type="text" name="cancelUrl" value="${settings.cancelUrl}"
|
|
7131
|
+
class="w-full rounded-lg border-0 bg-white dark:bg-zinc-800 px-3.5 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:ring-2 focus:ring-cyan-500" />
|
|
7132
|
+
<p class="mt-1 text-xs text-zinc-500 dark:text-zinc-400">Redirect URL if checkout is cancelled</p>
|
|
7133
|
+
</div>
|
|
7134
|
+
</div>
|
|
7135
|
+
|
|
7136
|
+
<div class="px-6 py-4 flex justify-end">
|
|
7137
|
+
<button type="submit"
|
|
7138
|
+
class="inline-flex items-center justify-center rounded-lg bg-zinc-950 dark:bg-white px-3.5 py-2.5 text-sm font-semibold text-white dark:text-zinc-950 hover:bg-zinc-800 dark:hover:bg-zinc-100 transition-colors shadow-sm">
|
|
7139
|
+
Save Settings
|
|
7140
|
+
</button>
|
|
7141
|
+
</div>
|
|
7142
|
+
</form>
|
|
7143
|
+
</div>
|
|
7144
|
+
|
|
7145
|
+
<script>
|
|
7146
|
+
document.getElementById('stripe-settings-form').addEventListener('submit', async (e) => {
|
|
7147
|
+
e.preventDefault()
|
|
7148
|
+
const form = e.target
|
|
7149
|
+
const data = Object.fromEntries(new FormData(form))
|
|
7150
|
+
const msg = document.getElementById('settings-message')
|
|
7151
|
+
try {
|
|
7152
|
+
const res = await fetch('/admin/plugins/stripe/settings', {
|
|
7153
|
+
method: 'POST',
|
|
7154
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7155
|
+
body: JSON.stringify(data)
|
|
7156
|
+
})
|
|
7157
|
+
const result = await res.json()
|
|
7158
|
+
msg.className = result.success
|
|
7159
|
+
? 'mb-4 rounded-lg p-4 text-sm bg-emerald-400/10 text-emerald-500 dark:text-emerald-400 ring-1 ring-inset ring-emerald-400/20'
|
|
7160
|
+
: 'mb-4 rounded-lg p-4 text-sm bg-red-400/10 text-red-500 dark:text-red-400 ring-1 ring-inset ring-red-400/20'
|
|
7161
|
+
msg.textContent = result.success ? 'Settings saved successfully.' : (result.error || 'Failed to save settings.')
|
|
7162
|
+
} catch {
|
|
7163
|
+
msg.className = 'mb-4 rounded-lg p-4 text-sm bg-red-400/10 text-red-500 dark:text-red-400 ring-1 ring-inset ring-red-400/20'
|
|
7164
|
+
msg.textContent = 'Network error. Please try again.'
|
|
7165
|
+
}
|
|
7166
|
+
})
|
|
7167
|
+
</script>
|
|
7168
|
+
`;
|
|
7169
|
+
return c.html(renderAdminLayoutCatalyst2({
|
|
7170
|
+
title: "Stripe Settings",
|
|
7171
|
+
pageTitle: "Stripe Settings",
|
|
7172
|
+
currentPath: "/admin/plugins/stripe",
|
|
7173
|
+
user: user ? { name: user.email, email: user.email, role: user.role } : void 0,
|
|
7174
|
+
content: content2,
|
|
7175
|
+
version: c.get("appVersion"),
|
|
7176
|
+
dynamicMenuItems: c.get("pluginMenuItems")
|
|
7177
|
+
}));
|
|
7178
|
+
});
|
|
6652
7179
|
adminRoutes3.post("/settings", async (c) => {
|
|
6653
7180
|
try {
|
|
6654
7181
|
const body = await c.req.json();
|
|
@@ -6738,6 +7265,30 @@ var StripeAPI = class {
|
|
|
6738
7265
|
}
|
|
6739
7266
|
return this.request("POST", "/customers", body);
|
|
6740
7267
|
}
|
|
7268
|
+
/**
|
|
7269
|
+
* List subscriptions with pagination (auto-expands across pages)
|
|
7270
|
+
*/
|
|
7271
|
+
async listSubscriptions(params) {
|
|
7272
|
+
const qs = new URLSearchParams();
|
|
7273
|
+
qs.append("limit", String(params?.limit || 100));
|
|
7274
|
+
if (params?.status) qs.append("status", params.status);
|
|
7275
|
+
if (params?.startingAfter) qs.append("starting_after", params.startingAfter);
|
|
7276
|
+
return this.request("GET", `/subscriptions?${qs.toString()}`);
|
|
7277
|
+
}
|
|
7278
|
+
/**
|
|
7279
|
+
* Fetch ALL subscriptions from Stripe (handles pagination automatically)
|
|
7280
|
+
*/
|
|
7281
|
+
async listAllSubscriptions() {
|
|
7282
|
+
const all = [];
|
|
7283
|
+
let startingAfter;
|
|
7284
|
+
while (true) {
|
|
7285
|
+
const result = await this.listSubscriptions({ limit: 100, startingAfter });
|
|
7286
|
+
all.push(...result.data);
|
|
7287
|
+
if (!result.has_more || result.data.length === 0) break;
|
|
7288
|
+
startingAfter = result.data[result.data.length - 1].id;
|
|
7289
|
+
}
|
|
7290
|
+
return all;
|
|
7291
|
+
}
|
|
6741
7292
|
/**
|
|
6742
7293
|
* Search for a customer by email
|
|
6743
7294
|
*/
|
|
@@ -6776,7 +7327,7 @@ function timingSafeEqual(a, b) {
|
|
|
6776
7327
|
|
|
6777
7328
|
// src/plugins/core-plugins/stripe-plugin/routes/api.ts
|
|
6778
7329
|
var apiRoutes3 = new Hono();
|
|
6779
|
-
async function
|
|
7330
|
+
async function getSettings4(db) {
|
|
6780
7331
|
try {
|
|
6781
7332
|
const pluginService = new PluginService(db);
|
|
6782
7333
|
const plugin2 = await pluginService.getPlugin("stripe");
|
|
@@ -6803,7 +7354,7 @@ function mapStripeStatus(status) {
|
|
|
6803
7354
|
}
|
|
6804
7355
|
apiRoutes3.post("/webhook", async (c) => {
|
|
6805
7356
|
const db = c.env.DB;
|
|
6806
|
-
const settings = await
|
|
7357
|
+
const settings = await getSettings4(db);
|
|
6807
7358
|
if (!settings.stripeWebhookSecret) {
|
|
6808
7359
|
return c.json({ error: "Webhook secret not configured" }, 500);
|
|
6809
7360
|
}
|
|
@@ -6816,7 +7367,11 @@ apiRoutes3.post("/webhook", async (c) => {
|
|
|
6816
7367
|
}
|
|
6817
7368
|
const event = JSON.parse(rawBody);
|
|
6818
7369
|
const subscriptionService = new SubscriptionService(db);
|
|
6819
|
-
|
|
7370
|
+
const eventService = new StripeEventService(db);
|
|
7371
|
+
await Promise.all([subscriptionService.ensureTable(), eventService.ensureTable()]);
|
|
7372
|
+
const obj = event.data.object;
|
|
7373
|
+
const objectId = obj?.id || "";
|
|
7374
|
+
const objectType = obj?.object || event.type.split(".")[0] || "";
|
|
6820
7375
|
try {
|
|
6821
7376
|
switch (event.type) {
|
|
6822
7377
|
case "customer.subscription.created": {
|
|
@@ -6891,8 +7446,35 @@ apiRoutes3.post("/webhook", async (c) => {
|
|
|
6891
7446
|
}
|
|
6892
7447
|
default:
|
|
6893
7448
|
console.log(`[Stripe] Unhandled event type: ${event.type}`);
|
|
6894
|
-
|
|
7449
|
+
await eventService.log({
|
|
7450
|
+
stripeEventId: event.id,
|
|
7451
|
+
type: event.type,
|
|
7452
|
+
objectId,
|
|
7453
|
+
objectType,
|
|
7454
|
+
data: event.data.object,
|
|
7455
|
+
status: "ignored"
|
|
7456
|
+
});
|
|
7457
|
+
return c.json({ received: true });
|
|
7458
|
+
}
|
|
7459
|
+
await eventService.log({
|
|
7460
|
+
stripeEventId: event.id,
|
|
7461
|
+
type: event.type,
|
|
7462
|
+
objectId,
|
|
7463
|
+
objectType,
|
|
7464
|
+
data: event.data.object,
|
|
7465
|
+
status: "processed"
|
|
7466
|
+
});
|
|
6895
7467
|
} catch (error) {
|
|
7468
|
+
await eventService.log({
|
|
7469
|
+
stripeEventId: event.id,
|
|
7470
|
+
type: event.type,
|
|
7471
|
+
objectId,
|
|
7472
|
+
objectType,
|
|
7473
|
+
data: event.data.object,
|
|
7474
|
+
status: "failed",
|
|
7475
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7476
|
+
}).catch(() => {
|
|
7477
|
+
});
|
|
6896
7478
|
console.error(`[Stripe] Error processing webhook event ${event.type}:`, error);
|
|
6897
7479
|
return c.json({ error: "Webhook processing failed" }, 500);
|
|
6898
7480
|
}
|
|
@@ -6902,7 +7484,7 @@ apiRoutes3.post("/create-checkout-session", requireAuth(), async (c) => {
|
|
|
6902
7484
|
const db = c.env.DB;
|
|
6903
7485
|
const user = c.get("user");
|
|
6904
7486
|
if (!user) return c.json({ error: "Unauthorized" }, 401);
|
|
6905
|
-
const settings = await
|
|
7487
|
+
const settings = await getSettings4(db);
|
|
6906
7488
|
if (!settings.stripeSecretKey) {
|
|
6907
7489
|
return c.json({ error: "Stripe not configured" }, 500);
|
|
6908
7490
|
}
|
|
@@ -6975,6 +7557,74 @@ apiRoutes3.get("/stats", requireAuth(), async (c) => {
|
|
|
6975
7557
|
const stats = await subscriptionService.getStats();
|
|
6976
7558
|
return c.json(stats);
|
|
6977
7559
|
});
|
|
7560
|
+
apiRoutes3.post("/sync-subscriptions", requireAuth(), async (c) => {
|
|
7561
|
+
const user = c.get("user");
|
|
7562
|
+
if (user?.role !== "admin") return c.json({ error: "Access denied" }, 403);
|
|
7563
|
+
const db = c.env.DB;
|
|
7564
|
+
const settings = await getSettings4(db);
|
|
7565
|
+
if (!settings.stripeSecretKey) {
|
|
7566
|
+
return c.json({ error: "Stripe secret key not configured" }, 400);
|
|
7567
|
+
}
|
|
7568
|
+
const stripeApi = new StripeAPI(settings.stripeSecretKey);
|
|
7569
|
+
const subscriptionService = new SubscriptionService(db);
|
|
7570
|
+
await subscriptionService.ensureTable();
|
|
7571
|
+
try {
|
|
7572
|
+
const allSubs = await stripeApi.listAllSubscriptions();
|
|
7573
|
+
let synced = 0;
|
|
7574
|
+
let errors = 0;
|
|
7575
|
+
for (const sub of allSubs) {
|
|
7576
|
+
try {
|
|
7577
|
+
const userId = sub.metadata?.sonicjs_user_id || await subscriptionService.getUserIdByStripeCustomer(sub.customer) || "";
|
|
7578
|
+
await subscriptionService.upsert({
|
|
7579
|
+
userId,
|
|
7580
|
+
stripeCustomerId: typeof sub.customer === "string" ? sub.customer : sub.customer.id,
|
|
7581
|
+
stripeSubscriptionId: sub.id,
|
|
7582
|
+
stripePriceId: sub.items?.data?.[0]?.price?.id || "",
|
|
7583
|
+
status: mapStripeStatus(sub.status),
|
|
7584
|
+
currentPeriodStart: sub.current_period_start,
|
|
7585
|
+
currentPeriodEnd: sub.current_period_end,
|
|
7586
|
+
cancelAtPeriodEnd: sub.cancel_at_period_end
|
|
7587
|
+
});
|
|
7588
|
+
synced++;
|
|
7589
|
+
} catch (err) {
|
|
7590
|
+
console.error(`[Stripe Sync] Failed to upsert subscription ${sub.id}:`, err);
|
|
7591
|
+
errors++;
|
|
7592
|
+
}
|
|
7593
|
+
}
|
|
7594
|
+
return c.json({
|
|
7595
|
+
success: true,
|
|
7596
|
+
total: allSubs.length,
|
|
7597
|
+
synced,
|
|
7598
|
+
errors
|
|
7599
|
+
});
|
|
7600
|
+
} catch (error) {
|
|
7601
|
+
console.error("[Stripe Sync] Error:", error);
|
|
7602
|
+
return c.json({
|
|
7603
|
+
success: false,
|
|
7604
|
+
error: error instanceof Error ? error.message : "Sync failed"
|
|
7605
|
+
}, 500);
|
|
7606
|
+
}
|
|
7607
|
+
});
|
|
7608
|
+
apiRoutes3.get("/events", requireAuth(), async (c) => {
|
|
7609
|
+
const user = c.get("user");
|
|
7610
|
+
if (user?.role !== "admin") return c.json({ error: "Access denied" }, 403);
|
|
7611
|
+
const db = c.env.DB;
|
|
7612
|
+
const eventService = new StripeEventService(db);
|
|
7613
|
+
await eventService.ensureTable();
|
|
7614
|
+
const filters = {
|
|
7615
|
+
type: c.req.query("type") || void 0,
|
|
7616
|
+
status: c.req.query("status") || void 0,
|
|
7617
|
+
objectId: c.req.query("objectId") || void 0,
|
|
7618
|
+
page: c.req.query("page") ? parseInt(c.req.query("page")) : 1,
|
|
7619
|
+
limit: c.req.query("limit") ? parseInt(c.req.query("limit")) : 50
|
|
7620
|
+
};
|
|
7621
|
+
const [result, stats, types] = await Promise.all([
|
|
7622
|
+
eventService.list(filters),
|
|
7623
|
+
eventService.getStats(),
|
|
7624
|
+
eventService.getDistinctTypes()
|
|
7625
|
+
]);
|
|
7626
|
+
return c.json({ ...result, stats, types });
|
|
7627
|
+
});
|
|
6978
7628
|
|
|
6979
7629
|
// src/plugins/core-plugins/stripe-plugin/index.ts
|
|
6980
7630
|
function createStripePlugin() {
|
|
@@ -7021,13 +7671,35 @@ function createStripePlugin() {
|
|
|
7021
7671
|
var stripePlugin = createStripePlugin();
|
|
7022
7672
|
|
|
7023
7673
|
// src/middleware/plugin-menu.ts
|
|
7024
|
-
var
|
|
7025
|
-
|
|
7026
|
-
|
|
7674
|
+
var REGISTRY_MENU_PLUGINS = Object.values(PLUGIN_REGISTRY).filter((p) => p.adminMenu !== null).map((p) => ({
|
|
7675
|
+
codeName: p.codeName,
|
|
7676
|
+
label: p.adminMenu.label,
|
|
7677
|
+
path: p.adminMenu.path,
|
|
7678
|
+
icon: p.adminMenu.icon,
|
|
7679
|
+
order: p.adminMenu.order
|
|
7680
|
+
}));
|
|
7681
|
+
var ICON_SVG = {
|
|
7682
|
+
"magnifying-glass": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"/></svg>',
|
|
7683
|
+
"chart-bar": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"/></svg>',
|
|
7684
|
+
"image": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg>',
|
|
7685
|
+
"palette": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.098 19.902a3.75 3.75 0 0 0 5.304 0l6.401-6.402M6.75 21A3.75 3.75 0 0 1 3 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 0 0 3.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008Z"/></svg>',
|
|
7686
|
+
"envelope": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg>',
|
|
7687
|
+
"hand-raised": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.05 4.575a1.575 1.575 0 1 0-3.15 0v3m3.15-3v-1.5a1.575 1.575 0 0 1 3.15 0v1.5m-3.15 0 .075 5.925m3.075-5.925v2.925m0-2.925a1.575 1.575 0 0 1 3.15 0V9.9m-3.15-2.4v5.325M16.5 9.9a1.575 1.575 0 0 1 3.15 0V15a6.15 6.15 0 0 1-6.15 6.15H12A6.15 6.15 0 0 1 5.85 15V9.525"/></svg>',
|
|
7688
|
+
"key": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"/></svg>',
|
|
7689
|
+
"arrow-right": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"/></svg>',
|
|
7690
|
+
"shield-check": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>',
|
|
7691
|
+
"credit-card": '<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"/></svg>'
|
|
7692
|
+
};
|
|
7693
|
+
function resolveIcon(iconName) {
|
|
7694
|
+
if (!iconName) return "";
|
|
7695
|
+
if (iconName.startsWith("<svg") || iconName.startsWith("<")) return iconName;
|
|
7696
|
+
return ICON_SVG[iconName] || "";
|
|
7697
|
+
}
|
|
7027
7698
|
var MARKER = "<!-- DYNAMIC_PLUGIN_MENU -->";
|
|
7028
7699
|
function renderMenuItem(item, currentPath) {
|
|
7029
7700
|
const isActive = currentPath === item.path || currentPath.startsWith(item.path);
|
|
7030
7701
|
const fallbackIcon = `<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>`;
|
|
7702
|
+
const resolvedIcon = resolveIcon(item.icon) || fallbackIcon;
|
|
7031
7703
|
return `
|
|
7032
7704
|
<span class="relative">
|
|
7033
7705
|
${isActive ? '<span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-cyan-500 dark:bg-cyan-400"></span>' : ""}
|
|
@@ -7037,7 +7709,7 @@ function renderMenuItem(item, currentPath) {
|
|
|
7037
7709
|
${isActive ? 'data-current="true"' : ""}
|
|
7038
7710
|
>
|
|
7039
7711
|
<span class="shrink-0 ${isActive ? "fill-zinc-950 dark:fill-white" : "fill-zinc-500 dark:fill-zinc-400"}">
|
|
7040
|
-
${
|
|
7712
|
+
${resolvedIcon}
|
|
7041
7713
|
</span>
|
|
7042
7714
|
<span class="truncate">${item.label}</span>
|
|
7043
7715
|
</a>
|
|
@@ -7052,23 +7724,28 @@ function pluginMenuMiddleware() {
|
|
|
7052
7724
|
let activeMenuItems = [];
|
|
7053
7725
|
try {
|
|
7054
7726
|
const db = c.env.DB;
|
|
7055
|
-
const
|
|
7056
|
-
if (
|
|
7057
|
-
const placeholders =
|
|
7727
|
+
const pluginCodeNames = REGISTRY_MENU_PLUGINS.map((p) => p.codeName);
|
|
7728
|
+
if (pluginCodeNames.length > 0) {
|
|
7729
|
+
const placeholders = pluginCodeNames.map(() => "?").join(",");
|
|
7058
7730
|
const result = await db.prepare(
|
|
7059
7731
|
`SELECT name FROM plugins WHERE name IN (${placeholders}) AND status = 'active'`
|
|
7060
|
-
).bind(...
|
|
7732
|
+
).bind(...pluginCodeNames).all();
|
|
7061
7733
|
const activeNames = new Set((result.results || []).map((r) => r.name));
|
|
7062
|
-
for (const plugin2 of
|
|
7063
|
-
if (activeNames.has(plugin2.
|
|
7064
|
-
activeMenuItems.push(
|
|
7734
|
+
for (const plugin2 of REGISTRY_MENU_PLUGINS) {
|
|
7735
|
+
if (activeNames.has(plugin2.codeName)) {
|
|
7736
|
+
activeMenuItems.push({
|
|
7737
|
+
label: plugin2.label,
|
|
7738
|
+
path: plugin2.path,
|
|
7739
|
+
icon: plugin2.icon,
|
|
7740
|
+
order: plugin2.order
|
|
7741
|
+
});
|
|
7065
7742
|
}
|
|
7066
7743
|
}
|
|
7067
|
-
activeMenuItems.sort((a, b) =>
|
|
7744
|
+
activeMenuItems.sort((a, b) => a.order - b.order);
|
|
7068
7745
|
}
|
|
7069
7746
|
} catch {
|
|
7070
7747
|
}
|
|
7071
|
-
c.set("pluginMenuItems", activeMenuItems.map((m) => ({ label: m.label, path: m.path, icon: m.icon || "" })));
|
|
7748
|
+
c.set("pluginMenuItems", activeMenuItems.map((m) => ({ label: m.label, path: m.path, icon: resolveIcon(m.icon) || "" })));
|
|
7072
7749
|
await next();
|
|
7073
7750
|
if (activeMenuItems.length > 0 && c.res.headers.get("content-type")?.includes("text/html")) {
|
|
7074
7751
|
const status = c.res.status;
|
|
@@ -9000,16 +9677,16 @@ function createSonicJSApp(config = {}) {
|
|
|
9000
9677
|
app2.route(route.path, route.handler);
|
|
9001
9678
|
}
|
|
9002
9679
|
}
|
|
9003
|
-
app2.route("/admin/plugins", adminPluginRoutes);
|
|
9004
|
-
app2.route("/admin/logs", adminLogsRoutes);
|
|
9005
|
-
app2.route("/admin", userRoutes);
|
|
9006
|
-
app2.route("/auth", auth_default);
|
|
9007
|
-
app2.route("/", test_cleanup_default);
|
|
9008
9680
|
if (stripePlugin.routes && stripePlugin.routes.length > 0) {
|
|
9009
9681
|
for (const route of stripePlugin.routes) {
|
|
9010
9682
|
app2.route(route.path, route.handler);
|
|
9011
9683
|
}
|
|
9012
9684
|
}
|
|
9685
|
+
app2.route("/admin/plugins", adminPluginRoutes);
|
|
9686
|
+
app2.route("/admin/logs", adminLogsRoutes);
|
|
9687
|
+
app2.route("/admin", userRoutes);
|
|
9688
|
+
app2.route("/auth", auth_default);
|
|
9689
|
+
app2.route("/", test_cleanup_default);
|
|
9013
9690
|
if (emailPlugin.routes && emailPlugin.routes.length > 0) {
|
|
9014
9691
|
for (const route of emailPlugin.routes) {
|
|
9015
9692
|
app2.route(route.path, route.handler);
|