@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,341 @@
|
|
|
1
|
+
const fetch = require('wonderful-fetch');
|
|
2
|
+
const powertools = require('node-powertools');
|
|
3
|
+
const discountCodes = require('../../../../libraries/payment/discount-codes.js');
|
|
4
|
+
const env = require('../../../../libraries/env.js');
|
|
5
|
+
|
|
6
|
+
// A declined subscription fires two events, and the second one reads what the
|
|
7
|
+
// first one wrote — how long it waits for that, and how often it looks
|
|
8
|
+
const ORDER_WAIT_ATTEMPTS = 40;
|
|
9
|
+
const ORDER_WAIT_INTERVAL = 500;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Test intent provider
|
|
13
|
+
* Creates fake Stripe-shaped checkout sessions and auto-fires webhooks
|
|
14
|
+
* Only available in non-production environments
|
|
15
|
+
*/
|
|
16
|
+
module.exports = {
|
|
17
|
+
/**
|
|
18
|
+
* Create a test payment intent
|
|
19
|
+
* Generates Stripe-shaped data and auto-fires a webhook to trigger the full pipeline
|
|
20
|
+
*
|
|
21
|
+
* @param {object} options
|
|
22
|
+
* @param {string} options.uid - User's UID
|
|
23
|
+
* @param {object} options.product - Full product object from config
|
|
24
|
+
* @param {string} options.productId - Product ID from config
|
|
25
|
+
* @param {string} options.frequency - 'monthly', 'annually', 'weekly', or 'daily' (subscriptions only)
|
|
26
|
+
* @param {boolean} options.trial - Whether to include a trial period (subscriptions only)
|
|
27
|
+
* @param {object} options.discount - Validated discount from discount-codes.validate(), or null
|
|
28
|
+
* @param {string} options.simulate - Checkout outcome to simulate ('decline' | 'abandon'), or null for success
|
|
29
|
+
* @param {string} options.confirmationUrl - Success redirect URL
|
|
30
|
+
* @param {string} options.cancelUrl - Cancel redirect URL
|
|
31
|
+
* @param {object} options.ctx - Assistant instance
|
|
32
|
+
* @returns {object} { id, url, raw }
|
|
33
|
+
*/
|
|
34
|
+
async createIntent({ uid, orderId, product, productId, frequency, trial, discount, simulate, confirmationUrl, ctx }) {
|
|
35
|
+
// Guard: test provider is not available in production
|
|
36
|
+
if (ctx.isProduction()) {
|
|
37
|
+
throw new Error('Test provider is not available in production');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const productType = product.type || 'subscription';
|
|
41
|
+
const declined = simulate === 'decline';
|
|
42
|
+
// An ABANDONED checkout is the absence of an event, not a different one: the
|
|
43
|
+
// session is created, the customer closes the tab, and the provider has
|
|
44
|
+
// nothing to report. So the session comes back exactly as it always does and
|
|
45
|
+
// no webhook is fired — the state under test is what that leaves behind
|
|
46
|
+
// ([#212](https://github.com/Omega-JS-Stack/omega/issues/212)).
|
|
47
|
+
const abandoned = simulate === 'abandon';
|
|
48
|
+
|
|
49
|
+
if (productType === 'subscription') {
|
|
50
|
+
return createSubscriptionIntent({ uid, orderId, product, frequency, trial, discount, declined, abandoned, confirmationUrl, ctx });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return createOneTimeIntent({ uid, orderId, product, productId, discount, declined, abandoned, confirmationUrl, ctx });
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a test subscription intent
|
|
59
|
+
* Generates Stripe-shaped subscription + customer.subscription.created event
|
|
60
|
+
*
|
|
61
|
+
* A declined checkout mirrors what a real provider does: the subscription is
|
|
62
|
+
* created in a dunning state (past_due → suspended) and its first invoice fails.
|
|
63
|
+
*/
|
|
64
|
+
async function createSubscriptionIntent({ uid, orderId, product, frequency, trial, discount, declined, abandoned, confirmationUrl, ctx }) {
|
|
65
|
+
// Generate IDs
|
|
66
|
+
const timestamp = Date.now();
|
|
67
|
+
const sessionId = `_test-cs-${timestamp}`;
|
|
68
|
+
const subscriptionId = `_test-sub-${timestamp}`;
|
|
69
|
+
const eventId = `_test-evt-${timestamp}`;
|
|
70
|
+
const invoiceId = `_test-in-${timestamp}`;
|
|
71
|
+
|
|
72
|
+
// Map frequency to Stripe interval
|
|
73
|
+
const FREQUENCY_TO_INTERVAL = { annually: 'year', monthly: 'month', weekly: 'week', daily: 'day' };
|
|
74
|
+
const FREQUENCY_TO_PERIOD = { annually: 365 * 86400, monthly: 30 * 86400, weekly: 7 * 86400, daily: 1 * 86400 };
|
|
75
|
+
const interval = FREQUENCY_TO_INTERVAL[frequency] || 'month';
|
|
76
|
+
|
|
77
|
+
// Build timestamps
|
|
78
|
+
const now = Math.floor(timestamp / 1000);
|
|
79
|
+
const periodEnd = now + (FREQUENCY_TO_PERIOD[frequency] || 30 * 86400);
|
|
80
|
+
|
|
81
|
+
// Build Stripe-shaped subscription object.
|
|
82
|
+
// Prefer the real Stripe product ID if configured; otherwise use a sentinel of the
|
|
83
|
+
// form "_test_<id>" that the Stripe resolver recognizes and maps back to product.id.
|
|
84
|
+
// This lets the test provider work in brands that haven't wired up Stripe yet.
|
|
85
|
+
const planProductId = product.stripe?.productId || `_test_${product.id}`;
|
|
86
|
+
|
|
87
|
+
const subscription = {
|
|
88
|
+
id: subscriptionId,
|
|
89
|
+
object: 'subscription',
|
|
90
|
+
status: declined ? 'past_due' : (trial && product.trial?.days ? 'trialing' : 'active'),
|
|
91
|
+
metadata: { uid, orderId },
|
|
92
|
+
plan: { product: planProductId, interval },
|
|
93
|
+
current_period_end: periodEnd,
|
|
94
|
+
current_period_start: now,
|
|
95
|
+
start_date: now,
|
|
96
|
+
cancel_at_period_end: false,
|
|
97
|
+
cancel_at: null,
|
|
98
|
+
canceled_at: null,
|
|
99
|
+
trial_start: null,
|
|
100
|
+
trial_end: null,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Add trial dates if applicable (a declined checkout claims no trial — nothing cleared)
|
|
104
|
+
if (!declined && trial && product.trial?.days) {
|
|
105
|
+
subscription.trial_start = now;
|
|
106
|
+
subscription.trial_end = now + (product.trial.days * 86400);
|
|
107
|
+
subscription.current_period_end = subscription.trial_end;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// What the coupon leaves on the first invoice — Stripe reports that reduced
|
|
111
|
+
// amount back on its own events, so the fabricated ones carry it too. The route
|
|
112
|
+
// owns the confirmation URL's amount; this is the PAYLOAD's
|
|
113
|
+
// ([#239](https://github.com/Omega-JS-Stack/omega/issues/239)). The renewal keeps
|
|
114
|
+
// the full config price — a 'once' coupon only touches the first charge.
|
|
115
|
+
const firstCharge = discountCodes.applyToAmount(product.prices?.[frequency] || 0, discount);
|
|
116
|
+
|
|
117
|
+
if (discount) {
|
|
118
|
+
subscription.discount = buildStripeDiscount(discount);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Build Stripe-shaped event
|
|
122
|
+
const event = {
|
|
123
|
+
id: eventId,
|
|
124
|
+
type: 'customer.subscription.created',
|
|
125
|
+
data: { object: subscription },
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
ctx.log(`Test subscription intent: sessionId=${sessionId}, subscriptionId=${subscriptionId}, eventId=${eventId}, trial=${!!subscription.trial_start}, declined=${!!declined}, abandoned=${!!abandoned}, discount=${discount?.code || 'none'}, firstCharge=${firstCharge}`);
|
|
129
|
+
|
|
130
|
+
// Auto-fire webhook
|
|
131
|
+
if (abandoned) {
|
|
132
|
+
ctx.log(`Test subscription intent: abandoned — no webhook fired, the intent stays pending`);
|
|
133
|
+
} else if (declined) {
|
|
134
|
+
// The failed invoice names the subscription, and the pipeline resolves that
|
|
135
|
+
// subscription from the order the FIRST event wrote — so the two events go out
|
|
136
|
+
// in order, not at once.
|
|
137
|
+
fireWebhook({ event, ctx })
|
|
138
|
+
.then(() => waitForOrder({ orderId, ctx }))
|
|
139
|
+
.then(() => fireWebhook({
|
|
140
|
+
event: buildFailedInvoiceEvent({
|
|
141
|
+
uid,
|
|
142
|
+
orderId,
|
|
143
|
+
invoiceId,
|
|
144
|
+
eventId: `${eventId}-invoice`,
|
|
145
|
+
amountDue: Math.round(firstCharge * 100),
|
|
146
|
+
billingReason: 'subscription_create',
|
|
147
|
+
subscriptionId,
|
|
148
|
+
}),
|
|
149
|
+
ctx,
|
|
150
|
+
}));
|
|
151
|
+
} else {
|
|
152
|
+
fireWebhook({ event, ctx });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
id: sessionId,
|
|
157
|
+
url: confirmationUrl,
|
|
158
|
+
raw: { id: sessionId, object: 'checkout.session', subscription: subscriptionId },
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Create a test one-time payment intent
|
|
164
|
+
* Generates Stripe-shaped checkout session + checkout.session.completed event
|
|
165
|
+
*
|
|
166
|
+
* A declined checkout still creates the session — the payment is what fails, so
|
|
167
|
+
* a failed manual invoice goes out in place of the completed session.
|
|
168
|
+
*/
|
|
169
|
+
async function createOneTimeIntent({ uid, orderId, product, productId, discount, declined, abandoned, confirmationUrl, ctx }) {
|
|
170
|
+
// Validate that a price exists
|
|
171
|
+
if (!product.prices?.once) {
|
|
172
|
+
throw new Error(`No one-time price configured for ${product.id}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Generate IDs
|
|
176
|
+
const timestamp = Date.now();
|
|
177
|
+
const sessionId = `_test-cs-${timestamp}`;
|
|
178
|
+
const invoiceId = `_test-in-${timestamp}`;
|
|
179
|
+
const eventId = `_test-evt-${timestamp}`;
|
|
180
|
+
|
|
181
|
+
// The single charge this purchase makes, after the coupon Stripe would have
|
|
182
|
+
// applied at its own checkout ([#239](https://github.com/Omega-JS-Stack/omega/issues/239))
|
|
183
|
+
const firstCharge = discountCodes.applyToAmount(product.prices.once || 0, discount);
|
|
184
|
+
|
|
185
|
+
// Build Stripe-shaped checkout session object
|
|
186
|
+
const session = {
|
|
187
|
+
id: sessionId,
|
|
188
|
+
object: 'checkout.session',
|
|
189
|
+
mode: 'payment',
|
|
190
|
+
status: 'complete',
|
|
191
|
+
payment_status: 'paid',
|
|
192
|
+
metadata: { uid, orderId, productId },
|
|
193
|
+
amount_total: Math.round(firstCharge * 100),
|
|
194
|
+
currency: 'usd',
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// What Stripe's session reports about the money the coupon took off
|
|
198
|
+
if (discount) {
|
|
199
|
+
session.total_details = {
|
|
200
|
+
amount_discount: Math.round(((product.prices.once || 0) - firstCharge) * 100),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Build Stripe-shaped event
|
|
205
|
+
const event = declined
|
|
206
|
+
? buildFailedInvoiceEvent({
|
|
207
|
+
uid,
|
|
208
|
+
orderId,
|
|
209
|
+
productId,
|
|
210
|
+
invoiceId,
|
|
211
|
+
eventId,
|
|
212
|
+
amountDue: session.amount_total,
|
|
213
|
+
billingReason: 'manual',
|
|
214
|
+
})
|
|
215
|
+
: {
|
|
216
|
+
id: eventId,
|
|
217
|
+
type: 'checkout.session.completed',
|
|
218
|
+
data: { object: session },
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
ctx.log(`Test one-time intent: sessionId=${sessionId}, eventId=${eventId}, productId=${productId}, declined=${!!declined}, abandoned=${!!abandoned}, discount=${discount?.code || 'none'}, firstCharge=${firstCharge}`);
|
|
222
|
+
|
|
223
|
+
// Auto-fire webhook
|
|
224
|
+
if (abandoned) {
|
|
225
|
+
ctx.log(`Test one-time intent: abandoned — no webhook fired, the intent stays pending`);
|
|
226
|
+
} else {
|
|
227
|
+
fireWebhook({ event, ctx });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
id: sessionId,
|
|
232
|
+
url: confirmationUrl,
|
|
233
|
+
raw: { id: sessionId, object: 'checkout.session', mode: 'payment' },
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Build the Stripe-shaped discount a coupon leaves on the resource
|
|
239
|
+
*
|
|
240
|
+
* Stripe attaches the coupon it applied to the subscription, so a reader of the
|
|
241
|
+
* webhook can see WHICH code moved the amount. `amount` is dollars on our side and
|
|
242
|
+
* cents on Stripe's, the same as `percent` → `percent_off`.
|
|
243
|
+
*
|
|
244
|
+
* @param {object} discount - Validated discount from discount-codes.validate()
|
|
245
|
+
* @returns {object} Stripe-shaped discount object
|
|
246
|
+
*/
|
|
247
|
+
function buildStripeDiscount(discount) {
|
|
248
|
+
return {
|
|
249
|
+
id: `_test-di-${discount.code}`,
|
|
250
|
+
object: 'discount',
|
|
251
|
+
coupon: {
|
|
252
|
+
id: `_test-coupon-${discount.code}`,
|
|
253
|
+
object: 'coupon',
|
|
254
|
+
name: discount.code,
|
|
255
|
+
percent_off: discount.percent || null,
|
|
256
|
+
amount_off: discount.amount ? Math.round(discount.amount * 100) : null,
|
|
257
|
+
duration: discount.duration || 'once',
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Build a Stripe-shaped invoice.payment_failed event
|
|
264
|
+
*
|
|
265
|
+
* `billingReason` is what the webhook parser reads to route the event: a
|
|
266
|
+
* 'subscription*' reason (with a subscription) resolves to the subscription,
|
|
267
|
+
* anything else ('manual') to the one-time invoice itself.
|
|
268
|
+
*/
|
|
269
|
+
function buildFailedInvoiceEvent({ uid, orderId, productId, invoiceId, eventId, amountDue, billingReason, subscriptionId }) {
|
|
270
|
+
const invoice = {
|
|
271
|
+
id: invoiceId,
|
|
272
|
+
object: 'invoice',
|
|
273
|
+
billing_reason: billingReason,
|
|
274
|
+
amount_due: amountDue,
|
|
275
|
+
amount_paid: 0,
|
|
276
|
+
status: 'open',
|
|
277
|
+
metadata: { uid, orderId },
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Only the one-time side carries a product on the invoice — the subscription
|
|
281
|
+
// side resolves its product from the subscription
|
|
282
|
+
if (productId) {
|
|
283
|
+
invoice.metadata.productId = productId;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (subscriptionId) {
|
|
287
|
+
invoice.parent = {
|
|
288
|
+
type: 'subscription_details',
|
|
289
|
+
subscription_details: {
|
|
290
|
+
subscription: subscriptionId,
|
|
291
|
+
metadata: { uid, orderId },
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
id: eventId,
|
|
298
|
+
type: 'invoice.payment_failed',
|
|
299
|
+
data: { object: invoice },
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Wait for the order the first webhook writes
|
|
305
|
+
*
|
|
306
|
+
* Only the declined subscription path needs this: the pipeline answers the failed
|
|
307
|
+
* invoice's subscription lookup out of payments-orders, so the invoice event that
|
|
308
|
+
* arrives before the order exists resolves against nothing.
|
|
309
|
+
*/
|
|
310
|
+
async function waitForOrder({ orderId, ctx }) {
|
|
311
|
+
const ref = ctx.Manager.libraries.admin.firestore().doc(`payments-orders/${orderId}`);
|
|
312
|
+
|
|
313
|
+
for (let attempt = 0; attempt < ORDER_WAIT_ATTEMPTS; attempt++) {
|
|
314
|
+
const doc = await ref.get();
|
|
315
|
+
|
|
316
|
+
if (doc.exists) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
await powertools.wait(ORDER_WAIT_INTERVAL);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
ctx.warn(`Test provider: payments-orders/${orderId} never landed after ${(ORDER_WAIT_ATTEMPTS * ORDER_WAIT_INTERVAL) / 1000}s — firing the failed invoice anyway, it will resolve against whatever state exists`);
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Fire-and-forget webhook to trigger the full pipeline
|
|
329
|
+
* Returns the request so a caller that must order two events can chain them
|
|
330
|
+
*/
|
|
331
|
+
function fireWebhook({ event, ctx }) {
|
|
332
|
+
const webhookUrl = `${ctx.Manager.getApiUrl()}/omega/payments/webhook?provider=test&key=${env.get('OMEGA_WEBHOOK_KEY')}`;
|
|
333
|
+
return fetch(webhookUrl, {
|
|
334
|
+
method: 'POST',
|
|
335
|
+
response: 'json',
|
|
336
|
+
body: event,
|
|
337
|
+
timeout: 60000,
|
|
338
|
+
}).catch((e) => {
|
|
339
|
+
ctx.log(`Test provider auto-webhook failed: ${e.message}`);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const loadProvider = require('../../../libraries/load-provider.js');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* POST /payments/plan
|
|
6
|
+
* Moves the authenticated user's LIVE subscription to a different plan — another
|
|
7
|
+
* product, another billing frequency, or both — without cancelling and
|
|
8
|
+
* re-subscribing. Delegates to the provider (e.g., Stripe) to swap the price on
|
|
9
|
+
* the existing subscription. The resulting webhook triggers the Firestore pipeline
|
|
10
|
+
* which updates subscription state and fires the plan-changed transition handler.
|
|
11
|
+
* This route writes no subscription state of its own, exactly like cancel.
|
|
12
|
+
* Requires authentication.
|
|
13
|
+
*
|
|
14
|
+
* Cross-provider and CAPABILITY-GATED: a provider that can move a live
|
|
15
|
+
* subscription exports switchPlan(), one that cannot simply lacks the export, and
|
|
16
|
+
* the route refuses before dispatch rather than letting the caller discover it as
|
|
17
|
+
* a provider error ([#212](https://github.com/Omega-JS-Stack/omega/issues/212)).
|
|
18
|
+
*
|
|
19
|
+
* A switch NEVER grants, resets, or extends a trial (Ian 2026-08-14,
|
|
20
|
+
* [#237](https://github.com/Omega-JS-Stack/omega/issues/237)): a mid-trial switch
|
|
21
|
+
* carries the trial over — same original end date, new plan. The route writes no
|
|
22
|
+
* state, so each provider's switchPlan() is what preserves it through the swap.
|
|
23
|
+
*/
|
|
24
|
+
module.exports = async ({ ctx, Manager, user, settings }) => {
|
|
25
|
+
// Require authentication
|
|
26
|
+
if (!user.authenticated) {
|
|
27
|
+
return ctx.respond('Authentication required', { code: 401 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const uid = user.auth.uid;
|
|
31
|
+
const productId = settings.productId;
|
|
32
|
+
const frequency = settings.frequency;
|
|
33
|
+
|
|
34
|
+
// Require explicit confirmation
|
|
35
|
+
if (!settings.confirmed) {
|
|
36
|
+
return ctx.respond('Plan change must be confirmed', { code: 400 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const subscription = user.subscription;
|
|
40
|
+
|
|
41
|
+
// Require an active paid subscription — there is nothing to move otherwise
|
|
42
|
+
if (!subscription || subscription.status !== 'active' || subscription.product?.id === 'basic') {
|
|
43
|
+
ctx.log(`Plan change rejected: uid=${uid}, status=${subscription?.status}, product=${subscription?.product?.id}`);
|
|
44
|
+
return ctx.respond('No active paid subscription found', { code: 400 });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// A cancellation already in flight is a decision of its own. The providers
|
|
48
|
+
// swap the PRICE, never the schedule, so a switch here would land the caller
|
|
49
|
+
// on a new plan still set to end at period end — silently. Undo the
|
|
50
|
+
// cancellation first; that is the honest button ([#237]).
|
|
51
|
+
if (subscription.cancellation?.pending === true) {
|
|
52
|
+
ctx.log(`Plan change rejected: uid=${uid}, cancellation pending`);
|
|
53
|
+
return ctx.respond('Your subscription is scheduled to cancel. Undo the cancellation before changing plans.', {
|
|
54
|
+
code: 400,
|
|
55
|
+
additional: { code: 'cancellation-pending' },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Validate the target product against the brand's own config — the same
|
|
60
|
+
// resolution the intent route does, over the same products array
|
|
61
|
+
const product = (Manager.config.payment?.products || []).find(p => p.id === productId);
|
|
62
|
+
|
|
63
|
+
if (!product) {
|
|
64
|
+
ctx.log(`Plan change rejected: uid=${uid}, product "${productId}" not found (available: ${(Manager.config.payment?.products || []).map(p => p.id).join(', ')})`);
|
|
65
|
+
return ctx.respond(`Product '${productId}' not found`, { code: 400 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const productType = product.type || 'subscription';
|
|
69
|
+
|
|
70
|
+
// A subscription can only move to another subscription plan
|
|
71
|
+
if (productType !== 'subscription' || productId === 'basic') {
|
|
72
|
+
ctx.log(`Plan change rejected: uid=${uid}, target product=${productId} is type=${productType}`);
|
|
73
|
+
return ctx.respond(`Product '${productId}' is not a subscription plan`, { code: 400 });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The target has to be a plan the brand actually sells at that frequency —
|
|
77
|
+
// catching it here keeps a client-fault input from surfacing as a 500 out of
|
|
78
|
+
// the provider's price lookup
|
|
79
|
+
if (!product.prices?.[frequency]) {
|
|
80
|
+
ctx.log(`Plan change rejected: uid=${uid}, no ${frequency} price for product=${productId}`);
|
|
81
|
+
return ctx.respond(`Product '${productId}' is not sold ${frequency}`, { code: 400 });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The switch has to BE a switch: same product at the same frequency is a
|
|
85
|
+
// no-op, and dispatching one costs a real provider call and a real webhook.
|
|
86
|
+
// The guard lives HERE and never leans on the client's own filtering — the
|
|
87
|
+
// modal's filter silently misses when the recorded frequency is absent
|
|
88
|
+
// ([#236]), and the trial the no-op switch used to end was real.
|
|
89
|
+
//
|
|
90
|
+
// An UNRECORDED current frequency refuses the whole product: `'monthly' ===
|
|
91
|
+
// undefined` is false, so a pair comparison alone would sail the no-op through
|
|
92
|
+
// to a real provider call — the exact state the QA repro was in. Both cadences
|
|
93
|
+
// are refused until the backend records one, mirroring the modal's own
|
|
94
|
+
// conservatism ([#236]).
|
|
95
|
+
const currentProductId = subscription.product?.id;
|
|
96
|
+
const currentFrequency = subscription.payment?.frequency;
|
|
97
|
+
|
|
98
|
+
if (productId === currentProductId && (!currentFrequency || frequency === currentFrequency)) {
|
|
99
|
+
ctx.log(`Plan change rejected: uid=${uid}, already on product=${productId}, frequency=${frequency} (current frequency: ${currentFrequency || 'unrecorded'})`);
|
|
100
|
+
return ctx.respond('You are already on that plan', {
|
|
101
|
+
code: 400,
|
|
102
|
+
additional: { code: 'already-on-plan' },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const provider = subscription.payment?.provider;
|
|
107
|
+
const resourceId = subscription.payment?.resourceId;
|
|
108
|
+
|
|
109
|
+
if (!provider || !resourceId) {
|
|
110
|
+
ctx.log(`Plan change rejected: uid=${uid}, missing provider=${provider} or resourceId=${resourceId}`);
|
|
111
|
+
return ctx.respond('Subscription payment details not found', { code: 400 });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Load the provider module
|
|
115
|
+
let providerModule;
|
|
116
|
+
try {
|
|
117
|
+
providerModule = loadProvider(path.join(__dirname, 'providers'), provider);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return ctx.respond(`Unknown provider: ${provider}`, { code: 400 });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The capability gate. A missing export is the provider saying it cannot do
|
|
123
|
+
// this at all — a CLIENT fault to be branched on, not an outage to retry, so
|
|
124
|
+
// the code rides the response properties where every 4xx carries its
|
|
125
|
+
// machine-readable half, and the sentence points at the fallback that works.
|
|
126
|
+
if (typeof providerModule.switchPlan !== 'function') {
|
|
127
|
+
ctx.log(`Plan change not supported: uid=${uid}, provider=${provider}`);
|
|
128
|
+
return ctx.respond('Your payment provider cannot change plans here. Please use the billing portal to manage your subscription.', {
|
|
129
|
+
code: 400,
|
|
130
|
+
additional: { code: 'not-supported-by-provider' },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Swap the plan via the provider
|
|
135
|
+
try {
|
|
136
|
+
await providerModule.switchPlan({ resourceId, uid, subscription, product, productType, frequency, ctx });
|
|
137
|
+
} catch (e) {
|
|
138
|
+
// The provider's own words stay in the logs — a client gets one neutral
|
|
139
|
+
// sentence, never an SDK message naming our internals ([#212]).
|
|
140
|
+
ctx.error(`Failed to change plan via ${provider}: uid=${uid}, sub=${resourceId}, product=${productId}, frequency=${frequency}, error=${e.message}`);
|
|
141
|
+
return ctx.respond('We could not change your plan right now. Please try again shortly.', { code: 500 });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
ctx.log(`Plan change requested: uid=${uid}, provider=${provider}, sub=${resourceId}, ${currentProductId}/${currentFrequency} → ${productId}/${frequency}`);
|
|
145
|
+
|
|
146
|
+
return ctx.respond({ success: true });
|
|
147
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chargebee plan-switch provider
|
|
3
|
+
* Moves a live subscription onto a different item price.
|
|
4
|
+
*
|
|
5
|
+
* update_for_items REPLACES the subscription's items with the ones sent, so the
|
|
6
|
+
* target item price is the whole payload. The item price id is the same
|
|
7
|
+
* deterministic `{itemId}-{frequency}` the checkout builds, so a plan switch
|
|
8
|
+
* lands on exactly the price a fresh subscription would have. Chargebee then
|
|
9
|
+
* sends subscription_changed, which the existing pipeline processes into the
|
|
10
|
+
* plan-changed transition.
|
|
11
|
+
*/
|
|
12
|
+
module.exports = {
|
|
13
|
+
/**
|
|
14
|
+
* Switch a Chargebee subscription to another plan
|
|
15
|
+
*
|
|
16
|
+
* @param {object} options
|
|
17
|
+
* @param {string} options.resourceId - Chargebee subscription ID
|
|
18
|
+
* @param {string} options.uid - User's UID (for logging)
|
|
19
|
+
* @param {object} options.product - Target product object from config
|
|
20
|
+
* @param {string} options.frequency - Target billing frequency ('monthly', 'annually', …)
|
|
21
|
+
* @param {object} options.ctx - Assistant instance for logging
|
|
22
|
+
*/
|
|
23
|
+
async switchPlan({ resourceId, uid, product, frequency, ctx }) {
|
|
24
|
+
const ChargebeeLib = require('../../../../libraries/payment/providers/chargebee.js');
|
|
25
|
+
ChargebeeLib.init();
|
|
26
|
+
|
|
27
|
+
const chargebeeItemId = product.chargebee?.itemId;
|
|
28
|
+
|
|
29
|
+
if (!chargebeeItemId) {
|
|
30
|
+
throw new Error(`No Chargebee item ID configured for product ${product.id}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Deterministic item price ID: {itemId}-{frequency} — the checkout's own convention
|
|
34
|
+
const itemPriceId = `${chargebeeItemId}-${frequency}`;
|
|
35
|
+
|
|
36
|
+
// A switch never grants, resets, or extends a trial ([#237]): a subscription
|
|
37
|
+
// still IN its trial keeps the end date it already had, restated on the
|
|
38
|
+
// update so the new item price cannot reopen or close the trial.
|
|
39
|
+
//
|
|
40
|
+
// The date comes from the LIVE subscription, never from our own user doc —
|
|
41
|
+
// exactly like the Stripe branch reads the item it is replacing. A user doc
|
|
42
|
+
// that lags the provider would otherwise make this route MOVE the trial it
|
|
43
|
+
// is meant to preserve, or restate an end date Chargebee has already passed
|
|
44
|
+
// (a 400). A trial that is over is left alone (encodeFormData drops the null).
|
|
45
|
+
const current = await ChargebeeLib.request(`/subscriptions/${resourceId}`);
|
|
46
|
+
const live = current.subscription || current;
|
|
47
|
+
const trialEnd = live.status === 'in_trial' ? (live.trial_end || null) : null;
|
|
48
|
+
|
|
49
|
+
await ChargebeeLib.request(`/subscriptions/${resourceId}/update_for_items`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
body: {
|
|
52
|
+
subscription_items: {
|
|
53
|
+
item_price_id: [itemPriceId],
|
|
54
|
+
quantity: [1],
|
|
55
|
+
},
|
|
56
|
+
trial_end: trialEnd,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
ctx.log(`Chargebee plan switched: sub=${resourceId}, uid=${uid}, itemPriceId=${itemPriceId} (${product.id}/${frequency}), trialEnd=${trialEnd || 'none'}`);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PayPal plan-switch provider
|
|
3
|
+
* Moves a live subscription onto a different plan via revise.
|
|
4
|
+
*
|
|
5
|
+
* Unlike uncancel — which PayPal has no verb for at all — revise is PayPal's
|
|
6
|
+
* first-class plan change: it swaps the plan on an ACTIVE subscription and
|
|
7
|
+
* prorates by its own rules. PayPal then sends BILLING.SUBSCRIPTION.UPDATED,
|
|
8
|
+
* which the existing pipeline processes into the plan-changed transition.
|
|
9
|
+
*
|
|
10
|
+
* The plan id is resolved from config at runtime (product + frequency → the
|
|
11
|
+
* matching active PayPal plan), the same resolution the checkout uses, so a
|
|
12
|
+
* switch lands on exactly the plan a fresh subscription would have.
|
|
13
|
+
*
|
|
14
|
+
* Trials ([#237]): revise takes no trial parameter, so unlike Stripe and
|
|
15
|
+
* Chargebee there is nothing to restate here. PayPal exposes no trial dates at
|
|
16
|
+
* all — the library derives them from the PLAN's `TRIAL` billing cycle anchored
|
|
17
|
+
* to the subscription's original `start_time` — so a revise cannot extend a
|
|
18
|
+
* trial past what it would have been from day one. It can still shift the end
|
|
19
|
+
* date when the target plan's trial is a different LENGTH; a brand that prices
|
|
20
|
+
* unequal trials on PayPal needs the plans reconciled, not this route patched.
|
|
21
|
+
*
|
|
22
|
+
* Which is why the plan lookup is asked for a TWIN ([#761]): a trial product
|
|
23
|
+
* carries one plan with the TRIAL cycle and one without, so a switcher still
|
|
24
|
+
* inside a trial has to land on the trial twin to carry that trial over, and a
|
|
25
|
+
* PAYING switcher has to land on the twin with no free cycle on it — landing
|
|
26
|
+
* a paying subscriber on a trial plan would read them as trialing everywhere
|
|
27
|
+
* the backend asks.
|
|
28
|
+
*/
|
|
29
|
+
const isTrialing = require('../../cancel/_is-trialing.js');
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
/**
|
|
33
|
+
* Switch a PayPal subscription to another plan
|
|
34
|
+
*
|
|
35
|
+
* @param {object} options
|
|
36
|
+
* @param {string} options.resourceId - PayPal subscription ID (e.g., 'I-xxx')
|
|
37
|
+
* @param {string} options.uid - User's UID (for logging)
|
|
38
|
+
* @param {object} options.subscription - The user's current subscription (picks the trial twin)
|
|
39
|
+
* @param {object} options.product - Target product object from config
|
|
40
|
+
* @param {string} options.frequency - Target billing frequency ('monthly', 'annually', …)
|
|
41
|
+
* @param {object} options.ctx - Assistant instance for logging
|
|
42
|
+
*/
|
|
43
|
+
async switchPlan({ resourceId, uid, subscription, product, frequency, ctx }) {
|
|
44
|
+
const PayPalLib = require('../../../../libraries/payment/providers/paypal.js');
|
|
45
|
+
|
|
46
|
+
// The twin is a property of the TARGET product: a trial-less product has
|
|
47
|
+
// no trial twin to carry a trial over to, so a switcher mid-trial lands on
|
|
48
|
+
// its one plan (the trial ends, as the header says it may) instead of a
|
|
49
|
+
// lookup for a plan no manage run will ever mint
|
|
50
|
+
const carryTrial = isTrialing(subscription) && !!product.trial?.days;
|
|
51
|
+
const planId = await PayPalLib.resolvePlanId(product, frequency, carryTrial);
|
|
52
|
+
|
|
53
|
+
await PayPalLib.request(`/v1/billing/subscriptions/${resourceId}/revise`, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
plan_id: planId,
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
ctx.log(`PayPal plan switched: sub=${resourceId}, uid=${uid}, plan=${planId} (${product.id}/${frequency})`);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stripe plan-switch provider
|
|
3
|
+
* Moves a live subscription onto a different price, prorating the difference.
|
|
4
|
+
*
|
|
5
|
+
* Stripe swaps plans by updating the subscription's ITEM: the existing item's id
|
|
6
|
+
* has to be sent alongside the new price, or Stripe adds a second item instead of
|
|
7
|
+
* replacing the first. proration_behavior 'create_prorations' credits the unused
|
|
8
|
+
* part of the current period and charges the new plan pro rata on the next
|
|
9
|
+
* invoice. Stripe then sends customer.subscription.updated, which the existing
|
|
10
|
+
* pipeline processes into the plan-changed transition.
|
|
11
|
+
*/
|
|
12
|
+
module.exports = {
|
|
13
|
+
/**
|
|
14
|
+
* Switch a Stripe subscription to another plan
|
|
15
|
+
*
|
|
16
|
+
* @param {object} options
|
|
17
|
+
* @param {string} options.resourceId - Stripe subscription ID (e.g., 'sub_xxx')
|
|
18
|
+
* @param {string} options.uid - User's UID (for logging)
|
|
19
|
+
* @param {object} options.product - Target product object from config
|
|
20
|
+
* @param {string} options.productType - Target product type ('subscription')
|
|
21
|
+
* @param {string} options.frequency - Target billing frequency ('monthly', 'annually', …)
|
|
22
|
+
* @param {object} options.ctx - Assistant instance for logging
|
|
23
|
+
*/
|
|
24
|
+
async switchPlan({ resourceId, uid, product, productType, frequency, ctx }) {
|
|
25
|
+
const StripeLib = require('../../../../libraries/payment/providers/stripe.js');
|
|
26
|
+
const stripe = StripeLib.init();
|
|
27
|
+
|
|
28
|
+
const priceId = await StripeLib.resolvePriceId(product, productType, frequency);
|
|
29
|
+
|
|
30
|
+
// The item being replaced — read from the live subscription, never assumed
|
|
31
|
+
const current = await stripe.subscriptions.retrieve(resourceId);
|
|
32
|
+
const itemId = current.items?.data?.[0]?.id;
|
|
33
|
+
|
|
34
|
+
if (!itemId) {
|
|
35
|
+
throw new Error(`Stripe subscription ${resourceId} has no subscription item to update`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const update = {
|
|
39
|
+
items: [{ id: itemId, price: priceId }],
|
|
40
|
+
proration_behavior: 'create_prorations',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// A switch never grants, resets, or extends a trial ([#237]): a subscription
|
|
44
|
+
// still IN its trial keeps the end date it already had, sent back to Stripe
|
|
45
|
+
// explicitly so the carry-over is this route's statement rather than a
|
|
46
|
+
// default we inherit. A trial that already ended is left alone — Stripe
|
|
47
|
+
// rejects a trial_end in the past, and its dates stay on the object anyway.
|
|
48
|
+
const nowUNIX = Math.floor(Date.now() / 1000);
|
|
49
|
+
const trialing = current.status === 'trialing' && current.trial_end > nowUNIX;
|
|
50
|
+
|
|
51
|
+
if (trialing) {
|
|
52
|
+
update.trial_end = current.trial_end;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await stripe.subscriptions.update(resourceId, update);
|
|
56
|
+
|
|
57
|
+
ctx.log(`Stripe plan switched: sub=${resourceId}, uid=${uid}, item=${itemId}, price=${priceId} (${product.id}/${frequency}), trialEnd=${trialing ? current.trial_end : 'none'}`);
|
|
58
|
+
},
|
|
59
|
+
};
|