qarpeo 0.0.0-alpha.10
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 +21 -0
- package/README.md +102 -0
- package/dist/chunks/auth-schema.mjs +219 -0
- package/dist/chunks/wrangler.mjs +783 -0
- package/dist/cli/index.mjs +1085 -0
- package/dist/dev/env.d.ts +12 -0
- package/dist/dev/wrangler.d.ts +18 -0
- package/dist/module.d.ts +16 -0
- package/dist/module.mjs +4436 -0
- package/dist/runtime/ai.d.ts +25 -0
- package/dist/runtime/ai.mjs +106 -0
- package/dist/runtime/analytics.d.ts +24 -0
- package/dist/runtime/analytics.mjs +38 -0
- package/dist/runtime/app/components/BlobImage.vue +100 -0
- package/dist/runtime/app/components/CMSBlocks.vue +93 -0
- package/dist/runtime/app/components/CMSContent.vue +23 -0
- package/dist/runtime/app/components/CMSImage.vue +49 -0
- package/dist/runtime/app/components/CMSLocaleSwitcher.vue +110 -0
- package/dist/runtime/app/components/CMSPreviewBanner.vue +79 -0
- package/dist/runtime/app/components/CMSVersionBanner.vue +49 -0
- package/dist/runtime/app/components/CMSVersionSwitcher.vue +126 -0
- package/dist/runtime/app/components/ContentRenderer.vue +80 -0
- package/dist/runtime/app/components/FileUpload.vue +196 -0
- package/dist/runtime/app/components/ImpersonationBanner.vue +57 -0
- package/dist/runtime/app/components/SignedIn.vue +13 -0
- package/dist/runtime/app/components/SignedOut.vue +13 -0
- package/dist/runtime/app/components/shiki.css +34 -0
- package/dist/runtime/app/composables/analytics.d.ts +125 -0
- package/dist/runtime/app/composables/analytics.mjs +81 -0
- package/dist/runtime/app/composables/api-tokens.d.ts +49 -0
- package/dist/runtime/app/composables/api-tokens.mjs +61 -0
- package/dist/runtime/app/composables/auth.d.ts +50 -0
- package/dist/runtime/app/composables/auth.mjs +101 -0
- package/dist/runtime/app/composables/chat.d.ts +69 -0
- package/dist/runtime/app/composables/chat.mjs +159 -0
- package/dist/runtime/app/composables/cms-preview.d.ts +33 -0
- package/dist/runtime/app/composables/cms-preview.mjs +83 -0
- package/dist/runtime/app/composables/cms.d.ts +307 -0
- package/dist/runtime/app/composables/cms.mjs +355 -0
- package/dist/runtime/app/composables/docs-version.d.ts +35 -0
- package/dist/runtime/app/composables/docs-version.mjs +70 -0
- package/dist/runtime/app/composables/impersonation.d.ts +14 -0
- package/dist/runtime/app/composables/impersonation.mjs +44 -0
- package/dist/runtime/app/composables/notifications.d.ts +35 -0
- package/dist/runtime/app/composables/notifications.mjs +139 -0
- package/dist/runtime/app/composables/organization.d.ts +127 -0
- package/dist/runtime/app/composables/organization.mjs +138 -0
- package/dist/runtime/app/composables/passkey.d.ts +6 -0
- package/dist/runtime/app/composables/passkey.mjs +105 -0
- package/dist/runtime/app/composables/passwordless.d.ts +7 -0
- package/dist/runtime/app/composables/passwordless.mjs +67 -0
- package/dist/runtime/app/composables/payments.d.ts +43 -0
- package/dist/runtime/app/composables/payments.mjs +74 -0
- package/dist/runtime/app/composables/realtime.d.ts +96 -0
- package/dist/runtime/app/composables/realtime.mjs +432 -0
- package/dist/runtime/app/composables/site.d.ts +35 -0
- package/dist/runtime/app/composables/site.mjs +36 -0
- package/dist/runtime/app/composables/storage.d.ts +60 -0
- package/dist/runtime/app/composables/storage.mjs +294 -0
- package/dist/runtime/app/composables/tenant.d.ts +82 -0
- package/dist/runtime/app/composables/tenant.mjs +120 -0
- package/dist/runtime/app/composables/turnstile.d.ts +3 -0
- package/dist/runtime/app/composables/turnstile.mjs +10 -0
- package/dist/runtime/app/composables/two-factor.d.ts +12 -0
- package/dist/runtime/app/composables/two-factor.mjs +65 -0
- package/dist/runtime/app/middleware/auth.global.d.ts +2 -0
- package/dist/runtime/app/middleware/auth.global.mjs +44 -0
- package/dist/runtime/app/pages/cms.vue +2249 -0
- package/dist/runtime/app/pages/devtools/agents.vue +717 -0
- package/dist/runtime/app/pages/devtools/analytics.vue +618 -0
- package/dist/runtime/app/pages/devtools/auth.vue +351 -0
- package/dist/runtime/app/pages/devtools/blob.vue +745 -0
- package/dist/runtime/app/pages/devtools/db.vue +546 -0
- package/dist/runtime/app/pages/devtools/domains.vue +772 -0
- package/dist/runtime/app/pages/devtools/kv.vue +829 -0
- package/dist/runtime/app/pages/devtools/logs.vue +655 -0
- package/dist/runtime/app/pages/devtools/mail.vue +389 -0
- package/dist/runtime/app/pages/devtools/overview.vue +390 -0
- package/dist/runtime/app/pages/devtools/settings.vue +560 -0
- package/dist/runtime/app/pages/devtools.vue +165 -0
- package/dist/runtime/app/plugins/seo.d.ts +2 -0
- package/dist/runtime/app/plugins/seo.mjs +105 -0
- package/dist/runtime/app/plugins/shortcuts.client.d.ts +6 -0
- package/dist/runtime/app/plugins/shortcuts.client.mjs +19 -0
- package/dist/runtime/app/public/sw-notifications.mjs +52 -0
- package/dist/runtime/auth.d.ts +25 -0
- package/dist/runtime/auth.mjs +11 -0
- package/dist/runtime/bindings.d.ts +27 -0
- package/dist/runtime/bindings.mjs +38 -0
- package/dist/runtime/blob.d.ts +26 -0
- package/dist/runtime/blob.mjs +334 -0
- package/dist/runtime/bots.d.ts +1 -0
- package/dist/runtime/bots.mjs +1 -0
- package/dist/runtime/browser.d.ts +21 -0
- package/dist/runtime/browser.mjs +64 -0
- package/dist/runtime/cache.d.ts +19 -0
- package/dist/runtime/cache.mjs +12 -0
- package/dist/runtime/cms.d.ts +2 -0
- package/dist/runtime/cms.mjs +0 -0
- package/dist/runtime/db.d.ts +21 -0
- package/dist/runtime/db.mjs +36 -0
- package/dist/runtime/drizzle.d.ts +11 -0
- package/dist/runtime/drizzle.mjs +18 -0
- package/dist/runtime/email.d.ts +51 -0
- package/dist/runtime/email.mjs +245 -0
- package/dist/runtime/hyperdrive.d.ts +21 -0
- package/dist/runtime/hyperdrive.mjs +49 -0
- package/dist/runtime/index.d.ts +23 -0
- package/dist/runtime/index.mjs +23 -0
- package/dist/runtime/kv.d.ts +16 -0
- package/dist/runtime/kv.mjs +126 -0
- package/dist/runtime/payments.d.ts +2 -0
- package/dist/runtime/payments.mjs +0 -0
- package/dist/runtime/queue.d.ts +21 -0
- package/dist/runtime/queue.mjs +41 -0
- package/dist/runtime/rag.d.ts +1 -0
- package/dist/runtime/rag.mjs +1 -0
- package/dist/runtime/ratelimit.d.ts +21 -0
- package/dist/runtime/ratelimit.mjs +38 -0
- package/dist/runtime/realtime.d.ts +6 -0
- package/dist/runtime/realtime.mjs +13 -0
- package/dist/runtime/server/db/cms-schema.d.ts +651 -0
- package/dist/runtime/server/db/cms-schema.mjs +74 -0
- package/dist/runtime/server/durable-objects/agent.d.ts +14 -0
- package/dist/runtime/server/durable-objects/agent.mjs +571 -0
- package/dist/runtime/server/durable-objects/room.d.ts +34 -0
- package/dist/runtime/server/durable-objects/room.mjs +514 -0
- package/dist/runtime/server/middleware/auth.d.ts +2 -0
- package/dist/runtime/server/middleware/auth.mjs +108 -0
- package/dist/runtime/server/middleware/devtools.d.ts +2 -0
- package/dist/runtime/server/middleware/devtools.mjs +61 -0
- package/dist/runtime/server/middleware/ratelimit.d.ts +2 -0
- package/dist/runtime/server/middleware/ratelimit.mjs +49 -0
- package/dist/runtime/server/middleware/tenancy.d.ts +6 -0
- package/dist/runtime/server/middleware/tenancy.mjs +15 -0
- package/dist/runtime/server/middleware/tracker.d.ts +7 -0
- package/dist/runtime/server/middleware/tracker.mjs +9 -0
- package/dist/runtime/server/plugins/agents.d.ts +7 -0
- package/dist/runtime/server/plugins/agents.mjs +3 -0
- package/dist/runtime/server/plugins/cms.d.ts +7 -0
- package/dist/runtime/server/plugins/cms.mjs +16 -0
- package/dist/runtime/server/plugins/dev-logs.d.ts +1 -0
- package/dist/runtime/server/plugins/dev-logs.mjs +58 -0
- package/dist/runtime/server/plugins/dev-proxy.d.ts +3 -0
- package/dist/runtime/server/plugins/dev-proxy.mjs +21 -0
- package/dist/runtime/server/plugins/env.d.ts +6 -0
- package/dist/runtime/server/plugins/env.mjs +17 -0
- package/dist/runtime/server/plugins/queue.d.ts +6 -0
- package/dist/runtime/server/plugins/queue.mjs +8 -0
- package/dist/runtime/server/routes/__qarpeo/agents/chat.post.d.ts +20 -0
- package/dist/runtime/server/routes/__qarpeo/agents/chat.post.mjs +110 -0
- package/dist/runtime/server/routes/__qarpeo/agents/clear.post.d.ts +7 -0
- package/dist/runtime/server/routes/__qarpeo/agents/clear.post.mjs +15 -0
- package/dist/runtime/server/routes/__qarpeo/agents/create.post.d.ts +17 -0
- package/dist/runtime/server/routes/__qarpeo/agents/create.post.mjs +47 -0
- package/dist/runtime/server/routes/__qarpeo/agents/history.get.d.ts +15 -0
- package/dist/runtime/server/routes/__qarpeo/agents/history.get.mjs +24 -0
- package/dist/runtime/server/routes/__qarpeo/agents/list.get.d.ts +5 -0
- package/dist/runtime/server/routes/__qarpeo/agents/list.get.mjs +63 -0
- package/dist/runtime/server/routes/__qarpeo/analytics/events.get.d.ts +64 -0
- package/dist/runtime/server/routes/__qarpeo/analytics/events.get.mjs +55 -0
- package/dist/runtime/server/routes/__qarpeo/analytics/summary.get.d.ts +30 -0
- package/dist/runtime/server/routes/__qarpeo/analytics/summary.get.mjs +73 -0
- package/dist/runtime/server/routes/__qarpeo/auth/revoke-session.post.d.ts +6 -0
- package/dist/runtime/server/routes/__qarpeo/auth/revoke-session.post.mjs +26 -0
- package/dist/runtime/server/routes/__qarpeo/auth/sessions.get.d.ts +6 -0
- package/dist/runtime/server/routes/__qarpeo/auth/sessions.get.mjs +46 -0
- package/dist/runtime/server/routes/__qarpeo/auth/users.get.d.ts +7 -0
- package/dist/runtime/server/routes/__qarpeo/auth/users.get.mjs +65 -0
- package/dist/runtime/server/routes/__qarpeo/blob/files.get.d.ts +22 -0
- package/dist/runtime/server/routes/__qarpeo/blob/files.get.mjs +36 -0
- package/dist/runtime/server/routes/__qarpeo/db/query.post.d.ts +23 -0
- package/dist/runtime/server/routes/__qarpeo/db/query.post.mjs +34 -0
- package/dist/runtime/server/routes/__qarpeo/db/tables.get.d.ts +31 -0
- package/dist/runtime/server/routes/__qarpeo/db/tables.get.mjs +81 -0
- package/dist/runtime/server/routes/__qarpeo/kv/delete.post.d.ts +11 -0
- package/dist/runtime/server/routes/__qarpeo/kv/delete.post.mjs +18 -0
- package/dist/runtime/server/routes/__qarpeo/kv/get.get.d.ts +11 -0
- package/dist/runtime/server/routes/__qarpeo/kv/get.get.mjs +20 -0
- package/dist/runtime/server/routes/__qarpeo/kv/keys.get.d.ts +8 -0
- package/dist/runtime/server/routes/__qarpeo/kv/keys.get.mjs +27 -0
- package/dist/runtime/server/routes/__qarpeo/kv/set.post.d.ts +14 -0
- package/dist/runtime/server/routes/__qarpeo/kv/set.post.mjs +24 -0
- package/dist/runtime/server/routes/__qarpeo/logs.delete.d.ts +5 -0
- package/dist/runtime/server/routes/__qarpeo/logs.delete.mjs +6 -0
- package/dist/runtime/server/routes/__qarpeo/logs.get.d.ts +5 -0
- package/dist/runtime/server/routes/__qarpeo/logs.get.mjs +26 -0
- package/dist/runtime/server/routes/__qarpeo/logs.post.d.ts +6 -0
- package/dist/runtime/server/routes/__qarpeo/logs.post.mjs +17 -0
- package/dist/runtime/server/routes/__qarpeo/mail/bounce.post.d.ts +5 -0
- package/dist/runtime/server/routes/__qarpeo/mail/bounce.post.mjs +37 -0
- package/dist/runtime/server/routes/__qarpeo/mail/clear.post.d.ts +4 -0
- package/dist/runtime/server/routes/__qarpeo/mail/clear.post.mjs +6 -0
- package/dist/runtime/server/routes/__qarpeo/mail/inbox.get.d.ts +5 -0
- package/dist/runtime/server/routes/__qarpeo/mail/inbox.get.mjs +7 -0
- package/dist/runtime/server/routes/__qarpeo/mail/send-test.post.d.ts +4 -0
- package/dist/runtime/server/routes/__qarpeo/mail/send-test.post.mjs +29 -0
- package/dist/runtime/server/routes/__qarpeo/mail/track/click/[id].get.d.ts +2 -0
- package/dist/runtime/server/routes/__qarpeo/mail/track/click/[id].get.mjs +23 -0
- package/dist/runtime/server/routes/__qarpeo/mail/track/open/[id].get.d.ts +2 -0
- package/dist/runtime/server/routes/__qarpeo/mail/track/open/[id].get.mjs +31 -0
- package/dist/runtime/server/routes/__qarpeo/overview/health.get.d.ts +8 -0
- package/dist/runtime/server/routes/__qarpeo/overview/health.get.mjs +46 -0
- package/dist/runtime/server/routes/__qarpeo/overview.get.d.ts +32 -0
- package/dist/runtime/server/routes/__qarpeo/overview.get.mjs +38 -0
- package/dist/runtime/server/routes/__qarpeo/settings/reset-dev-data.post.d.ts +11 -0
- package/dist/runtime/server/routes/__qarpeo/settings/reset-dev-data.post.mjs +84 -0
- package/dist/runtime/server/routes/__qarpeo/settings.get.d.ts +124 -0
- package/dist/runtime/server/routes/__qarpeo/settings.get.mjs +145 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/create.post.d.ts +7 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/create.post.mjs +26 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/domain-add.post.d.ts +6 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/domain-add.post.mjs +29 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/domain-delete.post.d.ts +7 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/domain-delete.post.mjs +21 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/domain-verify.post.d.ts +9 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/domain-verify.post.mjs +21 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/list.get.d.ts +15 -0
- package/dist/runtime/server/routes/__qarpeo/tenants/list.get.mjs +22 -0
- package/dist/runtime/server/routes/agents/[agent]/chat.post.d.ts +2 -0
- package/dist/runtime/server/routes/agents/[agent]/chat.post.mjs +35 -0
- package/dist/runtime/server/routes/agents/[agent]/clear.post.d.ts +4 -0
- package/dist/runtime/server/routes/agents/[agent]/clear.post.mjs +27 -0
- package/dist/runtime/server/routes/agents/[agent]/history.get.d.ts +4 -0
- package/dist/runtime/server/routes/agents/[agent]/history.get.mjs +27 -0
- package/dist/runtime/server/routes/analytics/events.get.d.ts +3 -0
- package/dist/runtime/server/routes/analytics/events.get.mjs +17 -0
- package/dist/runtime/server/routes/analytics/pageviews.get.d.ts +3 -0
- package/dist/runtime/server/routes/analytics/pageviews.get.mjs +15 -0
- package/dist/runtime/server/routes/analytics/summary.get.d.ts +6 -0
- package/dist/runtime/server/routes/analytics/summary.get.mjs +15 -0
- package/dist/runtime/server/routes/atom.xml.get.d.ts +2 -0
- package/dist/runtime/server/routes/atom.xml.get.mjs +76 -0
- package/dist/runtime/server/routes/auth/2fa/disable.post.d.ts +4 -0
- package/dist/runtime/server/routes/auth/2fa/disable.post.mjs +44 -0
- package/dist/runtime/server/routes/auth/2fa/setup.post.d.ts +6 -0
- package/dist/runtime/server/routes/auth/2fa/setup.post.mjs +32 -0
- package/dist/runtime/server/routes/auth/2fa/verify.post.d.ts +6 -0
- package/dist/runtime/server/routes/auth/2fa/verify.post.mjs +92 -0
- package/dist/runtime/server/routes/auth/[provider]/callback.d.ts +1 -0
- package/dist/runtime/server/routes/auth/[provider]/callback.mjs +1 -0
- package/dist/runtime/server/routes/auth/[provider].d.ts +3 -0
- package/dist/runtime/server/routes/auth/[provider].mjs +95 -0
- package/dist/runtime/server/routes/auth/account.delete.d.ts +5 -0
- package/dist/runtime/server/routes/auth/account.delete.mjs +52 -0
- package/dist/runtime/server/routes/auth/impersonate/start.post.d.ts +5 -0
- package/dist/runtime/server/routes/auth/impersonate/start.post.mjs +14 -0
- package/dist/runtime/server/routes/auth/impersonate/stop.post.d.ts +5 -0
- package/dist/runtime/server/routes/auth/impersonate/stop.post.mjs +5 -0
- package/dist/runtime/server/routes/auth/logout-all.post.d.ts +4 -0
- package/dist/runtime/server/routes/auth/logout-all.post.mjs +16 -0
- package/dist/runtime/server/routes/auth/logout-others.post.d.ts +4 -0
- package/dist/runtime/server/routes/auth/logout-others.post.mjs +11 -0
- package/dist/runtime/server/routes/auth/logout.post.d.ts +4 -0
- package/dist/runtime/server/routes/auth/logout.post.mjs +17 -0
- package/dist/runtime/server/routes/auth/magic-link/send.post.d.ts +10 -0
- package/dist/runtime/server/routes/auth/magic-link/send.post.mjs +29 -0
- package/dist/runtime/server/routes/auth/magic-link/verify.get.d.ts +2 -0
- package/dist/runtime/server/routes/auth/magic-link/verify.get.mjs +81 -0
- package/dist/runtime/server/routes/auth/organization/create.post.d.ts +5 -0
- package/dist/runtime/server/routes/auth/organization/create.post.mjs +10 -0
- package/dist/runtime/server/routes/auth/organization/invite.post.d.ts +9 -0
- package/dist/runtime/server/routes/auth/organization/invite.post.mjs +27 -0
- package/dist/runtime/server/routes/auth/organization/join.post.d.ts +6 -0
- package/dist/runtime/server/routes/auth/organization/join.post.mjs +15 -0
- package/dist/runtime/server/routes/auth/organization/list.get.d.ts +5 -0
- package/dist/runtime/server/routes/auth/organization/list.get.mjs +7 -0
- package/dist/runtime/server/routes/auth/organization/members/[id].delete.d.ts +4 -0
- package/dist/runtime/server/routes/auth/organization/members/[id].delete.mjs +33 -0
- package/dist/runtime/server/routes/auth/organization/members/index.get.d.ts +6 -0
- package/dist/runtime/server/routes/auth/organization/members/index.get.mjs +18 -0
- package/dist/runtime/server/routes/auth/organization/mfa.post.d.ts +6 -0
- package/dist/runtime/server/routes/auth/organization/mfa.post.mjs +33 -0
- package/dist/runtime/server/routes/auth/organization/sso/[id].delete.d.ts +4 -0
- package/dist/runtime/server/routes/auth/organization/sso/[id].delete.mjs +28 -0
- package/dist/runtime/server/routes/auth/organization/sso/index.get.d.ts +2 -0
- package/dist/runtime/server/routes/auth/organization/sso/index.get.mjs +23 -0
- package/dist/runtime/server/routes/auth/organization/sso/index.post.d.ts +5 -0
- package/dist/runtime/server/routes/auth/organization/sso/index.post.mjs +84 -0
- package/dist/runtime/server/routes/auth/organization/switch.post.d.ts +6 -0
- package/dist/runtime/server/routes/auth/organization/switch.post.mjs +14 -0
- package/dist/runtime/server/routes/auth/otp/send.post.d.ts +10 -0
- package/dist/runtime/server/routes/auth/otp/send.post.mjs +29 -0
- package/dist/runtime/server/routes/auth/otp/verify.post.d.ts +6 -0
- package/dist/runtime/server/routes/auth/otp/verify.post.mjs +95 -0
- package/dist/runtime/server/routes/auth/sessions/[id].delete.d.ts +5 -0
- package/dist/runtime/server/routes/auth/sessions/[id].delete.mjs +24 -0
- package/dist/runtime/server/routes/auth/sessions.get.d.ts +2 -0
- package/dist/runtime/server/routes/auth/sessions.get.mjs +11 -0
- package/dist/runtime/server/routes/auth/sso/login.get.d.ts +2 -0
- package/dist/runtime/server/routes/auth/sso/login.get.mjs +94 -0
- package/dist/runtime/server/routes/auth/sso/oidc/callback.get.d.ts +2 -0
- package/dist/runtime/server/routes/auth/sso/oidc/callback.get.mjs +113 -0
- package/dist/runtime/server/routes/auth/sso/saml/acs.post.d.ts +2 -0
- package/dist/runtime/server/routes/auth/sso/saml/acs.post.mjs +95 -0
- package/dist/runtime/server/routes/auth/tokens/[id].delete.d.ts +4 -0
- package/dist/runtime/server/routes/auth/tokens/[id].delete.mjs +13 -0
- package/dist/runtime/server/routes/auth/tokens/create.post.d.ts +9 -0
- package/dist/runtime/server/routes/auth/tokens/create.post.mjs +17 -0
- package/dist/runtime/server/routes/auth/tokens/index.get.d.ts +9 -0
- package/dist/runtime/server/routes/auth/tokens/index.get.mjs +5 -0
- package/dist/runtime/server/routes/auth/turnstile/verify.post.d.ts +2 -0
- package/dist/runtime/server/routes/auth/turnstile/verify.post.mjs +15 -0
- package/dist/runtime/server/routes/auth/user.get.d.ts +2 -0
- package/dist/runtime/server/routes/auth/user.get.mjs +6 -0
- package/dist/runtime/server/routes/auth/webauthn/login/options.post.d.ts +2 -0
- package/dist/runtime/server/routes/auth/webauthn/login/options.post.mjs +17 -0
- package/dist/runtime/server/routes/auth/webauthn/login/verify.post.d.ts +6 -0
- package/dist/runtime/server/routes/auth/webauthn/login/verify.post.mjs +127 -0
- package/dist/runtime/server/routes/auth/webauthn/register/options.post.d.ts +2 -0
- package/dist/runtime/server/routes/auth/webauthn/register/options.post.mjs +47 -0
- package/dist/runtime/server/routes/auth/webauthn/register/verify.post.d.ts +5 -0
- package/dist/runtime/server/routes/auth/webauthn/register/verify.post.mjs +88 -0
- package/dist/runtime/server/routes/cms/[collection]/[id]/preview-token.post.d.ts +8 -0
- package/dist/runtime/server/routes/cms/[collection]/[id]/preview-token.post.mjs +27 -0
- package/dist/runtime/server/routes/cms/[collection]/[id]/publish.post.d.ts +6 -0
- package/dist/runtime/server/routes/cms/[collection]/[id]/publish.post.mjs +19 -0
- package/dist/runtime/server/routes/cms/[collection]/[id]/review.post.d.ts +6 -0
- package/dist/runtime/server/routes/cms/[collection]/[id]/review.post.mjs +33 -0
- package/dist/runtime/server/routes/cms/[collection]/[id].delete.d.ts +4 -0
- package/dist/runtime/server/routes/cms/[collection]/[id].delete.mjs +17 -0
- package/dist/runtime/server/routes/cms/[collection]/[id].put.d.ts +4 -0
- package/dist/runtime/server/routes/cms/[collection]/[id].put.mjs +18 -0
- package/dist/runtime/server/routes/cms/[collection]/[slug].get.d.ts +4 -0
- package/dist/runtime/server/routes/cms/[collection]/[slug].get.mjs +19 -0
- package/dist/runtime/server/routes/cms/[collection]/atom.xml.get.d.ts +2 -0
- package/dist/runtime/server/routes/cms/[collection]/atom.xml.get.mjs +69 -0
- package/dist/runtime/server/routes/cms/[collection]/by-path.get.d.ts +8 -0
- package/dist/runtime/server/routes/cms/[collection]/by-path.get.mjs +23 -0
- package/dist/runtime/server/routes/cms/[collection]/feed.xml.get.d.ts +2 -0
- package/dist/runtime/server/routes/cms/[collection]/feed.xml.get.mjs +65 -0
- package/dist/runtime/server/routes/cms/[collection]/index.get.d.ts +7 -0
- package/dist/runtime/server/routes/cms/[collection]/index.get.mjs +15 -0
- package/dist/runtime/server/routes/cms/[collection]/index.post.d.ts +4 -0
- package/dist/runtime/server/routes/cms/[collection]/index.post.mjs +23 -0
- package/dist/runtime/server/routes/cms/[collection]/references.get.d.ts +19 -0
- package/dist/runtime/server/routes/cms/[collection]/references.get.mjs +47 -0
- package/dist/runtime/server/routes/cms/[collection]/surroundings.get.d.ts +10 -0
- package/dist/runtime/server/routes/cms/[collection]/surroundings.get.mjs +12 -0
- package/dist/runtime/server/routes/cms/collections/[id].delete.d.ts +4 -0
- package/dist/runtime/server/routes/cms/collections/[id].delete.mjs +10 -0
- package/dist/runtime/server/routes/cms/collections/[id].put.d.ts +4 -0
- package/dist/runtime/server/routes/cms/collections/[id].put.mjs +11 -0
- package/dist/runtime/server/routes/cms/collections/index.get.d.ts +4 -0
- package/dist/runtime/server/routes/cms/collections/index.get.mjs +6 -0
- package/dist/runtime/server/routes/cms/collections/index.post.d.ts +4 -0
- package/dist/runtime/server/routes/cms/collections/index.post.mjs +19 -0
- package/dist/runtime/server/routes/cms/components/index.get.d.ts +17 -0
- package/dist/runtime/server/routes/cms/components/index.get.mjs +16 -0
- package/dist/runtime/server/routes/cms/deploy.post.d.ts +4 -0
- package/dist/runtime/server/routes/cms/deploy.post.mjs +46 -0
- package/dist/runtime/server/routes/cms/export.post.d.ts +10 -0
- package/dist/runtime/server/routes/cms/export.post.mjs +55 -0
- package/dist/runtime/server/routes/cms/navigation.get.d.ts +8 -0
- package/dist/runtime/server/routes/cms/navigation.get.mjs +36 -0
- package/dist/runtime/server/routes/cms/preview/disable.get.d.ts +9 -0
- package/dist/runtime/server/routes/cms/preview/disable.get.mjs +9 -0
- package/dist/runtime/server/routes/cms/preview/enable.get.d.ts +14 -0
- package/dist/runtime/server/routes/cms/preview/enable.get.mjs +42 -0
- package/dist/runtime/server/routes/cms/publish-scheduled.post.d.ts +11 -0
- package/dist/runtime/server/routes/cms/publish-scheduled.post.mjs +35 -0
- package/dist/runtime/server/routes/cms/search.get.d.ts +13 -0
- package/dist/runtime/server/routes/cms/search.get.mjs +137 -0
- package/dist/runtime/server/routes/cms/sync.post.d.ts +7 -0
- package/dist/runtime/server/routes/cms/sync.post.mjs +18 -0
- package/dist/runtime/server/routes/cms/webhooks/[provider].post.d.ts +150 -0
- package/dist/runtime/server/routes/cms/webhooks/[provider].post.mjs +213 -0
- package/dist/runtime/server/routes/docs/search.get.d.ts +15 -0
- package/dist/runtime/server/routes/docs/search.get.mjs +20 -0
- package/dist/runtime/server/routes/feed.xml.get.d.ts +2 -0
- package/dist/runtime/server/routes/feed.xml.get.mjs +73 -0
- package/dist/runtime/server/routes/mail/bounce.post.d.ts +14 -0
- package/dist/runtime/server/routes/mail/bounce.post.mjs +56 -0
- package/dist/runtime/server/routes/mail/inbox/[id].get.d.ts +2 -0
- package/dist/runtime/server/routes/mail/inbox/[id].get.mjs +9 -0
- package/dist/runtime/server/routes/mail/inbox.get.d.ts +4 -0
- package/dist/runtime/server/routes/mail/inbox.get.mjs +7 -0
- package/dist/runtime/server/routes/mail/suppressions/[email].delete.d.ts +11 -0
- package/dist/runtime/server/routes/mail/suppressions/[email].delete.mjs +18 -0
- package/dist/runtime/server/routes/mail/suppressions/index.get.d.ts +12 -0
- package/dist/runtime/server/routes/mail/suppressions/index.get.mjs +23 -0
- package/dist/runtime/server/routes/mail/suppressions/index.post.d.ts +10 -0
- package/dist/runtime/server/routes/mail/suppressions/index.post.mjs +23 -0
- package/dist/runtime/server/routes/mail/unsubscribe.d.ts +13 -0
- package/dist/runtime/server/routes/mail/unsubscribe.mjs +91 -0
- package/dist/runtime/server/routes/mail/webhooks.post.d.ts +12 -0
- package/dist/runtime/server/routes/mail/webhooks.post.mjs +71 -0
- package/dist/runtime/server/routes/notifications/[id]/read.post.d.ts +4 -0
- package/dist/runtime/server/routes/notifications/[id]/read.post.mjs +13 -0
- package/dist/runtime/server/routes/notifications/[id].delete.d.ts +4 -0
- package/dist/runtime/server/routes/notifications/[id].delete.mjs +13 -0
- package/dist/runtime/server/routes/notifications/[id].get.d.ts +2 -0
- package/dist/runtime/server/routes/notifications/[id].get.mjs +15 -0
- package/dist/runtime/server/routes/notifications/index.get.d.ts +6 -0
- package/dist/runtime/server/routes/notifications/index.get.mjs +18 -0
- package/dist/runtime/server/routes/notifications/push/subscribe.post.d.ts +4 -0
- package/dist/runtime/server/routes/notifications/push/subscribe.post.mjs +20 -0
- package/dist/runtime/server/routes/notifications/push/unsubscribe.post.d.ts +4 -0
- package/dist/runtime/server/routes/notifications/push/unsubscribe.post.mjs +15 -0
- package/dist/runtime/server/routes/notifications/read-all.post.d.ts +4 -0
- package/dist/runtime/server/routes/notifications/read-all.post.mjs +11 -0
- package/dist/runtime/server/routes/payments/cancel.post.d.ts +7 -0
- package/dist/runtime/server/routes/payments/cancel.post.mjs +9 -0
- package/dist/runtime/server/routes/payments/change-plan.post.d.ts +7 -0
- package/dist/runtime/server/routes/payments/change-plan.post.mjs +12 -0
- package/dist/runtime/server/routes/payments/checkout.post.d.ts +9 -0
- package/dist/runtime/server/routes/payments/checkout.post.mjs +16 -0
- package/dist/runtime/server/routes/payments/pause.post.d.ts +7 -0
- package/dist/runtime/server/routes/payments/pause.post.mjs +9 -0
- package/dist/runtime/server/routes/payments/portal.post.d.ts +5 -0
- package/dist/runtime/server/routes/payments/portal.post.mjs +9 -0
- package/dist/runtime/server/routes/payments/subscription.get.d.ts +6 -0
- package/dist/runtime/server/routes/payments/subscription.get.mjs +9 -0
- package/dist/runtime/server/routes/payments/usage.get.d.ts +12 -0
- package/dist/runtime/server/routes/payments/usage.get.mjs +18 -0
- package/dist/runtime/server/routes/payments/usage.post.d.ts +6 -0
- package/dist/runtime/server/routes/payments/usage.post.mjs +24 -0
- package/dist/runtime/server/routes/payments/webhook.post.d.ts +14 -0
- package/dist/runtime/server/routes/payments/webhook.post.mjs +21 -0
- package/dist/runtime/server/routes/realtime/broadcast.post.d.ts +4 -0
- package/dist/runtime/server/routes/realtime/broadcast.post.mjs +26 -0
- package/dist/runtime/server/routes/realtime/history.delete.d.ts +13 -0
- package/dist/runtime/server/routes/realtime/history.delete.mjs +12 -0
- package/dist/runtime/server/routes/realtime/history.get.d.ts +16 -0
- package/dist/runtime/server/routes/realtime/history.get.mjs +15 -0
- package/dist/runtime/server/routes/realtime/presence.get.d.ts +6 -0
- package/dist/runtime/server/routes/realtime/presence.get.mjs +27 -0
- package/dist/runtime/server/routes/realtime/state.get.d.ts +5 -0
- package/dist/runtime/server/routes/realtime/state.get.mjs +26 -0
- package/dist/runtime/server/routes/realtime/state.post.d.ts +6 -0
- package/dist/runtime/server/routes/realtime/state.post.mjs +37 -0
- package/dist/runtime/server/routes/realtime/ws.get.d.ts +2 -0
- package/dist/runtime/server/routes/realtime/ws.get.mjs +73 -0
- package/dist/runtime/server/routes/robots.txt.get.d.ts +2 -0
- package/dist/runtime/server/routes/robots.txt.get.mjs +41 -0
- package/dist/runtime/server/routes/rss.xml.get.d.ts +2 -0
- package/dist/runtime/server/routes/rss.xml.get.mjs +2 -0
- package/dist/runtime/server/routes/sitemap.xml.get.d.ts +2 -0
- package/dist/runtime/server/routes/sitemap.xml.get.mjs +84 -0
- package/dist/runtime/server/routes/storage/file/[...key].delete.d.ts +4 -0
- package/dist/runtime/server/routes/storage/file/[...key].delete.mjs +23 -0
- package/dist/runtime/server/routes/storage/file/[...key].get.d.ts +2 -0
- package/dist/runtime/server/routes/storage/file/[...key].get.mjs +23 -0
- package/dist/runtime/server/routes/storage/files.get.d.ts +14 -0
- package/dist/runtime/server/routes/storage/files.get.mjs +11 -0
- package/dist/runtime/server/routes/storage/presigned.post.d.ts +8 -0
- package/dist/runtime/server/routes/storage/presigned.post.mjs +39 -0
- package/dist/runtime/server/routes/storage/sign.post.d.ts +6 -0
- package/dist/runtime/server/routes/storage/sign.post.mjs +24 -0
- package/dist/runtime/server/routes/storage/upload.post.d.ts +22 -0
- package/dist/runtime/server/routes/storage/upload.post.mjs +96 -0
- package/dist/runtime/server/routes/tenants/[tenant]/domains/[domain]/index.delete.d.ts +8 -0
- package/dist/runtime/server/routes/tenants/[tenant]/domains/[domain]/index.delete.mjs +39 -0
- package/dist/runtime/server/routes/tenants/[tenant]/domains/[domain]/verify.post.d.ts +8 -0
- package/dist/runtime/server/routes/tenants/[tenant]/domains/[domain]/verify.post.mjs +39 -0
- package/dist/runtime/server/routes/tenants/[tenant]/domains/index.get.d.ts +8 -0
- package/dist/runtime/server/routes/tenants/[tenant]/domains/index.get.mjs +20 -0
- package/dist/runtime/server/routes/tenants/[tenant]/domains/index.post.d.ts +8 -0
- package/dist/runtime/server/routes/tenants/[tenant]/domains/index.post.mjs +44 -0
- package/dist/runtime/server/routes/tenants/current.get.d.ts +8 -0
- package/dist/runtime/server/routes/tenants/current.get.mjs +6 -0
- package/dist/runtime/server/tasks/cms/publish.d.ts +4 -0
- package/dist/runtime/server/tasks/cms/publish.mjs +18 -0
- package/dist/runtime/server/utils/agent-store.d.ts +24 -0
- package/dist/runtime/server/utils/agent-store.mjs +53 -0
- package/dist/runtime/server/utils/agents-registry-fallback.d.ts +1 -0
- package/dist/runtime/server/utils/agents-registry-fallback.mjs +1 -0
- package/dist/runtime/server/utils/ai.d.ts +119 -0
- package/dist/runtime/server/utils/ai.mjs +607 -0
- package/dist/runtime/server/utils/analytics-query.d.ts +109 -0
- package/dist/runtime/server/utils/analytics-query.mjs +234 -0
- package/dist/runtime/server/utils/auth.d.ts +60 -0
- package/dist/runtime/server/utils/auth.mjs +361 -0
- package/dist/runtime/server/utils/blob-store.d.ts +13 -0
- package/dist/runtime/server/utils/blob-store.mjs +60 -0
- package/dist/runtime/server/utils/bots.d.ts +21 -0
- package/dist/runtime/server/utils/bots.mjs +491 -0
- package/dist/runtime/server/utils/cache.d.ts +33 -0
- package/dist/runtime/server/utils/cache.mjs +128 -0
- package/dist/runtime/server/utils/cms-events.d.ts +17 -0
- package/dist/runtime/server/utils/cms-events.mjs +80 -0
- package/dist/runtime/server/utils/cms-filter.d.ts +3 -0
- package/dist/runtime/server/utils/cms-filter.mjs +34 -0
- package/dist/runtime/server/utils/cms.d.ts +278 -0
- package/dist/runtime/server/utils/cms.mjs +3268 -0
- package/dist/runtime/server/utils/config.d.ts +27 -0
- package/dist/runtime/server/utils/config.mjs +38 -0
- package/dist/runtime/server/utils/context.d.ts +13 -0
- package/dist/runtime/server/utils/context.mjs +98 -0
- package/dist/runtime/server/utils/crypto.d.ts +16 -0
- package/dist/runtime/server/utils/crypto.mjs +56 -0
- package/dist/runtime/server/utils/dev-logs.d.ts +21 -0
- package/dist/runtime/server/utils/dev-logs.mjs +41 -0
- package/dist/runtime/server/utils/dev-proxy.d.ts +1 -0
- package/dist/runtime/server/utils/dev-proxy.mjs +69 -0
- package/dist/runtime/server/utils/domains.d.ts +46 -0
- package/dist/runtime/server/utils/domains.mjs +318 -0
- package/dist/runtime/server/utils/drizzle.d.ts +10 -0
- package/dist/runtime/server/utils/drizzle.mjs +36 -0
- package/dist/runtime/server/utils/email-templates.d.ts +44 -0
- package/dist/runtime/server/utils/email-templates.mjs +373 -0
- package/dist/runtime/server/utils/feed.d.ts +42 -0
- package/dist/runtime/server/utils/feed.mjs +161 -0
- package/dist/runtime/server/utils/impersonation.d.ts +67 -0
- package/dist/runtime/server/utils/impersonation.mjs +298 -0
- package/dist/runtime/server/utils/jobs.d.ts +70 -0
- package/dist/runtime/server/utils/jobs.mjs +125 -0
- package/dist/runtime/server/utils/kv-store.d.ts +9 -0
- package/dist/runtime/server/utils/kv-store.mjs +32 -0
- package/dist/runtime/server/utils/mail-suppression.d.ts +62 -0
- package/dist/runtime/server/utils/mail-suppression.mjs +328 -0
- package/dist/runtime/server/utils/mail.d.ts +90 -0
- package/dist/runtime/server/utils/mail.mjs +366 -0
- package/dist/runtime/server/utils/notifications.d.ts +51 -0
- package/dist/runtime/server/utils/notifications.mjs +215 -0
- package/dist/runtime/server/utils/organization.d.ts +54 -0
- package/dist/runtime/server/utils/organization.mjs +354 -0
- package/dist/runtime/server/utils/passwordless.d.ts +48 -0
- package/dist/runtime/server/utils/passwordless.mjs +179 -0
- package/dist/runtime/server/utils/payments.d.ts +121 -0
- package/dist/runtime/server/utils/payments.mjs +1067 -0
- package/dist/runtime/server/utils/queue-fallback.d.ts +2 -0
- package/dist/runtime/server/utils/queue-fallback.mjs +2 -0
- package/dist/runtime/server/utils/queue.d.ts +4 -0
- package/dist/runtime/server/utils/queue.mjs +3 -0
- package/dist/runtime/server/utils/rag.d.ts +25 -0
- package/dist/runtime/server/utils/rag.mjs +333 -0
- package/dist/runtime/server/utils/ratelimit.d.ts +45 -0
- package/dist/runtime/server/utils/ratelimit.mjs +182 -0
- package/dist/runtime/server/utils/realtime.d.ts +110 -0
- package/dist/runtime/server/utils/realtime.mjs +197 -0
- package/dist/runtime/server/utils/schema-fallback.d.ts +3 -0
- package/dist/runtime/server/utils/schema-fallback.mjs +2 -0
- package/dist/runtime/server/utils/session.d.ts +72 -0
- package/dist/runtime/server/utils/session.mjs +287 -0
- package/dist/runtime/server/utils/sso.d.ts +49 -0
- package/dist/runtime/server/utils/sso.mjs +131 -0
- package/dist/runtime/server/utils/storage.d.ts +111 -0
- package/dist/runtime/server/utils/storage.mjs +654 -0
- package/dist/runtime/server/utils/tenant.d.ts +44 -0
- package/dist/runtime/server/utils/tenant.mjs +285 -0
- package/dist/runtime/server/utils/totp.d.ts +33 -0
- package/dist/runtime/server/utils/totp.mjs +112 -0
- package/dist/runtime/server/utils/tracker.d.ts +72 -0
- package/dist/runtime/server/utils/tracker.mjs +139 -0
- package/dist/runtime/server/utils/turnstile.d.ts +17 -0
- package/dist/runtime/server/utils/turnstile.mjs +68 -0
- package/dist/runtime/server/utils/vapid.d.ts +35 -0
- package/dist/runtime/server/utils/vapid.mjs +211 -0
- package/dist/runtime/storage.d.ts +1 -0
- package/dist/runtime/storage.mjs +0 -0
- package/dist/runtime/types/ai.d.ts +68 -0
- package/dist/runtime/types/ai.mjs +0 -0
- package/dist/runtime/types/analytics.d.ts +30 -0
- package/dist/runtime/types/analytics.mjs +0 -0
- package/dist/runtime/types/auth-globals.d.ts +14 -0
- package/dist/runtime/types/auth.d.ts +153 -0
- package/dist/runtime/types/auth.mjs +0 -0
- package/dist/runtime/types/browser.d.ts +29 -0
- package/dist/runtime/types/browser.mjs +0 -0
- package/dist/runtime/types/d1.d.ts +44 -0
- package/dist/runtime/types/d1.mjs +0 -0
- package/dist/runtime/types/drizzle.d.ts +13 -0
- package/dist/runtime/types/drizzle.mjs +0 -0
- package/dist/runtime/types/durable-object.d.ts +51 -0
- package/dist/runtime/types/durable-object.mjs +0 -0
- package/dist/runtime/types/email.d.ts +66 -0
- package/dist/runtime/types/email.mjs +0 -0
- package/dist/runtime/types/hyperdrive.d.ts +18 -0
- package/dist/runtime/types/hyperdrive.mjs +0 -0
- package/dist/runtime/types/imports.d.ts +58 -0
- package/dist/runtime/types/index.d.ts +156 -0
- package/dist/runtime/types/index.mjs +13 -0
- package/dist/runtime/types/queue.d.ts +67 -0
- package/dist/runtime/types/queue.mjs +0 -0
- package/dist/runtime/types/r2.d.ts +108 -0
- package/dist/runtime/types/r2.mjs +0 -0
- package/dist/runtime/types/ratelimit.d.ts +21 -0
- package/dist/runtime/types/ratelimit.mjs +0 -0
- package/dist/runtime/types/vectorize.d.ts +74 -0
- package/dist/runtime/types/vectorize.mjs +0 -0
- package/dist/runtime/vectorize.d.ts +21 -0
- package/dist/runtime/vectorize.mjs +53 -0
- package/dist/types.d.ts +2750 -0
- package/dist/types.mjs +1 -0
- package/dist/utils/agents.d.ts +8 -0
- package/dist/utils/auth-schema.d.ts +11 -0
- package/dist/utils/bindings.d.ts +16 -0
- package/dist/utils/cms-schema.d.ts +1 -0
- package/dist/utils/cms-sources.d.ts +62 -0
- package/dist/utils/drizzle.d.ts +42 -0
- package/dist/utils/jobs.d.ts +8 -0
- package/dist/utils/local-db.d.ts +28 -0
- package/dist/utils/notifications-schema.d.ts +15 -0
- package/dist/utils/payments-schema.d.ts +11 -0
- package/dist/utils/pkg.d.ts +15 -0
- package/dist/utils/queue.d.ts +8 -0
- package/dist/utils/tenants-schema.d.ts +11 -0
- package/package.json +104 -0
|
@@ -0,0 +1,1067 @@
|
|
|
1
|
+
import { useNitroApp } from "nitro/app";
|
|
2
|
+
import { createError, useRuntimeConfig, getRequestHeader } from "nuxt/server";
|
|
3
|
+
import { getD1Binding } from "../../db.mjs";
|
|
4
|
+
import { getQarpeoConfig } from "./config.mjs";
|
|
5
|
+
import { getAuthContext } from "./context.mjs";
|
|
6
|
+
const STRIPE_API_VERSION = "2024-06-20";
|
|
7
|
+
function getPaymentsDb(event) {
|
|
8
|
+
const bindingName = globalThis.process?.env?.QARPEO_DEFAULT_DB_BINDING || "DB";
|
|
9
|
+
return getD1Binding(bindingName, event);
|
|
10
|
+
}
|
|
11
|
+
function nanoid(size = 21) {
|
|
12
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
13
|
+
const bytes = crypto.getRandomValues(new Uint8Array(size));
|
|
14
|
+
return Array.from(bytes, (b) => chars[b % chars.length]).join("");
|
|
15
|
+
}
|
|
16
|
+
export function getPaymentsConfig(event) {
|
|
17
|
+
const cfg = getQarpeoConfig(event).payments || {};
|
|
18
|
+
const cfEnv = event?.context?.cloudflare?.env || {};
|
|
19
|
+
const provider = cfg.provider || "stripe";
|
|
20
|
+
let secretKey = cfg.secretKey || "";
|
|
21
|
+
let webhookSecret = cfg.webhookSecret || "";
|
|
22
|
+
if (!secretKey) {
|
|
23
|
+
if (provider === "stripe") {
|
|
24
|
+
secretKey = process.env.STRIPE_SECRET_KEY || cfEnv.STRIPE_SECRET_KEY || "";
|
|
25
|
+
} else if (provider === "lemonsqueezy") {
|
|
26
|
+
secretKey = process.env.LEMONSQUEEZY_API_KEY || cfEnv.LEMONSQUEEZY_API_KEY || "";
|
|
27
|
+
} else if (provider === "polar") {
|
|
28
|
+
secretKey = process.env.POLAR_ACCESS_TOKEN || cfEnv.POLAR_ACCESS_TOKEN || "";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!webhookSecret) {
|
|
32
|
+
if (provider === "stripe") {
|
|
33
|
+
webhookSecret = process.env.STRIPE_WEBHOOK_SECRET || cfEnv.STRIPE_WEBHOOK_SECRET || "";
|
|
34
|
+
} else if (provider === "lemonsqueezy") {
|
|
35
|
+
webhookSecret = process.env.LEMONSQUEEZY_WEBHOOK_SECRET || cfEnv.LEMONSQUEEZY_WEBHOOK_SECRET || "";
|
|
36
|
+
} else if (provider === "polar") {
|
|
37
|
+
webhookSecret = process.env.POLAR_WEBHOOK_SECRET || cfEnv.POLAR_WEBHOOK_SECRET || "";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
prefix: "/api/payments",
|
|
42
|
+
portalReturnUrl: "/",
|
|
43
|
+
...cfg,
|
|
44
|
+
provider,
|
|
45
|
+
secretKey,
|
|
46
|
+
webhookSecret
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
let tablesEnsured = false;
|
|
50
|
+
export async function ensurePaymentsTables(event) {
|
|
51
|
+
if (tablesEnsured) return;
|
|
52
|
+
const db = getPaymentsDb(event);
|
|
53
|
+
const statements = [
|
|
54
|
+
`CREATE TABLE IF NOT EXISTS _qarpeo_customers (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
user_id TEXT NOT NULL,
|
|
57
|
+
org_id TEXT,
|
|
58
|
+
provider TEXT NOT NULL DEFAULT 'stripe',
|
|
59
|
+
customer_id TEXT NOT NULL,
|
|
60
|
+
email TEXT NOT NULL,
|
|
61
|
+
name TEXT,
|
|
62
|
+
metadata_json TEXT DEFAULT '{}',
|
|
63
|
+
created_at INTEGER NOT NULL,
|
|
64
|
+
updated_at INTEGER NOT NULL
|
|
65
|
+
);`,
|
|
66
|
+
`CREATE TABLE IF NOT EXISTS _qarpeo_subscriptions (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
customer_id TEXT NOT NULL,
|
|
69
|
+
user_id TEXT NOT NULL,
|
|
70
|
+
org_id TEXT,
|
|
71
|
+
provider TEXT NOT NULL DEFAULT 'stripe',
|
|
72
|
+
subscription_id TEXT NOT NULL UNIQUE,
|
|
73
|
+
plan_id TEXT NOT NULL,
|
|
74
|
+
price_id TEXT NOT NULL,
|
|
75
|
+
status TEXT NOT NULL,
|
|
76
|
+
quantity INTEGER NOT NULL DEFAULT 1,
|
|
77
|
+
cancel_at_period_end INTEGER NOT NULL DEFAULT 0,
|
|
78
|
+
current_period_start INTEGER NOT NULL,
|
|
79
|
+
current_period_end INTEGER NOT NULL,
|
|
80
|
+
trial_end INTEGER,
|
|
81
|
+
canceled_at INTEGER,
|
|
82
|
+
metadata_json TEXT DEFAULT '{}',
|
|
83
|
+
created_at INTEGER NOT NULL,
|
|
84
|
+
updated_at INTEGER NOT NULL
|
|
85
|
+
);`,
|
|
86
|
+
`CREATE TABLE IF NOT EXISTS _qarpeo_webhook_events (
|
|
87
|
+
event_id TEXT PRIMARY KEY,
|
|
88
|
+
provider TEXT NOT NULL,
|
|
89
|
+
event_type TEXT,
|
|
90
|
+
processed_at INTEGER NOT NULL
|
|
91
|
+
);`,
|
|
92
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_customers_user_idx ON _qarpeo_customers(user_id);`,
|
|
93
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS _qarpeo_customers_customer_idx ON _qarpeo_customers(customer_id);`,
|
|
94
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_customers_org_idx ON _qarpeo_customers(org_id);`,
|
|
95
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_subs_user_idx ON _qarpeo_subscriptions(user_id);`,
|
|
96
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_subs_subscription_idx ON _qarpeo_subscriptions(subscription_id);`,
|
|
97
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_subs_customer_idx ON _qarpeo_subscriptions(customer_id);`,
|
|
98
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_subs_status_idx ON _qarpeo_subscriptions(status);`,
|
|
99
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_subs_org_idx ON _qarpeo_subscriptions(org_id);`,
|
|
100
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_webhook_events_event_idx ON _qarpeo_webhook_events(event_id);`,
|
|
101
|
+
`CREATE TABLE IF NOT EXISTS _qarpeo_usage_records (
|
|
102
|
+
id TEXT PRIMARY KEY,
|
|
103
|
+
user_id TEXT,
|
|
104
|
+
org_id TEXT,
|
|
105
|
+
customer_id TEXT,
|
|
106
|
+
subscription_id TEXT,
|
|
107
|
+
event_name TEXT NOT NULL,
|
|
108
|
+
quantity REAL NOT NULL DEFAULT 1,
|
|
109
|
+
action TEXT NOT NULL DEFAULT 'increment',
|
|
110
|
+
metadata_json TEXT DEFAULT '{}',
|
|
111
|
+
timestamp INTEGER NOT NULL,
|
|
112
|
+
created_at INTEGER NOT NULL
|
|
113
|
+
);`,
|
|
114
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_usage_user_idx ON _qarpeo_usage_records(user_id);`,
|
|
115
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_usage_sub_idx ON _qarpeo_usage_records(subscription_id);`,
|
|
116
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_usage_event_idx ON _qarpeo_usage_records(event_name);`,
|
|
117
|
+
`CREATE INDEX IF NOT EXISTS _qarpeo_usage_timestamp_idx ON _qarpeo_usage_records(timestamp);`
|
|
118
|
+
];
|
|
119
|
+
for (const rawSql of statements) {
|
|
120
|
+
const sql = rawSql.replace(/\s+/g, " ").trim();
|
|
121
|
+
try {
|
|
122
|
+
await db.exec(sql);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.warn("[qarpeo:payments] ensurePaymentsTables warning:", err);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
tablesEnsured = true;
|
|
128
|
+
}
|
|
129
|
+
import { timingSafeEqualString } from "./crypto.mjs";
|
|
130
|
+
async function computeHmacSha256(keyStr, dataStr) {
|
|
131
|
+
const encoder = new TextEncoder();
|
|
132
|
+
const key = await crypto.subtle.importKey(
|
|
133
|
+
"raw",
|
|
134
|
+
encoder.encode(keyStr),
|
|
135
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
136
|
+
false,
|
|
137
|
+
["sign"]
|
|
138
|
+
);
|
|
139
|
+
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(dataStr));
|
|
140
|
+
return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
141
|
+
}
|
|
142
|
+
export async function verifyStripeSignature(rawBody, signatureHeader, secret, toleranceSec = 300) {
|
|
143
|
+
if (!signatureHeader || !secret) return false;
|
|
144
|
+
const parts = signatureHeader.split(",");
|
|
145
|
+
let timestamp = "";
|
|
146
|
+
const signatures = [];
|
|
147
|
+
for (const p of parts) {
|
|
148
|
+
const [k, v] = p.split("=");
|
|
149
|
+
if (k === "t") timestamp = v;
|
|
150
|
+
if (k === "v1") signatures.push(v);
|
|
151
|
+
}
|
|
152
|
+
if (!timestamp || !signatures.length) return false;
|
|
153
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
154
|
+
const tsNum = parseInt(timestamp, 10);
|
|
155
|
+
if (isNaN(tsNum) || Math.abs(now - tsNum) > toleranceSec) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const payload = `${timestamp}.${rawBody}`;
|
|
159
|
+
const expectedSignature = await computeHmacSha256(secret, payload);
|
|
160
|
+
return signatures.some((sig) => timingSafeEqualString(sig, expectedSignature));
|
|
161
|
+
}
|
|
162
|
+
export async function verifyLemonSqueezySignature(rawBody, signatureHeader, secret) {
|
|
163
|
+
if (!signatureHeader || !secret) return false;
|
|
164
|
+
const expected = await computeHmacSha256(secret, rawBody);
|
|
165
|
+
return timingSafeEqualString(signatureHeader, expected);
|
|
166
|
+
}
|
|
167
|
+
export async function verifyPolarSignature(rawBody, signatureHeader, secret) {
|
|
168
|
+
if (!signatureHeader || !secret) return false;
|
|
169
|
+
const expected = await computeHmacSha256(secret, rawBody);
|
|
170
|
+
return timingSafeEqualString(signatureHeader, expected);
|
|
171
|
+
}
|
|
172
|
+
export async function verifyPaddleSignature(rawBody, signatureHeader, secret) {
|
|
173
|
+
if (!signatureHeader || !secret) return false;
|
|
174
|
+
const parts = {};
|
|
175
|
+
for (const item of signatureHeader.split(";")) {
|
|
176
|
+
const idx = item.indexOf("=");
|
|
177
|
+
if (idx !== -1) {
|
|
178
|
+
parts[item.slice(0, idx).trim()] = item.slice(idx + 1).trim();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const ts = parts.ts;
|
|
182
|
+
const h1 = parts.h1;
|
|
183
|
+
if (!ts || !h1) return false;
|
|
184
|
+
const payload = `${ts}:${rawBody}`;
|
|
185
|
+
const expected = await computeHmacSha256(secret, payload);
|
|
186
|
+
return timingSafeEqualString(h1, expected);
|
|
187
|
+
}
|
|
188
|
+
export async function processPaymentWebhook(event, rawBody) {
|
|
189
|
+
await ensurePaymentsTables(event);
|
|
190
|
+
const db = getPaymentsDb(event);
|
|
191
|
+
const config = getPaymentsConfig(event);
|
|
192
|
+
const provider = config.provider;
|
|
193
|
+
let isValid = false;
|
|
194
|
+
if (provider === "stripe") {
|
|
195
|
+
const sig = getRequestHeader(event, "stripe-signature");
|
|
196
|
+
isValid = await verifyStripeSignature(rawBody, sig, config.webhookSecret);
|
|
197
|
+
} else if (provider === "lemonsqueezy") {
|
|
198
|
+
const sig = getRequestHeader(event, "x-signature");
|
|
199
|
+
isValid = await verifyLemonSqueezySignature(rawBody, sig, config.webhookSecret);
|
|
200
|
+
} else if (provider === "polar") {
|
|
201
|
+
const sig = getRequestHeader(event, "webhook-signature");
|
|
202
|
+
isValid = await verifyPolarSignature(rawBody, sig, config.webhookSecret);
|
|
203
|
+
}
|
|
204
|
+
if (!isValid) {
|
|
205
|
+
throw createError({
|
|
206
|
+
statusCode: 400,
|
|
207
|
+
statusMessage: "Bad Request",
|
|
208
|
+
message: "Invalid webhook signature"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
let payload;
|
|
212
|
+
try {
|
|
213
|
+
payload = JSON.parse(rawBody);
|
|
214
|
+
} catch {
|
|
215
|
+
throw createError({
|
|
216
|
+
statusCode: 400,
|
|
217
|
+
statusMessage: "Bad Request",
|
|
218
|
+
message: "Invalid JSON payload"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
let eventType = "";
|
|
223
|
+
let eventData = null;
|
|
224
|
+
let affectedSubId = null;
|
|
225
|
+
let eventId = "";
|
|
226
|
+
if (provider === "stripe") {
|
|
227
|
+
eventId = payload.id || "";
|
|
228
|
+
} else if (provider === "lemonsqueezy") {
|
|
229
|
+
eventId = String(payload.meta?.event_id || payload.meta?.id || payload.data?.id || "");
|
|
230
|
+
} else if (provider === "polar") {
|
|
231
|
+
eventId = payload.id || "";
|
|
232
|
+
}
|
|
233
|
+
if (provider === "lemonsqueezy") {
|
|
234
|
+
eventType = payload.meta?.event_name || "";
|
|
235
|
+
} else {
|
|
236
|
+
eventType = payload.type || "";
|
|
237
|
+
}
|
|
238
|
+
if (eventId) {
|
|
239
|
+
const dedupRes = await db.prepare(
|
|
240
|
+
"INSERT OR IGNORE INTO _qarpeo_webhook_events (event_id, provider, event_type, processed_at) VALUES (?, ?, ?, ?)"
|
|
241
|
+
).bind(eventId, provider, eventType || null, now).run();
|
|
242
|
+
if (!dedupRes?.meta?.changes) {
|
|
243
|
+
return { received: true, skipped: true, reason: "duplicate" };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (provider === "stripe") {
|
|
247
|
+
eventType = payload.type;
|
|
248
|
+
const obj = payload.data?.object || {};
|
|
249
|
+
eventData = obj;
|
|
250
|
+
switch (eventType) {
|
|
251
|
+
case "checkout.session.completed": {
|
|
252
|
+
const customerId = obj.customer;
|
|
253
|
+
const customerEmail = obj.customer_details?.email || obj.customer_email || "";
|
|
254
|
+
const customerName = obj.customer_details?.name || null;
|
|
255
|
+
const userId = obj.metadata?.userId || obj.client_reference_id;
|
|
256
|
+
const orgId = obj.metadata?.orgId || null;
|
|
257
|
+
if (userId && customerId) {
|
|
258
|
+
await db.prepare(
|
|
259
|
+
`INSERT INTO _qarpeo_customers (id, user_id, org_id, provider, customer_id, email, name, metadata_json, created_at, updated_at)
|
|
260
|
+
VALUES (?, ?, ?, 'stripe', ?, ?, ?, ?, ?, ?)
|
|
261
|
+
ON CONFLICT(customer_id) DO UPDATE SET email = excluded.email, name = excluded.name, org_id = excluded.org_id, metadata_json = excluded.metadata_json, updated_at = excluded.updated_at`
|
|
262
|
+
).bind(
|
|
263
|
+
nanoid(),
|
|
264
|
+
userId,
|
|
265
|
+
orgId,
|
|
266
|
+
customerId,
|
|
267
|
+
customerEmail,
|
|
268
|
+
customerName,
|
|
269
|
+
JSON.stringify(obj.metadata || {}),
|
|
270
|
+
now,
|
|
271
|
+
now
|
|
272
|
+
).run();
|
|
273
|
+
}
|
|
274
|
+
const subId = obj.subscription;
|
|
275
|
+
if (subId) {
|
|
276
|
+
affectedSubId = subId;
|
|
277
|
+
const planId = obj.metadata?.planId || "default";
|
|
278
|
+
await db.prepare(
|
|
279
|
+
`INSERT INTO _qarpeo_subscriptions
|
|
280
|
+
(id, customer_id, user_id, org_id, provider, subscription_id, plan_id, price_id, status, quantity, cancel_at_period_end, current_period_start, current_period_end, trial_end, canceled_at, metadata_json, created_at, updated_at)
|
|
281
|
+
VALUES (?, ?, ?, ?, 'stripe', ?, ?, '', 'active', 1, 0, ?, ?, null, null, ?, ?, ?)
|
|
282
|
+
ON CONFLICT(subscription_id) DO NOTHING`
|
|
283
|
+
).bind(
|
|
284
|
+
nanoid(),
|
|
285
|
+
customerId || "",
|
|
286
|
+
userId || "",
|
|
287
|
+
orgId,
|
|
288
|
+
subId,
|
|
289
|
+
planId,
|
|
290
|
+
now,
|
|
291
|
+
now + 30 * 864e5,
|
|
292
|
+
JSON.stringify(obj.metadata || {}),
|
|
293
|
+
now,
|
|
294
|
+
now
|
|
295
|
+
).run();
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case "customer.subscription.created":
|
|
300
|
+
case "customer.subscription.updated": {
|
|
301
|
+
const subId = obj.id;
|
|
302
|
+
affectedSubId = subId;
|
|
303
|
+
const customerId = obj.customer;
|
|
304
|
+
const status = obj.status;
|
|
305
|
+
const priceId = obj.items?.data?.[0]?.price?.id || obj.plan?.id || "";
|
|
306
|
+
const planId = obj.metadata?.planId || obj.items?.data?.[0]?.price?.lookup_key || priceId;
|
|
307
|
+
const quantity = obj.items?.data?.[0]?.quantity || obj.quantity || 1;
|
|
308
|
+
const cancelAtPeriodEnd = obj.cancel_at_period_end ? 1 : 0;
|
|
309
|
+
const periodStart = obj.current_period_start ? obj.current_period_start * 1e3 : now;
|
|
310
|
+
const periodEnd = obj.current_period_end ? obj.current_period_end * 1e3 : now + 30 * 864e5;
|
|
311
|
+
const trialEnd = obj.trial_end ? obj.trial_end * 1e3 : null;
|
|
312
|
+
const canceledAt = obj.canceled_at ? obj.canceled_at * 1e3 : null;
|
|
313
|
+
const cust = await db.prepare("SELECT user_id, org_id FROM _qarpeo_customers WHERE customer_id = ? LIMIT 1").bind(customerId).first();
|
|
314
|
+
const userId = cust?.user_id || obj.metadata?.userId || "";
|
|
315
|
+
const orgId = cust?.org_id || obj.metadata?.orgId || null;
|
|
316
|
+
await db.prepare(
|
|
317
|
+
`INSERT INTO _qarpeo_subscriptions
|
|
318
|
+
(id, customer_id, user_id, org_id, provider, subscription_id, plan_id, price_id, status, quantity, cancel_at_period_end, current_period_start, current_period_end, trial_end, canceled_at, metadata_json, created_at, updated_at)
|
|
319
|
+
VALUES (?, ?, ?, ?, 'stripe', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
320
|
+
ON CONFLICT(subscription_id) DO UPDATE SET
|
|
321
|
+
plan_id = excluded.plan_id, price_id = excluded.price_id, status = excluded.status,
|
|
322
|
+
quantity = excluded.quantity, cancel_at_period_end = excluded.cancel_at_period_end,
|
|
323
|
+
current_period_start = excluded.current_period_start, current_period_end = excluded.current_period_end,
|
|
324
|
+
trial_end = excluded.trial_end, canceled_at = excluded.canceled_at,
|
|
325
|
+
metadata_json = excluded.metadata_json, updated_at = excluded.updated_at`
|
|
326
|
+
).bind(
|
|
327
|
+
nanoid(),
|
|
328
|
+
customerId,
|
|
329
|
+
userId,
|
|
330
|
+
orgId,
|
|
331
|
+
subId,
|
|
332
|
+
planId,
|
|
333
|
+
priceId,
|
|
334
|
+
status,
|
|
335
|
+
quantity,
|
|
336
|
+
cancelAtPeriodEnd,
|
|
337
|
+
periodStart,
|
|
338
|
+
periodEnd,
|
|
339
|
+
trialEnd,
|
|
340
|
+
canceledAt,
|
|
341
|
+
JSON.stringify(obj.metadata || {}),
|
|
342
|
+
now,
|
|
343
|
+
now
|
|
344
|
+
).run();
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case "customer.subscription.deleted": {
|
|
348
|
+
const subId = obj.id;
|
|
349
|
+
affectedSubId = subId;
|
|
350
|
+
await db.prepare(
|
|
351
|
+
"UPDATE _qarpeo_subscriptions SET status = 'canceled', canceled_at = ?, updated_at = ? WHERE subscription_id = ?"
|
|
352
|
+
).bind(now, now, subId).run();
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
case "invoice.payment_succeeded": {
|
|
356
|
+
const subId = obj.subscription;
|
|
357
|
+
if (subId) {
|
|
358
|
+
affectedSubId = subId;
|
|
359
|
+
await db.prepare(
|
|
360
|
+
"UPDATE _qarpeo_subscriptions SET status = 'active', updated_at = ? WHERE subscription_id = ?"
|
|
361
|
+
).bind(now, subId).run();
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
case "invoice.payment_failed": {
|
|
366
|
+
const subId = obj.subscription;
|
|
367
|
+
if (subId) {
|
|
368
|
+
affectedSubId = subId;
|
|
369
|
+
await db.prepare(
|
|
370
|
+
"UPDATE _qarpeo_subscriptions SET status = 'past_due', updated_at = ? WHERE subscription_id = ?"
|
|
371
|
+
).bind(now, subId).run();
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (provider === "lemonsqueezy") {
|
|
378
|
+
eventType = payload.meta?.event_name;
|
|
379
|
+
const data = payload.data?.attributes || {};
|
|
380
|
+
eventData = data;
|
|
381
|
+
const subId = String(payload.data?.id || "");
|
|
382
|
+
affectedSubId = subId;
|
|
383
|
+
const customerId = String(data.customer_id || "");
|
|
384
|
+
const userEmail = data.user_email || "";
|
|
385
|
+
const customData = payload.meta?.custom_data || {};
|
|
386
|
+
const userId = customData.user_id || customData.userId || "";
|
|
387
|
+
const orgId = customData.org_id || customData.orgId || null;
|
|
388
|
+
const planId = String(data.variant_id || "");
|
|
389
|
+
if (eventType === "subscription_created" || eventType === "subscription_updated" || eventType === "subscription_resumed") {
|
|
390
|
+
const status = data.status === "active" ? "active" : data.status === "on_trial" ? "trialing" : data.status === "past_due" ? "past_due" : "canceled";
|
|
391
|
+
const periodStart = data.renews_at ? new Date(data.created_at).getTime() : now;
|
|
392
|
+
const periodEnd = data.renews_at ? new Date(data.renews_at).getTime() : now + 30 * 864e5;
|
|
393
|
+
const cancelAtPeriodEnd = data.cancelled ? 1 : 0;
|
|
394
|
+
if (customerId && userId) {
|
|
395
|
+
await db.prepare(
|
|
396
|
+
"INSERT INTO _qarpeo_customers (id, user_id, org_id, provider, customer_id, email, name, metadata_json, created_at, updated_at) VALUES (?, ?, ?, 'lemonsqueezy', ?, ?, ?, '{}', ?, ?) ON CONFLICT(customer_id) DO UPDATE SET email = excluded.email, name = excluded.name, updated_at = excluded.updated_at"
|
|
397
|
+
).bind(nanoid(), userId, orgId, customerId, userEmail, data.user_name || null, now, now).run();
|
|
398
|
+
}
|
|
399
|
+
await db.prepare(
|
|
400
|
+
`INSERT INTO _qarpeo_subscriptions (id, customer_id, user_id, org_id, provider, subscription_id, plan_id, price_id, status, quantity, cancel_at_period_end, current_period_start, current_period_end, trial_end, canceled_at, metadata_json, created_at, updated_at)
|
|
401
|
+
VALUES (?, ?, ?, ?, 'lemonsqueezy', ?, ?, ?, ?, 1, ?, ?, ?, null, null, '{}', ?, ?)
|
|
402
|
+
ON CONFLICT(subscription_id) DO UPDATE SET status = excluded.status, cancel_at_period_end = excluded.cancel_at_period_end, current_period_start = excluded.current_period_start, current_period_end = excluded.current_period_end, updated_at = excluded.updated_at`
|
|
403
|
+
).bind(
|
|
404
|
+
nanoid(),
|
|
405
|
+
customerId,
|
|
406
|
+
userId,
|
|
407
|
+
orgId,
|
|
408
|
+
subId,
|
|
409
|
+
planId,
|
|
410
|
+
planId,
|
|
411
|
+
status,
|
|
412
|
+
cancelAtPeriodEnd,
|
|
413
|
+
periodStart,
|
|
414
|
+
periodEnd,
|
|
415
|
+
now,
|
|
416
|
+
now
|
|
417
|
+
).run();
|
|
418
|
+
} else if (eventType === "subscription_cancelled" || eventType === "subscription_expired") {
|
|
419
|
+
await db.prepare(
|
|
420
|
+
"UPDATE _qarpeo_subscriptions SET status = 'canceled', canceled_at = ?, updated_at = ? WHERE subscription_id = ?"
|
|
421
|
+
).bind(now, now, subId).run();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (provider === "polar") {
|
|
425
|
+
eventType = payload.type;
|
|
426
|
+
const data = payload.data || {};
|
|
427
|
+
eventData = data;
|
|
428
|
+
const subId = data.id;
|
|
429
|
+
affectedSubId = subId;
|
|
430
|
+
const customerId = data.customer_id || data.user_id || "";
|
|
431
|
+
const userId = data.metadata?.userId || data.user?.id || "";
|
|
432
|
+
const orgId = data.metadata?.orgId || null;
|
|
433
|
+
const priceId = data.price_id || data.product_id || "";
|
|
434
|
+
if (eventType === "subscription.created" || eventType === "subscription.updated" || eventType === "subscription.active") {
|
|
435
|
+
const status = data.status === "active" ? "active" : data.status === "canceled" ? "canceled" : "past_due";
|
|
436
|
+
const periodStart = data.current_period_start ? new Date(data.current_period_start).getTime() : now;
|
|
437
|
+
const periodEnd = data.current_period_end ? new Date(data.current_period_end).getTime() : now + 30 * 864e5;
|
|
438
|
+
await db.prepare(
|
|
439
|
+
`INSERT INTO _qarpeo_subscriptions (id, customer_id, user_id, org_id, provider, subscription_id, plan_id, price_id, status, quantity, cancel_at_period_end, current_period_start, current_period_end, trial_end, canceled_at, metadata_json, created_at, updated_at)
|
|
440
|
+
VALUES (?, ?, ?, ?, 'polar', ?, ?, ?, ?, 1, 0, ?, ?, null, null, '{}', ?, ?)
|
|
441
|
+
ON CONFLICT(subscription_id) DO UPDATE SET status = excluded.status, current_period_start = excluded.current_period_start, current_period_end = excluded.current_period_end, updated_at = excluded.updated_at`
|
|
442
|
+
).bind(
|
|
443
|
+
nanoid(),
|
|
444
|
+
customerId,
|
|
445
|
+
userId,
|
|
446
|
+
orgId,
|
|
447
|
+
subId,
|
|
448
|
+
priceId,
|
|
449
|
+
priceId,
|
|
450
|
+
status,
|
|
451
|
+
periodStart,
|
|
452
|
+
periodEnd,
|
|
453
|
+
now,
|
|
454
|
+
now
|
|
455
|
+
).run();
|
|
456
|
+
} else if (eventType === "subscription.canceled" || eventType === "subscription.revoked") {
|
|
457
|
+
await db.prepare(
|
|
458
|
+
"UPDATE _qarpeo_subscriptions SET status = 'canceled', canceled_at = ?, updated_at = ? WHERE subscription_id = ?"
|
|
459
|
+
).bind(now, now, subId).run();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (provider === "paddle") {
|
|
463
|
+
eventType = payload.event_type || payload.type || "";
|
|
464
|
+
const data = payload.data || {};
|
|
465
|
+
eventData = data;
|
|
466
|
+
const subId = String(data.id || "");
|
|
467
|
+
affectedSubId = subId;
|
|
468
|
+
const customerId = String(data.customer_id || "");
|
|
469
|
+
const customData = data.custom_data || {};
|
|
470
|
+
const userId = customData.userId || customData.user_id || "";
|
|
471
|
+
const orgId = customData.orgId || customData.org_id || null;
|
|
472
|
+
const priceId = data.items?.[0]?.price?.id || "";
|
|
473
|
+
const planId = customData.planId || priceId;
|
|
474
|
+
if (eventType === "subscription.created" || eventType === "subscription.updated" || eventType === "subscription.activated" || eventType === "subscription.resumed") {
|
|
475
|
+
const status = data.status === "active" ? "active" : data.status === "trialing" ? "trialing" : data.status === "past_due" ? "past_due" : data.status === "paused" ? "paused" : "canceled";
|
|
476
|
+
const periodStart = data.current_billing_period?.starts_at ? new Date(data.current_billing_period.starts_at).getTime() : now;
|
|
477
|
+
const periodEnd = data.current_billing_period?.ends_at ? new Date(data.current_billing_period.ends_at).getTime() : now + 30 * 864e5;
|
|
478
|
+
const cancelAtPeriodEnd = data.scheduled_change?.action === "cancel" ? 1 : 0;
|
|
479
|
+
const quantity = data.items?.[0]?.quantity || 1;
|
|
480
|
+
if (customerId && userId) {
|
|
481
|
+
await db.prepare(
|
|
482
|
+
`INSERT INTO _qarpeo_customers (id, user_id, org_id, provider, customer_id, email, name, metadata_json, created_at, updated_at)
|
|
483
|
+
VALUES (?, ?, ?, 'paddle', ?, ?, ?, ?, ?, ?)
|
|
484
|
+
ON CONFLICT(customer_id) DO UPDATE SET org_id = excluded.org_id, metadata_json = excluded.metadata_json, updated_at = excluded.updated_at`
|
|
485
|
+
).bind(
|
|
486
|
+
nanoid(),
|
|
487
|
+
userId,
|
|
488
|
+
orgId,
|
|
489
|
+
customerId,
|
|
490
|
+
customData.email || "",
|
|
491
|
+
customData.name || null,
|
|
492
|
+
JSON.stringify(customData),
|
|
493
|
+
now,
|
|
494
|
+
now
|
|
495
|
+
).run();
|
|
496
|
+
}
|
|
497
|
+
await db.prepare(
|
|
498
|
+
`INSERT INTO _qarpeo_subscriptions
|
|
499
|
+
(id, customer_id, user_id, org_id, provider, subscription_id, plan_id, price_id, status, quantity, cancel_at_period_end, current_period_start, current_period_end, trial_end, canceled_at, metadata_json, created_at, updated_at)
|
|
500
|
+
VALUES (?, ?, ?, ?, 'paddle', ?, ?, ?, ?, ?, ?, ?, ?, null, null, ?, ?, ?)
|
|
501
|
+
ON CONFLICT(subscription_id) DO UPDATE SET
|
|
502
|
+
plan_id = excluded.plan_id, price_id = excluded.price_id, status = excluded.status,
|
|
503
|
+
quantity = excluded.quantity, cancel_at_period_end = excluded.cancel_at_period_end,
|
|
504
|
+
current_period_start = excluded.current_period_start, current_period_end = excluded.current_period_end,
|
|
505
|
+
metadata_json = excluded.metadata_json, updated_at = excluded.updated_at`
|
|
506
|
+
).bind(
|
|
507
|
+
nanoid(),
|
|
508
|
+
customerId,
|
|
509
|
+
userId,
|
|
510
|
+
orgId,
|
|
511
|
+
subId,
|
|
512
|
+
planId,
|
|
513
|
+
priceId,
|
|
514
|
+
status,
|
|
515
|
+
quantity,
|
|
516
|
+
cancelAtPeriodEnd,
|
|
517
|
+
periodStart,
|
|
518
|
+
periodEnd,
|
|
519
|
+
JSON.stringify(customData),
|
|
520
|
+
now,
|
|
521
|
+
now
|
|
522
|
+
).run();
|
|
523
|
+
} else if (eventType === "subscription.canceled" || eventType === "subscription.past_due") {
|
|
524
|
+
const newStatus = eventType === "subscription.past_due" ? "past_due" : "canceled";
|
|
525
|
+
await db.prepare(
|
|
526
|
+
"UPDATE _qarpeo_subscriptions SET status = ?, canceled_at = ?, updated_at = ? WHERE subscription_id = ?"
|
|
527
|
+
).bind(newStatus, newStatus === "canceled" ? now : null, now, subId).run();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
let syncedSubscription = null;
|
|
531
|
+
if (affectedSubId) {
|
|
532
|
+
try {
|
|
533
|
+
const subRow = await db.prepare("SELECT * FROM _qarpeo_subscriptions WHERE subscription_id = ? LIMIT 1").bind(affectedSubId).first();
|
|
534
|
+
if (subRow) {
|
|
535
|
+
syncedSubscription = rowToSubscription(subRow);
|
|
536
|
+
}
|
|
537
|
+
} catch {
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
const nitroApp = useNitroApp();
|
|
542
|
+
await nitroApp.hooks?.callHook("qarpeo:payments:webhook", {
|
|
543
|
+
event,
|
|
544
|
+
type: eventType,
|
|
545
|
+
data: eventData,
|
|
546
|
+
subscription: syncedSubscription,
|
|
547
|
+
provider
|
|
548
|
+
});
|
|
549
|
+
} catch (hookErr) {
|
|
550
|
+
console.warn(
|
|
551
|
+
"[qarpeo:payments] Error calling qarpeo:payments:webhook hook:",
|
|
552
|
+
hookErr?.message || hookErr
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
return { success: true, eventType };
|
|
556
|
+
}
|
|
557
|
+
export async function createCheckoutSession(event, options) {
|
|
558
|
+
await ensurePaymentsTables(event);
|
|
559
|
+
const db = getPaymentsDb(event);
|
|
560
|
+
const config = getPaymentsConfig(event);
|
|
561
|
+
const auth = await getAuthContext(event);
|
|
562
|
+
const user = auth?.user;
|
|
563
|
+
if (!config.secretKey) {
|
|
564
|
+
throw createError({
|
|
565
|
+
statusCode: 500,
|
|
566
|
+
statusMessage: "Internal Server Error",
|
|
567
|
+
message: "Payment provider secret key is not configured (STRIPE_SECRET_KEY)"
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
let priceId = options.priceId;
|
|
571
|
+
const planId = options.planId || "default";
|
|
572
|
+
if (!priceId && config.plans && config.plans[planId]) {
|
|
573
|
+
priceId = config.plans[planId].priceId;
|
|
574
|
+
}
|
|
575
|
+
if (!priceId) {
|
|
576
|
+
throw createError({
|
|
577
|
+
statusCode: 400,
|
|
578
|
+
message: `Could not resolve price ID for plan "${planId}"`
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
const siteUrl = useRuntimeConfig().public?.siteUrl || "http://localhost:3000";
|
|
582
|
+
const successUrl = options.successUrl || config.checkoutUrls?.success || `${siteUrl}/?checkout=success`;
|
|
583
|
+
const cancelUrl = options.cancelUrl || config.checkoutUrls?.cancel || `${siteUrl}/?checkout=cancelled`;
|
|
584
|
+
const metadata = {
|
|
585
|
+
planId,
|
|
586
|
+
...options.metadata || {}
|
|
587
|
+
};
|
|
588
|
+
if (user?.id) metadata.userId = user.id;
|
|
589
|
+
if (auth?.organization?.id) metadata.orgId = auth.organization.id;
|
|
590
|
+
if (config.provider === "stripe") {
|
|
591
|
+
let customerId = null;
|
|
592
|
+
if (user?.id) {
|
|
593
|
+
const cust = await db.prepare(
|
|
594
|
+
"SELECT customer_id FROM _qarpeo_customers WHERE user_id = ? AND provider = 'stripe' LIMIT 1"
|
|
595
|
+
).bind(user.id).first();
|
|
596
|
+
if (cust) customerId = cust.customer_id;
|
|
597
|
+
}
|
|
598
|
+
const mode = options.mode || "subscription";
|
|
599
|
+
const params = new URLSearchParams();
|
|
600
|
+
params.set("mode", mode);
|
|
601
|
+
params.set("line_items[0][price]", priceId);
|
|
602
|
+
params.set("line_items[0][quantity]", "1");
|
|
603
|
+
params.set("success_url", successUrl);
|
|
604
|
+
params.set("cancel_url", cancelUrl);
|
|
605
|
+
if (customerId) {
|
|
606
|
+
params.set("customer", customerId);
|
|
607
|
+
} else {
|
|
608
|
+
const email = options.customerEmail || user?.email;
|
|
609
|
+
if (email) params.set("customer_email", email);
|
|
610
|
+
}
|
|
611
|
+
if (user?.id) params.set("client_reference_id", user.id);
|
|
612
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
613
|
+
params.set(`metadata[${k}]`, String(v));
|
|
614
|
+
if (mode === "subscription") {
|
|
615
|
+
params.set(`subscription_data[metadata][${k}]`, String(v));
|
|
616
|
+
} else if (mode === "payment") {
|
|
617
|
+
params.set(`payment_intent_data[metadata][${k}]`, String(v));
|
|
618
|
+
} else if (mode === "setup") {
|
|
619
|
+
params.set(`setup_intent_data[metadata][${k}]`, String(v));
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (mode === "subscription") {
|
|
623
|
+
const trialDays = options.trialDays || config.trialDays;
|
|
624
|
+
if (trialDays && trialDays > 0) {
|
|
625
|
+
params.set("subscription_data[trial_period_days]", String(trialDays));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
const res = await fetch("https://api.stripe.com/v1/checkout/sessions", {
|
|
629
|
+
method: "POST",
|
|
630
|
+
headers: {
|
|
631
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
632
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
633
|
+
"Stripe-Version": STRIPE_API_VERSION
|
|
634
|
+
},
|
|
635
|
+
body: params.toString()
|
|
636
|
+
});
|
|
637
|
+
if (!res.ok) {
|
|
638
|
+
const errText = await res.text();
|
|
639
|
+
console.error("[qarpeo:payments] Stripe Checkout error:", errText);
|
|
640
|
+
throw createError({ statusCode: res.status, message: `Stripe checkout failed: ${errText}` });
|
|
641
|
+
}
|
|
642
|
+
const session = await res.json();
|
|
643
|
+
return { id: session.id, url: session.url };
|
|
644
|
+
}
|
|
645
|
+
throw createError({
|
|
646
|
+
statusCode: 501,
|
|
647
|
+
message: `Checkout not implemented for provider ${config.provider}`
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
export async function createCustomerPortalSession(event, options) {
|
|
651
|
+
await ensurePaymentsTables(event);
|
|
652
|
+
const db = getPaymentsDb(event);
|
|
653
|
+
const config = getPaymentsConfig(event);
|
|
654
|
+
const auth = await getAuthContext(event);
|
|
655
|
+
const user = auth?.user;
|
|
656
|
+
if (!user?.id) {
|
|
657
|
+
throw createError({
|
|
658
|
+
statusCode: 401,
|
|
659
|
+
message: "Authentication required to access billing portal"
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
const cust = await db.prepare("SELECT customer_id FROM _qarpeo_customers WHERE user_id = ? AND provider = ? LIMIT 1").bind(user.id, config.provider).first();
|
|
663
|
+
if (!cust?.customer_id) {
|
|
664
|
+
throw createError({ statusCode: 404, message: "No customer account found for this user" });
|
|
665
|
+
}
|
|
666
|
+
const siteUrl = useRuntimeConfig().public?.siteUrl || "http://localhost:3000";
|
|
667
|
+
const returnUrl = options?.returnUrl || config.portalReturnUrl || siteUrl;
|
|
668
|
+
if (config.provider === "stripe") {
|
|
669
|
+
const params = new URLSearchParams();
|
|
670
|
+
params.set("customer", cust.customer_id);
|
|
671
|
+
params.set("return_url", returnUrl);
|
|
672
|
+
const res = await fetch("https://api.stripe.com/v1/billing_portal/sessions", {
|
|
673
|
+
method: "POST",
|
|
674
|
+
headers: {
|
|
675
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
676
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
677
|
+
"Stripe-Version": STRIPE_API_VERSION
|
|
678
|
+
},
|
|
679
|
+
body: params.toString()
|
|
680
|
+
});
|
|
681
|
+
if (!res.ok) {
|
|
682
|
+
const errText = await res.text();
|
|
683
|
+
throw createError({ statusCode: res.status, message: `Billing portal failed: ${errText}` });
|
|
684
|
+
}
|
|
685
|
+
const session = await res.json();
|
|
686
|
+
return { url: session.url };
|
|
687
|
+
}
|
|
688
|
+
throw createError({
|
|
689
|
+
statusCode: 501,
|
|
690
|
+
message: `Billing portal not supported for provider ${config.provider}`
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
export async function cancelSubscription(event, subscriptionId) {
|
|
694
|
+
await ensurePaymentsTables(event);
|
|
695
|
+
const db = getPaymentsDb(event);
|
|
696
|
+
const config = getPaymentsConfig(event);
|
|
697
|
+
if (!subscriptionId) {
|
|
698
|
+
throw createError({ statusCode: 400, message: "subscriptionId is required" });
|
|
699
|
+
}
|
|
700
|
+
if (!config.secretKey) {
|
|
701
|
+
throw createError({
|
|
702
|
+
statusCode: 500,
|
|
703
|
+
statusMessage: "Internal Server Error",
|
|
704
|
+
message: "Payment provider secret key is not configured (STRIPE_SECRET_KEY)"
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
if (config.provider !== "stripe") {
|
|
708
|
+
throw createError({
|
|
709
|
+
statusCode: 501,
|
|
710
|
+
message: `Subscription cancel not implemented for provider ${config.provider}`
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const params = new URLSearchParams();
|
|
714
|
+
params.set("cancel_at_period_end", "true");
|
|
715
|
+
const res = await fetch(`https://api.stripe.com/v1/subscriptions/${subscriptionId}`, {
|
|
716
|
+
method: "POST",
|
|
717
|
+
headers: {
|
|
718
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
719
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
720
|
+
"Stripe-Version": STRIPE_API_VERSION
|
|
721
|
+
},
|
|
722
|
+
body: params.toString()
|
|
723
|
+
});
|
|
724
|
+
if (!res.ok) {
|
|
725
|
+
const errText = await res.text();
|
|
726
|
+
throw createError({ statusCode: res.status, message: `Stripe cancel failed: ${errText}` });
|
|
727
|
+
}
|
|
728
|
+
const now = Date.now();
|
|
729
|
+
await db.prepare(
|
|
730
|
+
"UPDATE _qarpeo_subscriptions SET status = 'canceled', cancel_at_period_end = 1, updated_at = ? WHERE subscription_id = ?"
|
|
731
|
+
).bind(now, subscriptionId).run();
|
|
732
|
+
return { success: true, subscriptionId, status: "canceled" };
|
|
733
|
+
}
|
|
734
|
+
export async function pauseSubscription(event, subscriptionId) {
|
|
735
|
+
await ensurePaymentsTables(event);
|
|
736
|
+
const db = getPaymentsDb(event);
|
|
737
|
+
const config = getPaymentsConfig(event);
|
|
738
|
+
if (!subscriptionId) {
|
|
739
|
+
throw createError({ statusCode: 400, message: "subscriptionId is required" });
|
|
740
|
+
}
|
|
741
|
+
if (!config.secretKey) {
|
|
742
|
+
throw createError({
|
|
743
|
+
statusCode: 500,
|
|
744
|
+
statusMessage: "Internal Server Error",
|
|
745
|
+
message: "Payment provider secret key is not configured (STRIPE_SECRET_KEY)"
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
if (config.provider !== "stripe") {
|
|
749
|
+
throw createError({
|
|
750
|
+
statusCode: 501,
|
|
751
|
+
message: `Subscription pause not implemented for provider ${config.provider}`
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
const params = new URLSearchParams();
|
|
755
|
+
params.set("pause_collection[behavior]", "void");
|
|
756
|
+
params.set("pause_collection[resumes_at]", "");
|
|
757
|
+
const res = await fetch(`https://api.stripe.com/v1/subscriptions/${subscriptionId}`, {
|
|
758
|
+
method: "POST",
|
|
759
|
+
headers: {
|
|
760
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
761
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
762
|
+
"Stripe-Version": STRIPE_API_VERSION
|
|
763
|
+
},
|
|
764
|
+
body: params.toString()
|
|
765
|
+
});
|
|
766
|
+
if (!res.ok) {
|
|
767
|
+
const errText = await res.text();
|
|
768
|
+
throw createError({ statusCode: res.status, message: `Stripe pause failed: ${errText}` });
|
|
769
|
+
}
|
|
770
|
+
const now = Date.now();
|
|
771
|
+
await db.prepare(
|
|
772
|
+
"UPDATE _qarpeo_subscriptions SET status = 'paused', updated_at = ? WHERE subscription_id = ?"
|
|
773
|
+
).bind(now, subscriptionId).run();
|
|
774
|
+
return { success: true, subscriptionId, status: "paused" };
|
|
775
|
+
}
|
|
776
|
+
export async function changePlan(event, subscriptionId, newPriceId) {
|
|
777
|
+
await ensurePaymentsTables(event);
|
|
778
|
+
const db = getPaymentsDb(event);
|
|
779
|
+
const config = getPaymentsConfig(event);
|
|
780
|
+
if (!subscriptionId) {
|
|
781
|
+
throw createError({ statusCode: 400, message: "subscriptionId is required" });
|
|
782
|
+
}
|
|
783
|
+
if (!newPriceId) {
|
|
784
|
+
throw createError({ statusCode: 400, message: "newPriceId is required" });
|
|
785
|
+
}
|
|
786
|
+
if (!config.secretKey) {
|
|
787
|
+
throw createError({
|
|
788
|
+
statusCode: 500,
|
|
789
|
+
statusMessage: "Internal Server Error",
|
|
790
|
+
message: "Payment provider secret key is not configured (STRIPE_SECRET_KEY)"
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
if (config.provider !== "stripe") {
|
|
794
|
+
throw createError({
|
|
795
|
+
statusCode: 501,
|
|
796
|
+
message: `Plan change not implemented for provider ${config.provider}`
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
const getRes = await fetch(`https://api.stripe.com/v1/subscriptions/${subscriptionId}`, {
|
|
800
|
+
method: "GET",
|
|
801
|
+
headers: {
|
|
802
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
803
|
+
"Stripe-Version": STRIPE_API_VERSION
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
if (!getRes.ok) {
|
|
807
|
+
const errText = await getRes.text();
|
|
808
|
+
throw createError({
|
|
809
|
+
statusCode: getRes.status,
|
|
810
|
+
message: `Stripe fetch subscription failed: ${errText}`
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
const sub = await getRes.json();
|
|
814
|
+
const currentItemId = sub?.items?.data?.[0]?.id;
|
|
815
|
+
if (!currentItemId) {
|
|
816
|
+
throw createError({ statusCode: 409, message: "Subscription has no item to change" });
|
|
817
|
+
}
|
|
818
|
+
const params = new URLSearchParams();
|
|
819
|
+
params.set("items[0][id]", currentItemId);
|
|
820
|
+
params.set("items[0][price]", newPriceId);
|
|
821
|
+
params.set("proration_behavior", "create_prorations");
|
|
822
|
+
const res = await fetch(`https://api.stripe.com/v1/subscriptions/${subscriptionId}`, {
|
|
823
|
+
method: "POST",
|
|
824
|
+
headers: {
|
|
825
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
826
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
827
|
+
"Stripe-Version": STRIPE_API_VERSION
|
|
828
|
+
},
|
|
829
|
+
body: params.toString()
|
|
830
|
+
});
|
|
831
|
+
if (!res.ok) {
|
|
832
|
+
const errText = await res.text();
|
|
833
|
+
throw createError({ statusCode: res.status, message: `Stripe plan change failed: ${errText}` });
|
|
834
|
+
}
|
|
835
|
+
const updated = await res.json();
|
|
836
|
+
const newPlanId = updated?.items?.data?.[0]?.price?.id || newPriceId;
|
|
837
|
+
const now = Date.now();
|
|
838
|
+
await db.prepare(
|
|
839
|
+
"UPDATE _qarpeo_subscriptions SET plan_id = ?, price_id = ?, updated_at = ? WHERE subscription_id = ?"
|
|
840
|
+
).bind(newPlanId, newPriceId, now, subscriptionId).run();
|
|
841
|
+
return { success: true, subscriptionId, planId: newPlanId };
|
|
842
|
+
}
|
|
843
|
+
function rowToSubscription(row) {
|
|
844
|
+
let metadata = {};
|
|
845
|
+
try {
|
|
846
|
+
metadata = JSON.parse(row.metadata_json || "{}");
|
|
847
|
+
} catch {
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
id: row.id,
|
|
851
|
+
customerId: row.customer_id,
|
|
852
|
+
userId: row.user_id,
|
|
853
|
+
orgId: row.org_id || null,
|
|
854
|
+
provider: row.provider,
|
|
855
|
+
subscriptionId: row.subscription_id,
|
|
856
|
+
planId: row.plan_id,
|
|
857
|
+
priceId: row.price_id,
|
|
858
|
+
status: row.status,
|
|
859
|
+
quantity: row.quantity || 1,
|
|
860
|
+
cancelAtPeriodEnd: Boolean(row.cancel_at_period_end),
|
|
861
|
+
currentPeriodStart: row.current_period_start,
|
|
862
|
+
currentPeriodEnd: row.current_period_end,
|
|
863
|
+
trialEnd: row.trial_end || null,
|
|
864
|
+
canceledAt: row.canceled_at || null,
|
|
865
|
+
metadata,
|
|
866
|
+
metadataJson: row.metadata_json || "{}",
|
|
867
|
+
createdAt: row.created_at,
|
|
868
|
+
updatedAt: row.updated_at
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
export async function getSubscription(event, options) {
|
|
872
|
+
await ensurePaymentsTables(event);
|
|
873
|
+
const db = getPaymentsDb(event);
|
|
874
|
+
const auth = await getAuthContext(event);
|
|
875
|
+
const userId = options?.userId || auth?.user?.id;
|
|
876
|
+
const orgId = options?.orgId || auth?.organization?.id;
|
|
877
|
+
if (!userId && !orgId) return null;
|
|
878
|
+
let row = null;
|
|
879
|
+
if (orgId) {
|
|
880
|
+
row = await db.prepare(
|
|
881
|
+
"SELECT * FROM _qarpeo_subscriptions WHERE org_id = ? AND status IN ('active', 'trialing') ORDER BY created_at DESC LIMIT 1"
|
|
882
|
+
).bind(orgId).first();
|
|
883
|
+
}
|
|
884
|
+
if (!row && userId) {
|
|
885
|
+
row = await db.prepare(
|
|
886
|
+
"SELECT * FROM _qarpeo_subscriptions WHERE user_id = ? AND status IN ('active', 'trialing') ORDER BY created_at DESC LIMIT 1"
|
|
887
|
+
).bind(userId).first();
|
|
888
|
+
}
|
|
889
|
+
return row ? rowToSubscription(row) : null;
|
|
890
|
+
}
|
|
891
|
+
export async function requireSubscription(event, options) {
|
|
892
|
+
const sub = await getSubscription(event);
|
|
893
|
+
if (!sub) {
|
|
894
|
+
throw createError({
|
|
895
|
+
statusCode: 402,
|
|
896
|
+
statusMessage: "Payment Required",
|
|
897
|
+
message: options?.message || "Active subscription required to access this resource"
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
const allowedStatuses = options?.status || ["active", "trialing"];
|
|
901
|
+
if (!allowedStatuses.includes(sub.status)) {
|
|
902
|
+
throw createError({
|
|
903
|
+
statusCode: 402,
|
|
904
|
+
statusMessage: "Payment Required",
|
|
905
|
+
message: options?.message || `Subscription status is ${sub.status}. Active subscription required.`
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
const requiredPlans = options?.plan ? Array.isArray(options.plan) ? options.plan : [options.plan] : [];
|
|
909
|
+
const requiredTiers = options?.tier ? Array.isArray(options.tier) ? options.tier : [options.tier] : [];
|
|
910
|
+
const allRequired = [...requiredPlans, ...requiredTiers];
|
|
911
|
+
if (allRequired.length > 0 && !allRequired.includes(sub.planId)) {
|
|
912
|
+
throw createError({
|
|
913
|
+
statusCode: 403,
|
|
914
|
+
statusMessage: "Forbidden",
|
|
915
|
+
message: options?.message || `Subscription tier "${sub.planId}" does not satisfy required plan (${allRequired.join(", ")})`
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
return sub;
|
|
919
|
+
}
|
|
920
|
+
export async function hasFeature(event, feature) {
|
|
921
|
+
const sub = await getSubscription(event);
|
|
922
|
+
if (!sub || sub.status !== "active" && sub.status !== "trialing") return false;
|
|
923
|
+
const config = getPaymentsConfig(event);
|
|
924
|
+
const plan = config.plans?.[sub.planId];
|
|
925
|
+
if (!plan?.features) return false;
|
|
926
|
+
return plan.features.includes(feature);
|
|
927
|
+
}
|
|
928
|
+
export async function checkLimit(event, limitKey, currentUsage) {
|
|
929
|
+
const sub = await getSubscription(event);
|
|
930
|
+
const config = getPaymentsConfig(event);
|
|
931
|
+
const plan = sub ? config.plans?.[sub.planId] : void 0;
|
|
932
|
+
const limit = plan?.limits?.[limitKey] ?? Infinity;
|
|
933
|
+
return {
|
|
934
|
+
allowed: currentUsage < limit,
|
|
935
|
+
limit,
|
|
936
|
+
current: currentUsage
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
export async function reportUsage(event, options) {
|
|
940
|
+
await ensurePaymentsTables(event);
|
|
941
|
+
const db = getPaymentsDb(event);
|
|
942
|
+
const config = getPaymentsConfig(event);
|
|
943
|
+
const auth = await getAuthContext(event).catch(() => null);
|
|
944
|
+
const userId = options.userId || auth?.user?.id || null;
|
|
945
|
+
const orgId = options.orgId || auth?.organization?.id || null;
|
|
946
|
+
const now = Date.now();
|
|
947
|
+
const timestamp = options.timestamp || now;
|
|
948
|
+
const id = nanoid();
|
|
949
|
+
const quantity = options.quantity ?? 1;
|
|
950
|
+
const action = options.action || "increment";
|
|
951
|
+
const metadataJson = options.metadata ? JSON.stringify(options.metadata) : "{}";
|
|
952
|
+
await db.prepare(
|
|
953
|
+
`INSERT INTO _qarpeo_usage_records
|
|
954
|
+
(id, user_id, org_id, customer_id, subscription_id, event_name, quantity, action, metadata_json, timestamp, created_at)
|
|
955
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
956
|
+
).bind(
|
|
957
|
+
id,
|
|
958
|
+
userId,
|
|
959
|
+
orgId,
|
|
960
|
+
options.customerId || null,
|
|
961
|
+
options.subscriptionId || null,
|
|
962
|
+
options.eventName,
|
|
963
|
+
quantity,
|
|
964
|
+
action,
|
|
965
|
+
metadataJson,
|
|
966
|
+
timestamp,
|
|
967
|
+
now
|
|
968
|
+
).run();
|
|
969
|
+
if (config.provider === "stripe" && config.secretKey) {
|
|
970
|
+
let custId = options.customerId;
|
|
971
|
+
if (!custId && userId) {
|
|
972
|
+
const cust = await db.prepare(
|
|
973
|
+
"SELECT customer_id FROM _qarpeo_customers WHERE user_id = ? AND provider = 'stripe' LIMIT 1"
|
|
974
|
+
).bind(userId).first();
|
|
975
|
+
if (cust) custId = cust.customer_id;
|
|
976
|
+
}
|
|
977
|
+
if (custId) {
|
|
978
|
+
try {
|
|
979
|
+
const params = new URLSearchParams();
|
|
980
|
+
params.set("event_name", options.eventName);
|
|
981
|
+
params.set("payload[value]", String(quantity));
|
|
982
|
+
params.set("payload[stripe_customer_id]", custId);
|
|
983
|
+
params.set("timestamp", String(Math.floor(timestamp / 1e3)));
|
|
984
|
+
const res = await fetch("https://api.stripe.com/v1/billing/meter_events", {
|
|
985
|
+
method: "POST",
|
|
986
|
+
headers: {
|
|
987
|
+
Authorization: `Bearer ${config.secretKey}`,
|
|
988
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
989
|
+
"Stripe-Version": STRIPE_API_VERSION
|
|
990
|
+
},
|
|
991
|
+
body: params.toString()
|
|
992
|
+
});
|
|
993
|
+
if (!res.ok) {
|
|
994
|
+
const errText = await res.text();
|
|
995
|
+
console.warn("[qarpeo:payments] Stripe meter event reported non-ok:", errText);
|
|
996
|
+
}
|
|
997
|
+
} catch (err) {
|
|
998
|
+
console.warn("[qarpeo:payments] Failed to report meter event to Stripe:", err);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
success: true,
|
|
1004
|
+
id,
|
|
1005
|
+
eventName: options.eventName,
|
|
1006
|
+
quantity,
|
|
1007
|
+
timestamp
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
export async function getUsageSummary(event, options) {
|
|
1011
|
+
await ensurePaymentsTables(event);
|
|
1012
|
+
const db = getPaymentsDb(event);
|
|
1013
|
+
const auth = await getAuthContext(event).catch(() => null);
|
|
1014
|
+
const userId = options.userId || auth?.user?.id;
|
|
1015
|
+
const orgId = options.orgId || auth?.organization?.id;
|
|
1016
|
+
let sql = "SELECT * FROM _qarpeo_usage_records WHERE 1=1";
|
|
1017
|
+
const params = [];
|
|
1018
|
+
if (options.eventName) {
|
|
1019
|
+
sql += " AND event_name = ?";
|
|
1020
|
+
params.push(options.eventName);
|
|
1021
|
+
}
|
|
1022
|
+
if (orgId) {
|
|
1023
|
+
sql += " AND org_id = ?";
|
|
1024
|
+
params.push(orgId);
|
|
1025
|
+
} else if (userId) {
|
|
1026
|
+
sql += " AND user_id = ?";
|
|
1027
|
+
params.push(userId);
|
|
1028
|
+
}
|
|
1029
|
+
if (options.since) {
|
|
1030
|
+
sql += " AND timestamp >= ?";
|
|
1031
|
+
params.push(options.since);
|
|
1032
|
+
}
|
|
1033
|
+
if (options.until) {
|
|
1034
|
+
sql += " AND timestamp <= ?";
|
|
1035
|
+
params.push(options.until);
|
|
1036
|
+
}
|
|
1037
|
+
sql += " ORDER BY timestamp DESC LIMIT 500";
|
|
1038
|
+
const stmt = db.prepare(sql);
|
|
1039
|
+
const bound = params.length > 0 ? stmt.bind(...params) : stmt;
|
|
1040
|
+
const result = await bound.all();
|
|
1041
|
+
const rows = result.results || [];
|
|
1042
|
+
let total = 0;
|
|
1043
|
+
for (const row of rows) {
|
|
1044
|
+
if (row.action === "set") {
|
|
1045
|
+
total = row.quantity;
|
|
1046
|
+
} else {
|
|
1047
|
+
total += row.quantity;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return {
|
|
1051
|
+
eventName: options.eventName || "all",
|
|
1052
|
+
total,
|
|
1053
|
+
count: rows.length,
|
|
1054
|
+
records: rows.map((r) => ({
|
|
1055
|
+
id: r.id,
|
|
1056
|
+
userId: r.user_id,
|
|
1057
|
+
orgId: r.org_id,
|
|
1058
|
+
customerId: r.customer_id,
|
|
1059
|
+
subscriptionId: r.subscription_id,
|
|
1060
|
+
eventName: r.event_name,
|
|
1061
|
+
quantity: r.quantity,
|
|
1062
|
+
action: r.action,
|
|
1063
|
+
metadata: JSON.parse(r.metadata_json || "{}"),
|
|
1064
|
+
timestamp: r.timestamp
|
|
1065
|
+
}))
|
|
1066
|
+
};
|
|
1067
|
+
}
|