backend-manager 5.0.167 → 5.0.169

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 CHANGED
@@ -14,6 +14,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ # [5.0.168] - 2026-03-21
18
+ ### Fixed
19
+ - Immediately suspend subscription on payment denial (PAYMENT.SALE.DENIED, invoice.payment_failed) instead of waiting for the processor to give up retrying — recovery via PAYMENT.SALE.COMPLETED restores active status
20
+
17
21
  # [5.0.167] - 2026-03-20
18
22
  ### Changed
19
23
  - Extracted `resolveTemperature()` helper for consistency with `resolveFormatting()` and `resolveReasoning()`
package/TODO-2.md CHANGED
@@ -10,6 +10,24 @@ payments/reactivate
10
10
  payments/upgrade
11
11
  * takes a subscription id and a new plan id and upgrades the user's subscription to the new plan. this can only be done if the user has an active subscription.
12
12
 
13
+
14
+ -------
15
+ USER OBJECT UPDGRADE --> INSTANCE?
16
+
17
+ const User = require('backend-manager/src/manager/helpers/user');
18
+ +
19
+ User.resolveSubscription(self.request.user);
20
+ -->
21
+ one object that cna do resolveSubscription(), getAccount(), etc
22
+
23
+ since you changed some things, do all tests in all projects align still?
24
+
25
+ after that, udpate README, CLAUDE.md and ANY TOPLEVLE CLAUDE SKILLS so that we never make this mistake again
26
+
27
+ including
28
+
29
+ ---------
30
+
13
31
  TEST NEWSLETTER
14
32
  POST /admin/cron { id: 'daily/marketing-newsletter-generate' }
15
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.167",
3
+ "version": "5.0.169",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -1,5 +1,6 @@
1
1
  const powertools = require('node-powertools');
2
2
  const { REMINDER_DELAYS, COLLECTION } = require('../../libraries/abandoned-cart-config.js');
3
+ const User = require('../../helpers/user.js');
3
4
 
4
5
  /**
5
6
  * Abandoned cart reminder cron job
@@ -49,8 +50,7 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
49
50
  const userDoc = userSnap.data();
50
51
 
51
52
  // Belt-and-suspenders: skip if user already has active paid subscription
52
- if (userDoc.subscription?.status === 'active'
53
- && userDoc.subscription?.product?.id !== 'basic') {
53
+ if (User.resolveSubscription(userDoc).active) {
54
54
  assistant.log(`User ${uid} now has active subscription, marking cart completed`);
55
55
  await markCompleted(doc, admin, nowUNIX);
56
56
  skipped++;
@@ -218,6 +218,16 @@ async function processPaymentEvent({ category, library, resource, resourceType,
218
218
  ? library.toUnifiedSubscription(resource, transformOptions)
219
219
  : library.toUnifiedOneTime(resource, transformOptions);
220
220
 
221
+ // Override: immediately suspend on payment denial
222
+ // Processors keep the sub active while retrying, but we revoke access right away.
223
+ // If the retry succeeds (e.g. PAYMENT.SALE.COMPLETED), it will restore active status.
224
+ // PayPal: PAYMENT.SALE.DENIED, Stripe: invoice.payment_failed, Chargebee: payment_failed
225
+ const PAYMENT_DENIED_EVENTS = ['PAYMENT.SALE.DENIED', 'invoice.payment_failed', 'payment_failed'];
226
+ if (isSubscription && PAYMENT_DENIED_EVENTS.includes(eventType) && unified.status === 'active') {
227
+ assistant.log(`Overriding status to suspended: ${eventType} received but provider still says active`);
228
+ unified.status = 'suspended';
229
+ }
230
+
221
231
  assistant.log(`Unified ${category}: product=${unified.product.id}, status=${unified.status}`, unified);
222
232
 
223
233
  // Read checkout context from payments-intents (attribution, discount, supplemental)
@@ -7,6 +7,7 @@ const path = require('path');
7
7
  const powertools = require('node-powertools');
8
8
  const { merge } = require('lodash');
9
9
  const JSON5 = require('json5');
10
+ const User = require('./user.js');
10
11
 
11
12
  function Middleware(m, req, res) {
12
13
  const self = this;
@@ -126,7 +127,8 @@ Middleware.prototype.run = function (libPath, options) {
126
127
 
127
128
  // Log working user
128
129
  const workingUser = assistant.getUser();
129
- assistant.log(`Middleware.process(): User (${workingUser.auth.uid}, ${workingUser.auth.email}, ${workingUser.subscription.product.id}=${workingUser.subscription.status}):`, safeStringify(workingUser));
130
+ const resolvedSub = User.resolveSubscription(workingUser);
131
+ assistant.log(`Middleware.process(): User (${workingUser.auth.uid}, ${workingUser.auth.email}, ${workingUser.subscription.product.id}=${workingUser.subscription.status} (resolved: ${resolvedSub.plan})):`, safeStringify(workingUser));
130
132
 
131
133
  // Setup analytics
132
134
  if (options.setupAnalytics) {
@@ -8,6 +8,7 @@
8
8
  const moment = require('moment');
9
9
  const _ = require('lodash');
10
10
  const hcaptcha = require('hcaptcha');
11
+ const User = require('./user.js');
11
12
 
12
13
  function Usage(m) {
13
14
  const self = this;
@@ -319,8 +320,8 @@ Usage.prototype.getProduct = function (id) {
319
320
 
320
321
  const products = Manager.config.payment?.products || [];
321
322
 
322
- // Look up by provided ID, or fall back to user's subscription product
323
- id = id || self.user.subscription.product.id;
323
+ // Look up by provided ID, or fall back to user's resolved plan
324
+ id = id || User.resolveSubscription(self.user).plan;
324
325
 
325
326
  return products.find(p => p.id === id)
326
327
  || products.find(p => p.id === 'basic')