@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,252 @@
1
+ /**
2
+ * Test: a failed webhook is retried by the frequent cron, then dead-lettered
3
+ *
4
+ * The webhook route 200s the provider immediately and the trigger does the work,
5
+ * so a doc the trigger marked `failed` was terminal — a transient fault (a provider
6
+ * API blip, a Firestore write that lost a race) dropped the payment on the floor and
7
+ * nothing ever picked it up again ([#220]).
8
+ *
9
+ * Retrying is only safe if reprocessing the same event is safe, which the first two
10
+ * steps prove against the real pipeline before the sweep is exercised at all: the
11
+ * second pass writes the same state and detects NO transition, so nobody is emailed
12
+ * about the same subscription twice.
13
+ */
14
+ const powertools = require('node-powertools');
15
+ const defineCases = require('../../../dist/vendor/devkit/test/define-cases.js');
16
+
17
+ // The suite's own seeded persona ([#406](https://github.com/Omega-JS-Stack/omega/issues/406)):
18
+ // exclusive to this suite, declared in the seed roster, and the half the seed
19
+ // owns here is the AUTH USER — the doc is deleted below on purpose.
20
+ const PERSONA = 'webhook-retry-sweep';
21
+ const ORDER_ID = '5150-5150-5150';
22
+
23
+ // Written into `transition` before the doc is put back to pending — the completion
24
+ // write overwrites it, so waiting on it gone is waiting on the SECOND pass, not the first
25
+ const REPROCESS_SENTINEL = '_awaiting-reprocess';
26
+
27
+ // The ceiling the sweep stops re-flipping at (retry-failed-webhooks.js MAX_RETRIES)
28
+ const MAX_RETRIES = 5;
29
+
30
+ module.exports = defineCases({
31
+ description: 'Failed webhooks retry on the frequent cron, then dead-letter',
32
+ type: 'suite',
33
+ timeout: 180000,
34
+
35
+ tests: [
36
+ {
37
+ name: 'reset-prior-state',
38
+ async run({ accounts, firestore, state }) {
39
+ state.uid = accounts[PERSONA].uid;
40
+
41
+ // The pipeline writes this subscriber's doc from scratch, so the seeded doc
42
+ // comes off first — the auth user behind it is what makes the write legal at
43
+ // all ([#399]) — and the fixed order/intent ids go with it, or a previous
44
+ // run's leftovers would make the first pass a plan change instead of a new
45
+ // subscription
46
+ await firestore.delete(`users/${state.uid}`);
47
+ await firestore.delete(`payments-orders/${ORDER_ID}`);
48
+ await firestore.delete(`payments-intents/${ORDER_ID}`);
49
+
50
+ state.orderId = ORDER_ID;
51
+ },
52
+ },
53
+
54
+ {
55
+ name: 'process-a-subscription-webhook',
56
+ async run({ http, firestore, assert, waitFor, state, config, payments, skip }) {
57
+ const paidProduct = config.payment.products.find(p => p.id !== 'basic' && p.prices);
58
+
59
+ if (!paidProduct) {
60
+ skip('No paid product configured in this brand');
61
+ }
62
+
63
+ state.productId = paidProduct.id;
64
+ state.resourceId = `_test-retry-sub-${Date.now()}`;
65
+ state.eventId = `_test-evt-retry-${Date.now()}`;
66
+
67
+ const response = await http.as('none').post(`backend-manager/payments/webhook?provider=test&key=${config.webhookKey}`, {
68
+ id: state.eventId,
69
+ type: 'customer.subscription.updated',
70
+ data: {
71
+ object: subscriptionResource({
72
+ resourceId: state.resourceId,
73
+ uid: state.uid,
74
+ orderId: ORDER_ID,
75
+ stripeProductId: payments.stripeProductIds[paidProduct.id],
76
+ interval: payments.products[paidProduct.id].interval,
77
+ }),
78
+ },
79
+ });
80
+
81
+ assert.isSuccess(response, 'Webhook should be accepted');
82
+
83
+ await waitFor(async () => {
84
+ const doc = await firestore.get(`payments-webhooks/${state.eventId}`);
85
+ return doc?.status === 'completed';
86
+ }, 15000, 500);
87
+
88
+ const webhookDoc = await firestore.get(`payments-webhooks/${state.eventId}`);
89
+ const userDoc = await firestore.get(`users/${state.uid}`);
90
+ const orderDoc = await firestore.get(`payments-orders/${ORDER_ID}`);
91
+
92
+ assert.equal(webhookDoc.transition, 'new-subscription', 'The first pass is the one that emails the customer');
93
+ assert.equal(userDoc.subscription.status, 'active', 'The subscription is active');
94
+ assert.equal(userDoc.subscription.product.id, paidProduct.id, `The subscription is ${paidProduct.id}`);
95
+ assert.equal(orderDoc.owner, state.uid, 'The order was written');
96
+
97
+ state.orderCreatedUNIX = orderDoc.metadata.created.timestampUNIX;
98
+ state.expiresUNIX = userDoc.subscription.expires.timestampUNIX;
99
+ },
100
+ },
101
+
102
+ {
103
+ name: 'reprocessing-the-same-event-changes-nothing',
104
+ async run({ firestore, assert, waitFor, state }) {
105
+ // Exactly what a retry does: the same doc, put back to pending
106
+ await firestore.set(`payments-webhooks/${state.eventId}`, {
107
+ status: 'pending',
108
+ transition: REPROCESS_SENTINEL,
109
+ }, { merge: true });
110
+
111
+ await waitFor(async () => {
112
+ const doc = await firestore.get(`payments-webhooks/${state.eventId}`);
113
+ return doc?.status === 'completed' && doc?.transition !== REPROCESS_SENTINEL;
114
+ }, 15000, 500);
115
+
116
+ const webhookDoc = await firestore.get(`payments-webhooks/${state.eventId}`);
117
+ const userDoc = await firestore.get(`users/${state.uid}`);
118
+ const orderDoc = await firestore.get(`payments-orders/${ORDER_ID}`);
119
+
120
+ assert.equal(webhookDoc.transition, null, 'The second pass detects no transition — no second welcome email');
121
+ assert.equal(userDoc.subscription.status, 'active', 'The subscription is unchanged');
122
+ assert.equal(userDoc.subscription.product.id, state.productId, 'The product is unchanged');
123
+ assert.equal(userDoc.subscription.expires.timestampUNIX, state.expiresUNIX, 'The expiry is unchanged');
124
+ assert.equal(orderDoc.owner, state.uid, 'The order still belongs to the same user');
125
+ assert.equal(orderDoc.metadata.created.timestampUNIX, state.orderCreatedUNIX, 'The order is the SAME order, not a second one');
126
+ },
127
+ },
128
+
129
+ {
130
+ name: 'a-failing-webhook-counts-its-attempt',
131
+ async run({ http, firestore, assert, waitFor, state, config, payments }) {
132
+ state.failedEventId = `_test-evt-retry-fail-${Date.now()}`;
133
+ state.failedResourceId = `_test-retry-fail-sub-${Date.now()}`;
134
+ state.failedOrderId = '5151-5151-5151';
135
+
136
+ // No uid anywhere the pipeline can reach — the trigger throws on it
137
+ const response = await http.as('none').post(`backend-manager/payments/webhook?provider=test&key=${config.webhookKey}`, {
138
+ id: state.failedEventId,
139
+ type: 'customer.subscription.updated',
140
+ data: {
141
+ object: subscriptionResource({
142
+ resourceId: state.failedResourceId,
143
+ orderId: state.failedOrderId,
144
+ stripeProductId: payments.stripeProductIds[state.productId],
145
+ interval: payments.products[state.productId].interval,
146
+ }),
147
+ },
148
+ });
149
+
150
+ assert.isSuccess(response, 'Webhook should be accepted (it fails during processing, not at the door)');
151
+
152
+ await waitFor(async () => {
153
+ const doc = await firestore.get(`payments-webhooks/${state.failedEventId}`);
154
+ return doc?.status === 'failed';
155
+ }, 15000, 500);
156
+
157
+ const webhookDoc = await firestore.get(`payments-webhooks/${state.failedEventId}`);
158
+
159
+ assert.match(webhookDoc.error, /no UID/i, 'The webhook failed on the unresolvable UID');
160
+ assert.equal(webhookDoc.retryCount, 1, 'The failed attempt is counted');
161
+ assert.ok(!webhookDoc.deadLetter, 'One failure is nowhere near the ceiling');
162
+ },
163
+ },
164
+
165
+ {
166
+ name: 'the-sweep-recovers-it',
167
+ timeout: 120000,
168
+ async run({ firestore, assert, waitFor, pubsub, state }) {
169
+ // Heal what made it fail — the owner the payload never carried. This is the
170
+ // transient fault standing in for a provider blip: the next pass succeeds.
171
+ await firestore.set(`payments-webhooks/${state.failedEventId}`, { owner: state.uid }, { merge: true });
172
+
173
+ await pubsub.trigger('omega_cronFrequent');
174
+
175
+ await waitFor(async () => {
176
+ const doc = await firestore.get(`payments-webhooks/${state.failedEventId}`);
177
+ return doc?.status === 'completed';
178
+ }, 90000, 1000);
179
+
180
+ const webhookDoc = await firestore.get(`payments-webhooks/${state.failedEventId}`);
181
+
182
+ assert.equal(webhookDoc.status, 'completed', 'The sweep put it back to pending and the retry completed it');
183
+ assert.equal(webhookDoc.retryCount, 1, 'A successful retry does not count another attempt');
184
+ assert.equal(webhookDoc.owner, state.uid, 'It processed as the recovered owner');
185
+ },
186
+ },
187
+
188
+ {
189
+ name: 'the-ceiling-dead-letters-instead-of-retrying-forever',
190
+ timeout: 120000,
191
+ async run({ firestore, assert, waitFor, pubsub, state, payments }) {
192
+ const now = powertools.timestamp(new Date(), { output: 'string' });
193
+ state.deadEventId = `_test-evt-retry-dead-${Date.now()}`;
194
+
195
+ // A doc that already burned every retry — seeded failed, so nothing processes
196
+ // it until the sweep decides whether to
197
+ await firestore.set(`payments-webhooks/${state.deadEventId}`, {
198
+ id: state.deadEventId,
199
+ provider: 'test',
200
+ status: 'failed',
201
+ retryCount: MAX_RETRIES,
202
+ error: 'Webhook event has no UID',
203
+ raw: { id: state.deadEventId, type: 'customer.subscription.updated', data: { object: subscriptionResource({ resourceId: `_test-retry-dead-sub-${Date.now()}`, orderId: '5152-5152-5152', stripeProductId: payments.stripeProductIds[state.productId], interval: payments.products[state.productId].interval }) } },
204
+ owner: null,
205
+ event: { type: 'customer.subscription.updated', category: 'subscription', resourceType: 'subscription', resourceId: `_test-retry-dead-sub-${Date.now()}` },
206
+ metadata: {
207
+ created: {
208
+ timestamp: now,
209
+ timestampUNIX: powertools.timestamp(now, { output: 'unix' }),
210
+ },
211
+ },
212
+ });
213
+
214
+ await pubsub.trigger('omega_cronFrequent');
215
+
216
+ await waitFor(async () => {
217
+ const doc = await firestore.get(`payments-webhooks/${state.deadEventId}`);
218
+ return doc?.deadLetter === true;
219
+ }, 90000, 1000);
220
+
221
+ const webhookDoc = await firestore.get(`payments-webhooks/${state.deadEventId}`);
222
+
223
+ assert.equal(webhookDoc.deadLetter, true, 'The doc at the ceiling is dead-lettered');
224
+ assert.equal(webhookDoc.status, 'failed', 'It is NOT put back to pending');
225
+ assert.equal(webhookDoc.retryCount, MAX_RETRIES, 'Dead-lettering counts no further attempt');
226
+ },
227
+ },
228
+ ],
229
+ });
230
+
231
+ /**
232
+ * A Stripe-shaped active subscription — the shape the test provider speaks
233
+ * Omitting uid is what makes the pipeline fail on an unresolvable owner
234
+ */
235
+ function subscriptionResource({ resourceId, uid, orderId, stripeProductId, interval }) {
236
+ const nowUNIX = Math.floor(Date.now() / 1000);
237
+
238
+ return {
239
+ id: resourceId,
240
+ object: 'subscription',
241
+ status: 'active',
242
+ metadata: uid ? { uid, orderId } : { orderId },
243
+ cancel_at_period_end: false,
244
+ canceled_at: null,
245
+ current_period_start: nowUNIX,
246
+ current_period_end: nowUNIX + 86400 * 30,
247
+ start_date: nowUNIX,
248
+ trial_start: null,
249
+ trial_end: null,
250
+ plan: { product: stripeProductId, interval: interval },
251
+ };
252
+ }
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Test: a transition is claimed per order, so concurrent webhooks dispatch it once
3
+ *
4
+ * Transition detection is a DIFF — the user's stored subscription against the
5
+ * unified state the event carries — so it is only correct while one webhook is in
6
+ * flight. Stripe sent `customer.subscription.created` and
7
+ * `invoice.payment_succeeded` one second apart for the same checkout: both runs
8
+ * read `users/{uid}` while it still said `basic` (the first run's write landed 11
9
+ * seconds later), both detected `subscription/new-subscription`, and both
10
+ * dispatched it — two order emails to one customer, two `trial_start` fires
11
+ * ([#665](https://github.com/Omega-JS-Stack/omega/issues/665)).
12
+ *
13
+ * The fix is a CLAIM on the order, taken in a transaction before the dispatch:
14
+ * `payments-orders/{orderId}.transitions.<name>`. Per order per transition NAME,
15
+ * so the order's own later transitions — a cancellation, a plan change — still
16
+ * fire; only a second run of the SAME transition on the SAME order is suppressed.
17
+ *
18
+ * Plain-node (no emulator, no network), for the reason the atomic-writes and
19
+ * sweep-staleness suites are: the emulator cannot run two triggers for one order
20
+ * interleaved on demand, and this bug only exists in the interleaving. The shared
21
+ * in-memory stand-in models Firestore's real transaction contract — versioned
22
+ * reads, staged writes, and a re-run of the whole block when a read document
23
+ * changed underneath — so the claim is genuinely contended rather than assumed.
24
+ *
25
+ * A claim gates all THREE things the transition drives — the handler, the analytics
26
+ * fire, and the marketing sync — because the live sighting duplicated all three: two
27
+ * order emails, two `trial_start`s (GA4 counted both), two marketing syncs.
28
+ *
29
+ * Each is counted through the runner's own testing-mode seam: `ctx.isTesting()` is
30
+ * true here, so the pipeline logs `… skipped (testing mode)` at exactly the point it
31
+ * would otherwise email a real customer, post a real conversion, or write to a real
32
+ * marketing list. Those lines ARE the three effects, counted.
33
+ *
34
+ * Run: npx omega test backend:events/payments/webhook-transition-claim
35
+ */
36
+ const assert = require('node:assert');
37
+ const onWrite = require('../../../dist/manager/events/firestore/payments-webhooks/on-write.js');
38
+ const { CLAIM_WINDOW_MS } = require('../../../dist/manager/events/firestore/payments-webhooks/transitions/index.js');
39
+ const { buildAdmin, CONFIG, subscriptionPayload } = require('./_webhook-harness.js');
40
+ const defineCases = require('../../../dist/vendor/devkit/test/define-cases.js');
41
+
42
+ const UID = '_test-claim-uid';
43
+ const ORDER_ID = '_test-claim-order';
44
+ const RESOURCE_ID = '_test-claim-sub';
45
+
46
+ const CREATED_EVENT = '_test-claim-subscription-created';
47
+ const INVOICE_EVENT = '_test-claim-invoice-succeeded';
48
+
49
+ const nowUNIX = () => Math.floor(Date.now() / 1000);
50
+
51
+ /** A `$timestamp` pair from a UNIX second */
52
+ function stamp(unix) {
53
+ return { timestamp: new Date(unix * 1000).toISOString(), timestampUNIX: unix };
54
+ }
55
+
56
+ /** A brand-new subscriber, still on basic — what BOTH racing runs read */
57
+ function basicUser() {
58
+ return {
59
+ auth: { email: 'claim@example.com' },
60
+ personal: { name: { first: 'Claim', last: 'Tester' } },
61
+ subscription: { product: { id: 'basic', name: 'Basic' }, status: 'active' },
62
+ };
63
+ }
64
+
65
+ /** An established paid subscriber, for the transition that comes AFTER the first one */
66
+ function paidUser() {
67
+ return {
68
+ auth: { email: 'claim@example.com' },
69
+ personal: { name: { first: 'Claim', last: 'Tester' } },
70
+ subscription: {
71
+ product: { id: 'premium', name: 'Premium' },
72
+ status: 'active',
73
+ cancellation: { pending: false },
74
+ payment: { provider: 'test', resourceId: RESOURCE_ID, frequency: 'monthly', price: 4.99 },
75
+ },
76
+ };
77
+ }
78
+
79
+ /**
80
+ * A pending webhook doc for this order, as the webhook route writes one
81
+ *
82
+ * @param {string} eventId - The webhook doc id
83
+ * @param {string} eventType - The provider's event name
84
+ * @param {object} [overrides] - Overrides for the subscription resource it carries
85
+ * @returns {object}
86
+ */
87
+ function webhookDoc(eventId, eventType, overrides = {}) {
88
+ const resource = { ...subscriptionPayload({ uid: UID, orderId: ORDER_ID, resourceId: RESOURCE_ID }), ...overrides };
89
+
90
+ return {
91
+ id: eventId,
92
+ provider: 'test',
93
+ status: 'pending',
94
+ raw: { id: eventId, type: eventType, data: { object: resource } },
95
+ owner: UID,
96
+ event: { type: eventType, category: 'subscription', resourceType: 'subscription', resourceId: RESOURCE_ID, refundId: null },
97
+ metadata: { created: { timestampUNIX: nowUNIX() } },
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Run the real trigger over one or more webhook docs against ONE store — the whole
103
+ * point of this suite, which `runTrigger` cannot serve: it builds a store per call,
104
+ * and two events that never share an order can never contend for its claim.
105
+ *
106
+ * @param {object} options
107
+ * @param {object[]} options.events - The pending webhook docs to process
108
+ * @param {object} options.seed - Extra documents the run starts from
109
+ * @param {boolean} [options.concurrent] - Process them together (the race) or one after the other
110
+ * @param {string|function|null} [options.failPath] - The document path whose write rejects, so a
111
+ * run can be made to fail AFTER it claimed — the state its own retry has to get past
112
+ * @returns {Promise<{ store: Map, logs: string[], run: function }>} `run` re-enters the trigger
113
+ * against this same store, which is how a retry of an event is played
114
+ */
115
+ async function runTriggers({ events, seed = {}, concurrent = true, failPath = null }) {
116
+ const webhooks = Object.fromEntries(events.map((event) => [`payments-webhooks/${event.id}`, event]));
117
+ const { admin, store } = buildAdmin({ seed: { ...webhooks, ...seed }, authUids: [UID], failPath: failPath });
118
+
119
+ const logs = [];
120
+ const Manager = { config: CONFIG, libraries: { admin, sentry: null } };
121
+ const ctx = {
122
+ Manager,
123
+ isTesting: () => true,
124
+ log: (...args) => logs.push(args.join(' ')),
125
+ warn: (...args) => logs.push(args.join(' ')),
126
+ error: (...args) => logs.push(args.join(' ')),
127
+ };
128
+
129
+ // The doc the trigger is handed is a point-in-time snapshot, exactly as a real
130
+ // trigger's `change` is — it stays `pending` even after the run flips the stored doc
131
+ const run = (event) => onWrite({
132
+ ctx,
133
+ change: { before: { data: () => event }, after: { data: () => event } },
134
+ context: { params: { eventId: event.id } },
135
+ });
136
+
137
+ if (concurrent) {
138
+ await Promise.all(events.map(run));
139
+ } else {
140
+ for (const event of events) {
141
+ await run(event);
142
+ }
143
+ }
144
+
145
+ return { store, logs, run };
146
+ }
147
+
148
+ /** How many times the pipeline reached the dispatch point for this transition */
149
+ function dispatchCount(logs, transitionName) {
150
+ return logs.filter((line) => line.includes(`Transition handler skipped (testing mode): subscription/${transitionName}`)).length;
151
+ }
152
+
153
+ /** How many runs reached a testing-mode seam — the effect that would have gone out for real */
154
+ function seamCount(logs, seam) {
155
+ return logs.filter((line) => line.includes(`${seam} skipped (testing mode)`)).length;
156
+ }
157
+
158
+ module.exports = defineCases({
159
+ description: 'Payment transitions: one claim per order per transition name gates the dispatch',
160
+ type: 'group',
161
+ timeout: 30000,
162
+
163
+ tests: [
164
+ {
165
+ name: 'two-concurrent-webhooks-for-one-order-dispatch-new-subscription-once',
166
+
167
+ async run() {
168
+ // The live sighting, reproduced: the subscription and the invoice event for
169
+ // ONE checkout, in flight together, both reading a user doc that still says
170
+ // basic. Detection is expected to fire twice — that is not the bug and is
171
+ // not what this fixes. Exactly one of them may ACT on it.
172
+ const { store, logs } = await runTriggers({
173
+ events: [
174
+ webhookDoc(CREATED_EVENT, 'customer.subscription.created'),
175
+ webhookDoc(INVOICE_EVENT, 'invoice.payment_succeeded'),
176
+ ],
177
+ seed: { [`users/${UID}`]: basicUser() },
178
+ });
179
+
180
+ const detected = logs.filter((line) => line.includes('Transition detected: subscription/new-subscription'));
181
+ assert.equal(detected.length, 2, `both runs must detect the transition — otherwise they never raced, got: ${logs.join(' | ')}`);
182
+
183
+ assert.equal(dispatchCount(logs, 'new-subscription'), 1, `exactly one run may dispatch new-subscription, got: ${logs.join(' | ')}`);
184
+
185
+ // The other two effects of the same transition, which the live sighting
186
+ // duplicated alongside the email: one conversion (GA4 deduplicates neither a
187
+ // custom `trial_start` nor a second event id) and one marketing sync.
188
+ assert.equal(seamCount(logs, 'Payment analytics'), 1, `exactly one run may fire payment analytics, got: ${logs.join(' | ')}`);
189
+ assert.equal(seamCount(logs, 'Marketing sync'), 1, `exactly one run may sync the marketing contact, got: ${logs.join(' | ')}`);
190
+ assert.ok(
191
+ logs.some((line) => line.includes('Payment analytics suppressed (claimed by')),
192
+ `the loser says why it tracked nothing, got: ${logs.join(' | ')}`,
193
+ );
194
+ assert.ok(
195
+ logs.some((line) => line.includes('Marketing sync suppressed (claimed by')),
196
+ `and why it synced nothing, got: ${logs.join(' | ')}`,
197
+ );
198
+
199
+ const claim = store.get(`payments-orders/${ORDER_ID}`).transitions['new-subscription'];
200
+ assert.ok([CREATED_EVENT, INVOICE_EVENT].includes(claim.eventId), 'the claim names the event that won it');
201
+ assert.ok(claim.timestampUNIX > 0, 'the claim is stamped');
202
+
203
+ const suppressed = logs.filter((line) => line.includes('Transition suppressed (claimed by'));
204
+ assert.equal(suppressed.length, 1, `the loser says so, once, got: ${logs.join(' | ')}`);
205
+ assert.ok(
206
+ suppressed[0].includes(`Transition suppressed (claimed by ${claim.eventId}): subscription/new-subscription on payments-orders/${ORDER_ID}`),
207
+ `the suppression names the winner and the order, got: ${suppressed[0]}`,
208
+ );
209
+
210
+ // Everything the claim does NOT change: both events are still acknowledged
211
+ // (a provider must stop redelivering them), both still record what they saw,
212
+ // and the subscriber is on the plan they paid for.
213
+ assert.equal(store.get(`payments-webhooks/${CREATED_EVENT}`).status, 'completed', 'the first event completes');
214
+ assert.equal(store.get(`payments-webhooks/${INVOICE_EVENT}`).status, 'completed', 'the second event completes too — suppression is not a failure');
215
+ assert.equal(store.get(`users/${UID}`).subscription.product.id, 'premium', 'the subscriber is on the plan they bought');
216
+ assert.deepEqual(
217
+ store.get(`payments-orders/${ORDER_ID}`).requests,
218
+ { cancellation: null, refund: null },
219
+ 'the order still gets its requests node — the claim writes the order doc first, so a bare existence test would have skipped it',
220
+ );
221
+ },
222
+ },
223
+
224
+ {
225
+ name: 'a-claim-already-on-the-order-suppresses-a-later-detection',
226
+
227
+ async run() {
228
+ // The claim is durable, not just a tiebreaker for one instant: a redelivery
229
+ // that re-detects new-subscription (the user doc write never landed, so the
230
+ // diff still reads basic → premium) finds the claim and stays quiet. Taken a
231
+ // minute ago and still running, so it is well inside the claim window.
232
+ const claimedUNIX = nowUNIX() - 60;
233
+
234
+ const { store, logs } = await runTriggers({
235
+ events: [webhookDoc(INVOICE_EVENT, 'invoice.payment_succeeded')],
236
+ seed: {
237
+ [`users/${UID}`]: basicUser(),
238
+ [`payments-orders/${ORDER_ID}`]: {
239
+ id: ORDER_ID,
240
+ type: 'subscription',
241
+ owner: UID,
242
+ resourceId: RESOURCE_ID,
243
+ transitions: { 'new-subscription': { eventId: CREATED_EVENT, status: 'claimed', ...stamp(claimedUNIX) } },
244
+ metadata: { created: stamp(claimedUNIX), updated: stamp(claimedUNIX) },
245
+ },
246
+ },
247
+ });
248
+
249
+ assert.equal(dispatchCount(logs, 'new-subscription'), 0, `nothing may dispatch against a claim, got: ${logs.join(' | ')}`);
250
+ assert.equal(seamCount(logs, 'Payment analytics'), 0, `nor track against one, got: ${logs.join(' | ')}`);
251
+ assert.equal(seamCount(logs, 'Marketing sync'), 0, `nor sync against one, got: ${logs.join(' | ')}`);
252
+ assert.ok(
253
+ logs.some((line) => line.includes(`Transition suppressed (claimed by ${CREATED_EVENT})`)),
254
+ `the skip names the event holding the claim, got: ${logs.join(' | ')}`,
255
+ );
256
+ assert.equal(
257
+ store.get(`payments-orders/${ORDER_ID}`).transitions['new-subscription'].eventId,
258
+ CREATED_EVENT,
259
+ 'the standing claim is left exactly as it was — a loser never overwrites the winner',
260
+ );
261
+ },
262
+ },
263
+
264
+ {
265
+ name: 'a-different-transition-on-the-same-order-still-fires',
266
+
267
+ async run() {
268
+ // The claim is per NAME, not per order. The same subscription's later life —
269
+ // a cancellation here — has its own transition to send, and a spent
270
+ // new-subscription claim must not swallow it.
271
+ const claimedUNIX = nowUNIX() - 3600;
272
+
273
+ const { store, logs } = await runTriggers({
274
+ events: [webhookDoc('_test-claim-subscription-deleted', 'customer.subscription.deleted', { status: 'canceled' })],
275
+ seed: {
276
+ [`users/${UID}`]: paidUser(),
277
+ [`payments-orders/${ORDER_ID}`]: {
278
+ id: ORDER_ID,
279
+ type: 'subscription',
280
+ owner: UID,
281
+ resourceId: RESOURCE_ID,
282
+ requests: { cancellation: null, refund: null },
283
+ transitions: { 'new-subscription': { eventId: CREATED_EVENT, ...stamp(claimedUNIX) } },
284
+ metadata: { created: stamp(claimedUNIX), updated: stamp(claimedUNIX) },
285
+ },
286
+ },
287
+ });
288
+
289
+ assert.equal(dispatchCount(logs, 'subscription-cancelled'), 1, `the cancellation dispatches on its own name, got: ${logs.join(' | ')}`);
290
+ assert.equal(seamCount(logs, 'Payment analytics'), 1, `and tracks, got: ${logs.join(' | ')}`);
291
+ assert.equal(seamCount(logs, 'Marketing sync'), 1, `and syncs — a spent claim gates only its own name, got: ${logs.join(' | ')}`);
292
+
293
+ const claims = store.get(`payments-orders/${ORDER_ID}`).transitions;
294
+ assert.equal(claims['subscription-cancelled'].eventId, '_test-claim-subscription-deleted', 'the cancellation takes its own claim');
295
+ assert.equal(claims['new-subscription'].eventId, CREATED_EVENT, 'and the earlier claim stands beside it');
296
+ },
297
+ },
298
+
299
+ {
300
+ name: 'a-claim-older-than-the-window-is-a-spent-one-and-the-transition-repeats',
301
+
302
+ async run() {
303
+ // The order id is stable for the subscription's LIFE, so every transition
304
+ // that can happen twice re-detects a name the order already carries: a
305
+ // second dunning cycle's payment-failed, a re-cancellation after an
306
+ // uncancel, a second plan change. Keyed on order + name alone, the first
307
+ // claim swallowed all of them — no dunning email, no analytics, no sync,
308
+ // for as long as the subscription lasted. The claim only speaks for its
309
+ // window; the race it settles is seconds wide.
310
+ const spentUNIX = nowUNIX() - (30 * 86400);
311
+
312
+ const { store, logs } = await runTriggers({
313
+ events: [webhookDoc('_test-claim-invoice-failed-2', 'invoice.payment_failed')],
314
+ seed: {
315
+ [`users/${UID}`]: paidUser(),
316
+ [`payments-orders/${ORDER_ID}`]: {
317
+ id: ORDER_ID,
318
+ type: 'subscription',
319
+ owner: UID,
320
+ resourceId: RESOURCE_ID,
321
+ requests: { cancellation: null, refund: null },
322
+ // What the FIRST dunning cycle left behind a month ago, finished
323
+ transitions: { 'payment-failed': { eventId: '_test-claim-invoice-failed-1', status: 'done', ...stamp(spentUNIX) } },
324
+ metadata: { created: stamp(spentUNIX), updated: stamp(spentUNIX) },
325
+ },
326
+ },
327
+ });
328
+
329
+ assert.equal(dispatchCount(logs, 'payment-failed'), 1, `the second dunning cycle dispatches, got: ${logs.join(' | ')}`);
330
+ assert.equal(seamCount(logs, 'Payment analytics'), 1, `and tracks, got: ${logs.join(' | ')}`);
331
+ assert.equal(seamCount(logs, 'Marketing sync'), 1, `and syncs, got: ${logs.join(' | ')}`);
332
+
333
+ const claim = store.get(`payments-orders/${ORDER_ID}`).transitions['payment-failed'];
334
+ assert.equal(claim.eventId, '_test-claim-invoice-failed-2', 'the spent claim is retaken by the event that repeated the transition');
335
+ assert.equal(claim.status, 'done', 'and settles like any other');
336
+ assert.ok(claim.timestampUNIX > spentUNIX, 'restamped, so it speaks for its own window and no longer for the first cycle');
337
+ assert.ok(CLAIM_WINDOW_MS < 30 * 86400 * 1000, 'the fixture has to be older than the window it is testing');
338
+ },
339
+ },
340
+
341
+ {
342
+ name: 'a-run-that-failed-after-claiming-is-not-refused-by-its-own-claim',
343
+
344
+ async run() {
345
+ // A claim can outlive the run that took it: the winner claims, then its state
346
+ // write fails. The webhook doc goes `failed` and the frequent cron re-runs
347
+ // THAT SAME event id (events/cron/frequent/retry-failed-webhooks.js) — and a
348
+ // claim carrying no outcome refused the retry forever, so the customer who
349
+ // paid got no order email, no conversion, no marketing sync, and no
350
+ // subscription. `failed` is the one reclaimable state, exactly as it is for
351
+ // the webhook doc itself and for a dispute alert.
352
+ const faulty = { writes: true };
353
+
354
+ const { store, logs, run } = await runTriggers({
355
+ events: [webhookDoc(CREATED_EVENT, 'customer.subscription.created')],
356
+ seed: { [`users/${UID}`]: basicUser() },
357
+ // The batch — everything the run writes AFTER it claimed
358
+ failPath: (path) => faulty.writes && path === `users/${UID}`,
359
+ });
360
+
361
+ assert.equal(store.get(`payments-webhooks/${CREATED_EVENT}`).status, 'failed', 'the run failed after it claimed');
362
+ assert.equal(store.get(`users/${UID}`).subscription.product.id, 'basic', 'and wrote no subscription — that is what the retry is for');
363
+
364
+ const claim = store.get(`payments-orders/${ORDER_ID}`).transitions['new-subscription'];
365
+ assert.equal(claim.status, 'failed', 'the claim records how the run ended, so a retry can tell abandoned from finished');
366
+ assert.equal(claim.eventId, CREATED_EVENT, 'and still names the event that took it');
367
+
368
+ // The sweep flips the failed doc back to pending, and the trigger sees the
369
+ // same event id a second time — with the fault cleared.
370
+ faulty.writes = false;
371
+ const mark = logs.length;
372
+ await run(webhookDoc(CREATED_EVENT, 'customer.subscription.created'));
373
+ const retryLogs = logs.slice(mark);
374
+
375
+ assert.equal(dispatchCount(retryLogs, 'new-subscription'), 1, `the retry reclaims and dispatches once, got: ${retryLogs.join(' | ')}`);
376
+ assert.equal(seamCount(retryLogs, 'Payment analytics'), 1, `and tracks once, got: ${retryLogs.join(' | ')}`);
377
+ assert.equal(seamCount(retryLogs, 'Marketing sync'), 1, `and syncs once, got: ${retryLogs.join(' | ')}`);
378
+
379
+ assert.equal(store.get(`users/${UID}`).subscription.product.id, 'premium', 'the subscriber ends up on the plan they paid for');
380
+ assert.equal(store.get(`payments-webhooks/${CREATED_EVENT}`).status, 'completed', 'and the event completes');
381
+ assert.equal(
382
+ store.get(`payments-orders/${ORDER_ID}`).transitions['new-subscription'].status,
383
+ 'done',
384
+ 'the claim that finished says so — a third run finds it spent, not retakeable',
385
+ );
386
+ },
387
+ },
388
+ ],
389
+ });