@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,767 @@
1
+ /**
2
+ * Test: payment transition detection
3
+ * Unit tests for detectSubscriptionTransition() — a pure function over the
4
+ * before/after unified subscription plus the webhook's event type.
5
+ *
6
+ * Covers the two things the event type alone decides:
7
+ * 1. Refund detection, which must know EVERY provider's refund event string
8
+ * (the strings are pinned against each provider's own webhook parser here,
9
+ * so a parser that renames one fails this test instead of silently dropping
10
+ * the customer's refund email)
11
+ * 2. Refund idempotency — a webhook doc that already completed once must not
12
+ * dispatch payment-refunded (and its email) a second time
13
+ * 3. The one-time side of the same question — detectOneTimeTransition() reads
14
+ * the event type alone, so EVERY one-time transition needs that same
15
+ * idempotency guard, not just the refund
16
+ *
17
+ * And the state rules, where ORDER inside the array is the behavior: each rule
18
+ * added here is proved to fire AND proved not to shadow the rule it sits in
19
+ * front of ([#212](https://github.com/Omega-JS-Stack/omega/issues/212)).
20
+ */
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const transitions = require('../../../dist/manager/events/firestore/payments-webhooks/transitions/index.js');
24
+ const stripeProvider = require('../../../dist/manager/routes/payments/webhook/providers/stripe.js');
25
+ const paypalProvider = require('../../../dist/manager/routes/payments/webhook/providers/paypal.js');
26
+ const chargebeeProvider = require('../../../dist/manager/routes/payments/webhook/providers/chargebee.js');
27
+ const coinbaseProvider = require('../../../dist/manager/routes/payments/webhook/providers/coinbase.js');
28
+ const defineCases = require('../../../dist/vendor/devkit/test/define-cases.js');
29
+
30
+ // Every transition name the detector can return, and the category whose folder
31
+ // holds its handler file
32
+ const HANDLED_TRANSITIONS = [
33
+ ['subscription', 'new-subscription'],
34
+ ['subscription', 'subscription-winback'],
35
+ ['subscription', 'checkout-declined'],
36
+ ['subscription', 'payment-failed'],
37
+ ['subscription', 'payment-recovered'],
38
+ ['subscription', 'cancellation-requested'],
39
+ ['subscription', 'cancellation-removed'],
40
+ ['subscription', 'subscription-cancelled'],
41
+ ['subscription', 'plan-changed'],
42
+ ['subscription', 'payment-refunded'],
43
+ ['one-time', 'purchase-completed'],
44
+ ['one-time', 'purchase-failed'],
45
+ ['one-time', 'purchase-refunded'],
46
+ ];
47
+
48
+ // A paid, active unified subscription — the shape a refund webhook arrives against
49
+ function paidSubscription() {
50
+ return {
51
+ product: { id: 'premium', name: 'Premium' },
52
+ status: 'active',
53
+ cancellation: { pending: false },
54
+ payment: { frequency: 'monthly', price: 9.99 },
55
+ };
56
+ }
57
+
58
+ // A user doc's birth state: active on basic, never subscribed to anything
59
+ function basicSubscription() {
60
+ return {
61
+ product: { id: 'basic', name: 'Basic' },
62
+ status: 'active',
63
+ cancellation: { pending: false },
64
+ payment: {},
65
+ };
66
+ }
67
+
68
+ // A fully cancelled paid subscriber — the state a win-back starts from
69
+ function cancelledSubscription() {
70
+ return {
71
+ product: { id: 'premium', name: 'Premium' },
72
+ status: 'cancelled',
73
+ cancellation: { pending: false },
74
+ payment: { frequency: 'monthly', price: 9.99 },
75
+ };
76
+ }
77
+
78
+ module.exports = defineCases({
79
+ description: 'Payment transition detection (state rules, refund events + idempotency)',
80
+ type: 'group',
81
+
82
+ tests: [
83
+ // ─── State rules: win-back ───
84
+
85
+ {
86
+ name: 'detects-a-win-back',
87
+ async run({ assert }) {
88
+ // A subscriber who cancelled outright and came back: the product id stays
89
+ // paid through the cancellation, so no rule but this one describes it
90
+ const detected = transitions.detectSubscriptionTransition(cancelledSubscription(), paidSubscription(), 'customer.subscription.created');
91
+
92
+ assert.equal(detected, 'subscription-winback', 'cancelled paid → active paid should detect subscription-winback');
93
+ },
94
+ },
95
+
96
+ {
97
+ name: 'a-first-subscription-is-still-new-subscription',
98
+ async run({ assert }) {
99
+ // The win-back rule sits behind new-subscription — a user arriving from
100
+ // basic is still buying for the first time
101
+ const detected = transitions.detectSubscriptionTransition(basicSubscription(), paidSubscription(), 'customer.subscription.created');
102
+
103
+ assert.equal(detected, 'new-subscription', 'basic → active paid should still detect new-subscription');
104
+ },
105
+ },
106
+
107
+ {
108
+ name: 'a-cancelled-basic-user-subscribing-is-still-new-subscription',
109
+ async run({ assert }) {
110
+ // A cancellation that also downgraded the product leaves nothing to win
111
+ // back — that user reads as a first purchase, as it always has
112
+ const before = cancelledSubscription();
113
+ before.product = { id: 'basic', name: 'Basic' };
114
+
115
+ const detected = transitions.detectSubscriptionTransition(before, paidSubscription(), 'customer.subscription.created');
116
+
117
+ assert.equal(detected, 'new-subscription', 'cancelled basic → active paid should still detect new-subscription');
118
+ },
119
+ },
120
+
121
+ // ─── State rules: checkout decline ───
122
+
123
+ {
124
+ name: 'detects-a-first-checkout-decline',
125
+ async run({ assert }) {
126
+ // A user doc is born active on basic, so a declined FIRST checkout is
127
+ // basic active → suspended: a checkout the user is watching fail, not a
128
+ // renewal that needs dunning
129
+ const after = paidSubscription();
130
+ after.status = 'suspended';
131
+
132
+ const detected = transitions.detectSubscriptionTransition(basicSubscription(), after, 'customer.subscription.created');
133
+
134
+ assert.equal(detected, 'checkout-declined', 'basic → suspended should detect checkout-declined');
135
+ },
136
+ },
137
+
138
+ {
139
+ name: 'a-new-user-declining-is-also-a-checkout-decline',
140
+ async run({ assert }) {
141
+ // No user doc at all (no before state) is the same case
142
+ const after = paidSubscription();
143
+ after.status = 'suspended';
144
+
145
+ const detected = transitions.detectSubscriptionTransition(null, after, 'customer.subscription.created');
146
+
147
+ assert.equal(detected, 'checkout-declined', 'null → suspended should detect checkout-declined');
148
+ },
149
+ },
150
+
151
+ {
152
+ name: 'detects-a-declined-win-back-checkout',
153
+ async run({ assert }) {
154
+ // The win-back branch of the same moment: a full cancellation leaves the
155
+ // PAID product id on the user doc, so a returning subscriber whose payment
156
+ // declines is cancelled paid → suspended — not the rule above (before must
157
+ // be basic), not subscription-winback (after must be active), not
158
+ // payment-failed (before must be active). It matched nothing at all, and
159
+ // the webhook completed with no log and no analytics
160
+ // ([#223](https://github.com/Omega-JS-Stack/omega/issues/223))
161
+ const after = paidSubscription();
162
+ after.status = 'suspended';
163
+
164
+ const detected = transitions.detectSubscriptionTransition(cancelledSubscription(), after, 'customer.subscription.created');
165
+
166
+ assert.equal(detected, 'checkout-declined', 'cancelled paid → suspended should detect checkout-declined');
167
+ },
168
+ },
169
+
170
+ {
171
+ name: 'a-returning-subscriber-who-pays-is-still-a-win-back',
172
+ async run({ assert }) {
173
+ // The declined-win-back rule sits BEHIND subscription-winback — the same
174
+ // before state, and only the outcome tells them apart
175
+ const detected = transitions.detectSubscriptionTransition(cancelledSubscription(), paidSubscription(), 'customer.subscription.created');
176
+
177
+ assert.equal(detected, 'subscription-winback', 'cancelled paid → active paid should still detect subscription-winback');
178
+ },
179
+ },
180
+
181
+ {
182
+ name: 'a-paying-subscriber-still-gets-payment-failed',
183
+ async run({ assert }) {
184
+ // The decline rule sits in FRONT of payment-failed, so this is the one
185
+ // that proves it did not swallow the dunning case it precedes
186
+ const after = paidSubscription();
187
+ after.status = 'suspended';
188
+
189
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), after, 'invoice.payment_failed');
190
+
191
+ assert.equal(detected, 'payment-failed', 'active paid → suspended should still detect payment-failed');
192
+ },
193
+ },
194
+
195
+ {
196
+ name: 'a-second-decline-event-fires-nothing',
197
+ async run({ assert }) {
198
+ // The declined checkout's failed invoice arrives after the subscription
199
+ // event already suspended the user — suspended → suspended, no repeat
200
+ const before = paidSubscription();
201
+ before.status = 'suspended';
202
+ const after = paidSubscription();
203
+ after.status = 'suspended';
204
+
205
+ const detected = transitions.detectSubscriptionTransition(before, after, 'invoice.payment_failed');
206
+
207
+ assert.equal(detected, null, 'suspended → suspended should detect nothing');
208
+ },
209
+ },
210
+
211
+ // ─── State rules: recovery ───
212
+
213
+ {
214
+ name: 'detects-a-plain-recovery',
215
+ async run({ assert }) {
216
+ // Dunning's happy ending, with no cancellation schedule in the way:
217
+ // the retry succeeded and the provider put the subscriber back
218
+ const before = paidSubscription();
219
+ before.status = 'suspended';
220
+
221
+ const detected = transitions.detectSubscriptionTransition(before, paidSubscription(), 'invoice.payment_succeeded');
222
+
223
+ assert.equal(detected, 'payment-recovered', 'suspended → active should detect payment-recovered');
224
+ },
225
+ },
226
+
227
+ // ─── State rules: cancellation withdrawn ───
228
+
229
+ {
230
+ name: 'detects-a-cancellation-being-withdrawn',
231
+ async run({ assert }) {
232
+ const before = paidSubscription();
233
+ before.cancellation = { pending: true };
234
+
235
+ const detected = transitions.detectSubscriptionTransition(before, paidSubscription(), 'customer.subscription.updated');
236
+
237
+ assert.equal(detected, 'cancellation-removed', 'pending true → false while active should detect cancellation-removed');
238
+ },
239
+ },
240
+
241
+ {
242
+ name: 'a-cancellation-being-scheduled-is-still-cancellation-requested',
243
+ async run({ assert }) {
244
+ const after = paidSubscription();
245
+ after.cancellation = { pending: true };
246
+
247
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), after, 'customer.subscription.updated');
248
+
249
+ assert.equal(detected, 'cancellation-requested', 'pending false → true should still detect cancellation-requested');
250
+ },
251
+ },
252
+
253
+ {
254
+ name: 'a-recovery-that-clears-a-pending-cancellation-is-still-payment-recovered',
255
+ async run({ assert }) {
256
+ // The withdrawal rule sits BEHIND payment-recovered — money moving is the
257
+ // more meaningful half of a suspended subscriber coming back
258
+ const before = paidSubscription();
259
+ before.status = 'suspended';
260
+ before.cancellation = { pending: true };
261
+
262
+ const detected = transitions.detectSubscriptionTransition(before, paidSubscription(), 'invoice.payment_succeeded');
263
+
264
+ assert.equal(detected, 'payment-recovered', 'suspended → active should still detect payment-recovered');
265
+ },
266
+ },
267
+
268
+ {
269
+ name: 'a-plan-change-that-clears-a-pending-cancellation-is-still-plan-changed',
270
+ async run({ assert }) {
271
+ // Same product is part of the rule, so switching plans stays a plan change
272
+ const before = paidSubscription();
273
+ before.cancellation = { pending: true };
274
+ const after = paidSubscription();
275
+ after.product = { id: 'pro', name: 'Pro' };
276
+
277
+ const detected = transitions.detectSubscriptionTransition(before, after, 'customer.subscription.updated');
278
+
279
+ assert.equal(detected, 'plan-changed', 'A different product should still detect plan-changed');
280
+ },
281
+ },
282
+
283
+ // ─── State rules: cancellation ───
284
+
285
+ {
286
+ name: 'detects-a-cancellation',
287
+ async run({ assert }) {
288
+ // The term actually ended (or the provider deleted the subscription):
289
+ // any non-cancelled state arriving at cancelled is the one rule for it
290
+ const after = paidSubscription();
291
+ after.status = 'cancelled';
292
+
293
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), after, 'customer.subscription.deleted');
294
+
295
+ assert.equal(detected, 'subscription-cancelled', 'active paid → cancelled should detect subscription-cancelled');
296
+ },
297
+ },
298
+
299
+ {
300
+ name: 'a-cancellation-that-also-drops-the-product-is-still-a-cancellation',
301
+ async run({ assert }) {
302
+ // The cancel route writes cancelled AND resets to basic in one go — the
303
+ // product moving must not make this read as a plan change
304
+ const after = cancelledSubscription();
305
+ after.product = { id: 'basic', name: 'Basic' };
306
+
307
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), after, 'customer.subscription.deleted');
308
+
309
+ assert.equal(detected, 'subscription-cancelled', 'active paid → cancelled basic should still detect subscription-cancelled');
310
+ },
311
+ },
312
+
313
+ {
314
+ name: 'a-second-cancellation-event-fires-nothing',
315
+ async run({ assert }) {
316
+ // The rule reads the EDGE (non-cancelled → cancelled), so a redelivered
317
+ // deletion against an already-cancelled subscriber sends no second email
318
+ const detected = transitions.detectSubscriptionTransition(cancelledSubscription(), cancelledSubscription(), 'customer.subscription.deleted');
319
+
320
+ assert.equal(detected, null, 'cancelled → cancelled should detect nothing');
321
+ },
322
+ },
323
+
324
+ // ─── State rules: plan change ───
325
+
326
+ {
327
+ name: 'detects-a-plan-change',
328
+ async run({ assert }) {
329
+ const after = paidSubscription();
330
+ after.product = { id: 'pro', name: 'Pro' };
331
+
332
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), after, 'customer.subscription.updated');
333
+
334
+ assert.equal(detected, 'plan-changed', 'active premium → active pro should detect plan-changed');
335
+ },
336
+ },
337
+
338
+ {
339
+ name: 'a-frequency-only-switch-detects-nothing',
340
+ async run({ assert }) {
341
+ // The rule keys on the PRODUCT, so monthly → annually on the same plan
342
+ // has no transition of its own. Recorded here because it is a real gap in
343
+ // the table, not an accident: the plan route's own tests pin the state it
344
+ // writes, and analytics reads the renewal, but no email fires for it
345
+ const after = paidSubscription();
346
+ after.payment = { frequency: 'annually', price: 99.99 };
347
+
348
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), after, 'customer.subscription.updated');
349
+
350
+ assert.equal(detected, null, 'A frequency-only switch has no transition rule');
351
+ },
352
+ },
353
+
354
+ // ─── The guards around the table ───
355
+
356
+ {
357
+ name: 'no-after-state-detects-nothing',
358
+ async run({ assert }) {
359
+ // The first line of the detector: with nothing to compare against, every
360
+ // rule below would read `undefined.status` and throw
361
+ assert.equal(transitions.detectSubscriptionTransition(paidSubscription(), null, 'customer.subscription.updated'), null, 'A null after state should detect nothing');
362
+ assert.equal(transitions.detectSubscriptionTransition(paidSubscription(), undefined, 'customer.subscription.updated'), null, 'An undefined after state should detect nothing');
363
+ },
364
+ },
365
+
366
+ {
367
+ name: 'an-unknown-category-detects-nothing',
368
+ async run({ assert }) {
369
+ // on-write refuses an unknown category before it ever gets here, so this
370
+ // is the detector's own belt: a category it does not know routes nowhere
371
+ const detected = transitions.detectTransition('donation', null, paidSubscription(), 'charge.refunded');
372
+
373
+ assert.equal(detected, null, 'An unknown category should route to no transition');
374
+ },
375
+ },
376
+
377
+ {
378
+ name: 'an-unknown-event-type-detects-nothing-on-either-side',
379
+ async run({ assert }) {
380
+ // The one-time side reads the event type ALONE, so an event nothing maps
381
+ // must come back null rather than falling into the last rule it read
382
+ assert.equal(transitions.detectOneTimeTransition('customer.subscription.trial_will_end'), null, 'An unmapped event should detect no one-time transition');
383
+
384
+ // The subscription side ignores the event type unless it is a refund, so
385
+ // an unknown type over an unchanged subscription (a renewal) is also null
386
+ assert.equal(transitions.detectSubscriptionTransition(paidSubscription(), paidSubscription(), 'invoice.payment_succeeded'), null, 'A renewal has no transition of its own');
387
+ },
388
+ },
389
+
390
+ {
391
+ name: 'the-paid-helpers-read-the-product-not-the-status',
392
+ async run({ assert }) {
393
+ // Every rule in the table branches on these two in boolean position, and
394
+ // both answer for a subscription in ANY status — the status is the other
395
+ // half of each rule, never these
396
+ assert.ok(transitions.isBasicOrNull(null), 'No subscription at all is basic-or-null');
397
+ assert.ok(transitions.isBasicOrNull({ status: 'active' }), 'A subscription with no product is basic-or-null');
398
+ assert.ok(transitions.isBasicOrNull(basicSubscription()), 'Basic is basic-or-null');
399
+ assert.ok(!transitions.isBasicOrNull(cancelledSubscription()), 'A cancelled PAID subscription is not basic — that is what makes the win-back rules necessary');
400
+
401
+ assert.ok(!transitions.isPaid(null), 'No subscription at all is not paid');
402
+ assert.ok(!transitions.isPaid(basicSubscription()), 'Basic is not paid');
403
+ assert.ok(transitions.isPaid(cancelledSubscription()), 'A cancelled paid subscription still reads as paid');
404
+ },
405
+ },
406
+
407
+ // ─── Every rule has somewhere to dispatch ───
408
+
409
+ {
410
+ name: 'every-detectable-transition-has-a-handler-file',
411
+ async run({ assert }) {
412
+ // dispatch() resolves <category>/<name>.js by name — a rule whose handler
413
+ // file is missing logs "not found" and silently sends the customer nothing
414
+ const root = path.join(__dirname, '../../../dist/manager/events/firestore/payments-webhooks/transitions');
415
+
416
+ for (const [category, name] of HANDLED_TRANSITIONS) {
417
+ const handlerPath = path.join(root, category, `${name}.js`);
418
+
419
+ assert.ok(fs.existsSync(handlerPath), `Transition ${category}/${name} should have a handler file`);
420
+ assert.equal(typeof require(handlerPath), 'function', `Transition ${category}/${name} handler should export a function`);
421
+ }
422
+ },
423
+ },
424
+
425
+ // ─── Refund event strings, per provider ───
426
+
427
+ {
428
+ name: 'detects-stripe-refund',
429
+ async run({ assert }) {
430
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), paidSubscription(), 'charge.refunded');
431
+
432
+ assert.equal(detected, 'payment-refunded', 'charge.refunded should detect payment-refunded');
433
+ assert.ok(stripeProvider.isSupported('charge.refunded'), 'Stripe provider should accept the same event string');
434
+ },
435
+ },
436
+
437
+ {
438
+ name: 'detects-paypal-refund',
439
+ async run({ assert }) {
440
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), paidSubscription(), 'PAYMENT.SALE.REFUNDED');
441
+
442
+ assert.equal(detected, 'payment-refunded', 'PAYMENT.SALE.REFUNDED should detect payment-refunded');
443
+ assert.ok(paypalProvider.isSupported('PAYMENT.SALE.REFUNDED'), 'PayPal provider should accept the same event string');
444
+ },
445
+ },
446
+
447
+ {
448
+ name: 'detects-chargebee-refund',
449
+ async run({ assert }) {
450
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), paidSubscription(), 'payment_refunded');
451
+
452
+ assert.equal(detected, 'payment-refunded', 'payment_refunded should detect payment-refunded');
453
+ assert.ok(chargebeeProvider.isSupported('payment_refunded'), 'Chargebee provider should accept the same event string');
454
+ },
455
+ },
456
+
457
+ {
458
+ name: 'a-refund-outranks-every-state-rule',
459
+ async run({ assert }) {
460
+ // The refund check sits in FRONT of the whole table, deliberately: a full
461
+ // refund usually cancels the subscription in the same breath, and reading
462
+ // that state diff instead would send the cancellation email for a refund
463
+ const after = paidSubscription();
464
+ after.status = 'cancelled';
465
+
466
+ const detected = transitions.detectSubscriptionTransition(paidSubscription(), after, 'charge.refunded');
467
+
468
+ assert.equal(detected, 'payment-refunded', 'A refund that also cancels should still detect payment-refunded');
469
+ },
470
+ },
471
+
472
+ {
473
+ name: 'every-refund-event-is-a-parsed-event',
474
+ async run({ assert }) {
475
+ // Each refund string must be one a webhook parser actually delivers —
476
+ // an event no provider reports could never fire the transition
477
+ for (const eventType of transitions.REFUND_EVENTS) {
478
+ const supported = stripeProvider.isSupported(eventType)
479
+ || paypalProvider.isSupported(eventType)
480
+ || chargebeeProvider.isSupported(eventType);
481
+
482
+ assert.ok(supported, `Refund event ${eventType} should be supported by a webhook provider`);
483
+ }
484
+ },
485
+ },
486
+
487
+ // ─── One-time refunds ───
488
+
489
+ {
490
+ name: 'detects-a-one-time-refund',
491
+ async run({ assert }) {
492
+ const detected = transitions.detectOneTimeTransition('charge.refunded');
493
+
494
+ assert.equal(detected, 'purchase-refunded', 'A refunded one-time purchase should detect purchase-refunded');
495
+ },
496
+ },
497
+
498
+ {
499
+ name: 'every-refund-event-detects-on-the-one-time-side',
500
+ async run({ assert }) {
501
+ // The one-time side reads the same provider strings the subscription
502
+ // side does — a refund is a refund whichever thing was bought
503
+ for (const eventType of transitions.REFUND_EVENTS) {
504
+ assert.equal(
505
+ transitions.detectOneTimeTransition(eventType),
506
+ 'purchase-refunded',
507
+ `${eventType} should detect purchase-refunded for a one-time purchase`,
508
+ );
509
+ }
510
+ },
511
+ },
512
+
513
+ {
514
+ name: 'dispatches-a-one-time-refund-through-detect-transition',
515
+ async run({ assert }) {
516
+ // The path on-write actually calls: category + event type, no before state
517
+ const detected = transitions.detectTransition('one-time', null, { product: { id: 'credits' }, status: 'completed' }, 'charge.refunded');
518
+
519
+ assert.equal(detected, 'purchase-refunded', 'A one-time refund should route to purchase-refunded');
520
+ },
521
+ },
522
+
523
+ {
524
+ name: 'a-one-time-purchase-still-detects-purchase-completed',
525
+ async run({ assert }) {
526
+ // The refund mapping must not shadow the events already on this side
527
+ assert.equal(transitions.detectOneTimeTransition('checkout.session.completed'), 'purchase-completed', 'A completed checkout is still a purchase');
528
+ assert.equal(transitions.detectOneTimeTransition('CHECKOUT.ORDER.APPROVED'), 'purchase-completed', 'An approved PayPal order is still a purchase');
529
+ assert.equal(transitions.detectOneTimeTransition('invoice.payment_failed'), 'purchase-failed', 'A failed one-time invoice is still a failure');
530
+ },
531
+ },
532
+
533
+ {
534
+ name: 'a-chargebee-one-time-failure-detects-through-the-provider-fold',
535
+ async run({ assert }) {
536
+ // The fold the pipeline runs: the provider categorizes a
537
+ // subscription-less payment_failed as one-time, and the detector reads
538
+ // the provider's OWN event string off that same parse. Reading only
539
+ // Stripe's string left the Chargebee half of this path detecting
540
+ // nothing, so the failed-payment email never sent
541
+ // ([#714](https://github.com/Omega-JS-Stack/omega/issues/714)).
542
+ const parsed = chargebeeProvider.parseWebhook({
543
+ body: {
544
+ id: 'ev_cb_onetime_fail',
545
+ event_type: 'payment_failed',
546
+ content: {
547
+ invoice: { id: 'inv_cb_onetime_fail' },
548
+ customer: { id: 'cust_cb_onetime', meta_data: '{"uid":"_test-cb-onetime-fail"}' },
549
+ },
550
+ },
551
+ });
552
+
553
+ assert.equal(parsed.category, 'one-time', 'A subscription-less Chargebee payment_failed parses as one-time');
554
+
555
+ const detected = transitions.detectTransition(parsed.category, null, { product: { id: 'credits' }, status: 'failed' }, parsed.eventType);
556
+
557
+ assert.equal(detected, 'purchase-failed', 'Chargebee payment_failed should detect purchase-failed for a one-time purchase');
558
+ },
559
+ },
560
+
561
+ {
562
+ name: 'a-chargebee-one-time-purchase-detects-through-the-provider-fold',
563
+ async run({ assert }) {
564
+ // The success half of the same fold: a subscription-less
565
+ // payment_succeeded is the honest paid signal for a one-time purchase.
566
+ // Nothing on this side read it, so a paid Chargebee one-time sent no
567
+ // receipt and fired no purchase analytics
568
+ // ([#729](https://github.com/Omega-JS-Stack/omega/issues/729)).
569
+ const parsed = chargebeeProvider.parseWebhook({
570
+ body: {
571
+ id: 'ev_cb_onetime_paid',
572
+ event_type: 'payment_succeeded',
573
+ content: {
574
+ invoice: { id: 'inv_cb_onetime_paid' },
575
+ customer: { id: 'cust_cb_onetime', meta_data: '{"uid":"_test-cb-onetime-paid"}' },
576
+ },
577
+ },
578
+ });
579
+
580
+ assert.equal(parsed.category, 'one-time', 'A subscription-less Chargebee payment_succeeded parses as one-time');
581
+
582
+ const detected = transitions.detectTransition(parsed.category, null, { product: { id: 'credits' }, status: 'completed' }, parsed.eventType);
583
+
584
+ assert.equal(detected, 'purchase-completed', 'Chargebee payment_succeeded should detect purchase-completed for a one-time purchase');
585
+ },
586
+ },
587
+
588
+ {
589
+ name: 'a-coinbase-charge-detects-through-the-provider-fold',
590
+ async run({ assert }) {
591
+ // Crypto's half of the same fold: every Coinbase Commerce event is about
592
+ // a CHARGE, which is one purchase, so the provider categorizes all three
593
+ // as one-time and the detector reads the provider's own event strings
594
+ // ([#642](https://github.com/Omega-JS-Stack/omega/issues/642)).
595
+ const confirmed = coinbaseProvider.parseWebhook({
596
+ body: {
597
+ id: '_test-delivery',
598
+ event: {
599
+ id: '_test-evt-coinbase-confirmed',
600
+ type: 'charge:confirmed',
601
+ data: { id: 'ch_coinbase_confirmed', metadata: { uid: '_test-coinbase-fold', orderId: 'ord-1', productId: 'credits' } },
602
+ },
603
+ },
604
+ });
605
+
606
+ assert.equal(confirmed.category, 'one-time', 'A confirmed crypto charge parses as one-time');
607
+ assert.equal(
608
+ transitions.detectTransition(confirmed.category, null, { product: { id: 'credits' }, status: 'completed' }, confirmed.eventType),
609
+ 'purchase-completed',
610
+ 'charge:confirmed should detect purchase-completed — the crypto settled',
611
+ );
612
+ },
613
+ },
614
+
615
+ {
616
+ name: 'an-unsettled-or-expired-crypto-charge-detects-nothing',
617
+ async run({ assert }) {
618
+ // Both write their order (so the account page tells the truth about it)
619
+ // and neither may MAIL: charge:pending is money detected on-chain but not
620
+ // confirmed, and charge:failed is an expired hosted page — the abandoned
621
+ // -checkout population, watched by the person who walked away from it,
622
+ // which is exactly the population checkout-declined refuses to mail. An
623
+ // abandoned Stripe session sends nothing either ([#642]).
624
+ assert.equal(transitions.detectOneTimeTransition('charge:pending'), null, 'Nothing is mailed for money that has not settled');
625
+ assert.equal(transitions.detectOneTimeTransition('charge:failed'), null, 'And nothing is mailed for a checkout nobody paid');
626
+ },
627
+ },
628
+
629
+ {
630
+ name: 'a-chargebee-renewal-charge-never-reaches-the-one-time-side',
631
+ async run({ assert }) {
632
+ // With a subscription present, payment_succeeded is the renewal's charge
633
+ // and `subscription_renewed` already carries it — so the provider gives it
634
+ // no category, no doc is stored, and nothing double-fires ([#729]).
635
+ const parsed = chargebeeProvider.parseWebhook({
636
+ body: {
637
+ id: 'ev_cb_renewal_paid',
638
+ event_type: 'payment_succeeded',
639
+ content: {
640
+ subscription: { id: 'sub_cb_renewal', meta_data: '{"uid":"_test-cb-renewal"}' },
641
+ invoice: { id: 'inv_cb_renewal', subscription_id: 'sub_cb_renewal' },
642
+ },
643
+ },
644
+ });
645
+
646
+ assert.equal(parsed.category, null, 'A Chargebee payment_succeeded carrying a subscription parses as null');
647
+ assert.equal(transitions.detectTransition(parsed.category, null, {}, parsed.eventType), null, 'No category → no transition');
648
+ },
649
+ },
650
+
651
+ {
652
+ name: 'an-unpaid-chargebee-invoice-detects-nothing',
653
+ async run({ assert }) {
654
+ // `invoice_generated` still stores its one-time doc, but an invoice is
655
+ // born UNPAID (the Chargebee library maps `payment_due`/`not_paid` →
656
+ // failed), so it must stay transition-less — mapping it would email a
657
+ // receipt for a purchase nobody paid for ([#729]).
658
+ const parsed = chargebeeProvider.parseWebhook({
659
+ body: {
660
+ id: 'ev_cb_invoice_generated',
661
+ event_type: 'invoice_generated',
662
+ content: {
663
+ invoice: { id: 'inv_cb_unpaid' },
664
+ customer: { id: 'cust_cb_onetime', meta_data: '{"uid":"_test-cb-unpaid"}' },
665
+ },
666
+ },
667
+ });
668
+
669
+ assert.equal(parsed.category, 'one-time', 'A non-recurring invoice_generated still parses as one-time');
670
+
671
+ const detected = transitions.detectTransition(parsed.category, null, { product: { id: 'credits' }, status: 'pending' }, parsed.eventType);
672
+
673
+ assert.equal(detected, null, 'invoice_generated is not a paid signal — no transition, no receipt');
674
+ },
675
+ },
676
+
677
+ {
678
+ name: 'a-redelivered-chargebee-purchase-dispatches-nothing',
679
+ async run({ assert }) {
680
+ // The new arm is event-type-only like the rest of this side, so it needs
681
+ // the same idempotency guard — one purchase, one receipt
682
+ assert.equal(
683
+ transitions.detectOneTimeTransition('payment_succeeded', { previouslyCompleted: true }),
684
+ null,
685
+ 'A reprocessed Chargebee purchase should dispatch nothing',
686
+ );
687
+ assert.equal(
688
+ transitions.detectOneTimeTransition('payment_succeeded', { previouslyCompleted: false }),
689
+ 'purchase-completed',
690
+ 'A first-pass Chargebee purchase should still dispatch',
691
+ );
692
+ },
693
+ },
694
+
695
+ // ─── Refund idempotency ───
696
+
697
+ {
698
+ name: 'refund-fires-on-first-pass',
699
+ async run({ assert }) {
700
+ const detected = transitions.detectSubscriptionTransition(
701
+ paidSubscription(), paidSubscription(), 'charge.refunded', { previouslyCompleted: false },
702
+ );
703
+
704
+ assert.equal(detected, 'payment-refunded', 'A first-pass refund should dispatch');
705
+ },
706
+ },
707
+
708
+ {
709
+ name: 'refund-suppressed-when-doc-already-completed',
710
+ async run({ assert }) {
711
+ const detected = transitions.detectSubscriptionTransition(
712
+ paidSubscription(), paidSubscription(), 'charge.refunded', { previouslyCompleted: true },
713
+ );
714
+
715
+ assert.equal(detected, null, 'A reprocessed refund webhook should dispatch nothing (one refund, one email)');
716
+ },
717
+ },
718
+
719
+ {
720
+ name: 'a-redelivered-one-time-completion-dispatches-nothing',
721
+ async run({ assert }) {
722
+ // The whole one-time side reads the event type alone, so a doc that
723
+ // already completed once would re-dispatch its confirmation email on a
724
+ // redelivery — the same hole the subscription refund path closed
725
+ const detected = transitions.detectOneTimeTransition('checkout.session.completed', { previouslyCompleted: true });
726
+
727
+ assert.equal(detected, null, 'A reprocessed one-time completion should dispatch nothing (one purchase, one email)');
728
+ },
729
+ },
730
+
731
+ {
732
+ name: 'a-first-pass-one-time-completion-still-dispatches',
733
+ async run({ assert }) {
734
+ const detected = transitions.detectOneTimeTransition('checkout.session.completed', { previouslyCompleted: false });
735
+
736
+ assert.equal(detected, 'purchase-completed', 'A first-pass one-time completion should still dispatch');
737
+ },
738
+ },
739
+
740
+ {
741
+ name: 'the-one-time-guard-reaches-detect-transition',
742
+ async run({ assert }) {
743
+ // The path on-write actually calls — the options have to travel through it
744
+ const detected = transitions.detectTransition('one-time', null, { product: { id: 'credits' }, status: 'completed' }, 'checkout.session.completed', { previouslyCompleted: true });
745
+
746
+ assert.equal(detected, null, 'A reprocessed one-time webhook should dispatch nothing through detectTransition');
747
+ },
748
+ },
749
+
750
+ {
751
+ name: 'reprocess-does-not-suppress-other-transitions',
752
+ async run({ assert }) {
753
+ // The guard covers the refund path only — a reprocessed state change is
754
+ // still detected from the before/after diff
755
+ const before = paidSubscription();
756
+ const after = paidSubscription();
757
+ after.status = 'cancelled';
758
+
759
+ const detected = transitions.detectSubscriptionTransition(
760
+ before, after, 'customer.subscription.deleted', { previouslyCompleted: true },
761
+ );
762
+
763
+ assert.equal(detected, 'subscription-cancelled', 'A cancellation should still be detected on a reprocess');
764
+ },
765
+ },
766
+ ],
767
+ });