backend-manager 5.9.5 → 5.9.6

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 (244) hide show
  1. package/package.json +8 -1
  2. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/metadata.json +14 -0
  3. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.html +486 -0
  4. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.md +41 -0
  5. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/newsletter.mjml +97 -0
  6. package/{test/email/fixtures/clean.json → src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/structure.json} +16 -4
  7. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-11T03-28-07/summary.md +1 -0
  8. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/metadata.json +14 -0
  9. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.html +486 -0
  10. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.md +41 -0
  11. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/newsletter.mjml +97 -0
  12. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/structure.json +42 -0
  13. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-00-28/summary.md +1 -0
  14. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/metadata.json +14 -0
  15. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.html +486 -0
  16. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.md +41 -0
  17. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/newsletter.mjml +97 -0
  18. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/structure.json +42 -0
  19. package/src/test/fixtures/firebase-project/.temp/newsletter/run-2026-06-18T01-01-34/summary.md +1 -0
  20. package/src/test/fixtures/firebase-project/.temp/test-mode.json +6 -0
  21. package/src/test/fixtures/firebase-project/database-debug.log +12 -0
  22. package/src/test/fixtures/firebase-project/firestore-debug.log +136 -0
  23. package/src/test/fixtures/firebase-project/pubsub-debug.log +17 -0
  24. package/.codeclimate.yml +0 -32
  25. package/.nvmrc +0 -1
  26. package/CHANGELOG.md +0 -1440
  27. package/PROGRESS.md +0 -93
  28. package/TODO-2.md +0 -121
  29. package/TODO-CHARGEBLAST.md +0 -32
  30. package/TODO-WEBHOOK-KEY-LEGACY-REMOVAL.md +0 -15
  31. package/TODO-WEBHOOK-KEY-UPGRADE.md +0 -138
  32. package/TODO-email-auth.md +0 -14
  33. package/TODO.md +0 -187
  34. package/plans/mcp2.md +0 -247
  35. package/plans/suspended-subscription-dead-zone.md +0 -97
  36. package/scripts/test-helper-providers.js +0 -162
  37. package/scripts/update-disposable-domains.js +0 -44
  38. package/templates/_.env +0 -42
  39. package/templates/_.gitignore +0 -60
  40. package/templates/backend-manager-config.json +0 -249
  41. package/templates/database.rules.json +0 -83
  42. package/templates/firebase.json +0 -66
  43. package/templates/firestore.indexes.json +0 -4
  44. package/templates/firestore.rules +0 -112
  45. package/templates/public/404.html +0 -26
  46. package/templates/public/index.html +0 -24
  47. package/templates/remoteconfig.template.json +0 -1
  48. package/templates/storage-lifecycle-config-1-day.json +0 -9
  49. package/templates/storage-lifecycle-config-30-days.json +0 -9
  50. package/templates/storage.rules +0 -8
  51. package/test/_init/accounts-validation.js +0 -58
  52. package/test/_legacy/ai/index.js +0 -231
  53. package/test/_legacy/ai/test.jpg +0 -0
  54. package/test/_legacy/ai/test.pdf +0 -0
  55. package/test/_legacy/index.js +0 -31
  56. package/test/_legacy/payment-resolver/chargebee/orders/refunded.json +0 -0
  57. package/test/_legacy/payment-resolver/chargebee/orders/unpaid.json +0 -98
  58. package/test/_legacy/payment-resolver/chargebee/subscriptions/active.json +0 -578
  59. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-in-trial.json +0 -38
  60. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-skipped-to-active.json +0 -135
  61. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active-to-cancelled.json +0 -42
  62. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-active.json +0 -44
  63. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-cancelled.json +0 -39
  64. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-refund.json +0 -143
  65. package/test/_legacy/payment-resolver/chargebee/subscriptions/trial-to-suspended.json +0 -48
  66. package/test/_legacy/payment-resolver/coinbase/orders/regular.json +0 -204
  67. package/test/_legacy/payment-resolver/coinbase/subscriptions/cancelled.json +0 -204
  68. package/test/_legacy/payment-resolver/coinbase/subscriptions/paid-2.json +0 -125
  69. package/test/_legacy/payment-resolver/index.js +0 -1558
  70. package/test/_legacy/payment-resolver/paypal/orders/refunded.json +0 -0
  71. package/test/_legacy/payment-resolver/paypal/orders/regular.json +0 -120
  72. package/test/_legacy/payment-resolver/paypal/subscriptions/active-refund-previous-stmnt.json +0 -323
  73. package/test/_legacy/payment-resolver/paypal/subscriptions/active.json +0 -192
  74. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-in-trial.json +0 -125
  75. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-not-complete.json +0 -76
  76. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue-2.json +0 -114
  77. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-payment-overdue.json +0 -114
  78. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active-to-cancelled.json +0 -111
  79. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-active.json +0 -132
  80. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-cancelled.json +0 -107
  81. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-expired.json +0 -91
  82. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-refund.json +0 -147
  83. package/test/_legacy/payment-resolver/paypal/subscriptions/trial-to-suspended.json +0 -120
  84. package/test/_legacy/payment-resolver/stripe/orders/refunded.json +0 -0
  85. package/test/_legacy/payment-resolver/stripe/orders/regular.json +0 -91
  86. package/test/_legacy/payment-resolver/stripe/subscriptions/active.json +0 -421
  87. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-in-trial.json +0 -349
  88. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active-failed-authorization.json +0 -430
  89. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-active.json +0 -319
  90. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-cancelled.json +0 -319
  91. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-refund.json +0 -414
  92. package/test/_legacy/payment-resolver/stripe/subscriptions/trial-to-suspended.json +0 -319
  93. package/test/_legacy/payment-resolver/stripe/subscriptions/unsure.json +0 -339
  94. package/test/_legacy/test-new +0 -1178
  95. package/test/_legacy/test.md +0 -89
  96. package/test/_legacy/usage.js +0 -175
  97. package/test/_legacy/user.js +0 -31
  98. package/test/ai/tools-live.js +0 -170
  99. package/test/boot/emulator-boots.js +0 -37
  100. package/test/content/blog-generate.js +0 -160
  101. package/test/email/campaign-send.js +0 -45
  102. package/test/email/consent-lifecycle.js +0 -257
  103. package/test/email/feedback-and-plain-send.js +0 -52
  104. package/test/email/fixtures/editorial.json +0 -30
  105. package/test/email/fixtures/field-report.json +0 -53
  106. package/test/email/marketing-lifecycle.js +0 -132
  107. package/test/email/newsletter-generate.js +0 -819
  108. package/test/email/newsletter-templates.js +0 -491
  109. package/test/email/templates.js +0 -207
  110. package/test/email/transactional-send.js +0 -34
  111. package/test/email/transactional.js +0 -560
  112. package/test/email/validation.js +0 -710
  113. package/test/events/auth-delete-race.js +0 -201
  114. package/test/events/payments/journey-payments-cancel-endpoint.js +0 -100
  115. package/test/events/payments/journey-payments-cancel.js +0 -182
  116. package/test/events/payments/journey-payments-discount.js +0 -89
  117. package/test/events/payments/journey-payments-failure.js +0 -146
  118. package/test/events/payments/journey-payments-legacy-product.js +0 -149
  119. package/test/events/payments/journey-payments-one-time-failure.js +0 -111
  120. package/test/events/payments/journey-payments-one-time.js +0 -136
  121. package/test/events/payments/journey-payments-plan-change.js +0 -144
  122. package/test/events/payments/journey-payments-refund-webhook.js +0 -180
  123. package/test/events/payments/journey-payments-suspend.js +0 -181
  124. package/test/events/payments/journey-payments-trial-cancel.js +0 -130
  125. package/test/events/payments/journey-payments-trial.js +0 -171
  126. package/test/events/payments/journey-payments-uid-resolution.js +0 -132
  127. package/test/events/payments/journey-payments-upgrade.js +0 -135
  128. package/test/fixtures/chargebee/invoice-one-time.json +0 -27
  129. package/test/fixtures/chargebee/subscription-active.json +0 -44
  130. package/test/fixtures/chargebee/subscription-cancelled.json +0 -42
  131. package/test/fixtures/chargebee/subscription-in-trial.json +0 -41
  132. package/test/fixtures/chargebee/subscription-legacy-plan.json +0 -41
  133. package/test/fixtures/chargebee/subscription-non-renewing.json +0 -41
  134. package/test/fixtures/chargebee/subscription-paused.json +0 -42
  135. package/test/fixtures/chargebee/webhook-payment-failed.json +0 -51
  136. package/test/fixtures/chargebee/webhook-subscription-created.json +0 -47
  137. package/test/fixtures/paypal/order-approved.json +0 -62
  138. package/test/fixtures/paypal/order-completed.json +0 -110
  139. package/test/fixtures/paypal/subscription-active.json +0 -76
  140. package/test/fixtures/paypal/subscription-cancelled.json +0 -50
  141. package/test/fixtures/paypal/subscription-suspended.json +0 -65
  142. package/test/fixtures/stripe/checkout-session-completed.json +0 -130
  143. package/test/fixtures/stripe/invoice-payment-failed.json +0 -148
  144. package/test/fixtures/stripe/invoice-subscription-payment-failed.json +0 -28
  145. package/test/fixtures/stripe/subscription-active.json +0 -161
  146. package/test/fixtures/stripe/subscription-canceled.json +0 -161
  147. package/test/fixtures/stripe/subscription-trialing.json +0 -161
  148. package/test/functions/admin/database-read.js +0 -134
  149. package/test/functions/admin/database-write.js +0 -159
  150. package/test/functions/admin/edit-post.js +0 -366
  151. package/test/functions/admin/firestore-query.js +0 -207
  152. package/test/functions/admin/firestore-read.js +0 -129
  153. package/test/functions/admin/firestore-write.js +0 -105
  154. package/test/functions/admin/get-stats.js +0 -115
  155. package/test/functions/admin/send-email.js +0 -119
  156. package/test/functions/admin/send-notification.js +0 -208
  157. package/test/functions/admin/write-repo-content.js +0 -223
  158. package/test/functions/general/add-marketing-contact.js +0 -335
  159. package/test/functions/general/fetch-post.js +0 -54
  160. package/test/functions/general/generate-uuid.js +0 -114
  161. package/test/functions/test/authenticate.js +0 -64
  162. package/test/functions/user/create-custom-token.js +0 -73
  163. package/test/functions/user/delete.js +0 -135
  164. package/test/functions/user/get-active-sessions.js +0 -111
  165. package/test/functions/user/get-subscription-info.js +0 -99
  166. package/test/functions/user/regenerate-api-keys.js +0 -156
  167. package/test/functions/user/resolve.js +0 -52
  168. package/test/functions/user/sign-out-all-sessions.js +0 -94
  169. package/test/functions/user/sign-up.js +0 -252
  170. package/test/functions/user/submit-feedback.js +0 -104
  171. package/test/functions/user/validate-settings.js +0 -82
  172. package/test/helpers/ai-request-payload.js +0 -300
  173. package/test/helpers/ai-test-provider.js +0 -202
  174. package/test/helpers/ai-tools-format.js +0 -383
  175. package/test/helpers/content/blog-auto-publisher.js +0 -484
  176. package/test/helpers/content/feed-parser.js +0 -528
  177. package/test/helpers/content/ghostii-blocks.js +0 -134
  178. package/test/helpers/content/ghostii-feed-integration.js +0 -404
  179. package/test/helpers/content/ghostii-write-article.js +0 -243
  180. package/test/helpers/environment.js +0 -230
  181. package/test/helpers/infer-contact.js +0 -113
  182. package/test/helpers/merge-line-files.js +0 -271
  183. package/test/helpers/payment/chargebee/parse-webhook.js +0 -413
  184. package/test/helpers/payment/chargebee/to-unified-one-time.js +0 -147
  185. package/test/helpers/payment/chargebee/to-unified-subscription.js +0 -648
  186. package/test/helpers/payment/paypal/parse-webhook.js +0 -539
  187. package/test/helpers/payment/paypal/to-unified-one-time.js +0 -382
  188. package/test/helpers/payment/paypal/to-unified-subscription.js +0 -820
  189. package/test/helpers/payment/stripe/parse-webhook.js +0 -447
  190. package/test/helpers/payment/stripe/to-unified-one-time.js +0 -306
  191. package/test/helpers/payment/stripe/to-unified-subscription.js +0 -708
  192. package/test/helpers/sanitize.js +0 -222
  193. package/test/helpers/slugify.js +0 -394
  194. package/test/helpers/storage.js +0 -194
  195. package/test/helpers/user.js +0 -755
  196. package/test/helpers/webhook-forward.js +0 -418
  197. package/test/mcp/discovery.js +0 -53
  198. package/test/mcp/oauth.js +0 -161
  199. package/test/mcp/protocol.js +0 -268
  200. package/test/mcp/roles.js +0 -188
  201. package/test/mcp/utils.js +0 -245
  202. package/test/routes/admin/create-post.js +0 -364
  203. package/test/routes/admin/database.js +0 -131
  204. package/test/routes/admin/deduplicate-image-alts.js +0 -190
  205. package/test/routes/admin/email.js +0 -114
  206. package/test/routes/admin/firestore-query.js +0 -204
  207. package/test/routes/admin/firestore.js +0 -127
  208. package/test/routes/admin/infer-contact.js +0 -218
  209. package/test/routes/admin/notification.js +0 -198
  210. package/test/routes/admin/post-resize-image.js +0 -184
  211. package/test/routes/admin/post.js +0 -368
  212. package/test/routes/admin/repo-content.js +0 -223
  213. package/test/routes/admin/stats.js +0 -112
  214. package/test/routes/content/post.js +0 -54
  215. package/test/routes/general/uuid.js +0 -115
  216. package/test/routes/marketing/campaign.js +0 -125
  217. package/test/routes/marketing/contact.js +0 -370
  218. package/test/routes/marketing/email-preferences.js +0 -292
  219. package/test/routes/marketing/push-send.js +0 -32
  220. package/test/routes/marketing/webhook-forward.js +0 -61
  221. package/test/routes/marketing/webhook.js +0 -639
  222. package/test/routes/payments/cancel.js +0 -163
  223. package/test/routes/payments/discount.js +0 -80
  224. package/test/routes/payments/dispute-alert.js +0 -320
  225. package/test/routes/payments/intent.js +0 -336
  226. package/test/routes/payments/portal.js +0 -92
  227. package/test/routes/payments/refund.js +0 -179
  228. package/test/routes/payments/trial-eligibility.js +0 -71
  229. package/test/routes/payments/webhook.js +0 -107
  230. package/test/routes/test/authenticate.js +0 -59
  231. package/test/routes/test/schema.js +0 -554
  232. package/test/routes/test/usage.js +0 -342
  233. package/test/routes/user/api-keys.js +0 -156
  234. package/test/routes/user/delete.js +0 -136
  235. package/test/routes/user/feedback.js +0 -104
  236. package/test/routes/user/sessions.js +0 -199
  237. package/test/routes/user/settings-validate.js +0 -82
  238. package/test/routes/user/signup.js +0 -542
  239. package/test/routes/user/subscription.js +0 -99
  240. package/test/routes/user/token.js +0 -73
  241. package/test/routes/user/user.js +0 -157
  242. package/test/rules/notifications.js +0 -410
  243. package/test/rules/payments-carts.js +0 -371
  244. package/test/rules/user.js +0 -396
@@ -1,163 +0,0 @@
1
- /**
2
- * Test: POST /payments/cancel - Validation errors
3
- * Tests rejection cases before any processor call is made.
4
- * See test/events/payments/journey-payments-cancel-endpoint.js for the full end-to-end journey.
5
- */
6
- module.exports = {
7
- description: 'Payment cancel endpoint: validation errors',
8
- type: 'group',
9
- timeout: 15000,
10
-
11
- tests: [
12
- {
13
- name: 'rejects-unauthenticated',
14
- async run({ http, assert }) {
15
- const response = await http.as('none').post('backend-manager/payments/cancel', {
16
- confirmed: true,
17
- });
18
-
19
- assert.isError(response, 401, 'Should reject unauthenticated request');
20
- },
21
- },
22
-
23
- {
24
- name: 'rejects-missing-confirmed',
25
- async run({ http, assert }) {
26
- const response = await http.as('basic').post('backend-manager/payments/cancel', {
27
- reason: 'Too expensive',
28
- });
29
-
30
- assert.isError(response, 400, 'Should reject missing confirmed field');
31
- },
32
- },
33
-
34
- {
35
- name: 'rejects-basic-user',
36
- async run({ http, assert }) {
37
- const response = await http.as('basic').post('backend-manager/payments/cancel', {
38
- confirmed: true,
39
- });
40
-
41
- assert.isError(response, 400, 'Should reject basic user with no paid subscription');
42
- },
43
- },
44
-
45
- {
46
- name: 'rejects-no-processor-or-resource-id',
47
- async run({ http, assert }) {
48
- // cancel-no-processor starts with payment.processor=null
49
- const response = await http.as('cancel-no-processor').post('backend-manager/payments/cancel', {
50
- confirmed: true,
51
- });
52
-
53
- assert.isError(response, 400, 'Should reject when no processor or resourceId is set');
54
- },
55
- },
56
-
57
- {
58
- name: 'rejects-already-pending-cancellation',
59
- async run({ http, assert }) {
60
- // cancel-already-pending starts with cancellation.pending=true
61
- const response = await http.as('cancel-already-pending').post('backend-manager/payments/cancel', {
62
- confirmed: true,
63
- });
64
-
65
- assert.isError(response, 400, 'Should reject when cancellation already pending');
66
- },
67
- },
68
-
69
- {
70
- name: 'rejects-subscription-younger-than-24-hours',
71
- async run({ http, assert }) {
72
- // cancel-too-young starts with startDate set to now (< 24 hours old)
73
- const response = await http.as('cancel-too-young').post('backend-manager/payments/cancel', {
74
- confirmed: true,
75
- });
76
-
77
- assert.isError(response, 400, 'Should reject subscription younger than 24 hours');
78
- },
79
- },
80
-
81
- {
82
- name: 'rejects-unknown-processor',
83
- async run({ http, assert }) {
84
- // cancel-unknown-processor starts with processor='unknown-processor'
85
- const response = await http.as('cancel-unknown-processor').post('backend-manager/payments/cancel', {
86
- confirmed: true,
87
- });
88
-
89
- assert.isError(response, 400, 'Should reject unknown processor');
90
- },
91
- },
92
-
93
- {
94
- name: 'allows-suspended-subscription',
95
- async run({ http, assert, config, accounts, firestore, waitFor, skip }) {
96
- const uid = accounts['cancel-suspended'].uid;
97
-
98
- const response = await http.as('cancel-suspended').post('backend-manager/payments/cancel', {
99
- confirmed: true,
100
- reason: 'Subscription was suspended',
101
- skipGuards: true,
102
- });
103
-
104
- assert.isSuccess(response, 'Should allow cancelling a suspended subscription');
105
- assert.equal(response.data.success, true, 'Should return success: true');
106
-
107
- // Verify the subscription was reset to cancelled (via fallback or webhook)
108
- await waitFor(async () => {
109
- const userDoc = await firestore.get(`users/${uid}`);
110
- return userDoc?.subscription?.status === 'cancelled'
111
- || userDoc?.subscription?.cancellation?.pending === true;
112
- }, 15000, 500);
113
- },
114
- },
115
-
116
- {
117
- name: 'succeeds-with-test-processor',
118
- async run({ http, assert, config, accounts, firestore, waitFor, skip }) {
119
- const uid = accounts['route-cancel-success'].uid;
120
- const paidProduct = config.payment.products.find(p => p.id !== 'basic' && p.prices?.monthly);
121
- if (!paidProduct) {
122
- skip('No paid product with monthly price configured in this brand');
123
- }
124
-
125
- // Step 1: Create a test subscription intent to set up a proper paid subscription
126
- const intentResponse = await http.as('route-cancel-success').post('backend-manager/payments/intent', {
127
- processor: 'test',
128
- productId: paidProduct.id,
129
- frequency: 'monthly',
130
- });
131
-
132
- assert.isSuccess(intentResponse, 'Intent should succeed');
133
-
134
- // Wait for the auto-webhook to activate the subscription
135
- await waitFor(async () => {
136
- const userDoc = await firestore.get(`users/${uid}`);
137
- return userDoc?.subscription?.payment?.processor === 'test'
138
- && userDoc?.subscription?.payment?.resourceId
139
- && userDoc?.subscription?.status === 'active';
140
- }, 15000, 500);
141
-
142
- // Step 2: Call the cancel endpoint (skipGuards bypasses the 24-hour age guard)
143
- const cancelResponse = await http.as('route-cancel-success').post('backend-manager/payments/cancel', {
144
- confirmed: true,
145
- reason: 'Too expensive',
146
- skipGuards: true,
147
- });
148
-
149
- assert.isSuccess(cancelResponse, 'Cancel should succeed');
150
- assert.equal(cancelResponse.data.success, true, 'Should return success: true');
151
-
152
- // Step 3: Verify cancellation.pending was set via the webhook pipeline
153
- await waitFor(async () => {
154
- const userDoc = await firestore.get(`users/${uid}`);
155
- return userDoc?.subscription?.cancellation?.pending === true;
156
- }, 15000, 500);
157
-
158
- const userDoc = await firestore.get(`users/${uid}`);
159
- assert.equal(userDoc?.subscription?.cancellation?.pending, true, 'Cancellation should be pending');
160
- },
161
- },
162
- ],
163
- };
@@ -1,80 +0,0 @@
1
- /**
2
- * Test: GET /payments/discount
3
- * Tests discount code validation endpoint
4
- */
5
- module.exports = {
6
- description: 'Discount code validation',
7
- type: 'group',
8
- timeout: 15000,
9
-
10
- tests: [
11
- {
12
- name: 'rejects-missing-code',
13
- async run({ http, assert }) {
14
- const response = await http.as('none').get('backend-manager/payments/discount');
15
-
16
- assert.isError(response, 400, 'Should reject missing code');
17
- },
18
- },
19
-
20
- {
21
- name: 'returns-valid-for-known-code',
22
- async run({ http, assert }) {
23
- const response = await http.as('none').get('backend-manager/payments/discount', {
24
- code: 'FLASH20',
25
- });
26
-
27
- assert.isSuccess(response, 'Should succeed for valid code');
28
- assert.equal(response.data.valid, true, 'Should be valid');
29
- assert.equal(response.data.code, 'FLASH20', 'Should return normalized code');
30
- assert.equal(response.data.percent, 20, 'Should return correct percent');
31
- assert.equal(response.data.duration, 'once', 'Should return duration');
32
- },
33
- },
34
-
35
- {
36
- name: 'returns-valid-case-insensitive',
37
- async run({ http, assert }) {
38
- const response = await http.as('none').get('backend-manager/payments/discount', {
39
- code: 'flash20',
40
- });
41
-
42
- assert.isSuccess(response, 'Should succeed for lowercase code');
43
- assert.equal(response.data.valid, true, 'Should be valid (case-insensitive)');
44
- assert.equal(response.data.code, 'FLASH20', 'Should return uppercase code');
45
- },
46
- },
47
-
48
- {
49
- name: 'returns-invalid-for-unknown-code',
50
- async run({ http, assert }) {
51
- const response = await http.as('none').get('backend-manager/payments/discount', {
52
- code: 'NOTAREALCODE',
53
- });
54
-
55
- assert.isSuccess(response, 'Should return 200 even for invalid code');
56
- assert.equal(response.data.valid, false, 'Should be invalid');
57
- },
58
- },
59
-
60
- {
61
- name: 'validates-all-known-codes',
62
- async run({ http, assert }) {
63
- const codes = [
64
- { code: 'FLASH20', percent: 20 },
65
- { code: 'SAVE10', percent: 10 },
66
- { code: 'WELCOME15', percent: 15 },
67
- ];
68
-
69
- for (const { code, percent } of codes) {
70
- const response = await http.as('none').get('backend-manager/payments/discount', { code });
71
-
72
- assert.isSuccess(response, `Should succeed for ${code}`);
73
- assert.equal(response.data.valid, true, `${code} should be valid`);
74
- assert.equal(response.data.percent, percent, `${code} should be ${percent}%`);
75
- assert.equal(response.data.duration, 'once', `${code} should be once`);
76
- }
77
- },
78
- },
79
- ],
80
- };
@@ -1,320 +0,0 @@
1
- /**
2
- * Test: POST /payments/dispute-alert
3
- * Tests the dispute alert endpoint validates requests and saves to Firestore
4
- */
5
- module.exports = {
6
- description: 'Dispute alert endpoint',
7
- type: 'group',
8
- timeout: 30000,
9
-
10
- tests: [
11
- {
12
- name: 'rejects-missing-key',
13
- auth: 'none',
14
- async run({ http, assert }) {
15
- const response = await http.as('none').post('backend-manager/payments/dispute-alert', {});
16
-
17
- assert.isError(response, 401, 'Should reject missing key');
18
- },
19
- },
20
-
21
- {
22
- name: 'rejects-invalid-key',
23
- auth: 'none',
24
- async run({ http, assert }) {
25
- const response = await http.as('none').post('backend-manager/payments/dispute-alert?key=wrong-key', {});
26
-
27
- assert.isError(response, 401, 'Should reject invalid key');
28
- },
29
- },
30
-
31
- {
32
- name: 'rejects-unknown-provider',
33
- auth: 'none',
34
- async run({ http, assert }) {
35
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?provider=unknown&key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
36
- id: '_test-dispute-unknown-provider',
37
- card: '4242',
38
- amount: 9.99,
39
- transactionDate: '2026-01-15',
40
- });
41
-
42
- assert.isError(response, 400, 'Should reject unknown alert provider');
43
- },
44
- },
45
-
46
- {
47
- name: 'rejects-missing-id',
48
- auth: 'none',
49
- async run({ http, assert }) {
50
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
51
- card: '4242',
52
- amount: 9.99,
53
- transactionDate: '2026-01-15',
54
- });
55
-
56
- assert.isError(response, 400, 'Should reject missing id');
57
- },
58
- },
59
-
60
- {
61
- name: 'rejects-missing-card',
62
- auth: 'none',
63
- async run({ http, assert }) {
64
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
65
- id: '_test-dispute-no-card',
66
- amount: 9.99,
67
- transactionDate: '2026-01-15',
68
- });
69
-
70
- assert.isError(response, 400, 'Should reject missing card');
71
- },
72
- },
73
-
74
- {
75
- name: 'rejects-missing-amount',
76
- auth: 'none',
77
- async run({ http, assert }) {
78
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
79
- id: '_test-dispute-no-amount',
80
- card: '4242',
81
- transactionDate: '2026-01-15',
82
- });
83
-
84
- assert.isError(response, 400, 'Should reject missing amount');
85
- },
86
- },
87
-
88
- {
89
- name: 'rejects-missing-transaction-date',
90
- auth: 'none',
91
- async run({ http, assert }) {
92
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
93
- id: '_test-dispute-no-date',
94
- card: '4242',
95
- amount: 9.99,
96
- });
97
-
98
- assert.isError(response, 400, 'Should reject missing transactionDate');
99
- },
100
- },
101
-
102
- {
103
- name: 'accepts-valid-chargeblast-alert',
104
- auth: 'none',
105
- async run({ http, assert, firestore }) {
106
- const alertId = '_test-dispute-valid';
107
-
108
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
109
- id: alertId,
110
- card: '4242424242424242',
111
- cardBrand: 'Visa',
112
- amount: 29.99,
113
- transactionDate: '2026-03-07 14:30:00',
114
- processor: 'stripe',
115
- alertType: 'FRAUD',
116
- customerEmail: 'test@example.com',
117
- externalOrder: 'ch_test123',
118
- metadata: 'pi_test456',
119
- externalUrl: 'https://dashboard.stripe.com/charges/ch_test123',
120
- reasonCode: 'WIP',
121
- subprovider: 'Ethoca',
122
- isRefunded: false,
123
- });
124
-
125
- assert.isSuccess(response, 'Should accept valid Chargeblast alert');
126
- assert.equal(response.data.received, true, 'Should confirm receipt');
127
-
128
- // Verify doc was saved to Firestore
129
- const doc = await firestore.get(`payments-disputes/${alertId}`);
130
- assert.ok(doc, 'Dispute doc should exist in Firestore');
131
- assert.equal(doc.provider, 'chargeblast', 'Provider should be chargeblast');
132
- assert.ok(
133
- doc.status === 'pending' || doc.status === 'processing',
134
- 'Status should be pending or processing',
135
- );
136
-
137
- // Verify core normalized alert data
138
- assert.equal(doc.alert.card.last4, '4242', 'Should extract last4 from full card number');
139
- assert.equal(doc.alert.card.brand, 'visa', 'Should lowercase card brand');
140
- assert.equal(doc.alert.amount, 29.99, 'Amount should be preserved');
141
- assert.equal(doc.alert.transactionDate, '2026-03-07', 'Should extract date without time');
142
- assert.equal(doc.alert.processor, 'stripe', 'Processor should be stripe');
143
-
144
- // Verify new normalized fields
145
- assert.equal(doc.alert.alertType, 'FRAUD', 'Alert type should be preserved');
146
- assert.equal(doc.alert.customerEmail, 'test@example.com', 'Customer email should be preserved');
147
- assert.equal(doc.alert.chargeId, 'ch_test123', 'Charge ID should be extracted from externalOrder');
148
- assert.equal(doc.alert.paymentIntentId, 'pi_test456', 'Payment intent ID should be extracted from metadata');
149
- assert.equal(doc.alert.stripeUrl, 'https://dashboard.stripe.com/charges/ch_test123', 'Stripe URL should be preserved');
150
- assert.equal(doc.alert.reasonCode, 'WIP', 'Reason code should be preserved');
151
- assert.equal(doc.alert.subprovider, 'Ethoca', 'Subprovider should be preserved');
152
- assert.equal(doc.alert.isRefunded, false, 'isRefunded should be preserved');
153
-
154
- // Verify raw payload is preserved
155
- assert.ok(doc.raw, 'Raw payload should be preserved');
156
- assert.equal(doc.raw.id, alertId, 'Raw id should match');
157
- },
158
- },
159
-
160
- {
161
- name: 'accepts-alert-with-alertId-field',
162
- auth: 'none',
163
- async run({ http, assert, firestore }) {
164
- const alertId = '_test-dispute-alertid-field';
165
-
166
- // Chargeblast alert.created events use alertId instead of id
167
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
168
- alertId: alertId,
169
- card: '546616******5805',
170
- cardBrand: 'Mastercard',
171
- amount: 8,
172
- transactionDate: '2026-03-19 00:00:00.000000Z',
173
- });
174
-
175
- assert.isSuccess(response, 'Should accept alert using alertId field');
176
-
177
- const doc = await firestore.get(`payments-disputes/${alertId}`);
178
- assert.ok(doc, 'Dispute doc should exist in Firestore');
179
- assert.equal(doc.id, alertId, 'Doc ID should match alertId');
180
- assert.equal(doc.alert.id, alertId, 'Alert id should be set from alertId');
181
- assert.equal(doc.alert.card.last4, '5805', 'Should extract last4 from masked card');
182
- },
183
- },
184
-
185
- {
186
- name: 'accepts-alert-without-optional-fields',
187
- auth: 'none',
188
- async run({ http, assert, firestore }) {
189
- const alertId = '_test-dispute-minimal';
190
-
191
- // Send minimal alert (alert.created shape — no externalOrder, metadata, etc.)
192
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
193
- id: alertId,
194
- card: '9124',
195
- amount: 10,
196
- transactionDate: '2026-03-21 00:01:02',
197
- });
198
-
199
- assert.isSuccess(response, 'Should accept minimal alert');
200
-
201
- const doc = await firestore.get(`payments-disputes/${alertId}`);
202
- assert.equal(doc.alert.card.last4, '9124', 'Should use card as last4');
203
- assert.equal(doc.alert.processor, 'stripe', 'Processor should default to stripe');
204
- assert.equal(doc.alert.chargeId, null, 'Charge ID should be null when not provided');
205
- assert.equal(doc.alert.paymentIntentId, null, 'Payment intent should be null when not provided');
206
- assert.equal(doc.alert.customerEmail, null, 'Customer email should be null when not provided');
207
- assert.equal(doc.alert.alertType, null, 'Alert type should be null when not provided');
208
- assert.equal(doc.alert.stripeUrl, null, 'Stripe URL should be null when not provided');
209
- assert.equal(doc.alert.reasonCode, null, 'Reason code should be null when not provided');
210
- assert.equal(doc.alert.subprovider, null, 'Subprovider should be null when not provided');
211
- assert.equal(doc.alert.isRefunded, false, 'isRefunded should default to false');
212
- },
213
- },
214
-
215
- {
216
- name: 'accepts-alert-with-last4-only',
217
- auth: 'none',
218
- async run({ http, assert, firestore }) {
219
- const alertId = '_test-dispute-last4';
220
-
221
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
222
- id: alertId,
223
- card: '1234',
224
- amount: 9.99,
225
- transactionDate: '2026-01-15',
226
- });
227
-
228
- assert.isSuccess(response, 'Should accept alert with card last4 only');
229
-
230
- const doc = await firestore.get(`payments-disputes/${alertId}`);
231
- assert.equal(doc.alert.card.last4, '1234', 'Should use card value as last4 when already 4 digits');
232
- assert.equal(doc.alert.processor, 'stripe', 'Processor should default to stripe');
233
- },
234
- },
235
-
236
- {
237
- name: 'deduplicates-dispute-alerts',
238
- auth: 'none',
239
- async run({ http, assert, firestore }) {
240
- const alertId = '_test-dispute-duplicate';
241
-
242
- // Pre-seed an in-flight dispute directly. We seed (rather than POST a first alert)
243
- // because the route fires an async on-write trigger that, for synthetic test data
244
- // with no matching charge, can transition the doc to 'failed' — at which point the
245
- // dedup contract intentionally treats it as RETRYABLE, not a duplicate. Racing a
246
- // live POST against that trigger made this test flaky. Seeding 'processing' (a
247
- // non-failed, in-flight state) deterministically exercises the dedup path.
248
- await firestore.set(`payments-disputes/${alertId}`, {
249
- id: alertId,
250
- status: 'processing',
251
- });
252
-
253
- // A subsequent identical alert must be reported as a duplicate (not reprocessed).
254
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
255
- id: alertId,
256
- card: '4242',
257
- amount: 29.99,
258
- transactionDate: '2026-03-07',
259
- });
260
-
261
- assert.isSuccess(response, 'Duplicate should still return 200');
262
- assert.equal(response.data.duplicate, true, 'Should indicate duplicate');
263
- },
264
- },
265
-
266
- {
267
- name: 'retries-failed-alerts',
268
- auth: 'none',
269
- async run({ http, assert, firestore }) {
270
- const alertId = '_test-dispute-retry';
271
-
272
- // Pre-seed a failed dispute
273
- await firestore.set(`payments-disputes/${alertId}`, {
274
- id: alertId,
275
- status: 'failed',
276
- error: 'Previous error',
277
- });
278
-
279
- // Send alert with same ID — should retry since previous status was 'failed'
280
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
281
- id: alertId,
282
- card: '4242',
283
- amount: 29.99,
284
- transactionDate: '2026-03-07',
285
- });
286
-
287
- assert.isSuccess(response, 'Should accept retry of failed alert');
288
- assert.ok(!response.data.duplicate, 'Should not indicate duplicate for failed retry');
289
-
290
- // Verify doc was updated
291
- const doc = await firestore.get(`payments-disputes/${alertId}`);
292
- assert.ok(
293
- doc.status === 'pending' || doc.status === 'processing',
294
- 'Status should be pending or processing after retry',
295
- );
296
- },
297
- },
298
-
299
- {
300
- name: 'defaults-provider-to-chargeblast',
301
- auth: 'none',
302
- async run({ http, assert, firestore }) {
303
- const alertId = '_test-dispute-default-provider';
304
-
305
- // Send without provider query param
306
- const response = await http.as('none').post(`backend-manager/payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
307
- id: alertId,
308
- card: '4242',
309
- amount: 9.99,
310
- transactionDate: '2026-01-15',
311
- });
312
-
313
- assert.isSuccess(response, 'Should accept without explicit provider param');
314
-
315
- const doc = await firestore.get(`payments-disputes/${alertId}`);
316
- assert.equal(doc.provider, 'chargeblast', 'Provider should default to chargeblast');
317
- },
318
- },
319
- ],
320
- };