backend-manager 2.5.89 → 2.5.91

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "2.5.89",
3
+ "version": "2.5.91",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  "bm": "./bin/bem"
9
9
  },
10
10
  "scripts": {
11
- "test": "echo \"Error: no test specified\" && exit 1",
11
+ "_test": "npm run prepare && ./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
12
+ "test": "./node_modules/mocha/bin/mocha test/ --recursive --timeout=10000",
12
13
  "start": "node src/index.js"
13
14
  },
14
15
  "repository": {
@@ -46,6 +47,7 @@
46
47
  "lodash": "^4.17.21",
47
48
  "lowdb": "^1.0.0",
48
49
  "mailchimp-api-v3": "^1.15.0",
50
+ "mocha": "^8.4.0",
49
51
  "moment": "^2.29.4",
50
52
  "nanoid": "^3.3.6",
51
53
  "node-fetch": "^2.6.9",
@@ -21,19 +21,27 @@ Module.prototype.main = function () {
21
21
 
22
22
  let count = 0;
23
23
 
24
- await self.signOutOfSession(uid, session).then(r => count += r);
25
- // Legacy for somiibo and old electron-manager
26
- await self.signOutOfSession(uid, 'gatherings/online').then(r => count += r);
27
-
28
- await self.libraries.admin
29
- .auth()
30
- .revokeRefreshTokens(uid)
31
- .then(() => {
32
- return resolve({data: {sessions: count, message: `Successfully signed ${uid} out of all sessions`}});
33
- })
34
- .catch(e => {
35
- return reject(assistant.errorManager(`Failed to sign out of all sessions: ${e}`, {code: 500, sentry: false, send: false, log: false}).error)
36
- })
24
+ try {
25
+ await self.signOutOfSession(uid, session)
26
+ .then(r => count += r)
27
+
28
+ // Legacy for somiibo and old electron-manager
29
+ await self.signOutOfSession(uid, 'gatherings/online')
30
+ .then(r => count += r)
31
+
32
+ await self.libraries.admin
33
+ .auth()
34
+ .revokeRefreshTokens(uid)
35
+ .then(() => {
36
+ return resolve({data: {sessions: count, message: `Successfully signed ${uid} out of all sessions`}});
37
+ })
38
+ .catch(e => {
39
+ return reject(assistant.errorManager(`Failed to sign out of all sessions: ${e}`, {code: 500, sentry: false, send: false, log: false}).error)
40
+ })
41
+ } catch (e) {
42
+ assistant.console.log(`@temp sign-out-all-sessions error: ${e}`);
43
+ return reject(assistant.errorManager(`Failed to sign out of all sessions: ${e}`, {code: 500, sentry: false, send: false, log: false}).error)
44
+ }
37
45
  })
38
46
  .catch(e => {
39
47
  return reject(e);
@@ -94,11 +94,11 @@ Module.prototype.main = function() {
94
94
  })
95
95
  .catch(e => {
96
96
  // console.log('---e', e);
97
- self.payload.response.status = e.code || 500;
97
+ self.payload.response.status = e && e.code ? e.code : 500;
98
98
  self.payload.response.error = e || new Error('Unknown error occured');
99
99
  })
100
100
  } catch (e) {
101
- self.payload.response.status = 500;
101
+ self.payload.response.status = e && e.code ? e.code : 500;
102
102
  self.payload.response.error = e || new Error('Unknown error occured');
103
103
  }
104
104
  })
@@ -123,7 +123,7 @@ Module.prototype.main = function() {
123
123
  return resolve();
124
124
  }
125
125
  } else {
126
- console.error(`Error executing ${resolved.command} @ ${resolved.path} (status=${self.payload.response.status}):`, self.payload.response.error)
126
+ self.assistant.error(`Error executing ${resolved.command} @ ${resolved.path} (status=${self.payload.response.status}):`, self.payload.response.error)
127
127
  // return res.send(self.payload.response.error.message);
128
128
  res.send(`${self.payload.response.error}`)
129
129
  return reject(self.payload.response.error);
@@ -5,8 +5,8 @@ function SubscriptionResolver(Manager, profile, resource) {
5
5
  const self = this;
6
6
 
7
7
  self.Manager = Manager;
8
- self.profile = profile;
9
- self.resource = resource;
8
+ self.profile = profile || {};
9
+ self.resource = resource || {};
10
10
 
11
11
  return self;
12
12
  }
@@ -15,8 +15,8 @@ SubscriptionResolver.prototype.resolve = function (options) {
15
15
  const self = this;
16
16
 
17
17
  const resolved = {
18
- status: 'cancelled',
19
- frequency: 'monthly',
18
+ status: '',
19
+ frequency: '',
20
20
  resource: {
21
21
  id: '',
22
22
  },
@@ -53,372 +53,123 @@ SubscriptionResolver.prototype.resolve = function (options) {
53
53
  const resource = self.resource;
54
54
 
55
55
  // Set defaults
56
+ profile.type = profile.type || null;
56
57
  profile.details = profile.details || {};
57
58
  profile.details.planFrequency = profile.details.planFrequency || null;
58
59
 
59
60
  // Set
60
61
  options = options || {};
62
+ options.log = typeof options.log === 'undefined' ? false : options.log;
63
+ options.resolveProcessor = typeof options.resolveProcessor === 'undefined' ? false : options.resolveProcessor;
64
+ options.resolveType = typeof options.resolveType === 'undefined' ? false : options.resolveType;
65
+ options.today = typeof options.today === 'undefined' ? moment() : moment(options.today);
61
66
 
62
67
  // Set provider if not set
63
68
  if (!profile.processor) {
69
+ /*** PayPal ***/
70
+ // Order
64
71
  if (
72
+ resource.purchase_units
73
+ ) {
74
+ profile.processor = 'paypal';
75
+ profile.type = profile.type || 'order';
76
+ // Order
77
+ } else if (
65
78
  // resource.billing_info
66
79
  resource.create_time
67
80
  ) {
68
81
  profile.processor = 'paypal';
82
+ profile.type = profile.type || 'subscription';
83
+
84
+ /*** Chargebee ***/
85
+ // Order
86
+ } else if (
87
+ resource.line_items
88
+ ) {
89
+ profile.processor = 'chargebee';
90
+ profile.type = profile.type || 'order';
91
+ // Subscription
69
92
  } else if (
70
93
  resource.billing_period_unit
71
94
  ) {
72
95
  profile.processor = 'chargebee';
96
+ profile.type = profile.type || 'subscription';
97
+
98
+ /*** Stripe ***/
99
+ // Order
73
100
  } else if (
74
- resource.customer
101
+ resource.object === 'charge'
75
102
  ) {
76
103
  profile.processor = 'stripe';
104
+ profile.type = profile.type || 'order';
105
+ // Subscription
106
+ } else if (
107
+ resource.object === 'subscription'
108
+ ) {
109
+ profile.processor = 'stripe';
110
+ profile.type = profile.type || 'subscription';
111
+
112
+ /*** Coinbase ***/
113
+ // Order AND Subscription
77
114
  } else if (
78
115
  resource.addresses
79
116
  ) {
80
117
  profile.processor = 'coinbase';
118
+ // profile.type = profile.type || 'subscription';
119
+
120
+ /*** Error ***/
81
121
  } else {
82
122
  throw new Error('Unable to determine subscription provider');
83
123
  }
84
124
  }
85
125
 
126
+ // Set profile.type
127
+ if (!profile.type) {
128
+ profile.type = profile.type || 'subscription';
129
+ }
130
+
131
+ // Set processor if needed
132
+ if (options.resolveProcessor) {
133
+ resolved.processor = profile.processor;
134
+ }
135
+
136
+ // Set type if needed
137
+ if (options.resolveType) {
138
+ resolved.type = profile.type;
139
+ }
140
+
141
+ // Set frequency if order
142
+ if (profile.type === 'order') {
143
+ resolved.frequency = 'single';
144
+ }
145
+
86
146
  // Log if requested
87
147
  if (options.log) {
88
148
  console.log('profile', profile);
89
149
  console.log('resource', resource);
90
150
  }
91
151
 
92
- // Process differently based on each provider
93
- if (profile.processor === 'paypal') {
94
- // Set status
95
- /*
96
- subscription: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_get
97
- APPROVAL_PENDING. The subscription is created but not yet approved by the buyer.
98
- APPROVED. The buyer has approved the subscription.
99
- ACTIVE. The subscription is active.
100
- SUSPENDED. The subscription is suspended.
101
- CANCELLED. The subscription is cancelled.
102
- EXPIRED. The subscription is expired.
103
-
104
- order: https://developer.paypal.com/docs/api/orders/v2/#orders_get
105
- CREATED. The order was created with the specified context.
106
- SAVED. The order was saved and persisted. The order status continues to be in progress until a capture is made with final_capture = true for all purchase units within the order.
107
- APPROVED. The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on.
108
- VOIDED. All purchase units in the order are voided. COMPLETED. The payment was authorized or the authorized payment was captured for the order.
109
- PAYER_ACTION_REQUIRED. The order requires an action from the payer (e.g. 3DS authentication). Redirect the payer to the "rel":"payer-action" HATEOAS link returned as part of the response prior to authorizing or capturing the order.
110
- */
111
- if (['ACTIVE'].includes(resource.status)) {
112
- resolved.status = 'active';
113
- } else if (['SUSPENDED'].includes(resource.status)) {
114
- resolved.status = 'suspended';
115
- } else {
116
- resolved.status = 'cancelled';
117
- }
118
-
119
- // Set resource ID
120
- resolved.resource.id = resource.id;
121
-
122
- // Set start
123
- resolved.start.timestamp = moment(
124
- get(resource, 'start_time', 0)
125
- )
126
-
127
- // Set expiration
128
- resolved.expires.timestamp = moment(
129
- get(resource, 'billing_info.last_payment.time', 0)
130
- )
131
-
132
- // Set cancelled
133
- if (resolved.status === 'cancelled') {
134
- resolved.cancelled.timestamp = moment(
135
- get(resource, 'status_update_time', 0)
136
- )
137
- }
138
-
139
- // Set last payment
140
- if (get(resource, 'billing_info.last_payment')) {
141
- resolved.lastPayment.amount = parseFloat(resource.billing_info.last_payment.amount.value);
142
- resolved.lastPayment.date.timestamp = moment(resource.billing_info.last_payment.time);
143
- }
144
-
145
- // Get trial
146
- const trialTenure = get(resource, 'plan.billing_cycles', []).find((cycle) => cycle.tenure_type === 'TRIAL');
147
- const regularTenure = get(resource, 'plan.billing_cycles', []).find((cycle) => cycle.tenure_type === 'REGULAR');
148
-
149
- // Resolve trial
150
- /*
151
- Special condition for PayPal
152
- Because you cannot remove trial on a sub-level, you have to charge a prorated amount for the "trial".
153
- Even if charged, it is still considered a trial period by paypal.
154
- Thus, we must remove the trial indicator if the user has been charged.
155
- */
156
- if (
157
- resolved.status === 'active'
158
- && (trialTenure && regularTenure && regularTenure.total_cycles === 0)
159
- && resolved.lastPayment.amount === 0
160
- ) {
161
- resolved.trial.active = true;
162
-
163
- // Set expiration
164
- resolved.expires.timestamp = moment(
165
- get(resource, 'billing_info.next_billing_time', 0)
166
- )
167
- }
168
-
169
- // Resolve frequency
170
- const unit = regularTenure.frequency.interval_unit;
171
- if (unit === 'YEAR') {
172
- resolved.frequency = 'annually';
173
- } else if (unit === 'MONTH') {
174
- resolved.frequency = 'monthly';
175
- } else if (unit === 'WEEK') {
176
- resolved.frequency = 'weekly';
177
- } else if (unit === 'DAY') {
178
- resolved.frequency = 'daily';
179
- } else {
180
- throw new Error('Unknown frequency');
181
- }
182
-
183
- // Set completed
184
- if (resource.plan_id) {
185
- resolved.payment.completed = !['APPROVAL_PENDING', 'APPROVED'].includes(resource.status);
186
- } else {
187
- resolved.payment.completed = !['CREATED', 'SAVED', 'APPROVED', 'VOIDED', 'PAYER_ACTION_REQUIRED'].includes(resource.status);
188
- }
189
-
190
- } else if (profile.processor === 'chargebee') {
191
- // Set status
192
- // subscription: https://apidocs.chargebee.com/docs/api/subscriptions?prod_cat_ver=2#subscription_status
193
- // future The subscription is scheduled to start at a future date.
194
- // in_trial The subscription is in trial.
195
- // active The subscription is active and will be charged for automatically based on the items in it.
196
- // non_renewing The subscription will be canceled at the end of the current term.
197
- // paused The subscription is paused. The subscription will not renew while in this state.
198
- // cancelled The subscription has been canceled and is no longer in service.
199
- if (['in_trial', 'active'].includes(resource.status)) {
200
- resolved.status = 'active';
201
-
202
- // If there's a due invoice, it's suspended
203
- if (resource.total_dues > 0) {
204
- resolved.status = 'suspended';
205
- }
206
- } else if (['paused'].includes(resource.status)) {
207
- resolved.status = 'suspended';
208
- } else {
209
- resolved.status = 'cancelled';
210
- }
211
-
212
- // Set resource ID
213
- resolved.resource.id = resource.id;
214
-
215
- // Set start
216
- resolved.start.timestamp = moment(
217
- get(resource, 'created_at', 0) * 1000
218
- )
219
-
220
- // Set expiration
221
- resolved.expires.timestamp = moment(
222
- (
223
- get(resource, 'current_term_start', 0)
224
- ) * 1000
225
- )
226
- // if (resource.total_dues > 0) {
227
- // resolved.expires.timestamp = moment(0);
228
- // } else {
229
- // resolved.expires.timestamp = moment(
230
- // (
231
- // get(resource, 'current_term_start', 0)
232
- // ) * 1000
233
- // )
234
- // }
235
-
236
- // Set cancelled
237
- if (resolved.status === 'cancelled') {
238
- resolved.cancelled.timestamp = moment(
239
- get(resource, 'cancelled_at', 0) * 1000
240
- )
241
- }
242
-
243
- // Set last payment
244
- if (resource.total_dues > 0) {
245
- resolved.lastPayment.amount = 0;
246
- resolved.lastPayment.date.timestamp = moment(
247
- (resource.due_since || 0) * 1000
248
- );
249
- } else {
250
- resolved.lastPayment.amount = resource.plan_amount / 100;
251
- resolved.lastPayment.date.timestamp = moment(
252
- (resource.current_term_start || 0) * 1000
253
- );
254
- }
255
-
256
- // Get trial
257
- if (resource.status === 'in_trial') {
258
- resolved.trial.active = true;
259
-
260
- // Set expiration
261
- resolved.expires.timestamp = moment(
262
- (
263
- get(resource, 'trial_end', 0)
264
- ) * 1000
265
- )
266
- }
267
-
268
- // Resolve frequency
269
- const unit = resource.billing_period_unit;
270
- if (unit === 'year') {
271
- resolved.frequency = 'annually';
272
- } else if (unit === 'month') {
273
- resolved.frequency = 'monthly';
274
- } else if (unit === 'week') {
275
- resolved.frequency = 'weekly';
276
- } else if (unit === 'day') {
277
- resolved.frequency = 'daily';
278
- } else {
279
- throw new Error('Unknown frequency');
280
- }
281
-
282
- // Set completed
283
- if (true) {
284
- resolved.payment.completed = !['future'].includes(resource.status);
285
- }
286
-
287
- } else if (profile.processor === 'stripe') {
288
- // Subscription: https://stripe.com/docs/api/subscriptions/object#subscription_object-status
289
- // incomplete, incomplete_expired, trialing, active, past_due, canceled, or unpaid
290
-
291
- // Charge: https://stripe.com/docs/api/payment_intents/object#payment_intent_object-status
292
- // requires_payment_method, requires_confirmation, requires_action, processing, requires_capture, canceled, or succeeded
293
- // Set status
294
- if (['trialing', 'active'].includes(resource.status)) {
295
- resolved.status = 'active';
296
- } else if (['past_due', 'unpaid'].includes(resource.status)) {
297
- resolved.status = 'suspended';
298
- } else {
299
- resolved.status = 'cancelled';
300
- }
301
-
302
- // Set resource ID
303
- resolved.resource.id = resource.id;
304
-
305
- // Set start
306
- resolved.start.timestamp = moment(
307
- get(resource, 'start_date', 0) * 1000
308
- );
309
-
310
- // Set expiration
311
- resolved.expires.timestamp = moment(
312
- get(resource, 'current_period_start', 0) * 1000
313
- );
314
-
315
- // Set cancelled
316
- if (resolved.status === 'cancelled') {
317
- resolved.cancelled.timestamp = moment(
318
- get(resource, 'canceled_at', 0) * 1000
319
- )
320
- }
321
-
322
- // Set last payment
323
- // TODO: check if suspended payments are handled correctly when using resource.latest_invoice.amount_paid
324
- if (resource.latest_invoice) {
325
- resolved.lastPayment.amount = resource.latest_invoice.amount_paid / 100;
326
- resolved.lastPayment.date.timestamp = moment(
327
- get(resource, 'latest_invoice.created', 0) * 1000
328
- );
329
- }
330
-
331
- // Get trial
332
- if (resource.status === 'trialing') {
333
- resolved.trial.active = true;
334
-
335
- // Set expiration
336
- resolved.expires.timestamp = moment(
337
- (
338
- get(resource, 'trial_end', 0)
339
- ) * 1000
340
- )
341
- }
342
-
343
- // Resolve frequency
344
- const unit = resource.plan.interval;
345
- if (unit === 'year') {
346
- resolved.frequency = 'annually';
347
- } else if (unit === 'month') {
348
- resolved.frequency = 'monthly';
349
- } else if (unit === 'week') {
350
- resolved.frequency = 'weekly';
351
- } else if (unit === 'day') {
352
- resolved.frequency = 'daily';
353
- } else {
354
- throw new Error('Unknown frequency');
355
- }
356
-
357
- // Set completed
358
- if (resource.object === 'subscription') {
359
- resolved.payment.completed = !['incomplete', 'incomplete_expired'].includes(resource.status);
360
- } else if (resource.object === 'payment_intent') {
361
- resolved.payment.completed = !['requires_payment_method', 'requires_confirmation', 'requires_action', 'processing', 'requires_capture', 'canceled'].includes(resource.status);
362
- }
363
-
364
- } else if (profile.processor === 'coinbase') {
365
- // Set status
366
- resolved.status = 'cancelled';
367
-
368
- // Set resource ID
369
- resolved.resource.id = resource.id;
370
-
371
- // Set start
372
- resolved.start.timestamp = moment(
373
- get(resource, 'created_at', 0)
374
- );
375
-
376
- // Set expiration
377
- resolved.expires.timestamp = moment(
378
- get(resource, 'created_at', 0)
379
- );
380
-
381
- // Set cancelled
382
- resolved.cancelled.timestamp = moment(
383
- get(resource, 'created_at', 0)
384
- )
385
-
386
- // Retrieve last payment
387
- const lastPayment = resource.payments.find(p => p.status === 'CONFIRMED');
388
-
389
- // Set last payment
390
- if (lastPayment) {
391
- resolved.lastPayment.amount = parseFloat(lastPayment.value.local.amount);
392
- resolved.lastPayment.date.timestamp = moment(lastPayment.detected_at);
393
- }
394
-
395
- // Get trial
396
- if (true) {
397
- resolved.trial.active = false;
398
- }
399
-
400
- // Resolve frequency
401
- const unit = profile.details.planFrequency;
402
- if (unit) {
403
- resolved.frequency = unit;
404
- } else {
405
- throw new Error('Unknown frequency');
406
- }
407
-
408
- // Set completed
409
- if (true) {
410
- resolved.payment.completed = !!lastPayment;
411
- }
412
-
152
+ // Resolve
153
+ const processor = self[`resolve_${profile.processor}`];
154
+ if (processor) {
155
+ processor(profile, resource, resolved);
413
156
  } else {
414
157
  throw new Error('Unknown processor');
415
158
  }
416
159
 
160
+ // console.log('---resolved', resolved);
161
+
162
+ // Check for frequency
163
+ if (!resolved.frequency) {
164
+ throw new Error('Unknown frequency');
165
+ }
166
+
417
167
  // Fix expiry by adding time to the date of last payment
168
+ // console.log('----expires 2', resolved.resource.id, resolved.status, resolved.frequency, resolved.trial.active, resolved.expires.timestamp.toISOString ? resolved.expires.timestamp.toISOString() : resolved.expires.timestamp);
418
169
  if (resolved.status === 'active') {
419
170
  // Set days left
420
171
  if (resolved.trial.active) {
421
- resolved.trial.daysLeft = resolved.expires.timestamp.diff(moment(), 'days');
172
+ resolved.trial.daysLeft = resolved.expires.timestamp.diff(options.today, 'days');
422
173
  }
423
174
 
424
175
  // Set expiration
@@ -437,6 +188,7 @@ SubscriptionResolver.prototype.resolve = function (options) {
437
188
  }
438
189
  }
439
190
  }
191
+ // console.log('----expires 3', resolved.resource.id, resolved.status, resolved.frequency, resolved.trial.active, resolved.expires.timestamp.toISOString ? resolved.expires.timestamp.toISOString() : resolved.expires.timestamp);
440
192
 
441
193
  // If they are not trialing AND there was NEVER any payment sent OR the last payment failed, then set the expiration to 0
442
194
  if (
@@ -445,13 +197,14 @@ SubscriptionResolver.prototype.resolve = function (options) {
445
197
  ) {
446
198
  resolved.expires.timestamp = moment(0);
447
199
  }
200
+ // console.log('----expires 4', resolved.resource.id, resolved.status, resolved.frequency, resolved.trial.active, resolved.expires.timestamp.toISOString ? resolved.expires.timestamp.toISOString() : resolved.expires.timestamp);
448
201
 
449
202
  // Fix timestamps
450
203
  resolved.start.timestampUNIX = resolved.start.timestamp.unix();
451
204
  resolved.start.timestamp = resolved.start.timestamp.toISOString();
452
205
 
453
206
  resolved.expires.timestampUNIX = resolved.expires.timestamp.unix();
454
- resolved.expires.timestamp = resolved.expires.timestamp.toISOString();
207
+ resolved.expires.timestamp = resolved.expires.timestamp.toISOString ? resolved.expires.timestamp.toISOString() : resolved.expires.timestamp;
455
208
 
456
209
  resolved.cancelled.timestampUNIX = resolved.cancelled.timestamp.unix();
457
210
  resolved.cancelled.timestamp = resolved.cancelled.timestamp.toISOString();
@@ -469,8 +222,476 @@ SubscriptionResolver.prototype.resolve = function (options) {
469
222
  }
470
223
 
471
224
  self.resolved = resolved;
225
+ // console.log('----expires 5', resolved.resource.id, resolved.status, resolved.frequency, resolved.trial.active, resolved.expires.timestamp.toISOString ? resolved.expires.timestamp.toISOString() : resolved.expires.timestamp);
472
226
 
473
227
  return resolved;
474
228
  };
475
229
 
230
+ SubscriptionResolver.prototype.resolve_paypal = function (profile, resource, resolved) {
231
+ const self = this;
232
+
233
+ // Set status
234
+ /*
235
+ subscription: https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_get
236
+ APPROVAL_PENDING. The subscription is created but not yet approved by the buyer.
237
+ APPROVED. The buyer has approved the subscription.
238
+ ACTIVE. The subscription is active.
239
+ SUSPENDED. The subscription is suspended.
240
+ CANCELLED. The subscription is cancelled.
241
+ EXPIRED. The subscription is expired.
242
+
243
+ order: https://developer.paypal.com/docs/api/orders/v2/#orders_get
244
+ CREATED The order was created with the specified context.
245
+ SAVED The order was saved and persisted. The order status continues to be in progress until a capture is made with final_capture = true for all purchase units within the order.
246
+ APPROVED The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on.
247
+ VOIDED All purchase units in the order are voided.
248
+ COMPLETED The payment was authorized or the authorized payment was captured for the order.
249
+ PAYER_ACTION_REQUIRED The order requires an action from the payer (e.g. 3DS authentication). Redirect the payer to the "rel":"payer-action" HATEOAS link returned as part of the response prior to authorizing or capturing the order.
250
+ */
251
+ if (['ACTIVE'].includes(resource.status)) {
252
+ resolved.status = 'active';
253
+ } else if (['SUSPENDED'].includes(resource.status)) {
254
+ resolved.status = 'suspended';
255
+ } else {
256
+ resolved.status = 'cancelled';
257
+ }
258
+
259
+ // Set resource ID
260
+ resolved.resource.id = resource.id;
261
+
262
+ // Set start
263
+ resolved.start.timestamp = moment(
264
+ (
265
+ // Subscription
266
+ get(resource, 'start_time', 0)
267
+
268
+ // Order
269
+ || get(resource, 'create_time', 0)
270
+ )
271
+ )
272
+
273
+ // Set expiration
274
+ resolved.expires.timestamp = moment(
275
+ (
276
+ // Subscription
277
+ get(resource, 'billing_info.last_payment.time', 0)
278
+
279
+ // Order
280
+ || get(resource, 'create_time', 0)
281
+ )
282
+ )
283
+
284
+ // Set cancelled
285
+ if (resolved.status === 'cancelled') {
286
+ resolved.cancelled.timestamp = moment(
287
+ (
288
+ // Subscription
289
+ get(resource, 'status_update_time', 0)
290
+
291
+ // Order
292
+ || get(resource, 'create_time', 0)
293
+ )
294
+ )
295
+ }
296
+
297
+ // Set last payment
298
+ const order = get(resource, 'purchase_units[0].payments.captures[0]');
299
+ const subscription = get(resource, 'billing_info.last_payment');
300
+ if (order) {
301
+ resolved.lastPayment.amount = parseFloat(
302
+ get(order, 'amount.value', '0.00')
303
+ );
304
+ resolved.lastPayment.date.timestamp = moment(
305
+ order.create_time || 0
306
+ );
307
+ } else if (subscription) {
308
+ resolved.lastPayment.amount = parseFloat(subscription.amount.value);
309
+ resolved.lastPayment.date.timestamp = moment(subscription.time);
310
+ }
311
+
312
+ // Get trial
313
+ const trialTenure = get(resource, 'plan.billing_cycles', []).find((cycle) => cycle.tenure_type === 'TRIAL');
314
+ const regularTenure = get(resource, 'plan.billing_cycles', []).find((cycle) => cycle.tenure_type === 'REGULAR');
315
+
316
+ // Resolve trial
317
+ /*
318
+ Special condition for PayPal
319
+ Because you cannot remove trial on a sub-level, you have to charge a prorated amount for the "trial".
320
+ Even if charged, it is still considered a trial period by paypal.
321
+ Thus, we must remove the trial indicator if the user has been charged.
322
+ */
323
+ if (
324
+ resolved.status === 'active'
325
+ && (trialTenure && regularTenure && regularTenure.total_cycles === 0)
326
+ && resolved.lastPayment.amount === 0
327
+ ) {
328
+ resolved.trial.active = true;
329
+
330
+ // Set expiration
331
+ resolved.expires.timestamp = moment(
332
+ get(resource, 'billing_info.next_billing_time', 0)
333
+ )
334
+ }
335
+
336
+ // Resolve frequency
337
+ const unit = get(regularTenure, 'frequency.interval_unit');
338
+ if (unit === 'YEAR') {
339
+ resolved.frequency = 'annually';
340
+ } else if (unit === 'MONTH') {
341
+ resolved.frequency = 'monthly';
342
+ } else if (unit === 'WEEK') {
343
+ resolved.frequency = 'weekly';
344
+ } else if (unit === 'DAY') {
345
+ resolved.frequency = 'daily';
346
+ }
347
+
348
+ // Set completed
349
+ if (!resource.billing_info) {
350
+ resolved.payment.completed = !['CREATED', 'SAVED', 'APPROVED', 'VOIDED', 'PAYER_ACTION_REQUIRED'].includes(resource.status);
351
+ } else {
352
+ resolved.payment.completed = !['APPROVAL_PENDING', 'APPROVED'].includes(resource.status);
353
+ }
354
+
355
+ return resolved;
356
+ }
357
+
358
+ SubscriptionResolver.prototype.resolve_chargebee = function (profile, resource, resolved) {
359
+ const self = this;
360
+
361
+ // Set status
362
+ // subscription: https://apidocs.chargebee.com/docs/api/subscriptions?prod_cat_ver=2#subscription_status
363
+ // future The subscription is scheduled to start at a future date.
364
+ // in_trial The subscription is in trial.
365
+ // active The subscription is active and will be charged for automatically based on the items in it.
366
+ // non_renewing The subscription will be canceled at the end of the current term.
367
+ // paused The subscription is paused. The subscription will not renew while in this state.
368
+ // cancelled The subscription has been canceled and is no longer in service.
369
+
370
+ // order: https://apidocs.chargebee.com/docs/api/invoices?prod_cat_ver=2#invoice_status
371
+ // paid: Indicates a paid invoice.
372
+ // posted: Indicates the payment is not yet collected and will be in this state till the due date to indicate the due period.
373
+ // payment_due: Indicates the payment is not yet collected and is being retried as per retry settings.
374
+ // not_paid: Indicates the payment is not made and all attempts to collect is failed.
375
+ // voided: Indicates a voided invoice.
376
+ // pending: The invoice is yet to be closed (sent for payment collection). An invoice is generated with this status when it has line items that belong to items that are metered or when the subscription.create_pending_invoicesattribute is set to true.
377
+
378
+ if (['in_trial', 'active'].includes(resource.status)) {
379
+ resolved.status = 'active';
380
+
381
+ // If there's a due invoice, it's suspended
382
+ if (resource.total_dues > 0) {
383
+ resolved.status = 'suspended';
384
+ }
385
+ } else if (['paused'].includes(resource.status)) {
386
+ resolved.status = 'suspended';
387
+ } else {
388
+ resolved.status = 'cancelled';
389
+ }
390
+
391
+ // Set resource ID
392
+ resolved.resource.id = resource.id;
393
+
394
+ // Set start
395
+ resolved.start.timestamp = moment(
396
+ (
397
+ // Order
398
+ get(resource, 'date', 0)
399
+
400
+ // Subscription
401
+ || get(resource, 'created_at', 0)
402
+ ) * 1000
403
+ )
404
+
405
+ // Set expiration
406
+ resolved.expires.timestamp = moment(
407
+ (
408
+ // Order
409
+ get(resource, 'date', 0)
410
+
411
+ // Subscription
412
+ || get(resource, 'current_term_start', 0)
413
+ ) * 1000
414
+ )
415
+ // console.log('---resolved.expires 1', resolved.expires);
416
+ // if (resource.total_dues > 0) {
417
+ // resolved.expires.timestamp = moment(0);
418
+ // } else {
419
+ // resolved.expires.timestamp = moment(
420
+ // (
421
+ // get(resource, 'current_term_start', 0)
422
+ // ) * 1000
423
+ // )
424
+ // }
425
+
426
+ // Set cancelled
427
+ if (resolved.status === 'cancelled') {
428
+ resolved.cancelled.timestamp = moment(
429
+ (
430
+ // Order
431
+ get(resource, 'date', 0)
432
+
433
+ // Subscription
434
+ || get(resource, 'cancelled_at', 0)
435
+ ) * 1000
436
+ )
437
+ }
438
+
439
+ // Set last payment
440
+ if (
441
+ // Order
442
+ resource.amount_due > 0
443
+
444
+ // Subscription
445
+ || resource.total_dues > 0
446
+ ) {
447
+ resolved.lastPayment.amount = 0;
448
+ resolved.lastPayment.date.timestamp = moment(
449
+ (
450
+ // Order
451
+ (resource.date || 0)
452
+
453
+ // Subscription
454
+ || (resource.due_since || 0)
455
+ ) * 1000
456
+ );
457
+ } else {
458
+ resolved.lastPayment.amount = (
459
+ (
460
+ // Order
461
+ (resource.amount_paid)
462
+
463
+ // Order
464
+ || (resource.plan_amount)
465
+ ) / 100
466
+ )
467
+ resolved.lastPayment.date.timestamp = moment(
468
+ (
469
+ // Order
470
+ (resource.date || 0)
471
+
472
+ // Subscription
473
+ || (resource.current_term_start || 0)
474
+ ) * 1000
475
+ );
476
+ }
477
+
478
+ // Get trial
479
+ if (resource.status === 'in_trial') {
480
+ resolved.trial.active = true;
481
+
482
+ // Set expiration
483
+ resolved.expires.timestamp = moment(
484
+ (
485
+ get(resource, 'trial_end', 0)
486
+ ) * 1000
487
+ )
488
+ }
489
+
490
+ // Resolve frequency
491
+ const unit = get(resource, 'billing_period_unit');
492
+ if (unit === 'year') {
493
+ resolved.frequency = 'annually';
494
+ } else if (unit === 'month') {
495
+ resolved.frequency = 'monthly';
496
+ } else if (unit === 'week') {
497
+ resolved.frequency = 'weekly';
498
+ } else if (unit === 'day') {
499
+ resolved.frequency = 'daily';
500
+ }
501
+
502
+ // Set completed
503
+ if (profile.type === 'order') {
504
+ resolved.payment.completed = !['posted', 'payment_due', 'not_paid', 'voided', 'pending'].includes(resource.status);
505
+ } else {
506
+ resolved.payment.completed = !['future'].includes(resource.status);
507
+ }
508
+
509
+ // Special chargebee reset lastPayment
510
+ // If trial is active OR if it was cancelled after the trial has ended
511
+ const trialEnd = get(resource, 'trial_end', 0);
512
+ const cancelledAt = get(resource, 'cancelled_at', 0);
513
+ if (
514
+ resolved.trial.active
515
+ || (trialEnd > 0 && cancelledAt > 0 && cancelledAt === trialEnd)
516
+ ) {
517
+ resolved.lastPayment.amount = 0;
518
+ resolved.lastPayment.date.timestamp = moment(0);
519
+ }
520
+
521
+ // console.log('----expires 1', resolved.resource.id, resolved.status, resolved.frequency, resolved.trial.active, resolved.expires.timestamp.toISOString ? resolved.expires.timestamp.toISOString() : resolved.expires.timestamp);
522
+
523
+ return resolved;
524
+ }
525
+
526
+ SubscriptionResolver.prototype.resolve_stripe = function (profile, resource, resolved) {
527
+ const self = this;
528
+
529
+ // Subscription: https://stripe.com/docs/api/subscriptions/object#subscription_object-status
530
+ // incomplete
531
+ // incomplete_expired
532
+ // trialing
533
+ // active
534
+ // past_due
535
+ // canceled
536
+ // unpaid
537
+
538
+ // Charge: https://stripe.com/docs/api/payment_intents/object#payment_intent_object-status
539
+ // requires_payment_method
540
+ // requires_confirmation
541
+ // requires_action
542
+ // processing
543
+ // requires_capture
544
+ // canceled
545
+ // succeeded
546
+ // Set status
547
+ if (['trialing', 'active'].includes(resource.status)) {
548
+ resolved.status = 'active';
549
+ } else if (['past_due', 'unpaid'].includes(resource.status)) {
550
+ resolved.status = 'suspended';
551
+ } else {
552
+ resolved.status = 'cancelled';
553
+ }
554
+
555
+ // Set resource ID
556
+ resolved.resource.id = resource.id;
557
+
558
+ // Set start
559
+ resolved.start.timestamp = moment(
560
+ (
561
+ // Order
562
+ get(resource, 'created', 0)
563
+
564
+ // Subscription
565
+ || get(resource, 'start_date', 0)
566
+ ) * 1000
567
+ );
568
+
569
+ // Set expiration
570
+ resolved.expires.timestamp = moment(
571
+ (
572
+ // Order
573
+ get(resource, 'created', 0)
574
+
575
+ // Subscription
576
+ || get(resource, 'current_period_start', 0)
577
+ ) * 1000
578
+ );
579
+
580
+ // Set cancelled
581
+ if (resolved.status === 'cancelled') {
582
+ resolved.cancelled.timestamp = moment(
583
+ (
584
+ // Order
585
+ get(resource, 'created', 0)
586
+
587
+ // Subscription
588
+ || get(resource, 'canceled_at', 0)
589
+ ) * 1000
590
+ )
591
+ }
592
+
593
+ // Set last payment
594
+ // TODO: check if suspended payments are handled correctly when using resource.latest_invoice.amount_paid
595
+ const order = resource.object === 'charge' ? resource : null;
596
+ const subscription = get(resource, 'latest_invoice');
597
+ if (order) {
598
+ resolved.lastPayment.amount = order.amount_captured / 100;
599
+ resolved.lastPayment.date.timestamp = moment(
600
+ (order.created || 0) * 1000
601
+ );
602
+ } else if (subscription) {
603
+ resolved.lastPayment.amount = subscription.amount_paid / 100;
604
+ resolved.lastPayment.date.timestamp = moment(
605
+ (subscription.created || 0) * 1000
606
+ );
607
+ }
608
+
609
+ // Get trial
610
+ if (resource.status === 'trialing') {
611
+ resolved.trial.active = true;
612
+
613
+ // Set expiration
614
+ resolved.expires.timestamp = moment(
615
+ (
616
+ get(resource, 'trial_end', 0)
617
+ ) * 1000
618
+ )
619
+ }
620
+
621
+ // Resolve frequency
622
+ const unit = get(resource, 'plan.interval');
623
+ if (unit === 'year') {
624
+ resolved.frequency = 'annually';
625
+ } else if (unit === 'month') {
626
+ resolved.frequency = 'monthly';
627
+ } else if (unit === 'week') {
628
+ resolved.frequency = 'weekly';
629
+ } else if (unit === 'day') {
630
+ resolved.frequency = 'daily';
631
+ }
632
+
633
+ // Set completed
634
+ if (resource.object === 'charge') {
635
+ resolved.payment.completed = !['requires_payment_method', 'requires_confirmation', 'requires_action', 'processing', 'requires_capture', 'canceled'].includes(resource.status);
636
+ } else {
637
+ resolved.payment.completed = !['incomplete', 'incomplete_expired'].includes(resource.status);
638
+ }
639
+
640
+ return resolved;
641
+ }
642
+
643
+ SubscriptionResolver.prototype.resolve_coinbase = function (profile, resource, resolved) {
644
+ const self = this;
645
+
646
+ // Set status
647
+ resolved.status = 'cancelled';
648
+
649
+ // Set resource ID
650
+ resolved.resource.id = resource.id;
651
+
652
+ // Set start
653
+ resolved.start.timestamp = moment(
654
+ get(resource, 'created_at', 0)
655
+ );
656
+
657
+ // Set expiration
658
+ resolved.expires.timestamp = moment(
659
+ get(resource, 'created_at', 0)
660
+ );
661
+
662
+ // Set cancelled
663
+ resolved.cancelled.timestamp = moment(
664
+ get(resource, 'created_at', 0)
665
+ )
666
+
667
+ // Retrieve last payment
668
+ const lastPayment = resource.payments.find(p => p.status === 'CONFIRMED');
669
+
670
+ // Set last payment
671
+ if (lastPayment) {
672
+ resolved.lastPayment.amount = parseFloat(lastPayment.value.local.amount);
673
+ resolved.lastPayment.date.timestamp = moment(lastPayment.detected_at);
674
+ }
675
+
676
+ // Get trial
677
+ if (true) {
678
+ resolved.trial.active = false;
679
+ }
680
+
681
+ // Resolve frequency
682
+ const unit = profile.details.planFrequency;
683
+ if (unit) {
684
+ resolved.frequency = unit;
685
+ } else {
686
+ resolved.frequency = 'single';
687
+ }
688
+
689
+ // Set completed
690
+ if (true) {
691
+ resolved.payment.completed = !!lastPayment;
692
+ }
693
+
694
+ return resolved;
695
+ }
696
+
476
697
  module.exports = SubscriptionResolver;