backend-manager 5.0.114 → 5.0.116
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/package.json +1 -1
- package/src/cli/commands/logs.js +4 -4
- package/src/cli/commands/setup-tests/bem-config.js +28 -0
- package/src/manager/cron/daily/data-requests.js +4 -4
- package/src/manager/events/firestore/payments-webhooks/on-write.js +3 -2
- package/src/manager/libraries/payment/processors/paypal.js +2 -1
- package/src/manager/libraries/payment/processors/stripe.js +1 -1
- package/src/manager/routes/payments/intent/post.js +1 -1
- package/src/manager/routes/payments/webhook/post.js +2 -2
- package/src/manager/routes/user/data-request/get.js +5 -5
- package/test/helpers/payment/paypal/to-unified-one-time.js +3 -3
- package/test/helpers/payment/stripe/to-unified-one-time.js +3 -3
package/package.json
CHANGED
package/src/cli/commands/logs.js
CHANGED
|
@@ -56,11 +56,11 @@ class LogsCommand extends BaseCommand {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Fetch historical logs.
|
|
59
|
-
* Usage: npx bm logs:read [--fn bm_api] [--severity ERROR] [--since 1h] [--limit
|
|
59
|
+
* Usage: npx bm logs:read [--fn bm_api] [--severity ERROR] [--since 1h] [--limit 300]
|
|
60
60
|
*/
|
|
61
61
|
async read(projectId, argv) {
|
|
62
62
|
const filter = this.buildFilter(argv);
|
|
63
|
-
const limit = parseInt(argv.limit, 10) ||
|
|
63
|
+
const limit = parseInt(argv.limit, 10) || 300;
|
|
64
64
|
|
|
65
65
|
const cmd = [
|
|
66
66
|
'gcloud', 'logging', 'read',
|
|
@@ -88,8 +88,8 @@ class LogsCommand extends BaseCommand {
|
|
|
88
88
|
|
|
89
89
|
const entries = JSON.parse(output || '[]');
|
|
90
90
|
|
|
91
|
-
// Save
|
|
92
|
-
jetpack.write(logPath, JSON.stringify(
|
|
91
|
+
// Save as newline-delimited JSON (matches tail format)
|
|
92
|
+
jetpack.write(logPath, entries.map(e => JSON.stringify(e)).join('\n'));
|
|
93
93
|
|
|
94
94
|
if (entries.length === 0) {
|
|
95
95
|
this.logWarning('No log entries found.');
|
|
@@ -20,6 +20,11 @@ class BemConfigTest extends BaseTest {
|
|
|
20
20
|
|
|
21
21
|
// Loop through all the keys in the template
|
|
22
22
|
powertools.getKeys(bemConfigTemplate).forEach((key) => {
|
|
23
|
+
// Skip if an ancestor is explicitly set to a non-object value (e.g. stripe: false)
|
|
24
|
+
if (this._isAncestorDisabled(key)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
const userValue = _.get(this.self.bemConfigJSON, key, undefined);
|
|
24
29
|
|
|
25
30
|
// If the user value is undefined, then we need to set pass to false
|
|
@@ -43,6 +48,11 @@ class BemConfigTest extends BaseTest {
|
|
|
43
48
|
|
|
44
49
|
// Log what keys are missing
|
|
45
50
|
powertools.getKeys(bemConfigTemplate).forEach((key) => {
|
|
51
|
+
// Skip if an ancestor is explicitly set to a non-object value (e.g. stripe: false)
|
|
52
|
+
if (this._isAncestorDisabled(key)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
const userValue = _.get(this.self.bemConfigJSON, key, undefined);
|
|
47
57
|
|
|
48
58
|
if (typeof userValue === 'undefined') {
|
|
@@ -54,6 +64,24 @@ class BemConfigTest extends BaseTest {
|
|
|
54
64
|
|
|
55
65
|
throw new Error('Missing required backend-manager-config.json keys');
|
|
56
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Check if any ancestor of a dot-notation key is a non-object value (e.g. false)
|
|
69
|
+
* This means the key is intentionally disabled, not missing
|
|
70
|
+
*/
|
|
71
|
+
_isAncestorDisabled(key) {
|
|
72
|
+
const parts = key.split('.');
|
|
73
|
+
let current = this.self.bemConfigJSON;
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
76
|
+
current = current?.[parts[i]];
|
|
77
|
+
|
|
78
|
+
if (current !== undefined && current !== null && typeof current !== 'object') {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
57
85
|
}
|
|
58
86
|
|
|
59
87
|
module.exports = BemConfigTest;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Data requests cron job
|
|
3
3
|
*
|
|
4
4
|
* Processes data request status transitions:
|
|
5
|
-
* - pending →
|
|
6
|
-
* -
|
|
5
|
+
* - pending → completed: 14 days after creation
|
|
6
|
+
* - completed → expired: 30 days after becoming completed (44 days after creation)
|
|
7
7
|
*
|
|
8
8
|
* Scans the entire collection (no index required) since data-requests is small.
|
|
9
9
|
*/
|
|
@@ -33,7 +33,7 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
|
|
|
33
33
|
const age = nowUNIX - createdUNIX;
|
|
34
34
|
|
|
35
35
|
if (data.status === 'pending' && age >= FOURTEEN_DAYS) {
|
|
36
|
-
await doc.ref.update({ status: '
|
|
36
|
+
await doc.ref.update({ status: 'completed' })
|
|
37
37
|
.then(() => {
|
|
38
38
|
completed++;
|
|
39
39
|
assistant.log(`Completed request ${doc.id} (age: ${Math.round(age / 86400)}d)`);
|
|
@@ -41,7 +41,7 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
|
|
|
41
41
|
.catch((e) => {
|
|
42
42
|
assistant.error(`Failed to complete request ${doc.id}: ${e.message}`);
|
|
43
43
|
});
|
|
44
|
-
} else if (data.status === '
|
|
44
|
+
} else if (data.status === 'completed' && age >= FORTY_FOUR_DAYS) {
|
|
45
45
|
await doc.ref.update({ status: 'expired' })
|
|
46
46
|
.then(() => {
|
|
47
47
|
expired++;
|
|
@@ -86,7 +86,7 @@ module.exports = async ({ assistant, change, context }) => {
|
|
|
86
86
|
// Build timestamps
|
|
87
87
|
const now = powertools.timestamp(new Date(), { output: 'string' });
|
|
88
88
|
const nowUNIX = powertools.timestamp(now, { output: 'unix' });
|
|
89
|
-
const webhookReceivedUNIX = dataAfter.metadata?.
|
|
89
|
+
const webhookReceivedUNIX = dataAfter.metadata?.created?.timestampUNIX || nowUNIX;
|
|
90
90
|
|
|
91
91
|
// Extract orderId from resource (processor-agnostic)
|
|
92
92
|
orderId = library.getOrderId(resource);
|
|
@@ -105,7 +105,7 @@ module.exports = async ({ assistant, change, context }) => {
|
|
|
105
105
|
orderId: orderId,
|
|
106
106
|
transition: transitionName,
|
|
107
107
|
metadata: {
|
|
108
|
-
|
|
108
|
+
completed: {
|
|
109
109
|
timestamp: now,
|
|
110
110
|
timestampUNIX: nowUNIX,
|
|
111
111
|
},
|
|
@@ -199,6 +199,7 @@ async function processPaymentEvent({ category, library, resource, resourceType,
|
|
|
199
199
|
id: orderId,
|
|
200
200
|
type: category,
|
|
201
201
|
owner: uid,
|
|
202
|
+
productId: unified.product.id,
|
|
202
203
|
processor: processor,
|
|
203
204
|
resourceId: resourceId,
|
|
204
205
|
unified: unified,
|
|
@@ -268,7 +268,7 @@ const PayPal = {
|
|
|
268
268
|
|
|
269
269
|
return {
|
|
270
270
|
product: product,
|
|
271
|
-
status: rawResource.status === 'COMPLETED' ? '
|
|
271
|
+
status: rawResource.status === 'COMPLETED' ? 'completed' : rawResource.status?.toLowerCase() || 'unknown',
|
|
272
272
|
payment: {
|
|
273
273
|
processor: 'paypal',
|
|
274
274
|
orderId: customData.orderId || null,
|
|
@@ -314,6 +314,7 @@ const PayPal = {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
// Fetch plans with full details (Prefer header includes billing_cycles in list response)
|
|
317
|
+
// TODO: Paginate — page_size=20 only returns first page. Fine for now (each product has ~2-4 plans in 1:1 model) but will break if a product ever accumulates >20 plans.
|
|
317
318
|
const response = await this.request(`/v1/billing/plans?product_id=${paypalProductId}&page_size=20&total_required=true`, {
|
|
318
319
|
headers: { 'Prefer': 'return=representation' },
|
|
319
320
|
});
|
|
@@ -304,7 +304,7 @@ const Stripe = {
|
|
|
304
304
|
|
|
305
305
|
return {
|
|
306
306
|
product: product,
|
|
307
|
-
status: rawResource.status || 'unknown',
|
|
307
|
+
status: rawResource.status === 'complete' ? 'completed' : rawResource.status || 'unknown',
|
|
308
308
|
payment: {
|
|
309
309
|
processor: 'stripe',
|
|
310
310
|
orderId: rawResource.metadata?.orderId || null,
|
|
@@ -116,7 +116,7 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
|
|
|
116
116
|
owner: uid,
|
|
117
117
|
status: 'pending',
|
|
118
118
|
productId: productId,
|
|
119
|
-
|
|
119
|
+
type: productType,
|
|
120
120
|
frequency: frequency,
|
|
121
121
|
trial: trial,
|
|
122
122
|
raw: result.raw,
|
|
@@ -91,11 +91,11 @@ module.exports = async ({ assistant, Manager, libraries }) => {
|
|
|
91
91
|
},
|
|
92
92
|
error: null,
|
|
93
93
|
metadata: {
|
|
94
|
-
|
|
94
|
+
created: {
|
|
95
95
|
timestamp: now,
|
|
96
96
|
timestampUNIX: nowUNIX,
|
|
97
97
|
},
|
|
98
|
-
|
|
98
|
+
completed: {
|
|
99
99
|
timestamp: null,
|
|
100
100
|
timestampUNIX: null,
|
|
101
101
|
},
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* GET /user/data-request - Check data request status or download data
|
|
3
3
|
*
|
|
4
4
|
* action=status (default): Returns the most recent request with its stored status.
|
|
5
|
-
* action=download: Compiles user data live and returns it. Only works when status is '
|
|
5
|
+
* action=download: Compiles user data live and returns it. Only works when status is 'completed'.
|
|
6
6
|
*
|
|
7
7
|
* Statuses:
|
|
8
|
-
* pending
|
|
9
|
-
*
|
|
8
|
+
* pending — request submitted, waiting to be processed (bm_cronDaily sets to 'completed' after 14 days)
|
|
9
|
+
* completed — data is available for download (downloads counter tracks how many times downloaded)
|
|
10
10
|
*/
|
|
11
11
|
module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
|
|
12
12
|
const { admin } = libraries;
|
|
@@ -43,8 +43,8 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Download action — only allowed when status is '
|
|
47
|
-
if (status !== '
|
|
46
|
+
// Download action — only allowed when status is 'completed'
|
|
47
|
+
if (status !== 'completed') {
|
|
48
48
|
return assistant.respond('Your data request is still being processed. Please check back later.', { code: 400 });
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -45,7 +45,7 @@ module.exports = {
|
|
|
45
45
|
name: 'status-completed',
|
|
46
46
|
async run({ assert }) {
|
|
47
47
|
const result = toUnifiedOneTime({ status: 'COMPLETED' });
|
|
48
|
-
assert.equal(result.status, '
|
|
48
|
+
assert.equal(result.status, 'completed', 'PayPal COMPLETED → unified completed');
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
51
|
|
|
@@ -250,7 +250,7 @@ module.exports = {
|
|
|
250
250
|
assert.ok(result.payment, 'Should have payment');
|
|
251
251
|
|
|
252
252
|
assert.equal(result.product.id, 'credits-100', 'Product should be credits-100');
|
|
253
|
-
assert.equal(result.status, '
|
|
253
|
+
assert.equal(result.status, 'completed', 'Status should be completed');
|
|
254
254
|
assert.equal(result.payment.processor, 'paypal', 'Processor should be paypal');
|
|
255
255
|
assert.equal(result.payment.resourceId, 'PAYID-FULL', 'Resource ID should match');
|
|
256
256
|
assert.equal(result.payment.orderId, '1234-5678-9012', 'orderId should match');
|
|
@@ -336,7 +336,7 @@ module.exports = {
|
|
|
336
336
|
async run({ assert }) {
|
|
337
337
|
const result = toUnifiedOneTime(FIXTURE_ORDER_COMPLETED);
|
|
338
338
|
|
|
339
|
-
assert.equal(result.status, '
|
|
339
|
+
assert.equal(result.status, 'completed', 'COMPLETED fixture → completed');
|
|
340
340
|
assert.equal(result.payment.processor, 'paypal', 'Processor is paypal');
|
|
341
341
|
assert.equal(result.payment.resourceId, '5UX02069M9686893E', 'Resource ID from fixture');
|
|
342
342
|
assert.equal(result.payment.orderId, 'ord-test-456', 'orderId from purchase_units custom_id');
|
|
@@ -44,7 +44,7 @@ module.exports = {
|
|
|
44
44
|
name: 'status-complete',
|
|
45
45
|
async run({ assert }) {
|
|
46
46
|
const result = toUnifiedOneTime({ status: 'complete' });
|
|
47
|
-
assert.equal(result.status, '
|
|
47
|
+
assert.equal(result.status, 'completed', 'Stripe complete → unified completed');
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
|
|
@@ -211,7 +211,7 @@ module.exports = {
|
|
|
211
211
|
assert.ok(result.payment, 'Should have payment');
|
|
212
212
|
|
|
213
213
|
assert.equal(result.product.id, 'credits-100', 'Product should be credits-100');
|
|
214
|
-
assert.equal(result.status, '
|
|
214
|
+
assert.equal(result.status, 'completed', 'Status should be completed');
|
|
215
215
|
assert.equal(result.payment.processor, 'stripe', 'Processor should be stripe');
|
|
216
216
|
assert.equal(result.payment.resourceId, 'cs_test_full', 'Resource ID should match');
|
|
217
217
|
assert.equal(result.payment.orderId, '1234-5678-9012', 'orderId should match');
|
|
@@ -253,7 +253,7 @@ module.exports = {
|
|
|
253
253
|
const result = toUnifiedOneTime(FIXTURE_SESSION);
|
|
254
254
|
|
|
255
255
|
assert.ok(result.product, 'Should have product');
|
|
256
|
-
assert.equal(result.status, '
|
|
256
|
+
assert.equal(result.status, 'completed', 'Real session fixture → completed');
|
|
257
257
|
assert.equal(result.payment.processor, 'stripe', 'Processor is stripe');
|
|
258
258
|
assert.equal(result.payment.resourceId, FIXTURE_SESSION.id, 'resourceId matches fixture ID');
|
|
259
259
|
},
|