backend-manager 5.0.73 → 5.0.74

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 (50) hide show
  1. package/CLAUDE.md +70 -0
  2. package/README.md +81 -7
  3. package/package.json +1 -1
  4. package/src/manager/cron/daily/reset-usage.js +5 -32
  5. package/src/manager/events/firestore/payments-webhooks/on-write.js +126 -0
  6. package/src/manager/functions/core/actions/api/admin/get-stats.js +3 -3
  7. package/src/manager/functions/core/actions/api/general/add-marketing-contact.js +1 -1
  8. package/src/manager/functions/core/actions/api/user/delete.js +5 -3
  9. package/src/manager/functions/core/actions/api/user/get-subscription-info.js +25 -9
  10. package/src/manager/functions/core/actions/api/user/validate-settings.js +1 -1
  11. package/src/manager/helpers/analytics.js +4 -4
  12. package/src/manager/helpers/api-manager.js +25 -42
  13. package/src/manager/helpers/middleware.js +1 -1
  14. package/src/manager/helpers/usage.js +24 -93
  15. package/src/manager/helpers/user.js +29 -38
  16. package/src/manager/index.js +22 -10
  17. package/src/manager/libraries/stripe.js +293 -0
  18. package/src/manager/routes/admin/stats/get.js +3 -3
  19. package/src/manager/routes/marketing/contact/post.js +1 -1
  20. package/src/manager/routes/payments/intent/post.js +94 -0
  21. package/src/manager/routes/payments/intent/providers/stripe.js +66 -0
  22. package/src/manager/routes/payments/webhook/post.js +87 -0
  23. package/src/manager/routes/payments/webhook/providers/stripe.js +35 -0
  24. package/src/manager/routes/test/schema/post.js +5 -5
  25. package/src/manager/routes/user/delete.js +5 -3
  26. package/src/manager/routes/user/settings/validate/post.js +3 -3
  27. package/src/manager/routes/user/subscription/get.js +25 -9
  28. package/src/manager/schemas/payments/intent/post.js +22 -0
  29. package/src/manager/schemas/payments/webhook/post.js +6 -0
  30. package/src/manager/schemas/test/schema/post.js +1 -1
  31. package/src/test/test-accounts.js +63 -25
  32. package/src/test/utils/firestore-rules-client.js +5 -5
  33. package/templates/backend-manager-config.json +32 -0
  34. package/templates/firestore.rules +1 -1
  35. package/test/_init/accounts-validation.js +3 -3
  36. package/test/functions/user/delete.js +1 -1
  37. package/test/functions/user/get-subscription-info.js +18 -24
  38. package/test/payments/intent.js +104 -0
  39. package/test/payments/journey-payment-cancel.js +166 -0
  40. package/test/payments/journey-payment-suspend.js +162 -0
  41. package/test/payments/journey-payment-trial.js +167 -0
  42. package/test/payments/journey-payment-upgrade.js +136 -0
  43. package/test/payments/webhook.js +128 -0
  44. package/test/routes/test/schema.js +1 -1
  45. package/test/routes/user/delete.js +1 -1
  46. package/test/routes/user/subscription.js +18 -24
  47. package/test/routes/user/user.js +14 -14
  48. package/test/rules/user.js +8 -8
  49. package/src/manager/helpers/subscription-resolver-new.js +0 -827
  50. package/src/manager/helpers/subscription-resolver.js +0 -841
package/CLAUDE.md CHANGED
@@ -490,6 +490,76 @@ assert.fail(message) // Explicit fail
490
490
  | `src/test/utils/http-client.js` | HTTP client |
491
491
  | `src/test/test-accounts.js` | Test account definitions |
492
492
 
493
+ ## Subscription System
494
+
495
+ ### Subscription Statuses
496
+
497
+ | Status | Meaning | User can delete account? |
498
+ |--------|---------|--------------------------|
499
+ | `active` | Subscription is current and valid (includes trialing) | No (unless `product.id === 'basic'`) |
500
+ | `suspended` | Payment failed (Stripe: `past_due`, `unpaid`) | No |
501
+ | `cancelled` | Subscription terminated (Stripe: `canceled`, `incomplete`, `incomplete_expired`) | Yes |
502
+
503
+ ### Stripe Status Mapping
504
+
505
+ | Stripe Status | `subscription.status` | Notes |
506
+ |---|---|---|
507
+ | `active` | `active` | Normal active subscription |
508
+ | `trialing` | `active` | `trial.activated = true` |
509
+ | `past_due` | `suspended` | Payment failed, retrying |
510
+ | `unpaid` | `suspended` | Payment failed |
511
+ | `canceled` | `cancelled` | Subscription terminated |
512
+ | `incomplete` | `cancelled` | Never completed initial payment |
513
+ | `incomplete_expired` | `cancelled` | Expired before completion |
514
+ | `active` + `cancel_at_period_end` | `active` | `cancellation.pending = true` |
515
+
516
+ ### Unified Subscription Object (`users/{uid}.subscription`)
517
+
518
+ ```javascript
519
+ subscription: {
520
+ product: {
521
+ id: 'basic', // product ID from config ('basic', 'premium', etc.)
522
+ name: 'Basic', // display name from config
523
+ },
524
+ status: 'active', // 'active' | 'suspended' | 'cancelled'
525
+ expires: { timestamp, timestampUNIX },
526
+ trial: {
527
+ activated: false, // has user EVER used a trial
528
+ expires: { timestamp, timestampUNIX },
529
+ },
530
+ cancellation: {
531
+ pending: false, // true = cancel at period end
532
+ date: { timestamp, timestampUNIX },
533
+ },
534
+ payment: {
535
+ processor: null, // 'stripe' | 'paypal' | etc.
536
+ resourceId: null, // provider subscription ID (e.g., 'sub_xxx')
537
+ frequency: null, // 'monthly' | 'annually'
538
+ startDate: { timestamp, timestampUNIX },
539
+ updatedBy: {
540
+ event: { name: null, id: null },
541
+ date: { timestamp, timestampUNIX },
542
+ },
543
+ },
544
+ }
545
+ ```
546
+
547
+ ### Access Check Patterns
548
+
549
+ ```javascript
550
+ // Is premium (paid)?
551
+ user.subscription.status === 'active' && user.subscription.product.id !== 'basic'
552
+
553
+ // Is on trial?
554
+ user.subscription.trial.activated && user.subscription.status === 'active'
555
+
556
+ // Has pending cancellation?
557
+ user.subscription.cancellation.pending === true
558
+
559
+ // Payment failed?
560
+ user.subscription.status === 'suspended'
561
+ ```
562
+
493
563
  ## Common Mistakes to Avoid
494
564
 
495
565
  1. **Don't modify Manager internals directly** - Use factory methods and public APIs
package/README.md CHANGED
@@ -460,13 +460,14 @@ const userProps = Manager.User(existingData, { defaults: true }).properties;
460
460
  // User structure:
461
461
  {
462
462
  auth: { uid, email, temporary },
463
- plan: {
464
- id: 'basic', // basic | advanced | premium
465
- status: 'active', // active | suspended | cancelled
463
+ subscription: {
464
+ product: { id, name }, // product from config ('basic', 'premium', etc.)
465
+ status: 'active', // active | suspended | cancelled
466
466
  expires: { timestamp, timestampUNIX },
467
467
  trial: { activated, expires: {...} },
468
+ cancellation: { pending, date: {...} },
468
469
  limits: {},
469
- payment: { processor, orderId, resourceId, frequency, active, startDate, updatedBy }
470
+ payment: { processor, resourceId, frequency, startDate, updatedBy }
470
471
  },
471
472
  roles: { admin, betaTester, developer },
472
473
  affiliate: { code, referrals, referrer },
@@ -478,7 +479,6 @@ const userProps = Manager.User(existingData, { defaults: true }).properties;
478
479
  }
479
480
 
480
481
  // Methods
481
- userProps.resolve(); // Check plan expiration
482
482
  userProps.merge(otherUser); // Merge with another user object
483
483
  ```
484
484
 
@@ -602,7 +602,7 @@ const results = await utilities.iterateCollection(
602
602
  collection: 'users',
603
603
  batchSize: 1000,
604
604
  maxBatches: 10,
605
- where: [{ field: 'plan.id', operator: '==', value: 'premium' }],
605
+ where: [{ field: 'subscription.product.id', operator: '==', value: 'premium' }],
606
606
  orderBy: { field: 'activity.created.timestamp', direction: 'desc' },
607
607
  startAfter: 'lastDocId',
608
608
  log: true,
@@ -710,7 +710,7 @@ const user = await assistant.authenticate();
710
710
  authenticated: true,
711
711
  auth: { uid: 'abc123', email: 'user@example.com' },
712
712
  roles: { admin: false, betaTester: false, developer: false },
713
- plan: { id: 'basic', status: 'active', ... },
713
+ subscription: { product: { id: 'basic', name: 'Basic' }, status: 'active', ... },
714
714
  api: { clientId: '...', privateKey: '...' },
715
715
  // ... other user properties
716
716
  }
@@ -857,6 +857,80 @@ module.exports = {
857
857
 
858
858
  See `CLAUDE.md` for complete test API documentation.
859
859
 
860
+ ## Subscription System
861
+
862
+ BEM includes a built-in payment/subscription system with Stripe integration (extensible to other providers).
863
+
864
+ ### Subscription Statuses
865
+
866
+ | Status | Meaning | User can delete account? |
867
+ |--------|---------|--------------------------|
868
+ | `active` | Subscription is current and valid (includes trialing) | No (unless `product.id === 'basic'`) |
869
+ | `suspended` | Payment failed (Stripe: `past_due`, `unpaid`) | No |
870
+ | `cancelled` | Subscription terminated (Stripe: `canceled`, `incomplete`, `incomplete_expired`) | Yes |
871
+
872
+ ### Stripe Status Mapping
873
+
874
+ | Stripe Status | `subscription.status` | Notes |
875
+ |---|---|---|
876
+ | `active` | `active` | Normal active subscription |
877
+ | `trialing` | `active` | `trial.activated = true` |
878
+ | `past_due` | `suspended` | Payment failed, retrying |
879
+ | `unpaid` | `suspended` | Payment failed |
880
+ | `canceled` | `cancelled` | Subscription terminated |
881
+ | `incomplete` | `cancelled` | Never completed initial payment |
882
+ | `incomplete_expired` | `cancelled` | Expired before completion |
883
+ | `active` + `cancel_at_period_end` | `active` | `cancellation.pending = true` |
884
+
885
+ ### Unified Subscription Object
886
+
887
+ The same subscription shape is stored in `users/{uid}.subscription` and `payments-subscriptions/{subId}.subscription`:
888
+
889
+ ```javascript
890
+ subscription: {
891
+ product: {
892
+ id: 'basic', // product ID from config ('basic', 'premium', etc.)
893
+ name: 'Basic', // display name from config
894
+ },
895
+ status: 'active', // 'active' | 'suspended' | 'cancelled'
896
+ expires: { timestamp, timestampUNIX },
897
+ trial: {
898
+ activated: false, // has user EVER used a trial
899
+ expires: { timestamp, timestampUNIX },
900
+ },
901
+ cancellation: {
902
+ pending: false, // true = cancel at period end
903
+ date: { timestamp, timestampUNIX },
904
+ },
905
+ payment: {
906
+ processor: null, // 'stripe' | 'paypal' | etc.
907
+ resourceId: null, // provider subscription ID (e.g., 'sub_xxx')
908
+ frequency: null, // 'monthly' | 'annually'
909
+ startDate: { timestamp, timestampUNIX },
910
+ updatedBy: {
911
+ event: { name: null, id: null },
912
+ date: { timestamp, timestampUNIX },
913
+ },
914
+ },
915
+ }
916
+ ```
917
+
918
+ ### Access Check Patterns
919
+
920
+ ```javascript
921
+ // Is premium (paid)?
922
+ user.subscription.status === 'active' && user.subscription.product.id !== 'basic'
923
+
924
+ // Is on trial?
925
+ user.subscription.trial.activated && user.subscription.status === 'active'
926
+
927
+ // Has pending cancellation?
928
+ user.subscription.cancellation.pending === true
929
+
930
+ // Payment failed?
931
+ user.subscription.status === 'suspended'
932
+ ```
933
+
860
934
  ## Final Words
861
935
 
862
936
  If you are still having difficulty, we would love for you to post a question to [the Backend Manager issues page](https://github.com/itw-creative-works/backend-manager/issues). It is much easier to answer questions that include your code and relevant files! So if you can provide them, we'd be extremely grateful (and more likely to help you find the answer!)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.73",
3
+ "version": "5.0.74",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -1,5 +1,3 @@
1
- const fetch = require('wonderful-fetch');
2
-
3
1
  /**
4
2
  * Reset usage cron job
5
3
  *
@@ -38,42 +36,17 @@ async function clearFirestore(Manager, assistant, libraries) {
38
36
  // Log status
39
37
  assistant.log('[firestore]: Starting...');
40
38
 
41
- // Clear storage
42
- const metrics = await fetch('https://us-central1-itw-creative-works.cloudfunctions.net/getApp', {
43
- method: 'post',
44
- response: 'json',
45
- body: {
46
- id: Manager.config.app.id,
47
- },
48
- })
49
- .then(response => {
50
- response.products = response.products || {};
51
-
52
- for (let product of Object.values(response.products)) {
53
- product = product || {};
54
- product.planId = product.planId || '';
55
-
56
- if (product.planId.includes('basic')) {
57
- return product.limits;
58
- }
59
- }
60
-
61
- return new Error('No basic product found');
62
- })
63
- .catch(e => e);
39
+ // Get metric names from the basic product in config
40
+ const products = Manager.config.products || [];
41
+ const basicProduct = products.find(p => p.id === 'basic') || {};
42
+ const metrics = { ...(basicProduct.limits || {}) };
64
43
 
65
44
  // Ensure requests is always included as a default metric
66
- if (!(metrics instanceof Error)) {
67
- metrics.requests = metrics.requests || 1;
68
- }
45
+ metrics.requests = metrics.requests || 1;
69
46
 
70
47
  // Log status
71
48
  assistant.log('[firestore]: Resetting metrics', metrics);
72
49
 
73
- if (metrics instanceof Error) {
74
- throw assistant.errorify(`Failed to check providers: ${metrics}`, { code: 500 });
75
- }
76
-
77
50
  // Reset all metrics with for loop of metrics
78
51
  // TODO: OPTIMIZATION: Put all of the changes into a single batch
79
52
  for (const metric of Object.keys(metrics)) {
@@ -0,0 +1,126 @@
1
+ const powertools = require('node-powertools');
2
+
3
+ /**
4
+ * Firestore trigger: payments-webhooks/{eventId} onWrite
5
+ *
6
+ * Processes pending webhook events:
7
+ * 1. Transforms raw provider data into unified subscription object
8
+ * 2. Updates the user's subscription in users/{uid}
9
+ * 3. Stores the subscription doc in payments-subscriptions/{resourceId}
10
+ * 4. Marks the webhook as completed
11
+ */
12
+ module.exports = async ({ Manager, assistant, change, context, libraries }) => {
13
+ const { admin } = libraries;
14
+
15
+ const dataAfter = change.after.data();
16
+
17
+ // Short-circuit: deleted doc or non-pending status
18
+ if (!dataAfter || dataAfter.status !== 'pending') {
19
+ return;
20
+ }
21
+
22
+ const eventId = context.params.eventId;
23
+ const webhookRef = admin.firestore().doc(`payments-webhooks/${eventId}`);
24
+
25
+ // Set status to processing
26
+ await webhookRef.set({ status: 'processing' }, { merge: true });
27
+
28
+ try {
29
+ const processor = dataAfter.processor;
30
+ const uid = dataAfter.uid;
31
+ const raw = dataAfter.raw;
32
+ const eventType = dataAfter.event?.type;
33
+
34
+ // Validate UID
35
+ if (!uid) {
36
+ throw new Error('Webhook event has no UID — cannot process');
37
+ }
38
+
39
+ // Load and initialize the shared library for this processor
40
+ let library;
41
+ try {
42
+ library = require(`../../../libraries/${processor}.js`);
43
+ } catch (e) {
44
+ throw new Error(`Unknown processor library: ${processor}`);
45
+ }
46
+
47
+ library.init();
48
+
49
+ // Extract the subscription object from the raw event
50
+ // Stripe sends events with event.data.object as the subscription
51
+ const rawSubscription = raw.data?.object || {};
52
+
53
+ // Transform raw data into unified subscription object
54
+ const unified = library.toUnified(rawSubscription, {
55
+ config: Manager.config,
56
+ eventName: eventType,
57
+ eventId: eventId,
58
+ });
59
+
60
+ assistant.log(`Processing webhook ${eventId}:`, {
61
+ processor,
62
+ uid,
63
+ eventType,
64
+ productId: unified.product.id,
65
+ status: unified.status,
66
+ });
67
+
68
+ // Build timestamps
69
+ const now = powertools.timestamp(new Date(), { output: 'string' });
70
+ const nowUNIX = powertools.timestamp(now, { output: 'unix' });
71
+
72
+ // Write unified subscription to user doc
73
+ await admin.firestore().doc(`users/${uid}`).set({
74
+ subscription: unified,
75
+ }, { merge: true });
76
+
77
+ // Write to payments-subscriptions/{resourceId}
78
+ const resourceId = unified.payment.resourceId;
79
+ if (resourceId) {
80
+ await admin.firestore().doc(`payments-subscriptions/${resourceId}`).set({
81
+ uid: uid,
82
+ processor: processor,
83
+ subscription: unified,
84
+ raw: rawSubscription,
85
+ metadata: {
86
+ created: {
87
+ timestamp: now,
88
+ timestampUNIX: nowUNIX,
89
+ },
90
+ updated: {
91
+ timestamp: now,
92
+ timestampUNIX: nowUNIX,
93
+ },
94
+ updatedBy: {
95
+ event: {
96
+ name: eventType,
97
+ id: eventId,
98
+ },
99
+ },
100
+ },
101
+ }, { merge: true });
102
+ }
103
+
104
+ // Mark webhook as completed
105
+ await webhookRef.set({
106
+ status: 'completed',
107
+ uid: uid,
108
+ metadata: {
109
+ processed: {
110
+ timestamp: now,
111
+ timestampUNIX: nowUNIX,
112
+ },
113
+ },
114
+ }, { merge: true });
115
+
116
+ assistant.log(`Webhook ${eventId} processed successfully`);
117
+ } catch (e) {
118
+ assistant.error(`Webhook ${eventId} processing failed:`, e);
119
+
120
+ // Mark as failed with error message
121
+ await webhookRef.set({
122
+ status: 'failed',
123
+ error: e.message || String(e),
124
+ }, { merge: true });
125
+ }
126
+ };
@@ -265,7 +265,7 @@ Module.prototype.getAllSubscriptions = function () {
265
265
 
266
266
  // Get subscriptions
267
267
  await self.libraries.admin.firestore().collection('users')
268
- .where('plan.expires.timestampUNIX', '>=', new Date().getTime() / 1000)
268
+ .where('subscription.expires.timestampUNIX', '>=', new Date().getTime() / 1000)
269
269
  .get()
270
270
  .then((snapshot) => {
271
271
  const stats = {
@@ -280,8 +280,8 @@ Module.prototype.getAllSubscriptions = function () {
280
280
  snapshot
281
281
  .forEach((doc, i) => {
282
282
  const data = doc.data();
283
- const planId = data?.plan?.id || 'basic';
284
- const frequency = data?.plan?.payment?.frequency || 'unknown';
283
+ const planId = data?.subscription?.product?.id || 'basic';
284
+ const frequency = data?.subscription?.payment?.frequency || 'unknown';
285
285
  const isAdmin = data?.roles?.admin || false;
286
286
  const isVip = data?.roles?.vip || false;
287
287
 
@@ -62,7 +62,7 @@ Module.prototype.main = function () {
62
62
 
63
63
  // Check rate limit via Usage API
64
64
  try {
65
- await usage.validate('marketing-subscribe', { throw: true, useCaptchaResponse: false });
65
+ await usage.validate('marketing-subscribe', { useCaptchaResponse: false });
66
66
  usage.increment('marketing-subscribe');
67
67
  await usage.update();
68
68
  } catch (e) {
@@ -17,10 +17,12 @@ Module.prototype.main = function () {
17
17
  .then(async (user) => {
18
18
  const uid = user?.auth?.uid;
19
19
 
20
- // Disallow deleting users with subscriptions in any state other than cancelled or active payments
20
+ // Disallow deleting users with active or suspended paid subscriptions
21
+ const subStatus = user?.subscription?.status;
22
+ const subId = user?.subscription?.product?.id;
21
23
  if (
22
- (user?.plan?.status && user?.plan?.status !== 'cancelled')
23
- || user?.plan?.payment?.active
24
+ (subStatus === 'active' || subStatus === 'suspended')
25
+ && subId !== 'basic'
24
26
  ) {
25
27
  return reject(assistant.errorify(`This account cannot be deleted because it has a paid subscription attached to it. In order to delete the account, you must first cancel the paid subscription.`, {code: 400}));
26
28
  }
@@ -18,21 +18,37 @@ Module.prototype.main = function () {
18
18
  Api.resolveUser({adminRequired: false})
19
19
  .then(async (user) => {
20
20
  const result = {
21
- plan: {
22
- id: user?.plan?.id || 'unknown',
21
+ subscription: {
22
+ product: {
23
+ id: user?.subscription?.product?.id || 'basic',
24
+ name: user?.subscription?.product?.name || 'Basic',
25
+ },
26
+ status: user?.subscription?.status || 'active',
23
27
  expires: {
24
- timestamp: user?.plan?.expires?.timestamp || oldDate,
25
- timestampUNIX: user?.plan?.expires?.timestampUNIX || oldDateUNIX,
28
+ timestamp: user?.subscription?.expires?.timestamp || oldDate,
29
+ timestampUNIX: user?.subscription?.expires?.timestampUNIX || oldDateUNIX,
26
30
  },
27
31
  trial: {
28
- activated: user?.plan?.trial?.activated ?? false,
32
+ activated: user?.subscription?.trial?.activated ?? false,
33
+ expires: {
34
+ timestamp: user?.subscription?.trial?.expires?.timestamp || oldDate,
35
+ timestampUNIX: user?.subscription?.trial?.expires?.timestampUNIX || oldDateUNIX,
36
+ },
37
+ },
38
+ cancellation: {
39
+ pending: user?.subscription?.cancellation?.pending ?? false,
29
40
  date: {
30
- timestamp: user?.plan?.trial?.date?.timestamp || oldDate,
31
- timestampUNIX: user?.plan?.trial?.date?.timestampUNIX || oldDateUNIX,
32
- }
41
+ timestamp: user?.subscription?.cancellation?.date?.timestamp || oldDate,
42
+ timestampUNIX: user?.subscription?.cancellation?.date?.timestampUNIX || oldDateUNIX,
43
+ },
33
44
  },
34
45
  payment: {
35
- active: user?.plan?.payment?.active ?? false,
46
+ processor: user?.subscription?.payment?.processor || null,
47
+ frequency: user?.subscription?.payment?.frequency || null,
48
+ startDate: {
49
+ timestamp: user?.subscription?.payment?.startDate?.timestamp || oldDate,
50
+ timestampUNIX: user?.subscription?.payment?.startDate?.timestampUNIX || oldDateUNIX,
51
+ },
36
52
  },
37
53
  }
38
54
  }
@@ -36,7 +36,7 @@ Module.prototype.main = function () {
36
36
  // Load the file
37
37
  try {
38
38
  const defaults = _.get(require(resolvedPath)(), payload.data.payload.defaultsPath);
39
- const combined = combine(defaults.all, defaults[user.plan.id] || {})
39
+ const combined = combine(defaults.all, defaults[user.subscription.product.id] || {})
40
40
 
41
41
  assistant.log('Combined settings', combined)
42
42
 
@@ -110,11 +110,11 @@ function Analytics(Manager, options) {
110
110
  authenticated: {
111
111
  value: authUser?.auth?.uid ? true : false,
112
112
  },
113
- plan_id: {
114
- value: authUser?.plan?.id || 'basic',
113
+ subscription_id: {
114
+ value: authUser?.subscription?.product?.id || 'basic',
115
115
  },
116
- plan_trial_activated: {
117
- value: authUser?.plan?.trial?.activated || false,
116
+ subscription_trial_activated: {
117
+ value: authUser?.subscription?.trial?.activated || false,
118
118
  },
119
119
  activity_created: {
120
120
  value: moment(authUser?.activity?.created?.timestampUNIX
@@ -1,13 +1,14 @@
1
1
  const moment = require('moment');
2
- const fetch = require('node-fetch');
3
2
  const uuidv5 = require('uuid').v5;
4
3
  const { get, set, merge } = require('lodash');
5
4
 
6
5
  let sampleUser = {
7
6
  api: {},
8
7
  auth: {},
9
- plan: {
10
- id: '',
8
+ subscription: {
9
+ product: {
10
+ id: '',
11
+ },
11
12
  limits: {
12
13
 
13
14
  }
@@ -52,40 +53,20 @@ ApiManager.prototype.init = function (options) {
52
53
  options.officialAPIKeys = options.officialAPIKeys || [];
53
54
  options.whitelistedAPIKeys = options.whitelistedAPIKeys || [];
54
55
 
55
- await fetch('https://us-central1-itw-creative-works.cloudfunctions.net/getApp', {
56
- method: 'POST',
57
- headers: { 'Content-Type': 'application/json' },
58
- body: JSON.stringify({
59
- id: options.app,
60
- }),
61
- })
62
- .then(res => {
63
- res.text()
64
- .then(text => {
65
- if (res.ok) {
66
- const data = JSON.parse(text);
67
-
68
- options.plans = {};
69
-
70
- Object.keys(data.products)
71
- .forEach((id, i) => {
72
- const product = data.products[id]
73
- options.plans[product.planId] = {}
74
- options.plans[product.planId].limits = product.limits || {};
75
- });
76
-
77
- self.options = options;
78
- self.initialized = true;
79
-
80
- return resolve(self);
81
- } else {
82
- throw new Error(text || res.statusText || 'Unknown error.')
83
- }
84
- })
85
- })
86
- .catch(e => {
87
- return reject(e)
88
- })
56
+ // Read limits from config products
57
+ const products = self.Manager.config.products || [];
58
+ options.plans = {};
59
+
60
+ for (const product of products) {
61
+ options.plans[product.id] = {
62
+ limits: product.limits || {},
63
+ };
64
+ }
65
+
66
+ self.options = options;
67
+ self.initialized = true;
68
+
69
+ return resolve(self);
89
70
  });
90
71
  };
91
72
 
@@ -107,8 +88,10 @@ ApiManager.prototype._createNewUser = function (authenticatedUser, planId, persi
107
88
  let newUser = {
108
89
  api: authenticatedUser?.api || {},
109
90
  auth: authenticatedUser?.auth || {},
110
- plan: {
111
- id: planId,
91
+ subscription: {
92
+ product: {
93
+ id: planId,
94
+ },
112
95
  limits: {
113
96
  }
114
97
  },
@@ -124,7 +107,7 @@ ApiManager.prototype._createNewUser = function (authenticatedUser, planId, persi
124
107
  .forEach((id, i) => {
125
108
  // console.log('----id', id);
126
109
  // console.log('======currentPlan[id]', currentPlan[id]);
127
- newUser.plan.limits[id] = get(authenticatedUser, `plan.limits.${id}`, currentPlan[id])
110
+ newUser.subscription.limits[id] = get(authenticatedUser, `subscription.limits.${id}`, currentPlan[id])
128
111
  // const product = data.products[id]
129
112
  // options.plans[product.planId] = {}
130
113
  // options.plans[product.planId].limits = product.limits || {};
@@ -185,7 +168,7 @@ ApiManager.prototype.getUser = async function (assistant) {
185
168
  // console.log('---doesnt exist so reauthing');
186
169
  authenticatedUser = await assistant.authenticate({apiKey: apiKey});
187
170
  // console.log('---authenticatedUser', authenticatedUser);
188
- const planId = authenticatedUser?.plan?.id || 'basic';
171
+ const planId = authenticatedUser?.subscription?.product?.id || 'basic';
189
172
  let workingUID = !authenticatedUser.authenticated
190
173
  ? uuidv5(assistant.request.geolocation.ip, '1b671a64-40d5-491e-99b0-da01ff1f3341')
191
174
  : authenticatedUser.auth.uid
@@ -229,7 +212,7 @@ function _getUserStat(self, user, stat, def) {
229
212
  // console.log('----isWhitelistedAPIKey', isWhitelistedAPIKey);
230
213
  return {
231
214
  current: !isWhitelistedAPIKey ? get(user, `_APIManager.stats.${stat}`, typeof def !== 'undefined' ? def : 0) : 0,
232
- limit: !isWhitelistedAPIKey ? get(user, `plan.limits.${stat}`, typeof def !== 'undefined' ? def : 0) : Infinity,
215
+ limit: !isWhitelistedAPIKey ? get(user, `subscription.limits.${stat}`, typeof def !== 'undefined' ? def : 0) : Infinity,
233
216
  }
234
217
  }
235
218
 
@@ -126,7 +126,7 @@ Middleware.prototype.run = function (libPath, options) {
126
126
 
127
127
  // Log working user
128
128
  const workingUser = assistant.getUser();
129
- assistant.log(`Middleware.process(): User (${workingUser.auth.uid}, ${workingUser.auth.email}, ${workingUser.plan.id}=${workingUser.plan.status}):`, safeStringify(workingUser));
129
+ assistant.log(`Middleware.process(): User (${workingUser.auth.uid}, ${workingUser.auth.email}, ${workingUser.subscription.product.id}=${workingUser.subscription.status}):`, safeStringify(workingUser));
130
130
 
131
131
  // Setup analytics
132
132
  if (options.setupAnalytics) {