@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.
Files changed (993) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +1053 -0
  3. package/bin/omega +2 -0
  4. package/bin/omega-backend +2 -0
  5. package/bin/omg +2 -0
  6. package/cli.js +3 -0
  7. package/dist/cli/command-table.js +268 -0
  8. package/dist/cli/commands/auth.js +259 -0
  9. package/dist/cli/commands/base-command.js +451 -0
  10. package/dist/cli/commands/build.js +27 -0
  11. package/dist/cli/commands/clean.js +13 -0
  12. package/dist/cli/commands/clear.js +11 -0
  13. package/dist/cli/commands/cwd.js +9 -0
  14. package/dist/cli/commands/deploy.js +205 -0
  15. package/dist/cli/commands/emulator-orphans.js +440 -0
  16. package/dist/cli/commands/emulator.js +1829 -0
  17. package/dist/cli/commands/firebase-init.js +101 -0
  18. package/dist/cli/commands/firestore.js +261 -0
  19. package/dist/cli/commands/indexes.js +51 -0
  20. package/dist/cli/commands/install.js +151 -0
  21. package/dist/cli/commands/logs.js +358 -0
  22. package/dist/cli/commands/mcp.js +41 -0
  23. package/dist/cli/commands/migrate-markers.js +176 -0
  24. package/dist/cli/commands/migrate-rules.js +90 -0
  25. package/dist/cli/commands/migrate.js +59 -0
  26. package/dist/cli/commands/serve.js +208 -0
  27. package/dist/cli/commands/setup-tests/base-test.js +174 -0
  28. package/dist/cli/commands/setup-tests/emulator-config.js +109 -0
  29. package/dist/cli/commands/setup-tests/env-runtime-config-deprecated.js +47 -0
  30. package/dist/cli/commands/setup-tests/firebase-admin.js +49 -0
  31. package/dist/cli/commands/setup-tests/firebase-auth.js +26 -0
  32. package/dist/cli/commands/setup-tests/firebase-cli.js +26 -0
  33. package/dist/cli/commands/setup-tests/firebase-functions.js +49 -0
  34. package/dist/cli/commands/setup-tests/firestore-indexes-file.js +72 -0
  35. package/dist/cli/commands/setup-tests/firestore-indexes-in-json.js +20 -0
  36. package/dist/cli/commands/setup-tests/firestore-indexes-required.js +108 -0
  37. package/dist/cli/commands/setup-tests/firestore-indexes-synced.js +236 -0
  38. package/dist/cli/commands/setup-tests/firestore-rules-file.js +115 -0
  39. package/dist/cli/commands/setup-tests/firestore-rules-in-json.js +49 -0
  40. package/dist/cli/commands/setup-tests/functions-package.js +44 -0
  41. package/dist/cli/commands/setup-tests/gcloud-cli.js +26 -0
  42. package/dist/cli/commands/setup-tests/gitignore.js +122 -0
  43. package/dist/cli/commands/setup-tests/helpers/merge-line-files.js +28 -0
  44. package/dist/cli/commands/setup-tests/helpers/required-indexes.js +109 -0
  45. package/dist/cli/commands/setup-tests/helpers/seed-campaigns.js +246 -0
  46. package/dist/cli/commands/setup-tests/helpers.js +72 -0
  47. package/dist/cli/commands/setup-tests/hosting-folder.js +23 -0
  48. package/dist/cli/commands/setup-tests/hosting-rewrites.js +52 -0
  49. package/dist/cli/commands/setup-tests/index.js +111 -0
  50. package/dist/cli/commands/setup-tests/is-firebase-project.js +21 -0
  51. package/dist/cli/commands/setup-tests/java-installed.js +27 -0
  52. package/dist/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
  53. package/dist/cli/commands/setup-tests/marketing-campaigns-seeded.js +230 -0
  54. package/dist/cli/commands/setup-tests/node-version.js +60 -0
  55. package/dist/cli/commands/setup-tests/npm-project-scripts.js +42 -0
  56. package/dist/cli/commands/setup-tests/nvmrc-version.js +36 -0
  57. package/dist/cli/commands/setup-tests/omega-backend.js +42 -0
  58. package/dist/cli/commands/setup-tests/omega-config.js +84 -0
  59. package/dist/cli/commands/setup-tests/project-directories.js +34 -0
  60. package/dist/cli/commands/setup-tests/project-id-consistency.js +201 -0
  61. package/dist/cli/commands/setup-tests/public-html-files.js +22 -0
  62. package/dist/cli/commands/setup-tests/realtime-rules-file.js +69 -0
  63. package/dist/cli/commands/setup-tests/realtime-rules-in-json.js +20 -0
  64. package/dist/cli/commands/setup-tests/remoteconfig-template-file.js +32 -0
  65. package/dist/cli/commands/setup-tests/remoteconfig-template-in-json.js +31 -0
  66. package/dist/cli/commands/setup-tests/service-account.js +86 -0
  67. package/dist/cli/commands/setup-tests/storage-lifecycle-policy.js +81 -0
  68. package/dist/cli/commands/setup-tests/storage-rules-file.js +32 -0
  69. package/dist/cli/commands/setup-tests/storage-rules-in-json.js +20 -0
  70. package/dist/cli/commands/stripe.js +14 -0
  71. package/dist/cli/commands/test-lanes/stripe-live.js +440 -0
  72. package/dist/cli/commands/test.js +976 -0
  73. package/dist/cli/commands/update.js +27 -0
  74. package/dist/cli/commands/version.js +10 -0
  75. package/dist/cli/commands/watch.js +196 -0
  76. package/dist/cli/flags.js +13 -0
  77. package/dist/cli/index.js +173 -0
  78. package/dist/cli/run.js +33 -0
  79. package/dist/cli/utils/attach-log-file.js +4 -0
  80. package/dist/cli/utils/compile-rules.js +1155 -0
  81. package/dist/cli/utils/ensure-target.js +314 -0
  82. package/dist/cli/utils/project-type.js +140 -0
  83. package/dist/cli/utils/public-files.js +49 -0
  84. package/dist/cli/utils/safe-install.js +4 -0
  85. package/dist/cli/utils/spawn-shell.js +94 -0
  86. package/dist/cli/utils/stage-functions.js +338 -0
  87. package/dist/cli/utils/stage-local-packages.js +210 -0
  88. package/dist/cli/utils/target-checks.js +161 -0
  89. package/dist/cli/utils/target.js +24 -0
  90. package/dist/cli/utils/ui.js +292 -0
  91. package/dist/defaults/AGENTS.md +125 -0
  92. package/dist/defaults/CHANGELOG.md +15 -0
  93. package/dist/defaults/CLAUDE.md +1 -0
  94. package/dist/defaults/_.gitignore +65 -0
  95. package/dist/defaults/docs/README.md +17 -0
  96. package/dist/defaults/test/README.md +65 -0
  97. package/dist/defaults/test/_init.js +14 -0
  98. package/dist/defaults/test/helpers/connect-trap.js +72 -0
  99. package/dist/defaults/test/unit/registration.test.js +143 -0
  100. package/dist/defaults/test/unit/rules-posture.test.js +101 -0
  101. package/dist/defaults/test/unit/socket-free.test.js +43 -0
  102. package/dist/manager/events/auth/before-create.js +89 -0
  103. package/dist/manager/events/auth/before-signin.js +76 -0
  104. package/dist/manager/events/auth/on-create.js +112 -0
  105. package/dist/manager/events/auth/on-delete.js +141 -0
  106. package/dist/manager/events/auth/utils.js +93 -0
  107. package/dist/manager/events/cron/daily/blog-auto-publisher.js +287 -0
  108. package/dist/manager/events/cron/daily/data-requests.js +57 -0
  109. package/dist/manager/events/cron/daily/expire-paypal-cancellations.js +191 -0
  110. package/dist/manager/events/cron/daily/marketing-prune.js +486 -0
  111. package/dist/manager/events/cron/daily/reset-usage.js +188 -0
  112. package/dist/manager/events/cron/daily/trial-lapse-sweep.js +355 -0
  113. package/dist/manager/events/cron/daily.js +3 -0
  114. package/dist/manager/events/cron/frequent/abandoned-carts.js +204 -0
  115. package/dist/manager/events/cron/frequent/email-queue.js +71 -0
  116. package/dist/manager/events/cron/frequent/marketing-campaigns.js +463 -0
  117. package/dist/manager/events/cron/frequent/retry-failed-webhooks.js +70 -0
  118. package/dist/manager/events/cron/frequent.js +3 -0
  119. package/dist/manager/events/cron/runner.js +64 -0
  120. package/dist/manager/events/firestore/notifications/on-write.js +123 -0
  121. package/dist/manager/events/firestore/payments-disputes/on-write.js +256 -0
  122. package/dist/manager/events/firestore/payments-disputes/providers/stripe.js +218 -0
  123. package/dist/manager/events/firestore/payments-disputes/providers/test.js +231 -0
  124. package/dist/manager/events/firestore/payments-webhooks/analytics.js +687 -0
  125. package/dist/manager/events/firestore/payments-webhooks/on-write.js +1224 -0
  126. package/dist/manager/events/firestore/payments-webhooks/transitions/index.js +395 -0
  127. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +58 -0
  128. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +28 -0
  129. package/dist/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-refunded.js +30 -0
  130. package/dist/manager/events/firestore/payments-webhooks/transitions/send-email.js +74 -0
  131. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-removed.js +14 -0
  132. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +28 -0
  133. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/checkout-declined.js +18 -0
  134. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +67 -0
  135. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +25 -0
  136. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +26 -0
  137. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/payment-refunded.js +34 -0
  138. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +42 -0
  139. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +33 -0
  140. package/dist/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-winback.js +17 -0
  141. package/dist/manager/functions/_legacy/actions/create-post-handler.js +188 -0
  142. package/dist/manager/functions/_legacy/actions/generate-uuid.js +63 -0
  143. package/dist/manager/functions/_legacy/actions/sign-up-handler.js +205 -0
  144. package/dist/manager/functions/_legacy/admin/create-post.js +207 -0
  145. package/dist/manager/functions/_legacy/admin/firestore-write.js +72 -0
  146. package/dist/manager/functions/_legacy/admin/get-stats.js +218 -0
  147. package/dist/manager/functions/_legacy/admin/query.js +198 -0
  148. package/dist/manager/functions/_legacy/admin/send-notification.js +206 -0
  149. package/dist/manager/functions/_legacy/template.js +33 -0
  150. package/dist/manager/functions/_legacy/test/authenticate.js +40 -0
  151. package/dist/manager/functions/_legacy/test/webhook.js +37 -0
  152. package/dist/manager/functions/wrappers/mailchimp/addToList.js +25 -0
  153. package/dist/manager/helpers/analytics.js +498 -0
  154. package/dist/manager/helpers/api-manager.js +313 -0
  155. package/dist/manager/helpers/backend-router.js +37 -0
  156. package/dist/manager/helpers/context/authenticate.js +215 -0
  157. package/dist/manager/helpers/context/client-info.js +91 -0
  158. package/dist/manager/helpers/context/index.js +228 -0
  159. package/dist/manager/helpers/context/logging.js +150 -0
  160. package/dist/manager/helpers/context/parse.js +160 -0
  161. package/dist/manager/helpers/context/respond.js +247 -0
  162. package/dist/manager/helpers/event-middleware.js +109 -0
  163. package/dist/manager/helpers/metadata.js +32 -0
  164. package/dist/manager/helpers/middleware.js +541 -0
  165. package/dist/manager/helpers/redact-secret.js +25 -0
  166. package/dist/manager/helpers/resolved-config.js +34 -0
  167. package/dist/manager/helpers/roles.js +69 -0
  168. package/dist/manager/helpers/safe-compare.js +31 -0
  169. package/dist/manager/helpers/schema-engine.js +314 -0
  170. package/dist/manager/helpers/schema-zod.js +203 -0
  171. package/dist/manager/helpers/settings.js +215 -0
  172. package/dist/manager/helpers/usage.js +541 -0
  173. package/dist/manager/helpers/user.js +57 -0
  174. package/dist/manager/helpers/utilities.js +565 -0
  175. package/dist/manager/index.js +1404 -0
  176. package/dist/manager/libraries/abandoned-cart-config.js +12 -0
  177. package/dist/manager/libraries/ai/index.js +321 -0
  178. package/dist/manager/libraries/ai/prompt.js +85 -0
  179. package/dist/manager/libraries/ai/providers/anthropic-format.js +297 -0
  180. package/dist/manager/libraries/ai/providers/anthropic.js +169 -0
  181. package/dist/manager/libraries/ai/providers/claude-code.js +183 -0
  182. package/dist/manager/libraries/ai/providers/openai.js +1272 -0
  183. package/dist/manager/libraries/ai/providers/test.js +257 -0
  184. package/dist/manager/libraries/ai/tokens.js +54 -0
  185. package/dist/manager/libraries/analytics/conversions.js +478 -0
  186. package/dist/manager/libraries/analytics/match-data.js +573 -0
  187. package/dist/manager/libraries/analytics/signup.js +120 -0
  188. package/dist/manager/libraries/auth-user.js +46 -0
  189. package/dist/manager/libraries/content/feed-parser.js +198 -0
  190. package/dist/manager/libraries/content/ghostii.js +191 -0
  191. package/dist/manager/libraries/content/source-resolver.js +629 -0
  192. package/dist/manager/libraries/email/constants.js +532 -0
  193. package/dist/manager/libraries/email/data/blocked-local-parts.json +55 -0
  194. package/dist/manager/libraries/email/data/blocked-local-patterns.js +14 -0
  195. package/dist/manager/libraries/email/data/corporate-domains.json +23 -0
  196. package/dist/manager/libraries/email/data/custom-disposable-domains.json +58 -0
  197. package/dist/manager/libraries/email/data/disposable-domains.json +8184 -0
  198. package/dist/manager/libraries/email/data/typo-domains.js +83 -0
  199. package/dist/manager/libraries/email/disposable-domains.js +92 -0
  200. package/dist/manager/libraries/email/generators/lib/filter.js +179 -0
  201. package/dist/manager/libraries/email/generators/lib/image-host.js +344 -0
  202. package/dist/manager/libraries/email/generators/lib/image-illustrator.js +154 -0
  203. package/dist/manager/libraries/email/generators/lib/markdown-renderer.js +294 -0
  204. package/dist/manager/libraries/email/generators/lib/mjml-template.js +120 -0
  205. package/dist/manager/libraries/email/generators/lib/structure.js +299 -0
  206. package/dist/manager/libraries/email/generators/lib/svg-illustrator.js +197 -0
  207. package/dist/manager/libraries/email/generators/lib/templates/base.js +229 -0
  208. package/dist/manager/libraries/email/generators/lib/templates/card.js +33 -0
  209. package/dist/manager/libraries/email/generators/lib/templates/classic-schema.js +59 -0
  210. package/dist/manager/libraries/email/generators/lib/templates/clean.js +82 -0
  211. package/dist/manager/libraries/email/generators/lib/templates/editorial/helpers.js +100 -0
  212. package/dist/manager/libraries/email/generators/lib/templates/editorial/index.js +318 -0
  213. package/dist/manager/libraries/email/generators/lib/templates/feedback.js +101 -0
  214. package/dist/manager/libraries/email/generators/lib/templates/field-report/helpers.js +138 -0
  215. package/dist/manager/libraries/email/generators/lib/templates/field-report/index.js +494 -0
  216. package/dist/manager/libraries/email/generators/lib/templates/index.js +47 -0
  217. package/dist/manager/libraries/email/generators/lib/templates/newsletter-shared.js +610 -0
  218. package/dist/manager/libraries/email/generators/lib/templates/order.js +461 -0
  219. package/dist/manager/libraries/email/generators/lib/templates/plain.js +83 -0
  220. package/dist/manager/libraries/email/generators/lib/templates/shared-campaign.js +64 -0
  221. package/dist/manager/libraries/email/generators/newsletter.js +899 -0
  222. package/dist/manager/libraries/email/index.js +146 -0
  223. package/dist/manager/libraries/email/marketing/index.js +754 -0
  224. package/dist/manager/libraries/email/prepare.js +365 -0
  225. package/dist/manager/libraries/email/providers/beehiiv.js +589 -0
  226. package/dist/manager/libraries/email/providers/sendgrid.js +845 -0
  227. package/dist/manager/libraries/email/transactional/index.js +510 -0
  228. package/dist/manager/libraries/email/utm.js +86 -0
  229. package/dist/manager/libraries/email/validation-provider-neverbounce.js +70 -0
  230. package/dist/manager/libraries/email/validation-provider-zerobounce.js +42 -0
  231. package/dist/manager/libraries/email/validation.js +255 -0
  232. package/dist/manager/libraries/env.js +198 -0
  233. package/dist/manager/libraries/infer-contact.js +101 -0
  234. package/dist/manager/libraries/load-provider.js +65 -0
  235. package/dist/manager/libraries/notification.js +256 -0
  236. package/dist/manager/libraries/openai.js +13 -0
  237. package/dist/manager/libraries/payment/discount-codes.js +204 -0
  238. package/dist/manager/libraries/payment/fetch-failure.js +56 -0
  239. package/dist/manager/libraries/payment/license.js +44 -0
  240. package/dist/manager/libraries/payment/order-id.js +57 -0
  241. package/dist/manager/libraries/payment/provider-errors.js +71 -0
  242. package/dist/manager/libraries/payment/providers/chargebee.js +817 -0
  243. package/dist/manager/libraries/payment/providers/coinbase.js +330 -0
  244. package/dist/manager/libraries/payment/providers/paypal.js +987 -0
  245. package/dist/manager/libraries/payment/providers/stripe.js +755 -0
  246. package/dist/manager/libraries/payment/providers/test.js +250 -0
  247. package/dist/manager/libraries/payment/refund-linkage.js +78 -0
  248. package/dist/manager/libraries/payment/refund-policy.js +113 -0
  249. package/dist/manager/libraries/payment/winback.js +55 -0
  250. package/dist/manager/libraries/prompts/infer-contact.md +78 -0
  251. package/dist/manager/libraries/rate-limits.js +17 -0
  252. package/dist/manager/libraries/recaptcha.js +58 -0
  253. package/dist/manager/libraries/user-doc.js +234 -0
  254. package/dist/manager/routes/admin/backup/post.js +106 -0
  255. package/dist/manager/routes/admin/cron/post.js +37 -0
  256. package/dist/manager/routes/admin/database/get.js +35 -0
  257. package/dist/manager/routes/admin/database/post.js +34 -0
  258. package/dist/manager/routes/admin/email/post.js +74 -0
  259. package/dist/manager/routes/admin/firestore/get.js +35 -0
  260. package/dist/manager/routes/admin/firestore/post.js +56 -0
  261. package/dist/manager/routes/admin/firestore/query/post.js +128 -0
  262. package/dist/manager/routes/admin/hook/post.js +104 -0
  263. package/dist/manager/routes/admin/infer-contact/post.js +35 -0
  264. package/dist/manager/routes/admin/notification/post.js +37 -0
  265. package/dist/manager/routes/admin/payment/post.js +56 -0
  266. package/dist/manager/routes/admin/post/deduplicate-image-alts.js +52 -0
  267. package/dist/manager/routes/admin/post/dispatch-deploy.js +36 -0
  268. package/dist/manager/routes/admin/post/post.js +487 -0
  269. package/dist/manager/routes/admin/post/put.js +146 -0
  270. package/dist/manager/routes/admin/post/templates/post.html +14 -0
  271. package/dist/manager/routes/admin/repo/content/post.js +106 -0
  272. package/dist/manager/routes/admin/stats/get.js +222 -0
  273. package/dist/manager/routes/admin/users/disable/post.js +50 -0
  274. package/dist/manager/routes/admin/users/list/get.js +110 -0
  275. package/dist/manager/routes/admin/users/sync/post.js +119 -0
  276. package/dist/manager/routes/brand/get.js +26 -0
  277. package/dist/manager/routes/content/post/get.js +110 -0
  278. package/dist/manager/routes/general/email/post.js +89 -0
  279. package/dist/manager/routes/general/email/templates/general/download-app-link.js +42 -0
  280. package/dist/manager/routes/general/uuid/post.js +31 -0
  281. package/dist/manager/routes/handler/post/post.js +142 -0
  282. package/dist/manager/routes/health/get.js +43 -0
  283. package/dist/manager/routes/index.js +11 -0
  284. package/dist/manager/routes/marketing/campaign/delete.js +45 -0
  285. package/dist/manager/routes/marketing/campaign/get.js +69 -0
  286. package/dist/manager/routes/marketing/campaign/post.js +124 -0
  287. package/dist/manager/routes/marketing/campaign/put.js +86 -0
  288. package/dist/manager/routes/marketing/campaign/utils.js +59 -0
  289. package/dist/manager/routes/marketing/contact/delete.js +100 -0
  290. package/dist/manager/routes/marketing/contact/post.js +149 -0
  291. package/dist/manager/routes/marketing/contact/put.js +37 -0
  292. package/dist/manager/routes/marketing/email-preferences/post.js +275 -0
  293. package/dist/manager/routes/marketing/webhook/forward/post.js +170 -0
  294. package/dist/manager/routes/marketing/webhook/post.js +123 -0
  295. package/dist/manager/routes/marketing/webhook/providers/beehiiv.js +197 -0
  296. package/dist/manager/routes/marketing/webhook/providers/sendgrid.js +194 -0
  297. package/dist/manager/routes/payments/cancel/_is-trialing.js +40 -0
  298. package/dist/manager/routes/payments/cancel/post.js +149 -0
  299. package/dist/manager/routes/payments/cancel/providers/chargebee.js +41 -0
  300. package/dist/manager/routes/payments/cancel/providers/paypal.js +56 -0
  301. package/dist/manager/routes/payments/cancel/providers/stripe.js +33 -0
  302. package/dist/manager/routes/payments/cancel/providers/test.js +118 -0
  303. package/dist/manager/routes/payments/discount/get.js +25 -0
  304. package/dist/manager/routes/payments/dispute-alert/post.js +117 -0
  305. package/dist/manager/routes/payments/dispute-alert/providers/chargeblast.js +55 -0
  306. package/dist/manager/routes/payments/intent/post.js +341 -0
  307. package/dist/manager/routes/payments/intent/providers/chargebee.js +197 -0
  308. package/dist/manager/routes/payments/intent/providers/coinbase.js +94 -0
  309. package/dist/manager/routes/payments/intent/providers/paypal.js +244 -0
  310. package/dist/manager/routes/payments/intent/providers/stripe.js +141 -0
  311. package/dist/manager/routes/payments/intent/providers/test.js +341 -0
  312. package/dist/manager/routes/payments/plan/post.js +147 -0
  313. package/dist/manager/routes/payments/plan/providers/chargebee.js +62 -0
  314. package/dist/manager/routes/payments/plan/providers/paypal.js +62 -0
  315. package/dist/manager/routes/payments/plan/providers/stripe.js +59 -0
  316. package/dist/manager/routes/payments/plan/providers/test.js +113 -0
  317. package/dist/manager/routes/payments/portal/post.js +113 -0
  318. package/dist/manager/routes/payments/portal/providers/chargebee.js +62 -0
  319. package/dist/manager/routes/payments/portal/providers/paypal.js +25 -0
  320. package/dist/manager/routes/payments/portal/providers/stripe.js +62 -0
  321. package/dist/manager/routes/payments/portal/providers/test.js +18 -0
  322. package/dist/manager/routes/payments/refund/post.js +207 -0
  323. package/dist/manager/routes/payments/refund/providers/chargebee.js +147 -0
  324. package/dist/manager/routes/payments/refund/providers/coinbase.js +40 -0
  325. package/dist/manager/routes/payments/refund/providers/paypal.js +239 -0
  326. package/dist/manager/routes/payments/refund/providers/stripe.js +160 -0
  327. package/dist/manager/routes/payments/refund/providers/test.js +204 -0
  328. package/dist/manager/routes/payments/trial-eligibility/get.js +29 -0
  329. package/dist/manager/routes/payments/uncancel/post.js +106 -0
  330. package/dist/manager/routes/payments/uncancel/providers/chargebee.js +30 -0
  331. package/dist/manager/routes/payments/uncancel/providers/paypal.js +25 -0
  332. package/dist/manager/routes/payments/uncancel/providers/stripe.js +27 -0
  333. package/dist/manager/routes/payments/uncancel/providers/test.js +114 -0
  334. package/dist/manager/routes/payments/webhook/post.js +157 -0
  335. package/dist/manager/routes/payments/webhook/providers/chargebee.js +218 -0
  336. package/dist/manager/routes/payments/webhook/providers/coinbase.js +91 -0
  337. package/dist/manager/routes/payments/webhook/providers/paypal.js +214 -0
  338. package/dist/manager/routes/payments/webhook/providers/stripe.js +182 -0
  339. package/dist/manager/routes/payments/webhook/providers/test.js +18 -0
  340. package/dist/manager/routes/payments/winback/post.js +276 -0
  341. package/dist/manager/routes/payments/winback/providers/chargebee.js +24 -0
  342. package/dist/manager/routes/payments/winback/providers/paypal.js +22 -0
  343. package/dist/manager/routes/payments/winback/providers/stripe.js +41 -0
  344. package/dist/manager/routes/payments/winback/providers/test.js +42 -0
  345. package/dist/manager/routes/restart/index.js +37 -0
  346. package/dist/manager/routes/special/electron-client/post.js +59 -0
  347. package/dist/manager/routes/test/authenticate/get.js +11 -0
  348. package/dist/manager/routes/test/health/get.js +8 -0
  349. package/dist/manager/routes/test/index.js +16 -0
  350. package/dist/manager/routes/test/lab/post.js +17 -0
  351. package/dist/manager/routes/test/redirect/get.js +27 -0
  352. package/dist/manager/routes/test/reset-account/post.js +79 -0
  353. package/dist/manager/routes/test/roster/get.js +40 -0
  354. package/dist/manager/routes/test/schema/post.js +20 -0
  355. package/dist/manager/routes/test/usage/post.js +44 -0
  356. package/dist/manager/routes/test/webhook/post.js +21 -0
  357. package/dist/manager/routes/user/api-keys/post.js +51 -0
  358. package/dist/manager/routes/user/connections/_context.js +194 -0
  359. package/dist/manager/routes/user/connections/_grant.js +247 -0
  360. package/dist/manager/routes/user/connections/_lease.js +184 -0
  361. package/dist/manager/routes/user/connections/_providers.js +191 -0
  362. package/dist/manager/routes/user/connections/_state.js +117 -0
  363. package/dist/manager/routes/user/connections/delete.js +55 -0
  364. package/dist/manager/routes/user/connections/get.js +167 -0
  365. package/dist/manager/routes/user/connections/post.js +370 -0
  366. package/dist/manager/routes/user/connections/providers/discord.js +41 -0
  367. package/dist/manager/routes/user/connections/providers/google.js +55 -0
  368. package/dist/manager/routes/user/connections/providers/kick.js +68 -0
  369. package/dist/manager/routes/user/connections/providers/spotify.js +40 -0
  370. package/dist/manager/routes/user/connections/providers/twitch.js +52 -0
  371. package/dist/manager/routes/user/data-request/delete.js +84 -0
  372. package/dist/manager/routes/user/data-request/get.js +229 -0
  373. package/dist/manager/routes/user/data-request/post.js +136 -0
  374. package/dist/manager/routes/user/delete.js +149 -0
  375. package/dist/manager/routes/user/feedback/post.js +81 -0
  376. package/dist/manager/routes/user/get.js +14 -0
  377. package/dist/manager/routes/user/orders/get.js +95 -0
  378. package/dist/manager/routes/user/sessions/delete.js +95 -0
  379. package/dist/manager/routes/user/sessions/get.js +40 -0
  380. package/dist/manager/routes/user/settings/validate/post.js +86 -0
  381. package/dist/manager/routes/user/signup/post.js +662 -0
  382. package/dist/manager/routes/user/subscription/get.js +77 -0
  383. package/dist/manager/routes/user/token/post.js +30 -0
  384. package/dist/manager/routes/verts/delete.js +43 -0
  385. package/dist/manager/routes/verts/get.js +49 -0
  386. package/dist/manager/routes/verts/post.js +61 -0
  387. package/dist/manager/routes/verts/put.js +75 -0
  388. package/dist/manager/routes/verts/redirect/get.js +36 -0
  389. package/dist/manager/routes/verts/serve/get.js +67 -0
  390. package/dist/manager/routes/verts/utils.js +436 -0
  391. package/dist/manager/schemas/admin/backup/post.js +8 -0
  392. package/dist/manager/schemas/admin/cron/post.js +8 -0
  393. package/dist/manager/schemas/admin/database/get.js +5 -0
  394. package/dist/manager/schemas/admin/database/post.js +6 -0
  395. package/dist/manager/schemas/admin/email/post.js +31 -0
  396. package/dist/manager/schemas/admin/firestore/get.js +5 -0
  397. package/dist/manager/schemas/admin/firestore/post.js +8 -0
  398. package/dist/manager/schemas/admin/firestore/query/post.js +5 -0
  399. package/dist/manager/schemas/admin/hook/post.js +8 -0
  400. package/dist/manager/schemas/admin/infer-contact/post.js +6 -0
  401. package/dist/manager/schemas/admin/notification/post.js +23 -0
  402. package/dist/manager/schemas/admin/payment/post.js +8 -0
  403. package/dist/manager/schemas/admin/post/post.js +23 -0
  404. package/dist/manager/schemas/admin/post/put.js +13 -0
  405. package/dist/manager/schemas/admin/repo/content/post.js +10 -0
  406. package/dist/manager/schemas/admin/stats/get.js +5 -0
  407. package/dist/manager/schemas/admin/users/disable/post.js +9 -0
  408. package/dist/manager/schemas/admin/users/list/get.js +10 -0
  409. package/dist/manager/schemas/admin/users/sync/post.js +7 -0
  410. package/dist/manager/schemas/brand/get.js +3 -0
  411. package/dist/manager/schemas/content/post/get.js +8 -0
  412. package/dist/manager/schemas/general/email/post.js +10 -0
  413. package/dist/manager/schemas/general/uuid/post.js +9 -0
  414. package/dist/manager/schemas/handler/post/post.js +13 -0
  415. package/dist/manager/schemas/health/get.js +3 -0
  416. package/dist/manager/schemas/marketing/campaign/delete.js +8 -0
  417. package/dist/manager/schemas/marketing/campaign/get.js +13 -0
  418. package/dist/manager/schemas/marketing/campaign/post.js +43 -0
  419. package/dist/manager/schemas/marketing/campaign/put.js +37 -0
  420. package/dist/manager/schemas/marketing/contact/delete.js +8 -0
  421. package/dist/manager/schemas/marketing/contact/post.js +14 -0
  422. package/dist/manager/schemas/marketing/contact/put.js +8 -0
  423. package/dist/manager/schemas/marketing/email-preferences/post.js +15 -0
  424. package/dist/manager/schemas/marketing/webhook/forward/post.js +8 -0
  425. package/dist/manager/schemas/marketing/webhook/post.js +7 -0
  426. package/dist/manager/schemas/payments/cancel/post.js +15 -0
  427. package/dist/manager/schemas/payments/discount/get.js +8 -0
  428. package/dist/manager/schemas/payments/dispute-alert/post.js +6 -0
  429. package/dist/manager/schemas/payments/intent/post.js +28 -0
  430. package/dist/manager/schemas/payments/plan/post.js +11 -0
  431. package/dist/manager/schemas/payments/portal/post.js +12 -0
  432. package/dist/manager/schemas/payments/refund/post.js +14 -0
  433. package/dist/manager/schemas/payments/trial-eligibility/get.js +7 -0
  434. package/dist/manager/schemas/payments/uncancel/post.js +9 -0
  435. package/dist/manager/schemas/payments/webhook/post.js +6 -0
  436. package/dist/manager/schemas/payments/winback/post.js +12 -0
  437. package/dist/manager/schemas/restart/index.js +5 -0
  438. package/dist/manager/schemas/special/electron-client/post.js +11 -0
  439. package/dist/manager/schemas/test/authenticate/get.js +3 -0
  440. package/dist/manager/schemas/test/health/get.js +3 -0
  441. package/dist/manager/schemas/test/index.js +5 -0
  442. package/dist/manager/schemas/test/lab/post.js +3 -0
  443. package/dist/manager/schemas/test/redirect/get.js +7 -0
  444. package/dist/manager/schemas/test/reset-account/post.js +3 -0
  445. package/dist/manager/schemas/test/roster/get.js +3 -0
  446. package/dist/manager/schemas/test/schema/post.js +70 -0
  447. package/dist/manager/schemas/test/usage/post.js +8 -0
  448. package/dist/manager/schemas/test/webhook/post.js +7 -0
  449. package/dist/manager/schemas/user/api-keys/post.js +6 -0
  450. package/dist/manager/schemas/user/connections/delete.js +8 -0
  451. package/dist/manager/schemas/user/connections/get.js +21 -0
  452. package/dist/manager/schemas/user/connections/post.js +12 -0
  453. package/dist/manager/schemas/user/data-request/delete.js +3 -0
  454. package/dist/manager/schemas/user/data-request/get.js +5 -0
  455. package/dist/manager/schemas/user/data-request/post.js +6 -0
  456. package/dist/manager/schemas/user/delete.js +6 -0
  457. package/dist/manager/schemas/user/feedback/post.js +8 -0
  458. package/dist/manager/schemas/user/get.js +3 -0
  459. package/dist/manager/schemas/user/orders/get.js +6 -0
  460. package/dist/manager/schemas/user/sessions/delete.js +6 -0
  461. package/dist/manager/schemas/user/sessions/get.js +6 -0
  462. package/dist/manager/schemas/user/settings/validate/post.js +8 -0
  463. package/dist/manager/schemas/user/signup/post.js +27 -0
  464. package/dist/manager/schemas/user/subscription/get.js +5 -0
  465. package/dist/manager/schemas/user/token/post.js +5 -0
  466. package/dist/manager/schemas/verts/delete.js +8 -0
  467. package/dist/manager/schemas/verts/get.js +9 -0
  468. package/dist/manager/schemas/verts/post.js +23 -0
  469. package/dist/manager/schemas/verts/put.js +20 -0
  470. package/dist/manager/schemas/verts/redirect/get.js +10 -0
  471. package/dist/manager/schemas/verts/serve/get.js +14 -0
  472. package/dist/manager/server-manager.js +69 -0
  473. package/dist/mcp/client.js +98 -0
  474. package/dist/mcp/handler.js +521 -0
  475. package/dist/mcp/index.js +157 -0
  476. package/dist/mcp/tools.js +555 -0
  477. package/dist/mcp/utils.js +108 -0
  478. package/dist/omega-bin.js +6 -0
  479. package/dist/require.js +3 -0
  480. package/dist/test/fixtures/firebase-project/.firebaserc +5 -0
  481. package/dist/test/fixtures/firebase-project/config/omega.json5 +131 -0
  482. package/dist/test/fixtures/firebase-project/database.rules.json +6 -0
  483. package/dist/test/fixtures/firebase-project/firebase.json +49 -0
  484. package/dist/test/fixtures/firebase-project/firestore.indexes.json +4 -0
  485. package/dist/test/fixtures/firebase-project/package.json +14 -0
  486. package/dist/test/fixtures/firebase-project/src/index.js +11 -0
  487. package/dist/test/fixtures/firebase-project/storage.rules +8 -0
  488. package/dist/test/parse-audit.js +25 -0
  489. package/dist/test/run-tests.js +90 -0
  490. package/dist/test/runner.js +982 -0
  491. package/dist/test/seed.js +242 -0
  492. package/dist/test/test-accounts.js +2660 -0
  493. package/dist/test/utils/assertions.js +189 -0
  494. package/dist/test/utils/email-capture.js +252 -0
  495. package/dist/test/utils/extended-mode-warning.js +11 -0
  496. package/dist/test/utils/firestore-rules-client.js +277 -0
  497. package/dist/test/utils/http-client.js +228 -0
  498. package/dist/test/utils/test-mode-file.js +192 -0
  499. package/dist/utils/merge-line-files.js +6 -0
  500. package/dist/utils/scaffold-defaults.js +71 -0
  501. package/dist/utils/test-lanes.js +62 -0
  502. package/dist/vendor/account/engine.js +182 -0
  503. package/dist/vendor/account/features.js +220 -0
  504. package/dist/vendor/account/index.js +53 -0
  505. package/dist/vendor/account/schema.js +272 -0
  506. package/dist/vendor/account/subscription.js +38 -0
  507. package/dist/vendor/analytics/adapters/ga4.js +26 -0
  508. package/dist/vendor/analytics/adapters/meta.js +26 -0
  509. package/dist/vendor/analytics/adapters/resolve.js +130 -0
  510. package/dist/vendor/analytics/adapters/tiktok.js +27 -0
  511. package/dist/vendor/analytics/catalog.js +908 -0
  512. package/dist/vendor/analytics/identity.js +136 -0
  513. package/dist/vendor/config/company.js +31 -0
  514. package/dist/vendor/config/defaults.js +173 -0
  515. package/dist/vendor/config/demo.js +18 -0
  516. package/dist/vendor/config/desktop-artifacts.js +110 -0
  517. package/dist/vendor/config/edit.js +769 -0
  518. package/dist/vendor/config/env-delivery.js +145 -0
  519. package/dist/vendor/config/env-rules.js +93 -0
  520. package/dist/vendor/config/env-schema.js +1078 -0
  521. package/dist/vendor/config/env.js +445 -0
  522. package/dist/vendor/config/hooks.js +97 -0
  523. package/dist/vendor/config/index.js +237 -0
  524. package/dist/vendor/config/instances.js +208 -0
  525. package/dist/vendor/config/load.js +490 -0
  526. package/dist/vendor/config/merge.js +68 -0
  527. package/dist/vendor/config/order.js +139 -0
  528. package/dist/vendor/config/ports.js +374 -0
  529. package/dist/vendor/config/providers.js +32 -0
  530. package/dist/vendor/config/repo.js +142 -0
  531. package/dist/vendor/config/retired-keys.js +430 -0
  532. package/dist/vendor/config/schema.js +1610 -0
  533. package/dist/vendor/config/secrets.js +50 -0
  534. package/dist/vendor/config/seed.js +34 -0
  535. package/dist/vendor/config/site-global.js +205 -0
  536. package/dist/vendor/config/validate.js +554 -0
  537. package/dist/vendor/config/winback.js +61 -0
  538. package/dist/vendor/devkit/attach-log-file.js +262 -0
  539. package/dist/vendor/devkit/bare-requires.js +153 -0
  540. package/dist/vendor/devkit/command-path.js +46 -0
  541. package/dist/vendor/devkit/defaults-engine.js +372 -0
  542. package/dist/vendor/devkit/deploy-record.js +180 -0
  543. package/dist/vendor/devkit/deploy.js +286 -0
  544. package/dist/vendor/devkit/license.js +155 -0
  545. package/dist/vendor/devkit/local-https.js +360 -0
  546. package/dist/vendor/devkit/local.js +1905 -0
  547. package/dist/vendor/devkit/logger.js +128 -0
  548. package/dist/vendor/devkit/merge-line-files.js +296 -0
  549. package/dist/vendor/devkit/npm-registry.js +52 -0
  550. package/dist/vendor/devkit/omega-bin.js +345 -0
  551. package/dist/vendor/devkit/parse-audit.js +74 -0
  552. package/dist/vendor/devkit/safe-install.js +18 -0
  553. package/dist/vendor/devkit/scaffold-guard.js +96 -0
  554. package/dist/vendor/devkit/stop-signals.js +28 -0
  555. package/dist/vendor/devkit/test/assert.js +120 -0
  556. package/dist/vendor/devkit/test/define-cases.js +104 -0
  557. package/dist/vendor/devkit/test/runner-core.js +554 -0
  558. package/dist/vendor/devkit/test/scope.js +162 -0
  559. package/dist/vendor/devkit/update.js +569 -0
  560. package/dist/vendor/monitoring/core.js +180 -0
  561. package/dist/vendor/monitoring/env.js +49 -0
  562. package/dist/vendor/monitoring/logger.js +39 -0
  563. package/dist/vendor/monitoring/node.js +72 -0
  564. package/docs/admin-post-route.md +57 -0
  565. package/docs/ai-library.md +171 -0
  566. package/docs/architecture.md +62 -0
  567. package/docs/audit.md +70 -0
  568. package/docs/auth-hooks.md +74 -0
  569. package/docs/build-system.md +65 -0
  570. package/docs/cdp-debugging.md +29 -0
  571. package/docs/cli-firestore-auth.md +85 -0
  572. package/docs/cli-logs.md +67 -0
  573. package/docs/cli-output.md +146 -0
  574. package/docs/code-patterns.md +77 -0
  575. package/docs/common-mistakes.md +12 -0
  576. package/docs/common-operations.md +60 -0
  577. package/docs/connections.md +212 -0
  578. package/docs/consent.md +362 -0
  579. package/docs/directory-structure.md +150 -0
  580. package/docs/email-system.md +459 -0
  581. package/docs/environment-detection.md +93 -0
  582. package/docs/file-naming.md +10 -0
  583. package/docs/firestore.md +134 -0
  584. package/docs/ghostii.md +240 -0
  585. package/docs/index.md +299 -0
  586. package/docs/key-files.md +37 -0
  587. package/docs/logging.md +60 -0
  588. package/docs/marketing-campaigns.md +407 -0
  589. package/docs/marketing-fields.md +25 -0
  590. package/docs/mcp.md +222 -0
  591. package/docs/migration.md +129 -0
  592. package/docs/payment-system.md +856 -0
  593. package/docs/paypal-sandbox-qa.md +105 -0
  594. package/docs/response-headers.md +9 -0
  595. package/docs/routes.md +211 -0
  596. package/docs/sanitization.md +71 -0
  597. package/docs/schemas.md +163 -0
  598. package/docs/shared/agent-docs.md +89 -0
  599. package/docs/shared/analytics.md +612 -0
  600. package/docs/shared/brands.md +51 -0
  601. package/docs/shared/breaking-changes.md +497 -0
  602. package/docs/shared/config.md +1387 -0
  603. package/docs/shared/deploys.md +215 -0
  604. package/docs/shared/icons.md +201 -0
  605. package/docs/shared/local-dev.md +147 -0
  606. package/docs/shared/logging.md +202 -0
  607. package/docs/shared/monitoring.md +153 -0
  608. package/docs/shared/publishing.md +183 -0
  609. package/docs/shared/rulings.md +34 -0
  610. package/docs/shared/testing.md +147 -0
  611. package/docs/shared/theming.md +604 -0
  612. package/docs/shared/translation.md +291 -0
  613. package/docs/shared/updates.md +61 -0
  614. package/docs/stripe-webhook-forwarding.md +20 -0
  615. package/docs/test-boot-layer.md +67 -0
  616. package/docs/test-framework.md +583 -0
  617. package/docs/usage-rate-limiting.md +121 -0
  618. package/docs/verts.md +29 -0
  619. package/package.json +143 -0
  620. package/templates/config/omega.json5 +322 -0
  621. package/templates/database.rules.json +82 -0
  622. package/templates/firebase.json +67 -0
  623. package/templates/firestore.framework.rules +191 -0
  624. package/templates/firestore.indexes.json +4 -0
  625. package/templates/firestore.rules +59 -0
  626. package/templates/index.js +12 -0
  627. package/templates/public/404.html +26 -0
  628. package/templates/public/index.html +24 -0
  629. package/templates/remoteconfig.template.json +1 -0
  630. package/templates/storage-lifecycle-config-1-day.json +9 -0
  631. package/templates/storage-lifecycle-config-30-days.json +9 -0
  632. package/templates/storage.rules +11 -0
  633. package/test/_init/accounts-validation.js +58 -0
  634. package/test/ai/tools-live.test.js +171 -0
  635. package/test/analytics/conversion-delivery.test.js +1013 -0
  636. package/test/analytics/match-normalization.test.js +238 -0
  637. package/test/analytics/signup-conversion.test.js +135 -0
  638. package/test/boot/cli-dispatch.test.js +142 -0
  639. package/test/boot/defaults-scaffold.test.js +248 -0
  640. package/test/boot/deploy-staging.test.js +446 -0
  641. package/test/boot/disposable-domains.test.js +154 -0
  642. package/test/boot/emulator-boots.test.js +39 -0
  643. package/test/boot/emulator-port-preflight.test.js +126 -0
  644. package/test/boot/emulator-ready-timeout.test.js +46 -0
  645. package/test/boot/project-id-refresh.test.js +73 -0
  646. package/test/boot/suite-portability.test.js +278 -0
  647. package/test/boot/update-command.test.js +63 -0
  648. package/test/cli/cross-platform.test.js +168 -0
  649. package/test/cli/custom-project-type.test.js +426 -0
  650. package/test/cli/deploy-license-env.test.js +100 -0
  651. package/test/cli/emulator-adoption.test.js +254 -0
  652. package/test/cli/emulator-orphans.test.js +658 -0
  653. package/test/cli/emulator-ownership.test.js +177 -0
  654. package/test/cli/emulator-port-retry.test.js +395 -0
  655. package/test/cli/emulator-shutdown.test.js +788 -0
  656. package/test/cli/emulator-stale-reap.test.js +574 -0
  657. package/test/cli/ensure-target.test.js +325 -0
  658. package/test/cli/flags.test.js +64 -0
  659. package/test/cli/https-trust.test.js +38 -0
  660. package/test/cli/install-update-dispatch.test.js +47 -0
  661. package/test/cli/lane-environments.test.js +159 -0
  662. package/test/cli/marketing-campaigns-seeded.test.js +194 -0
  663. package/test/cli/migrate-bare-requires.test.js +128 -0
  664. package/test/cli/migrate-markers.test.js +530 -0
  665. package/test/cli/required-indexes.test.js +64 -0
  666. package/test/cli/rules-compile.test.js +622 -0
  667. package/test/cli/rules-migration-deferral.test.js +259 -0
  668. package/test/cli/rules-version.test.js +104 -0
  669. package/test/cli/setup-load-files.test.js +66 -0
  670. package/test/cli/setup-offline-mode.test.js +276 -0
  671. package/test/cli/setup-retired.test.js +64 -0
  672. package/test/cli/setup-shared-project-indexes.test.js +262 -0
  673. package/test/cli/setup-tests-requires.test.js +45 -0
  674. package/test/cli/stage-env-compose.test.js +316 -0
  675. package/test/cli/stripe-live-lane.test.js +303 -0
  676. package/test/cli/target.test.js +182 -0
  677. package/test/cli/templates.test.js +66 -0
  678. package/test/cli/test-runner-env.test.js +115 -0
  679. package/test/cli/test-stack-shutdown.test.js +275 -0
  680. package/test/cli/test-target-no-match.test.js +128 -0
  681. package/test/cli/verb-logs.test.js +118 -0
  682. package/test/cli/version-dispatch.test.js +39 -0
  683. package/test/content/blog-generate.test.js +164 -0
  684. package/test/email/campaign-config-fault.test.js +376 -0
  685. package/test/email/campaign-cron-pipeline.test.js +536 -0
  686. package/test/email/campaign-send.test.js +47 -0
  687. package/test/email/consent-lifecycle.test.js +258 -0
  688. package/test/email/content-html-policy.test.js +270 -0
  689. package/test/email/feedback-and-plain-send.test.js +54 -0
  690. package/test/email/fixtures/clean.json +30 -0
  691. package/test/email/fixtures/editorial.json +30 -0
  692. package/test/email/fixtures/field-report.json +53 -0
  693. package/test/email/identity.test.js +321 -0
  694. package/test/email/marketing/consent-gate.test.js +265 -0
  695. package/test/email/marketing/custom-fields-catalog.test.js +259 -0
  696. package/test/email/marketing/prune-per-provider.test.js +762 -0
  697. package/test/email/marketing/remove-log-privacy.test.js +225 -0
  698. package/test/email/marketing-lifecycle.test.js +139 -0
  699. package/test/email/newsletter-generate.test.js +853 -0
  700. package/test/email/newsletter-svg-tokens.test.js +51 -0
  701. package/test/email/newsletter-templates.test.js +492 -0
  702. package/test/email/order-one-time-cta.test.js +97 -0
  703. package/test/email/render-content.test.js +248 -0
  704. package/test/email/safe-url.test.js +0 -0
  705. package/test/email/sanitize-images.test.js +66 -0
  706. package/test/email/send-log-privacy.test.js +107 -0
  707. package/test/email/templates.test.js +288 -0
  708. package/test/email/testing-capture.test.js +260 -0
  709. package/test/email/transactional-send.test.js +36 -0
  710. package/test/email/transactional.test.js +562 -0
  711. package/test/email/unsubscribe-groups.test.js +155 -0
  712. package/test/email/unsubscribe-key.test.js +91 -0
  713. package/test/email/validation-cases.test.js +152 -0
  714. package/test/email/validation.test.js +746 -0
  715. package/test/events/auth-delete-conversion.test.js +138 -0
  716. package/test/events/auth-delete-race.test.js +211 -0
  717. package/test/events/auth-on-create-log.test.js +164 -0
  718. package/test/events/auth-signup-conversion.test.js +141 -0
  719. package/test/events/auth-signup-limit.test.js +142 -0
  720. package/test/events/auth-trigger-log-privacy.test.js +256 -0
  721. package/test/events/cron-job-doc-shape.test.js +101 -0
  722. package/test/events/cron-reset-usage.test.js +116 -0
  723. package/test/events/notification-conversion.test.js +182 -0
  724. package/test/events/payments/_webhook-harness.js +307 -0
  725. package/test/events/payments/abandoned-cart-activity.test.js +135 -0
  726. package/test/events/payments/analytics-payment-events.test.js +945 -0
  727. package/test/events/payments/dispute-email-status.test.js +55 -0
  728. package/test/events/payments/journey-payments-abandoned.test.js +126 -0
  729. package/test/events/payments/journey-payments-cancel-endpoint.test.js +102 -0
  730. package/test/events/payments/journey-payments-cancel-no-order.test.js +76 -0
  731. package/test/events/payments/journey-payments-cancel.test.js +184 -0
  732. package/test/events/payments/journey-payments-decline.test.js +225 -0
  733. package/test/events/payments/journey-payments-discount.test.js +91 -0
  734. package/test/events/payments/journey-payments-dispute.test.js +190 -0
  735. package/test/events/payments/journey-payments-failure.test.js +148 -0
  736. package/test/events/payments/journey-payments-legacy-product.test.js +151 -0
  737. package/test/events/payments/journey-payments-one-time-decline.test.js +127 -0
  738. package/test/events/payments/journey-payments-one-time-failure.test.js +113 -0
  739. package/test/events/payments/journey-payments-one-time-refund.test.js +186 -0
  740. package/test/events/payments/journey-payments-one-time.test.js +175 -0
  741. package/test/events/payments/journey-payments-plan-change.test.js +146 -0
  742. package/test/events/payments/journey-payments-plan-switch-trial.test.js +161 -0
  743. package/test/events/payments/journey-payments-plan-switch.test.js +182 -0
  744. package/test/events/payments/journey-payments-refund-no-order.test.js +76 -0
  745. package/test/events/payments/journey-payments-refund-webhook.test.js +225 -0
  746. package/test/events/payments/journey-payments-suspend.test.js +183 -0
  747. package/test/events/payments/journey-payments-trial-cancel.test.js +132 -0
  748. package/test/events/payments/journey-payments-trial.test.js +173 -0
  749. package/test/events/payments/journey-payments-uid-resolution.test.js +134 -0
  750. package/test/events/payments/journey-payments-uncancel.test.js +157 -0
  751. package/test/events/payments/journey-payments-upgrade.test.js +137 -0
  752. package/test/events/payments/journey-payments-winback-decline.test.js +171 -0
  753. package/test/events/payments/journey-payments-winback.test.js +195 -0
  754. package/test/events/payments/paypal-expiry-cron.test.js +166 -0
  755. package/test/events/payments/purchase-failed-handler.test.js +80 -0
  756. package/test/events/payments/purchase-refunded-handler.test.js +108 -0
  757. package/test/events/payments/test-processor-doc-shape.test.js +162 -0
  758. package/test/events/payments/transition-order-emails.test.js +185 -0
  759. package/test/events/payments/transition-promo-lines.test.js +192 -0
  760. package/test/events/payments/transitions-detect.test.js +767 -0
  761. package/test/events/payments/trial-lapse-sweep-staleness.test.js +173 -0
  762. package/test/events/payments/trial-lapse-sweep.test.js +359 -0
  763. package/test/events/payments/webhook-atomic-writes.test.js +69 -0
  764. package/test/events/payments/webhook-chargebee-unreachable.test.js +106 -0
  765. package/test/events/payments/webhook-discount-clear.test.js +189 -0
  766. package/test/events/payments/webhook-failure-intent.test.js +93 -0
  767. package/test/events/payments/webhook-hosted-page-uid-trust.test.js +212 -0
  768. package/test/events/payments/webhook-ordering.test.js +326 -0
  769. package/test/events/payments/webhook-provider-lookup-trust.test.js +206 -0
  770. package/test/events/payments/webhook-refund-amount-trust.test.js +223 -0
  771. package/test/events/payments/webhook-refund-envelope-shape.test.js +148 -0
  772. package/test/events/payments/webhook-refund-linkage-trust.test.js +404 -0
  773. package/test/events/payments/webhook-refund-transaction-fallback.test.js +184 -0
  774. package/test/events/payments/webhook-refund-without-order.test.js +170 -0
  775. package/test/events/payments/webhook-refusal-reporting.test.js +194 -0
  776. package/test/events/payments/webhook-refused-intent.test.js +142 -0
  777. package/test/events/payments/webhook-retry-sweep.test.js +252 -0
  778. package/test/events/payments/webhook-transition-claim.test.js +389 -0
  779. package/test/events/payments/webhook-uid-trust.test.js +203 -0
  780. package/test/events/payments/webhook-user-without-auth.test.js +161 -0
  781. package/test/fixtures/chargebee/invoice-one-time.json +27 -0
  782. package/test/fixtures/chargebee/subscription-active.json +44 -0
  783. package/test/fixtures/chargebee/subscription-cancelled.json +42 -0
  784. package/test/fixtures/chargebee/subscription-in-trial.json +41 -0
  785. package/test/fixtures/chargebee/subscription-legacy-plan.json +41 -0
  786. package/test/fixtures/chargebee/subscription-non-renewing.json +41 -0
  787. package/test/fixtures/chargebee/subscription-paused.json +42 -0
  788. package/test/fixtures/chargebee/webhook-payment-failed.json +51 -0
  789. package/test/fixtures/chargebee/webhook-subscription-created.json +47 -0
  790. package/test/fixtures/coinbase/charge-confirmed.json +50 -0
  791. package/test/fixtures/coinbase/charge-failed.json +34 -0
  792. package/test/fixtures/coinbase/charge-pending.json +34 -0
  793. package/test/fixtures/migrate/ported-route.js.txt +11 -0
  794. package/test/fixtures/paypal/capture-completed.json +51 -0
  795. package/test/fixtures/paypal/capture-refunded.json +43 -0
  796. package/test/fixtures/paypal/order-approved.json +62 -0
  797. package/test/fixtures/paypal/order-completed.json +110 -0
  798. package/test/fixtures/paypal/sale-refunded.json +38 -0
  799. package/test/fixtures/paypal/subscription-active.json +76 -0
  800. package/test/fixtures/paypal/subscription-cancelled.json +50 -0
  801. package/test/fixtures/paypal/subscription-suspended.json +65 -0
  802. package/test/fixtures/stripe/checkout-session-completed.json +130 -0
  803. package/test/fixtures/stripe/invoice-payment-failed.json +148 -0
  804. package/test/fixtures/stripe/invoice-subscription-payment-failed.json +28 -0
  805. package/test/fixtures/stripe/invoice-subscription-payment-succeeded.json +28 -0
  806. package/test/fixtures/stripe/subscription-active.json +161 -0
  807. package/test/fixtures/stripe/subscription-canceled.json +161 -0
  808. package/test/fixtures/stripe/subscription-trialing.json +161 -0
  809. package/test/helpers/_shared-config.js +15 -0
  810. package/test/helpers/ai-request-payload.test.js +619 -0
  811. package/test/helpers/ai-schema-resolve.test.js +125 -0
  812. package/test/helpers/ai-test-provider.test.js +227 -0
  813. package/test/helpers/ai-token-accounting.test.js +150 -0
  814. package/test/helpers/ai-tools-format.test.js +384 -0
  815. package/test/helpers/analytics-no-id-notice.test.js +146 -0
  816. package/test/helpers/analytics-user-data.test.js +194 -0
  817. package/test/helpers/api-manager.test.js +311 -0
  818. package/test/helpers/backend-router.test.js +136 -0
  819. package/test/helpers/content/blog-auto-publisher.test.js +482 -0
  820. package/test/helpers/content/feed-parser.test.js +529 -0
  821. package/test/helpers/content/ghostii-blocks.test.js +135 -0
  822. package/test/helpers/content/ghostii-feed-integration.test.js +405 -0
  823. package/test/helpers/content/ghostii-write-article.test.js +244 -0
  824. package/test/helpers/dev-only-routes.test.js +225 -0
  825. package/test/helpers/env-reader.test.js +362 -0
  826. package/test/helpers/environment.test.js +263 -0
  827. package/test/helpers/event-middleware.test.js +407 -0
  828. package/test/helpers/infer-contact.test.js +157 -0
  829. package/test/helpers/lane-url.test.js +90 -0
  830. package/test/helpers/merge-line-files.test.js +272 -0
  831. package/test/helpers/metadata.test.js +122 -0
  832. package/test/helpers/middleware-request-log.test.js +134 -0
  833. package/test/helpers/middleware-user-log.test.js +124 -0
  834. package/test/helpers/payment/chargebee/parse-webhook.test.js +490 -0
  835. package/test/helpers/payment/chargebee/refund-details.test.js +198 -0
  836. package/test/helpers/payment/chargebee/to-unified-one-time.test.js +148 -0
  837. package/test/helpers/payment/chargebee/to-unified-subscription.test.js +649 -0
  838. package/test/helpers/payment/coinbase/create-intent.test.js +247 -0
  839. package/test/helpers/payment/coinbase/parse-webhook.test.js +195 -0
  840. package/test/helpers/payment/coinbase/refund-unsupported.test.js +141 -0
  841. package/test/helpers/payment/coinbase/to-unified-one-time.test.js +201 -0
  842. package/test/helpers/payment/discount-codes.test.js +141 -0
  843. package/test/helpers/payment/extract-resource.test.js +113 -0
  844. package/test/helpers/payment/fetch-failure.test.js +215 -0
  845. package/test/helpers/payment/license-gate.test.js +108 -0
  846. package/test/helpers/payment/order-id.test.js +99 -0
  847. package/test/helpers/payment/paypal/create-intent.test.js +382 -0
  848. package/test/helpers/payment/paypal/fetch-capture.test.js +123 -0
  849. package/test/helpers/payment/paypal/fetch-sale.test.js +183 -0
  850. package/test/helpers/payment/paypal/parse-webhook.test.js +678 -0
  851. package/test/helpers/payment/paypal/refund-details.test.js +202 -0
  852. package/test/helpers/payment/paypal/resolve-plan-id.test.js +108 -0
  853. package/test/helpers/payment/paypal/switch-plan.test.js +92 -0
  854. package/test/helpers/payment/paypal/to-unified-one-time.test.js +383 -0
  855. package/test/helpers/payment/paypal/to-unified-subscription.test.js +884 -0
  856. package/test/helpers/payment/stripe/fetch-charge.test.js +147 -0
  857. package/test/helpers/payment/stripe/parse-webhook.test.js +448 -0
  858. package/test/helpers/payment/stripe/refund-details.test.js +147 -0
  859. package/test/helpers/payment/stripe/to-unified-one-time.test.js +307 -0
  860. package/test/helpers/payment/stripe/to-unified-subscription.test.js +709 -0
  861. package/test/helpers/persona-domain.test.js +87 -0
  862. package/test/helpers/recaptcha.test.js +166 -0
  863. package/test/helpers/resolved-config.test.js +106 -0
  864. package/test/helpers/response-log-redaction.test.js +262 -0
  865. package/test/helpers/roles.test.js +155 -0
  866. package/test/helpers/route-context-debug-gate.test.js +118 -0
  867. package/test/helpers/route-context-logging.test.js +214 -0
  868. package/test/helpers/route-context.test.js +129 -0
  869. package/test/helpers/safe-compare.test.js +114 -0
  870. package/test/helpers/sanitize.test.js +223 -0
  871. package/test/helpers/schema-engine.test.js +537 -0
  872. package/test/helpers/schema-zod.test.js +554 -0
  873. package/test/helpers/seed-accounts-load.test.js +163 -0
  874. package/test/helpers/seed-google-personas.test.js +114 -0
  875. package/test/helpers/seeded-personas.test.js +823 -0
  876. package/test/helpers/settings.test.js +292 -0
  877. package/test/helpers/setup-engines-pin.test.js +68 -0
  878. package/test/helpers/setup-manifest-sync.test.js +116 -0
  879. package/test/helpers/slugify.test.js +395 -0
  880. package/test/helpers/storage.test.js +195 -0
  881. package/test/helpers/test-banner-latch.test.js +139 -0
  882. package/test/helpers/usage-consume.test.js +460 -0
  883. package/test/helpers/usage-log-privacy.test.js +121 -0
  884. package/test/helpers/user-doc-heal.test.js +540 -0
  885. package/test/helpers/user.test.js +771 -0
  886. package/test/helpers/webhook-forward.test.js +419 -0
  887. package/test/helpers/wipe-auth-project.test.js +143 -0
  888. package/test/mcp/discovery.test.js +52 -0
  889. package/test/mcp/oauth.test.js +160 -0
  890. package/test/mcp/protocol.test.js +278 -0
  891. package/test/mcp/roles.test.js +203 -0
  892. package/test/mcp/utils.test.js +245 -0
  893. package/test/notification/identity.test.js +155 -0
  894. package/test/routes/admin/create-post.test.js +362 -0
  895. package/test/routes/admin/database.test.js +133 -0
  896. package/test/routes/admin/deduplicate-image-alts.test.js +191 -0
  897. package/test/routes/admin/email-content-html.test.js +58 -0
  898. package/test/routes/admin/email-request-log.test.js +86 -0
  899. package/test/routes/admin/email.test.js +116 -0
  900. package/test/routes/admin/firestore-query.test.js +206 -0
  901. package/test/routes/admin/firestore.test.js +129 -0
  902. package/test/routes/admin/infer-contact.test.js +220 -0
  903. package/test/routes/admin/notification.test.js +199 -0
  904. package/test/routes/admin/post-convert-image.test.js +159 -0
  905. package/test/routes/admin/post-deploy-flag.test.js +81 -0
  906. package/test/routes/admin/post-download-error.test.js +90 -0
  907. package/test/routes/admin/post-resize-image.test.js +185 -0
  908. package/test/routes/admin/post.test.js +369 -0
  909. package/test/routes/admin/repo-content.test.js +223 -0
  910. package/test/routes/admin/stats.test.js +114 -0
  911. package/test/routes/admin/users-disable.test.js +63 -0
  912. package/test/routes/admin/users-list.test.js +71 -0
  913. package/test/routes/content/post.test.js +60 -0
  914. package/test/routes/general/uuid.test.js +133 -0
  915. package/test/routes/health.test.js +110 -0
  916. package/test/routes/marketing/campaign.test.js +184 -0
  917. package/test/routes/marketing/contact.test.js +416 -0
  918. package/test/routes/marketing/email-preferences.test.js +293 -0
  919. package/test/routes/marketing/push-send.test.js +34 -0
  920. package/test/routes/marketing/webhook-forward.test.js +63 -0
  921. package/test/routes/marketing/webhook.test.js +641 -0
  922. package/test/routes/payments/_route-harness.js +145 -0
  923. package/test/routes/payments/cancel-provider-errors.test.js +227 -0
  924. package/test/routes/payments/cancel-skip-guards.test.js +126 -0
  925. package/test/routes/payments/cancel-trialing.test.js +428 -0
  926. package/test/routes/payments/cancel.test.js +165 -0
  927. package/test/routes/payments/dedup-race.test.js +186 -0
  928. package/test/routes/payments/discount.test.js +82 -0
  929. package/test/routes/payments/dispute-alert.test.js +324 -0
  930. package/test/routes/payments/intent-discount-amounts.test.js +550 -0
  931. package/test/routes/payments/intent-discount-coupons.test.js +293 -0
  932. package/test/routes/payments/intent-one-time-metadata.test.js +122 -0
  933. package/test/routes/payments/intent-purchaser-guard.test.js +139 -0
  934. package/test/routes/payments/intent-zero-total.test.js +260 -0
  935. package/test/routes/payments/intent.test.js +413 -0
  936. package/test/routes/payments/plan.test.js +367 -0
  937. package/test/routes/payments/portal-return-url.test.js +107 -0
  938. package/test/routes/payments/portal.test.js +94 -0
  939. package/test/routes/payments/refund-one-time.test.js +254 -0
  940. package/test/routes/payments/refund-paypal-proration.test.js +190 -0
  941. package/test/routes/payments/refund.test.js +181 -0
  942. package/test/routes/payments/trial-eligibility.test.js +73 -0
  943. package/test/routes/payments/uncancel.test.js +270 -0
  944. package/test/routes/payments/webhook-stripe-invoice.test.js +129 -0
  945. package/test/routes/payments/webhook-stripe-refund-one-time.test.js +130 -0
  946. package/test/routes/payments/webhook-test-provider.test.js +156 -0
  947. package/test/routes/payments/webhook.test.js +115 -0
  948. package/test/routes/payments/winback-stripe-coupon.test.js +126 -0
  949. package/test/routes/payments/winback.test.js +597 -0
  950. package/test/routes/test/authenticate.test.js +79 -0
  951. package/test/routes/test/redirect.test.js +66 -0
  952. package/test/routes/test/reset-account.test.js +126 -0
  953. package/test/routes/test/roster.test.js +101 -0
  954. package/test/routes/test/schema.test.js +556 -0
  955. package/test/routes/test/usage.test.js +374 -0
  956. package/test/routes/user/api-keys.test.js +158 -0
  957. package/test/routes/user/connections-grant.test.js +759 -0
  958. package/test/routes/user/connections-identity.test.js +260 -0
  959. package/test/routes/user/connections-log-privacy.test.js +206 -0
  960. package/test/routes/user/connections-refresh-lease.test.js +696 -0
  961. package/test/routes/user/connections-return.test.js +261 -0
  962. package/test/routes/user/connections-uid.test.js +516 -0
  963. package/test/routes/user/delete.test.js +138 -0
  964. package/test/routes/user/feedback.test.js +106 -0
  965. package/test/routes/user/orders.test.js +233 -0
  966. package/test/routes/user/sessions.test.js +231 -0
  967. package/test/routes/user/settings-validate.test.js +84 -0
  968. package/test/routes/user/signup-emails.test.js +113 -0
  969. package/test/routes/user/signup-location.test.js +84 -0
  970. package/test/routes/user/signup-log-privacy.test.js +112 -0
  971. package/test/routes/user/signup.test.js +579 -0
  972. package/test/routes/user/subscription.test.js +101 -0
  973. package/test/routes/user/token.test.js +111 -0
  974. package/test/routes/user/user.test.js +158 -0
  975. package/test/routes/verts/cache.test.js +78 -0
  976. package/test/routes/verts/click-destination.test.js +76 -0
  977. package/test/routes/verts/crud.test.js +194 -0
  978. package/test/routes/verts/redirect.test.js +147 -0
  979. package/test/routes/verts/selection.test.js +237 -0
  980. package/test/routes/verts/serve.test.js +185 -0
  981. package/test/routes/verts/unit-document.test.js +117 -0
  982. package/test/rules/_environment.js +66 -0
  983. package/test/rules/brand-merge.test.js +195 -0
  984. package/test/rules/field-helpers.test.js +309 -0
  985. package/test/rules/notifications.test.js +577 -0
  986. package/test/rules/payments-carts.test.js +406 -0
  987. package/test/rules/sessions.test.js +170 -0
  988. package/test/rules/user.test.js +517 -0
  989. package/test/rules/verts.test.js +101 -0
  990. package/test/security/fetch-log-secrets.test.js +124 -0
  991. package/test/security/primitives.test.js +105 -0
  992. package/test/security/repo-pinning.test.js +77 -0
  993. 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
+ });