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.
- package/CHANGELOG.md +34 -0
- package/TODO-CANCEL-EMAIL-MISSING-ORDER-ID.md +159 -0
- package/TODO-WEBHOOK-KEY-LEGACY-REMOVAL.md +15 -0
- package/TODO-WEBHOOK-KEY-UPGRADE.md +138 -0
- package/docs/stripe-webhook-forwarding.md +2 -2
- package/package.json +1 -1
- package/scripts/test-helper-providers.js +162 -0
- package/src/cli/commands/base-command.js +5 -5
- package/src/cli/commands/emulator.js +201 -54
- package/src/cli/commands/test.js +80 -9
- package/src/manager/events/cron/daily/ghostii-auto-publisher.js +2 -2
- package/src/manager/events/firestore/payments-webhooks/analytics.js +2 -2
- package/src/manager/functions/core/actions/api/user/delete.js +1 -1
- package/src/manager/helpers/analytics.js +1 -1
- package/src/manager/libraries/email/generators/newsletter.js +2 -2
- package/src/manager/libraries/email/providers/beehiiv.js +11 -6
- package/src/manager/libraries/email/providers/sendgrid.js +6 -6
- package/src/manager/libraries/email/validation.js +1 -1
- package/src/manager/libraries/infer-contact.js +1 -1
- package/src/manager/routes/general/email/post.js +4 -2
- package/src/manager/routes/marketing/email-preferences/post.js +2 -2
- package/src/manager/routes/payments/dispute-alert/post.js +3 -3
- package/src/manager/routes/payments/intent/processors/test.js +2 -2
- package/src/manager/routes/payments/webhook/post.js +2 -2
- package/src/manager/routes/user/delete.js +1 -1
- package/src/manager/routes/user/oauth2/providers/discord.js +1 -1
- package/src/manager/routes/user/oauth2/providers/google.js +1 -1
- package/src/test/runner.js +7 -0
- package/src/test/utils/http-client.js +1 -0
- package/test/events/payments/journey-payments-cancel.js +4 -4
- package/test/events/payments/journey-payments-failure.js +2 -2
- package/test/events/payments/journey-payments-legacy-product.js +1 -1
- package/test/events/payments/journey-payments-one-time-failure.js +1 -1
- package/test/events/payments/journey-payments-plan-change.js +1 -1
- package/test/events/payments/journey-payments-refund-webhook.js +4 -4
- package/test/events/payments/journey-payments-suspend.js +4 -4
- package/test/events/payments/journey-payments-trial.js +2 -2
- package/test/events/payments/journey-payments-uid-resolution.js +1 -1
- package/test/routes/payments/dispute-alert.js +13 -13
- package/test/routes/payments/webhook.js +3 -3
- /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', `${
|
|
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:
|
|
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:
|
|
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
|
|
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.
|
|
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:
|
|
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
|
|
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:
|
|
51
|
+
timeout: 60000,
|
|
52
52
|
response: 'json',
|
|
53
53
|
tries: 2,
|
|
54
54
|
log: true,
|
|
@@ -27,7 +27,7 @@ module.exports = {
|
|
|
27
27
|
|
|
28
28
|
const response = await fetch(this.urls.revoke, {
|
|
29
29
|
method: 'POST',
|
|
30
|
-
timeout:
|
|
30
|
+
timeout: 60000,
|
|
31
31
|
body: new URLSearchParams({ token }),
|
|
32
32
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
33
33
|
}).catch(e => e);
|
package/src/test/runner.js
CHANGED
|
@@ -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
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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: {
|
|
File without changes
|