@omega.js/backend 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +98 -0
- package/README.md +1053 -0
- package/bin/omega +2 -0
- package/bin/omega-backend +2 -0
- package/bin/omg +2 -0
- package/cli.js +3 -0
- package/dist/cli/command-table.js +268 -0
- package/dist/cli/commands/auth.js +259 -0
- package/dist/cli/commands/base-command.js +451 -0
- package/dist/cli/commands/build.js +27 -0
- package/dist/cli/commands/clean.js +13 -0
- package/dist/cli/commands/clear.js +11 -0
- package/dist/cli/commands/cwd.js +9 -0
- package/dist/cli/commands/deploy.js +205 -0
- package/dist/cli/commands/emulator-orphans.js +440 -0
- package/dist/cli/commands/emulator.js +1829 -0
- package/dist/cli/commands/firebase-init.js +101 -0
- package/dist/cli/commands/firestore.js +261 -0
- package/dist/cli/commands/indexes.js +51 -0
- package/dist/cli/commands/install.js +151 -0
- package/dist/cli/commands/logs.js +358 -0
- package/dist/cli/commands/mcp.js +41 -0
- package/dist/cli/commands/migrate-markers.js +176 -0
- package/dist/cli/commands/migrate-rules.js +90 -0
- package/dist/cli/commands/migrate.js +59 -0
- package/dist/cli/commands/serve.js +208 -0
- package/dist/cli/commands/setup-tests/base-test.js +174 -0
- package/dist/cli/commands/setup-tests/emulator-config.js +109 -0
- package/dist/cli/commands/setup-tests/env-runtime-config-deprecated.js +47 -0
- package/dist/cli/commands/setup-tests/firebase-admin.js +49 -0
- package/dist/cli/commands/setup-tests/firebase-auth.js +26 -0
- package/dist/cli/commands/setup-tests/firebase-cli.js +26 -0
- package/dist/cli/commands/setup-tests/firebase-functions.js +49 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-file.js +72 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-in-json.js +20 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-required.js +108 -0
- package/dist/cli/commands/setup-tests/firestore-indexes-synced.js +236 -0
- package/dist/cli/commands/setup-tests/firestore-rules-file.js +115 -0
- package/dist/cli/commands/setup-tests/firestore-rules-in-json.js +49 -0
- package/dist/cli/commands/setup-tests/functions-package.js +44 -0
- package/dist/cli/commands/setup-tests/gcloud-cli.js +26 -0
- package/dist/cli/commands/setup-tests/gitignore.js +122 -0
- package/dist/cli/commands/setup-tests/helpers/merge-line-files.js +28 -0
- package/dist/cli/commands/setup-tests/helpers/required-indexes.js +109 -0
- package/dist/cli/commands/setup-tests/helpers/seed-campaigns.js +246 -0
- package/dist/cli/commands/setup-tests/helpers.js +72 -0
- package/dist/cli/commands/setup-tests/hosting-folder.js +23 -0
- package/dist/cli/commands/setup-tests/hosting-rewrites.js +52 -0
- package/dist/cli/commands/setup-tests/index.js +111 -0
- package/dist/cli/commands/setup-tests/is-firebase-project.js +21 -0
- package/dist/cli/commands/setup-tests/java-installed.js +27 -0
- package/dist/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
- package/dist/cli/commands/setup-tests/marketing-campaigns-seeded.js +230 -0
- package/dist/cli/commands/setup-tests/node-version.js +60 -0
- package/dist/cli/commands/setup-tests/npm-project-scripts.js +42 -0
- package/dist/cli/commands/setup-tests/nvmrc-version.js +36 -0
- package/dist/cli/commands/setup-tests/omega-backend.js +42 -0
- package/dist/cli/commands/setup-tests/omega-config.js +84 -0
- package/dist/cli/commands/setup-tests/project-directories.js +34 -0
- package/dist/cli/commands/setup-tests/project-id-consistency.js +201 -0
- package/dist/cli/commands/setup-tests/public-html-files.js +22 -0
- package/dist/cli/commands/setup-tests/realtime-rules-file.js +69 -0
- package/dist/cli/commands/setup-tests/realtime-rules-in-json.js +20 -0
- package/dist/cli/commands/setup-tests/remoteconfig-template-file.js +32 -0
- package/dist/cli/commands/setup-tests/remoteconfig-template-in-json.js +31 -0
- package/dist/cli/commands/setup-tests/service-account.js +86 -0
- package/dist/cli/commands/setup-tests/storage-lifecycle-policy.js +81 -0
- package/dist/cli/commands/setup-tests/storage-rules-file.js +32 -0
- package/dist/cli/commands/setup-tests/storage-rules-in-json.js +20 -0
- package/dist/cli/commands/stripe.js +14 -0
- package/dist/cli/commands/test-lanes/stripe-live.js +440 -0
- package/dist/cli/commands/test.js +976 -0
- package/dist/cli/commands/update.js +27 -0
- package/dist/cli/commands/version.js +10 -0
- package/dist/cli/commands/watch.js +196 -0
- package/dist/cli/flags.js +13 -0
- package/dist/cli/index.js +173 -0
- package/dist/cli/run.js +33 -0
- package/dist/cli/utils/attach-log-file.js +4 -0
- package/dist/cli/utils/compile-rules.js +1155 -0
- package/dist/cli/utils/ensure-target.js +314 -0
- package/dist/cli/utils/project-type.js +140 -0
- package/dist/cli/utils/public-files.js +49 -0
- package/dist/cli/utils/safe-install.js +4 -0
- package/dist/cli/utils/spawn-shell.js +94 -0
- package/dist/cli/utils/stage-functions.js +338 -0
- package/dist/cli/utils/stage-local-packages.js +210 -0
- package/dist/cli/utils/target-checks.js +161 -0
- package/dist/cli/utils/target.js +24 -0
- package/dist/cli/utils/ui.js +292 -0
- package/dist/defaults/AGENTS.md +125 -0
- package/dist/defaults/CHANGELOG.md +15 -0
- package/dist/defaults/CLAUDE.md +1 -0
- package/dist/defaults/_.gitignore +65 -0
- package/dist/defaults/docs/README.md +17 -0
- package/dist/defaults/test/README.md +65 -0
- package/dist/defaults/test/_init.js +14 -0
- package/dist/defaults/test/helpers/connect-trap.js +72 -0
- package/dist/defaults/test/unit/registration.test.js +143 -0
- package/dist/defaults/test/unit/rules-posture.test.js +101 -0
- package/dist/defaults/test/unit/socket-free.test.js +43 -0
- package/dist/manager/events/auth/before-create.js +89 -0
- package/dist/manager/events/auth/before-signin.js +76 -0
- package/dist/manager/events/auth/on-create.js +112 -0
- package/dist/manager/events/auth/on-delete.js +141 -0
- package/dist/manager/events/auth/utils.js +93 -0
- package/dist/manager/events/cron/daily/blog-auto-publisher.js +287 -0
- package/dist/manager/events/cron/daily/data-requests.js +57 -0
- package/dist/manager/events/cron/daily/expire-paypal-cancellations.js +191 -0
- package/dist/manager/events/cron/daily/marketing-prune.js +486 -0
- package/dist/manager/events/cron/daily/reset-usage.js +188 -0
- package/dist/manager/events/cron/daily/trial-lapse-sweep.js +355 -0
- package/dist/manager/events/cron/daily.js +3 -0
- package/dist/manager/events/cron/frequent/abandoned-carts.js +204 -0
- package/dist/manager/events/cron/frequent/email-queue.js +71 -0
- package/dist/manager/events/cron/frequent/marketing-campaigns.js +463 -0
- package/dist/manager/events/cron/frequent/retry-failed-webhooks.js +70 -0
- package/dist/manager/events/cron/frequent.js +3 -0
- package/dist/manager/events/cron/runner.js +64 -0
- package/dist/manager/events/firestore/notifications/on-write.js +123 -0
- package/dist/manager/events/firestore/payments-disputes/on-write.js +256 -0
- package/dist/manager/events/firestore/payments-disputes/providers/stripe.js +218 -0
- package/dist/manager/events/firestore/payments-disputes/providers/test.js +231 -0
- package/dist/manager/events/firestore/payments-webhooks/analytics.js +687 -0
- package/dist/manager/events/firestore/payments-webhooks/on-write.js +1224 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/index.js +395 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +58 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +28 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-refunded.js +30 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/send-email.js +74 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-removed.js +14 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +28 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/checkout-declined.js +18 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +67 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +25 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +26 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-refunded.js +34 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +42 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +33 -0
- package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-winback.js +17 -0
- package/dist/manager/functions/_legacy/actions/create-post-handler.js +188 -0
- package/dist/manager/functions/_legacy/actions/generate-uuid.js +63 -0
- package/dist/manager/functions/_legacy/actions/sign-up-handler.js +205 -0
- package/dist/manager/functions/_legacy/admin/create-post.js +207 -0
- package/dist/manager/functions/_legacy/admin/firestore-write.js +72 -0
- package/dist/manager/functions/_legacy/admin/get-stats.js +218 -0
- package/dist/manager/functions/_legacy/admin/query.js +198 -0
- package/dist/manager/functions/_legacy/admin/send-notification.js +206 -0
- package/dist/manager/functions/_legacy/template.js +33 -0
- package/dist/manager/functions/_legacy/test/authenticate.js +40 -0
- package/dist/manager/functions/_legacy/test/webhook.js +37 -0
- package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
- package/dist/manager/helpers/analytics.js +498 -0
- package/dist/manager/helpers/api-manager.js +313 -0
- package/dist/manager/helpers/backend-router.js +37 -0
- package/dist/manager/helpers/context/authenticate.js +215 -0
- package/dist/manager/helpers/context/client-info.js +91 -0
- package/dist/manager/helpers/context/index.js +228 -0
- package/dist/manager/helpers/context/logging.js +150 -0
- package/dist/manager/helpers/context/parse.js +160 -0
- package/dist/manager/helpers/context/respond.js +247 -0
- package/dist/manager/helpers/event-middleware.js +109 -0
- package/dist/manager/helpers/metadata.js +32 -0
- package/dist/manager/helpers/middleware.js +541 -0
- package/dist/manager/helpers/redact-secret.js +25 -0
- package/dist/manager/helpers/resolved-config.js +34 -0
- package/dist/manager/helpers/roles.js +69 -0
- package/dist/manager/helpers/safe-compare.js +31 -0
- package/dist/manager/helpers/schema-engine.js +314 -0
- package/dist/manager/helpers/schema-zod.js +203 -0
- package/dist/manager/helpers/settings.js +215 -0
- package/dist/manager/helpers/usage.js +541 -0
- package/dist/manager/helpers/user.js +57 -0
- package/dist/manager/helpers/utilities.js +565 -0
- package/dist/manager/index.js +1404 -0
- package/dist/manager/libraries/abandoned-cart-config.js +12 -0
- package/dist/manager/libraries/ai/index.js +321 -0
- package/dist/manager/libraries/ai/prompt.js +85 -0
- package/dist/manager/libraries/ai/providers/anthropic-format.js +297 -0
- package/dist/manager/libraries/ai/providers/anthropic.js +169 -0
- package/dist/manager/libraries/ai/providers/claude-code.js +183 -0
- package/dist/manager/libraries/ai/providers/openai.js +1272 -0
- package/dist/manager/libraries/ai/providers/test.js +257 -0
- package/dist/manager/libraries/ai/tokens.js +54 -0
- package/dist/manager/libraries/analytics/conversions.js +478 -0
- package/dist/manager/libraries/analytics/match-data.js +573 -0
- package/dist/manager/libraries/analytics/signup.js +120 -0
- package/dist/manager/libraries/auth-user.js +46 -0
- package/dist/manager/libraries/content/feed-parser.js +198 -0
- package/dist/manager/libraries/content/ghostii.js +191 -0
- package/dist/manager/libraries/content/source-resolver.js +629 -0
- package/dist/manager/libraries/email/constants.js +532 -0
- package/dist/manager/libraries/email/data/blocked-local-parts.json +55 -0
- package/dist/manager/libraries/email/data/blocked-local-patterns.js +14 -0
- package/dist/manager/libraries/email/data/corporate-domains.json +23 -0
- package/dist/manager/libraries/email/data/custom-disposable-domains.json +58 -0
- package/dist/manager/libraries/email/data/disposable-domains.json +8184 -0
- package/dist/manager/libraries/email/data/typo-domains.js +83 -0
- package/dist/manager/libraries/email/disposable-domains.js +92 -0
- package/dist/manager/libraries/email/generators/lib/filter.js +179 -0
- package/dist/manager/libraries/email/generators/lib/image-host.js +344 -0
- package/dist/manager/libraries/email/generators/lib/image-illustrator.js +154 -0
- package/dist/manager/libraries/email/generators/lib/markdown-renderer.js +294 -0
- package/dist/manager/libraries/email/generators/lib/mjml-template.js +120 -0
- package/dist/manager/libraries/email/generators/lib/structure.js +299 -0
- package/dist/manager/libraries/email/generators/lib/svg-illustrator.js +197 -0
- package/dist/manager/libraries/email/generators/lib/templates/base.js +229 -0
- package/dist/manager/libraries/email/generators/lib/templates/card.js +33 -0
- package/dist/manager/libraries/email/generators/lib/templates/classic-schema.js +59 -0
- package/dist/manager/libraries/email/generators/lib/templates/clean.js +82 -0
- package/dist/manager/libraries/email/generators/lib/templates/editorial/helpers.js +100 -0
- package/dist/manager/libraries/email/generators/lib/templates/editorial/index.js +318 -0
- package/dist/manager/libraries/email/generators/lib/templates/feedback.js +101 -0
- package/dist/manager/libraries/email/generators/lib/templates/field-report/helpers.js +138 -0
- package/dist/manager/libraries/email/generators/lib/templates/field-report/index.js +494 -0
- package/dist/manager/libraries/email/generators/lib/templates/index.js +47 -0
- package/dist/manager/libraries/email/generators/lib/templates/newsletter-shared.js +610 -0
- package/dist/manager/libraries/email/generators/lib/templates/order.js +461 -0
- package/dist/manager/libraries/email/generators/lib/templates/plain.js +83 -0
- package/dist/manager/libraries/email/generators/lib/templates/shared-campaign.js +64 -0
- package/dist/manager/libraries/email/generators/newsletter.js +899 -0
- package/dist/manager/libraries/email/index.js +146 -0
- package/dist/manager/libraries/email/marketing/index.js +754 -0
- package/dist/manager/libraries/email/prepare.js +365 -0
- package/dist/manager/libraries/email/providers/beehiiv.js +589 -0
- package/dist/manager/libraries/email/providers/sendgrid.js +845 -0
- package/dist/manager/libraries/email/transactional/index.js +510 -0
- package/dist/manager/libraries/email/utm.js +86 -0
- package/dist/manager/libraries/email/validation-provider-neverbounce.js +70 -0
- package/dist/manager/libraries/email/validation-provider-zerobounce.js +42 -0
- package/dist/manager/libraries/email/validation.js +255 -0
- package/dist/manager/libraries/env.js +198 -0
- package/dist/manager/libraries/infer-contact.js +101 -0
- package/dist/manager/libraries/load-provider.js +65 -0
- package/dist/manager/libraries/notification.js +256 -0
- package/dist/manager/libraries/openai.js +13 -0
- package/dist/manager/libraries/payment/discount-codes.js +204 -0
- package/dist/manager/libraries/payment/fetch-failure.js +56 -0
- package/dist/manager/libraries/payment/license.js +44 -0
- package/dist/manager/libraries/payment/order-id.js +57 -0
- package/dist/manager/libraries/payment/provider-errors.js +71 -0
- package/dist/manager/libraries/payment/providers/chargebee.js +817 -0
- package/dist/manager/libraries/payment/providers/coinbase.js +330 -0
- package/dist/manager/libraries/payment/providers/paypal.js +987 -0
- package/dist/manager/libraries/payment/providers/stripe.js +755 -0
- package/dist/manager/libraries/payment/providers/test.js +250 -0
- package/dist/manager/libraries/payment/refund-linkage.js +78 -0
- package/dist/manager/libraries/payment/refund-policy.js +113 -0
- package/dist/manager/libraries/payment/winback.js +55 -0
- package/dist/manager/libraries/prompts/infer-contact.md +78 -0
- package/dist/manager/libraries/rate-limits.js +17 -0
- package/dist/manager/libraries/recaptcha.js +58 -0
- package/dist/manager/libraries/user-doc.js +234 -0
- package/dist/manager/routes/admin/backup/post.js +106 -0
- package/dist/manager/routes/admin/cron/post.js +37 -0
- package/dist/manager/routes/admin/database/get.js +35 -0
- package/dist/manager/routes/admin/database/post.js +34 -0
- package/dist/manager/routes/admin/email/post.js +74 -0
- package/dist/manager/routes/admin/firestore/get.js +35 -0
- package/dist/manager/routes/admin/firestore/post.js +56 -0
- package/dist/manager/routes/admin/firestore/query/post.js +128 -0
- package/dist/manager/routes/admin/hook/post.js +104 -0
- package/dist/manager/routes/admin/infer-contact/post.js +35 -0
- package/dist/manager/routes/admin/notification/post.js +37 -0
- package/dist/manager/routes/admin/payment/post.js +56 -0
- package/dist/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
- package/dist/manager/routes/admin/post/dispatch-deploy.js +36 -0
- package/dist/manager/routes/admin/post/post.js +487 -0
- package/dist/manager/routes/admin/post/put.js +146 -0
- package/dist/manager/routes/admin/post/templates/post.html +14 -0
- package/dist/manager/routes/admin/repo/content/post.js +106 -0
- package/dist/manager/routes/admin/stats/get.js +222 -0
- package/dist/manager/routes/admin/users/disable/post.js +50 -0
- package/dist/manager/routes/admin/users/list/get.js +110 -0
- package/dist/manager/routes/admin/users/sync/post.js +119 -0
- package/dist/manager/routes/brand/get.js +26 -0
- package/dist/manager/routes/content/post/get.js +110 -0
- package/dist/manager/routes/general/email/post.js +89 -0
- package/dist/manager/routes/general/email/templates/general/download-app-link.js +42 -0
- package/dist/manager/routes/general/uuid/post.js +31 -0
- package/dist/manager/routes/handler/post/post.js +142 -0
- package/dist/manager/routes/health/get.js +43 -0
- package/dist/manager/routes/index.js +11 -0
- package/dist/manager/routes/marketing/campaign/delete.js +45 -0
- package/dist/manager/routes/marketing/campaign/get.js +69 -0
- package/dist/manager/routes/marketing/campaign/post.js +124 -0
- package/dist/manager/routes/marketing/campaign/put.js +86 -0
- package/dist/manager/routes/marketing/campaign/utils.js +59 -0
- package/dist/manager/routes/marketing/contact/delete.js +100 -0
- package/dist/manager/routes/marketing/contact/post.js +149 -0
- package/dist/manager/routes/marketing/contact/put.js +37 -0
- package/dist/manager/routes/marketing/email-preferences/post.js +275 -0
- package/dist/manager/routes/marketing/webhook/forward/post.js +170 -0
- package/dist/manager/routes/marketing/webhook/post.js +123 -0
- package/dist/manager/routes/marketing/webhook/providers/beehiiv.js +197 -0
- package/dist/manager/routes/marketing/webhook/providers/sendgrid.js +194 -0
- package/dist/manager/routes/payments/cancel/_is-trialing.js +40 -0
- package/dist/manager/routes/payments/cancel/post.js +149 -0
- package/dist/manager/routes/payments/cancel/providers/chargebee.js +41 -0
- package/dist/manager/routes/payments/cancel/providers/paypal.js +56 -0
- package/dist/manager/routes/payments/cancel/providers/stripe.js +33 -0
- package/dist/manager/routes/payments/cancel/providers/test.js +118 -0
- package/dist/manager/routes/payments/discount/get.js +25 -0
- package/dist/manager/routes/payments/dispute-alert/post.js +117 -0
- package/dist/manager/routes/payments/dispute-alert/providers/chargeblast.js +55 -0
- package/dist/manager/routes/payments/intent/post.js +341 -0
- package/dist/manager/routes/payments/intent/providers/chargebee.js +197 -0
- package/dist/manager/routes/payments/intent/providers/coinbase.js +94 -0
- package/dist/manager/routes/payments/intent/providers/paypal.js +244 -0
- package/dist/manager/routes/payments/intent/providers/stripe.js +141 -0
- package/dist/manager/routes/payments/intent/providers/test.js +341 -0
- package/dist/manager/routes/payments/plan/post.js +147 -0
- package/dist/manager/routes/payments/plan/providers/chargebee.js +62 -0
- package/dist/manager/routes/payments/plan/providers/paypal.js +62 -0
- package/dist/manager/routes/payments/plan/providers/stripe.js +59 -0
- package/dist/manager/routes/payments/plan/providers/test.js +113 -0
- package/dist/manager/routes/payments/portal/post.js +113 -0
- package/dist/manager/routes/payments/portal/providers/chargebee.js +62 -0
- package/dist/manager/routes/payments/portal/providers/paypal.js +25 -0
- package/dist/manager/routes/payments/portal/providers/stripe.js +62 -0
- package/dist/manager/routes/payments/portal/providers/test.js +18 -0
- package/dist/manager/routes/payments/refund/post.js +207 -0
- package/dist/manager/routes/payments/refund/providers/chargebee.js +147 -0
- package/dist/manager/routes/payments/refund/providers/coinbase.js +40 -0
- package/dist/manager/routes/payments/refund/providers/paypal.js +239 -0
- package/dist/manager/routes/payments/refund/providers/stripe.js +160 -0
- package/dist/manager/routes/payments/refund/providers/test.js +204 -0
- package/dist/manager/routes/payments/trial-eligibility/get.js +29 -0
- package/dist/manager/routes/payments/uncancel/post.js +106 -0
- package/dist/manager/routes/payments/uncancel/providers/chargebee.js +30 -0
- package/dist/manager/routes/payments/uncancel/providers/paypal.js +25 -0
- package/dist/manager/routes/payments/uncancel/providers/stripe.js +27 -0
- package/dist/manager/routes/payments/uncancel/providers/test.js +114 -0
- package/dist/manager/routes/payments/webhook/post.js +157 -0
- package/dist/manager/routes/payments/webhook/providers/chargebee.js +218 -0
- package/dist/manager/routes/payments/webhook/providers/coinbase.js +91 -0
- package/dist/manager/routes/payments/webhook/providers/paypal.js +214 -0
- package/dist/manager/routes/payments/webhook/providers/stripe.js +182 -0
- package/dist/manager/routes/payments/webhook/providers/test.js +18 -0
- package/dist/manager/routes/payments/winback/post.js +276 -0
- package/dist/manager/routes/payments/winback/providers/chargebee.js +24 -0
- package/dist/manager/routes/payments/winback/providers/paypal.js +22 -0
- package/dist/manager/routes/payments/winback/providers/stripe.js +41 -0
- package/dist/manager/routes/payments/winback/providers/test.js +42 -0
- package/dist/manager/routes/restart/index.js +37 -0
- package/dist/manager/routes/special/electron-client/post.js +59 -0
- package/dist/manager/routes/test/authenticate/get.js +11 -0
- package/dist/manager/routes/test/health/get.js +8 -0
- package/dist/manager/routes/test/index.js +16 -0
- package/dist/manager/routes/test/lab/post.js +17 -0
- package/dist/manager/routes/test/redirect/get.js +27 -0
- package/dist/manager/routes/test/reset-account/post.js +79 -0
- package/dist/manager/routes/test/roster/get.js +40 -0
- package/dist/manager/routes/test/schema/post.js +20 -0
- package/dist/manager/routes/test/usage/post.js +44 -0
- package/dist/manager/routes/test/webhook/post.js +21 -0
- package/dist/manager/routes/user/api-keys/post.js +51 -0
- package/dist/manager/routes/user/connections/_context.js +194 -0
- package/dist/manager/routes/user/connections/_grant.js +247 -0
- package/dist/manager/routes/user/connections/_lease.js +184 -0
- package/dist/manager/routes/user/connections/_providers.js +191 -0
- package/dist/manager/routes/user/connections/_state.js +117 -0
- package/dist/manager/routes/user/connections/delete.js +55 -0
- package/dist/manager/routes/user/connections/get.js +167 -0
- package/dist/manager/routes/user/connections/post.js +370 -0
- package/dist/manager/routes/user/connections/providers/discord.js +41 -0
- package/dist/manager/routes/user/connections/providers/google.js +55 -0
- package/dist/manager/routes/user/connections/providers/kick.js +68 -0
- package/dist/manager/routes/user/connections/providers/spotify.js +40 -0
- package/dist/manager/routes/user/connections/providers/twitch.js +52 -0
- package/dist/manager/routes/user/data-request/delete.js +84 -0
- package/dist/manager/routes/user/data-request/get.js +229 -0
- package/dist/manager/routes/user/data-request/post.js +136 -0
- package/dist/manager/routes/user/delete.js +149 -0
- package/dist/manager/routes/user/feedback/post.js +81 -0
- package/dist/manager/routes/user/get.js +14 -0
- package/dist/manager/routes/user/orders/get.js +95 -0
- package/dist/manager/routes/user/sessions/delete.js +95 -0
- package/dist/manager/routes/user/sessions/get.js +40 -0
- package/dist/manager/routes/user/settings/validate/post.js +86 -0
- package/dist/manager/routes/user/signup/post.js +662 -0
- package/dist/manager/routes/user/subscription/get.js +77 -0
- package/dist/manager/routes/user/token/post.js +30 -0
- package/dist/manager/routes/verts/delete.js +43 -0
- package/dist/manager/routes/verts/get.js +49 -0
- package/dist/manager/routes/verts/post.js +61 -0
- package/dist/manager/routes/verts/put.js +75 -0
- package/dist/manager/routes/verts/redirect/get.js +36 -0
- package/dist/manager/routes/verts/serve/get.js +67 -0
- package/dist/manager/routes/verts/utils.js +436 -0
- package/dist/manager/schemas/admin/backup/post.js +8 -0
- package/dist/manager/schemas/admin/cron/post.js +8 -0
- package/dist/manager/schemas/admin/database/get.js +5 -0
- package/dist/manager/schemas/admin/database/post.js +6 -0
- package/dist/manager/schemas/admin/email/post.js +31 -0
- package/dist/manager/schemas/admin/firestore/get.js +5 -0
- package/dist/manager/schemas/admin/firestore/post.js +8 -0
- package/dist/manager/schemas/admin/firestore/query/post.js +5 -0
- package/dist/manager/schemas/admin/hook/post.js +8 -0
- package/dist/manager/schemas/admin/infer-contact/post.js +6 -0
- package/dist/manager/schemas/admin/notification/post.js +23 -0
- package/dist/manager/schemas/admin/payment/post.js +8 -0
- package/dist/manager/schemas/admin/post/post.js +23 -0
- package/dist/manager/schemas/admin/post/put.js +13 -0
- package/dist/manager/schemas/admin/repo/content/post.js +10 -0
- package/dist/manager/schemas/admin/stats/get.js +5 -0
- package/dist/manager/schemas/admin/users/disable/post.js +9 -0
- package/dist/manager/schemas/admin/users/list/get.js +10 -0
- package/dist/manager/schemas/admin/users/sync/post.js +7 -0
- package/dist/manager/schemas/brand/get.js +3 -0
- package/dist/manager/schemas/content/post/get.js +8 -0
- package/dist/manager/schemas/general/email/post.js +10 -0
- package/dist/manager/schemas/general/uuid/post.js +9 -0
- package/dist/manager/schemas/handler/post/post.js +13 -0
- package/dist/manager/schemas/health/get.js +3 -0
- package/dist/manager/schemas/marketing/campaign/delete.js +8 -0
- package/dist/manager/schemas/marketing/campaign/get.js +13 -0
- package/dist/manager/schemas/marketing/campaign/post.js +43 -0
- package/dist/manager/schemas/marketing/campaign/put.js +37 -0
- package/dist/manager/schemas/marketing/contact/delete.js +8 -0
- package/dist/manager/schemas/marketing/contact/post.js +14 -0
- package/dist/manager/schemas/marketing/contact/put.js +8 -0
- package/dist/manager/schemas/marketing/email-preferences/post.js +15 -0
- package/dist/manager/schemas/marketing/webhook/forward/post.js +8 -0
- package/dist/manager/schemas/marketing/webhook/post.js +7 -0
- package/dist/manager/schemas/payments/cancel/post.js +15 -0
- package/dist/manager/schemas/payments/discount/get.js +8 -0
- package/dist/manager/schemas/payments/dispute-alert/post.js +6 -0
- package/dist/manager/schemas/payments/intent/post.js +28 -0
- package/dist/manager/schemas/payments/plan/post.js +11 -0
- package/dist/manager/schemas/payments/portal/post.js +12 -0
- package/dist/manager/schemas/payments/refund/post.js +14 -0
- package/dist/manager/schemas/payments/trial-eligibility/get.js +7 -0
- package/dist/manager/schemas/payments/uncancel/post.js +9 -0
- package/dist/manager/schemas/payments/webhook/post.js +6 -0
- package/dist/manager/schemas/payments/winback/post.js +12 -0
- package/dist/manager/schemas/restart/index.js +5 -0
- package/dist/manager/schemas/special/electron-client/post.js +11 -0
- package/dist/manager/schemas/test/authenticate/get.js +3 -0
- package/dist/manager/schemas/test/health/get.js +3 -0
- package/dist/manager/schemas/test/index.js +5 -0
- package/dist/manager/schemas/test/lab/post.js +3 -0
- package/dist/manager/schemas/test/redirect/get.js +7 -0
- package/dist/manager/schemas/test/reset-account/post.js +3 -0
- package/dist/manager/schemas/test/roster/get.js +3 -0
- package/dist/manager/schemas/test/schema/post.js +70 -0
- package/dist/manager/schemas/test/usage/post.js +8 -0
- package/dist/manager/schemas/test/webhook/post.js +7 -0
- package/dist/manager/schemas/user/api-keys/post.js +6 -0
- package/dist/manager/schemas/user/connections/delete.js +8 -0
- package/dist/manager/schemas/user/connections/get.js +21 -0
- package/dist/manager/schemas/user/connections/post.js +12 -0
- package/dist/manager/schemas/user/data-request/delete.js +3 -0
- package/dist/manager/schemas/user/data-request/get.js +5 -0
- package/dist/manager/schemas/user/data-request/post.js +6 -0
- package/dist/manager/schemas/user/delete.js +6 -0
- package/dist/manager/schemas/user/feedback/post.js +8 -0
- package/dist/manager/schemas/user/get.js +3 -0
- package/dist/manager/schemas/user/orders/get.js +6 -0
- package/dist/manager/schemas/user/sessions/delete.js +6 -0
- package/dist/manager/schemas/user/sessions/get.js +6 -0
- package/dist/manager/schemas/user/settings/validate/post.js +8 -0
- package/dist/manager/schemas/user/signup/post.js +27 -0
- package/dist/manager/schemas/user/subscription/get.js +5 -0
- package/dist/manager/schemas/user/token/post.js +5 -0
- package/dist/manager/schemas/verts/delete.js +8 -0
- package/dist/manager/schemas/verts/get.js +9 -0
- package/dist/manager/schemas/verts/post.js +23 -0
- package/dist/manager/schemas/verts/put.js +20 -0
- package/dist/manager/schemas/verts/redirect/get.js +10 -0
- package/dist/manager/schemas/verts/serve/get.js +14 -0
- package/dist/manager/server-manager.js +69 -0
- package/dist/mcp/client.js +98 -0
- package/dist/mcp/handler.js +521 -0
- package/dist/mcp/index.js +157 -0
- package/dist/mcp/tools.js +555 -0
- package/dist/mcp/utils.js +108 -0
- package/dist/omega-bin.js +6 -0
- package/dist/require.js +3 -0
- package/dist/test/fixtures/firebase-project/.firebaserc +5 -0
- package/dist/test/fixtures/firebase-project/config/omega.json5 +131 -0
- package/dist/test/fixtures/firebase-project/database.rules.json +6 -0
- package/dist/test/fixtures/firebase-project/firebase.json +49 -0
- package/dist/test/fixtures/firebase-project/firestore.indexes.json +4 -0
- package/dist/test/fixtures/firebase-project/package.json +14 -0
- package/dist/test/fixtures/firebase-project/src/index.js +11 -0
- package/dist/test/fixtures/firebase-project/storage.rules +8 -0
- package/dist/test/parse-audit.js +25 -0
- package/dist/test/run-tests.js +90 -0
- package/dist/test/runner.js +982 -0
- package/dist/test/seed.js +242 -0
- package/dist/test/test-accounts.js +2660 -0
- package/dist/test/utils/assertions.js +189 -0
- package/dist/test/utils/email-capture.js +252 -0
- package/dist/test/utils/extended-mode-warning.js +11 -0
- package/dist/test/utils/firestore-rules-client.js +277 -0
- package/dist/test/utils/http-client.js +228 -0
- package/dist/test/utils/test-mode-file.js +192 -0
- package/dist/utils/merge-line-files.js +6 -0
- package/dist/utils/scaffold-defaults.js +71 -0
- package/dist/utils/test-lanes.js +62 -0
- package/dist/vendor/account/engine.js +182 -0
- package/dist/vendor/account/features.js +220 -0
- package/dist/vendor/account/index.js +53 -0
- package/dist/vendor/account/schema.js +272 -0
- package/dist/vendor/account/subscription.js +38 -0
- package/dist/vendor/analytics/adapters/ga4.js +26 -0
- package/dist/vendor/analytics/adapters/meta.js +26 -0
- package/dist/vendor/analytics/adapters/resolve.js +130 -0
- package/dist/vendor/analytics/adapters/tiktok.js +27 -0
- package/dist/vendor/analytics/catalog.js +908 -0
- package/dist/vendor/analytics/identity.js +136 -0
- package/dist/vendor/config/company.js +31 -0
- package/dist/vendor/config/defaults.js +173 -0
- package/dist/vendor/config/demo.js +18 -0
- package/dist/vendor/config/desktop-artifacts.js +110 -0
- package/dist/vendor/config/edit.js +769 -0
- package/dist/vendor/config/env-delivery.js +145 -0
- package/dist/vendor/config/env-rules.js +93 -0
- package/dist/vendor/config/env-schema.js +1078 -0
- package/dist/vendor/config/env.js +445 -0
- package/dist/vendor/config/hooks.js +97 -0
- package/dist/vendor/config/index.js +237 -0
- package/dist/vendor/config/instances.js +208 -0
- package/dist/vendor/config/load.js +490 -0
- package/dist/vendor/config/merge.js +68 -0
- package/dist/vendor/config/order.js +139 -0
- package/dist/vendor/config/ports.js +374 -0
- package/dist/vendor/config/providers.js +32 -0
- package/dist/vendor/config/repo.js +142 -0
- package/dist/vendor/config/retired-keys.js +430 -0
- package/dist/vendor/config/schema.js +1610 -0
- package/dist/vendor/config/secrets.js +50 -0
- package/dist/vendor/config/seed.js +34 -0
- package/dist/vendor/config/site-global.js +205 -0
- package/dist/vendor/config/validate.js +554 -0
- package/dist/vendor/config/winback.js +61 -0
- package/dist/vendor/devkit/attach-log-file.js +262 -0
- package/dist/vendor/devkit/bare-requires.js +153 -0
- package/dist/vendor/devkit/command-path.js +46 -0
- package/dist/vendor/devkit/defaults-engine.js +372 -0
- package/dist/vendor/devkit/deploy-record.js +180 -0
- package/dist/vendor/devkit/deploy.js +286 -0
- package/dist/vendor/devkit/license.js +155 -0
- package/dist/vendor/devkit/local-https.js +360 -0
- package/dist/vendor/devkit/local.js +1905 -0
- package/dist/vendor/devkit/logger.js +128 -0
- package/dist/vendor/devkit/merge-line-files.js +296 -0
- package/dist/vendor/devkit/npm-registry.js +52 -0
- package/dist/vendor/devkit/omega-bin.js +345 -0
- package/dist/vendor/devkit/parse-audit.js +74 -0
- package/dist/vendor/devkit/safe-install.js +18 -0
- package/dist/vendor/devkit/scaffold-guard.js +96 -0
- package/dist/vendor/devkit/stop-signals.js +28 -0
- package/dist/vendor/devkit/test/assert.js +120 -0
- package/dist/vendor/devkit/test/define-cases.js +104 -0
- package/dist/vendor/devkit/test/runner-core.js +554 -0
- package/dist/vendor/devkit/test/scope.js +162 -0
- package/dist/vendor/devkit/update.js +569 -0
- package/dist/vendor/monitoring/core.js +180 -0
- package/dist/vendor/monitoring/env.js +49 -0
- package/dist/vendor/monitoring/logger.js +39 -0
- package/dist/vendor/monitoring/node.js +72 -0
- package/docs/admin-post-route.md +57 -0
- package/docs/ai-library.md +171 -0
- package/docs/architecture.md +62 -0
- package/docs/audit.md +70 -0
- package/docs/auth-hooks.md +74 -0
- package/docs/build-system.md +65 -0
- package/docs/cdp-debugging.md +29 -0
- package/docs/cli-firestore-auth.md +85 -0
- package/docs/cli-logs.md +67 -0
- package/docs/cli-output.md +146 -0
- package/docs/code-patterns.md +77 -0
- package/docs/common-mistakes.md +12 -0
- package/docs/common-operations.md +60 -0
- package/docs/connections.md +212 -0
- package/docs/consent.md +362 -0
- package/docs/directory-structure.md +150 -0
- package/docs/email-system.md +459 -0
- package/docs/environment-detection.md +93 -0
- package/docs/file-naming.md +10 -0
- package/docs/firestore.md +134 -0
- package/docs/ghostii.md +240 -0
- package/docs/index.md +299 -0
- package/docs/key-files.md +37 -0
- package/docs/logging.md +60 -0
- package/docs/marketing-campaigns.md +407 -0
- package/docs/marketing-fields.md +25 -0
- package/docs/mcp.md +222 -0
- package/docs/migration.md +129 -0
- package/docs/payment-system.md +856 -0
- package/docs/paypal-sandbox-qa.md +105 -0
- package/docs/response-headers.md +9 -0
- package/docs/routes.md +211 -0
- package/docs/sanitization.md +71 -0
- package/docs/schemas.md +163 -0
- package/docs/shared/agent-docs.md +89 -0
- package/docs/shared/analytics.md +612 -0
- package/docs/shared/brands.md +51 -0
- package/docs/shared/breaking-changes.md +497 -0
- package/docs/shared/config.md +1387 -0
- package/docs/shared/deploys.md +215 -0
- package/docs/shared/icons.md +201 -0
- package/docs/shared/local-dev.md +147 -0
- package/docs/shared/logging.md +202 -0
- package/docs/shared/monitoring.md +153 -0
- package/docs/shared/publishing.md +183 -0
- package/docs/shared/rulings.md +34 -0
- package/docs/shared/testing.md +147 -0
- package/docs/shared/theming.md +604 -0
- package/docs/shared/translation.md +291 -0
- package/docs/shared/updates.md +61 -0
- package/docs/stripe-webhook-forwarding.md +20 -0
- package/docs/test-boot-layer.md +67 -0
- package/docs/test-framework.md +583 -0
- package/docs/usage-rate-limiting.md +121 -0
- package/docs/verts.md +29 -0
- package/package.json +143 -0
- package/templates/config/omega.json5 +322 -0
- package/templates/database.rules.json +82 -0
- package/templates/firebase.json +67 -0
- package/templates/firestore.framework.rules +191 -0
- package/templates/firestore.indexes.json +4 -0
- package/templates/firestore.rules +59 -0
- package/templates/index.js +12 -0
- package/templates/public/404.html +26 -0
- package/templates/public/index.html +24 -0
- package/templates/remoteconfig.template.json +1 -0
- package/templates/storage-lifecycle-config-1-day.json +9 -0
- package/templates/storage-lifecycle-config-30-days.json +9 -0
- package/templates/storage.rules +11 -0
- package/test/_init/accounts-validation.js +58 -0
- package/test/ai/tools-live.test.js +171 -0
- package/test/analytics/conversion-delivery.test.js +1013 -0
- package/test/analytics/match-normalization.test.js +238 -0
- package/test/analytics/signup-conversion.test.js +135 -0
- package/test/boot/cli-dispatch.test.js +142 -0
- package/test/boot/defaults-scaffold.test.js +248 -0
- package/test/boot/deploy-staging.test.js +446 -0
- package/test/boot/disposable-domains.test.js +154 -0
- package/test/boot/emulator-boots.test.js +39 -0
- package/test/boot/emulator-port-preflight.test.js +126 -0
- package/test/boot/emulator-ready-timeout.test.js +46 -0
- package/test/boot/project-id-refresh.test.js +73 -0
- package/test/boot/suite-portability.test.js +278 -0
- package/test/boot/update-command.test.js +63 -0
- package/test/cli/cross-platform.test.js +168 -0
- package/test/cli/custom-project-type.test.js +426 -0
- package/test/cli/deploy-license-env.test.js +100 -0
- package/test/cli/emulator-adoption.test.js +254 -0
- package/test/cli/emulator-orphans.test.js +658 -0
- package/test/cli/emulator-ownership.test.js +177 -0
- package/test/cli/emulator-port-retry.test.js +395 -0
- package/test/cli/emulator-shutdown.test.js +788 -0
- package/test/cli/emulator-stale-reap.test.js +574 -0
- package/test/cli/ensure-target.test.js +325 -0
- package/test/cli/flags.test.js +64 -0
- package/test/cli/https-trust.test.js +38 -0
- package/test/cli/install-update-dispatch.test.js +47 -0
- package/test/cli/lane-environments.test.js +159 -0
- package/test/cli/marketing-campaigns-seeded.test.js +194 -0
- package/test/cli/migrate-bare-requires.test.js +128 -0
- package/test/cli/migrate-markers.test.js +530 -0
- package/test/cli/required-indexes.test.js +64 -0
- package/test/cli/rules-compile.test.js +622 -0
- package/test/cli/rules-migration-deferral.test.js +259 -0
- package/test/cli/rules-version.test.js +104 -0
- package/test/cli/setup-load-files.test.js +66 -0
- package/test/cli/setup-offline-mode.test.js +276 -0
- package/test/cli/setup-retired.test.js +64 -0
- package/test/cli/setup-shared-project-indexes.test.js +262 -0
- package/test/cli/setup-tests-requires.test.js +45 -0
- package/test/cli/stage-env-compose.test.js +316 -0
- package/test/cli/stripe-live-lane.test.js +303 -0
- package/test/cli/target.test.js +182 -0
- package/test/cli/templates.test.js +66 -0
- package/test/cli/test-runner-env.test.js +115 -0
- package/test/cli/test-stack-shutdown.test.js +275 -0
- package/test/cli/test-target-no-match.test.js +128 -0
- package/test/cli/verb-logs.test.js +118 -0
- package/test/cli/version-dispatch.test.js +39 -0
- package/test/content/blog-generate.test.js +164 -0
- package/test/email/campaign-config-fault.test.js +376 -0
- package/test/email/campaign-cron-pipeline.test.js +536 -0
- package/test/email/campaign-send.test.js +47 -0
- package/test/email/consent-lifecycle.test.js +258 -0
- package/test/email/content-html-policy.test.js +270 -0
- package/test/email/feedback-and-plain-send.test.js +54 -0
- package/test/email/fixtures/clean.json +30 -0
- package/test/email/fixtures/editorial.json +30 -0
- package/test/email/fixtures/field-report.json +53 -0
- package/test/email/identity.test.js +321 -0
- package/test/email/marketing/consent-gate.test.js +265 -0
- package/test/email/marketing/custom-fields-catalog.test.js +259 -0
- package/test/email/marketing/prune-per-provider.test.js +762 -0
- package/test/email/marketing/remove-log-privacy.test.js +225 -0
- package/test/email/marketing-lifecycle.test.js +139 -0
- package/test/email/newsletter-generate.test.js +853 -0
- package/test/email/newsletter-svg-tokens.test.js +51 -0
- package/test/email/newsletter-templates.test.js +492 -0
- package/test/email/order-one-time-cta.test.js +97 -0
- package/test/email/render-content.test.js +248 -0
- package/test/email/safe-url.test.js +0 -0
- package/test/email/sanitize-images.test.js +66 -0
- package/test/email/send-log-privacy.test.js +107 -0
- package/test/email/templates.test.js +288 -0
- package/test/email/testing-capture.test.js +260 -0
- package/test/email/transactional-send.test.js +36 -0
- package/test/email/transactional.test.js +562 -0
- package/test/email/unsubscribe-groups.test.js +155 -0
- package/test/email/unsubscribe-key.test.js +91 -0
- package/test/email/validation-cases.test.js +152 -0
- package/test/email/validation.test.js +746 -0
- package/test/events/auth-delete-conversion.test.js +138 -0
- package/test/events/auth-delete-race.test.js +211 -0
- package/test/events/auth-on-create-log.test.js +164 -0
- package/test/events/auth-signup-conversion.test.js +141 -0
- package/test/events/auth-signup-limit.test.js +142 -0
- package/test/events/auth-trigger-log-privacy.test.js +256 -0
- package/test/events/cron-job-doc-shape.test.js +101 -0
- package/test/events/cron-reset-usage.test.js +116 -0
- package/test/events/notification-conversion.test.js +182 -0
- package/test/events/payments/_webhook-harness.js +307 -0
- package/test/events/payments/abandoned-cart-activity.test.js +135 -0
- package/test/events/payments/analytics-payment-events.test.js +945 -0
- package/test/events/payments/dispute-email-status.test.js +55 -0
- package/test/events/payments/journey-payments-abandoned.test.js +126 -0
- package/test/events/payments/journey-payments-cancel-endpoint.test.js +102 -0
- package/test/events/payments/journey-payments-cancel-no-order.test.js +76 -0
- package/test/events/payments/journey-payments-cancel.test.js +184 -0
- package/test/events/payments/journey-payments-decline.test.js +225 -0
- package/test/events/payments/journey-payments-discount.test.js +91 -0
- package/test/events/payments/journey-payments-dispute.test.js +190 -0
- package/test/events/payments/journey-payments-failure.test.js +148 -0
- package/test/events/payments/journey-payments-legacy-product.test.js +151 -0
- package/test/events/payments/journey-payments-one-time-decline.test.js +127 -0
- package/test/events/payments/journey-payments-one-time-failure.test.js +113 -0
- package/test/events/payments/journey-payments-one-time-refund.test.js +186 -0
- package/test/events/payments/journey-payments-one-time.test.js +175 -0
- package/test/events/payments/journey-payments-plan-change.test.js +146 -0
- package/test/events/payments/journey-payments-plan-switch-trial.test.js +161 -0
- package/test/events/payments/journey-payments-plan-switch.test.js +182 -0
- package/test/events/payments/journey-payments-refund-no-order.test.js +76 -0
- package/test/events/payments/journey-payments-refund-webhook.test.js +225 -0
- package/test/events/payments/journey-payments-suspend.test.js +183 -0
- package/test/events/payments/journey-payments-trial-cancel.test.js +132 -0
- package/test/events/payments/journey-payments-trial.test.js +173 -0
- package/test/events/payments/journey-payments-uid-resolution.test.js +134 -0
- package/test/events/payments/journey-payments-uncancel.test.js +157 -0
- package/test/events/payments/journey-payments-upgrade.test.js +137 -0
- package/test/events/payments/journey-payments-winback-decline.test.js +171 -0
- package/test/events/payments/journey-payments-winback.test.js +195 -0
- package/test/events/payments/paypal-expiry-cron.test.js +166 -0
- package/test/events/payments/purchase-failed-handler.test.js +80 -0
- package/test/events/payments/purchase-refunded-handler.test.js +108 -0
- package/test/events/payments/test-processor-doc-shape.test.js +162 -0
- package/test/events/payments/transition-order-emails.test.js +185 -0
- package/test/events/payments/transition-promo-lines.test.js +192 -0
- package/test/events/payments/transitions-detect.test.js +767 -0
- package/test/events/payments/trial-lapse-sweep-staleness.test.js +173 -0
- package/test/events/payments/trial-lapse-sweep.test.js +359 -0
- package/test/events/payments/webhook-atomic-writes.test.js +69 -0
- package/test/events/payments/webhook-chargebee-unreachable.test.js +106 -0
- package/test/events/payments/webhook-discount-clear.test.js +189 -0
- package/test/events/payments/webhook-failure-intent.test.js +93 -0
- package/test/events/payments/webhook-hosted-page-uid-trust.test.js +212 -0
- package/test/events/payments/webhook-ordering.test.js +326 -0
- package/test/events/payments/webhook-provider-lookup-trust.test.js +206 -0
- package/test/events/payments/webhook-refund-amount-trust.test.js +223 -0
- package/test/events/payments/webhook-refund-envelope-shape.test.js +148 -0
- package/test/events/payments/webhook-refund-linkage-trust.test.js +404 -0
- package/test/events/payments/webhook-refund-transaction-fallback.test.js +184 -0
- package/test/events/payments/webhook-refund-without-order.test.js +170 -0
- package/test/events/payments/webhook-refusal-reporting.test.js +194 -0
- package/test/events/payments/webhook-refused-intent.test.js +142 -0
- package/test/events/payments/webhook-retry-sweep.test.js +252 -0
- package/test/events/payments/webhook-transition-claim.test.js +389 -0
- package/test/events/payments/webhook-uid-trust.test.js +203 -0
- package/test/events/payments/webhook-user-without-auth.test.js +161 -0
- package/test/fixtures/chargebee/invoice-one-time.json +27 -0
- package/test/fixtures/chargebee/subscription-active.json +44 -0
- package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
- package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
- package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
- package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
- package/test/fixtures/chargebee/subscription-paused.json +42 -0
- package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
- package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
- package/test/fixtures/coinbase/charge-confirmed.json +50 -0
- package/test/fixtures/coinbase/charge-failed.json +34 -0
- package/test/fixtures/coinbase/charge-pending.json +34 -0
- package/test/fixtures/migrate/ported-route.js.txt +11 -0
- package/test/fixtures/paypal/capture-completed.json +51 -0
- package/test/fixtures/paypal/capture-refunded.json +43 -0
- package/test/fixtures/paypal/order-approved.json +62 -0
- package/test/fixtures/paypal/order-completed.json +110 -0
- package/test/fixtures/paypal/sale-refunded.json +38 -0
- package/test/fixtures/paypal/subscription-active.json +76 -0
- package/test/fixtures/paypal/subscription-cancelled.json +50 -0
- package/test/fixtures/paypal/subscription-suspended.json +65 -0
- package/test/fixtures/stripe/checkout-session-completed.json +130 -0
- package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
- package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
- package/test/fixtures/stripe/invoice-subscription-payment-succeeded.json +28 -0
- package/test/fixtures/stripe/subscription-active.json +161 -0
- package/test/fixtures/stripe/subscription-canceled.json +161 -0
- package/test/fixtures/stripe/subscription-trialing.json +161 -0
- package/test/helpers/_shared-config.js +15 -0
- package/test/helpers/ai-request-payload.test.js +619 -0
- package/test/helpers/ai-schema-resolve.test.js +125 -0
- package/test/helpers/ai-test-provider.test.js +227 -0
- package/test/helpers/ai-token-accounting.test.js +150 -0
- package/test/helpers/ai-tools-format.test.js +384 -0
- package/test/helpers/analytics-no-id-notice.test.js +146 -0
- package/test/helpers/analytics-user-data.test.js +194 -0
- package/test/helpers/api-manager.test.js +311 -0
- package/test/helpers/backend-router.test.js +136 -0
- package/test/helpers/content/blog-auto-publisher.test.js +482 -0
- package/test/helpers/content/feed-parser.test.js +529 -0
- package/test/helpers/content/ghostii-blocks.test.js +135 -0
- package/test/helpers/content/ghostii-feed-integration.test.js +405 -0
- package/test/helpers/content/ghostii-write-article.test.js +244 -0
- package/test/helpers/dev-only-routes.test.js +225 -0
- package/test/helpers/env-reader.test.js +362 -0
- package/test/helpers/environment.test.js +263 -0
- package/test/helpers/event-middleware.test.js +407 -0
- package/test/helpers/infer-contact.test.js +157 -0
- package/test/helpers/lane-url.test.js +90 -0
- package/test/helpers/merge-line-files.test.js +272 -0
- package/test/helpers/metadata.test.js +122 -0
- package/test/helpers/middleware-request-log.test.js +134 -0
- package/test/helpers/middleware-user-log.test.js +124 -0
- package/test/helpers/payment/chargebee/parse-webhook.test.js +490 -0
- package/test/helpers/payment/chargebee/refund-details.test.js +198 -0
- package/test/helpers/payment/chargebee/to-unified-one-time.test.js +148 -0
- package/test/helpers/payment/chargebee/to-unified-subscription.test.js +649 -0
- package/test/helpers/payment/coinbase/create-intent.test.js +247 -0
- package/test/helpers/payment/coinbase/parse-webhook.test.js +195 -0
- package/test/helpers/payment/coinbase/refund-unsupported.test.js +141 -0
- package/test/helpers/payment/coinbase/to-unified-one-time.test.js +201 -0
- package/test/helpers/payment/discount-codes.test.js +141 -0
- package/test/helpers/payment/extract-resource.test.js +113 -0
- package/test/helpers/payment/fetch-failure.test.js +215 -0
- package/test/helpers/payment/license-gate.test.js +108 -0
- package/test/helpers/payment/order-id.test.js +99 -0
- package/test/helpers/payment/paypal/create-intent.test.js +382 -0
- package/test/helpers/payment/paypal/fetch-capture.test.js +123 -0
- package/test/helpers/payment/paypal/fetch-sale.test.js +183 -0
- package/test/helpers/payment/paypal/parse-webhook.test.js +678 -0
- package/test/helpers/payment/paypal/refund-details.test.js +202 -0
- package/test/helpers/payment/paypal/resolve-plan-id.test.js +108 -0
- package/test/helpers/payment/paypal/switch-plan.test.js +92 -0
- package/test/helpers/payment/paypal/to-unified-one-time.test.js +383 -0
- package/test/helpers/payment/paypal/to-unified-subscription.test.js +884 -0
- package/test/helpers/payment/stripe/fetch-charge.test.js +147 -0
- package/test/helpers/payment/stripe/parse-webhook.test.js +448 -0
- package/test/helpers/payment/stripe/refund-details.test.js +147 -0
- package/test/helpers/payment/stripe/to-unified-one-time.test.js +307 -0
- package/test/helpers/payment/stripe/to-unified-subscription.test.js +709 -0
- package/test/helpers/persona-domain.test.js +87 -0
- package/test/helpers/recaptcha.test.js +166 -0
- package/test/helpers/resolved-config.test.js +106 -0
- package/test/helpers/response-log-redaction.test.js +262 -0
- package/test/helpers/roles.test.js +155 -0
- package/test/helpers/route-context-debug-gate.test.js +118 -0
- package/test/helpers/route-context-logging.test.js +214 -0
- package/test/helpers/route-context.test.js +129 -0
- package/test/helpers/safe-compare.test.js +114 -0
- package/test/helpers/sanitize.test.js +223 -0
- package/test/helpers/schema-engine.test.js +537 -0
- package/test/helpers/schema-zod.test.js +554 -0
- package/test/helpers/seed-accounts-load.test.js +163 -0
- package/test/helpers/seed-google-personas.test.js +114 -0
- package/test/helpers/seeded-personas.test.js +823 -0
- package/test/helpers/settings.test.js +292 -0
- package/test/helpers/setup-engines-pin.test.js +68 -0
- package/test/helpers/setup-manifest-sync.test.js +116 -0
- package/test/helpers/slugify.test.js +395 -0
- package/test/helpers/storage.test.js +195 -0
- package/test/helpers/test-banner-latch.test.js +139 -0
- package/test/helpers/usage-consume.test.js +460 -0
- package/test/helpers/usage-log-privacy.test.js +121 -0
- package/test/helpers/user-doc-heal.test.js +540 -0
- package/test/helpers/user.test.js +771 -0
- package/test/helpers/webhook-forward.test.js +419 -0
- package/test/helpers/wipe-auth-project.test.js +143 -0
- package/test/mcp/discovery.test.js +52 -0
- package/test/mcp/oauth.test.js +160 -0
- package/test/mcp/protocol.test.js +278 -0
- package/test/mcp/roles.test.js +203 -0
- package/test/mcp/utils.test.js +245 -0
- package/test/notification/identity.test.js +155 -0
- package/test/routes/admin/create-post.test.js +362 -0
- package/test/routes/admin/database.test.js +133 -0
- package/test/routes/admin/deduplicate-image-alts.test.js +191 -0
- package/test/routes/admin/email-content-html.test.js +58 -0
- package/test/routes/admin/email-request-log.test.js +86 -0
- package/test/routes/admin/email.test.js +116 -0
- package/test/routes/admin/firestore-query.test.js +206 -0
- package/test/routes/admin/firestore.test.js +129 -0
- package/test/routes/admin/infer-contact.test.js +220 -0
- package/test/routes/admin/notification.test.js +199 -0
- package/test/routes/admin/post-convert-image.test.js +159 -0
- package/test/routes/admin/post-deploy-flag.test.js +81 -0
- package/test/routes/admin/post-download-error.test.js +90 -0
- package/test/routes/admin/post-resize-image.test.js +185 -0
- package/test/routes/admin/post.test.js +369 -0
- package/test/routes/admin/repo-content.test.js +223 -0
- package/test/routes/admin/stats.test.js +114 -0
- package/test/routes/admin/users-disable.test.js +63 -0
- package/test/routes/admin/users-list.test.js +71 -0
- package/test/routes/content/post.test.js +60 -0
- package/test/routes/general/uuid.test.js +133 -0
- package/test/routes/health.test.js +110 -0
- package/test/routes/marketing/campaign.test.js +184 -0
- package/test/routes/marketing/contact.test.js +416 -0
- package/test/routes/marketing/email-preferences.test.js +293 -0
- package/test/routes/marketing/push-send.test.js +34 -0
- package/test/routes/marketing/webhook-forward.test.js +63 -0
- package/test/routes/marketing/webhook.test.js +641 -0
- package/test/routes/payments/_route-harness.js +145 -0
- package/test/routes/payments/cancel-provider-errors.test.js +227 -0
- package/test/routes/payments/cancel-skip-guards.test.js +126 -0
- package/test/routes/payments/cancel-trialing.test.js +428 -0
- package/test/routes/payments/cancel.test.js +165 -0
- package/test/routes/payments/dedup-race.test.js +186 -0
- package/test/routes/payments/discount.test.js +82 -0
- package/test/routes/payments/dispute-alert.test.js +324 -0
- package/test/routes/payments/intent-discount-amounts.test.js +550 -0
- package/test/routes/payments/intent-discount-coupons.test.js +293 -0
- package/test/routes/payments/intent-one-time-metadata.test.js +122 -0
- package/test/routes/payments/intent-purchaser-guard.test.js +139 -0
- package/test/routes/payments/intent-zero-total.test.js +260 -0
- package/test/routes/payments/intent.test.js +413 -0
- package/test/routes/payments/plan.test.js +367 -0
- package/test/routes/payments/portal-return-url.test.js +107 -0
- package/test/routes/payments/portal.test.js +94 -0
- package/test/routes/payments/refund-one-time.test.js +254 -0
- package/test/routes/payments/refund-paypal-proration.test.js +190 -0
- package/test/routes/payments/refund.test.js +181 -0
- package/test/routes/payments/trial-eligibility.test.js +73 -0
- package/test/routes/payments/uncancel.test.js +270 -0
- package/test/routes/payments/webhook-stripe-invoice.test.js +129 -0
- package/test/routes/payments/webhook-stripe-refund-one-time.test.js +130 -0
- package/test/routes/payments/webhook-test-provider.test.js +156 -0
- package/test/routes/payments/webhook.test.js +115 -0
- package/test/routes/payments/winback-stripe-coupon.test.js +126 -0
- package/test/routes/payments/winback.test.js +597 -0
- package/test/routes/test/authenticate.test.js +79 -0
- package/test/routes/test/redirect.test.js +66 -0
- package/test/routes/test/reset-account.test.js +126 -0
- package/test/routes/test/roster.test.js +101 -0
- package/test/routes/test/schema.test.js +556 -0
- package/test/routes/test/usage.test.js +374 -0
- package/test/routes/user/api-keys.test.js +158 -0
- package/test/routes/user/connections-grant.test.js +759 -0
- package/test/routes/user/connections-identity.test.js +260 -0
- package/test/routes/user/connections-log-privacy.test.js +206 -0
- package/test/routes/user/connections-refresh-lease.test.js +696 -0
- package/test/routes/user/connections-return.test.js +261 -0
- package/test/routes/user/connections-uid.test.js +516 -0
- package/test/routes/user/delete.test.js +138 -0
- package/test/routes/user/feedback.test.js +106 -0
- package/test/routes/user/orders.test.js +233 -0
- package/test/routes/user/sessions.test.js +231 -0
- package/test/routes/user/settings-validate.test.js +84 -0
- package/test/routes/user/signup-emails.test.js +113 -0
- package/test/routes/user/signup-location.test.js +84 -0
- package/test/routes/user/signup-log-privacy.test.js +112 -0
- package/test/routes/user/signup.test.js +579 -0
- package/test/routes/user/subscription.test.js +101 -0
- package/test/routes/user/token.test.js +111 -0
- package/test/routes/user/user.test.js +158 -0
- package/test/routes/verts/cache.test.js +78 -0
- package/test/routes/verts/click-destination.test.js +76 -0
- package/test/routes/verts/crud.test.js +194 -0
- package/test/routes/verts/redirect.test.js +147 -0
- package/test/routes/verts/selection.test.js +237 -0
- package/test/routes/verts/serve.test.js +185 -0
- package/test/routes/verts/unit-document.test.js +117 -0
- package/test/rules/_environment.js +66 -0
- package/test/rules/brand-merge.test.js +195 -0
- package/test/rules/field-helpers.test.js +309 -0
- package/test/rules/notifications.test.js +577 -0
- package/test/rules/payments-carts.test.js +406 -0
- package/test/rules/sessions.test.js +170 -0
- package/test/rules/user.test.js +517 -0
- package/test/rules/verts.test.js +101 -0
- package/test/security/fetch-log-secrets.test.js +124 -0
- package/test/security/primitives.test.js +105 -0
- package/test/security/repo-pinning.test.js +77 -0
- package/test/stripe-live/subscription-lifecycle.test.js +141 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beehiiv webhook provider
|
|
3
|
+
*
|
|
4
|
+
* Beehiiv sends one event per POST (unlike SendGrid's batched array).
|
|
5
|
+
* Each event includes a `publication_id` so the provider can decide whether
|
|
6
|
+
* the event belongs to THIS brand or a sibling brand sharing the same parent.
|
|
7
|
+
*
|
|
8
|
+
* Supported event types (anything else is silently ignored):
|
|
9
|
+
* - 'subscription.unsubscribed' — user clicked unsubscribe in a Beehiiv email
|
|
10
|
+
* - 'subscription.deleted' — admin or API removed the subscriber
|
|
11
|
+
* - 'subscription.paused' — user paused delivery (treat as revoke; we
|
|
12
|
+
* can't differentiate "pause" semantics)
|
|
13
|
+
*
|
|
14
|
+
* Publication routing:
|
|
15
|
+
* - If the event's publication_id doesn't match this brand's configured
|
|
16
|
+
* publication, silent skip. This is how the shared-devbeans publication
|
|
17
|
+
* case works: the parent fans the event to every child, and only the
|
|
18
|
+
* brand(s) sharing that publication actually process it.
|
|
19
|
+
* - getPublicationId() reads from config.marketing.newsletter.providers.beehiiv.publicationId
|
|
20
|
+
* or fuzzy-matches by brand name against the Beehiiv API.
|
|
21
|
+
*
|
|
22
|
+
* No idempotency ledger — the revoke + cross-provider remove are idempotent, so a
|
|
23
|
+
* provider retry re-runs safely with the same end state.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const REVOKE_EVENT_TYPES = new Set([
|
|
27
|
+
'subscription.unsubscribed',
|
|
28
|
+
'subscription.deleted',
|
|
29
|
+
'subscription.paused',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse the raw webhook request into a normalized array (single-element).
|
|
34
|
+
* Beehiiv sends one event per POST. Some HTTP clients serialize objects with
|
|
35
|
+
* extra keys merged in (e.g. our test client merges query params into the body) —
|
|
36
|
+
* we tolerate that by pulling fields explicitly.
|
|
37
|
+
*/
|
|
38
|
+
function parseWebhook(req) {
|
|
39
|
+
const body = req.body;
|
|
40
|
+
|
|
41
|
+
if (!body || typeof body !== 'object') {
|
|
42
|
+
throw new Error('Empty or non-object webhook body');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Beehiiv's event ID lives at `data.id` (per their docs) but their actual
|
|
46
|
+
// delivery shape can vary across endpoints. Try common locations.
|
|
47
|
+
const eventId = body.id
|
|
48
|
+
|| body.event_id
|
|
49
|
+
|| body.data?.id
|
|
50
|
+
|| null;
|
|
51
|
+
|
|
52
|
+
const eventType = body.event || body.type || null;
|
|
53
|
+
|
|
54
|
+
// Email may be at top-level or nested under `data`
|
|
55
|
+
const rawEmail = body.email || body.data?.email || null;
|
|
56
|
+
const email = typeof rawEmail === 'string' ? rawEmail.trim().toLowerCase() : null;
|
|
57
|
+
|
|
58
|
+
// Publication ID — required to decide whether THIS brand should handle it
|
|
59
|
+
const publicationId = body.publication_id || body.data?.publication_id || null;
|
|
60
|
+
|
|
61
|
+
// Timestamp — Beehiiv uses ISO 8601. Fall back to data.created_at variants.
|
|
62
|
+
const timestampISO = body.created_at
|
|
63
|
+
|| body.timestamp
|
|
64
|
+
|| body.data?.created_at
|
|
65
|
+
|| null;
|
|
66
|
+
|
|
67
|
+
const timestampUNIX = timestampISO
|
|
68
|
+
? Math.floor(new Date(timestampISO).getTime() / 1000) || null
|
|
69
|
+
: null;
|
|
70
|
+
|
|
71
|
+
return [{
|
|
72
|
+
eventId,
|
|
73
|
+
eventType,
|
|
74
|
+
email,
|
|
75
|
+
timestamp: timestampUNIX,
|
|
76
|
+
publicationId,
|
|
77
|
+
raw: body,
|
|
78
|
+
}];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns true if this event type represents a revocation we should act on.
|
|
83
|
+
*/
|
|
84
|
+
function isSupported(parsed) {
|
|
85
|
+
return REVOKE_EVENT_TYPES.has(parsed.eventType);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Process a single parsed event. Called by the dispatcher for each supported event.
|
|
90
|
+
* Returns a result object summarizing what happened.
|
|
91
|
+
*/
|
|
92
|
+
async function handleEvent({ Manager, ctx, parsed }) {
|
|
93
|
+
const { admin } = Manager.libraries;
|
|
94
|
+
const { eventId, eventType, email, timestamp, publicationId } = parsed;
|
|
95
|
+
|
|
96
|
+
if (!email) {
|
|
97
|
+
ctx.log(`beehiiv webhook: event ${eventId} (${eventType}) missing email, skipping`);
|
|
98
|
+
return { handled: false, reason: 'missing-email' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Publication filter — silent skip if the event isn't for our publication.
|
|
102
|
+
// This is THE mechanism that routes shared-publication events (e.g. devbeans
|
|
103
|
+
// across 6 brands) to only the brands that share that publication.
|
|
104
|
+
// beehiivProvider.getPublicationId() reads Manager.config.marketing.newsletter.providers.beehiiv.publicationId
|
|
105
|
+
// first, then falls back to fuzzy-match against the Beehiiv API by brand name.
|
|
106
|
+
if (publicationId) {
|
|
107
|
+
let ourPublicationId = null;
|
|
108
|
+
try {
|
|
109
|
+
const beehiivProvider = require('../../../../libraries/email/providers/beehiiv.js');
|
|
110
|
+
if (typeof beehiivProvider.getPublicationId === 'function') {
|
|
111
|
+
ourPublicationId = await beehiivProvider.getPublicationId();
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
ctx.error('beehiiv webhook: failed to resolve our publication ID:', e);
|
|
115
|
+
return { handled: false, reason: 'publication-resolve-failed' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!ourPublicationId) {
|
|
119
|
+
ctx.log(`beehiiv webhook: no publication configured for this brand, skipping event ${eventId}`);
|
|
120
|
+
return { handled: false, reason: 'no-local-publication' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (ourPublicationId !== publicationId) {
|
|
124
|
+
ctx.log(`beehiiv webhook: publication mismatch (event=${publicationId}, ours=${ourPublicationId}), skipping`);
|
|
125
|
+
return { handled: false, reason: 'publication-mismatch' };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Build the canonical revokedAt timestamp from the event (or server time if missing)
|
|
130
|
+
const startTime = ctx.meta.startTime;
|
|
131
|
+
const eventUNIX = typeof timestamp === 'number' ? timestamp : startTime.timestampUNIX;
|
|
132
|
+
const eventISO = new Date(eventUNIX * 1000).toISOString();
|
|
133
|
+
|
|
134
|
+
// Look up the user by email
|
|
135
|
+
const snapshot = await admin.firestore().collection('users')
|
|
136
|
+
.where('auth.email', '==', email)
|
|
137
|
+
.limit(1)
|
|
138
|
+
.get()
|
|
139
|
+
.catch((e) => {
|
|
140
|
+
ctx.error(`beehiiv webhook: user lookup failed for ${email}:`, e);
|
|
141
|
+
return null;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!snapshot || snapshot.empty) {
|
|
145
|
+
// Silent skip — this email may not map to a customer of THIS brand even if
|
|
146
|
+
// the publication matched (legitimate for shared-devbeans where 6 brands
|
|
147
|
+
// process every event but only one has the user).
|
|
148
|
+
ctx.log(`beehiiv webhook: no user found for ${email}, skipping doc update`);
|
|
149
|
+
return { handled: false, reason: 'user-not-found', email };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const userDoc = snapshot.docs[0];
|
|
153
|
+
const uid = userDoc.id;
|
|
154
|
+
|
|
155
|
+
// Write consent.marketing.status = 'revoked' (preserve grantedAt — informational audit trail)
|
|
156
|
+
await admin.firestore().doc(`users/${uid}`).set({
|
|
157
|
+
consent: {
|
|
158
|
+
marketing: {
|
|
159
|
+
status: 'revoked',
|
|
160
|
+
revokedAt: {
|
|
161
|
+
timestamp: eventISO,
|
|
162
|
+
timestampUNIX: eventUNIX,
|
|
163
|
+
source: 'beehiiv',
|
|
164
|
+
ip: null,
|
|
165
|
+
text: null,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
metadata: Manager.Metadata().set({ tag: 'marketing/webhook:beehiiv' }),
|
|
170
|
+
}, { merge: true });
|
|
171
|
+
|
|
172
|
+
ctx.log(`beehiiv webhook: revoked consent.marketing for ${uid} (${email}) — eventType=${eventType}`);
|
|
173
|
+
|
|
174
|
+
// Cross-provider sync: also remove from SendGrid (best-effort, idempotent on 404)
|
|
175
|
+
const shouldCallExternalAPIs = !ctx.isTesting() || process.env.TEST_EXTENDED_MODE;
|
|
176
|
+
|
|
177
|
+
if (shouldCallExternalAPIs) {
|
|
178
|
+
try {
|
|
179
|
+
const mailer = Manager.Email(ctx);
|
|
180
|
+
await mailer.remove(email);
|
|
181
|
+
ctx.log(`beehiiv webhook: cross-provider sync complete for ${email}`);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
// Best-effort — user doc is already updated. Log + continue.
|
|
184
|
+
ctx.error(`beehiiv webhook: cross-provider sync failed for ${email}:`, e);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
ctx.log('beehiiv webhook: skipping cross-provider sync (OMEGA_TEST_MODE=true)');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { handled: true, uid, email, eventType };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
parseWebhook,
|
|
195
|
+
isSupported,
|
|
196
|
+
handleEvent,
|
|
197
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SendGrid webhook provider
|
|
3
|
+
*
|
|
4
|
+
* SendGrid sends an array of events per POST. This module parses the array,
|
|
5
|
+
* decides which events represent an unsubscribe (revocation of marketing consent),
|
|
6
|
+
* and exposes a per-event handler that:
|
|
7
|
+
* 1. Looks up the user by email in this brand's Firestore (silent skip if not found)
|
|
8
|
+
* 2. Writes consent.marketing.status = 'revoked' to the user doc, source: 'sendgrid'
|
|
9
|
+
* 3. Calls Beehiiv to remove the contact too (cross-provider sync — idempotent on 404)
|
|
10
|
+
*
|
|
11
|
+
* Supported event types (anything else is silently ignored):
|
|
12
|
+
* - 'unsubscribe' — user clicked the unified unsubscribe link
|
|
13
|
+
* - 'group_unsubscribe' — user unsubscribed from a specific ASM group
|
|
14
|
+
* - 'spamreport' — user marked email as spam
|
|
15
|
+
* - 'bounce' — ONLY when bounce_classification is 'Invalid Address' (hard bounce).
|
|
16
|
+
* Technical bounces (DMARC, TLS, DNS) are sender-side issues and
|
|
17
|
+
* should NOT revoke the recipient's consent.
|
|
18
|
+
* - 'dropped' — ONLY when bounce_classification is 'Invalid Address'.
|
|
19
|
+
*
|
|
20
|
+
* Note: 'group_unsubscribe' is the most common one (matches our ASM-link flow), and since
|
|
21
|
+
* an unsubscribe group is account-global across every brand sharing the SendGrid account,
|
|
22
|
+
* an unsub from the marketing group legitimately removes the user from marketing across
|
|
23
|
+
* the entire account.
|
|
24
|
+
*
|
|
25
|
+
* No idempotency ledger — the revoke + cross-provider remove are idempotent, so a
|
|
26
|
+
* provider retry re-runs safely with the same end state.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const REVOKE_EVENT_TYPES = new Set([
|
|
30
|
+
'unsubscribe',
|
|
31
|
+
'group_unsubscribe',
|
|
32
|
+
'spamreport',
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// Bounce events only revoke consent when the bounce_classification indicates
|
|
36
|
+
// the mailbox genuinely doesn't exist. Technical bounces (DMARC, TLS, DNS),
|
|
37
|
+
// reputation bounces, content blocks, and temporary failures are the SENDER's
|
|
38
|
+
// problem — the recipient's email is still valid.
|
|
39
|
+
const HARD_BOUNCE_CLASSIFICATIONS = new Set([
|
|
40
|
+
'Invalid Address',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse the raw webhook request into a normalized array of events.
|
|
45
|
+
* SendGrid sends an array of events as the body. Some HTTP clients (including
|
|
46
|
+
* @omega.js/backend's own test client) JSON-encode arrays as objects with numeric keys —
|
|
47
|
+
* we tolerate both shapes plus the rare single-event object form.
|
|
48
|
+
*/
|
|
49
|
+
function parseWebhook(req) {
|
|
50
|
+
const body = req.body;
|
|
51
|
+
|
|
52
|
+
if (!body) {
|
|
53
|
+
throw new Error('Empty webhook body');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let events;
|
|
57
|
+
if (Array.isArray(body)) {
|
|
58
|
+
// Real SendGrid: array body
|
|
59
|
+
events = body;
|
|
60
|
+
} else if (body && typeof body === 'object' && typeof body['0'] === 'object' && body['0'] !== null) {
|
|
61
|
+
// Array serialized as object-with-numeric-keys (test clients, some proxies)
|
|
62
|
+
events = [];
|
|
63
|
+
let i = 0;
|
|
64
|
+
while (body[String(i)]) {
|
|
65
|
+
events.push(body[String(i)]);
|
|
66
|
+
i += 1;
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
// Single event sent as a bare object
|
|
70
|
+
events = [body];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return events.map((event) => {
|
|
74
|
+
// sg_event_id is SendGrid's per-event unique ID, retained for log context.
|
|
75
|
+
// smtp-id is another stable identifier we fall back to.
|
|
76
|
+
const eventId = event.sg_event_id || event['smtp-id'] || event.smtpId || null;
|
|
77
|
+
const eventType = event.event;
|
|
78
|
+
const email = typeof event.email === 'string' ? event.email.trim().toLowerCase() : null;
|
|
79
|
+
const timestamp = typeof event.timestamp === 'number' ? event.timestamp : null;
|
|
80
|
+
const asmGroupId = event.asm_group_id || null;
|
|
81
|
+
const bounceClassification = event.bounce_classification || null;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
eventId,
|
|
85
|
+
eventType,
|
|
86
|
+
email,
|
|
87
|
+
timestamp,
|
|
88
|
+
asmGroupId,
|
|
89
|
+
bounceClassification,
|
|
90
|
+
raw: event,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns true if this parsed event represents a revocation we should act on.
|
|
97
|
+
* For bounce/dropped events, only hard bounces (Invalid Address) qualify.
|
|
98
|
+
*/
|
|
99
|
+
function isSupported(parsed) {
|
|
100
|
+
const { eventType, bounceClassification } = parsed;
|
|
101
|
+
|
|
102
|
+
if (REVOKE_EVENT_TYPES.has(eventType)) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (eventType === 'bounce' || eventType === 'dropped') {
|
|
107
|
+
return HARD_BOUNCE_CLASSIFICATIONS.has(bounceClassification);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Process a single parsed event. Called by the dispatcher for each supported event.
|
|
115
|
+
*
|
|
116
|
+
* Returns a result object summarizing what happened (for logging/response).
|
|
117
|
+
*/
|
|
118
|
+
async function handleEvent({ Manager, ctx, parsed }) {
|
|
119
|
+
const { admin } = Manager.libraries;
|
|
120
|
+
const { eventId, eventType, email, timestamp } = parsed;
|
|
121
|
+
|
|
122
|
+
if (!email) {
|
|
123
|
+
ctx.log(`sendgrid webhook: event ${eventId} (${eventType}) missing email, skipping`);
|
|
124
|
+
return { handled: false, reason: 'missing-email' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Convert SendGrid's UNIX timestamp to our canonical { timestamp, timestampUNIX } shape.
|
|
128
|
+
// Fall back to server time if missing.
|
|
129
|
+
const startTime = ctx.meta.startTime;
|
|
130
|
+
const eventUNIX = typeof timestamp === 'number' ? timestamp : startTime.timestampUNIX;
|
|
131
|
+
const eventISO = new Date(eventUNIX * 1000).toISOString();
|
|
132
|
+
|
|
133
|
+
// Look up the user by email
|
|
134
|
+
const snapshot = await admin.firestore().collection('users')
|
|
135
|
+
.where('auth.email', '==', email)
|
|
136
|
+
.limit(1)
|
|
137
|
+
.get()
|
|
138
|
+
.catch((e) => {
|
|
139
|
+
ctx.error(`sendgrid webhook: user lookup failed for ${email}:`, e);
|
|
140
|
+
return null;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!snapshot || snapshot.empty) {
|
|
144
|
+
// Silent skip — this email may not map to a customer of THIS brand (shared SendGrid account).
|
|
145
|
+
ctx.log(`sendgrid webhook: no user found for ${email}, skipping doc update`);
|
|
146
|
+
return { handled: false, reason: 'user-not-found', email };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const userDoc = snapshot.docs[0];
|
|
150
|
+
const uid = userDoc.id;
|
|
151
|
+
|
|
152
|
+
// Write consent.marketing.status = 'revoked' (preserve grantedAt — informational audit trail)
|
|
153
|
+
await admin.firestore().doc(`users/${uid}`).set({
|
|
154
|
+
consent: {
|
|
155
|
+
marketing: {
|
|
156
|
+
status: 'revoked',
|
|
157
|
+
revokedAt: {
|
|
158
|
+
timestamp: eventISO,
|
|
159
|
+
timestampUNIX: eventUNIX,
|
|
160
|
+
source: 'sendgrid',
|
|
161
|
+
ip: null,
|
|
162
|
+
text: null,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
metadata: Manager.Metadata().set({ tag: 'marketing/webhook:sendgrid' }),
|
|
167
|
+
}, { merge: true });
|
|
168
|
+
|
|
169
|
+
ctx.log(`sendgrid webhook: revoked consent.marketing for ${uid} (${email}) — eventType=${eventType}`);
|
|
170
|
+
|
|
171
|
+
// Cross-provider sync: also remove from Beehiiv (best-effort, idempotent on 404)
|
|
172
|
+
const shouldCallExternalAPIs = !ctx.isTesting() || process.env.TEST_EXTENDED_MODE;
|
|
173
|
+
|
|
174
|
+
if (shouldCallExternalAPIs) {
|
|
175
|
+
try {
|
|
176
|
+
const mailer = Manager.Email(ctx);
|
|
177
|
+
await mailer.remove(email);
|
|
178
|
+
ctx.log(`sendgrid webhook: cross-provider sync complete for ${email}`);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// Best-effort — user doc is already updated. Log + continue.
|
|
181
|
+
ctx.error(`sendgrid webhook: cross-provider sync failed for ${email}:`, e);
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
ctx.log('sendgrid webhook: skipping cross-provider sync (OMEGA_TEST_MODE=true)');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { handled: true, uid, email, eventType };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
parseWebhook,
|
|
192
|
+
isSupported,
|
|
193
|
+
handleEvent,
|
|
194
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is this subscription still INSIDE its free trial?
|
|
3
|
+
*
|
|
4
|
+
* The cancel flow asks this in two places and both have to agree, so it is one
|
|
5
|
+
* function rather than a copy per caller ([#267](https://github.com/Omega-JS-Stack/omega/issues/267)):
|
|
6
|
+
*
|
|
7
|
+
* - the route waives the 24-hour young-subscription guard for a trial (that
|
|
8
|
+
* guard exists for settlement games on a PAID subscription; a trial has no
|
|
9
|
+
* payment to settle, and a same-day trial cancel is the most common trial
|
|
10
|
+
* behavior there is), and
|
|
11
|
+
* - every provider cancels a trial NOW instead of at period end — Ian's ruling
|
|
12
|
+
* (2026-08-15): we do not keep serving a trial we know will not convert.
|
|
13
|
+
*
|
|
14
|
+
* The test: the trial is claimed, the subscription is live, and the subscription
|
|
15
|
+
* still expires exactly when the TRIAL does. That last clause is what makes a
|
|
16
|
+
* CONVERTED trial read false — conversion moves `expires` out to the end of the
|
|
17
|
+
* first paid period while `trial.expires` stays where it was, so the two stop
|
|
18
|
+
* matching the moment real money is involved.
|
|
19
|
+
*
|
|
20
|
+
* A trial that has run out but has not been swept yet still reads true, which is
|
|
21
|
+
* the answer this flow wants: nothing was ever paid, so there is nothing to let
|
|
22
|
+
* ride to a period end.
|
|
23
|
+
*
|
|
24
|
+
* The expiry has to EXIST to match. The three providers this was folded out of
|
|
25
|
+
* compared the two timestamps directly, so a subscription carrying neither one
|
|
26
|
+
* matched itself on `undefined` and read as trialing — harmless while the answer
|
|
27
|
+
* only chose a cancel mode, but this now also waives a guard, and a guard must
|
|
28
|
+
* never be waived by missing data.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} [subscription] - The user's subscription object
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
module.exports = function isTrialing(subscription) {
|
|
34
|
+
const trialExpires = subscription?.trial?.expires?.timestampUNIX;
|
|
35
|
+
|
|
36
|
+
return !!(subscription?.trial?.claimed
|
|
37
|
+
&& subscription?.status === 'active'
|
|
38
|
+
&& trialExpires
|
|
39
|
+
&& trialExpires === subscription?.expires?.timestampUNIX);
|
|
40
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const loadProvider = require('../../../libraries/load-provider.js');
|
|
3
|
+
const powertools = require('node-powertools');
|
|
4
|
+
const isAlreadyGone = require('../../../libraries/payment/provider-errors.js');
|
|
5
|
+
const isTrialing = require('./_is-trialing.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* POST /payments/cancel
|
|
9
|
+
* Cancels the authenticated user's subscription at the end of the current billing period.
|
|
10
|
+
* Delegates to the provider (e.g., Stripe) to set cancel_at_period_end=true.
|
|
11
|
+
* The resulting webhook triggers the Firestore pipeline which updates subscription state
|
|
12
|
+
* and fires the cancellation-requested transition handler.
|
|
13
|
+
* Stores the cancellation reason/feedback on payments-orders/{orderId}.requests.cancellation.
|
|
14
|
+
* Requires authentication.
|
|
15
|
+
*/
|
|
16
|
+
module.exports = async ({ ctx, user, settings }) => {
|
|
17
|
+
// Require authentication
|
|
18
|
+
if (!user.authenticated) {
|
|
19
|
+
return ctx.respond('Authentication required', { code: 401 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const uid = user.auth.uid;
|
|
23
|
+
const confirmed = settings.confirmed;
|
|
24
|
+
|
|
25
|
+
// Require explicit confirmation
|
|
26
|
+
if (!confirmed) {
|
|
27
|
+
return ctx.respond('Cancellation must be confirmed', { code: 400 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const subscription = user.subscription;
|
|
31
|
+
|
|
32
|
+
// Require an active or suspended paid subscription
|
|
33
|
+
if (!subscription || (subscription.status !== 'active' && subscription.status !== 'suspended') || subscription.product?.id === 'basic') {
|
|
34
|
+
ctx.log(`Cancel rejected: uid=${uid}, status=${subscription?.status}, product=${subscription?.product?.id}`);
|
|
35
|
+
return ctx.respond('No active paid subscription found', { code: 400 });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// `skipGuards` arrives in the request body, so it is a REQUEST, not a decision:
|
|
39
|
+
// it is honored for an admin, or anywhere outside a real deployment (the test
|
|
40
|
+
// suites and the dev palette cancel seeded subscriptions minutes old). Every
|
|
41
|
+
// other caller is ignored — loudly — and the guards below run as normal.
|
|
42
|
+
const mayBypassGuards = user.roles?.admin === true || ctx.isDevelopment() || ctx.isTesting();
|
|
43
|
+
const skipGuards = settings.skipGuards === true && mayBypassGuards;
|
|
44
|
+
|
|
45
|
+
if (settings.skipGuards === true && !mayBypassGuards) {
|
|
46
|
+
ctx.warn(`Ignoring skipGuards on cancel: uid=${uid} is not permitted to bypass the cancellation guards`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Guard: subscription younger than 24 hours (privileged callers may bypass via skipGuards).
|
|
50
|
+
//
|
|
51
|
+
// A TRIAL is exempt ([#267](https://github.com/Omega-JS-Stack/omega/issues/267)):
|
|
52
|
+
// the guard exists to stop a cancellation racing a PAID checkout that is still
|
|
53
|
+
// settling, and a trial has no payment to settle. Blocking it told the most
|
|
54
|
+
// common trial behavior there is — cancelling the same day you started — that
|
|
55
|
+
// the subscription "is still being set up".
|
|
56
|
+
const trialing = isTrialing(subscription);
|
|
57
|
+
const startDateUNIX = subscription.payment?.startDate?.timestampUNIX;
|
|
58
|
+
if (!skipGuards && !trialing && startDateUNIX) {
|
|
59
|
+
const ageMs = Date.now() - (startDateUNIX * 1000);
|
|
60
|
+
const twentyFourHoursMs = 24 * 60 * 60 * 1000;
|
|
61
|
+
if (ageMs < twentyFourHoursMs) {
|
|
62
|
+
ctx.log(`Cancel rejected: uid=${uid}, subscription is only ${Math.round(ageMs / 1000 / 60)} minutes old`);
|
|
63
|
+
return ctx.respond('Your subscription is still being set up. Please try again after 24-48 hours.', { code: 400 });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Guard: already pending cancellation
|
|
68
|
+
if (subscription.cancellation?.pending === true) {
|
|
69
|
+
ctx.log(`Cancel rejected: uid=${uid}, cancellation already pending`);
|
|
70
|
+
return ctx.respond('Subscription is already pending cancellation', { code: 400 });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const provider = subscription.payment?.provider;
|
|
74
|
+
const resourceId = subscription.payment?.resourceId;
|
|
75
|
+
|
|
76
|
+
if (!provider || !resourceId) {
|
|
77
|
+
ctx.log(`Cancel rejected: uid=${uid}, missing provider=${provider} or resourceId=${resourceId}`);
|
|
78
|
+
return ctx.respond('Subscription payment details not found', { code: 400 });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Load the provider module
|
|
82
|
+
let providerModule;
|
|
83
|
+
try {
|
|
84
|
+
providerModule = loadProvider(path.join(__dirname, 'providers'), provider);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return ctx.respond(`Unknown provider: ${provider}`, { code: 400 });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Cancel at period end via the provider
|
|
90
|
+
try {
|
|
91
|
+
await providerModule.cancelAtPeriodEnd({ resourceId, uid, subscription, ctx });
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// A suspended subscription the provider says NO LONGER EXISTS is a dead
|
|
94
|
+
// record on our side alone: reset it directly so the user can re-subscribe.
|
|
95
|
+
// The classification is the whole guard — every transient or unrecognized
|
|
96
|
+
// failure falls through to the caller with NOTHING written, so a network
|
|
97
|
+
// blip can never fabricate a cancellation ([#212]).
|
|
98
|
+
if (subscription.status === 'suspended' && isAlreadyGone(e)) {
|
|
99
|
+
ctx.log(`Provider reports the suspended subscription is already gone (${e.message}), resetting directly`);
|
|
100
|
+
const admin = ctx.Manager.libraries.admin;
|
|
101
|
+
const now = powertools.timestamp(new Date(), { output: 'string' });
|
|
102
|
+
const nowUNIX = powertools.timestamp(now, { output: 'unix' });
|
|
103
|
+
|
|
104
|
+
await admin.firestore().doc(`users/${uid}`).set({
|
|
105
|
+
subscription: {
|
|
106
|
+
status: 'cancelled',
|
|
107
|
+
product: { id: 'basic', name: 'Basic' },
|
|
108
|
+
cancellation: { pending: false, date: { timestamp: now, timestampUNIX: nowUNIX } },
|
|
109
|
+
},
|
|
110
|
+
}, { merge: true });
|
|
111
|
+
|
|
112
|
+
ctx.log(`Directly cancelled suspended subscription for uid=${uid}`);
|
|
113
|
+
return ctx.respond({ success: true });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The provider's own words stay in the logs — a client gets one neutral
|
|
117
|
+
// sentence, never an SDK message naming our internals ([#212]).
|
|
118
|
+
ctx.error(`Failed to cancel subscription via ${provider}: uid=${uid}, sub=${resourceId}, error=${e.message}`);
|
|
119
|
+
return ctx.respond('We could not cancel your subscription right now. Please try again shortly.', { code: 500 });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Store cancellation reason/feedback on the order doc
|
|
123
|
+
const orderId = subscription.payment?.orderId;
|
|
124
|
+
|
|
125
|
+
if (orderId) {
|
|
126
|
+
const admin = ctx.Manager.libraries.admin;
|
|
127
|
+
const now = powertools.timestamp(new Date(), { output: 'string' });
|
|
128
|
+
const nowUNIX = powertools.timestamp(now, { output: 'unix' });
|
|
129
|
+
|
|
130
|
+
await admin.firestore().doc(`payments-orders/${orderId}`).set({
|
|
131
|
+
requests: {
|
|
132
|
+
cancellation: {
|
|
133
|
+
reason: settings.reason || null,
|
|
134
|
+
feedback: settings.feedback || null,
|
|
135
|
+
date: {
|
|
136
|
+
timestamp: now,
|
|
137
|
+
timestampUNIX: nowUNIX,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
}, { merge: true });
|
|
142
|
+
|
|
143
|
+
ctx.log(`Stored cancellation request on payments-orders/${orderId}: reason=${settings.reason}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
ctx.log(`Cancel ${trialing ? 'immediate (trialing)' : 'scheduled'}: uid=${uid}, provider=${provider}, sub=${resourceId}, reason=${settings.reason}`);
|
|
147
|
+
|
|
148
|
+
return ctx.respond({ success: true });
|
|
149
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const isTrialing = require('../_is-trialing.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Chargebee cancel provider
|
|
5
|
+
* Cancels a subscription — immediately if trialing, at period end otherwise.
|
|
6
|
+
*
|
|
7
|
+
* Chargebee cancel_option:
|
|
8
|
+
* - 'end_of_term': Sets status to non_renewing (cancel at period end)
|
|
9
|
+
* - 'immediately': Cancels right away (status → cancelled)
|
|
10
|
+
*/
|
|
11
|
+
module.exports = {
|
|
12
|
+
/**
|
|
13
|
+
* Cancel a Chargebee subscription
|
|
14
|
+
*
|
|
15
|
+
* @param {object} options
|
|
16
|
+
* @param {string} options.resourceId - Chargebee subscription ID
|
|
17
|
+
* @param {string} options.uid - User's UID (for logging)
|
|
18
|
+
* @param {object} options.subscription - User's current subscription object
|
|
19
|
+
* @param {object} options.ctx - Assistant instance for logging
|
|
20
|
+
*/
|
|
21
|
+
async cancelAtPeriodEnd({ resourceId, uid, subscription, ctx }) {
|
|
22
|
+
const ChargebeeLib = require('../../../../libraries/payment/providers/chargebee.js');
|
|
23
|
+
ChargebeeLib.init();
|
|
24
|
+
|
|
25
|
+
if (isTrialing(subscription)) {
|
|
26
|
+
// Immediate cancel for trials
|
|
27
|
+
await ChargebeeLib.request(`/subscriptions/${resourceId}/cancel_for_items`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
body: { cancel_option: 'immediately' },
|
|
30
|
+
});
|
|
31
|
+
ctx.log(`Chargebee cancel immediate (trialing): sub=${resourceId}, uid=${uid}`);
|
|
32
|
+
} else {
|
|
33
|
+
// Cancel at end of billing period
|
|
34
|
+
await ChargebeeLib.request(`/subscriptions/${resourceId}/cancel_for_items`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
body: { cancel_option: 'end_of_term' },
|
|
37
|
+
});
|
|
38
|
+
ctx.log(`Chargebee cancel at period end: sub=${resourceId}, uid=${uid}`);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const isTrialing = require('../_is-trialing.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PayPal cancel provider
|
|
5
|
+
* Cancels a subscription — immediately if trialing, at period end otherwise.
|
|
6
|
+
*
|
|
7
|
+
* Note: PayPal does not have "cancel at period end" like Stripe. Cancellation
|
|
8
|
+
* stops the billing agreement right away, and for a PAID subscription the
|
|
9
|
+
* subscriber keeps access until the end of the period they already paid for
|
|
10
|
+
* (PayPal's default behavior, and ours — the pipeline leaves the future
|
|
11
|
+
* `expires` in place).
|
|
12
|
+
*
|
|
13
|
+
* A TRIAL is where the two part company
|
|
14
|
+
* ([#267](https://github.com/Omega-JS-Stack/omega/issues/267)): access ends NOW,
|
|
15
|
+
* because there is no paid-for period to serve out. PayPal exposes no second
|
|
16
|
+
* cancel mode to ask for that, so the call itself is necessarily the same one —
|
|
17
|
+
* the immediacy is enforced on OUR side, in the unified transform: for a
|
|
18
|
+
* subscription still inside its trial window `calculatePeriodEnd()` returns null
|
|
19
|
+
* (libraries/payment/providers/paypal.js), so the cancellation webhook resolves
|
|
20
|
+
* the subscription to `cancelled` with nothing pending and no future expiry,
|
|
21
|
+
* instead of pricing a "remaining period" off a setup-fee payment. The
|
|
22
|
+
* `subscription-cancelled` transition only shapes the email that follows. The
|
|
23
|
+
* branch is here so the intent is stated at the call site and the logs say which
|
|
24
|
+
* cancel this was, exactly as the Stripe and Chargebee providers do.
|
|
25
|
+
*/
|
|
26
|
+
module.exports = {
|
|
27
|
+
/**
|
|
28
|
+
* Cancel a PayPal subscription
|
|
29
|
+
*
|
|
30
|
+
* @param {object} options
|
|
31
|
+
* @param {string} options.resourceId - PayPal subscription ID (e.g., 'I-xxx')
|
|
32
|
+
* @param {string} options.uid - User's UID (for logging)
|
|
33
|
+
* @param {object} options.subscription - User's current subscription object
|
|
34
|
+
* @param {object} options.ctx - Assistant instance for logging
|
|
35
|
+
*/
|
|
36
|
+
async cancelAtPeriodEnd({ resourceId, uid, subscription, ctx }) {
|
|
37
|
+
const PayPalLib = require('../../../../libraries/payment/providers/paypal.js');
|
|
38
|
+
|
|
39
|
+
const trialing = isTrialing(subscription);
|
|
40
|
+
|
|
41
|
+
await PayPalLib.request(`/v1/billing/subscriptions/${resourceId}/cancel`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
reason: trialing
|
|
45
|
+
? 'Customer cancelled during free trial'
|
|
46
|
+
: 'Customer requested cancellation',
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (trialing) {
|
|
51
|
+
ctx.log(`PayPal cancel immediate (trialing): sub=${resourceId}, uid=${uid}`);
|
|
52
|
+
} else {
|
|
53
|
+
ctx.log(`PayPal cancel at period end: sub=${resourceId}, uid=${uid}`);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|