backend-manager 5.2.5 → 5.2.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 (41) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/TODO-CANCEL-EMAIL-MISSING-ORDER-ID.md +159 -0
  3. package/TODO-WEBHOOK-KEY-LEGACY-REMOVAL.md +15 -0
  4. package/TODO-WEBHOOK-KEY-UPGRADE.md +138 -0
  5. package/docs/stripe-webhook-forwarding.md +2 -2
  6. package/package.json +1 -1
  7. package/scripts/test-helper-providers.js +162 -0
  8. package/src/cli/commands/base-command.js +5 -5
  9. package/src/cli/commands/emulator.js +201 -54
  10. package/src/cli/commands/test.js +80 -9
  11. package/src/manager/events/cron/daily/ghostii-auto-publisher.js +2 -2
  12. package/src/manager/events/firestore/payments-webhooks/analytics.js +2 -2
  13. package/src/manager/functions/core/actions/api/user/delete.js +1 -1
  14. package/src/manager/helpers/analytics.js +1 -1
  15. package/src/manager/libraries/email/generators/newsletter.js +2 -2
  16. package/src/manager/libraries/email/providers/beehiiv.js +11 -6
  17. package/src/manager/libraries/email/providers/sendgrid.js +6 -6
  18. package/src/manager/libraries/email/validation.js +1 -1
  19. package/src/manager/libraries/infer-contact.js +1 -1
  20. package/src/manager/routes/general/email/post.js +4 -2
  21. package/src/manager/routes/marketing/email-preferences/post.js +2 -2
  22. package/src/manager/routes/payments/dispute-alert/post.js +3 -3
  23. package/src/manager/routes/payments/intent/processors/test.js +2 -2
  24. package/src/manager/routes/payments/webhook/post.js +2 -2
  25. package/src/manager/routes/user/delete.js +1 -1
  26. package/src/manager/routes/user/oauth2/providers/discord.js +1 -1
  27. package/src/manager/routes/user/oauth2/providers/google.js +1 -1
  28. package/src/test/runner.js +7 -0
  29. package/src/test/utils/http-client.js +1 -0
  30. package/test/events/payments/journey-payments-cancel.js +4 -4
  31. package/test/events/payments/journey-payments-failure.js +2 -2
  32. package/test/events/payments/journey-payments-legacy-product.js +1 -1
  33. package/test/events/payments/journey-payments-one-time-failure.js +1 -1
  34. package/test/events/payments/journey-payments-plan-change.js +1 -1
  35. package/test/events/payments/journey-payments-refund-webhook.js +4 -4
  36. package/test/events/payments/journey-payments-suspend.js +4 -4
  37. package/test/events/payments/journey-payments-trial.js +2 -2
  38. package/test/events/payments/journey-payments-uid-resolution.js +1 -1
  39. package/test/routes/payments/dispute-alert.js +13 -13
  40. package/test/routes/payments/webhook.js +3 -3
  41. /package/src/manager/routes/general/email/templates/{download-app-link.js → general/download-app-link.js} +0 -0
@@ -22,10 +22,12 @@ module.exports = async ({ assistant, Manager, settings }) => {
22
22
  payload: {},
23
23
  };
24
24
 
25
- // Load email template
25
+ // Load email template — colons in id are converted to nested folders
26
+ // (e.g. "general:download-app-link" → templates/general/download-app-link.js)
27
+ const templatePath = settings.id.split(':').join('/');
26
28
  let emailPayload;
27
29
  try {
28
- const script = require(path.join(__dirname, 'templates', `${settings.id}.js`));
30
+ const script = require(path.join(__dirname, 'templates', `${templatePath}.js`));
29
31
  emailPayload = merge(
30
32
  {},
31
33
  DEFAULT,
@@ -166,7 +166,7 @@ async function handleAnonymous({ assistant, Manager, settings, analytics }) {
166
166
  method: 'POST',
167
167
  response: 'json',
168
168
  headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },
169
- timeout: 10000,
169
+ timeout: 60000,
170
170
  body: { recipient_emails: [email] },
171
171
  });
172
172
  } else {
@@ -176,7 +176,7 @@ async function handleAnonymous({ assistant, Manager, settings, analytics }) {
176
176
  method: 'DELETE',
177
177
  response: 'text',
178
178
  headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },
179
- timeout: 10000,
179
+ timeout: 60000,
180
180
  });
181
181
  }
182
182
  } catch (e) {
@@ -8,16 +8,16 @@ const powertools = require('node-powertools');
8
8
  *
9
9
  * Query params:
10
10
  * - provider: alert provider name (default: 'chargeblast')
11
- * - key: must match BACKEND_MANAGER_KEY
11
+ * - key: must match BACKEND_MANAGER_WEBHOOK_KEY (BACKEND_MANAGER_KEY accepted as legacy fallback)
12
12
  */
13
13
  module.exports = async ({ assistant, Manager, libraries }) => {
14
14
  const { admin } = libraries;
15
15
  const body = assistant.request.body;
16
16
  const query = assistant.request.query;
17
17
 
18
- // Validate key against BACKEND_MANAGER_KEY
18
+ // Validate key accept either BACKEND_MANAGER_WEBHOOK_KEY (preferred) or BACKEND_MANAGER_KEY (legacy)
19
19
  const key = query.key;
20
- if (!key || key !== process.env.BACKEND_MANAGER_KEY) {
20
+ if (!key || (key !== process.env.BACKEND_MANAGER_WEBHOOK_KEY && key !== process.env.BACKEND_MANAGER_KEY)) {
21
21
  return assistant.respond('Invalid key', { code: 401 });
22
22
  }
23
23
 
@@ -155,12 +155,12 @@ async function createOneTimeIntent({ uid, orderId, product, productId, confirmat
155
155
  * Fire-and-forget webhook to trigger the full pipeline
156
156
  */
157
157
  function fireWebhook({ event, assistant }) {
158
- const webhookUrl = `${assistant.Manager.project.apiUrl}/backend-manager/payments/webhook?processor=test&key=${process.env.BACKEND_MANAGER_KEY}`;
158
+ const webhookUrl = `${assistant.Manager.project.apiUrl}/backend-manager/payments/webhook?processor=test&key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`;
159
159
  fetch(webhookUrl, {
160
160
  method: 'POST',
161
161
  response: 'json',
162
162
  body: event,
163
- timeout: 15000,
163
+ timeout: 60000,
164
164
  }).catch((e) => {
165
165
  assistant.log(`Test processor auto-webhook failed: ${e.message}`);
166
166
  });
@@ -24,8 +24,8 @@ module.exports = async ({ assistant, Manager, libraries }) => {
24
24
  return assistant.respond('Missing processor parameter', { code: 400 });
25
25
  }
26
26
 
27
- // Validate key against BACKEND_MANAGER_KEY
28
- if (!key || key !== process.env.BACKEND_MANAGER_KEY) {
27
+ // Validate key accept either BACKEND_MANAGER_WEBHOOK_KEY (preferred) or BACKEND_MANAGER_KEY (legacy)
28
+ if (!key || (key !== process.env.BACKEND_MANAGER_WEBHOOK_KEY && key !== process.env.BACKEND_MANAGER_KEY)) {
29
29
  return assistant.respond('Invalid key', { code: 401 });
30
30
  }
31
31
 
@@ -48,7 +48,7 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
48
48
 
49
49
  await fetch(`${Manager.project.apiUrl}/backend-manager/user/sessions`, {
50
50
  method: 'delete',
51
- timeout: 30000,
51
+ timeout: 60000,
52
52
  response: 'json',
53
53
  tries: 2,
54
54
  log: true,
@@ -22,7 +22,7 @@ module.exports = {
22
22
 
23
23
  const response = await fetch(this.urls.revoke, {
24
24
  method: 'POST',
25
- timeout: 30000,
25
+ timeout: 60000,
26
26
  body: new URLSearchParams({
27
27
  token,
28
28
  client_id: clientId,
@@ -27,7 +27,7 @@ module.exports = {
27
27
 
28
28
  const response = await fetch(this.urls.revoke, {
29
29
  method: 'POST',
30
- timeout: 30000,
30
+ timeout: 60000,
31
31
  body: new URLSearchParams({ token }),
32
32
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
33
33
  }).catch(e => e);
@@ -147,6 +147,12 @@ class TestRunner {
147
147
  return false;
148
148
  }
149
149
 
150
+ if (!this.options.backendManagerWebhookKey) {
151
+ console.log(chalk.red(' ✗ Missing backendManagerWebhookKey'));
152
+ console.log(chalk.gray(' Set BEM_BACKEND_MANAGER_WEBHOOK_KEY environment variable or pass --webhook-key flag'));
153
+ return false;
154
+ }
155
+
150
156
  if (!this.options.brand?.id) {
151
157
  console.log(chalk.red(' ✗ Missing brand.id'));
152
158
  console.log(chalk.gray(' Could not determine brand ID from configuration'));
@@ -661,6 +667,7 @@ class TestRunner {
661
667
  timeout: this.options.timeout,
662
668
  accounts: this.accounts,
663
669
  backendManagerKey: this.options.backendManagerKey,
670
+ backendManagerWebhookKey: this.options.backendManagerWebhookKey,
664
671
  });
665
672
 
666
673
  // Set default auth
@@ -22,6 +22,7 @@ class HttpClient {
22
22
  // Store accounts reference for as() method
23
23
  this.accounts = options.accounts || null;
24
24
  this.backendManagerKey = options.backendManagerKey || '';
25
+ this.backendManagerWebhookKey = options.backendManagerWebhookKey || '';
25
26
  }
26
27
 
27
28
  /**
@@ -56,7 +56,7 @@ module.exports = {
56
56
 
57
57
  state.eventId1 = `_test-evt-journey-cancel-pending-${Date.now()}`;
58
58
 
59
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
59
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
60
60
  id: state.eventId1,
61
61
  type: 'customer.subscription.updated',
62
62
  data: {
@@ -64,7 +64,7 @@ module.exports = {
64
64
  id: state.subscriptionId,
65
65
  object: 'subscription',
66
66
  status: 'active',
67
- metadata: { uid: state.uid },
67
+ metadata: { uid: state.uid, orderId: state.orderId },
68
68
  cancel_at_period_end: true,
69
69
  cancel_at: Math.floor(futureDate.getTime() / 1000),
70
70
  canceled_at: null,
@@ -105,7 +105,7 @@ module.exports = {
105
105
  async run({ http, assert, state, config, payments }) {
106
106
  state.eventId2 = `_test-evt-journey-cancel-final-${Date.now()}`;
107
107
 
108
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
108
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
109
109
  id: state.eventId2,
110
110
  type: 'customer.subscription.deleted',
111
111
  data: {
@@ -113,7 +113,7 @@ module.exports = {
113
113
  id: state.subscriptionId,
114
114
  object: 'subscription',
115
115
  status: 'canceled',
116
- metadata: { uid: state.uid },
116
+ metadata: { uid: state.uid, orderId: state.orderId },
117
117
  cancel_at_period_end: false,
118
118
  canceled_at: Math.floor(Date.now() / 1000),
119
119
  current_period_end: Math.floor(Date.now() / 1000),
@@ -58,7 +58,7 @@ module.exports = {
58
58
 
59
59
  // Send invoice.payment_failed with subscription billing reason
60
60
  // This tests the new parseWebhook routing: billing_reason=subscription_cycle → subscription category
61
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
61
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
62
62
  id: state.eventId,
63
63
  type: 'invoice.payment_failed',
64
64
  data: {
@@ -72,7 +72,7 @@ module.exports = {
72
72
  parent: {
73
73
  subscription_details: {
74
74
  subscription: state.subscriptionId,
75
- metadata: { uid: state.uid },
75
+ metadata: { uid: state.uid, orderId: state.orderId },
76
76
  },
77
77
  type: 'subscription_details',
78
78
  },
@@ -66,7 +66,7 @@ module.exports = {
66
66
  // Send a subscription created webhook with the LEGACY product ID
67
67
  // This simulates an existing subscriber whose Stripe subscription still
68
68
  // references the old product ID from before migration
69
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
69
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
70
70
  id: state.legacyEventId,
71
71
  type: 'customer.subscription.created',
72
72
  data: {
@@ -39,7 +39,7 @@ module.exports = {
39
39
 
40
40
  // Send invoice.payment_failed with a non-subscription billing reason
41
41
  // This routes to category: 'one-time' in the webhook parser
42
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
42
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
43
43
  id: state.eventId,
44
44
  type: 'invoice.payment_failed',
45
45
  data: {
@@ -59,7 +59,7 @@ module.exports = {
59
59
  state.eventId = `_test-evt-journey-plan-change-${Date.now()}`;
60
60
 
61
61
  // Send subscription.updated with product B's Stripe product ID (or test sentinel)
62
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
62
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
63
63
  id: state.eventId,
64
64
  type: 'customer.subscription.updated',
65
65
  data: {
@@ -59,7 +59,7 @@ module.exports = {
59
59
 
60
60
  state.cancelEventId = `_test-evt-journey-refund-cancel-${Date.now()}`;
61
61
 
62
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
62
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
63
63
  id: state.cancelEventId,
64
64
  type: 'customer.subscription.updated',
65
65
  data: {
@@ -67,7 +67,7 @@ module.exports = {
67
67
  id: state.subscriptionId,
68
68
  object: 'subscription',
69
69
  status: 'active',
70
- metadata: { uid: state.uid },
70
+ metadata: { uid: state.uid, orderId: state.orderId },
71
71
  cancel_at_period_end: true,
72
72
  cancel_at: Math.floor(futureDate.getTime() / 1000),
73
73
  canceled_at: null,
@@ -110,7 +110,7 @@ module.exports = {
110
110
  state.refundEventId = `_test-evt-journey-refund-charge-${Date.now()}`;
111
111
  state.refundAmountCents = 2800; // $28.00
112
112
 
113
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
113
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
114
114
  id: state.refundEventId,
115
115
  type: 'charge.refunded',
116
116
  data: {
@@ -121,7 +121,7 @@ module.exports = {
121
121
  amount_refunded: state.refundAmountCents,
122
122
  currency: 'usd',
123
123
  subscription: state.subscriptionId,
124
- metadata: { uid: state.uid },
124
+ metadata: { uid: state.uid, orderId: state.orderId },
125
125
  refunds: {
126
126
  data: [
127
127
  {
@@ -53,7 +53,7 @@ module.exports = {
53
53
  async run({ http, assert, state, config, payments }) {
54
54
  state.eventId1 = `_test-evt-journey-suspend-fail-${Date.now()}`;
55
55
 
56
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
56
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
57
57
  id: state.eventId1,
58
58
  type: 'customer.subscription.updated',
59
59
  data: {
@@ -61,7 +61,7 @@ module.exports = {
61
61
  id: state.subscriptionId,
62
62
  object: 'subscription',
63
63
  status: 'past_due',
64
- metadata: { uid: state.uid },
64
+ metadata: { uid: state.uid, orderId: state.orderId },
65
65
  cancel_at_period_end: false,
66
66
  canceled_at: null,
67
67
  current_period_end: Math.floor(Date.now() / 1000) + 86400,
@@ -104,7 +104,7 @@ module.exports = {
104
104
 
105
105
  state.eventId2 = `_test-evt-journey-suspend-recover-${Date.now()}`;
106
106
 
107
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
107
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
108
108
  id: state.eventId2,
109
109
  type: 'customer.subscription.updated',
110
110
  data: {
@@ -112,7 +112,7 @@ module.exports = {
112
112
  id: state.subscriptionId,
113
113
  object: 'subscription',
114
114
  status: 'active',
115
- metadata: { uid: state.uid },
115
+ metadata: { uid: state.uid, orderId: state.orderId },
116
116
  cancel_at_period_end: false,
117
117
  canceled_at: null,
118
118
  current_period_end: Math.floor(futureDate.getTime() / 1000),
@@ -115,7 +115,7 @@ module.exports = {
115
115
 
116
116
  state.eventId2 = `_test-evt-journey-trial-active-${Date.now()}`;
117
117
 
118
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
118
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
119
119
  id: state.eventId2,
120
120
  type: 'customer.subscription.updated',
121
121
  data: {
@@ -123,7 +123,7 @@ module.exports = {
123
123
  id: state.subscriptionId,
124
124
  object: 'subscription',
125
125
  status: 'active',
126
- metadata: { uid: state.uid },
126
+ metadata: { uid: state.uid, orderId: state.orderId },
127
127
  cancel_at_period_end: false,
128
128
  canceled_at: null,
129
129
  current_period_end: Math.floor(futureDate.getTime() / 1000),
@@ -65,7 +65,7 @@ module.exports = {
65
65
 
66
66
  state.noUidEventId = `_test-evt-journey-uid-resolve-${Date.now()}`;
67
67
 
68
- const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerKey}`, {
68
+ const response = await http.as('none').post(`payments/webhook?processor=test&key=${config.backendManagerWebhookKey}`, {
69
69
  id: state.noUidEventId,
70
70
  type: 'customer.subscription.updated',
71
71
  data: {
@@ -32,7 +32,7 @@ module.exports = {
32
32
  name: 'rejects-unknown-provider',
33
33
  auth: 'none',
34
34
  async run({ http, assert }) {
35
- const response = await http.as('none').post(`payments/dispute-alert?provider=unknown&key=${process.env.BACKEND_MANAGER_KEY}`, {
35
+ const response = await http.as('none').post(`payments/dispute-alert?provider=unknown&key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
36
36
  id: '_test-dispute-unknown-provider',
37
37
  card: '4242',
38
38
  amount: 9.99,
@@ -47,7 +47,7 @@ module.exports = {
47
47
  name: 'rejects-missing-id',
48
48
  auth: 'none',
49
49
  async run({ http, assert }) {
50
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
50
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
51
51
  card: '4242',
52
52
  amount: 9.99,
53
53
  transactionDate: '2026-01-15',
@@ -61,7 +61,7 @@ module.exports = {
61
61
  name: 'rejects-missing-card',
62
62
  auth: 'none',
63
63
  async run({ http, assert }) {
64
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
64
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
65
65
  id: '_test-dispute-no-card',
66
66
  amount: 9.99,
67
67
  transactionDate: '2026-01-15',
@@ -75,7 +75,7 @@ module.exports = {
75
75
  name: 'rejects-missing-amount',
76
76
  auth: 'none',
77
77
  async run({ http, assert }) {
78
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
78
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
79
79
  id: '_test-dispute-no-amount',
80
80
  card: '4242',
81
81
  transactionDate: '2026-01-15',
@@ -89,7 +89,7 @@ module.exports = {
89
89
  name: 'rejects-missing-transaction-date',
90
90
  auth: 'none',
91
91
  async run({ http, assert }) {
92
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
92
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
93
93
  id: '_test-dispute-no-date',
94
94
  card: '4242',
95
95
  amount: 9.99,
@@ -105,7 +105,7 @@ module.exports = {
105
105
  async run({ http, assert, firestore }) {
106
106
  const alertId = '_test-dispute-valid';
107
107
 
108
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
108
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
109
109
  id: alertId,
110
110
  card: '4242424242424242',
111
111
  cardBrand: 'Visa',
@@ -164,7 +164,7 @@ module.exports = {
164
164
  const alertId = '_test-dispute-alertid-field';
165
165
 
166
166
  // Chargeblast alert.created events use alertId instead of id
167
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
167
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
168
168
  alertId: alertId,
169
169
  card: '546616******5805',
170
170
  cardBrand: 'Mastercard',
@@ -189,7 +189,7 @@ module.exports = {
189
189
  const alertId = '_test-dispute-minimal';
190
190
 
191
191
  // Send minimal alert (alert.created shape — no externalOrder, metadata, etc.)
192
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
192
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
193
193
  id: alertId,
194
194
  card: '9124',
195
195
  amount: 10,
@@ -218,7 +218,7 @@ module.exports = {
218
218
  async run({ http, assert, firestore }) {
219
219
  const alertId = '_test-dispute-last4';
220
220
 
221
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
221
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
222
222
  id: alertId,
223
223
  card: '1234',
224
224
  amount: 9.99,
@@ -240,7 +240,7 @@ module.exports = {
240
240
  const alertId = '_test-dispute-duplicate';
241
241
 
242
242
  // Send first alert
243
- await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
243
+ await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
244
244
  id: alertId,
245
245
  card: '4242',
246
246
  amount: 29.99,
@@ -248,7 +248,7 @@ module.exports = {
248
248
  });
249
249
 
250
250
  // Send duplicate
251
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
251
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
252
252
  id: alertId,
253
253
  card: '4242',
254
254
  amount: 29.99,
@@ -274,7 +274,7 @@ module.exports = {
274
274
  });
275
275
 
276
276
  // Send alert with same ID — should retry since previous status was 'failed'
277
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
277
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
278
278
  id: alertId,
279
279
  card: '4242',
280
280
  amount: 29.99,
@@ -300,7 +300,7 @@ module.exports = {
300
300
  const alertId = '_test-dispute-default-provider';
301
301
 
302
302
  // Send without provider query param
303
- const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
303
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
304
304
  id: alertId,
305
305
  card: '4242',
306
306
  amount: 9.99,
@@ -34,7 +34,7 @@ module.exports = {
34
34
  name: 'rejects-unknown-processor',
35
35
  auth: 'none',
36
36
  async run({ http, assert }) {
37
- const response = await http.as('none').post(`payments/webhook?processor=unknown&key=${process.env.BACKEND_MANAGER_KEY}`, {
37
+ const response = await http.as('none').post(`payments/webhook?processor=unknown&key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
38
38
  id: 'evt_test_unknown',
39
39
  type: 'test.event',
40
40
  data: { object: {} },
@@ -50,7 +50,7 @@ module.exports = {
50
50
  async run({ http, assert, firestore }) {
51
51
  const eventId = '_test-evt-valid-webhook';
52
52
 
53
- const response = await http.as('none').post(`payments/webhook?processor=stripe&key=${process.env.BACKEND_MANAGER_KEY}`, {
53
+ const response = await http.as('none').post(`payments/webhook?processor=stripe&key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
54
54
  id: eventId,
55
55
  type: 'customer.subscription.updated',
56
56
  data: {
@@ -84,7 +84,7 @@ module.exports = {
84
84
 
85
85
  // Use the test processor so the on-write trigger doesn't require STRIPE_SECRET_KEY
86
86
  // (a failed first webhook would let the dedup-retry branch fire instead of returning duplicate=true)
87
- const send = () => http.as('none').post(`payments/webhook?processor=test&key=${process.env.BACKEND_MANAGER_KEY}`, {
87
+ const send = () => http.as('none').post(`payments/webhook?processor=test&key=${process.env.BACKEND_MANAGER_WEBHOOK_KEY}`, {
88
88
  id: eventId,
89
89
  type: 'customer.subscription.updated',
90
90
  data: {