@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,945 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: payment analytics event resolution
|
|
3
|
+
* Unit tests for resolvePaymentEvent() — the pure resolver that decides what a
|
|
4
|
+
* webhook is worth tracking as.
|
|
5
|
+
*
|
|
6
|
+
* The renewal path is the one that only the event type can decide: a recurring
|
|
7
|
+
* charge produces no transition (active → active, same product), so a provider
|
|
8
|
+
* whose renewal event is missing from isPaymentEvent() silently reports zero
|
|
9
|
+
* recurring revenue. Every provider's renewal string is pinned here against its
|
|
10
|
+
* own webhook parser.
|
|
11
|
+
*
|
|
12
|
+
* Since [#385](https://github.com/Omega-JS-Stack/omega/issues/385) the resolver
|
|
13
|
+
* also names the CANONICAL catalog event each transition is — the name every
|
|
14
|
+
* provider's dialect is derived from — and covers the two outcomes it never
|
|
15
|
+
* mapped: a refund and a cancellation that took effect.
|
|
16
|
+
*
|
|
17
|
+
* [#407](https://github.com/Omega-JS-Stack/omega/issues/407) added the four
|
|
18
|
+
* subscription lifecycle moments that were still dark or mislabeled: a plan
|
|
19
|
+
* change, an uncancel, a trial that ended unpaid, and the first real charge after
|
|
20
|
+
* a trial — which the generic renewal branch used to swallow. The last two turn on
|
|
21
|
+
* the PRIOR state's term, so those cases pass a `before` the way the webhook does.
|
|
22
|
+
*
|
|
23
|
+
* [#414](https://github.com/Omega-JS-Stack/omega/issues/414) closed the two holes
|
|
24
|
+
* that left: a cancellation INSIDE the trial term is the trial's outcome on every
|
|
25
|
+
* provider, not paid churn, and a degraded payload carrying no term at all is not
|
|
26
|
+
* a trial when the subscription is on record as already having had one.
|
|
27
|
+
*/
|
|
28
|
+
const analytics = require('../../../dist/manager/events/firestore/payments-webhooks/analytics.js');
|
|
29
|
+
const paypalProvider = require('../../../dist/manager/routes/payments/webhook/providers/paypal.js');
|
|
30
|
+
const chargebeeProvider = require('../../../dist/manager/routes/payments/webhook/providers/chargebee.js');
|
|
31
|
+
const stripeProvider = require('../../../dist/manager/routes/payments/webhook/providers/stripe.js');
|
|
32
|
+
const testProvider = require('../../../dist/manager/routes/payments/webhook/providers/test.js');
|
|
33
|
+
const defineCases = require('../../../dist/vendor/devkit/test/define-cases.js');
|
|
34
|
+
|
|
35
|
+
// A renewed paid subscription — no transition, money moved
|
|
36
|
+
function renewedSubscription() {
|
|
37
|
+
return {
|
|
38
|
+
product: { id: 'premium', name: 'Premium' },
|
|
39
|
+
status: 'active',
|
|
40
|
+
trial: { claimed: false },
|
|
41
|
+
payment: { frequency: 'monthly', price: 9.99, resourceId: '_test-sub-analytics' },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveRenewal(eventType) {
|
|
46
|
+
return analytics.resolvePaymentEvent('subscription', null, eventType, renewedSubscription(), {});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The trial term, as every provider reports it: while a trial runs, the
|
|
50
|
+
// subscription's paid-through date IS the trial's end, and only the first real
|
|
51
|
+
// charge moves it past.
|
|
52
|
+
const TRIAL_END_UNIX = 1750000000;
|
|
53
|
+
const NEXT_TERM_UNIX = TRIAL_END_UNIX + 30 * 24 * 60 * 60;
|
|
54
|
+
|
|
55
|
+
// The subscription DURING its trial — nothing has ever been charged
|
|
56
|
+
function subscriptionInTrial() {
|
|
57
|
+
return {
|
|
58
|
+
product: { id: 'premium', name: 'Premium' },
|
|
59
|
+
status: 'active',
|
|
60
|
+
trial: { claimed: true, expires: { timestampUNIX: TRIAL_END_UNIX } },
|
|
61
|
+
expires: { timestampUNIX: TRIAL_END_UNIX },
|
|
62
|
+
payment: { frequency: 'monthly', price: 9.99, resourceId: '_test-sub-analytics' },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The same subscription once a charge has carried it past the trial's end.
|
|
67
|
+
// `trial.claimed` is still true — it means "this subscription HAD a trial", never
|
|
68
|
+
// "it converted", which is exactly why the term has to be the tiebreaker.
|
|
69
|
+
function subscriptionAfterTrial() {
|
|
70
|
+
return { ...subscriptionInTrial(), expires: { timestampUNIX: NEXT_TERM_UNIX } };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// The STORED subscription once its conversion has been recorded — `trial.outcome`,
|
|
74
|
+
// the one stored conversion signal, stamped by whichever path saw the trial end
|
|
75
|
+
// ([#697](https://github.com/Omega-JS-Stack/omega/issues/697)). No provider payload
|
|
76
|
+
// ever carries it: the unified transforms produce `claimed` + `expires` only, so this
|
|
77
|
+
// shape is a `before`, never an `after`.
|
|
78
|
+
function subscriptionConverted() {
|
|
79
|
+
return { ...subscriptionAfterTrial(), trial: { ...subscriptionInTrial().trial, outcome: 'converted' } };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Chargebee's in-trial payload names no `current_term_end` at all — only
|
|
83
|
+
// `trial_end` and `next_billing_at` (test/fixtures/chargebee/subscription-in-trial.json)
|
|
84
|
+
// — so `expires` folds to the epoch while `trial.expires` is real. Every trial
|
|
85
|
+
// rule has to see THIS shape as a trial, or Chargebee's entire trial funnel is
|
|
86
|
+
// dark on a provider the framework fully supports.
|
|
87
|
+
function chargebeeSubscriptionInTrial() {
|
|
88
|
+
return { ...subscriptionInTrial(), expires: { timestampUNIX: 0 } };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// The degraded shape [#414] guards against: the provider API was unreachable, so
|
|
92
|
+
// the #222 stale fallback hands over the webhook's own body, and a Chargebee body
|
|
93
|
+
// that named no `current_term_end` would fold `expires` to the epoch for a PAID
|
|
94
|
+
// subscription exactly the way it does for a trialing one.
|
|
95
|
+
//
|
|
96
|
+
// DEFENSIVE, and deliberately so: no fixture in this repo produces an active paid
|
|
97
|
+
// Chargebee body with no `current_term_end` (every cancelled and active fixture
|
|
98
|
+
// carries one), so the premise is the issue's, not something reproduced here. What
|
|
99
|
+
// the fixture DOES prove is the part that matters — the two payloads are
|
|
100
|
+
// indistinguishable on their own (this one IS that one), so nothing but the state
|
|
101
|
+
// the subscription arrived FROM can ever say which is which.
|
|
102
|
+
function degradedPaidSubscription() {
|
|
103
|
+
return chargebeeSubscriptionInTrial();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// A completed one-time purchase, as the order carries it when the refund lands
|
|
107
|
+
function completedPurchase() {
|
|
108
|
+
return {
|
|
109
|
+
product: { id: 'lifetime', name: 'Lifetime' },
|
|
110
|
+
status: 'completed',
|
|
111
|
+
payment: { price: 99, resourceId: '_test-order-analytics' },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = defineCases({
|
|
116
|
+
description: 'Payment analytics event resolution (renewals per provider)',
|
|
117
|
+
type: 'group',
|
|
118
|
+
|
|
119
|
+
tests: [
|
|
120
|
+
{
|
|
121
|
+
name: 'stripe-renewal-tracks',
|
|
122
|
+
async run({ assert }) {
|
|
123
|
+
const resolved = resolveRenewal('invoice.payment_succeeded');
|
|
124
|
+
|
|
125
|
+
assert.ok(resolved, 'A Stripe renewal should resolve to a trackable event');
|
|
126
|
+
assert.equal(resolved.event, 'subscription_renew', 'Recurring revenue has its own canonical name');
|
|
127
|
+
assert.equal(resolved.reason, 'renewal', 'Reason should be renewal');
|
|
128
|
+
assert.equal(resolved.value, 9.99, 'Renewals track the full price');
|
|
129
|
+
assert.ok(stripeProvider.isSupported('invoice.payment_succeeded'), 'Stripe provider should accept the same event string');
|
|
130
|
+
|
|
131
|
+
// `invoice.paid` stays out of the INGEST on purpose: Stripe fires it for
|
|
132
|
+
// the same paid invoice, and this resolver has no per-invoice dedup, so
|
|
133
|
+
// ingesting both would report a renewal's revenue twice. The resolver
|
|
134
|
+
// still accepts the string for a brand that pins its endpoint to it —
|
|
135
|
+
// which is exactly why the ingest side has to be the one that decides.
|
|
136
|
+
assert.equal(stripeProvider.isSupported('invoice.paid'), false, 'Only one Stripe invoice event may be ingested per renewal');
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
{
|
|
141
|
+
name: 'paypal-renewal-tracks',
|
|
142
|
+
async run({ assert }) {
|
|
143
|
+
const resolved = resolveRenewal('PAYMENT.SALE.COMPLETED');
|
|
144
|
+
|
|
145
|
+
assert.ok(resolved, 'A PayPal renewal should resolve to a trackable event');
|
|
146
|
+
assert.equal(resolved.reason, 'renewal', 'Reason should be renewal');
|
|
147
|
+
assert.ok(paypalProvider.isSupported('PAYMENT.SALE.COMPLETED'), 'PayPal provider should accept the same event string');
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
{
|
|
152
|
+
name: 'chargebee-renewal-tracks',
|
|
153
|
+
async run({ assert }) {
|
|
154
|
+
const resolved = resolveRenewal('subscription_renewed');
|
|
155
|
+
|
|
156
|
+
assert.ok(resolved, 'A Chargebee renewal should resolve to a trackable event');
|
|
157
|
+
assert.equal(resolved.reason, 'renewal', 'Reason should be renewal');
|
|
158
|
+
assert.equal(resolved.isRecurring, true, 'Renewals are recurring revenue');
|
|
159
|
+
assert.ok(chargebeeProvider.isSupported('subscription_renewed'), 'Chargebee provider should accept the same event string');
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
{
|
|
164
|
+
name: 'win-back-tracks-a-purchase-not-a-renewal',
|
|
165
|
+
async run({ assert }) {
|
|
166
|
+
// A returning subscriber's checkout arrives on a payment event, so before
|
|
167
|
+
// the win-back transition existed this resolved as a renewal — recurring
|
|
168
|
+
// revenue reported for what is a fresh purchase
|
|
169
|
+
const resolved = analytics.resolvePaymentEvent('subscription', 'subscription-winback', 'invoice.payment_succeeded', renewedSubscription(), {});
|
|
170
|
+
|
|
171
|
+
assert.ok(resolved, 'A win-back should resolve to a trackable event');
|
|
172
|
+
assert.equal(resolved.event, 'purchase', 'A win-back is reported as a purchase');
|
|
173
|
+
assert.equal(resolved.reason, 'winback-purchase', 'Reason should be winback-purchase');
|
|
174
|
+
assert.equal(resolved.isRecurring, false, 'A win-back is a purchase, not recurring revenue');
|
|
175
|
+
assert.equal(resolved.value, 9.99, 'Value should be what the customer paid');
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
{
|
|
180
|
+
name: 'a-declined-checkout-tracks-nothing',
|
|
181
|
+
async run({ assert }) {
|
|
182
|
+
// No money moved — the transition exists to suppress the dunning email,
|
|
183
|
+
// not to report revenue
|
|
184
|
+
const resolved = analytics.resolvePaymentEvent('subscription', 'checkout-declined', 'invoice.payment_failed', renewedSubscription(), {});
|
|
185
|
+
|
|
186
|
+
assert.equal(resolved, null, 'A declined checkout should track nothing');
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
{
|
|
191
|
+
name: 'non-payment-event-tracks-nothing',
|
|
192
|
+
async run({ assert }) {
|
|
193
|
+
const resolved = resolveRenewal('subscription_changed');
|
|
194
|
+
|
|
195
|
+
assert.equal(resolved, null, 'An event where no money moved should track nothing');
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
{
|
|
200
|
+
// Inventory gap 4 (#328): a refund had no truth event at all, while GA4's
|
|
201
|
+
// native `refund` sat unused and the dashboard's user_refund_request counted a
|
|
202
|
+
// UI click as if it were the outcome.
|
|
203
|
+
name: 'a-subscription-refund-reports-what-went-back',
|
|
204
|
+
async run({ assert }) {
|
|
205
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
206
|
+
'subscription', 'payment-refunded', 'charge.refunded', renewedSubscription(), {},
|
|
207
|
+
{ amount: 4.99, currency: 'USD', reason: 'requested_by_customer' }
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
assert.ok(resolved, 'A refund should resolve to a trackable event');
|
|
211
|
+
assert.equal(resolved.event, 'refund', 'The canonical name is GA4\'s own refund event');
|
|
212
|
+
assert.equal(resolved.value, 4.99, 'A partial refund reports what was actually reversed, not the price');
|
|
213
|
+
assert.equal(resolved.isRecurring, false, 'Money going back is never recurring revenue');
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
{
|
|
218
|
+
name: 'a-one-time-refund-reports-the-refund-too',
|
|
219
|
+
async run({ assert }) {
|
|
220
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
221
|
+
'one-time', 'purchase-refunded', 'PAYMENT.CAPTURE.REFUNDED', completedPurchase(), {},
|
|
222
|
+
{ amount: 99, currency: 'USD', reason: null }
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
assert.equal(resolved.event, 'refund', 'Both categories refund through one canonical event');
|
|
226
|
+
assert.equal(resolved.value, 99);
|
|
227
|
+
assert.equal(resolved.productId, 'lifetime', 'The refund keeps the purchase it reversed');
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
{
|
|
232
|
+
name: 'a-refund-with-no-provider-amount-falls-back-to-the-price',
|
|
233
|
+
async run({ assert }) {
|
|
234
|
+
const unified = completedPurchase();
|
|
235
|
+
const resolved = analytics.resolvePaymentEvent('one-time', 'purchase-refunded', 'charge.refunded', unified, {}, null);
|
|
236
|
+
|
|
237
|
+
assert.equal(resolved.value, 99, 'A provider that names no amount still reports the reversal');
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
{
|
|
242
|
+
// Inventory gap 3 (#328): the cancellation transitions fired nothing.
|
|
243
|
+
name: 'a-cancellation-that-took-effect-is-tracked',
|
|
244
|
+
async run({ assert }) {
|
|
245
|
+
const resolved = analytics.resolvePaymentEvent('subscription', 'subscription-cancelled', 'customer.subscription.deleted', renewedSubscription(), {});
|
|
246
|
+
|
|
247
|
+
assert.ok(resolved, 'A cancellation should resolve to a trackable event');
|
|
248
|
+
assert.equal(resolved.event, 'subscription_cancel');
|
|
249
|
+
assert.equal(resolved.value, 9.99, 'The value is the subscription that ended');
|
|
250
|
+
assert.equal(resolved.isRecurring, false, 'Nothing was charged');
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
{
|
|
255
|
+
// [#414](https://github.com/Omega-JS-Stack/omega/issues/414): Chargebee ends a
|
|
256
|
+
// trial with no card on file by CANCELLING it, so every no-card lapse was
|
|
257
|
+
// booked as paid churn for a customer who had never paid a cent.
|
|
258
|
+
name: 'a-trial-cancelled-with-no-card-on-file-is-a-lapse-not-churn',
|
|
259
|
+
async run({ assert }) {
|
|
260
|
+
const cancelled = { ...chargebeeSubscriptionInTrial(), status: 'cancelled' };
|
|
261
|
+
|
|
262
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
263
|
+
'subscription', 'subscription-cancelled', 'subscription_cancelled', cancelled, {}, null, chargebeeSubscriptionInTrial()
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
assert.ok(resolved, 'a trial cancelled at its end should resolve to a trackable event');
|
|
267
|
+
assert.equal(resolved.event, 'trial_lapse', 'nobody churned: a trial ended without ever paying');
|
|
268
|
+
assert.notEqual(resolved.event, 'subscription_cancel', 'a customer who never paid cannot be paid churn');
|
|
269
|
+
assert.equal(resolved.value, 9.99, 'the value is the subscription that never started paying');
|
|
270
|
+
assert.equal(resolved.isRecurring, false, 'nothing was ever charged');
|
|
271
|
+
|
|
272
|
+
// The sweep derives this same id for a lapse, which collapses a race on the
|
|
273
|
+
// two platforms that key on an event id. It is NOT what keeps GA4 honest —
|
|
274
|
+
// GA4 deduplicates ecommerce on `transaction_id`, and a lapse is a CUSTOM
|
|
275
|
+
// event with no such dedupe ([#656]) — a cancelled subscription simply
|
|
276
|
+
// leaves the sweep's `status == active` candidate query.
|
|
277
|
+
assert.equal(
|
|
278
|
+
analytics.resolveEventId(resolved, { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_cancel' } } } }),
|
|
279
|
+
'trial_lapse._test-sub-analytics',
|
|
280
|
+
'the lapse keys on the SUBSCRIPTION however it was announced',
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
{
|
|
286
|
+
// The rule reads the unified TERM, never a provider name: a subscriber who
|
|
287
|
+
// simply quits mid-trial on Stripe or PayPal is the same story.
|
|
288
|
+
name: 'a-cancellation-inside-the-trial-is-a-lapse-on-every-provider',
|
|
289
|
+
async run({ assert }) {
|
|
290
|
+
const cancelled = { ...subscriptionInTrial(), status: 'cancelled' };
|
|
291
|
+
|
|
292
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
293
|
+
'subscription', 'subscription-cancelled', 'customer.subscription.deleted', cancelled, {}, null, subscriptionInTrial()
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
assert.equal(resolved.event, 'trial_lapse', 'quitting mid-trial is the trial\'s outcome, not a churned subscriber');
|
|
297
|
+
assert.equal(resolved.isRecurring, false);
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
{
|
|
302
|
+
name: 'a-cancellation-after-the-trial-is-still-paid-churn',
|
|
303
|
+
async run({ assert }) {
|
|
304
|
+
// The term is the only thing that moved: this subscriber converted and paid,
|
|
305
|
+
// and their cancellation is the churn number it always was.
|
|
306
|
+
const cancelled = { ...subscriptionAfterTrial(), status: 'cancelled' };
|
|
307
|
+
|
|
308
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
309
|
+
'subscription', 'subscription-cancelled', 'customer.subscription.deleted', cancelled, {}, null, subscriptionAfterTrial()
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
assert.equal(resolved.event, 'subscription_cancel', 'subscription_cancel means a PAID subscriber left');
|
|
313
|
+
assert.equal(resolved.value, 9.99, 'the value is the subscription that ended');
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
{
|
|
318
|
+
// The lapse rule reads the PRIOR state, and that state is a stored delivery
|
|
319
|
+
// like any other: a degraded one carries no term, which reads as a trial the
|
|
320
|
+
// subscription left long ago. So the cancelled payload's own evidence has the
|
|
321
|
+
// last word — a term that outlived the trial's end is a subscriber who paid.
|
|
322
|
+
name: 'a-paid-cancellation-is-churn-even-when-the-stored-record-arrived-degraded',
|
|
323
|
+
async run({ assert }) {
|
|
324
|
+
const cancelled = { ...subscriptionAfterTrial(), status: 'cancelled' };
|
|
325
|
+
|
|
326
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
327
|
+
'subscription', 'subscription-cancelled', 'customer.subscription.deleted', cancelled, {}, null, degradedPaidSubscription()
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
assert.equal(resolved.event, 'subscription_cancel', 'a thin record on file cannot turn paid churn into a trial');
|
|
331
|
+
assert.equal(resolved.value, 9.99);
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
{
|
|
336
|
+
// A trial that lapses tells its story ONCE, at the decline. The provider then
|
|
337
|
+
// exhausts dunning and cancels the same never-converted subscription, and that
|
|
338
|
+
// second webhook used to book paid churn at full price on top of the lapse —
|
|
339
|
+
// the very pollution the lapse rule exists to stop.
|
|
340
|
+
name: 'the-dunning-that-exhausts-a-lapsed-trial-tells-no-second-story',
|
|
341
|
+
async run({ assert }) {
|
|
342
|
+
// A1: the trial's charge declines. This is the outcome, and it is reported.
|
|
343
|
+
const suspended = { ...subscriptionInTrial(), status: 'suspended' };
|
|
344
|
+
const lapse = analytics.resolvePaymentEvent(
|
|
345
|
+
'subscription', 'payment-failed', 'invoice.payment_failed', suspended, {}, null, subscriptionInTrial()
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
assert.equal(lapse.event, 'trial_lapse', 'the decline is where a lapsed trial is told');
|
|
349
|
+
|
|
350
|
+
// A2: dunning runs out and the provider cancels what it was holding.
|
|
351
|
+
const cancelled = { ...subscriptionInTrial(), status: 'cancelled' };
|
|
352
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
353
|
+
'subscription', 'subscription-cancelled', 'customer.subscription.deleted', cancelled, {}, null, suspended
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
assert.equal(resolved, null, 'the outcome was already told at the decline');
|
|
357
|
+
|
|
358
|
+
// Chargebee suspends a mid-trial subscription with no term of its own, so the
|
|
359
|
+
// same sequence has to read alike through the epoch-expiry shape.
|
|
360
|
+
const chargebeeSuspended = { ...chargebeeSubscriptionInTrial(), status: 'suspended' };
|
|
361
|
+
const chargebeeCancelled = { ...chargebeeSubscriptionInTrial(), status: 'cancelled' };
|
|
362
|
+
|
|
363
|
+
assert.equal(
|
|
364
|
+
analytics.resolvePaymentEvent('subscription', 'subscription-cancelled', 'subscription_cancelled', chargebeeCancelled, {}, null, chargebeeSuspended),
|
|
365
|
+
null,
|
|
366
|
+
'a term that never existed never left the trial either',
|
|
367
|
+
);
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
{
|
|
372
|
+
// The boundary of the rule above: this subscriber converted and paid for
|
|
373
|
+
// months before dunning took them, so the cancellation IS the churn number.
|
|
374
|
+
name: 'the-dunning-that-exhausts-a-paying-subscriber-is-still-churn',
|
|
375
|
+
async run({ assert }) {
|
|
376
|
+
const suspended = { ...subscriptionAfterTrial(), status: 'suspended' };
|
|
377
|
+
const cancelled = { ...subscriptionAfterTrial(), status: 'cancelled' };
|
|
378
|
+
|
|
379
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
380
|
+
'subscription', 'subscription-cancelled', 'customer.subscription.deleted', cancelled, {}, null, suspended
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
assert.equal(resolved.event, 'subscription_cancel', 'a subscriber who paid and then lapsed on dunning is churn');
|
|
384
|
+
assert.equal(resolved.value, 9.99, 'the value is the subscription that ended');
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
{
|
|
389
|
+
name: 'requesting-a-cancellation-stays-event-less',
|
|
390
|
+
async run({ assert }) {
|
|
391
|
+
// Requesting one changes a SCHEDULE, not an outcome: the subscription is
|
|
392
|
+
// still active, no money moved, and it may never cancel at all.
|
|
393
|
+
const resolved = analytics.resolvePaymentEvent('subscription', 'cancellation-requested', 'customer.subscription.updated', renewedSubscription(), {});
|
|
394
|
+
|
|
395
|
+
assert.equal(resolved, null, 'cancellation-requested should track nothing');
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
{
|
|
400
|
+
// Gap 2 (#407): the uncancel was event-less too, which left a retention win
|
|
401
|
+
// completely dark. The pair is not symmetric on purpose.
|
|
402
|
+
name: 'withdrawing-a-cancellation-is-a-retention-win',
|
|
403
|
+
async run({ assert }) {
|
|
404
|
+
const resolved = analytics.resolvePaymentEvent('subscription', 'cancellation-removed', 'customer.subscription.updated', renewedSubscription(), {});
|
|
405
|
+
|
|
406
|
+
assert.ok(resolved, 'an uncancel should resolve to a trackable event');
|
|
407
|
+
assert.equal(resolved.event, 'subscription_uncancel');
|
|
408
|
+
assert.equal(resolved.value, 9.99, 'the value is the subscription that was kept');
|
|
409
|
+
assert.equal(resolved.isRecurring, false, 'nothing was charged');
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
{
|
|
414
|
+
// Gap 1 (#407): a plan change sent an order email and fired nothing, so
|
|
415
|
+
// every upgrade was invisible to every platform.
|
|
416
|
+
name: 'a-plan-change-reports-the-plan-it-came-from',
|
|
417
|
+
async run({ assert }) {
|
|
418
|
+
const before = {
|
|
419
|
+
...renewedSubscription(),
|
|
420
|
+
product: { id: 'starter', name: 'Starter' },
|
|
421
|
+
payment: { frequency: 'monthly', price: 4.99, resourceId: '_test-sub-analytics' },
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const resolved = analytics.resolvePaymentEvent('subscription', 'plan-changed', 'customer.subscription.updated', renewedSubscription(), {}, null, before);
|
|
425
|
+
|
|
426
|
+
assert.ok(resolved, 'a plan change should resolve to a trackable event');
|
|
427
|
+
assert.equal(resolved.event, 'subscription_plan_change');
|
|
428
|
+
assert.equal(resolved.value, 9.99, 'the value is the plan they moved TO');
|
|
429
|
+
assert.equal(resolved.isRecurring, false, 'no money moves at the switch itself');
|
|
430
|
+
|
|
431
|
+
const params = analytics.buildParams({ resolved, currency: 'USD', provider: 'stripe' });
|
|
432
|
+
|
|
433
|
+
assert.equal(params.items[0].item_id, 'premium', 'items[] is the new plan');
|
|
434
|
+
assert.equal(params.previous_item_id, 'starter', 'and the from-plan rides beside it');
|
|
435
|
+
assert.equal(params.previous_item_name, 'Starter');
|
|
436
|
+
assert.equal(params.previous_value, 4.99, 'so the direction of the switch is readable without a second event');
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
{
|
|
441
|
+
name: 'only-a-plan-change-carries-a-previous-plan',
|
|
442
|
+
async run({ assert }) {
|
|
443
|
+
const params = analytics.buildParams({ resolved: resolveRenewal('invoice.payment_succeeded'), currency: 'USD', provider: 'stripe' });
|
|
444
|
+
|
|
445
|
+
assert.equal('previous_item_id' in params, false, 'a renewal has no plan it came from');
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
{
|
|
450
|
+
// Gap 4 (#407): the conversion charge arrives exactly like a renewal — no
|
|
451
|
+
// transition, a payment event — so it resolved as subscription_renew and
|
|
452
|
+
// the funnel's most valuable step was undercounted.
|
|
453
|
+
name: 'the-first-charge-after-a-trial-is-a-conversion-not-a-renewal',
|
|
454
|
+
async run({ assert }) {
|
|
455
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
456
|
+
'subscription', null, 'invoice.payment_succeeded', subscriptionAfterTrial(), {}, null, subscriptionInTrial()
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
assert.ok(resolved, 'a converting trial should resolve to a trackable event');
|
|
460
|
+
assert.notEqual(resolved.event, 'subscription_renew', 'a conversion is not a routine renewal');
|
|
461
|
+
assert.equal(resolved.event, 'trial_convert');
|
|
462
|
+
assert.equal(resolved.reason, 'trial-converted');
|
|
463
|
+
assert.equal(resolved.value, 9.99, 'the first real charge is what they actually paid');
|
|
464
|
+
assert.equal(resolved.isRecurring, false, 'the FIRST payment is not recurring revenue');
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
{
|
|
469
|
+
// [#697](https://github.com/Omega-JS-Stack/omega/issues/697): live evidence
|
|
470
|
+
// (playground order 2315-1401-3544) had Stripe deliver
|
|
471
|
+
// `customer.subscription.updated` — trial over, term pushed out — a full hour
|
|
472
|
+
// BEFORE `invoice.payment_succeeded`. The stored doc had already left the
|
|
473
|
+
// trial by charge time, so the conversion booked as a month-two renewal. The
|
|
474
|
+
// two webhooks race in real time, so an arbitrary share of conversions
|
|
475
|
+
// mislabeled. Arrival order is no longer the signal.
|
|
476
|
+
name: 'a-conversion-is-a-conversion-whichever-webhook-lands-first',
|
|
477
|
+
async run({ assert }) {
|
|
478
|
+
// The prior state the racing `customer.subscription.updated` left behind: out
|
|
479
|
+
// of the trial already, on the very term this charge is paying for.
|
|
480
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
481
|
+
'subscription', null, 'invoice.payment_succeeded', subscriptionAfterTrial(), {}, null, subscriptionAfterTrial()
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
assert.ok(resolved, 'the conversion charge should still resolve to a trackable event');
|
|
485
|
+
assert.equal(resolved.event, 'trial_convert', 'a claimed trial with no recorded outcome has not had its first paid charge yet');
|
|
486
|
+
assert.equal(resolved.isRecurring, false, 'the FIRST payment is not recurring revenue, whatever order it arrived in');
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
{
|
|
491
|
+
name: 'the-renewal-behind-a-conversion-is-a-renewal-again',
|
|
492
|
+
async run({ assert }) {
|
|
493
|
+
// Month two: same trial.claimed, same payment event, same term on both
|
|
494
|
+
// sides — the racing conversion above is INDISTINGUISHABLE from this on
|
|
495
|
+
// dates alone, which is why the stored outcome is what tells them apart.
|
|
496
|
+
// The payload never carries the stamp; only the doc it arrives at does.
|
|
497
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
498
|
+
'subscription', null, 'invoice.payment_succeeded', subscriptionAfterTrial(), {}, null, subscriptionConverted()
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
assert.equal(resolved.event, 'subscription_renew', 'every charge after the conversion is an ordinary renewal');
|
|
502
|
+
assert.equal(resolved.isRecurring, true);
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
{
|
|
507
|
+
// The resolver decides the label and the pipeline decides the WRITE that makes
|
|
508
|
+
// the next charge a renewal, so both read one predicate rather than keeping a
|
|
509
|
+
// copy each ([#697]) — the same arrangement `isInsideTrial` has with the
|
|
510
|
+
// trial-lapse sweep.
|
|
511
|
+
name: 'the-conversion-the-pipeline-stamps-is-the-conversion-the-resolver-books',
|
|
512
|
+
async run({ assert }) {
|
|
513
|
+
const charge = { transitionName: null, eventType: 'invoice.payment_succeeded' };
|
|
514
|
+
|
|
515
|
+
assert.equal(
|
|
516
|
+
analytics.isTrialConversion({ ...charge, unified: subscriptionAfterTrial(), before: subscriptionAfterTrial() }),
|
|
517
|
+
true,
|
|
518
|
+
'the charge that converts is the one the pipeline stamps trial.outcome on',
|
|
519
|
+
);
|
|
520
|
+
assert.equal(
|
|
521
|
+
analytics.isTrialConversion({ ...charge, unified: subscriptionAfterTrial(), before: subscriptionConverted() }),
|
|
522
|
+
false,
|
|
523
|
+
'and it is stamped once — a second paid charge converts nothing',
|
|
524
|
+
);
|
|
525
|
+
assert.equal(
|
|
526
|
+
analytics.isTrialConversion({ ...charge, unified: subscriptionInTrial(), before: subscriptionInTrial() }),
|
|
527
|
+
false,
|
|
528
|
+
'nor does the $0 invoice a trial STARTS with',
|
|
529
|
+
);
|
|
530
|
+
assert.equal(
|
|
531
|
+
analytics.isTrialConversion({ ...charge, unified: renewedSubscription(), before: renewedSubscription() }),
|
|
532
|
+
false,
|
|
533
|
+
'nor a renewal on a subscription that never claimed a trial at all',
|
|
534
|
+
);
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
{
|
|
539
|
+
// Gap 3 (#407): a trial ending unpaid routed through payment-failed, which
|
|
540
|
+
// sent an email and fired nothing at all.
|
|
541
|
+
name: 'a-trial-that-ends-unpaid-is-a-lapse',
|
|
542
|
+
async run({ assert }) {
|
|
543
|
+
const suspended = { ...subscriptionInTrial(), status: 'suspended' };
|
|
544
|
+
|
|
545
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
546
|
+
'subscription', 'payment-failed', 'invoice.payment_failed', suspended, {}, null, subscriptionInTrial()
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
assert.ok(resolved, 'the funnel\'s failure mode should resolve to a trackable event');
|
|
550
|
+
assert.equal(resolved.event, 'trial_lapse');
|
|
551
|
+
assert.equal(resolved.value, 9.99, 'the value is the subscription that never started paying');
|
|
552
|
+
assert.equal(resolved.isRecurring, false, 'nothing was ever charged');
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
{
|
|
557
|
+
// The trial START invoice is $0 and carries no transition, so it lands on
|
|
558
|
+
// the very same branch the conversion does — and, failing that, on the
|
|
559
|
+
// renewal branch, which reads the PLAN price and booked 9.99 of revenue for
|
|
560
|
+
// an invoice that charged nothing. Before and after are both inside the
|
|
561
|
+
// trial: nothing converted, nothing renewed, nothing was charged, so the
|
|
562
|
+
// only honest answer is no event at all.
|
|
563
|
+
name: 'the-zero-dollar-trial-invoice-books-nothing',
|
|
564
|
+
async run({ assert }) {
|
|
565
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
566
|
+
'subscription', null, 'invoice.payment_succeeded', subscriptionInTrial(), {}, null, subscriptionInTrial()
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
assert.notEqual(resolved?.event, 'trial_convert', 'the trial has not converted — the customer is still inside it');
|
|
570
|
+
assert.equal(resolved, null, 'and a $0 invoice inside a trial is not recurring revenue either');
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
{
|
|
575
|
+
name: 'a-chargebee-zero-dollar-trial-invoice-books-nothing-either',
|
|
576
|
+
async run({ assert }) {
|
|
577
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
578
|
+
'subscription', null, 'invoice.payment_succeeded', chargebeeSubscriptionInTrial(), {}, null, chargebeeSubscriptionInTrial()
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
assert.equal(resolved, null, 'the no-term shape is inside the trial on both sides too');
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
{
|
|
586
|
+
// Chargebee names no current_term_end while a trial runs, so a term-end
|
|
587
|
+
// comparison cannot see a Chargebee trial at all — the conversion fell
|
|
588
|
+
// straight through to the renewal branch and the funnel stayed dark.
|
|
589
|
+
name: 'a-chargebee-conversion-is-a-conversion-too',
|
|
590
|
+
async run({ assert }) {
|
|
591
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
592
|
+
'subscription', null, 'invoice.payment_succeeded', subscriptionAfterTrial(), {}, null, chargebeeSubscriptionInTrial()
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
assert.ok(resolved, 'a converting Chargebee trial should resolve to a trackable event');
|
|
596
|
+
assert.equal(resolved.event, 'trial_convert', 'an epoch expiry is the ABSENCE of a term, not a term that ended in 1970');
|
|
597
|
+
assert.equal(resolved.isRecurring, false);
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
|
|
601
|
+
{
|
|
602
|
+
name: 'a-chargebee-trial-that-ends-unpaid-is-a-lapse-too',
|
|
603
|
+
async run({ assert }) {
|
|
604
|
+
const suspended = { ...chargebeeSubscriptionInTrial(), status: 'suspended' };
|
|
605
|
+
|
|
606
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
607
|
+
'subscription', 'payment-failed', 'invoice.payment_failed', suspended, {}, null, chargebeeSubscriptionInTrial()
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
assert.ok(resolved, 'a lapsing Chargebee trial should resolve to a trackable event');
|
|
611
|
+
assert.equal(resolved.event, 'trial_lapse');
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
{
|
|
616
|
+
// [#414](https://github.com/Omega-JS-Stack/omega/issues/414): the widening that
|
|
617
|
+
// let the resolver see a Chargebee trial also read a DEGRADED payload as one.
|
|
618
|
+
// The #222 stale fallback hands over a body with no term at all, and for a paid
|
|
619
|
+
// subscriber that is a hole in the payload, not a trial. A renewal on that path
|
|
620
|
+
// matched no branch and booked nothing: silent under-reporting of real revenue.
|
|
621
|
+
name: 'a-renewal-on-a-degraded-payload-still-books-its-revenue',
|
|
622
|
+
async run({ assert }) {
|
|
623
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
624
|
+
'subscription', null, 'subscription_renewed', degradedPaidSubscription(), {}, null, subscriptionConverted()
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
assert.ok(resolved, 'a renewal must never go unreported because its payload arrived thin');
|
|
628
|
+
assert.equal(resolved.event, 'subscription_renew');
|
|
629
|
+
assert.equal(resolved.value, 9.99, 'the money moved whatever the payload could say about the term');
|
|
630
|
+
assert.equal(resolved.isRecurring, true);
|
|
631
|
+
|
|
632
|
+
// The bound needs one healthy delivery behind it. Two degraded deliveries in
|
|
633
|
+
// a row leave no term on either side, and that renewal still books nothing —
|
|
634
|
+
// narrower than the hole it closes, and it fails toward silence.
|
|
635
|
+
assert.equal(
|
|
636
|
+
analytics.resolvePaymentEvent('subscription', null, 'subscription_renewed', degradedPaidSubscription(), {}, null, degradedPaidSubscription()),
|
|
637
|
+
null,
|
|
638
|
+
'nothing on either side names a term, so nothing can prove this was not a trial',
|
|
639
|
+
);
|
|
640
|
+
},
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
{
|
|
644
|
+
name: 'a-dunning-failure-months-after-a-trial-is-not-a-lapse',
|
|
645
|
+
async run({ assert }) {
|
|
646
|
+
// The same active → suspended shape and the same trial.claimed. Only the
|
|
647
|
+
// term tells them apart: this subscriber has been paying for months.
|
|
648
|
+
const suspended = { ...subscriptionAfterTrial(), status: 'suspended' };
|
|
649
|
+
|
|
650
|
+
const resolved = analytics.resolvePaymentEvent(
|
|
651
|
+
'subscription', 'payment-failed', 'invoice.payment_failed', suspended, {}, null, subscriptionAfterTrial()
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
assert.equal(resolved, null, 'a routine payment failure is not the trial\'s outcome');
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
{
|
|
659
|
+
// A `purchase` is the ONE payment event with a browser twin, and the
|
|
660
|
+
// confirmation page can only ever compute `purchase.<orderId>` — the
|
|
661
|
+
// webhook's event id never reaches a browser. Two ids for one purchase is
|
|
662
|
+
// the double count dedupe exists to prevent.
|
|
663
|
+
name: 'a-purchase-keys-its-dedupe-id-on-the-order-the-browser-also-knows',
|
|
664
|
+
async run({ assert }) {
|
|
665
|
+
const order = { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_checkout' } } } };
|
|
666
|
+
|
|
667
|
+
const firstPurchase = analytics.resolvePaymentEvent('subscription', 'new-subscription', 'invoice.payment_succeeded', renewedSubscription(), {});
|
|
668
|
+
assert.equal(analytics.resolveEventId(firstPurchase, order), 'purchase._test-order', 'the id the confirmation page will send');
|
|
669
|
+
|
|
670
|
+
const oneTime = analytics.resolvePaymentEvent('one-time', 'purchase-completed', 'checkout.session.completed', completedPurchase(), {});
|
|
671
|
+
assert.equal(analytics.resolveEventId(oneTime, order), 'purchase._test-order', 'a one-time purchase is the same event to the platforms');
|
|
672
|
+
|
|
673
|
+
// A win-back is a second purchase on the same order, but it lands weeks or
|
|
674
|
+
// months later — far outside Meta's and TikTok's ~48h dedupe windows.
|
|
675
|
+
const winback = analytics.resolvePaymentEvent('subscription', 'subscription-winback', 'invoice.payment_succeeded', renewedSubscription(), {});
|
|
676
|
+
assert.equal(analytics.resolveEventId(winback, order), 'purchase._test-order');
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
|
|
680
|
+
{
|
|
681
|
+
// Everything without a browser twin keys on the webhook delivery instead —
|
|
682
|
+
// a subscription renews against the same order id month after month, so an
|
|
683
|
+
// order-keyed id would have the platforms discard every renewal but the first.
|
|
684
|
+
name: 'every-other-payment-event-keys-its-dedupe-id-per-webhook-delivery',
|
|
685
|
+
async run({ assert }) {
|
|
686
|
+
const resolved = resolveRenewal('invoice.payment_succeeded');
|
|
687
|
+
const order = { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_september' } } } };
|
|
688
|
+
|
|
689
|
+
assert.equal(analytics.resolveEventId(resolved, order), 'subscription_renew.evt_september');
|
|
690
|
+
assert.equal(
|
|
691
|
+
analytics.resolveEventId(resolved, { id: '_test-order' }),
|
|
692
|
+
'subscription_renew._test-order',
|
|
693
|
+
'An order with no webhook stamp still gets an id'
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
// October's renewal must not collide with September's.
|
|
697
|
+
assert.notEqual(
|
|
698
|
+
analytics.resolveEventId(resolved, { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_october' } } } }),
|
|
699
|
+
analytics.resolveEventId(resolved, order),
|
|
700
|
+
'Two renewals on one order are two conversions'
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
const refund = analytics.resolvePaymentEvent('subscription', 'payment-refunded', 'charge.refunded', renewedSubscription(), {}, { amount: 9.99 });
|
|
704
|
+
assert.equal(analytics.resolveEventId(refund, order), 'refund.evt_september', 'a refund has no browser twin either');
|
|
705
|
+
|
|
706
|
+
// A trial is the one exception on this side: its outcome is keyed to the
|
|
707
|
+
// SUBSCRIPTION, not the delivery, because two paths can be the one to see
|
|
708
|
+
// it — this webhook, and the trial-lapse sweep when no webhook ever comes
|
|
709
|
+
// (PayPal fires no trial-end event). One outcome must never be two
|
|
710
|
+
// conversions, so both paths compute the same id.
|
|
711
|
+
const converted = analytics.resolvePaymentEvent('subscription', null, 'invoice.payment_succeeded', subscriptionAfterTrial(), {}, null, subscriptionInTrial());
|
|
712
|
+
assert.equal(analytics.resolveEventId(converted, order), 'trial_convert._test-sub-analytics');
|
|
713
|
+
assert.equal(
|
|
714
|
+
analytics.resolveEventId(converted, { id: '_other-order', metadata: { updatedBy: { event: { id: 'evt_october' } } } }),
|
|
715
|
+
'trial_convert._test-sub-analytics',
|
|
716
|
+
'a redelivery on another order id is still the same one conversion',
|
|
717
|
+
);
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
|
|
721
|
+
{
|
|
722
|
+
// [#656]: `transaction_id` was the SUBSCRIPTION id, which never changes —
|
|
723
|
+
// and GA4 deduplicates `purchase` on it
|
|
724
|
+
// (https://support.google.com/analytics/answer/12313109), so it counted the
|
|
725
|
+
// first charge of a subscription and dropped every renewal after it.
|
|
726
|
+
name: 'two-renewals-of-one-subscription-are-two-transaction-ids',
|
|
727
|
+
async run({ assert }) {
|
|
728
|
+
const resolved = resolveRenewal('invoice.payment_succeeded');
|
|
729
|
+
|
|
730
|
+
const september = analytics.buildParams({
|
|
731
|
+
resolved, currency: 'USD', provider: 'stripe',
|
|
732
|
+
order: { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_september' } } } },
|
|
733
|
+
chargeId: 'in_september',
|
|
734
|
+
});
|
|
735
|
+
const october = analytics.buildParams({
|
|
736
|
+
resolved, currency: 'USD', provider: 'stripe',
|
|
737
|
+
order: { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_october' } } } },
|
|
738
|
+
chargeId: 'in_october',
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
assert.equal(september.transaction_id, 'in_september', 'the invoice IS the charge');
|
|
742
|
+
assert.equal(october.transaction_id, 'in_october');
|
|
743
|
+
assert.notEqual(september.transaction_id, october.transaction_id, 'two charges are two transactions to GA4');
|
|
744
|
+
assert.notEqual(september.transaction_id, '_test-sub-analytics', 'and neither is the subscription id');
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
|
|
748
|
+
{
|
|
749
|
+
// The one id both halves can compute. The confirmation page holds the order
|
|
750
|
+
// id and nothing else, and GA4 collapses the two halves on it ([#656]).
|
|
751
|
+
name: 'a-first-purchase-and-a-trial-start-name-the-order-the-browser-also-holds',
|
|
752
|
+
async run({ assert }) {
|
|
753
|
+
const order = { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_checkout' } } } };
|
|
754
|
+
|
|
755
|
+
const firstPurchase = analytics.resolvePaymentEvent('subscription', 'new-subscription', 'customer.subscription.created', renewedSubscription(), {});
|
|
756
|
+
assert.equal(
|
|
757
|
+
analytics.buildParams({ resolved: firstPurchase, currency: 'USD', provider: 'stripe', order, chargeId: null }).transaction_id,
|
|
758
|
+
'_test-order',
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
const trialStart = analytics.resolvePaymentEvent('subscription', 'new-subscription', 'customer.subscription.created', subscriptionInTrial(), {});
|
|
762
|
+
assert.equal(trialStart.event, 'trial_start');
|
|
763
|
+
assert.equal(
|
|
764
|
+
analytics.buildParams({ resolved: trialStart, currency: 'USD', provider: 'stripe', order, chargeId: null }).transaction_id,
|
|
765
|
+
'_test-order',
|
|
766
|
+
'the trial checkout the browser half is about',
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
// A one-time buy has exactly one charge, and the order names it.
|
|
770
|
+
const oneTime = analytics.resolvePaymentEvent('one-time', 'purchase-completed', 'checkout.session.completed', completedPurchase(), {});
|
|
771
|
+
assert.equal(
|
|
772
|
+
analytics.buildParams({ resolved: oneTime, currency: 'USD', provider: 'stripe', order, chargeId: null }).transaction_id,
|
|
773
|
+
'_test-order',
|
|
774
|
+
);
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
|
|
778
|
+
{
|
|
779
|
+
name: 'a-refund-names-the-charge-it-reverses-and-a-charge-event-never-falls-back-to-the-order',
|
|
780
|
+
async run({ assert }) {
|
|
781
|
+
const order = { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_september' } } } };
|
|
782
|
+
|
|
783
|
+
const refund = analytics.resolvePaymentEvent('subscription', 'payment-refunded', 'charge.refunded', renewedSubscription(), {}, { amount: 9.99 });
|
|
784
|
+
|
|
785
|
+
assert.equal(
|
|
786
|
+
analytics.buildParams({ resolved: refund, currency: 'USD', provider: 'stripe', order, chargeId: 'in_september' }).transaction_id,
|
|
787
|
+
'in_september',
|
|
788
|
+
'GA4 nets a refund against the transaction id of the charge it reverses',
|
|
789
|
+
);
|
|
790
|
+
|
|
791
|
+
// No charge id in the payload: the WEBHOOK delivery, never the order —
|
|
792
|
+
// the order is the collapse [#656] exists to undo.
|
|
793
|
+
assert.equal(
|
|
794
|
+
analytics.buildParams({ resolved: resolveRenewal('invoice.payment_succeeded'), currency: 'USD', provider: 'stripe', order, chargeId: null }).transaction_id,
|
|
795
|
+
'evt_september',
|
|
796
|
+
);
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
|
|
800
|
+
{
|
|
801
|
+
// A one-time order has exactly ONE charge, and no provider names it on the
|
|
802
|
+
// refund — every one-time refund branch hands the pipeline a null chargeId.
|
|
803
|
+
// So the refund names the ORDER, which is the id the purchase it reverses
|
|
804
|
+
// was reported under, and GA4 nets the two ([#656]).
|
|
805
|
+
name: 'a-one-time-refund-nets-against-the-purchase-it-reverses',
|
|
806
|
+
async run({ assert }) {
|
|
807
|
+
const order = { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_refund' } } } };
|
|
808
|
+
|
|
809
|
+
const purchase = analytics.resolvePaymentEvent('one-time', 'purchase-completed', 'checkout.session.completed', completedPurchase(), {});
|
|
810
|
+
const refund = analytics.resolvePaymentEvent(
|
|
811
|
+
'one-time', 'purchase-refunded', 'charge.refunded', completedPurchase(), {},
|
|
812
|
+
{ amount: 99, currency: 'USD', reason: null }
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
const purchaseId = analytics.buildParams({ resolved: purchase, currency: 'USD', provider: 'stripe', order, chargeId: null }).transaction_id;
|
|
816
|
+
const refundId = analytics.buildParams({ resolved: refund, currency: 'USD', provider: 'stripe', order, chargeId: null }).transaction_id;
|
|
817
|
+
|
|
818
|
+
assert.equal(purchaseId, '_test-order');
|
|
819
|
+
assert.equal(refundId, purchaseId, 'GA4 nets a one-time refund only if it names the same transaction');
|
|
820
|
+
assert.notEqual(refundId, 'evt_refund', 'the refund webhook delivery would net against nothing');
|
|
821
|
+
|
|
822
|
+
// A SUBSCRIPTION refund keeps the charge id: its renewals are separate
|
|
823
|
+
// charges, so the order id would net October's reversal against the
|
|
824
|
+
// first purchase.
|
|
825
|
+
const renewalRefund = analytics.resolvePaymentEvent(
|
|
826
|
+
'subscription', 'payment-refunded', 'charge.refunded', renewedSubscription(), {},
|
|
827
|
+
{ amount: 9.99, currency: 'USD', reason: null }
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
assert.equal(
|
|
831
|
+
analytics.buildParams({ resolved: renewalRefund, currency: 'USD', provider: 'stripe', order, chargeId: 'in_october' }).transaction_id,
|
|
832
|
+
'in_october',
|
|
833
|
+
);
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
|
|
837
|
+
{
|
|
838
|
+
// The outcomes that are NOT charges keep naming their subject: each is a
|
|
839
|
+
// GA4 custom event with no dedupe of its own.
|
|
840
|
+
name: 'the-events-that-are-not-charges-still-name-the-subscription',
|
|
841
|
+
async run({ assert }) {
|
|
842
|
+
const order = { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_cancel' } } } };
|
|
843
|
+
const cancelled = { ...renewedSubscription(), status: 'cancelled' };
|
|
844
|
+
|
|
845
|
+
const resolved = analytics.resolvePaymentEvent('subscription', 'subscription-cancelled', 'customer.subscription.deleted', cancelled, {}, null, renewedSubscription());
|
|
846
|
+
|
|
847
|
+
assert.equal(resolved.event, 'subscription_cancel');
|
|
848
|
+
assert.equal(
|
|
849
|
+
analytics.buildParams({ resolved, currency: 'USD', provider: 'stripe', order, chargeId: null }).transaction_id,
|
|
850
|
+
'_test-sub-analytics',
|
|
851
|
+
);
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
|
|
855
|
+
{
|
|
856
|
+
// Every provider has to hand the pipeline an id for the charge, or the
|
|
857
|
+
// renewal branch above has nothing to key on ([#656]).
|
|
858
|
+
name: 'every-provider-exposes-its-own-per-charge-id-on-the-parsed-event',
|
|
859
|
+
async run({ assert }) {
|
|
860
|
+
const stripe = stripeProvider.parseWebhook({
|
|
861
|
+
body: {
|
|
862
|
+
id: 'evt_stripe',
|
|
863
|
+
type: 'invoice.payment_succeeded',
|
|
864
|
+
data: { object: { id: 'in_september', billing_reason: 'subscription_cycle', parent: { subscription_details: { subscription: 'sub_1', metadata: { uid: 'u1' } } } } },
|
|
865
|
+
},
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
assert.equal(stripe.resourceId, 'sub_1', 'the resource is still the subscription to fetch');
|
|
869
|
+
assert.equal(stripe.chargeId, 'in_september', 'and the invoice is the charge');
|
|
870
|
+
|
|
871
|
+
const paypal = paypalProvider.parseWebhook({
|
|
872
|
+
body: {
|
|
873
|
+
id: 'WH-paypal',
|
|
874
|
+
event_type: 'PAYMENT.SALE.COMPLETED',
|
|
875
|
+
resource: { id: 'SALE-september', billing_agreement_id: 'I-1', custom_id: 'uid:u1' },
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
assert.equal(paypal.resourceId, 'I-1', 'the billing agreement is the subscription');
|
|
880
|
+
assert.equal(paypal.chargeId, 'SALE-september', 'and the sale is the charge');
|
|
881
|
+
|
|
882
|
+
const chargebee = chargebeeProvider.parseWebhook({
|
|
883
|
+
body: {
|
|
884
|
+
id: 'ev_chargebee',
|
|
885
|
+
event_type: 'subscription_renewed',
|
|
886
|
+
content: { subscription: { id: 'cb_sub_1' }, invoice: { id: 'inv_september' } },
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
assert.equal(chargebee.resourceId, 'cb_sub_1');
|
|
891
|
+
assert.equal(chargebee.chargeId, 'inv_september', 'Chargebee names the renewal invoice');
|
|
892
|
+
|
|
893
|
+
// ONLY the renewal's. A `subscription_created` payload carries the
|
|
894
|
+
// checkout's first invoice (test/fixtures/chargebee/webhook-subscription-created.json),
|
|
895
|
+
// and that charge is the ORDER's — the id the browser half sends too.
|
|
896
|
+
const chargebeeCreated = chargebeeProvider.parseWebhook({
|
|
897
|
+
body: {
|
|
898
|
+
id: 'ev_created',
|
|
899
|
+
event_type: 'subscription_created',
|
|
900
|
+
content: { subscription: { id: 'cb_sub_1' }, invoice: { id: 'inv_first' } },
|
|
901
|
+
},
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
assert.equal(chargebeeCreated.chargeId, null, 'a first charge is the order\'s, never the invoice\'s');
|
|
905
|
+
|
|
906
|
+
// The test provider IS Stripe's parser, so it carries the same id.
|
|
907
|
+
assert.equal(testProvider.parseWebhook({
|
|
908
|
+
body: {
|
|
909
|
+
id: 'evt_test',
|
|
910
|
+
type: 'invoice.payment_succeeded',
|
|
911
|
+
data: { object: { id: 'in_test', billing_reason: 'subscription_cycle', parent: { subscription_details: { subscription: 'sub_1' } } } },
|
|
912
|
+
},
|
|
913
|
+
}).chargeId, 'in_test');
|
|
914
|
+
|
|
915
|
+
// A subscription's lifecycle events name no charge at all — the first
|
|
916
|
+
// purchase is the order's, and that is the branch that must not guess.
|
|
917
|
+
assert.equal(stripeProvider.parseWebhook({
|
|
918
|
+
body: { id: 'evt_created', type: 'customer.subscription.created', data: { object: { id: 'sub_1', metadata: { uid: 'u1' } } } },
|
|
919
|
+
}).chargeId, null);
|
|
920
|
+
},
|
|
921
|
+
},
|
|
922
|
+
|
|
923
|
+
{
|
|
924
|
+
// [#654]: the confirmation page fired `purchase` for a TRIAL checkout while
|
|
925
|
+
// this fired `trial_start`. Different event names never deduplicate, so a $0
|
|
926
|
+
// trial produced a browser Purchase carrying the plan's price beside the
|
|
927
|
+
// server's StartTrial. Both halves now say trial_start, keyed on the order —
|
|
928
|
+
// the only id a browser can compute.
|
|
929
|
+
name: 'a-trial-start-keys-its-dedupe-id-on-the-order-like-its-browser-twin',
|
|
930
|
+
async run({ assert }) {
|
|
931
|
+
const order = { id: '_test-order', metadata: { updatedBy: { event: { id: 'evt_checkout' } } } };
|
|
932
|
+
|
|
933
|
+
const trialStart = analytics.resolvePaymentEvent('subscription', 'new-subscription', 'customer.subscription.created', subscriptionInTrial(), {});
|
|
934
|
+
|
|
935
|
+
assert.equal(trialStart.event, 'trial_start');
|
|
936
|
+
assert.equal(analytics.resolveEventId(trialStart, order), 'trial_start._test-order', 'the id the confirmation page will send');
|
|
937
|
+
assert.notEqual(
|
|
938
|
+
analytics.resolveEventId(trialStart, order),
|
|
939
|
+
'trial_start.evt_checkout',
|
|
940
|
+
'the webhook delivery is not derivable in a browser, so it can never key a two-sided event',
|
|
941
|
+
);
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
],
|
|
945
|
+
});
|